901530ae41
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
import { ref, type Ref } from 'vue';
|
|
|
|
export interface DevIndicesApi {
|
|
currentTarget: Ref<HTMLElement | null>;
|
|
currentId: Ref<number | null>;
|
|
overlayMode: Ref<boolean>;
|
|
hoverEnabled: Ref<boolean>;
|
|
setTarget(el: HTMLElement | null): void;
|
|
toggleOverlay(): void;
|
|
walkToParent(): void;
|
|
walkToChild(): void;
|
|
pauseHover(ms: number): void;
|
|
reset(): void;
|
|
}
|
|
|
|
// Module-level singleton state — shared across all consumers
|
|
const currentTarget = ref<HTMLElement | null>(null);
|
|
const currentId = ref<number | null>(null);
|
|
const overlayMode = ref(false);
|
|
const hoverEnabled = ref(true);
|
|
let pauseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
function parseId(el: HTMLElement | null): number | null {
|
|
if (!el) return null;
|
|
const raw = el.getAttribute('data-dx');
|
|
if (raw == null) return null;
|
|
const n = Number(raw);
|
|
return Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
function setTarget(el: HTMLElement | null): void {
|
|
if (el == null) {
|
|
currentTarget.value = null;
|
|
currentId.value = null;
|
|
return;
|
|
}
|
|
const id = parseId(el);
|
|
if (id == null) return;
|
|
currentTarget.value = el;
|
|
currentId.value = id;
|
|
}
|
|
|
|
function toggleOverlay(): void {
|
|
overlayMode.value = !overlayMode.value;
|
|
}
|
|
|
|
function findAncestorWithDx(el: HTMLElement | null): HTMLElement | null {
|
|
let cur: HTMLElement | null = el?.parentElement ?? null;
|
|
while (cur) {
|
|
if (cur.hasAttribute('data-dx')) return cur;
|
|
cur = cur.parentElement;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findFirstDescendantWithDx(el: HTMLElement | null): HTMLElement | null {
|
|
if (!el) return null;
|
|
// BFS: find first descendant with data-dx
|
|
const queue: HTMLElement[] = Array.from(el.children) as HTMLElement[];
|
|
while (queue.length) {
|
|
const cur = queue.shift()!;
|
|
if (cur.hasAttribute('data-dx')) return cur;
|
|
queue.push(...(Array.from(cur.children) as HTMLElement[]));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function walkToParent(): void {
|
|
const parent = findAncestorWithDx(currentTarget.value);
|
|
if (parent) setTarget(parent);
|
|
}
|
|
|
|
function walkToChild(): void {
|
|
const child = findFirstDescendantWithDx(currentTarget.value);
|
|
if (child) setTarget(child);
|
|
}
|
|
|
|
function pauseHover(ms: number): void {
|
|
hoverEnabled.value = false;
|
|
if (pauseTimer) clearTimeout(pauseTimer);
|
|
pauseTimer = setTimeout(() => {
|
|
hoverEnabled.value = true;
|
|
pauseTimer = null;
|
|
}, ms);
|
|
}
|
|
|
|
function reset(): void {
|
|
currentTarget.value = null;
|
|
currentId.value = null;
|
|
overlayMode.value = false;
|
|
hoverEnabled.value = true;
|
|
if (pauseTimer) {
|
|
clearTimeout(pauseTimer);
|
|
pauseTimer = null;
|
|
}
|
|
}
|
|
|
|
export function useDevIndices(): DevIndicesApi {
|
|
return {
|
|
currentTarget,
|
|
currentId,
|
|
overlayMode,
|
|
hoverEnabled,
|
|
setTarget,
|
|
toggleOverlay,
|
|
walkToParent,
|
|
walkToChild,
|
|
pauseHover,
|
|
reset,
|
|
};
|
|
}
|