help-layer 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/dom-builder.js", "../src/floating.js", "../src/geometry.js", "../src/blocking-layer.js", "../src/config.js", "../src/overlap.js", "../src/markers.js", "../src/safe.js", "../src/observer.js", "../src/matcher.js", "../src/popup.js", "../src/state.js", "../src/style.js", "../src/toggle.js", "../src/index.js"],
4
- "sourcesContent": ["/**\n * Factory functions that create the DOM elements for markers, popups, and the blocking layer.\n * Event wiring and positioning are not done here (that is the caller's responsibility).\n *\n * Accessibility:\n * - Markers are <button> elements so they are focusable and can be activated with Enter/Space.\n * - The popup uses role=\"dialog\" + aria-labelledby (the title element) to describe itself to assistive tech.\n */\n\nconst POPUP_TITLE_ID = 'help-layer-popup-title';\n\nexport function createBlockingLayer() {\n const layer = document.createElement('div');\n layer.className = 'help-layer-blocking-layer';\n return layer;\n}\n\n/**\n * @param {string} title description title used for the assistive-tech label\n * @param {string} [label] character shown on the marker (default '?'). Visual only; does not affect the aria-label.\n */\nexport function createMarker(title, label = '?') {\n const marker = document.createElement('button');\n marker.type = 'button';\n marker.className = 'help-layer-marker';\n marker.textContent = label;\n marker.setAttribute('aria-label', `Help: ${title}`);\n return marker;\n}\n\n/**\n * Create the single popup shared across the whole library.\n * Also returns references to titleEl/textEl (used to update the content) and the close button closeEl.\n */\nexport function createPopup() {\n const root = document.createElement('div');\n root.className = 'help-layer-popup';\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-labelledby', POPUP_TITLE_ID);\n root.tabIndex = -1;\n\n const titleEl = document.createElement('div');\n titleEl.className = 'help-layer-popup__title';\n titleEl.id = POPUP_TITLE_ID;\n\n const textEl = document.createElement('div');\n textEl.className = 'help-layer-popup__text';\n\n // Explicit close affordance. Wiring the click is popup.js's job (only element creation here).\n const closeEl = document.createElement('button');\n closeEl.type = 'button';\n closeEl.className = 'help-layer-popup__close';\n closeEl.textContent = '\u00D7';\n closeEl.setAttribute('aria-label', 'Close');\n\n root.append(titleEl, textEl, closeEl);\n\n return { root, titleEl, textEl, closeEl };\n}\n", "/**\n * A thin wrapper around Floating UI (@floating-ui/dom).\n * Use of Floating UI is confined to this one file; other modules only call the\n * purpose-specific functions (anchorMarker / anchorPopup / watchReference /\n * makeVirtualElement). That way, if Floating UI is ever swapped out, the blast\n * radius stays limited to here.\n *\n * Floating UI in a nutshell (note for readers unfamiliar with the DOM):\n * - computePosition(reference, floating, options) computes the optimal placement\n * coordinates (x,y) for \"this exact moment\" and returns them (one-shot).\n * - autoUpdate(reference, floating, update) watches scroll, resize, and element-size\n * changes (internally using ResizeObserver, etc.) and calls update on every change.\n * Calling the returned cleanup stops watching. This is what makes the element \"stick\"\n * to its target.\n */\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nimport { docRectToViewportRect } from './geometry.js';\n\n/**\n * Create a \"virtual reference element\" for free-placement items not bound to an element.\n * When getDocRect() returns document coordinates, this converts them to viewport\n * coordinates according to the current scroll. Because autoUpdate re-evaluates on every\n * scroll, the element sticks to the given coordinate while scrolling along with the page.\n * @param {() => {top:number,left:number,width?:number,height?:number}} getDocRect\n */\nexport function makeVirtualElement(getDocRect) {\n return {\n // Tell autoUpdate that this element's ancestor is body (= scroll is watched up to window).\n // Without this the virtual element isn't scroll-watched and won't follow page scroll.\n contextElement: document.body,\n getBoundingClientRect() {\n return docRectToViewportRect(getDocRect(), { x: window.scrollX, y: window.scrollY });\n },\n };\n}\n\nfunction place(el, x, y) {\n el.style.left = `${x}px`;\n el.style.top = `${y}px`;\n}\n\n/**\n * Whether the reference lives in a `position: fixed` subtree. Such a reference stays put in the\n * viewport while the page scrolls, so an absolutely-positioned floating element (which scrolls with\n * the document) would have to be re-corrected every frame and visibly jitters. For these we switch the\n * floating element to Floating UI's `fixed` strategy (and position:fixed) so both live in the same\n * viewport space and stay glued without per-frame correction.\n *\n * Virtual elements (free placements) aren't in the DOM and already track scroll via their getRect, so\n * they report false. Walks across shadow boundaries via the host so Shadow DOM targets are handled too.\n * @param {Element|object} reference\n */\nexport function isFixedReference(reference) {\n if (!(reference instanceof Element)) {\n return false;\n }\n let node = reference;\n while (node) {\n if (getComputedStyle(node).position === 'fixed') {\n return true;\n }\n const parent = node.parentElement;\n if (parent) {\n node = parent;\n } else {\n const root = node.getRootNode();\n node = root instanceof ShadowRoot ? root.host : null;\n }\n }\n return false;\n}\n\n// Half of the default marker size (22px). The amount used to overlap the marker onto the\n// target's corner with an \"inset\". (If the marker-size CSS variable is changed, the resulting\n// drift is left as existing behavior = not compensated for here.)\nconst MARKER_INSET = 11;\n\n/**\n * Derive the offset for overlapping the marker onto the target's corner from the placement.\n * mainAxis is always negative (bites inward past the target's edge). crossAxis flips sign by\n * alignment direction: `-end` (right/bottom-aligned) is negative to go inward, `-start`\n * (left/top-aligned) is positive to go inward.\n * @param {string} placement\n */\nfunction markerOffset(placement) {\n const isStart = placement.endsWith('-start');\n return { mainAxis: -MARKER_INSET, crossAxis: isStart ? MARKER_INSET : -MARKER_INSET };\n}\n\n/**\n * Overlap the marker onto a corner of the target (element or virtual element), stick it there, and keep it following.\n * @param {Element|object} reference\n * @param {HTMLElement} markerEl\n * @param {() => void} [onPlaced] called every time placement is finalized (used to trigger the overlap-avoidance pass, etc.)\n * @param {import('@floating-ui/dom').Placement} [placement] corner to overlap (top-end/top-start/bottom-end/bottom-start). Default 'top-end'\n * @returns {() => void} cleanup\n */\nexport function anchorMarker(reference, markerEl, onPlaced, placement = 'top-end') {\n // Match the floating element's strategy to the reference: a fixed reference needs a fixed marker, or\n // it jitters while scrolling (see isFixedReference). Inline !important beats the stylesheet's\n // `position: absolute !important`.\n const strategy = isFixedReference(reference) ? 'fixed' : 'absolute';\n if (strategy === 'fixed') {\n markerEl.style.setProperty('position', 'fixed', 'important');\n }\n const update = () => {\n computePosition(reference, markerEl, {\n placement,\n strategy,\n middleware: [offset(markerOffset(placement))],\n }).then(({ x, y }) => {\n place(markerEl, x, y);\n if (onPlaced) {\n onPlaced();\n }\n // Swallow silently: this runs every animation frame, so logging would flood the console, and a\n // stray rejection must not surface in the host app's unhandledrejection handler (e.g. Sentry).\n }).catch(() => {});\n };\n // animationFrame: true syncs repositioning to the rAF loop. With the default (scroll/resize\n // events only), computePosition resolves asynchronously, so left/top is written the frame after\n // the browser already painted the scroll \u2014 the marker lags a frame and visibly jitters.\n return autoUpdate(reference, markerEl, update, { animationFrame: true });\n}\n\n/**\n * Place the popup below the target, and at screen edges use flip (flip to the opposite side) /\n * shift (nudge) to avoid clipping. Only follows while visible.\n * @param {Element|object} reference\n * @param {HTMLElement} popupEl\n * @param {import('@floating-ui/dom').Placement} [placement] initial placement (Floating UI placement). Default 'bottom-start'\n * @returns {{ update: () => void, cleanup: () => void }}\n * calling update repositions immediately (used for reference-side transform moves that autoUpdate doesn't pick up, etc.).\n */\nexport function anchorPopup(reference, popupEl, placement = 'bottom-start') {\n // The reference is the clicked marker. If it's fixed (anchored to a fixed target), the popup must be\n // fixed too or it jitters on scroll. Set position every open so reopening on a normal marker restores\n // absolute. Inline !important beats the stylesheet's `position: absolute !important`.\n const strategy = isFixedReference(reference) ? 'fixed' : 'absolute';\n popupEl.style.setProperty('position', strategy, 'important');\n const update = () => {\n computePosition(reference, popupEl, {\n placement,\n strategy,\n middleware: [offset(8), flip({ padding: 8 }), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n place(popupEl, x, y);\n // Same rationale as anchorMarker: swallow per-frame rejections so they don't reach the host.\n }).catch(() => {});\n };\n // animationFrame: true for the same smooth-tracking reason as anchorMarker. The reference here is\n // the marker element, which itself moves per frame, so the popup must track per frame to stay glued.\n const cleanup = autoUpdate(reference, popupEl, update, { animationFrame: true });\n return { update, cleanup };\n}\n\n/**\n * Watch a reference element's position/size changes and call onUpdate on every change.\n * (Used for non-placement purposes, e.g. keeping the blocking layer's clip-path hole following the toggle position.)\n * autoUpdate requires a floating element, so floatingEl is just passed as a dummy that\n * onUpdate doesn't actually position.\n * @param {Element} referenceEl\n * @param {HTMLElement} floatingEl\n * @param {() => void} onUpdate\n * @returns {() => void} cleanup\n */\nexport function watchReference(referenceEl, floatingEl, onUpdate) {\n return autoUpdate(referenceEl, floatingEl, onUpdate);\n}\n", "/**\n * Pure geometry calculations. Takes no DOM elements, only numbers already read off.\n *\n * Clamping things that overflow the viewport is handled by Floating UI's shift()\n * middleware. toDocumentPosition is used for the virtual-element math of free placement, etc.\n */\n\n/**\n * Given getBoundingClientRect() values (viewport-relative) and the scroll offset,\n * compute coordinates relative to the whole document.\n */\nexport function toDocumentPosition(rect, scroll) {\n return {\n top: rect.top + scroll.y,\n left: rect.left + scroll.x,\n };\n}\n\n/**\n * Convert a document-coordinate rect into a viewport-coordinate rect by subtracting\n * the current scroll offset. This is what the getBoundingClientRect of a Floating UI\n * virtual reference element (a free-placement marker) returns.\n * @param {{top:number,left:number,width?:number,height?:number}} docRect\n * @param {{x:number,y:number}} scroll\n */\nexport function docRectToViewportRect(docRect, scroll) {\n const width = docRect.width || 0;\n const height = docRect.height || 0;\n const left = docRect.left - scroll.x;\n const top = docRect.top - scroll.y;\n return {\n x: left,\n y: top,\n left,\n top,\n right: left + width,\n bottom: top + height,\n width,\n height,\n };\n}\n", "/**\n * Transparent interaction-blocking layer.\n *\n * Without touching the original app's event listeners at all, it keeps interactions from getting through. How it works:\n *\n * 1. Let the toggle show through via a clip-path \"hole\":\n * Set a clip-path polygon on the full-screen layer made of the outer rectangle + the toggle\n * rectangle (the hole). Inside the hole the layer isn't painted and hit-testing passes through,\n * so the toggle can be clicked natively without touching z-index at all. Unlike approaches that\n * shuffle z-index, this doesn't break depending on ancestor stacking contexts.\n * The hole is updated via autoUpdate to follow the toggle's scroll/resize.\n *\n * 2. Focus containment:\n * On ON, blur activeElement, and via focusin (capture) detect focus moving to anything other\n * than the library UI and pull it back to the toggle. Host listeners are not detached.\n *\n * 3. Key-input suppression:\n * Capture keydown/keyup in document's capture phase; for anything outside the library UI,\n * stopPropagation+preventDefault. Escape has dedicated handling (close popup / exit mode).\n * Inside the library UI (marker/popup/toggle), normal interactions like Tab are allowed.\n */\nimport { createBlockingLayer } from './dom-builder.js';\nimport { watchReference } from './floating.js';\n\nfunction buildClipPath(rect) {\n const x1 = rect.left;\n const y1 = rect.top;\n const x2 = rect.right;\n const y2 = rect.bottom;\n // A single stroke: outer ring (clockwise) -> bridge -> toggle rectangle (counter-clockwise).\n // Under the nonzero winding rule, making the inner ring wind opposite to the outer one punches a hole.\n return `polygon(\n 0px 0px, 100% 0px, 100% 100%, 0px 100%, 0px 0px,\n ${x1}px ${y1}px, ${x1}px ${y2}px, ${x2}px ${y2}px, ${x2}px ${y1}px, ${x1}px ${y1}px\n )`;\n}\n\nexport function activateBlockingLayer(state, {\n toggleEl,\n onBackgroundClick,\n isLibraryElement,\n onEscape,\n}) {\n const layer = createBlockingLayer();\n document.body.appendChild(layer);\n state.track(() => layer.remove());\n\n // --- 1. Keep the clip-path hole following the toggle position ---\n // If there's no toggle element (programmatic control only), keeping the whole surface blocked with no hole is correct.\n if (toggleEl) {\n const updateClip = () => {\n layer.style.clipPath = buildClipPath(toggleEl.getBoundingClientRect());\n };\n const cleanupClipWatch = watchReference(toggleEl, layer, updateClip);\n state.track(cleanupClipWatch);\n }\n\n // Clicking the background (the layer itself) closes the popup\n if (onBackgroundClick) {\n layer.addEventListener('click', onBackgroundClick);\n state.track(() => layer.removeEventListener('click', onBackgroundClick));\n }\n\n // --- 2. Focus containment ---\n // Turning ON happens via a click on the toggle, so don't blur if the active element is the toggle\n // itself (blurring would leave focus floating). For any other host element, make it let go.\n const activeEl = document.activeElement;\n if (\n activeEl instanceof HTMLElement &&\n activeEl !== document.body &&\n activeEl !== toggleEl\n ) {\n activeEl.blur();\n }\n\n const handleFocusIn = (event) => {\n if (isLibraryElement(event.target)) {\n return;\n }\n // If focus tries to move to a host element, take it back.\n // To the toggle if there is one; otherwise blur that element so it isn't handed to the host.\n event.stopPropagation();\n if (toggleEl) {\n toggleEl.focus({ preventScroll: true });\n } else if (event.target instanceof HTMLElement) {\n event.target.blur();\n }\n };\n document.addEventListener('focusin', handleFocusIn, true);\n state.track(() => document.removeEventListener('focusin', handleFocusIn, true));\n\n // --- 3. Key-input suppression + Escape ---\n // Shared logic that doesn't pass key input destined outside the library UI through to the host.\n const blockNonLibrary = (event) => {\n if (isLibraryElement(event.target)) {\n return;\n }\n event.stopPropagation();\n event.preventDefault();\n };\n // keyup / keypress: don't leak to the host, Escape included (Escape's real handling is on the keydown side).\n const blockKey = (event) => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n event.preventDefault();\n return;\n }\n blockNonLibrary(event);\n };\n const handleKeydown = (event) => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n event.preventDefault();\n if (onEscape) {\n onEscape();\n }\n return;\n }\n blockNonLibrary(event);\n };\n document.addEventListener('keydown', handleKeydown, true);\n document.addEventListener('keyup', blockKey, true);\n document.addEventListener('keypress', blockKey, true);\n state.track(() => {\n document.removeEventListener('keydown', handleKeydown, true);\n document.removeEventListener('keyup', blockKey, true);\n document.removeEventListener('keypress', blockKey, true);\n });\n\n return layer;\n}\n", "/**\n * Validation and normalization of the helpConfig object. A pure function that does no DOM work.\n */\n\n/**\n * @typedef {object} HelpEntry\n * @property {string} title heading shown on the marker / popup (non-empty)\n * @property {string} text description body (non-empty)\n * @property {{ top: number, left: number }} [position] if given, becomes a free placement not tied to an element\n */\n\n/**\n * The helpConfig itself. For element-bound entries the key is the `data-help-id` value;\n * for free placement it is any identifier.\n * @typedef {Object<string, HelpEntry>} HelpConfig\n */\n\nexport function isPlainObject(value) {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isValidPosition(position) {\n // Number.isFinite rejects NaN / Infinity / non-numbers, so a computed coordinate that became NaN\n // fails validation here instead of silently pinning the marker to 0,0 at render time.\n return (\n isPlainObject(position) &&\n Number.isFinite(position.top) &&\n Number.isFinite(position.left)\n );\n}\n\n/**\n * Validate the shape of helpConfig. Throws an Error on any problem (fail fast).\n */\nexport function validateConfig(config) {\n if (!isPlainObject(config)) {\n throw new Error('helpConfig must be a plain object');\n }\n\n for (const [key, entry] of Object.entries(config)) {\n if (!isPlainObject(entry)) {\n throw new Error(`helpConfig[\"${key}\"] must be an object`);\n }\n if (typeof entry.title !== 'string' || entry.title === '') {\n throw new Error(`helpConfig[\"${key}\"].title must be a non-empty string`);\n }\n if (typeof entry.text !== 'string' || entry.text === '') {\n throw new Error(`helpConfig[\"${key}\"].text must be a non-empty string`);\n }\n if (entry.position !== undefined && !isValidPosition(entry.position)) {\n throw new Error(`helpConfig[\"${key}\"].position must be { top: finite number, left: finite number }`);\n }\n }\n}\n\n/**\n * Convert a validated helpConfig into the shared array of \"help items\" the rendering\n * code works with. The target of kind:'element' is null at this point (DOM matching is\n * left to matcher.js).\n */\nexport function normalizeConfig(config) {\n return Object.entries(config).map(([key, entry]) => {\n if (isValidPosition(entry.position)) {\n return {\n key,\n title: entry.title,\n text: entry.text,\n kind: 'free',\n target: null,\n position: { top: entry.position.top, left: entry.position.left },\n };\n }\n\n return {\n key,\n title: entry.title,\n text: entry.text,\n kind: 'element',\n target: null,\n position: null,\n };\n });\n}\n", "/**\n * Overlap avoidance between markers (pure function).\n *\n * Takes an array of each marker's \"base position\" (the center coordinate Floating UI\n * decided on) and returns an array of extra offsets that push overlapping ones apart.\n * Touches no DOM.\n *\n * The algorithm is a simple iterative push-out (a lightweight force-based separation):\n * if two circles are closer than the minimum distance, push them apart in opposite\n * directions. Repeat a few times. Markers are small circles, so a circle-to-circle\n * distance test is enough.\n */\n\n/**\n * @param {Array<{x:number,y:number}>} centers base coordinate of each marker center\n * @param {object} [options]\n * @param {number} [options.minDistance] center-to-center distance closer than this counts as overlap\n * @param {number} [options.iterations] number of iterations\n * @returns {Array<{dx:number,dy:number}>} offset to add to each marker\n */\nexport function resolveOverlaps(centers, options = {}) {\n const minDistance = options.minDistance ?? 26;\n const iterations = options.iterations ?? 6;\n\n // Working positions (base + accumulated offset).\n const positions = centers.map((c) => ({ x: c.x, y: c.y }));\n\n for (let iter = 0; iter < iterations; iter++) {\n let moved = false;\n\n for (let i = 0; i < positions.length; i++) {\n for (let j = i + 1; j < positions.length; j++) {\n const a = positions[i];\n const b = positions[j];\n let dx = b.x - a.x;\n let dy = b.y - a.y;\n let dist = Math.hypot(dx, dy);\n\n if (dist >= minDistance) {\n continue;\n }\n\n // If the coordinates are exactly identical, separate in a deterministic direction (horizontal).\n if (dist === 0) {\n dx = 1;\n dy = 0;\n dist = 1;\n }\n\n const overlap = (minDistance - dist) / 2;\n const ux = dx / dist;\n const uy = dy / dist;\n\n a.x -= ux * overlap;\n a.y -= uy * overlap;\n b.x += ux * overlap;\n b.y += uy * overlap;\n moved = true;\n }\n }\n\n if (!moved) {\n break;\n }\n }\n\n return positions.map((p, i) => ({\n dx: p.x - centers[i].x,\n dy: p.y - centers[i].y,\n }));\n}\n", "/**\n * Marker manager.\n * Markers can be dynamically mounted/unmounted per help record (SPA dynamic-element support).\n * Each marker keeps following its target (an element, or the virtual element of a free placement)\n * via Floating UI's autoUpdate. On every finalized placement it triggers the overlap-avoidance pass,\n * debounced with rAF.\n *\n * Marker identifier (id):\n * - element-bound: the target element itself (distinguishes multiple elements with the same data-help-id)\n * - free placement: the config key string\n */\nimport { createMarker } from './dom-builder.js';\nimport { anchorMarker, makeVirtualElement } from './floating.js';\nimport { resolveOverlaps } from './overlap.js';\n\n// Temporary class added to the target element only while the marker is hovered/focused (matches the style.js definition).\nconst TARGET_HIGHLIGHT_CLASS = 'help-layer-target-highlight';\n\n/** @param {import('./matcher.js').HelpRecord} record */\nfunction referenceFor(record) {\n if (record.kind === 'free') {\n return makeVirtualElement(() => ({\n top: record.position.top,\n left: record.position.left,\n width: 0,\n height: 0,\n }));\n }\n return record.target;\n}\n\n/**\n * @param {object} state teardown registry\n * @param {object} options\n * @param {(record: import('./matcher.js').HelpRecord, markerEl: HTMLElement) => void} options.onMarkerClick\n * @param {() => void} [options.onOverlapResolved]\n * @param {string} [options.markerLabel] character shown on the marker (default '?')\n * @param {import('@floating-ui/dom').Placement} [options.markerPlacement] corner to overlap (default 'top-end')\n */\nexport function createMarkerManager(state, {\n onMarkerClick,\n onOverlapResolved,\n markerLabel = '?',\n markerPlacement = 'top-end',\n}) {\n /** @type {Map<Element|string, {record:import('./matcher.js').HelpRecord, el:HTMLElement, cleanup:() => void}>} */\n const markers = new Map();\n let rafId = null;\n // Don't schedule a new rAF during teardown (prevents a frame lingering after teardown).\n let tornDown = false;\n\n function runOverlapPass() {\n rafId = null;\n const entries = [...markers.values()];\n // With one marker or fewer, overlap is impossible. Skip getBoundingClientRect (forced reflow)\n // and the O(n^2) push-out math entirely (avoids a per-frame reflow while scrolling on screens\n // with few targets). However, right after dropping from 2 to 1, if the remaining one still has\n // a leftover push-out transform, clear it and let an open popup follow that move (in steady\n // state, with an empty transform, do nothing).\n if (entries.length <= 1) {\n const el = entries.length === 1 ? entries[0].el : null;\n if (el && el.style.transform) {\n el.style.transform = '';\n if (onOverlapResolved) {\n onOverlapResolved();\n }\n }\n return;\n }\n\n // Clear the accumulated transform to measure base centers, then reapply after resolving overlaps.\n entries.forEach((e) => { e.el.style.transform = ''; });\n const centers = entries.map((e) => {\n const r = e.el.getBoundingClientRect();\n return { x: r.left + r.width / 2, y: r.top + r.height / 2 };\n });\n const offsets = resolveOverlaps(centers);\n entries.forEach((e, i) => {\n const { dx, dy } = offsets[i];\n e.el.style.transform = (dx || dy) ? `translate(${dx}px, ${dy}px)` : '';\n });\n\n // Marker positions moved, so give an open popup etc. the chance to follow.\n if (onOverlapResolved) {\n onOverlapResolved();\n }\n }\n\n function scheduleOverlapPass() {\n if (rafId !== null || tornDown) {\n return;\n }\n rafId = requestAnimationFrame(runOverlapPass);\n }\n\n /** @param {import('./matcher.js').HelpRecord} record */\n function mount(record) {\n if (markers.has(record.id)) {\n return;\n }\n\n const el = createMarker(record.title, markerLabel);\n document.body.appendChild(el);\n\n const handleClick = () => onMarkerClick(record, el);\n el.addEventListener('click', handleClick);\n\n const cleanupAnchor = anchorMarker(referenceFor(record), el, scheduleOverlapPass, markerPlacement);\n\n // Target-element highlight (element-bound only; free placement has no target, so skip).\n // Show an outline on the target only while the marker is hovered/focused, to make clear \"which element this explains\".\n const target = record.kind === 'element' ? record.target : null;\n const addHighlight = () => target && target.classList.add(TARGET_HIGHLIGHT_CLASS);\n const removeHighlight = () => target && target.classList.remove(TARGET_HIGHLIGHT_CLASS);\n if (target) {\n el.addEventListener('mouseenter', addHighlight);\n el.addEventListener('mouseleave', removeHighlight);\n el.addEventListener('focus', addHighlight);\n el.addEventListener('blur', removeHighlight);\n }\n\n let done = false;\n const cleanup = () => {\n if (done) {\n return;\n }\n done = true;\n cleanupAnchor();\n el.removeEventListener('click', handleClick);\n if (target) {\n el.removeEventListener('mouseenter', addHighlight);\n el.removeEventListener('mouseleave', removeHighlight);\n el.removeEventListener('focus', addHighlight);\n el.removeEventListener('blur', removeHighlight);\n removeHighlight(); // don't leave the highlight on the target if unmounted while highlighted\n }\n el.remove();\n markers.delete(record.id);\n scheduleOverlapPass();\n };\n\n markers.set(record.id, { record, el, cleanup });\n }\n\n function unmount(id) {\n const entry = markers.get(id);\n if (entry) {\n entry.cleanup();\n }\n }\n\n function mountAll(records) {\n records.forEach(mount);\n }\n\n // Register a single teardown for the whole manager with state\n // (individual mount/unmount happen many times during a session, so they're bundled here).\n state.track(() => {\n tornDown = true;\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n [...markers.values()].forEach((entry) => entry.cleanup());\n });\n\n return {\n mount,\n unmount,\n mountAll,\n has(id) {\n return markers.has(id);\n },\n // Return the first entry matching the config key (either element-bound or free placement).\n // Used by the programmatic open(key).\n findByKey(key) {\n for (const entry of markers.values()) {\n if (entry.record.key === key) {\n return entry;\n }\n }\n return null;\n },\n };\n}\n", "/**\n * Isolation for user-supplied callbacks (render / onOpen / onClose / onEnable / onDisable).\n *\n * These run inside the library's own control flow (event handlers, teardown), so a throw from a\n * caller's mistake must not derail us: it could leave a popup half-open or abort a teardown midway,\n * stranding markers, observers and injected styles. We swallow the error and log it instead, so the\n * developer still sees their bug while the library keeps its internal state consistent.\n */\n\n/**\n * Invoke a user callback, never letting it throw into library code.\n * @template T\n * @param {string} label name used in the error log (e.g. 'onClose')\n * @param {((...args: any[]) => T)|undefined|null} fn the callback, or nullish to skip\n * @param {...any} args arguments forwarded to fn\n * @returns {T|undefined} fn's return value, or undefined when absent or it threw\n */\nexport function safeInvoke(label, fn, ...args) {\n if (!fn) {\n return undefined;\n }\n try {\n return fn(...args);\n } catch (err) {\n // Always logged regardless of `silent` (that flag only gates unregistered-key warnings).\n console.error(`[help-layer] ${label} threw:`, err);\n return undefined;\n }\n}\n", "/**\n * DOM observation and Shadow DOM-piercing traversal.\n *\n * A normal querySelectorAll does not cross shadow boundaries, so queryAllDeep walks open\n * shadowRoots recursively. A closed shadowRoot is unreachable from JS, so it is unsupported\n * (a known limitation).\n *\n * For SPA support, while ON a MutationObserver watches for additions/removals of data-help-id\n * elements and mounts/unmounts markers dynamically.\n */\n\nimport { safeInvoke } from './safe.js';\n\nconst ELEMENT_NODE = 1;\n\n/**\n * Internal worker that traverses everything under root (including inside open shadowRoots) once,\n * collecting both selector-matching elements and shadowRoots at the same time. It uses a single\n * `querySelectorAll('*')` pass and does both the `matches` test and shadowRoot detection within it.\n * The goal is to cut what used to be two separate full scans (\"collect matches\" and \"find shadow\n * hosts\") down to one (lightening the hot path that reacts to host DOM changes while ON).\n * @param {ParentNode} root\n * @param {string} selector\n * @param {(el: Element) => void} [onMatch]\n * @param {(shadow: ShadowRoot) => void} [onShadowRoot]\n */\nfunction walkDeep(root, selector, onMatch, onShadowRoot) {\n if (typeof root.querySelectorAll !== 'function') {\n return;\n }\n // querySelectorAll('*') does not cross shadow boundaries, so run it flatly once per root and,\n // when a shadowRoot is found, recurse into it right there (depth-first).\n root.querySelectorAll('*').forEach((el) => {\n if (onMatch && el.matches(selector)) {\n onMatch(el);\n }\n if (el.shadowRoot) {\n if (onShadowRoot) {\n onShadowRoot(el.shadowRoot);\n }\n walkDeep(el.shadowRoot, selector, onMatch, onShadowRoot);\n }\n });\n}\n\n/**\n * Collect every element under root (including inside open shadowRoots) matching selector.\n * @param {ParentNode} root\n * @param {string} selector\n * @returns {Element[]}\n */\nexport function queryAllDeep(root, selector) {\n const results = [];\n walkDeep(root, selector, (el) => results.push(el));\n return results;\n}\n\n/**\n * Collect every open shadowRoot under root (excluding root itself).\n * @param {ParentNode} root\n * @returns {ShadowRoot[]}\n */\nexport function collectShadowRoots(root) {\n const roots = [];\n walkDeep(root, '*', null, (shadow) => roots.push(shadow));\n return roots;\n}\n\n/**\n * Traverse a node and its descendants (including shadow) once, returning both selector-matching\n * elements and the descendants' open shadowRoots together. Used in MutationObserver added-node\n * handling to fold \"collect matches\" and \"add shadow observation\" into one traversal per subtree.\n * @param {Node} node\n * @param {string} selector\n * @returns {{ matches: Element[], shadowRoots: ShadowRoot[] }}\n */\nfunction scanSubtree(node, selector) {\n /** @type {Element[]} */\n const matches = [];\n /** @type {ShadowRoot[]} */\n const shadowRoots = [];\n if (node.nodeType !== ELEMENT_NODE) {\n return { matches, shadowRoots };\n }\n // node itself is not included in querySelectorAll('*'), so test it separately (equivalent to the former matchingWithin).\n const el = /** @type {Element} */ (node);\n if (typeof el.matches === 'function' && el.matches(selector)) {\n matches.push(el);\n }\n // The added node itself may be a shadow host. walkDeep only inspects the shadowRoots of\n // descendants (querySelectorAll('*') never includes the root), so handle the node's own\n // shadowRoot here before descending into the light-DOM subtree.\n if (el.shadowRoot) {\n shadowRoots.push(el.shadowRoot);\n walkDeep(el.shadowRoot, selector, (m) => matches.push(m), (shadow) => shadowRoots.push(shadow));\n }\n walkDeep(el, selector, (m) => matches.push(m), (shadow) => shadowRoots.push(shadow));\n return { matches, shadowRoots };\n}\n\n/**\n * While ON, watch root and all shadowRoots under it, notifying on entry/exit of selector-matching elements.\n * If an added element has a new shadowRoot, that shadowRoot is also added to the observation.\n *\n * @param {object} params\n * @param {ParentNode} [params.root=document]\n * @param {string} params.selector\n * @param {(el: Element) => void} params.onAdded\n * @param {(el: Element) => void} params.onRemoved\n * @returns {{ disconnect(): void }}\n */\nexport function createMutationWatcher({ root = document, selector, onAdded, onRemoved }) {\n const observed = new Set();\n\n const handle = (records) => {\n for (const record of records) {\n // For added nodes, get both \"matching elements\" and \"shadowRoots to start observing\" in one traversal per subtree.\n record.addedNodes.forEach((node) => {\n const { matches, shadowRoots } = scanSubtree(node, selector);\n // Isolate each callback: a throw on one element must not abort the rest of the batch nor\n // kill ongoing observation (which would silently stop all later SPA tracking).\n matches.forEach((el) => safeInvoke('observer onAdded', onAdded, el));\n shadowRoots.forEach(observe);\n });\n record.removedNodes.forEach((node) => {\n scanSubtree(node, selector).matches.forEach((el) => safeInvoke('observer onRemoved', onRemoved, el));\n });\n }\n };\n\n const observer = new MutationObserver(handle);\n\n function observe(target) {\n if (observed.has(target)) {\n return;\n }\n observed.add(target);\n observer.observe(target, { childList: true, subtree: true });\n }\n\n observe(root);\n collectShadowRoots(root).forEach(observe);\n\n return {\n disconnect() {\n observer.disconnect();\n observed.clear();\n },\n };\n}\n", "/**\n * Map the help-item array returned by normalizeConfig() onto actual DOM elements or free\n * placements, producing the \"help records\" the marker manager works with. Reads the DOM only;\n * never writes.\n *\n * Behavior of this module:\n * - Searches with queryAllDeep for Shadow DOM support.\n * - Emits a marker for each of multiple elements sharing the same data-help-id (correct in practice).\n * Because of that, an element-bound record uses \"the element itself\" as its id (identity).\n * - A free-placement record uses the config key as its id.\n *\n * Resolution order for title/text:\n * - First look up config by the `data-help-id` value (config always wins).\n * - If config has no match, use the element's `data-help-title` / `data-help-text` as an inline definition.\n * This lets you adopt the library with markup alone, without a config object.\n */\nimport { queryAllDeep } from './observer.js';\n\n/**\n * One marker's worth of \"help record\". Produced by matcher; consumed by markers/popup/toggle/index \u2014 the shared contract.\n * Other modules reference it via `import('./matcher.js').HelpRecord` (the same style as config.js's HelpConfig).\n * @typedef {object} HelpRecord\n * @property {Element|string} id for element-bound, the target element itself; for free placement, the config key string\n * @property {'element'|'free'} kind\n * @property {string|null} key config key (null for an inline-definition-only element)\n * @property {string} title\n * @property {string} text\n * @property {Element} [target] the target element when kind:'element'\n * @property {{top:number,left:number}} [position] the placement coordinate when kind:'free'\n */\n\n// Attribute names used for inline definitions (fixed defaults so as not to grow the API).\nexport const TITLE_ATTR = 'data-help-title';\nexport const TEXT_ATTR = 'data-help-text';\n\n/**\n * Build the selector to scan. In addition to elements with `data-help-id` (default), also pick up\n * elements that only have an inline definition (`data-help-title`).\n * A single source of truth so collectElementRecords and the MutationObserver share the same condition.\n * @param {string} [attribute] attribute name marking targets (default 'data-help-id')\n */\nexport function targetSelector(attribute = 'data-help-id') {\n return `[${attribute}], [${TITLE_ATTR}]`;\n}\n\n/** Turn element-bound items into a key->item Map. */\nexport function elementConfigMap(items) {\n const map = new Map();\n for (const item of items) {\n if (item.kind === 'element') {\n map.set(item.key, item);\n }\n }\n return map;\n}\n\n/**\n * Turn free-placement items into records.\n * @returns {HelpRecord[]}\n */\nexport function freeRecords(items) {\n return items\n .filter((item) => item.kind === 'free')\n .map((item) => ({\n id: item.key,\n kind: 'free',\n key: item.key,\n title: item.title,\n text: item.text,\n position: item.position,\n }));\n}\n\n/**\n * Build the help record for a single element. title/text prefer config; if absent, fall back to\n * the element's data-help-title / data-help-text (inline definition). If neither source yields\n * both title and text, return null (not a target).\n * (Used by both the initial scan and SPA dynamic additions.)\n * @param {string} [attribute] attribute name marking targets (default 'data-help-id')\n * @returns {HelpRecord|null}\n */\nexport function recordForElement(el, configMap, attribute = 'data-help-id') {\n const key = el.getAttribute(attribute);\n // config wins. If there's no matching key, fall back to the inline attributes.\n const item = key != null ? configMap.get(key) : undefined;\n const title = item ? item.title : el.getAttribute(TITLE_ATTR);\n const text = item ? item.text : el.getAttribute(TEXT_ATTR);\n // If both title and text aren't present, it's not a target (treated as unregistered).\n if (!title || !text) {\n return null;\n }\n return {\n id: el,\n kind: 'element',\n key,\n title,\n text,\n target: el,\n };\n}\n\n/**\n * Scan target-attribute elements under root (including Shadow DOM) and collect element-bound records.\n * Targets not in config are warned about and ignored (non-fatal). silent:true suppresses the warning.\n * @param {object[]} items\n * @param {ParentNode} [root]\n * @param {object} [options]\n * @param {boolean} [options.silent] don't warn on unregistered keys\n * @param {string} [options.attribute] attribute name marking targets (default 'data-help-id')\n * @returns {HelpRecord[]}\n */\nexport function collectElementRecords(items, root = document, { silent = false, attribute = 'data-help-id' } = {}) {\n const configMap = elementConfigMap(items);\n const records = [];\n\n queryAllDeep(root, targetSelector(attribute)).forEach((el) => {\n const record = recordForElement(el, configMap, attribute);\n if (!record) {\n if (!silent) {\n const key = el.getAttribute(attribute);\n console.warn(\n key != null\n ? `[help-layer] element with ${attribute}=\"${key}\" has no matching helpConfig entry or inline ${TITLE_ATTR}/${TEXT_ATTR}`\n : `[help-layer] element needs both ${TITLE_ATTR} and ${TEXT_ATTR} (or a ${attribute} matching helpConfig)`,\n );\n }\n return;\n }\n records.push(record);\n });\n\n return records;\n}\n", "/**\n * The single popup shared across the whole library.\n * Placed on its target (the clicked marker) with Floating UI; at screen edges, flip/shift avoid\n * clipping. While visible it follows via autoUpdate.\n *\n * Accessibility:\n * - On open, move focus to the popup (role=dialog).\n * - On close, return focus to the trigger element (the marker).\n */\nimport { createPopup } from './dom-builder.js';\nimport { anchorPopup } from './floating.js';\nimport { safeInvoke } from './safe.js';\n\n/**\n * @param {object} state teardown registry\n * @param {object} [options]\n * @param {() => void} [options.onClose] called when the popup closes (transitions from shown to hidden)\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render]\n * Escape hatch to render the body area with your own DOM node. Return a Node to display it;\n * if nothing is returned, fall back to safe text rendering (textContent). The title is always record.title.\n * Note: the return value is appendChild'd as-is without sanitization, so untrusted data must be neutralized by the caller.\n * @param {import('@floating-ui/dom').Placement} [options.popupPlacement] initial placement (default 'bottom-start')\n */\nexport function createPopupController(state, { onClose, render, popupPlacement = 'bottom-start' } = {}) {\n const { root, titleEl, textEl, closeEl } = createPopup();\n // Drive the open/close state with an inline !important display so it beats both this library's own\n // stylesheet and any host rule (e.g. div { display:none !important }). Start hidden.\n root.style.setProperty('display', 'none', 'important');\n document.body.appendChild(root);\n\n // The close (\u00D7) button. root is removed on teardown, so explicitly detaching the listener isn't needed.\n closeEl.addEventListener('click', () => close());\n\n let openId = null;\n let triggerEl = null;\n let anchor = null;\n\n function stopAnchor() {\n if (anchor) {\n anchor.cleanup();\n anchor = null;\n }\n }\n\n /**\n * @param {import('./matcher.js').HelpRecord} record\n * @param {HTMLElement} referenceEl placement reference (the clicked marker element)\n */\n function open(record, referenceEl) {\n titleEl.textContent = record.title;\n // If render exists, replace the body with a custom Node; otherwise fall back to safe text rendering.\n // A throwing render yields undefined here, so we degrade to the safe textContent path below.\n const custom = safeInvoke('render', render, record);\n textEl.textContent = '';\n if (custom) {\n textEl.appendChild(custom);\n } else {\n textEl.textContent = record.text;\n }\n root.style.setProperty('display', 'block', 'important');\n openId = record.id;\n triggerEl = referenceEl;\n\n stopAnchor();\n anchor = anchorPopup(referenceEl, root, popupPlacement);\n\n // preventScroll: the popup is positioned asynchronously (computePosition().then), so at this\n // point it's still at its stale position; a default focus would scroll toward that, causing a\n // visible jump. flip/shift keep it in the viewport, so suppressing the scroll is safe.\n root.focus({ preventScroll: true });\n }\n\n // Reposition immediately, only when open.\n // (Called e.g. right after a marker shifts due to the overlap-avoidance transform.)\n function reposition() {\n if (anchor) {\n anchor.update();\n }\n }\n\n function hide() {\n // Call onClose only if it was open (catches both the close-path and teardown-path close routes at one point).\n const wasOpen = openId !== null;\n stopAnchor();\n openId = null;\n triggerEl = null;\n root.style.setProperty('display', 'none', 'important');\n if (wasOpen) {\n safeInvoke('onClose', onClose);\n }\n }\n\n /**\n * Close and return focus.\n * @param {HTMLElement} [focusTarget] explicit focus-return target.\n * If omitted, returns to the trigger element (the marker). In contexts where the trigger\n * disappears (SPA removal), pass another surviving element such as the toggle.\n */\n function close(focusTarget) {\n const returnTo = focusTarget ?? triggerEl;\n hide();\n // Return focus if the target is still in the DOM.\n if (returnTo && returnTo.isConnected && typeof returnTo.focus === 'function') {\n returnTo.focus({ preventScroll: true });\n }\n }\n\n state.track(() => {\n hide();\n root.remove();\n });\n\n return {\n root,\n isOpen(id) {\n return openId === id;\n },\n getOpenId() {\n return openId;\n },\n open,\n close,\n reposition,\n };\n}\n", "/**\n * Registry of teardown callbacks.\n * DOM, listeners, and style changes added while the mode is ON are unwound in\n * reverse order of creation (LIFO), so that dependent cleanups (e.g. detach an\n * internal listener, then remove its element) run in a natural order.\n */\nexport function createState() {\n const cleanupFns = [];\n\n return {\n track(fn) {\n cleanupFns.push(fn);\n },\n teardownAll() {\n while (cleanupFns.length > 0) {\n const cleanup = cleanupFns.pop();\n // A throwing cleanup (e.g. a user onClose run during teardown) must not abort the rest of\n // the unwind, otherwise later-registered subsystems (markers, observer, styles) would leak.\n try {\n cleanup();\n } catch (err) {\n console.error('[help-layer] teardown step threw:', err);\n }\n }\n },\n };\n}\n", "/**\n * The z-index constants help-layer uses, and the CSS it injects.\n * Things that must sit above the blocking layer use Z_TOP (markers); the popup uses Z_POPUP so it\n * always paints in front of the markers (they share a stacking context as <body> children, so a tie\n * would otherwise be decided by DOM order and a remounted marker could cover an open popup).\n * The toggle is made visible through the clip-path \"hole\", so its z-index is left untouched.\n */\nexport const Z_BLOCKING_LAYER = 2147483000;\nexport const Z_TOP = 2147483001;\nexport const Z_POPUP = 2147483002;\n\nconst STYLE_ATTR = 'data-help-layer-style';\n\n// The theme is fully exposed via CSS custom properties. Users can change the look just by\n// overriding the following variables in host-side CSS (e.g. :root or any scope):\n// --help-layer-marker-size marker diameter (default 22px)\n// --help-layer-marker-bg marker background color (default #2563eb)\n// --help-layer-marker-color marker text color (default #fff)\n// --help-layer-popup-bg popup background color (default #fff)\n// --help-layer-popup-color popup text color (default #1f2933)\n// --help-layer-popup-max-width popup max width (default 280px)\n// --help-layer-popup-max-height body max height (default 50vh, scrolls when exceeded)\n// --help-layer-accent focus ring color (default #1d4ed8)\n// --help-layer-overlay-bg blocking-layer (scrim) background (default transparent; e.g. rgba(0,0,0,0.15))\n// --help-layer-overlay-cursor cursor over the blocked area (default default; e.g. not-allowed / help)\nconst CSS = `\n.help-layer-blocking-layer {\n /* Structural properties !important so a host can't accidentally un-fix or restack the layer and\n defeat the blocking guarantee. */\n position: fixed !important;\n inset: 0 !important;\n pointer-events: auto !important;\n /* Default transparent (unchanged). Set --help-layer-overlay-bg to tint it into a scrim that signals\n \"the host app is inactive\". The clip-path hole isn't painted, so the toggle stays untinted. */\n background: var(--help-layer-overlay-bg, transparent);\n /* Cursor over the blocked area only (the toggle shows through the hole and keeps its own cursor).\n e.g. not-allowed / help makes \"this won't respond\" obvious without needing a tint. */\n cursor: var(--help-layer-overlay-cursor, default);\n z-index: ${Z_BLOCKING_LAYER} !important;\n}\n\n.help-layer-marker {\n /* reset of the button element */\n appearance: none;\n -webkit-appearance: none;\n margin: 0;\n padding: 0;\n border: none;\n /* Structural properties are !important so a host's broad rules (e.g. button { display:none }) can't\n hide or distort the marker. top/left stay non-important because place() writes them inline per\n frame; !important there would override that and pin the marker to 0,0. Theme stays var()-driven.\n Note: for targets in a position:fixed subtree, floating.js overrides this with an inline\n position:fixed !important (inline important beats this rule) so the marker doesn't jitter. */\n position: absolute !important;\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n pointer-events: auto !important;\n top: 0;\n left: 0;\n width: var(--help-layer-marker-size, 22px) !important;\n height: var(--help-layer-marker-size, 22px) !important;\n border-radius: 50%;\n background: var(--help-layer-marker-bg, #2563eb);\n color: var(--help-layer-marker-color, #fff);\n font-family: sans-serif;\n font-size: 13px;\n font-weight: bold;\n line-height: var(--help-layer-marker-size, 22px);\n text-align: center;\n cursor: pointer;\n user-select: none;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n z-index: ${Z_TOP} !important;\n}\n\n.help-layer-marker:focus-visible {\n outline: 3px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 2px;\n}\n\n.help-layer-popup {\n /* Structural !important guards against host resets; top/left stay inline (place()), and display is\n deliberately NOT !important here \u2014 popup.js toggles it via an inline !important declaration so the\n open/close state itself can also beat a host rule without this stylesheet fighting the toggle. */\n position: absolute !important;\n visibility: visible !important;\n opacity: 1 !important;\n pointer-events: auto !important;\n top: 0;\n left: 0;\n display: none;\n max-width: var(--help-layer-popup-max-width, 280px);\n background: var(--help-layer-popup-bg, #fff);\n color: var(--help-layer-popup-color, #1f2933);\n border-radius: 6px;\n padding: 12px 14px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);\n font-family: sans-serif;\n font-size: 13px;\n line-height: 1.5;\n z-index: ${Z_POPUP} !important;\n}\n\n.help-layer-popup:focus {\n outline: none;\n}\n\n.help-layer-popup:focus-visible {\n outline: 3px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 2px;\n}\n\n.help-layer-popup__title {\n font-weight: bold;\n margin-bottom: 4px;\n /* Reserve space so it doesn't overlap the \u00D7 button at the top-right. */\n padding-right: 16px;\n}\n\n.help-layer-popup__text {\n /* Render the body's \\n as line breaks (still textContent, so no XSS risk). */\n white-space: pre-line;\n /* Keep long text from spilling off-screen; only the body scrolls within the popup. */\n max-height: var(--help-layer-popup-max-height, 50vh);\n overflow-y: auto;\n}\n\n.help-layer-popup__close {\n /* reset of the button element */\n appearance: none;\n -webkit-appearance: none;\n /* Keep the close affordance visible/placed even under host button { ... } rules. */\n display: block !important;\n position: absolute !important;\n pointer-events: auto !important;\n top: 6px;\n right: 6px;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n}\n\n.help-layer-popup__close:hover {\n background: rgba(0, 0, 0, 0.08);\n}\n\n.help-layer-popup__close:focus-visible {\n outline: 2px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 1px;\n}\n\n/*\n * Show an outline on the target element only while the marker is hovered/focused (clarifies \"which element this explains\").\n * Make only the outline !important so it can beat host-side outline resets.\n */\n.help-layer-target-highlight {\n outline: 2px solid var(--help-layer-accent, #1d4ed8) !important;\n outline-offset: 2px !important;\n}\n\n/*\n * Dark-mode defaults. If the user specifies CSS variables, those always win via var(), so here we\n * only swap the dark fallback values (the properties themselves aren't re-declared).\n */\n@media (prefers-color-scheme: dark) {\n .help-layer-popup {\n background: var(--help-layer-popup-bg, #1f2933);\n color: var(--help-layer-popup-color, #e5e7eb);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.55);\n }\n}\n`;\n\n/**\n * Inject a <style> tag into head and return that element.\n * @param {string} [nonce] nonce to allow this <style> under a strict CSP (style-src 'nonce-\u2026').\n * The nonce attribute is added only when provided. If omitted, nothing is added (as before).\n */\nexport function injectStyles(nonce) {\n const styleEl = document.createElement('style');\n styleEl.setAttribute(STYLE_ATTR, '');\n // Under a CSP running style-src 'nonce-\u2026', only a <style> with a matching nonce is applied.\n if (nonce) {\n styleEl.setAttribute('nonce', nonce);\n }\n styleEl.textContent = CSS;\n document.head.appendChild(styleEl);\n return styleEl;\n}\n\n/**\n * Remove the <style> tag injected by injectStyles().\n */\nexport function removeStyles(styleEl) {\n styleEl.remove();\n}\n", "/**\n * Orchestration of the help mode's ON/OFF.\n * Starts each subsystem (style injection, marker manager, popup, blocking layer, DOM observation)\n * and aggregates their teardown into the cleanup registry (state).\n */\nimport { activateBlockingLayer } from './blocking-layer.js';\nimport { isPlainObject, normalizeConfig, validateConfig } from './config.js';\nimport { createMarkerManager } from './markers.js';\nimport {\n collectElementRecords,\n elementConfigMap,\n freeRecords,\n recordForElement,\n targetSelector,\n} from './matcher.js';\nimport { createMutationWatcher } from './observer.js';\nimport { createPopupController } from './popup.js';\nimport { safeInvoke } from './safe.js';\nimport { createState } from './state.js';\nimport { injectStyles, removeStyles } from './style.js';\n\nfunction resolveToggleElement(toggle) {\n if (typeof toggle === 'string') {\n const el = document.querySelector(toggle);\n if (!el) {\n throw new Error(`help-layer: toggle element not found for selector \"${toggle}\"`);\n }\n return /** @type {HTMLElement} */ (el);\n }\n // Reject truthy garbage (a number, a plain object, ...) early; otherwise it would be accepted as a\n // toggle and only blow up cryptically later at toggleEl.addEventListener.\n if (toggle instanceof HTMLElement) {\n return toggle;\n }\n throw new Error('help-layer: toggle must be a CSS selector string or a DOM element');\n}\n\n/**\n * @param {object} options\n * @param {object} options.config helpConfig\n * @param {string|HTMLElement} [options.toggle] DOM element that switches ON/OFF (if omitted, programmatic control only)\n * @param {() => void} [options.onEnable] called right after the mode is turned ON\n * @param {() => void} [options.onDisable] called right after the mode is turned OFF\n * @param {(record: import('./matcher.js').HelpRecord) => void} [options.onOpen] called when a popup is opened\n * @param {() => void} [options.onClose] called when a popup is closed\n * @param {boolean} [options.silent] suppress the warning log for unregistered keys\n * @param {string} [options.attribute] attribute name marking targets (default 'data-help-id')\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render] render the popup body with your own Node\n * (the return value is inserted as-is without sanitization, so untrusted data must be neutralized by the caller)\n * @param {string} [options.markerLabel] character shown on the marker (default '?')\n * @param {import('@floating-ui/dom').Placement} [options.markerPlacement] corner to overlap the marker onto (default 'top-end')\n * @param {import('@floating-ui/dom').Placement} [options.popupPlacement] initial popup placement (default 'bottom-start')\n * @param {string} [options.nonce] nonce to allow the injected <style> under a strict CSP (style-src 'nonce-\u2026')\n */\nexport function createToggleController(options) {\n // Validate before destructuring so initHelpLayer() / initHelpLayer(null) get a clear message\n // instead of a cryptic \"Cannot destructure property 'config' of undefined\".\n if (!isPlainObject(options)) {\n throw new Error('help-layer: initHelpLayer requires an options object');\n }\n const {\n config,\n toggle,\n onEnable,\n onDisable,\n onOpen,\n onClose,\n silent = false,\n attribute = 'data-help-id',\n render,\n markerLabel = '?',\n markerPlacement = 'top-end',\n popupPlacement = 'bottom-start',\n nonce,\n } = options;\n\n let activeConfig = config;\n validateConfig(activeConfig);\n // The toggle element is optional. If omitted, it's driven solely by programmatic control like enable()/disable().\n const toggleEl = toggle != null ? resolveToggleElement(toggle) : null;\n\n let state = null;\n // References to the current subsystems that exist only while ON. Hoisted because open(key)/close() touch them too.\n let popup = null;\n let markers = null;\n\n // Only builds the side effects (onEnable/onDisable aren't called here; they fire on the enable/disable side).\n function turnOn() {\n if (state) {\n return;\n }\n state = createState();\n\n // On OFF, return focus to the toggle last (at the LIFO tail) (only when there is a toggle).\n if (toggleEl) {\n state.track(() => {\n if (toggleEl.isConnected && typeof toggleEl.focus === 'function') {\n toggleEl.focus({ preventScroll: true });\n }\n });\n }\n\n const styleEl = injectStyles(nonce);\n state.track(() => removeStyles(styleEl));\n\n const items = normalizeConfig(activeConfig);\n const configMap = elementConfigMap(items);\n\n popup = createPopupController(state, { onClose, render, popupPlacement });\n markers = createMarkerManager(state, {\n markerLabel,\n markerPlacement,\n onMarkerClick: (record, markerEl) => {\n if (popup.isOpen(record.id)) {\n popup.close();\n return;\n }\n popup.open(record, markerEl);\n safeInvoke('onOpen', onOpen, record);\n },\n // When overlap avoidance moves a marker, make the open popup follow.\n onOverlapResolved: () => popup.reposition(),\n });\n\n // Initial mount (free placements + elements currently in the DOM, including Shadow DOM)\n markers.mountAll(freeRecords(items));\n markers.mountAll(collectElementRecords(items, document, { silent, attribute }));\n\n // SPA dynamic elements: follow additions/removals while ON\n const watcher = createMutationWatcher({\n selector: targetSelector(attribute),\n onAdded: (el) => {\n const record = recordForElement(el, configMap, attribute);\n if (record && !markers.has(record.id)) {\n markers.mount(record);\n }\n },\n onRemoved: (el) => {\n // Both the target and its marker disappear, so return focus to the toggle (or the default if absent).\n if (popup.isOpen(el)) {\n popup.close(toggleEl ?? undefined);\n }\n markers.unmount(el);\n },\n });\n state.track(() => watcher.disconnect());\n\n const isLibraryElement = (target) =>\n !!target &&\n ((toggleEl ? toggleEl.contains(target) : false) ||\n popup.root.contains(target) ||\n (typeof target.closest === 'function' && !!target.closest('.help-layer-marker')));\n\n activateBlockingLayer(state, {\n toggleEl,\n onBackgroundClick: () => popup.close(),\n isLibraryElement,\n onEscape: () => {\n if (popup.getOpenId() !== null) {\n popup.close();\n } else {\n disable();\n }\n },\n });\n }\n\n function turnOff() {\n if (state) {\n state.teardownAll();\n state = null;\n popup = null;\n markers = null;\n }\n }\n\n function enable() {\n if (state) {\n return;\n }\n turnOn();\n safeInvoke('onEnable', onEnable);\n }\n\n function disable() {\n if (!state) {\n return;\n }\n turnOff();\n safeInvoke('onDisable', onDisable);\n }\n\n function toggleMode() {\n if (state) {\n disable();\n } else {\n enable();\n }\n }\n\n // Open the description for a given key programmatically. When OFF, first enable() to create the markers.\n function openByKey(key) {\n if (!state) {\n enable();\n }\n if (!markers || !popup) {\n return;\n }\n const entry = markers.findByKey(key);\n if (!entry) {\n if (!silent) {\n console.warn(`[help-layer] open(): no help marker for key \"${key}\"`);\n }\n return;\n }\n popup.open(entry.record, entry.el);\n safeInvoke('onOpen', onOpen, entry.record);\n }\n\n // Close the open description (does not turn the mode itself OFF).\n function closePopup() {\n if (popup) {\n popup.close();\n }\n }\n\n // Replace the helpConfig. If ON, rebuild silently (onEnable/onDisable are not fired).\n function update(newConfig) {\n validateConfig(newConfig);\n activeConfig = newConfig;\n if (state) {\n turnOff();\n turnOn();\n }\n }\n\n if (toggleEl) {\n toggleEl.addEventListener('click', toggleMode);\n }\n\n return {\n enable,\n disable,\n toggle: toggleMode,\n isActive() {\n return state !== null;\n },\n open: openByKey,\n close: closePopup,\n update,\n destroy() {\n disable();\n if (toggleEl) {\n toggleEl.removeEventListener('click', toggleMode);\n }\n },\n };\n}\n", "import { createToggleController } from './toggle.js';\n\n/**\n * Initialize the help mode.\n *\n * It can be toggled ON/OFF by clicking the toggle element, and also controlled programmatically\n * via the returned API. If `toggle` is omitted, there's no DOM toggle and it's programmatic-only.\n *\n * @param {object} options\n * @param {import('./config.js').HelpConfig} options.config - configuration that specifies targets by data-help-id or position.\n * Elements not in config can still be targets via the data-help-title / data-help-text inline definition (config wins)\n * @param {string|HTMLElement} [options.toggle] - toggle element that switches ON/OFF (CSS selector string or element). Optional\n * @param {() => void} [options.onEnable] - called right after the mode is turned ON\n * @param {() => void} [options.onDisable] - called right after the mode is turned OFF\n * @param {(record: import('./matcher.js').HelpRecord) => void} [options.onOpen] - called when a description popup is opened\n * @param {() => void} [options.onClose] - called when a description popup is closed\n * @param {boolean} [options.silent] - suppress the warning log for unregistered keys\n * @param {string} [options.attribute] - attribute name marking targets (default 'data-help-id')\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render] - render the popup body with your own DOM.\n * Return a Node to display it; if nothing is returned, fall back to safe text display (the title is always record.title).\n * \u26A0\uFE0F The return value is inserted as-is without sanitization. If it contains untrusted data, neutralize it on the caller side (XSS prevention)\n * @param {string} [options.markerLabel] - character shown on the marker (default '?')\n * @param {import('@floating-ui/dom').Placement} [options.markerPlacement] - corner to overlap the marker onto (default 'top-end')\n * @param {import('@floating-ui/dom').Placement} [options.popupPlacement] - initial popup placement (default 'bottom-start')\n * @param {string} [options.nonce] - nonce to allow the injected <style> under a strict CSP (style-src 'nonce-\u2026')\n * @returns {{\n * enable(): void,\n * disable(): void,\n * toggle(): void,\n * isActive(): boolean,\n * open(key: string): void,\n * close(): void,\n * update(config: import('./config.js').HelpConfig): void,\n * destroy(): void,\n * }} a handle to control the mode and fully clean up at the end.\n * open(key) opens the description for the given key (auto-enables when OFF). close() closes the open description.\n */\nexport function initHelpLayer(options) {\n return createToggleController(options);\n}\n"],
5
- "mappings": "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",
6
- "names": ["POPUP_TITLE_ID", "createBlockingLayer", "layer", "createMarker", "title", "label", "marker", "createPopup", "root", "titleEl", "textEl", "closeEl", "autoUpdate", "computePosition", "flip", "offset", "shift", "docRectToViewportRect", "docRect", "scroll", "width", "height", "left", "top", "makeVirtualElement", "getDocRect", "docRectToViewportRect", "place", "el", "x", "y", "isFixedReference", "reference", "node", "parent", "root", "MARKER_INSET", "markerOffset", "placement", "isStart", "anchorMarker", "markerEl", "onPlaced", "strategy", "autoUpdate", "computePosition", "offset", "anchorPopup", "popupEl", "update", "flip", "shift", "cleanup", "watchReference", "referenceEl", "floatingEl", "onUpdate", "buildClipPath", "rect", "x1", "y1", "x2", "y2", "activateBlockingLayer", "state", "toggleEl", "onBackgroundClick", "isLibraryElement", "onEscape", "layer", "createBlockingLayer", "cleanupClipWatch", "watchReference", "activeEl", "handleFocusIn", "event", "blockNonLibrary", "blockKey", "handleKeydown", "isPlainObject", "value", "isValidPosition", "position", "validateConfig", "config", "key", "entry", "normalizeConfig", "resolveOverlaps", "centers", "options", "minDistance", "iterations", "positions", "c", "iter", "moved", "i", "j", "a", "b", "dx", "dy", "dist", "overlap", "ux", "uy", "p", "TARGET_HIGHLIGHT_CLASS", "referenceFor", "record", "makeVirtualElement", "createMarkerManager", "state", "onMarkerClick", "onOverlapResolved", "markerLabel", "markerPlacement", "markers", "rafId", "tornDown", "runOverlapPass", "entries", "el", "e", "centers", "r", "offsets", "resolveOverlaps", "i", "dx", "dy", "scheduleOverlapPass", "mount", "createMarker", "handleClick", "cleanupAnchor", "anchorMarker", "target", "addHighlight", "removeHighlight", "done", "cleanup", "unmount", "id", "entry", "mountAll", "records", "key", "safeInvoke", "label", "fn", "args", "err", "ELEMENT_NODE", "walkDeep", "root", "selector", "onMatch", "onShadowRoot", "el", "queryAllDeep", "results", "collectShadowRoots", "roots", "shadow", "scanSubtree", "node", "matches", "shadowRoots", "m", "createMutationWatcher", "onAdded", "onRemoved", "observed", "handle", "records", "record", "safeInvoke", "observe", "observer", "target", "TITLE_ATTR", "TEXT_ATTR", "targetSelector", "attribute", "elementConfigMap", "items", "map", "item", "freeRecords", "recordForElement", "el", "configMap", "key", "title", "text", "collectElementRecords", "root", "silent", "records", "queryAllDeep", "record", "createPopupController", "state", "onClose", "render", "popupPlacement", "root", "titleEl", "textEl", "closeEl", "createPopup", "close", "openId", "triggerEl", "anchor", "stopAnchor", "open", "record", "referenceEl", "custom", "safeInvoke", "anchorPopup", "reposition", "hide", "wasOpen", "focusTarget", "returnTo", "id", "createState", "cleanupFns", "fn", "cleanup", "err", "STYLE_ATTR", "CSS", "injectStyles", "nonce", "styleEl", "removeStyles", "resolveToggleElement", "toggle", "el", "createToggleController", "options", "isPlainObject", "config", "onEnable", "onDisable", "onOpen", "onClose", "silent", "attribute", "render", "markerLabel", "markerPlacement", "popupPlacement", "nonce", "activeConfig", "validateConfig", "toggleEl", "state", "popup", "markers", "turnOn", "createState", "styleEl", "injectStyles", "removeStyles", "items", "normalizeConfig", "configMap", "elementConfigMap", "createPopupController", "createMarkerManager", "record", "markerEl", "safeInvoke", "freeRecords", "collectElementRecords", "watcher", "createMutationWatcher", "targetSelector", "recordForElement", "activateBlockingLayer", "target", "disable", "turnOff", "enable", "toggleMode", "openByKey", "key", "entry", "closePopup", "update", "newConfig", "initHelpLayer", "options", "createToggleController"]
3
+ "sources": ["../src/aria-isolation.js", "../src/dom-builder.js", "../src/geometry.js", "../src/reference.js", "../src/floating.self.js", "../src/blocking-layer.js", "../src/config.js", "../src/overlap.js", "../src/markers.js", "../src/safe.js", "../src/observer.js", "../src/matcher.js", "../src/popup.js", "../src/state.js", "../src/style.js", "../src/toggle.js", "../src/index.js"],
4
+ "sourcesContent": ["/**\n * Semantic background blocking for assistive technology (AT).\n *\n * The clip-path layer / focus containment / key suppression block pointer, physical focus, and\n * physical keys, but a screen reader's virtual cursor (browse mode) reads and can activate background\n * content regardless of focus or hit-testing. So while ON we also remove the host from the\n * accessibility tree with `inert` (which both excludes from the a11y tree and suppresses interaction),\n * giving AT users the same \"host is inactive\" guarantee.\n *\n * Why operate at document.body's top level: `inert` is inherited and cannot be cancelled on a\n * descendant of an inert subtree. The toggle is host-owned and may be deeply nested, so\u2014exactly like\n * the clip-path \"hole\" that lets the toggle show through\u2014we inert each body child that is neither a\n * library node nor the branch containing the toggle. The toggle's own top-level branch stays\n * reachable (a bounded leak when the toggle is nested; none when it's a direct body child).\n */\n\nconst ELEMENT_NODE = 1;\n\n/**\n * @param {object} state teardown registry\n * @param {object} params\n * @param {HTMLElement|null} params.toggleEl the toggle (must stay reachable), or null for programmatic-only\n * @param {(el: Element) => boolean} params.isLibraryNode whether a body child belongs to the library UI\n */\nexport function isolateBackgroundFromAT(state, { toggleEl, isLibraryNode }) {\n /** @type {Set<Element>} */\n const isolated = new Set();\n\n /** @param {Node} node */\n function isolate(node) {\n if (node.nodeType !== ELEMENT_NODE) {\n return;\n }\n const el = /** @type {Element} */ (node);\n // Skip library UI, the toggle's branch, and anything the host already made inert (so restore\n // doesn't clobber the host's own inert state).\n if (\n isLibraryNode(el) ||\n (toggleEl && (el === toggleEl || el.contains(toggleEl))) ||\n el.hasAttribute('inert')\n ) {\n return;\n }\n el.toggleAttribute('inert', true);\n isolated.add(el);\n }\n\n for (const child of [...document.body.children]) {\n isolate(child);\n }\n\n // The host may add top-level nodes while ON (SPA route changes, portals, ...). Keep them isolated\n // too. Only direct body children matter here, so childList without subtree is enough.\n const observer = new MutationObserver((records) => {\n for (const record of records) {\n record.addedNodes.forEach(isolate);\n }\n });\n observer.observe(document.body, { childList: true });\n\n state.track(() => {\n observer.disconnect();\n // Remove inert only from the nodes we added it to (leave any host-owned inert untouched).\n isolated.forEach((el) => el.removeAttribute('inert'));\n isolated.clear();\n });\n}\n", "/**\n * Factory functions that create the DOM elements for markers, popups, and the blocking layer.\n * Event wiring and positioning are not done here (that is the caller's responsibility).\n *\n * Accessibility:\n * - Markers are <button> elements so they are focusable and can be activated with Enter/Space.\n * - The popup uses role=\"dialog\" + aria-labelledby (the title element) so assistive tech announces it,\n * plus aria-describedby (the body element) so the description text is read out, not just the title.\n */\n\n// Each initHelpLayer instance builds its own popup; a fixed id would collide when the\n// library is initialized more than once on a page (invalid duplicate id + ambiguous\n// aria-labelledby). Hand out a unique id per popup instead.\nlet popupSeq = 0;\n\nexport function createBlockingLayer() {\n const layer = document.createElement('div');\n layer.className = 'help-layer-blocking-layer';\n return layer;\n}\n\n/**\n * @param {string} title description title used for the assistive-tech label\n * @param {string} [label] character shown on the marker (default '?'). Visual only; does not affect the aria-label.\n */\nexport function createMarker(title, label = '?') {\n const marker = document.createElement('button');\n marker.type = 'button';\n marker.className = 'help-layer-marker';\n marker.textContent = label;\n marker.setAttribute('aria-label', `Help: ${title}`);\n return marker;\n}\n\n/**\n * Create the single popup shared across the whole library.\n * Also returns references to titleEl/textEl (used to update the content) and the close button closeEl.\n */\nexport function createPopup() {\n // One sequence value per popup, shared by the title and body ids (then advanced once), so two\n // instances on a page never collide on either id.\n const seq = popupSeq++;\n const titleId = `help-layer-popup-title-${seq}`;\n const textId = `help-layer-popup-text-${seq}`;\n\n const root = document.createElement('div');\n root.className = 'help-layer-popup';\n root.setAttribute('role', 'dialog');\n // aria-modal tells AT that content outside the dialog is inert while it's shown (the host is also\n // inert'd at the document level during help mode). Harmless when hidden: display:none drops the\n // popup from the a11y tree.\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-labelledby', titleId);\n // Point at the body container (not its contents) so the description is announced even after a custom\n // render swaps the body's children \u2014 the container id stays stable.\n root.setAttribute('aria-describedby', textId);\n root.tabIndex = -1;\n\n const titleEl = document.createElement('div');\n titleEl.className = 'help-layer-popup__title';\n titleEl.id = titleId;\n\n const textEl = document.createElement('div');\n textEl.className = 'help-layer-popup__text';\n textEl.id = textId;\n\n // Explicit close affordance. Wiring the click is popup.js's job (only element creation here).\n const closeEl = document.createElement('button');\n closeEl.type = 'button';\n closeEl.className = 'help-layer-popup__close';\n closeEl.textContent = '\u00D7';\n closeEl.setAttribute('aria-label', 'Close');\n\n root.append(titleEl, textEl, closeEl);\n\n return { root, titleEl, textEl, closeEl };\n}\n", "/**\n * Pure geometry calculations. Takes no DOM elements, only numbers already read off.\n *\n * Clamping things that overflow the viewport is handled by computePopupPosition's shift step\n * below. toDocumentPosition is used for the virtual-element math of free placement, etc.\n */\n\n/**\n * Given getBoundingClientRect() values (viewport-relative) and the scroll offset,\n * compute coordinates relative to the whole document.\n */\nexport function toDocumentPosition(rect, scroll) {\n return {\n top: rect.top + scroll.y,\n left: rect.left + scroll.x,\n };\n}\n\n/**\n * Convert a document-coordinate rect into a viewport-coordinate rect by subtracting\n * the current scroll offset. This is what the getBoundingClientRect of a virtual\n * reference element (a free-placement marker) returns.\n * @param {{top:number,left:number,width?:number,height?:number}} docRect\n * @param {{x:number,y:number}} scroll\n */\nexport function docRectToViewportRect(docRect, scroll) {\n const width = docRect.width || 0;\n const height = docRect.height || 0;\n const left = docRect.left - scroll.x;\n const top = docRect.top - scroll.y;\n return {\n x: left,\n y: top,\n left,\n top,\n right: left + width,\n bottom: top + height,\n width,\n height,\n };\n}\n\n// Half of the default marker size (24px). The marker bites this far inward past the target's edge so\n// it overlaps the corner with an \"inset\". (If the marker-size CSS variable is changed, the marker\n// size is read at runtime in markers.js, but this inset stays fixed = existing behavior.)\nexport const MARKER_INSET = 12;\n\n/**\n * Compute a marker's top-left in viewport coordinates so it overlaps a corner of the reference rect,\n * replicating what Floating UI's computePosition(placement) + offset(markerOffset) produced before.\n *\n * placement is \"<side>\" or \"<side>-<align>\" where side is top/bottom/left/right and align is\n * start/end (omitted = centered). The marker is a square of the given size. mainAxis bites INSET\n * inward (overlapping the target edge); crossAxis nudges INSET inward from the aligned edge.\n * @param {{top:number,left:number,width:number,height:number}} refRect viewport rect of the target\n * @param {number} size marker width/height (square)\n * @param {string} placement a placement string (see Placement in types.js; default 'top-end')\n * @returns {{left:number, top:number}} viewport coordinates of the marker's top-left corner\n */\nexport function markerViewportTopLeft(refRect, size, placement = 'top-end') {\n const [side, align] = placement.split('-');\n const isStart = align === 'start';\n // crossAxis: -end (right/bottom-aligned) goes inward negative; -start goes inward positive.\n const cross = isStart ? MARKER_INSET : -MARKER_INSET;\n\n let left;\n let top;\n if (side === 'top' || side === 'bottom') {\n // Vertical placement: main axis is Y, cross axis is X.\n top = side === 'top'\n ? refRect.top - size + MARKER_INSET // above, then bite down into the target\n : refRect.top + refRect.height - MARKER_INSET; // below, then bite up\n if (align === 'start') {\n left = refRect.left;\n } else if (align === 'end') {\n left = refRect.left + refRect.width - size;\n } else {\n left = refRect.left + refRect.width / 2 - size / 2;\n }\n left += cross;\n } else {\n // Horizontal placement (left/right): main axis is X, cross axis is Y.\n left = side === 'left'\n ? refRect.left - size + MARKER_INSET\n : refRect.left + refRect.width - MARKER_INSET;\n if (align === 'start') {\n top = refRect.top;\n } else if (align === 'end') {\n top = refRect.top + refRect.height - size;\n } else {\n top = refRect.top + refRect.height / 2 - size / 2;\n }\n top += cross;\n }\n return { left, top };\n}\n\n/**\n * Convert a viewport coordinate into the left/top to set on a `position:absolute` element whose\n * offsetParent is `document.body`. Both the marker's reference rect and the body rect come from\n * getBoundingClientRect (viewport space), so their difference is scroll-invariant \u2014 which is exactly\n * why the marker stays anchored to its target as the page scrolls. clientLeft/clientTop subtract the\n * body's border so the offset is measured from the body's padding-box origin (the absolute origin).\n * For `position:fixed` markers no conversion is needed (viewport coordinates are used as-is).\n * @param {number} vx viewport x\n * @param {number} vy viewport y\n * @param {{left:number, top:number}} bodyRect document.body's getBoundingClientRect\n * @param {number} clientLeft document.body.clientLeft (left border width)\n * @param {number} clientTop document.body.clientTop (top border width)\n * @returns {{left:number, top:number}}\n */\nexport function viewportToAbsolute(vx, vy, bodyRect, clientLeft, clientTop) {\n return {\n left: vx - bodyRect.left - clientLeft,\n top: vy - bodyRect.top - clientTop,\n };\n}\n\nconst clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));\n\n/**\n * Compute the popup's top-left in viewport coordinates, replicating the small subset of Floating UI\n * we relied on: place on a side of the reference with a gap (offset), flip to the opposite side when\n * the preferred side doesn't fit (main axis), and shift along the cross axis to keep it inside the\n * viewport (with padding). This covers the popup's case \u2014 a single element over document.body whose\n * clipping boundary is the viewport \u2014 and is intentionally simpler than Floating UI (no nested\n * clipping ancestors / transforms / RTL).\n *\n * placement is \"<side>\" or \"<side>-<align>\" (side: top/bottom/left/right; align: start/end, omitted = center).\n * @param {{top:number,left:number,width:number,height:number}} refRect viewport rect of the reference\n * @param {{width:number,height:number}} popupSize the popup's measured size\n * @param {{width:number,height:number}} viewport innerWidth/innerHeight\n * @param {object} [opts]\n * @param {string} [opts.placement] default 'bottom-start'\n * @param {number} [opts.offset] gap between reference and popup along the main axis (default 8)\n * @param {number} [opts.padding] minimum gap kept from the viewport edges (default 8)\n * @returns {{left:number, top:number, placement:string}} resolved viewport coords + the side actually used\n */\nexport function computePopupPosition(refRect, popupSize, viewport, opts = {}) {\n const placement = opts.placement ?? 'bottom-start';\n const offset = opts.offset ?? 8;\n const padding = opts.padding ?? 8;\n const [side, align] = placement.split('-');\n const vertical = side === 'top' || side === 'bottom';\n\n // Main-axis position for a given side (top/left coordinate that places the popup on that side).\n const mainPos = (s) => {\n if (s === 'bottom') {\n return refRect.top + refRect.height + offset;\n }\n if (s === 'top') {\n return refRect.top - popupSize.height - offset;\n }\n if (s === 'right') {\n return refRect.left + refRect.width + offset;\n }\n return refRect.left - popupSize.width - offset; // left\n };\n\n // Available space (for the popup) on a side, after keeping `padding` from the viewport edge.\n const spaceFor = (s) => {\n if (s === 'bottom') {\n return viewport.height - padding - (refRect.top + refRect.height + offset);\n }\n if (s === 'top') {\n return refRect.top - offset - padding;\n }\n if (s === 'right') {\n return viewport.width - padding - (refRect.left + refRect.width + offset);\n }\n return refRect.left - offset - padding; // left\n };\n\n // Flip (main axis): if the preferred side can't fit the popup, use whichever side has more room.\n const opposite = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }[side];\n const need = vertical ? popupSize.height : popupSize.width;\n const chosen = spaceFor(side) >= need || spaceFor(side) >= spaceFor(opposite) ? side : opposite;\n\n // Cross-axis position from the alignment.\n const crossPos = (extentRef, extentPopup, start) => {\n if (align === 'start') {\n return start;\n }\n if (align === 'end') {\n return start + extentRef - extentPopup;\n }\n return start + extentRef / 2 - extentPopup / 2;\n };\n\n let left;\n let top;\n if (vertical) {\n top = mainPos(chosen);\n left = crossPos(refRect.width, popupSize.width, refRect.left);\n // Shift along x to keep the popup within the viewport.\n left = clamp(left, padding, viewport.width - popupSize.width - padding);\n } else {\n left = mainPos(chosen);\n top = crossPos(refRect.height, popupSize.height, refRect.top);\n // Shift along y to keep the popup within the viewport.\n top = clamp(top, padding, viewport.height - popupSize.height - padding);\n }\n\n return { left, top, placement: align ? `${chosen}-${align}` : chosen };\n}\n", "/**\n * Backend-agnostic helpers about a positioning \"reference\" (the element a marker/popup points at,\n * or a virtual element for free placements). These touch only the DOM \u2014 no positioning library \u2014 so\n * both the self-implemented and the Floating UI positioning backends share them unchanged.\n */\nimport { docRectToViewportRect } from './geometry.js';\n\n/**\n * Create a \"virtual reference element\" for free-placement items not bound to an element.\n * getDocRect() returns document coordinates; this converts them to viewport coordinates for the\n * current scroll, so the element tracks the page as it scrolls (it's re-read every frame).\n * @param {() => {top:number,left:number,width?:number,height?:number}} getDocRect\n */\nexport function makeVirtualElement(getDocRect) {\n return {\n // Kept for parity with the Floating UI backend (its autoUpdate uses contextElement to know which\n // scroll ancestors to watch). Harmless for the self backend, which reads the rect directly.\n contextElement: document.body,\n getBoundingClientRect() {\n return docRectToViewportRect(getDocRect(), { x: window.scrollX, y: window.scrollY });\n },\n };\n}\n\n/**\n * Whether the reference lives in a `position: fixed` subtree. Such a reference stays put in the\n * viewport while the page scrolls, so an absolutely-positioned floating element (which scrolls with\n * the document) would drift; for these we switch the floating element to a fixed strategy so both\n * live in the same viewport space and stay glued without per-frame correction.\n *\n * Virtual elements (free placements) aren't in the DOM and already track scroll via their getRect, so\n * they report false. Walks across shadow boundaries via the host so Shadow DOM targets are handled too.\n * @param {Element|object} reference\n */\nexport function isFixedReference(reference) {\n if (!(reference instanceof Element)) {\n return false;\n }\n let node = reference;\n while (node) {\n if (getComputedStyle(node).position === 'fixed') {\n return true;\n }\n const parent = node.parentElement;\n if (parent) {\n node = parent;\n } else {\n const root = node.getRootNode();\n node = root instanceof ShadowRoot ? root.host : null;\n }\n }\n return false;\n}\n\n/**\n * Whether a reference element is currently not rendered (hidden). Free placements use a virtual\n * element with no host node, so they are never \"hidden\" (return false).\n * @param {Element|object} reference\n */\nexport function isReferenceHidden(reference) {\n if (!(reference instanceof Element)) {\n return false;\n }\n // checkVisibility() catches display:none (incl. an ancestor), content-visibility, and\u2014with the\n // option\u2014visibility:hidden, in one cheap call without any extra observers. NOTE: do NOT use\n // offsetParent here; it is null for position:fixed elements too (which this lib supports as\n // targets) and would wrongly hide their markers. Engines without checkVisibility fall back to the\n // rect: a display:none element measures 0x0 (the worst case \u2014 the marker would jump to 0,0).\n if (typeof reference.checkVisibility === 'function') {\n return !reference.checkVisibility({ visibilityProperty: true, contentVisibilityAuto: true });\n }\n const r = reference.getBoundingClientRect();\n return r.width === 0 && r.height === 0;\n}\n", "/**\n * Default positioning backend \u2014 dependency-free (no Floating UI).\n *\n * Markers position themselves in markers.js; this module only handles the single shared popup and the\n * blocking layer's toggle-following clip-path. It re-implements the small slice of Floating UI the\n * library used: place-on-a-side + offset + flip + shift (see computePopupPosition in geometry.js), and\n * an autoUpdate-style tracker (track) built on the same per-frame rAF pattern markers.js uses.\n *\n * The reference helpers (isFixedReference / isReferenceHidden / makeVirtualElement) live in\n * reference.js and are re-exported so this backend and floating.floatingui.js have an identical\n * surface \u2014 the two are interchangeable via the one-line seam in floating.js.\n */\nimport { computePopupPosition, viewportToAbsolute } from './geometry.js';\nimport { isFixedReference, isReferenceHidden, makeVirtualElement } from './reference.js';\n\nexport { isFixedReference, isReferenceHidden, makeVirtualElement };\n\nfunction place(el, x, y) {\n el.style.left = `${x}px`;\n el.style.top = `${y}px`;\n}\n\nfunction sameRect(a, b) {\n return a.top === b.top && a.left === b.left && a.width === b.width && a.height === b.height;\n}\n\n/**\n * Keep calling onChange while the reference moves/resizes: once synchronously now (like Floating UI's\n * initial autoUpdate run), then on every animation frame in which the reference's rect changed. The\n * popup's reference is the marker, which markers.js moves per frame, so per-frame tracking keeps the\n * popup glued; an unchanged rect costs only one getBoundingClientRect and no work.\n * @param {Element|object} reference\n * @param {() => void} onChange\n * @returns {() => void} cleanup\n */\nfunction track(reference, onChange) {\n onChange(); // initial placement, synchronous\n let prev = reference.getBoundingClientRect();\n let stopped = false;\n let frame = requestAnimationFrame(function loop() {\n if (stopped) {\n return;\n }\n const rect = reference.getBoundingClientRect();\n if (!sameRect(rect, prev)) {\n onChange();\n }\n prev = rect;\n frame = requestAnimationFrame(loop);\n });\n // A viewport resize can change the popup's flip/shift even when the reference itself doesn't move\n // (e.g. a popup shifted near the right edge while its marker stays put). The rect-change loop above\n // wouldn't catch that, so listen for resize explicitly \u2014 matching the Floating UI backend, whose\n // autoUpdate also reacts to ancestor/window resize.\n const onResize = () => onChange();\n window.addEventListener('resize', onResize);\n return () => {\n stopped = true;\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', onResize);\n };\n}\n\n/**\n * Place the popup on a side of the target with a gap, flipping/shifting at screen edges to stay\n * visible, and keep it following while open.\n * @param {Element|object} reference\n * @param {HTMLElement} popupEl\n * @param {import('./types.js').Placement} [placement] initial placement. Default 'bottom-start'\n * @returns {{ update: () => void, cleanup: () => void }}\n */\nexport function anchorPopup(reference, popupEl, placement = 'bottom-start') {\n // Match the strategy to the reference: a fixed reference needs a fixed popup or it jitters on\n // scroll. Set it every open so reopening on a normal marker restores absolute. Inline !important\n // beats the stylesheet's `position: absolute !important`.\n const strategy = isFixedReference(reference) ? 'fixed' : 'absolute';\n popupEl.style.setProperty('position', strategy, 'important');\n\n const update = () => {\n const refRect = reference.getBoundingClientRect();\n const popupSize = { width: popupEl.offsetWidth, height: popupEl.offsetHeight };\n const viewport = { width: window.innerWidth, height: window.innerHeight };\n const { left, top } = computePopupPosition(refRect, popupSize, viewport, { placement });\n if (strategy === 'fixed') {\n place(popupEl, left, top); // fixed: viewport coordinates are used as-is\n } else {\n // absolute: convert viewport coords to body-relative (same scroll-invariant trick as markers).\n const body = document.body;\n const abs = viewportToAbsolute(left, top, body.getBoundingClientRect(), body.clientLeft, body.clientTop);\n place(popupEl, abs.left, abs.top);\n }\n };\n\n const cleanup = track(reference, update);\n return { update, cleanup };\n}\n\n/**\n * Watch a reference element's position/size changes and call onUpdate on every change.\n * (Used to keep the blocking layer's clip-path hole following the toggle.) floatingEl is accepted for\n * signature parity with the Floating UI backend but isn't used here.\n * @param {Element} referenceEl\n * @param {HTMLElement} _floatingEl unused (kept for backend parity)\n * @param {() => void} onUpdate\n * @returns {() => void} cleanup\n */\nexport function watchReference(referenceEl, _floatingEl, onUpdate) {\n return track(referenceEl, onUpdate);\n}\n", "/**\n * Transparent interaction-blocking layer.\n *\n * Without touching the original app's event listeners at all, it keeps interactions from getting through. How it works:\n *\n * 1. Let the toggle show through via a clip-path \"hole\":\n * Set a clip-path polygon on the full-screen layer made of the outer rectangle + the toggle\n * rectangle (the hole). Inside the hole the layer isn't painted and hit-testing passes through,\n * so the toggle can be clicked natively without touching z-index at all. Unlike approaches that\n * shuffle z-index, this doesn't break depending on ancestor stacking contexts.\n * The hole is updated via watchReference (a per-frame rAF tracker) to follow the toggle's scroll/resize.\n *\n * 2. Focus containment:\n * On ON, blur activeElement, and via focusin (capture) detect focus moving to anything other\n * than the library UI and pull it back to the toggle. Host listeners are not detached.\n *\n * 3. Key-input suppression:\n * Capture keydown/keyup in document's capture phase; for anything outside the library UI,\n * stopPropagation+preventDefault. Escape has dedicated handling (close popup / exit mode).\n * Inside the library UI (marker/popup/toggle), normal interactions like Tab are allowed.\n */\nimport { createBlockingLayer } from './dom-builder.js';\nimport { watchReference } from './floating.js';\n\nfunction buildClipPath(rect) {\n const x1 = rect.left;\n const y1 = rect.top;\n const x2 = rect.right;\n const y2 = rect.bottom;\n // A single stroke: outer ring (clockwise) -> bridge -> toggle rectangle (counter-clockwise).\n // Under the nonzero winding rule, making the inner ring wind opposite to the outer one punches a hole.\n return `polygon(\n 0px 0px, 100% 0px, 100% 100%, 0px 100%, 0px 0px,\n ${x1}px ${y1}px, ${x1}px ${y2}px, ${x2}px ${y2}px, ${x2}px ${y1}px, ${x1}px ${y1}px\n )`;\n}\n\nexport function activateBlockingLayer(state, {\n toggleEl,\n onBackgroundClick,\n isLibraryElement,\n onEscape,\n}) {\n const layer = createBlockingLayer();\n document.body.appendChild(layer);\n state.track(() => layer.remove());\n\n // --- 1. Keep the clip-path hole following the toggle position ---\n // If there's no toggle element (programmatic control only), keeping the whole surface blocked with no hole is correct.\n if (toggleEl) {\n const updateClip = () => {\n layer.style.clipPath = buildClipPath(toggleEl.getBoundingClientRect());\n };\n const cleanupClipWatch = watchReference(toggleEl, layer, updateClip);\n state.track(cleanupClipWatch);\n }\n\n // Clicking the background (the layer itself) closes the popup\n if (onBackgroundClick) {\n layer.addEventListener('click', onBackgroundClick);\n state.track(() => layer.removeEventListener('click', onBackgroundClick));\n }\n\n // --- 2. Focus containment ---\n // Turning ON happens via a click on the toggle, so don't blur if the active element is the toggle\n // itself (blurring would leave focus floating). For any other host element, make it let go.\n const activeEl = document.activeElement;\n if (\n activeEl instanceof HTMLElement &&\n activeEl !== document.body &&\n activeEl !== toggleEl\n ) {\n activeEl.blur();\n }\n\n const handleFocusIn = (event) => {\n if (isLibraryElement(event.target)) {\n return;\n }\n // If focus tries to move to a host element, take it back.\n // To the toggle if there is one; otherwise blur that element so it isn't handed to the host.\n event.stopPropagation();\n if (toggleEl) {\n toggleEl.focus({ preventScroll: true });\n } else if (event.target instanceof HTMLElement) {\n event.target.blur();\n }\n };\n document.addEventListener('focusin', handleFocusIn, true);\n state.track(() => document.removeEventListener('focusin', handleFocusIn, true));\n\n // --- 3. Key-input suppression + Escape ---\n // Shared logic that doesn't pass key input destined outside the library UI through to the host.\n const blockNonLibrary = (event) => {\n if (isLibraryElement(event.target)) {\n return;\n }\n event.stopPropagation();\n event.preventDefault();\n };\n // keyup / keypress: don't leak to the host, Escape included (Escape's real handling is on the keydown side).\n const blockKey = (event) => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n event.preventDefault();\n return;\n }\n blockNonLibrary(event);\n };\n const handleKeydown = (event) => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n event.preventDefault();\n if (onEscape) {\n onEscape();\n }\n return;\n }\n blockNonLibrary(event);\n };\n document.addEventListener('keydown', handleKeydown, true);\n document.addEventListener('keyup', blockKey, true);\n document.addEventListener('keypress', blockKey, true);\n state.track(() => {\n document.removeEventListener('keydown', handleKeydown, true);\n document.removeEventListener('keyup', blockKey, true);\n document.removeEventListener('keypress', blockKey, true);\n });\n\n return layer;\n}\n", "/**\n * Validation and normalization of the helpConfig object. A pure function that does no DOM work.\n */\n\n/**\n * @typedef {object} HelpEntry\n * @property {string} title heading shown on the marker / popup (non-empty)\n * @property {string} text description body (non-empty)\n * @property {{ top: number, left: number }} [position] if given, becomes a free placement not tied to an element\n */\n\n/**\n * The helpConfig itself. For element-bound entries the key is the `data-help-id` value;\n * for free placement it is any identifier.\n * @typedef {Object<string, HelpEntry>} HelpConfig\n */\n\nexport function isPlainObject(value) {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isValidPosition(position) {\n // Number.isFinite rejects NaN / Infinity / non-numbers, so a computed coordinate that became NaN\n // fails validation here instead of silently pinning the marker to 0,0 at render time.\n return (\n isPlainObject(position) &&\n Number.isFinite(position.top) &&\n Number.isFinite(position.left)\n );\n}\n\n/**\n * Validate the shape of helpConfig. Throws an Error on any problem (fail fast).\n */\nexport function validateConfig(config) {\n if (!isPlainObject(config)) {\n throw new Error('helpConfig must be a plain object');\n }\n\n for (const [key, entry] of Object.entries(config)) {\n if (!isPlainObject(entry)) {\n throw new Error(`helpConfig[\"${key}\"] must be an object`);\n }\n if (typeof entry.title !== 'string' || entry.title === '') {\n throw new Error(`helpConfig[\"${key}\"].title must be a non-empty string`);\n }\n if (typeof entry.text !== 'string' || entry.text === '') {\n throw new Error(`helpConfig[\"${key}\"].text must be a non-empty string`);\n }\n if (entry.position !== undefined && !isValidPosition(entry.position)) {\n throw new Error(`helpConfig[\"${key}\"].position must be { top: finite number, left: finite number }`);\n }\n }\n}\n\n/**\n * Convert a validated helpConfig into the shared array of \"help items\" the rendering\n * code works with. The target of kind:'element' is null at this point (DOM matching is\n * left to matcher.js).\n */\nexport function normalizeConfig(config) {\n return Object.entries(config).map(([key, entry]) => {\n if (isValidPosition(entry.position)) {\n return {\n key,\n title: entry.title,\n text: entry.text,\n kind: 'free',\n target: null,\n position: { top: entry.position.top, left: entry.position.left },\n };\n }\n\n return {\n key,\n title: entry.title,\n text: entry.text,\n kind: 'element',\n target: null,\n position: null,\n };\n });\n}\n", "/**\n * Overlap avoidance between markers (pure function).\n *\n * Takes an array of each marker's \"base position\" (the center coordinate the positioning\n * pass decided on) and returns an array of extra offsets that push overlapping ones apart.\n * Touches no DOM.\n *\n * The algorithm is a simple iterative push-out (a lightweight force-based separation):\n * if two circles are closer than the minimum distance, push them apart in opposite\n * directions. Repeat a few times. Markers are small circles, so a circle-to-circle\n * distance test is enough.\n */\n\n/**\n * @param {Array<{x:number,y:number}>} centers base coordinate of each marker center\n * @param {object} [options]\n * @param {number} [options.minDistance] center-to-center distance closer than this counts as overlap\n * @param {number} [options.iterations] number of iterations\n * @returns {Array<{dx:number,dy:number}>} offset to add to each marker\n */\nexport function resolveOverlaps(centers, options = {}) {\n const minDistance = options.minDistance ?? 26;\n const iterations = options.iterations ?? 6;\n\n // Working positions (base + accumulated offset).\n const positions = centers.map((c) => ({ x: c.x, y: c.y }));\n\n for (let iter = 0; iter < iterations; iter++) {\n let moved = false;\n\n for (let i = 0; i < positions.length; i++) {\n for (let j = i + 1; j < positions.length; j++) {\n const a = positions[i];\n const b = positions[j];\n let dx = b.x - a.x;\n let dy = b.y - a.y;\n let dist = Math.hypot(dx, dy);\n\n if (dist >= minDistance) {\n continue;\n }\n\n // If the coordinates are exactly identical, separate in a deterministic direction (horizontal).\n if (dist === 0) {\n dx = 1;\n dy = 0;\n dist = 1;\n }\n\n const overlap = (minDistance - dist) / 2;\n const ux = dx / dist;\n const uy = dy / dist;\n\n a.x -= ux * overlap;\n a.y -= uy * overlap;\n b.x += ux * overlap;\n b.y += uy * overlap;\n moved = true;\n }\n }\n\n if (!moved) {\n break;\n }\n }\n\n return positions.map((p, i) => ({\n dx: p.x - centers[i].x,\n dy: p.y - centers[i].y,\n }));\n}\n", "/**\n * Marker manager.\n * Markers can be dynamically mounted/unmounted per help record (SPA dynamic-element support).\n *\n * Positioning runs in ONE shared requestAnimationFrame loop owned by the manager (not one Floating UI\n * autoUpdate per marker). Each frame the loop:\n * 1. reads every visible marker's reference rect (and the shared offsetParent geometry) once,\n * 2. computes each marker's corner-overlap position synchronously (markers only need an offset,\n * never flip/shift \u2014 those are popup-only), runs overlap avoidance on the centers, and\n * 3. writes left/top in a single batched pass.\n * Folding tracking + overlap into one read-then-write loop avoids the layout thrashing and the\n * doubled rect reads of running N independent animation-frame loops, which is what made large marker\n * counts expensive. Smoothness is unchanged: writes still happen every frame before paint.\n *\n * Marker identifier (id):\n * - element-bound: the target element itself (distinguishes multiple elements with the same data-help-id)\n * - free placement: the config key string\n */\nimport { createMarker } from './dom-builder.js';\nimport { isFixedReference, isReferenceHidden, makeVirtualElement } from './floating.js';\nimport { markerViewportTopLeft, viewportToAbsolute } from './geometry.js';\nimport { resolveOverlaps } from './overlap.js';\n\n// Temporary class added to the target element only while the marker is hovered/focused (matches the style.js definition).\nconst TARGET_HIGHLIGHT_CLASS = 'help-layer-target-highlight';\n\n// Fallback marker size if the real size can't be measured yet (matches the CSS default). Used only\n// until a laid-out marker reports a non-zero offsetWidth, which is then cached.\nconst DEFAULT_MARKER_SIZE = 24;\n\n/** @param {import('./matcher.js').HelpRecord} record */\nfunction referenceFor(record) {\n if (record.kind === 'free') {\n return makeVirtualElement(() => ({\n top: record.position.top,\n left: record.position.left,\n width: 0,\n height: 0,\n }));\n }\n return record.target;\n}\n\n/**\n * @param {object} state teardown registry\n * @param {object} options\n * @param {(record: import('./matcher.js').HelpRecord, markerEl: HTMLElement) => void} options.onMarkerClick\n * @param {() => void} [options.onOverlapResolved] called once per frame in which any marker actually moved\n * @param {(record: import('./matcher.js').HelpRecord) => void} [options.onMarkerHidden] called when a\n * marker's target transitions to hidden (e.g. display:none) \u2014 lets the caller close a popup open on it\n * @param {string} [options.markerLabel] character shown on the marker (default '?')\n * @param {import('./types.js').Placement} [options.markerPlacement] corner to overlap (default 'top-end')\n */\nexport function createMarkerManager(state, {\n onMarkerClick,\n onOverlapResolved,\n onMarkerHidden,\n markerLabel = '?',\n markerPlacement = 'top-end',\n}) {\n /**\n * @typedef {object} MarkerEntry\n * @property {import('./matcher.js').HelpRecord} record\n * @property {HTMLElement} el\n * @property {Element|object} reference positioning reference (element or virtual element)\n * @property {'fixed'|'absolute'} strategy positioning strategy chosen from the reference\n * @property {import('./types.js').Placement} placement corner to overlap onto\n * @property {() => void} cleanup\n * @property {boolean} hidden whether the target is currently reported hidden (edge tracking for onMarkerHidden)\n * @property {DOMRect=} refRect the reference rect read during the current frame's read phase\n * @property {{left:number,top:number}|null} lastBaseEl previous frame's pre-overlap position (element space) \u2014 movement detection\n * @property {number|undefined} lastLeft last written left (px), to skip redundant DOM writes\n * @property {number|undefined} lastTop last written top (px)\n */\n /** @type {Map<Element|string, MarkerEntry>} */\n const markers = new Map();\n let rafId = null;\n // Don't schedule a new rAF during teardown (prevents a frame lingering after teardown).\n let tornDown = false;\n // Cached marker size (square). Measured once from a laid-out marker; 0 until then.\n let markerSize = 0;\n // Visible-marker count from the previous frame, to detect membership changes (a marker entering or\n // leaving the visible set means overlap must be recomputed even if no surviving marker's base moved).\n let prevVisibleCount = -1;\n\n // One positioning pass: read references + offsetParent once, compute corner placements + overlap,\n // then write left/top in a batch. Pure of scheduling so it can run either synchronously (initial\n // placement, to avoid a one-frame flash at 0,0) or from the continuous rAF loop (tracking).\n function positionAll() {\n if (tornDown || markers.size === 0) {\n return;\n }\n\n // --- Read phase: visibility edges + reference rects, plus the shared offsetParent geometry. ---\n const bodyRect = document.body.getBoundingClientRect();\n const bodyClientLeft = document.body.clientLeft;\n const bodyClientTop = document.body.clientTop;\n\n /** @type {MarkerEntry[]} */\n const visible = [];\n for (const entry of markers.values()) {\n if (isReferenceHidden(entry.reference)) {\n // Target went hidden (e.g. display:none). Hide the marker too instead of leaving it stranded\n // (a display:none target measures 0x0, which would otherwise fling the marker to 0,0). Inline\n // !important beats the stylesheet's `display:block !important`. Fire onMarkerHidden only on the\n // visible -> hidden edge (e.g. to close a popup open on this marker).\n if (!entry.hidden) {\n entry.hidden = true;\n entry.lastBaseEl = null; // force a fresh placement when it reshows\n entry.el.style.setProperty('display', 'none', 'important');\n if (onMarkerHidden) {\n onMarkerHidden(entry.record);\n }\n }\n continue;\n }\n if (entry.hidden) {\n entry.hidden = false;\n entry.el.style.removeProperty('display'); // back to the stylesheet's display:block\n }\n entry.refRect = entry.reference.getBoundingClientRect();\n visible.push(entry);\n }\n\n // Cache the marker size once a real measurement is available (custom --help-layer-marker-size honored).\n if (!markerSize && visible.length) {\n const measured = visible[0].el.offsetWidth;\n if (measured > 0) {\n markerSize = measured;\n }\n }\n const size = markerSize || DEFAULT_MARKER_SIZE;\n\n // --- Compute phase (no DOM): base positions, movement/membership detection, overlap offsets. ---\n let dirty = visible.length !== prevVisibleCount;\n prevVisibleCount = visible.length;\n /** @type {{left:number,top:number}[]} */\n const bases = [];\n /** @type {{x:number,y:number}[]} */\n const centers = [];\n for (const entry of visible) {\n const bv = markerViewportTopLeft(entry.refRect, size, entry.placement);\n centers.push({ x: bv.left + size / 2, y: bv.top + size / 2 });\n // Convert the viewport position to what we actually write. For absolute markers this is\n // scroll-invariant (refRect and bodyRect both shift with scroll), so plain page scroll produces\n // no write \u2014 the marker rides the document for free. A write happens only when the target really\n // moves relative to the document (layout, resize, animation).\n const be = entry.strategy === 'fixed'\n ? { left: bv.left, top: bv.top }\n : viewportToAbsolute(bv.left, bv.top, bodyRect, bodyClientLeft, bodyClientTop);\n bases.push(be);\n if (!entry.lastBaseEl || entry.lastBaseEl.left !== be.left || entry.lastBaseEl.top !== be.top) {\n dirty = true;\n }\n entry.lastBaseEl = be;\n }\n\n // --- Write phase: only when something changed, and only the markers whose position differs. ---\n if (dirty && visible.length) {\n const offsets = resolveOverlaps(centers);\n let moved = false;\n for (let i = 0; i < visible.length; i++) {\n const entry = visible[i];\n const left = bases[i].left + offsets[i].dx;\n const top = bases[i].top + offsets[i].dy;\n if (entry.lastLeft !== left || entry.lastTop !== top) {\n entry.el.style.left = `${left}px`;\n entry.el.style.top = `${top}px`;\n entry.lastLeft = left;\n entry.lastTop = top;\n moved = true;\n }\n }\n // Marker positions moved, so give an open popup etc. the chance to follow.\n if (moved && onOverlapResolved) {\n onOverlapResolved();\n }\n }\n }\n\n // Continuous tracking: position every frame, then re-schedule. Stops re-scheduling once there are no\n // markers left (or after teardown); ensureLoop() restarts it on the next mount.\n function frameTick() {\n rafId = null;\n if (tornDown || markers.size === 0) {\n return;\n }\n positionAll();\n rafId = requestAnimationFrame(frameTick);\n }\n\n function ensureLoop() {\n if (rafId !== null || tornDown || markers.size === 0) {\n return;\n }\n rafId = requestAnimationFrame(frameTick);\n }\n\n /** @param {import('./matcher.js').HelpRecord} record */\n function mount(record) {\n if (markers.has(record.id)) {\n return;\n }\n\n const el = createMarker(record.title, markerLabel);\n document.body.appendChild(el);\n\n const handleClick = () => onMarkerClick(record, el);\n el.addEventListener('click', handleClick);\n\n const reference = referenceFor(record);\n // Match the strategy to the reference: a fixed reference needs a fixed marker, or it scrolls with\n // the document while the fixed target stays put and visibly drifts (see isFixedReference). Inline\n // !important beats the stylesheet's `position: absolute !important`.\n const strategy = isFixedReference(reference) ? 'fixed' : 'absolute';\n if (strategy === 'fixed') {\n el.style.setProperty('position', 'fixed', 'important');\n }\n\n // Target-element highlight (element-bound only; free placement has no target, so skip).\n // Show an outline on the target only while the marker is hovered/focused, to make clear \"which element this explains\".\n const target = record.kind === 'element' ? record.target : null;\n const addHighlight = () => target && target.classList.add(TARGET_HIGHLIGHT_CLASS);\n const removeHighlight = () => target && target.classList.remove(TARGET_HIGHLIGHT_CLASS);\n if (target) {\n el.addEventListener('mouseenter', addHighlight);\n el.addEventListener('mouseleave', removeHighlight);\n el.addEventListener('focus', addHighlight);\n el.addEventListener('blur', removeHighlight);\n }\n\n let done = false;\n const cleanup = () => {\n if (done) {\n return;\n }\n done = true;\n el.removeEventListener('click', handleClick);\n if (target) {\n el.removeEventListener('mouseenter', addHighlight);\n el.removeEventListener('mouseleave', removeHighlight);\n el.removeEventListener('focus', addHighlight);\n el.removeEventListener('blur', removeHighlight);\n removeHighlight(); // don't leave the highlight on the target if unmounted while highlighted\n }\n el.remove();\n markers.delete(record.id);\n ensureLoop(); // keep the loop alive so the next frame re-packs the remaining markers\n };\n\n markers.set(record.id, {\n record,\n el,\n reference,\n strategy,\n placement: markerPlacement,\n cleanup,\n hidden: false,\n lastBaseEl: null,\n lastLeft: undefined,\n lastTop: undefined,\n });\n ensureLoop();\n }\n\n function unmount(id) {\n const entry = markers.get(id);\n if (entry) {\n entry.cleanup();\n }\n }\n\n function mountAll(records) {\n records.forEach(mount);\n // Place the whole batch synchronously (before paint) so markers don't flash at (0,0) for a frame\n // on enable; the rAF loop started by mount() then takes over tracking. Done once per batch (not\n // per mount) to keep this O(n), not O(n^2).\n positionAll();\n }\n\n // Register a single teardown for the whole manager with state\n // (individual mount/unmount happen many times during a session, so they're bundled here).\n state.track(() => {\n tornDown = true;\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n [...markers.values()].forEach((entry) => entry.cleanup());\n });\n\n return {\n mount,\n unmount,\n mountAll,\n has(id) {\n return markers.has(id);\n },\n // Return the first entry matching the config key (either element-bound or free placement).\n // Used by the programmatic open(key).\n findByKey(key) {\n for (const entry of markers.values()) {\n if (entry.record.key === key) {\n return entry;\n }\n }\n return null;\n },\n };\n}\n", "/**\n * Isolation for user-supplied callbacks (render / onOpen / onClose / onEnable / onDisable).\n *\n * These run inside the library's own control flow (event handlers, teardown), so a throw from a\n * caller's mistake must not derail us: it could leave a popup half-open or abort a teardown midway,\n * stranding markers, observers and injected styles. We swallow the error and log it instead, so the\n * developer still sees their bug while the library keeps its internal state consistent.\n */\n\n/**\n * Invoke a user callback, never letting it throw into library code.\n * @template T\n * @param {string} label name used in the error log (e.g. 'onClose')\n * @param {((...args: any[]) => T)|undefined|null} fn the callback, or nullish to skip\n * @param {...any} args arguments forwarded to fn\n * @returns {T|undefined} fn's return value, or undefined when absent or it threw\n */\nexport function safeInvoke(label, fn, ...args) {\n if (!fn) {\n return undefined;\n }\n try {\n return fn(...args);\n } catch (err) {\n // Always logged regardless of `silent` (that flag only gates unregistered-key warnings).\n console.error(`[help-layer] ${label} threw:`, err);\n return undefined;\n }\n}\n", "/**\n * DOM observation and Shadow DOM-piercing traversal.\n *\n * A normal querySelectorAll does not cross shadow boundaries, so queryAllDeep walks open\n * shadowRoots recursively. A closed shadowRoot is unreachable from JS, so it is unsupported\n * (a known limitation).\n *\n * For SPA support, while ON a MutationObserver watches for additions/removals of data-help-id\n * elements and mounts/unmounts markers dynamically.\n */\n\nimport { safeInvoke } from './safe.js';\n\nconst ELEMENT_NODE = 1;\n\n/**\n * Internal worker that traverses everything under root (including inside open shadowRoots) once,\n * collecting both selector-matching elements and shadowRoots at the same time. It uses a single\n * `querySelectorAll('*')` pass and does both the `matches` test and shadowRoot detection within it.\n * The goal is to cut what used to be two separate full scans (\"collect matches\" and \"find shadow\n * hosts\") down to one (lightening the hot path that reacts to host DOM changes while ON).\n * @param {ParentNode} root\n * @param {string} selector\n * @param {(el: Element) => void} [onMatch]\n * @param {(shadow: ShadowRoot) => void} [onShadowRoot]\n */\nfunction walkDeep(root, selector, onMatch, onShadowRoot) {\n if (typeof root.querySelectorAll !== 'function') {\n return;\n }\n // querySelectorAll('*') does not cross shadow boundaries, so run it flatly once per root and,\n // when a shadowRoot is found, recurse into it right there (depth-first).\n root.querySelectorAll('*').forEach((el) => {\n if (onMatch && el.matches(selector)) {\n onMatch(el);\n }\n if (el.shadowRoot) {\n if (onShadowRoot) {\n onShadowRoot(el.shadowRoot);\n }\n walkDeep(el.shadowRoot, selector, onMatch, onShadowRoot);\n }\n });\n}\n\n/**\n * Collect every element under root (including inside open shadowRoots) matching selector.\n * @param {ParentNode} root\n * @param {string} selector\n * @returns {Element[]}\n */\nexport function queryAllDeep(root, selector) {\n const results = [];\n walkDeep(root, selector, (el) => results.push(el));\n return results;\n}\n\n/**\n * Collect every open shadowRoot under root (excluding root itself).\n * @param {ParentNode} root\n * @returns {ShadowRoot[]}\n */\nexport function collectShadowRoots(root) {\n const roots = [];\n walkDeep(root, '*', null, (shadow) => roots.push(shadow));\n return roots;\n}\n\n/**\n * Traverse a node and its descendants (including shadow) once, returning both selector-matching\n * elements and the descendants' open shadowRoots together. Used in MutationObserver added-node\n * handling to fold \"collect matches\" and \"add shadow observation\" into one traversal per subtree.\n * @param {Node} node\n * @param {string} selector\n * @returns {{ matches: Element[], shadowRoots: ShadowRoot[] }}\n */\nfunction scanSubtree(node, selector) {\n /** @type {Element[]} */\n const matches = [];\n /** @type {ShadowRoot[]} */\n const shadowRoots = [];\n if (node.nodeType !== ELEMENT_NODE) {\n return { matches, shadowRoots };\n }\n // node itself is not included in querySelectorAll('*'), so test it separately (equivalent to the former matchingWithin).\n const el = /** @type {Element} */ (node);\n if (typeof el.matches === 'function' && el.matches(selector)) {\n matches.push(el);\n }\n // The added node itself may be a shadow host. walkDeep only inspects the shadowRoots of\n // descendants (querySelectorAll('*') never includes the root), so handle the node's own\n // shadowRoot here before descending into the light-DOM subtree.\n if (el.shadowRoot) {\n shadowRoots.push(el.shadowRoot);\n walkDeep(el.shadowRoot, selector, (m) => matches.push(m), (shadow) => shadowRoots.push(shadow));\n }\n walkDeep(el, selector, (m) => matches.push(m), (shadow) => shadowRoots.push(shadow));\n return { matches, shadowRoots };\n}\n\n/**\n * While ON, watch root and all shadowRoots under it, notifying on entry/exit of selector-matching elements.\n * If an added element has a new shadowRoot, that shadowRoot is also added to the observation.\n *\n * @param {object} params\n * @param {ParentNode} [params.root=document]\n * @param {string} params.selector\n * @param {(el: Element) => void} params.onAdded\n * @param {(el: Element) => void} params.onRemoved\n * @returns {{ disconnect(): void }}\n */\nexport function createMutationWatcher({ root = document, selector, onAdded, onRemoved }) {\n const observed = new Set();\n\n const handle = (records) => {\n for (const record of records) {\n // For added nodes, get both \"matching elements\" and \"shadowRoots to start observing\" in one traversal per subtree.\n record.addedNodes.forEach((node) => {\n const { matches, shadowRoots } = scanSubtree(node, selector);\n // Isolate each callback: a throw on one element must not abort the rest of the batch nor\n // kill ongoing observation (which would silently stop all later SPA tracking).\n matches.forEach((el) => safeInvoke('observer onAdded', onAdded, el));\n shadowRoots.forEach(observe);\n });\n record.removedNodes.forEach((node) => {\n scanSubtree(node, selector).matches.forEach((el) => safeInvoke('observer onRemoved', onRemoved, el));\n });\n }\n };\n\n const observer = new MutationObserver(handle);\n\n function observe(target) {\n if (observed.has(target)) {\n return;\n }\n observed.add(target);\n observer.observe(target, { childList: true, subtree: true });\n }\n\n observe(root);\n collectShadowRoots(root).forEach(observe);\n\n return {\n disconnect() {\n observer.disconnect();\n observed.clear();\n },\n };\n}\n", "/**\n * Map the help-item array returned by normalizeConfig() onto actual DOM elements or free\n * placements, producing the \"help records\" the marker manager works with. Reads the DOM only;\n * never writes.\n *\n * Behavior of this module:\n * - Searches with queryAllDeep for Shadow DOM support.\n * - Emits a marker for each of multiple elements sharing the same data-help-id (correct in practice).\n * Because of that, an element-bound record uses \"the element itself\" as its id (identity).\n * - A free-placement record uses the config key as its id.\n *\n * Resolution order for title/text:\n * - First look up config by the `data-help-id` value (config always wins).\n * - If config has no match, use the element's `data-help-title` / `data-help-text` as an inline definition.\n * This lets you adopt the library with markup alone, without a config object.\n */\nimport { queryAllDeep } from './observer.js';\n\n/**\n * One marker's worth of \"help record\". Produced by matcher; consumed by markers/popup/toggle/index \u2014 the shared contract.\n * Other modules reference it via `import('./matcher.js').HelpRecord` (the same style as config.js's HelpConfig).\n * @typedef {object} HelpRecord\n * @property {Element|string} id for element-bound, the target element itself; for free placement, the config key string\n * @property {'element'|'free'} kind\n * @property {string|null} key config key (null for an inline-definition-only element)\n * @property {string} title\n * @property {string} text\n * @property {Element} [target] the target element when kind:'element'\n * @property {{top:number,left:number}} [position] the placement coordinate when kind:'free'\n */\n\n// Attribute names used for inline definitions (fixed defaults so as not to grow the API).\nexport const TITLE_ATTR = 'data-help-title';\nexport const TEXT_ATTR = 'data-help-text';\n\n/**\n * Build the selector to scan. In addition to elements with `data-help-id` (default), also pick up\n * elements that only have an inline definition (`data-help-title`).\n * A single source of truth so collectElementRecords and the MutationObserver share the same condition.\n * @param {string} [attribute] attribute name marking targets (default 'data-help-id')\n */\nexport function targetSelector(attribute = 'data-help-id') {\n return `[${attribute}], [${TITLE_ATTR}]`;\n}\n\n/** Turn element-bound items into a key->item Map. */\nexport function elementConfigMap(items) {\n const map = new Map();\n for (const item of items) {\n if (item.kind === 'element') {\n map.set(item.key, item);\n }\n }\n return map;\n}\n\n/**\n * Turn free-placement items into records.\n * @returns {HelpRecord[]}\n */\nexport function freeRecords(items) {\n return items\n .filter((item) => item.kind === 'free')\n .map((item) => ({\n id: item.key,\n kind: 'free',\n key: item.key,\n title: item.title,\n text: item.text,\n position: item.position,\n }));\n}\n\n/**\n * Build the help record for a single element. title/text prefer config; if absent, fall back to\n * the element's data-help-title / data-help-text (inline definition). If neither source yields\n * both title and text, return null (not a target).\n * (Used by both the initial scan and SPA dynamic additions.)\n * @param {string} [attribute] attribute name marking targets (default 'data-help-id')\n * @returns {HelpRecord|null}\n */\nexport function recordForElement(el, configMap, attribute = 'data-help-id') {\n const key = el.getAttribute(attribute);\n // config wins. If there's no matching key, fall back to the inline attributes.\n const item = key != null ? configMap.get(key) : undefined;\n const title = item ? item.title : el.getAttribute(TITLE_ATTR);\n const text = item ? item.text : el.getAttribute(TEXT_ATTR);\n // If both title and text aren't present, it's not a target (treated as unregistered).\n if (!title || !text) {\n return null;\n }\n return {\n id: el,\n kind: 'element',\n key,\n title,\n text,\n target: el,\n };\n}\n\n/**\n * Scan target-attribute elements under root (including Shadow DOM) and collect element-bound records.\n * Targets not in config are warned about and ignored (non-fatal). silent:true suppresses the warning.\n * @param {object[]} items\n * @param {ParentNode} [root]\n * @param {object} [options]\n * @param {boolean} [options.silent] don't warn on unregistered keys\n * @param {string} [options.attribute] attribute name marking targets (default 'data-help-id')\n * @returns {HelpRecord[]}\n */\nexport function collectElementRecords(items, root = document, { silent = false, attribute = 'data-help-id' } = {}) {\n const configMap = elementConfigMap(items);\n const records = [];\n\n queryAllDeep(root, targetSelector(attribute)).forEach((el) => {\n const record = recordForElement(el, configMap, attribute);\n if (!record) {\n if (!silent) {\n const key = el.getAttribute(attribute);\n console.warn(\n key != null\n ? `[help-layer] element with ${attribute}=\"${key}\" has no matching helpConfig entry or inline ${TITLE_ATTR}/${TEXT_ATTR}`\n : `[help-layer] element needs both ${TITLE_ATTR} and ${TEXT_ATTR} (or a ${attribute} matching helpConfig)`,\n );\n }\n return;\n }\n records.push(record);\n });\n\n return records;\n}\n", "/**\n * The single popup shared across the whole library.\n * Placed on its target (the clicked marker) via the positioning seam (anchorPopup in floating.js);\n * at screen edges, flip/shift avoid clipping. While visible it follows the marker per animation frame.\n *\n * Accessibility:\n * - On open, move focus to the popup (role=dialog).\n * - On close, return focus to the trigger element (the marker).\n */\nimport { createPopup } from './dom-builder.js';\nimport { anchorPopup } from './floating.js';\nimport { safeInvoke } from './safe.js';\n\n/**\n * @param {object} state teardown registry\n * @param {object} [options]\n * @param {() => void} [options.onClose] called when the popup closes (transitions from shown to hidden)\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render]\n * Escape hatch to render the body area with your own DOM node. Return a Node to display it;\n * if nothing is returned, fall back to safe text rendering (textContent). The title is always record.title.\n * Note: the return value is appendChild'd as-is without sanitization, so untrusted data must be neutralized by the caller.\n * @param {import('./types.js').Placement} [options.popupPlacement] initial placement (default 'bottom-start')\n */\nexport function createPopupController(state, { onClose, render, popupPlacement = 'bottom-start' } = {}) {\n const { root, titleEl, textEl, closeEl } = createPopup();\n // Drive the open/close state with an inline !important display so it beats both this library's own\n // stylesheet and any host rule (e.g. div { display:none !important }). Start hidden.\n root.style.setProperty('display', 'none', 'important');\n document.body.appendChild(root);\n\n // The close (\u00D7) button. root is removed on teardown, so explicitly detaching the listener isn't needed.\n closeEl.addEventListener('click', () => close());\n\n let openId = null;\n let triggerEl = null;\n let anchor = null;\n\n function stopAnchor() {\n if (anchor) {\n anchor.cleanup();\n anchor = null;\n }\n }\n\n // Focus trap. aria-modal=\"true\" promises AT that the rest of the page is inert, but keyboard Tab\n // would still escape to the markers/toggle behind the popup (they're \"library elements\", so the\n // blocking layer lets their keys through). Keep Tab cycling inside the dialog to match the promise.\n const FOCUSABLE = 'a[href],button,input,select,textarea,[tabindex]:not([tabindex=\"-1\"])';\n function trapTab(event) {\n if (event.key !== 'Tab') {\n return;\n }\n // Recompute every keypress: a custom render() can add its own focusables to the body. We don't\n // filter by layout visibility here \u2014 the popup's contents are controlled (a close button plus\n // whatever render returns), and a layout probe (offsetParent/getClientRects) is unreliable anyway.\n const focusable = [...root.querySelectorAll(FOCUSABLE)].filter((el) => el instanceof HTMLElement);\n event.preventDefault();\n if (focusable.length === 0) {\n // Nothing focusable inside: hold focus on the dialog itself rather than letting it escape.\n root.focus({ preventScroll: true });\n return;\n }\n const count = focusable.length;\n const index = focusable.indexOf(document.activeElement instanceof HTMLElement ? document.activeElement : null);\n // Step in the requested direction, wrapping at both ends. When focus is on the dialog root\n // (index -1), Tab starts at the first element and Shift+Tab at the last.\n const next = index === -1\n ? (event.shiftKey ? focusable[count - 1] : focusable[0])\n : focusable[(index + (event.shiftKey ? -1 : 1) + count) % count];\n next.focus({ preventScroll: true });\n }\n\n /**\n * @param {import('./matcher.js').HelpRecord} record\n * @param {HTMLElement} referenceEl placement reference (the clicked marker element)\n */\n function open(record, referenceEl) {\n titleEl.textContent = record.title;\n // If render exists, replace the body with a custom Node; otherwise fall back to safe text rendering.\n // A throwing render yields undefined here, so we degrade to the safe textContent path below.\n const custom = safeInvoke('render', render, record);\n textEl.textContent = '';\n if (custom) {\n textEl.appendChild(custom);\n } else {\n textEl.textContent = record.text;\n }\n root.style.setProperty('display', 'block', 'important');\n openId = record.id;\n triggerEl = referenceEl;\n\n stopAnchor();\n anchor = anchorPopup(referenceEl, root, popupPlacement);\n\n // Keep Tab inside the dialog while it's open (removed in hide()). Capture phase so it runs before\n // any focusable's own keydown can act on the Tab.\n root.addEventListener('keydown', trapTab, true);\n\n // preventScroll: anchorPopup positions the popup synchronously above, so it's already in place,\n // but focusing it can still nudge an ancestor scroll container toward it; flip/shift keep it in\n // the viewport, so suppressing that scroll is safe and avoids a visible jump.\n root.focus({ preventScroll: true });\n }\n\n // Reposition immediately, only when open.\n // (Called e.g. right after a marker's left/top shifts from the overlap-avoidance pass.)\n function reposition() {\n if (anchor) {\n anchor.update();\n }\n }\n\n function hide() {\n // Call onClose only if it was open (catches both the close-path and teardown-path close routes at one point).\n const wasOpen = openId !== null;\n stopAnchor();\n root.removeEventListener('keydown', trapTab, true);\n openId = null;\n triggerEl = null;\n root.style.setProperty('display', 'none', 'important');\n if (wasOpen) {\n safeInvoke('onClose', onClose);\n }\n }\n\n /**\n * Close and return focus.\n * @param {HTMLElement} [focusTarget] explicit focus-return target.\n * If omitted, returns to the trigger element (the marker). In contexts where the trigger\n * disappears (SPA removal), pass another surviving element such as the toggle.\n */\n function close(focusTarget) {\n const returnTo = focusTarget ?? triggerEl;\n hide();\n // Return focus if the target is still in the DOM.\n if (returnTo && returnTo.isConnected && typeof returnTo.focus === 'function') {\n returnTo.focus({ preventScroll: true });\n }\n }\n\n state.track(() => {\n hide();\n root.remove();\n });\n\n return {\n root,\n isOpen(id) {\n return openId === id;\n },\n getOpenId() {\n return openId;\n },\n open,\n close,\n reposition,\n };\n}\n", "/**\n * Registry of teardown callbacks.\n * DOM, listeners, and style changes added while the mode is ON are unwound in\n * reverse order of creation (LIFO), so that dependent cleanups (e.g. detach an\n * internal listener, then remove its element) run in a natural order.\n */\nexport function createState() {\n const cleanupFns = [];\n\n return {\n track(fn) {\n cleanupFns.push(fn);\n },\n teardownAll() {\n while (cleanupFns.length > 0) {\n const cleanup = cleanupFns.pop();\n // A throwing cleanup (e.g. a user onClose run during teardown) must not abort the rest of\n // the unwind, otherwise later-registered subsystems (markers, observer, styles) would leak.\n try {\n cleanup();\n } catch (err) {\n console.error('[help-layer] teardown step threw:', err);\n }\n }\n },\n };\n}\n", "/**\n * The z-index constants help-layer uses, and the CSS it injects.\n * Things that must sit above the blocking layer use Z_TOP (markers); the popup uses Z_POPUP so it\n * always paints in front of the markers (they share a stacking context as <body> children, so a tie\n * would otherwise be decided by DOM order and a remounted marker could cover an open popup).\n * The toggle is made visible through the clip-path \"hole\", so its z-index is left untouched.\n */\nexport const Z_BLOCKING_LAYER = 2147483000;\nexport const Z_TOP = 2147483001;\nexport const Z_POPUP = 2147483002;\n\nconst STYLE_ATTR = 'data-help-layer-style';\n\n// The theme is fully exposed via CSS custom properties. Users can change the look just by\n// overriding the following variables in host-side CSS (e.g. :root or any scope):\n// --help-layer-marker-size marker diameter (default 24px, WCAG 2.5.8 minimum target size)\n// --help-layer-marker-bg marker background color (default #2563eb)\n// --help-layer-marker-color marker text color (default #fff)\n// --help-layer-popup-bg popup background color (default #fff)\n// --help-layer-popup-color popup text color (default #1f2933)\n// --help-layer-popup-max-width popup max width (default 280px)\n// --help-layer-popup-max-height body max height (default 50vh, scrolls when exceeded)\n// --help-layer-accent focus ring color (default #1d4ed8)\n// --help-layer-overlay-bg blocking-layer (scrim) background (default transparent; e.g. rgba(0,0,0,0.15))\n// --help-layer-overlay-cursor cursor over the blocked area (default default; e.g. not-allowed / help)\nconst CSS = `\n.help-layer-blocking-layer {\n /* Structural properties !important so a host can't accidentally un-fix or restack the layer and\n defeat the blocking guarantee. */\n position: fixed !important;\n inset: 0 !important;\n pointer-events: auto !important;\n /* Default transparent (unchanged). Set --help-layer-overlay-bg to tint it into a scrim that signals\n \"the host app is inactive\". The clip-path hole isn't painted, so the toggle stays untinted. */\n background: var(--help-layer-overlay-bg, transparent);\n /* Cursor over the blocked area only (the toggle shows through the hole and keeps its own cursor).\n e.g. not-allowed / help makes \"this won't respond\" obvious without needing a tint. */\n cursor: var(--help-layer-overlay-cursor, default);\n z-index: ${Z_BLOCKING_LAYER} !important;\n}\n\n.help-layer-marker {\n /* reset of the button element */\n appearance: none;\n -webkit-appearance: none;\n margin: 0;\n padding: 0;\n border: none;\n /* Structural properties are !important so a host's broad rules (e.g. button { display:none }) can't\n hide or distort the marker. top/left stay non-important because place() writes them inline per\n frame; !important there would override that and pin the marker to 0,0. Theme stays var()-driven.\n Note: for targets in a position:fixed subtree, floating.js overrides this with an inline\n position:fixed !important (inline important beats this rule) so the marker doesn't jitter. */\n position: absolute !important;\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n pointer-events: auto !important;\n top: 0;\n left: 0;\n width: var(--help-layer-marker-size, 24px) !important;\n height: var(--help-layer-marker-size, 24px) !important;\n border-radius: 50%;\n background: var(--help-layer-marker-bg, #2563eb);\n color: var(--help-layer-marker-color, #fff);\n font-family: sans-serif;\n font-size: 13px;\n font-weight: bold;\n line-height: var(--help-layer-marker-size, 24px);\n text-align: center;\n cursor: pointer;\n user-select: none;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n z-index: ${Z_TOP} !important;\n}\n\n.help-layer-marker:focus-visible {\n outline: 3px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 2px;\n}\n\n.help-layer-popup {\n /* Structural !important guards against host resets; top/left stay inline (place()), and display is\n deliberately NOT !important here \u2014 popup.js toggles it via an inline !important declaration so the\n open/close state itself can also beat a host rule without this stylesheet fighting the toggle. */\n position: absolute !important;\n visibility: visible !important;\n opacity: 1 !important;\n pointer-events: auto !important;\n top: 0;\n left: 0;\n display: none;\n max-width: var(--help-layer-popup-max-width, 280px);\n background: var(--help-layer-popup-bg, #fff);\n color: var(--help-layer-popup-color, #1f2933);\n border-radius: 6px;\n padding: 12px 14px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);\n font-family: sans-serif;\n font-size: 13px;\n line-height: 1.5;\n z-index: ${Z_POPUP} !important;\n}\n\n.help-layer-popup:focus {\n outline: none;\n}\n\n.help-layer-popup:focus-visible {\n outline: 3px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 2px;\n}\n\n.help-layer-popup__title {\n font-weight: bold;\n margin-bottom: 4px;\n /* Reserve space so it doesn't overlap the \u00D7 button at the top-right. */\n padding-right: 16px;\n}\n\n.help-layer-popup__text {\n /* Render the body's \\n as line breaks (still textContent, so no XSS risk). */\n white-space: pre-line;\n /* Keep long text from spilling off-screen; only the body scrolls within the popup. */\n max-height: var(--help-layer-popup-max-height, 50vh);\n overflow-y: auto;\n}\n\n.help-layer-popup__close {\n /* reset of the button element */\n appearance: none;\n -webkit-appearance: none;\n /* Keep the close affordance visible/placed even under host button { ... } rules. */\n display: block !important;\n position: absolute !important;\n pointer-events: auto !important;\n top: 6px;\n right: 6px;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n}\n\n.help-layer-popup__close:hover {\n background: rgba(0, 0, 0, 0.08);\n}\n\n.help-layer-popup__close:focus-visible {\n outline: 2px solid var(--help-layer-accent, #1d4ed8);\n outline-offset: 1px;\n}\n\n/*\n * Show an outline on the target element only while the marker is hovered/focused (clarifies \"which element this explains\").\n * Make only the outline !important so it can beat host-side outline resets.\n */\n.help-layer-target-highlight {\n outline: 2px solid var(--help-layer-accent, #1d4ed8) !important;\n outline-offset: 2px !important;\n}\n\n/*\n * Dark-mode defaults. If the user specifies CSS variables, those always win via var(), so here we\n * only swap the dark fallback values (the properties themselves aren't re-declared).\n */\n@media (prefers-color-scheme: dark) {\n .help-layer-popup {\n background: var(--help-layer-popup-bg, #1f2933);\n color: var(--help-layer-popup-color, #e5e7eb);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.55);\n }\n}\n`;\n\n/**\n * Inject a <style> tag into head and return that element.\n * @param {string} [nonce] nonce to allow this <style> under a strict CSP (style-src 'nonce-\u2026').\n * The nonce attribute is added only when provided. If omitted, nothing is added (as before).\n */\nexport function injectStyles(nonce) {\n const styleEl = document.createElement('style');\n styleEl.setAttribute(STYLE_ATTR, '');\n // Under a CSP running style-src 'nonce-\u2026', only a <style> with a matching nonce is applied.\n if (nonce) {\n styleEl.setAttribute('nonce', nonce);\n }\n styleEl.textContent = CSS;\n document.head.appendChild(styleEl);\n return styleEl;\n}\n\n/**\n * Remove the <style> tag injected by injectStyles().\n */\nexport function removeStyles(styleEl) {\n styleEl.remove();\n}\n", "/**\n * Orchestration of the help mode's ON/OFF.\n * Starts each subsystem (style injection, marker manager, popup, blocking layer, DOM observation)\n * and aggregates their teardown into the cleanup registry (state).\n */\nimport { isolateBackgroundFromAT } from './aria-isolation.js';\nimport { activateBlockingLayer } from './blocking-layer.js';\nimport { isPlainObject, normalizeConfig, validateConfig } from './config.js';\nimport { createMarkerManager } from './markers.js';\nimport {\n collectElementRecords,\n elementConfigMap,\n freeRecords,\n recordForElement,\n targetSelector,\n} from './matcher.js';\nimport { createMutationWatcher } from './observer.js';\nimport { createPopupController } from './popup.js';\nimport { safeInvoke } from './safe.js';\nimport { createState } from './state.js';\nimport { injectStyles, removeStyles } from './style.js';\n\nfunction resolveToggleElement(toggle) {\n if (typeof toggle === 'string') {\n const el = document.querySelector(toggle);\n if (!el) {\n throw new Error(`help-layer: toggle element not found for selector \"${toggle}\"`);\n }\n return /** @type {HTMLElement} */ (el);\n }\n // Reject truthy garbage (a number, a plain object, ...) early; otherwise it would be accepted as a\n // toggle and only blow up cryptically later at toggleEl.addEventListener.\n if (toggle instanceof HTMLElement) {\n return toggle;\n }\n throw new Error('help-layer: toggle must be a CSS selector string or a DOM element');\n}\n\n/**\n * @param {object} options\n * @param {object} options.config helpConfig\n * @param {string|HTMLElement} [options.toggle] DOM element that switches ON/OFF (if omitted, programmatic control only)\n * @param {() => void} [options.onEnable] called right after the mode is turned ON\n * @param {() => void} [options.onDisable] called right after the mode is turned OFF\n * @param {(record: import('./matcher.js').HelpRecord) => void} [options.onOpen] called when a popup is opened\n * @param {() => void} [options.onClose] called when a popup is closed\n * @param {boolean} [options.silent] suppress the warning log for unregistered keys\n * @param {string} [options.attribute] attribute name marking targets (default 'data-help-id')\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render] render the popup body with your own Node\n * (the return value is inserted as-is without sanitization, so untrusted data must be neutralized by the caller)\n * @param {string} [options.markerLabel] character shown on the marker (default '?')\n * @param {import('./types.js').Placement} [options.markerPlacement] corner to overlap the marker onto (default 'top-end')\n * @param {import('./types.js').Placement} [options.popupPlacement] initial popup placement (default 'bottom-start')\n * @param {string} [options.nonce] nonce to allow the injected <style> under a strict CSP (style-src 'nonce-\u2026')\n */\nexport function createToggleController(options) {\n // Validate before destructuring so initHelpLayer() / initHelpLayer(null) get a clear message\n // instead of a cryptic \"Cannot destructure property 'config' of undefined\".\n if (!isPlainObject(options)) {\n throw new Error('help-layer: initHelpLayer requires an options object');\n }\n const {\n config,\n toggle,\n onEnable,\n onDisable,\n onOpen,\n onClose,\n silent = false,\n attribute = 'data-help-id',\n render,\n markerLabel = '?',\n markerPlacement = 'top-end',\n popupPlacement = 'bottom-start',\n nonce,\n } = options;\n\n let activeConfig = config;\n validateConfig(activeConfig);\n // The toggle element is optional. If omitted, it's driven solely by programmatic control like enable()/disable().\n const toggleEl = toggle != null ? resolveToggleElement(toggle) : null;\n\n let state = null;\n // References to the current subsystems that exist only while ON. Hoisted because open(key)/close() touch them too.\n let popup = null;\n let markers = null;\n\n // Only builds the side effects (onEnable/onDisable aren't called here; they fire on the enable/disable side).\n function turnOn() {\n if (state) {\n return;\n }\n state = createState();\n\n // On OFF, return focus to the toggle last (at the LIFO tail) (only when there is a toggle).\n if (toggleEl) {\n state.track(() => {\n if (toggleEl.isConnected && typeof toggleEl.focus === 'function') {\n toggleEl.focus({ preventScroll: true });\n }\n });\n }\n\n const styleEl = injectStyles(nonce);\n state.track(() => removeStyles(styleEl));\n\n const items = normalizeConfig(activeConfig);\n const configMap = elementConfigMap(items);\n\n popup = createPopupController(state, { onClose, render, popupPlacement });\n markers = createMarkerManager(state, {\n markerLabel,\n markerPlacement,\n onMarkerClick: (record, markerEl) => {\n if (popup.isOpen(record.id)) {\n popup.close();\n return;\n }\n popup.open(record, markerEl);\n safeInvoke('onOpen', onOpen, record);\n },\n // When overlap avoidance moves a marker, make the open popup follow.\n onOverlapResolved: () => popup.reposition(),\n // If a target is hidden (e.g. display:none) while its popup is open, close it (its marker just\n // collapsed to 0x0) \u2014 same as the SPA-removal path. Return focus to the toggle since the marker\n // is no longer focusable.\n onMarkerHidden: (record) => {\n if (popup.isOpen(record.id)) {\n popup.close(toggleEl ?? undefined);\n }\n },\n });\n\n // Initial mount (free placements + elements currently in the DOM, including Shadow DOM)\n markers.mountAll(freeRecords(items));\n markers.mountAll(collectElementRecords(items, document, { silent, attribute }));\n\n // SPA dynamic elements: follow additions/removals while ON\n const watcher = createMutationWatcher({\n selector: targetSelector(attribute),\n onAdded: (el) => {\n const record = recordForElement(el, configMap, attribute);\n if (record && !markers.has(record.id)) {\n markers.mount(record);\n }\n },\n onRemoved: (el) => {\n // Both the target and its marker disappear, so return focus to the toggle (or the default if absent).\n if (popup.isOpen(el)) {\n popup.close(toggleEl ?? undefined);\n }\n markers.unmount(el);\n },\n });\n state.track(() => watcher.disconnect());\n\n const isLibraryElement = (target) =>\n !!target &&\n ((toggleEl ? toggleEl.contains(target) : false) ||\n popup.root.contains(target) ||\n (typeof target.closest === 'function' && !!target.closest('.help-layer-marker')));\n\n const layer = activateBlockingLayer(state, {\n toggleEl,\n onBackgroundClick: () => popup.close(),\n isLibraryElement,\n onEscape: () => {\n if (popup.getOpenId() !== null) {\n popup.close();\n } else {\n disable();\n }\n },\n });\n\n // Semantic blocking for assistive tech: remove the host from the a11y tree while ON (the layer/\n // popup/markers and the toggle stay reachable). Runs last so the just-mounted library nodes are\n // present and skipped by the initial scan.\n const isLibraryNode = (el) =>\n el === layer ||\n el === popup.root ||\n (!!el.classList && el.classList.contains('help-layer-marker'));\n isolateBackgroundFromAT(state, { toggleEl, isLibraryNode });\n }\n\n function turnOff() {\n if (state) {\n state.teardownAll();\n state = null;\n popup = null;\n markers = null;\n }\n }\n\n function enable() {\n if (state) {\n return;\n }\n turnOn();\n safeInvoke('onEnable', onEnable);\n }\n\n function disable() {\n if (!state) {\n return;\n }\n turnOff();\n safeInvoke('onDisable', onDisable);\n }\n\n function toggleMode() {\n if (state) {\n disable();\n } else {\n enable();\n }\n }\n\n // Open the description for a given key programmatically. When OFF, first enable() to create the markers.\n function openByKey(key) {\n if (!state) {\n enable();\n }\n if (!markers || !popup) {\n return;\n }\n const entry = markers.findByKey(key);\n if (!entry) {\n if (!silent) {\n console.warn(`[help-layer] open(): no help marker for key \"${key}\"`);\n }\n return;\n }\n popup.open(entry.record, entry.el);\n safeInvoke('onOpen', onOpen, entry.record);\n }\n\n // Close the open description (does not turn the mode itself OFF).\n function closePopup() {\n if (popup) {\n popup.close();\n }\n }\n\n // Replace the helpConfig. If ON, rebuild silently (onEnable/onDisable are not fired).\n function update(newConfig) {\n validateConfig(newConfig);\n activeConfig = newConfig;\n if (state) {\n turnOff();\n turnOn();\n }\n }\n\n if (toggleEl) {\n toggleEl.addEventListener('click', toggleMode);\n }\n\n return {\n enable,\n disable,\n toggle: toggleMode,\n isActive() {\n return state !== null;\n },\n open: openByKey,\n close: closePopup,\n update,\n destroy() {\n disable();\n if (toggleEl) {\n toggleEl.removeEventListener('click', toggleMode);\n }\n },\n };\n}\n", "import { createToggleController } from './toggle.js';\n\n/**\n * Initialize the help mode.\n *\n * It can be toggled ON/OFF by clicking the toggle element, and also controlled programmatically\n * via the returned API. If `toggle` is omitted, there's no DOM toggle and it's programmatic-only.\n *\n * @param {object} options\n * @param {import('./config.js').HelpConfig} options.config - configuration that specifies targets by data-help-id or position.\n * Elements not in config can still be targets via the data-help-title / data-help-text inline definition (config wins)\n * @param {string|HTMLElement} [options.toggle] - toggle element that switches ON/OFF (CSS selector string or element). Optional\n * @param {() => void} [options.onEnable] - called right after the mode is turned ON\n * @param {() => void} [options.onDisable] - called right after the mode is turned OFF\n * @param {(record: import('./matcher.js').HelpRecord) => void} [options.onOpen] - called when a description popup is opened\n * @param {() => void} [options.onClose] - called when a description popup is closed\n * @param {boolean} [options.silent] - suppress the warning log for unregistered keys\n * @param {string} [options.attribute] - attribute name marking targets (default 'data-help-id')\n * @param {(record: import('./matcher.js').HelpRecord) => (Node|null|undefined)} [options.render] - render the popup body with your own DOM.\n * Return a Node to display it; if nothing is returned, fall back to safe text display (the title is always record.title).\n * \u26A0\uFE0F The return value is inserted as-is without sanitization. If it contains untrusted data, neutralize it on the caller side (XSS prevention)\n * @param {string} [options.markerLabel] - character shown on the marker (default '?')\n * @param {import('./types.js').Placement} [options.markerPlacement] - corner to overlap the marker onto (default 'top-end')\n * @param {import('./types.js').Placement} [options.popupPlacement] - initial popup placement (default 'bottom-start')\n * @param {string} [options.nonce] - nonce to allow the injected <style> under a strict CSP (style-src 'nonce-\u2026')\n * @returns {{\n * enable(): void,\n * disable(): void,\n * toggle(): void,\n * isActive(): boolean,\n * open(key: string): void,\n * close(): void,\n * update(config: import('./config.js').HelpConfig): void,\n * destroy(): void,\n * }} a handle to control the mode and fully clean up at the end.\n * open(key) opens the description for the given key (auto-enables when OFF). close() closes the open description.\n */\nexport function initHelpLayer(options) {\n return createToggleController(options);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["isolateBackgroundFromAT", "state", "toggleEl", "isLibraryNode", "isolated", "isolate", "node", "el", "child", "observer", "records", "record", "popupSeq", "createBlockingLayer", "layer", "createMarker", "title", "label", "marker", "createPopup", "seq", "titleId", "textId", "root", "titleEl", "textEl", "closeEl", "docRectToViewportRect", "docRect", "scroll", "width", "height", "left", "top", "MARKER_INSET", "markerViewportTopLeft", "refRect", "size", "placement", "side", "align", "cross", "viewportToAbsolute", "vx", "vy", "bodyRect", "clientLeft", "clientTop", "clamp", "value", "min", "max", "computePopupPosition", "popupSize", "viewport", "opts", "offset", "padding", "vertical", "mainPos", "spaceFor", "opposite", "need", "chosen", "crossPos", "extentRef", "extentPopup", "start", "makeVirtualElement", "getDocRect", "docRectToViewportRect", "isFixedReference", "reference", "node", "parent", "root", "isReferenceHidden", "r", "place", "el", "x", "y", "sameRect", "a", "b", "track", "reference", "onChange", "prev", "stopped", "frame", "loop", "rect", "onResize", "anchorPopup", "popupEl", "placement", "strategy", "isFixedReference", "update", "refRect", "popupSize", "viewport", "left", "top", "computePopupPosition", "body", "abs", "viewportToAbsolute", "cleanup", "watchReference", "referenceEl", "_floatingEl", "onUpdate", "buildClipPath", "rect", "x1", "y1", "x2", "y2", "activateBlockingLayer", "state", "toggleEl", "onBackgroundClick", "isLibraryElement", "onEscape", "layer", "createBlockingLayer", "cleanupClipWatch", "watchReference", "activeEl", "handleFocusIn", "event", "blockNonLibrary", "blockKey", "handleKeydown", "isPlainObject", "value", "isValidPosition", "position", "validateConfig", "config", "key", "entry", "normalizeConfig", "resolveOverlaps", "centers", "options", "minDistance", "iterations", "positions", "c", "iter", "moved", "i", "j", "a", "b", "dx", "dy", "dist", "overlap", "ux", "uy", "p", "TARGET_HIGHLIGHT_CLASS", "DEFAULT_MARKER_SIZE", "referenceFor", "record", "makeVirtualElement", "createMarkerManager", "state", "onMarkerClick", "onOverlapResolved", "onMarkerHidden", "markerLabel", "markerPlacement", "markers", "rafId", "tornDown", "markerSize", "prevVisibleCount", "positionAll", "bodyRect", "bodyClientLeft", "bodyClientTop", "visible", "entry", "isReferenceHidden", "measured", "size", "dirty", "bases", "centers", "bv", "markerViewportTopLeft", "be", "viewportToAbsolute", "offsets", "resolveOverlaps", "moved", "i", "left", "top", "frameTick", "ensureLoop", "mount", "el", "createMarker", "handleClick", "reference", "strategy", "isFixedReference", "target", "addHighlight", "removeHighlight", "done", "cleanup", "unmount", "id", "mountAll", "records", "key", "safeInvoke", "label", "fn", "args", "err", "ELEMENT_NODE", "walkDeep", "root", "selector", "onMatch", "onShadowRoot", "el", "queryAllDeep", "results", "collectShadowRoots", "roots", "shadow", "scanSubtree", "node", "matches", "shadowRoots", "m", "createMutationWatcher", "onAdded", "onRemoved", "observed", "handle", "records", "record", "safeInvoke", "observe", "observer", "target", "TITLE_ATTR", "TEXT_ATTR", "targetSelector", "attribute", "elementConfigMap", "items", "map", "item", "freeRecords", "recordForElement", "el", "configMap", "key", "title", "text", "collectElementRecords", "root", "silent", "records", "queryAllDeep", "record", "createPopupController", "state", "onClose", "render", "popupPlacement", "root", "titleEl", "textEl", "closeEl", "createPopup", "close", "openId", "triggerEl", "anchor", "stopAnchor", "FOCUSABLE", "trapTab", "event", "focusable", "el", "count", "index", "open", "record", "referenceEl", "custom", "safeInvoke", "anchorPopup", "reposition", "hide", "wasOpen", "focusTarget", "returnTo", "id", "createState", "cleanupFns", "fn", "cleanup", "err", "STYLE_ATTR", "CSS", "injectStyles", "nonce", "styleEl", "removeStyles", "resolveToggleElement", "toggle", "el", "createToggleController", "options", "isPlainObject", "config", "onEnable", "onDisable", "onOpen", "onClose", "silent", "attribute", "render", "markerLabel", "markerPlacement", "popupPlacement", "nonce", "activeConfig", "validateConfig", "toggleEl", "state", "popup", "markers", "turnOn", "createState", "styleEl", "injectStyles", "removeStyles", "items", "normalizeConfig", "configMap", "elementConfigMap", "createPopupController", "createMarkerManager", "record", "markerEl", "safeInvoke", "freeRecords", "collectElementRecords", "watcher", "createMutationWatcher", "targetSelector", "recordForElement", "layer", "activateBlockingLayer", "target", "disable", "isolateBackgroundFromAT", "turnOff", "enable", "toggleMode", "openByKey", "key", "entry", "closePopup", "update", "newConfig", "initHelpLayer", "options", "createToggleController"]
7
7
  }