mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Read the section index only when the groups change, and keep its label on screen
Live counts repaint the rows many times a second; the index now reads its headings at most once a frame, and only when a heading changes. The label goes before the tick when there is no room after it, as in a right-to-left language, and the ticks sit at the panel's inline end. Co-authored-by: Manjunath Paparaju <[email protected]>
This commit is contained in:
1 parent
919ea8d4c0
commit
f940484498
2 files changed
+44
-5
No files matched your search
@@ -6,7 +6,7 @@
|
||||
.layer-section-index {
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
right: 2px;
|
||||
inset-inline-end: 2px;
|
||||
bottom: 24px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
@@ -80,6 +80,11 @@
|
||||
animation: layer-section-index-label 120ms ease both;
|
||||
}
|
||||
|
||||
/* Drawn before the tick when there is no room after it. */
|
||||
.layer-section-index__label--before {
|
||||
transform: translate(-100%, -50%);
|
||||
}
|
||||
|
||||
@keyframes layer-section-index-label {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -17,9 +17,33 @@ function headings() {
|
||||
return list ? [...list.querySelectorAll('.data-layer-group-heading:not(.is-filtered)')] : [];
|
||||
}
|
||||
function readSections() {
|
||||
sections.value = headings().map((h, i) => ({ i, label: h.textContent.trim() }));
|
||||
const next = headings().map((h, i) => ({ i, label: h.textContent.trim() }));
|
||||
// Live counts repaint the rows all the time; keep the ticks unless the
|
||||
// groups themselves changed.
|
||||
const same =
|
||||
next.length === sections.value.length &&
|
||||
next.every((s, i) => s.label === sections.value[i].label);
|
||||
if (!same) sections.value = next;
|
||||
syncCurrent();
|
||||
}
|
||||
// Only headings matter: a heading added, removed, renamed (a language change)
|
||||
// or filtered out. Rows' counts and states change many times a second.
|
||||
const isHeading = (node) =>
|
||||
node?.classList?.contains('data-layer-group-heading') ||
|
||||
node?.parentElement?.classList?.contains('data-layer-group-heading');
|
||||
let frame = 0;
|
||||
function onMutations(mutations) {
|
||||
if (frame) return;
|
||||
const relevant = mutations.some(
|
||||
(m) =>
|
||||
(m.type === 'childList' && m.target === list) || isHeading(m.target),
|
||||
);
|
||||
if (!relevant) return;
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = 0;
|
||||
readSections();
|
||||
});
|
||||
}
|
||||
function syncCurrent() {
|
||||
if (!list) return;
|
||||
const top = list.getBoundingClientRect().top + 8;
|
||||
@@ -38,7 +62,15 @@ function syncCurrent() {
|
||||
const tip = ref(null);
|
||||
function showTip(event, section) {
|
||||
const box = event.currentTarget.getBoundingClientRect();
|
||||
tip.value = { label: section.label, left: `${Math.round(box.right + 10)}px`, top: `${Math.round(box.top + box.height / 2)}px` };
|
||||
// Beside the tick on whichever side has room: the stack sits on the right
|
||||
// in a right-to-left language.
|
||||
const before = window.innerWidth - box.right < 180;
|
||||
tip.value = {
|
||||
label: section.label,
|
||||
before,
|
||||
left: `${Math.round(before ? box.left - 10 : box.right + 10)}px`,
|
||||
top: `${Math.round(box.top + box.height / 2)}px`,
|
||||
};
|
||||
}
|
||||
const hideTip = () => {
|
||||
tip.value = null;
|
||||
@@ -52,7 +84,7 @@ function jump(i) {
|
||||
onMounted(() => {
|
||||
list = document.getElementById('data-toggles');
|
||||
if (!list) return;
|
||||
observer = new MutationObserver(readSections);
|
||||
observer = new MutationObserver(onMutations);
|
||||
observer.observe(list, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class'] });
|
||||
list.addEventListener('scroll', syncCurrent, { passive: true });
|
||||
// The list is hidden while the panel is shut; read again when it opens.
|
||||
@@ -63,6 +95,7 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
observer?.disconnect();
|
||||
resize?.disconnect();
|
||||
cancelAnimationFrame(frame);
|
||||
list?.removeEventListener('scroll', syncCurrent);
|
||||
});
|
||||
</script>
|
||||
@@ -84,7 +117,8 @@ onBeforeUnmount(() => {
|
||||
@blur="hideTip"
|
||||
></button>
|
||||
<Teleport to="body">
|
||||
<span v-if="tip" class="layer-section-index__label" :style="{ left: tip.left, top: tip.top }" aria-hidden="true">{{ tip.label }}</span>
|
||||
<span v-if="tip" class="layer-section-index__label"
|
||||
:class="{ 'layer-section-index__label--before': tip.before }" :style="{ left: tip.left, top: tip.top }" aria-hidden="true">{{ tip.label }}</span>
|
||||
</Teleport>
|
||||
</nav>
|
||||
</template>
|
||||
Reference in new issue
Block a user