commit 9fed1b6ef2bce09b629216894dd6f2391ead3459 Author: publish Date: Wed Aug 5 18:57:34 2026 +0200 build(utils): build26 from 45504a2b diff --git a/README.md b/README.md new file mode 100644 index 00000000..ec3fd4ef --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# @videojs/utils + +[![package-badge]][package] + +> **⚠️ Beta** Close to stable. Experimental adoption in real projects. + +## Community + +If you need help with anything related to Video.js 10, or if you'd like to casually chat with other +members: + +- [Join Discord Server][discord] +- [See GitHub Discussions][gh-discussions] + +## License + +[Apache-2.0](./LICENSE) + +[package]: https://www.npmjs.com/package/@videojs/utils +[package-badge]: https://img.shields.io/npm/v/@videojs/utils?label=@videojs/utils +[discord]: https://discord.gg/JBqHh485uF +[gh-discussions]: https://github.com/videojs/v10/discussions diff --git a/dist/array.d.ts b/dist/array.d.ts new file mode 100644 index 00000000..546c9470 --- /dev/null +++ b/dist/array.d.ts @@ -0,0 +1,2 @@ +import { uniqBy } from "./array/uniq-by.js"; +export { uniqBy }; \ No newline at end of file diff --git a/dist/array.js b/dist/array.js new file mode 100644 index 00000000..f51adf43 --- /dev/null +++ b/dist/array.js @@ -0,0 +1,2 @@ +import { uniqBy } from "./array/uniq-by.js"; +export { uniqBy }; diff --git a/dist/array/uniq-by.d.ts b/dist/array/uniq-by.d.ts new file mode 100644 index 00000000..caea7ee6 --- /dev/null +++ b/dist/array/uniq-by.d.ts @@ -0,0 +1,16 @@ +//#region src/array/uniq-by.d.ts +/** + * Returns array with duplicates removed, keeping the LAST occurrence. + * Useful for feature merging where extensions should override base features. + * + * @example + * ```ts + * const features = [{ id: 'a', v: 1 }, { id: 'b', v: 2 }, { id: 'a', v: 3 }]; + * uniqBy(features, s => s.id); + * // => [{ id: 'b', v: 2 }, { id: 'a', v: 3 }] + * ``` + */ +declare function uniqBy(arr: T[], mapper: (item: T) => K): T[]; +//#endregion +export { uniqBy }; +//# sourceMappingURL=uniq-by.d.ts.map \ No newline at end of file diff --git a/dist/array/uniq-by.d.ts.map b/dist/array/uniq-by.d.ts.map new file mode 100644 index 00000000..54a25820 --- /dev/null +++ b/dist/array/uniq-by.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"uniq-by.d.ts","names":[],"sources":["../../src/array/uniq-by.ts"],"mappings":";;;;;;;;;;;;iBAWgB,OAAO,GAAG,GAAG,KAAK,KAAK,SAAS,MAAM,MAAM,IAAI"} \ No newline at end of file diff --git a/dist/array/uniq-by.js b/dist/array/uniq-by.js new file mode 100644 index 00000000..d46da841 --- /dev/null +++ b/dist/array/uniq-by.js @@ -0,0 +1,21 @@ +//#region src/array/uniq-by.ts +/** +* Returns array with duplicates removed, keeping the LAST occurrence. +* Useful for feature merging where extensions should override base features. +* +* @example +* ```ts +* const features = [{ id: 'a', v: 1 }, { id: 'b', v: 2 }, { id: 'a', v: 3 }]; +* uniqBy(features, s => s.id); +* // => [{ id: 'b', v: 2 }, { id: 'a', v: 3 }] +* ``` +*/ +function uniqBy(arr, mapper) { + const seen = /* @__PURE__ */ new Map(); + arr.forEach((item, i) => seen.set(mapper(item), i)); + return arr.filter((_, i) => [...seen.values()].includes(i)); +} +//#endregion +export { uniqBy }; + +//# sourceMappingURL=uniq-by.js.map \ No newline at end of file diff --git a/dist/array/uniq-by.js.map b/dist/array/uniq-by.js.map new file mode 100644 index 00000000..30020cb9 --- /dev/null +++ b/dist/array/uniq-by.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uniq-by.js","names":[],"sources":["../../src/array/uniq-by.ts"],"sourcesContent":["/**\n * Returns array with duplicates removed, keeping the LAST occurrence.\n * Useful for feature merging where extensions should override base features.\n *\n * @example\n * ```ts\n * const features = [{ id: 'a', v: 1 }, { id: 'b', v: 2 }, { id: 'a', v: 3 }];\n * uniqBy(features, s => s.id);\n * // => [{ id: 'b', v: 2 }, { id: 'a', v: 3 }]\n * ```\n */\nexport function uniqBy(arr: T[], mapper: (item: T) => K): T[] {\n const seen = new Map();\n arr.forEach((item, i) => seen.set(mapper(item), i));\n return arr.filter((_, i) => [...seen.values()].includes(i));\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,OAAa,KAAU,QAA6B;CAClE,MAAM,uBAAO,IAAI,IAAe;CAChC,IAAI,SAAS,MAAM,MAAM,KAAK,IAAI,OAAO,IAAI,GAAG,CAAC,CAAC;CAClD,OAAO,IAAI,QAAQ,GAAG,MAAM,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC5D"} \ No newline at end of file diff --git a/dist/dom.d.ts b/dist/dom.d.ts new file mode 100644 index 00000000..eeb0e87f --- /dev/null +++ b/dist/dom.d.ts @@ -0,0 +1,30 @@ +import { animationFrame } from "./dom/animation-frame.js"; +import { namedNodeMapToObject, serializeAttributes } from "./dom/attributes.js"; +import { isRTL } from "./dom/direction.js"; +import { OnEventOptions, onEvent, resolveEventTarget } from "./dom/event.js"; +import { getDeepActiveElement } from "./dom/focus.js"; +import { idleCallback } from "./dom/idle-callback.js"; +import { EDITABLE_SELECTOR, INTERACTIVE_SELECTOR, isEditableElement, isEditableTarget, isInteractiveActivation, isInteractiveTarget } from "./dom/interactive.js"; +import { listen } from "./dom/listen.js"; +import { effectiveLocale } from "./dom/locale/effective-locale.js"; +import { findNearestLang } from "./dom/locale/find-nearest-lang.js"; +import { mergeLocaleOverlays } from "./dom/locale/merge-locale-overlays.js"; +import { resolveLangAttr } from "./dom/locale/resolve-lang-attr.js"; +import { subscribeAmbientLang } from "./dom/locale/subscribe-ambient-lang.js"; +import { isMacOS } from "./dom/platform.js"; +import { PositionSide, PositionSideOffsets, PositionSideOptions, getPositionedSide, tryHidePopover, tryShowPopover } from "./dom/popover.js"; +import { isDocument, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isShadowRoot } from "./dom/predicates.js"; +import { RafThrottled, rafThrottle } from "./dom/raf-throttle.js"; +import { loadScript } from "./dom/script.js"; +import { ShadowStyle, applyShadowStyles, createShadowStyle, ensureGlobalStyle } from "./dom/shadow-styles.js"; +import { getSlottedElement, querySlot } from "./dom/slotted.js"; +import { applyStyles, resolveCSSLength } from "./dom/style.js"; +import { supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback } from "./dom/supports.js"; +import { createTemplate, renderTemplate } from "./dom/template.js"; +import { CaptionOrSubtitleKind, findTrackElement, getTextTrackList, isCaptionOrSubtitleTrack } from "./dom/text-track.js"; +import { serializeTimeRanges } from "./dom/time-ranges.js"; +import { containsComposed } from "./dom/tree.js"; +import { CustomElement, CustomElementCallbacks, EventListenerFor, EventType, QueriedElement } from "./dom/types.js"; +import { walkAncestors } from "./dom/walk-ancestors.js"; +import { WebKitDocument, WebKitFullscreenElement, WebKitPresentationMode, WebKitVideoElement, WebkitAvailabilityEvent, isWebKitAirPlayCapable, supportsWebKitAirPlay } from "./dom/webkit.js"; +export { type CaptionOrSubtitleKind, type CustomElement, type CustomElementCallbacks, EDITABLE_SELECTOR, type EventListenerFor, type EventType, INTERACTIVE_SELECTOR, type OnEventOptions, type PositionSide, type PositionSideOffsets, type PositionSideOptions, type QueriedElement, type RafThrottled, type ShadowStyle, type WebKitDocument, type WebKitFullscreenElement, type WebKitPresentationMode, type WebKitVideoElement, type WebkitAvailabilityEvent, animationFrame, applyShadowStyles, applyStyles, containsComposed, createShadowStyle, createTemplate, effectiveLocale, ensureGlobalStyle, findNearestLang, findTrackElement, getDeepActiveElement, getPositionedSide, getSlottedElement, getTextTrackList, idleCallback, isCaptionOrSubtitleTrack, isDocument, isEditableElement, isEditableTarget, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isInteractiveActivation, isInteractiveTarget, isMacOS, isRTL, isShadowRoot, isWebKitAirPlayCapable, listen, loadScript, mergeLocaleOverlays, namedNodeMapToObject, findNearestLang as nearestLang, onEvent, querySlot, rafThrottle, renderTemplate, resolveCSSLength, resolveEventTarget, resolveLangAttr, serializeAttributes, serializeTimeRanges, subscribeAmbientLang, supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback, supportsWebKitAirPlay, tryHidePopover, tryShowPopover, walkAncestors }; \ No newline at end of file diff --git a/dist/dom.js b/dist/dom.js new file mode 100644 index 00000000..e3936099 --- /dev/null +++ b/dist/dom.js @@ -0,0 +1,29 @@ +import { animationFrame } from "./dom/animation-frame.js"; +import { namedNodeMapToObject, serializeAttributes } from "./dom/attributes.js"; +import { isRTL } from "./dom/direction.js"; +import { onEvent, resolveEventTarget } from "./dom/event.js"; +import { getDeepActiveElement } from "./dom/focus.js"; +import { supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback } from "./dom/supports.js"; +import { idleCallback } from "./dom/idle-callback.js"; +import { EDITABLE_SELECTOR, INTERACTIVE_SELECTOR, isEditableElement, isEditableTarget, isInteractiveActivation, isInteractiveTarget } from "./dom/interactive.js"; +import { listen } from "./dom/listen.js"; +import { effectiveLocale } from "./dom/locale/effective-locale.js"; +import { walkAncestors } from "./dom/walk-ancestors.js"; +import { findNearestLang } from "./dom/locale/find-nearest-lang.js"; +import { mergeLocaleOverlays } from "./dom/locale/merge-locale-overlays.js"; +import { resolveLangAttr } from "./dom/locale/resolve-lang-attr.js"; +import { subscribeAmbientLang } from "./dom/locale/subscribe-ambient-lang.js"; +import { isMacOS } from "./dom/platform.js"; +import { getPositionedSide, tryHidePopover, tryShowPopover } from "./dom/popover.js"; +import { isDocument, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isShadowRoot } from "./dom/predicates.js"; +import { rafThrottle } from "./dom/raf-throttle.js"; +import { loadScript } from "./dom/script.js"; +import { applyShadowStyles, createShadowStyle, ensureGlobalStyle } from "./dom/shadow-styles.js"; +import { getSlottedElement, querySlot } from "./dom/slotted.js"; +import { applyStyles, resolveCSSLength } from "./dom/style.js"; +import { createTemplate, renderTemplate } from "./dom/template.js"; +import { findTrackElement, getTextTrackList, isCaptionOrSubtitleTrack } from "./dom/text-track.js"; +import { serializeTimeRanges } from "./dom/time-ranges.js"; +import { containsComposed } from "./dom/tree.js"; +import { isWebKitAirPlayCapable, supportsWebKitAirPlay } from "./dom/webkit.js"; +export { EDITABLE_SELECTOR, INTERACTIVE_SELECTOR, animationFrame, applyShadowStyles, applyStyles, containsComposed, createShadowStyle, createTemplate, effectiveLocale, ensureGlobalStyle, findNearestLang, findTrackElement, getDeepActiveElement, getPositionedSide, getSlottedElement, getTextTrackList, idleCallback, isCaptionOrSubtitleTrack, isDocument, isEditableElement, isEditableTarget, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isInteractiveActivation, isInteractiveTarget, isMacOS, isRTL, isShadowRoot, isWebKitAirPlayCapable, listen, loadScript, mergeLocaleOverlays, namedNodeMapToObject, findNearestLang as nearestLang, onEvent, querySlot, rafThrottle, renderTemplate, resolveCSSLength, resolveEventTarget, resolveLangAttr, serializeAttributes, serializeTimeRanges, subscribeAmbientLang, supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback, supportsWebKitAirPlay, tryHidePopover, tryShowPopover, walkAncestors }; diff --git a/dist/dom/animation-frame.d.ts b/dist/dom/animation-frame.d.ts new file mode 100644 index 00000000..6d12f520 --- /dev/null +++ b/dist/dom/animation-frame.d.ts @@ -0,0 +1,14 @@ +//#region src/dom/animation-frame.d.ts +/** + * Request an animation frame with cleanup. + * + * @example + * ```ts + * const cancel = animationFrame((time) => console.log('Frame at', time)); + * cancel(); // Cancel if needed + * ``` + */ +declare function animationFrame(callback: FrameRequestCallback): () => void; +//#endregion +export { animationFrame }; +//# sourceMappingURL=animation-frame.d.ts.map \ No newline at end of file diff --git a/dist/dom/animation-frame.d.ts.map b/dist/dom/animation-frame.d.ts.map new file mode 100644 index 00000000..bb3f2aa0 --- /dev/null +++ b/dist/dom/animation-frame.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"animation-frame.d.ts","names":[],"sources":["../../src/dom/animation-frame.ts"],"mappings":";;;;;;;;;;iBASgB,eAAe,UAAU"} \ No newline at end of file diff --git a/dist/dom/animation-frame.js b/dist/dom/animation-frame.js new file mode 100644 index 00000000..6ff98ef0 --- /dev/null +++ b/dist/dom/animation-frame.js @@ -0,0 +1,18 @@ +//#region src/dom/animation-frame.ts +/** +* Request an animation frame with cleanup. +* +* @example +* ```ts +* const cancel = animationFrame((time) => console.log('Frame at', time)); +* cancel(); // Cancel if needed +* ``` +*/ +function animationFrame(callback) { + const id = requestAnimationFrame(callback); + return () => cancelAnimationFrame(id); +} +//#endregion +export { animationFrame }; + +//# sourceMappingURL=animation-frame.js.map \ No newline at end of file diff --git a/dist/dom/animation-frame.js.map b/dist/dom/animation-frame.js.map new file mode 100644 index 00000000..0f0308f5 --- /dev/null +++ b/dist/dom/animation-frame.js.map @@ -0,0 +1 @@ +{"version":3,"file":"animation-frame.js","names":[],"sources":["../../src/dom/animation-frame.ts"],"sourcesContent":["/**\n * Request an animation frame with cleanup.\n *\n * @example\n * ```ts\n * const cancel = animationFrame((time) => console.log('Frame at', time));\n * cancel(); // Cancel if needed\n * ```\n */\nexport function animationFrame(callback: FrameRequestCallback): () => void {\n const id = requestAnimationFrame(callback);\n return () => cancelAnimationFrame(id);\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,eAAe,UAA4C;CACzE,MAAM,KAAK,sBAAsB,QAAQ;CACzC,aAAa,qBAAqB,EAAE;AACtC"} \ No newline at end of file diff --git a/dist/dom/attributes.d.ts b/dist/dom/attributes.d.ts new file mode 100644 index 00000000..84af0cff --- /dev/null +++ b/dist/dom/attributes.d.ts @@ -0,0 +1,12 @@ +//#region src/dom/attributes.d.ts +/** + * Convert a NamedNodeMap to a plain object. + */ +declare function namedNodeMapToObject(namedNodeMap: NamedNodeMap): Record; +/** + * Helper function to serialize attributes into a string. + */ +declare function serializeAttributes(attrs: Record): string; +//#endregion +export { namedNodeMapToObject, serializeAttributes }; +//# sourceMappingURL=attributes.d.ts.map \ No newline at end of file diff --git a/dist/dom/attributes.d.ts.map b/dist/dom/attributes.d.ts.map new file mode 100644 index 00000000..e55e99a6 --- /dev/null +++ b/dist/dom/attributes.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"attributes.d.ts","names":[],"sources":["../../src/dom/attributes.ts"],"mappings":";;;;iBAKgB,qBAAqB,cAAc,eAAY;;;;iBAW/C,oBAAoB,OAAO"} \ No newline at end of file diff --git a/dist/dom/attributes.js b/dist/dom/attributes.js new file mode 100644 index 00000000..5180daf0 --- /dev/null +++ b/dist/dom/attributes.js @@ -0,0 +1,26 @@ +import { escapeHtml } from "../string/escape-html.js"; +//#region src/dom/attributes.ts +/** +* Convert a NamedNodeMap to a plain object. +*/ +function namedNodeMapToObject(namedNodeMap) { + const obj = {}; + for (const attr of namedNodeMap) obj[attr.name] = attr.value; + return obj; +} +/** +* Helper function to serialize attributes into a string. +*/ +function serializeAttributes(attrs) { + let html = ""; + for (const key in attrs) { + const value = attrs[key]; + if (value === "") html += ` ${key}`; + else html += ` ${key}="${escapeHtml(value)}"`; + } + return html; +} +//#endregion +export { namedNodeMapToObject, serializeAttributes }; + +//# sourceMappingURL=attributes.js.map \ No newline at end of file diff --git a/dist/dom/attributes.js.map b/dist/dom/attributes.js.map new file mode 100644 index 00000000..9b77b886 --- /dev/null +++ b/dist/dom/attributes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"attributes.js","names":[],"sources":["../../src/dom/attributes.ts"],"sourcesContent":["import { escapeHtml } from '../string/escape-html';\n\n/**\n * Convert a NamedNodeMap to a plain object.\n */\nexport function namedNodeMapToObject(namedNodeMap: NamedNodeMap) {\n const obj: Record = {};\n for (const attr of namedNodeMap) {\n obj[attr.name] = attr.value;\n }\n return obj;\n}\n\n/**\n * Helper function to serialize attributes into a string.\n */\nexport function serializeAttributes(attrs: Record) {\n let html = '';\n for (const key in attrs) {\n const value = attrs[key]!;\n if (value === '') html += ` ${key}`;\n else html += ` ${key}=\"${escapeHtml(value)}\"`;\n }\n return html;\n}\n"],"mappings":";;;;;AAKA,SAAgB,qBAAqB,cAA4B;CAC/D,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,QAAQ,cACjB,IAAI,KAAK,QAAQ,KAAK;CAExB,OAAO;AACT;;;;AAKA,SAAgB,oBAAoB,OAA+B;CACjE,IAAI,OAAO;CACX,KAAK,MAAM,OAAO,OAAO;EACvB,MAAM,QAAQ,MAAM;EACpB,IAAI,UAAU,IAAI,QAAQ,IAAI;OACzB,QAAQ,IAAI,IAAI,IAAI,WAAW,KAAK,EAAE;CAC7C;CACA,OAAO;AACT"} \ No newline at end of file diff --git a/dist/dom/direction.d.ts b/dist/dom/direction.d.ts new file mode 100644 index 00000000..3da23a34 --- /dev/null +++ b/dist/dom/direction.d.ts @@ -0,0 +1,6 @@ +//#region src/dom/direction.d.ts +/** Check whether an element's text direction is right-to-left. */ +declare function isRTL(element: Element): boolean; +//#endregion +export { isRTL }; +//# sourceMappingURL=direction.d.ts.map \ No newline at end of file diff --git a/dist/dom/direction.d.ts.map b/dist/dom/direction.d.ts.map new file mode 100644 index 00000000..070308f9 --- /dev/null +++ b/dist/dom/direction.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"direction.d.ts","names":[],"sources":["../../src/dom/direction.ts"],"mappings":";;iBACgB,MAAM,SAAS"} \ No newline at end of file diff --git a/dist/dom/direction.js b/dist/dom/direction.js new file mode 100644 index 00000000..7782c192 --- /dev/null +++ b/dist/dom/direction.js @@ -0,0 +1,11 @@ +//#region src/dom/direction.ts +/** Check whether an element's text direction is right-to-left. */ +function isRTL(element) { + const dir = element.closest("[dir]")?.getAttribute("dir"); + if (dir) return dir.toLowerCase() === "rtl"; + return getComputedStyle(element).direction === "rtl"; +} +//#endregion +export { isRTL }; + +//# sourceMappingURL=direction.js.map \ No newline at end of file diff --git a/dist/dom/direction.js.map b/dist/dom/direction.js.map new file mode 100644 index 00000000..478485a5 --- /dev/null +++ b/dist/dom/direction.js.map @@ -0,0 +1 @@ +{"version":3,"file":"direction.js","names":[],"sources":["../../src/dom/direction.ts"],"sourcesContent":["/** Check whether an element's text direction is right-to-left. */\nexport function isRTL(element: Element): boolean {\n const dir = element.closest('[dir]')?.getAttribute('dir');\n if (dir) return dir.toLowerCase() === 'rtl';\n return getComputedStyle(element).direction === 'rtl';\n}\n"],"mappings":";;AACA,SAAgB,MAAM,SAA2B;CAC/C,MAAM,MAAM,QAAQ,QAAQ,OAAO,CAAC,EAAE,aAAa,KAAK;CACxD,IAAI,KAAK,OAAO,IAAI,YAAY,MAAM;CACtC,OAAO,iBAAiB,OAAO,CAAC,CAAC,cAAc;AACjD"} \ No newline at end of file diff --git a/dist/dom/event.d.ts b/dist/dom/event.d.ts new file mode 100644 index 00000000..887d357d --- /dev/null +++ b/dist/dom/event.d.ts @@ -0,0 +1,27 @@ +//#region src/dom/event.d.ts +/** Resolve the deepest event target, preferring composedPath for shadow DOM. */ +declare function resolveEventTarget(event: Event): EventTarget | null; +interface OnEventOptions extends AddEventListenerOptions { + /** + * An AbortSignal to cancel waiting for the event. + * + * If aborted, the returned promise will reject with an `AbortError`. + */ + signal?: AbortSignal; +} +/** + * Wait for an event to occur on a target. + * + * @example + * ```ts + * const event = await onEvent(video, 'seeked'); + * ``` + */ +declare function onEvent(target: HTMLMediaElement, type: K, options?: OnEventOptions): Promise; +declare function onEvent(target: HTMLElement, type: K, options?: OnEventOptions): Promise; +declare function onEvent(target: Window, type: K, options?: OnEventOptions): Promise; +declare function onEvent(target: Document, type: K, options?: OnEventOptions): Promise; +declare function onEvent(target: EventTarget, type: string, options?: OnEventOptions): Promise; +//#endregion +export { OnEventOptions, onEvent, resolveEventTarget }; +//# sourceMappingURL=event.d.ts.map \ No newline at end of file diff --git a/dist/dom/event.d.ts.map b/dist/dom/event.d.ts.map new file mode 100644 index 00000000..bddcee7c --- /dev/null +++ b/dist/dom/event.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"event.d.ts","names":[],"sources":["../../src/dom/event.ts"],"mappings":";;iBACgB,mBAAmB,OAAO,QAAQ;UAKjC,uBAAuB;;;;;;EAMtC,SAAS;;;;;;;;;;iBAWK,QAAQ,gBAAgB,0BACtC,QAAQ,kBACR,MAAM,GACN,UAAU,iBACT,QAAQ,yBAAyB;iBAEpB,QAAQ,gBAAgB,qBACtC,QAAQ,aACR,MAAM,GACN,UAAU,iBACT,QAAQ,oBAAoB;iBAEf,QAAQ,gBAAgB,gBACtC,QAAQ,QACR,MAAM,GACN,UAAU,iBACT,QAAQ,eAAe;iBAEV,QAAQ,gBAAgB,kBACtC,QAAQ,UACR,MAAM,GACN,UAAU,iBACT,QAAQ,iBAAiB;iBAEZ,QAAQ,QAAQ,aAAa,cAAc,UAAU,iBAAiB,QAAQ"} \ No newline at end of file diff --git a/dist/dom/event.js b/dist/dom/event.js new file mode 100644 index 00000000..ae60a93e --- /dev/null +++ b/dist/dom/event.js @@ -0,0 +1,29 @@ +//#region src/dom/event.ts +/** Resolve the deepest event target, preferring composedPath for shadow DOM. */ +function resolveEventTarget(event) { + const path = event.composedPath(); + return path.length > 0 ? path[0] : event.target; +} +function onEvent(target, type, options) { + return new Promise((resolve, reject) => { + const handleAbort = () => { + reject(options?.signal?.reason ?? "Aborted"); + }; + if (options?.signal?.aborted) { + handleAbort(); + return; + } + options?.signal?.addEventListener("abort", handleAbort, { once: true }); + target.addEventListener(type, (event) => { + options?.signal?.removeEventListener("abort", handleAbort); + resolve(event); + }, { + ...options, + once: true + }); + }); +} +//#endregion +export { onEvent, resolveEventTarget }; + +//# sourceMappingURL=event.js.map \ No newline at end of file diff --git a/dist/dom/event.js.map b/dist/dom/event.js.map new file mode 100644 index 00000000..566ddf34 --- /dev/null +++ b/dist/dom/event.js.map @@ -0,0 +1 @@ +{"version":3,"file":"event.js","names":[],"sources":["../../src/dom/event.ts"],"sourcesContent":["/** Resolve the deepest event target, preferring composedPath for shadow DOM. */\nexport function resolveEventTarget(event: Event): EventTarget | null {\n const path = event.composedPath();\n return path.length > 0 ? path[0]! : event.target;\n}\n\nexport interface OnEventOptions extends AddEventListenerOptions {\n /**\n * An AbortSignal to cancel waiting for the event.\n *\n * If aborted, the returned promise will reject with an `AbortError`.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Wait for an event to occur on a target.\n *\n * @example\n * ```ts\n * const event = await onEvent(video, 'seeked');\n * ```\n */\nexport function onEvent(\n target: HTMLMediaElement,\n type: K,\n options?: OnEventOptions\n): Promise;\n\nexport function onEvent(\n target: HTMLElement,\n type: K,\n options?: OnEventOptions\n): Promise;\n\nexport function onEvent(\n target: Window,\n type: K,\n options?: OnEventOptions\n): Promise;\n\nexport function onEvent(\n target: Document,\n type: K,\n options?: OnEventOptions\n): Promise;\n\nexport function onEvent(target: EventTarget, type: string, options?: OnEventOptions): Promise;\n\nexport function onEvent(target: EventTarget, type: string, options?: OnEventOptions): Promise {\n return new Promise((resolve, reject) => {\n const handleAbort = () => {\n reject(options?.signal?.reason ?? 'Aborted');\n };\n\n // If already aborted, reject immediately\n if (options?.signal?.aborted) {\n handleAbort();\n return;\n }\n\n // Listen for abort\n options?.signal?.addEventListener('abort', handleAbort, { once: true });\n\n // Listen for the event\n target.addEventListener(\n type,\n (event) => {\n options?.signal?.removeEventListener('abort', handleAbort);\n resolve(event);\n },\n { ...options, once: true }\n );\n });\n}\n"],"mappings":";;AACA,SAAgB,mBAAmB,OAAkC;CACnE,MAAM,OAAO,MAAM,aAAa;CAChC,OAAO,KAAK,SAAS,IAAI,KAAK,KAAM,MAAM;AAC5C;AA6CA,SAAgB,QAAQ,QAAqB,MAAc,SAA0C;CACnG,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,oBAAoB;GACxB,OAAO,SAAS,QAAQ,UAAU,SAAS;EAC7C;EAGA,IAAI,SAAS,QAAQ,SAAS;GAC5B,YAAY;GACZ;EACF;EAGA,SAAS,QAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;EAGtE,OAAO,iBACL,OACC,UAAU;GACT,SAAS,QAAQ,oBAAoB,SAAS,WAAW;GACzD,QAAQ,KAAK;EACf,GACA;GAAE,GAAG;GAAS,MAAM;EAAK,CAC3B;CACF,CAAC;AACH"} \ No newline at end of file diff --git a/dist/dom/focus.d.ts b/dist/dom/focus.d.ts new file mode 100644 index 00000000..506a4dd2 --- /dev/null +++ b/dist/dom/focus.d.ts @@ -0,0 +1,5 @@ +//#region src/dom/focus.d.ts +declare function getDeepActiveElement(root?: Document | ShadowRoot): Element | null; +//#endregion +export { getDeepActiveElement }; +//# sourceMappingURL=focus.d.ts.map \ No newline at end of file diff --git a/dist/dom/focus.d.ts.map b/dist/dom/focus.d.ts.map new file mode 100644 index 00000000..8c6d3335 --- /dev/null +++ b/dist/dom/focus.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"focus.d.ts","names":[],"sources":["../../src/dom/focus.ts"],"mappings":";iBAAgB,qBAAqB,OAAM,WAAW,aAAwB"} \ No newline at end of file diff --git a/dist/dom/focus.js b/dist/dom/focus.js new file mode 100644 index 00000000..36c28c3c --- /dev/null +++ b/dist/dom/focus.js @@ -0,0 +1,10 @@ +//#region src/dom/focus.ts +function getDeepActiveElement(root = document) { + let active = root.activeElement; + while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement; + return active; +} +//#endregion +export { getDeepActiveElement }; + +//# sourceMappingURL=focus.js.map \ No newline at end of file diff --git a/dist/dom/focus.js.map b/dist/dom/focus.js.map new file mode 100644 index 00000000..2c148814 --- /dev/null +++ b/dist/dom/focus.js.map @@ -0,0 +1 @@ +{"version":3,"file":"focus.js","names":[],"sources":["../../src/dom/focus.ts"],"sourcesContent":["export function getDeepActiveElement(root: Document | ShadowRoot = document): Element | null {\n let active = root.activeElement;\n\n while (active?.shadowRoot?.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n\n return active;\n}\n"],"mappings":";AAAA,SAAgB,qBAAqB,OAA8B,UAA0B;CAC3F,IAAI,SAAS,KAAK;CAElB,OAAO,QAAQ,YAAY,eACzB,SAAS,OAAO,WAAW;CAG7B,OAAO;AACT"} \ No newline at end of file diff --git a/dist/dom/idle-callback.d.ts b/dist/dom/idle-callback.d.ts new file mode 100644 index 00000000..9ba03276 --- /dev/null +++ b/dist/dom/idle-callback.d.ts @@ -0,0 +1,14 @@ +//#region src/dom/idle-callback.d.ts +/** + * Request an idle callback with cleanup. Falls back to setTimeout for Safari. + * + * @example + * ```ts + * const cancel = idleCallback(doWork, { timeout: 1000 }); + * cancel(); // Cancel if needed + * ``` + */ +declare function idleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): () => void; +//#endregion +export { idleCallback }; +//# sourceMappingURL=idle-callback.d.ts.map \ No newline at end of file diff --git a/dist/dom/idle-callback.d.ts.map b/dist/dom/idle-callback.d.ts.map new file mode 100644 index 00000000..4cce3545 --- /dev/null +++ b/dist/dom/idle-callback.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"idle-callback.d.ts","names":[],"sources":["../../src/dom/idle-callback.ts"],"mappings":";;;;;;;;;;iBAWgB,aAAa,UAAU,qBAAqB,UAAU"} \ No newline at end of file diff --git a/dist/dom/idle-callback.js b/dist/dom/idle-callback.js new file mode 100644 index 00000000..744a81fb --- /dev/null +++ b/dist/dom/idle-callback.js @@ -0,0 +1,28 @@ +import { supportsIdleCallback } from "./supports.js"; +//#region src/dom/idle-callback.ts +/** +* Request an idle callback with cleanup. Falls back to setTimeout for Safari. +* +* @example +* ```ts +* const cancel = idleCallback(doWork, { timeout: 1000 }); +* cancel(); // Cancel if needed +* ``` +*/ +function idleCallback(callback, options) { + if (supportsIdleCallback()) { + const id = requestIdleCallback(callback, options); + return () => cancelIdleCallback(id); + } + const id = setTimeout(() => { + callback({ + didTimeout: false, + timeRemaining: () => 50 + }); + }, 1); + return () => clearTimeout(id); +} +//#endregion +export { idleCallback }; + +//# sourceMappingURL=idle-callback.js.map \ No newline at end of file diff --git a/dist/dom/idle-callback.js.map b/dist/dom/idle-callback.js.map new file mode 100644 index 00000000..286fb642 --- /dev/null +++ b/dist/dom/idle-callback.js.map @@ -0,0 +1 @@ +{"version":3,"file":"idle-callback.js","names":[],"sources":["../../src/dom/idle-callback.ts"],"sourcesContent":["import { supportsIdleCallback } from './supports';\n\n/**\n * Request an idle callback with cleanup. Falls back to setTimeout for Safari.\n *\n * @example\n * ```ts\n * const cancel = idleCallback(doWork, { timeout: 1000 });\n * cancel(); // Cancel if needed\n * ```\n */\nexport function idleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): () => void {\n if (supportsIdleCallback()) {\n const id = requestIdleCallback(callback, options);\n return () => cancelIdleCallback(id);\n }\n\n // Fallback for Safari and other browsers without requestIdleCallback\n const id = setTimeout(() => {\n callback({\n didTimeout: false,\n timeRemaining: () => 50, // Approximate idle time\n });\n }, 1);\n\n return () => clearTimeout(id);\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,aAAa,UAA+B,SAA0C;CACpG,IAAI,qBAAqB,GAAG;EAC1B,MAAM,KAAK,oBAAoB,UAAU,OAAO;EAChD,aAAa,mBAAmB,EAAE;CACpC;CAGA,MAAM,KAAK,iBAAiB;EAC1B,SAAS;GACP,YAAY;GACZ,qBAAqB;EACvB,CAAC;CACH,GAAG,CAAC;CAEJ,aAAa,aAAa,EAAE;AAC9B"} \ No newline at end of file diff --git a/dist/dom/interactive.d.ts b/dist/dom/interactive.d.ts new file mode 100644 index 00000000..9a4704ce --- /dev/null +++ b/dist/dom/interactive.d.ts @@ -0,0 +1,13 @@ +//#region src/dom/interactive.d.ts +declare const INTERACTIVE_SELECTOR: string; +declare const EDITABLE_SELECTOR: string; +declare function isEditableElement(el: Element): boolean; +/** Whether the keyboard event target is an editable element (input, textarea, etc). */ +declare function isEditableTarget(event: KeyboardEvent): boolean; +/** Whether the event originated from an interactive control (button, slider, etc). */ +declare function isInteractiveTarget(event: Event): boolean; +/** Whether the event is an activation key on an activatable element (button, link, slider). */ +declare function isInteractiveActivation(event: KeyboardEvent): boolean; +//#endregion +export { EDITABLE_SELECTOR, INTERACTIVE_SELECTOR, isEditableElement, isEditableTarget, isInteractiveActivation, isInteractiveTarget }; +//# sourceMappingURL=interactive.d.ts.map \ No newline at end of file diff --git a/dist/dom/interactive.d.ts.map b/dist/dom/interactive.d.ts.map new file mode 100644 index 00000000..88928dd8 --- /dev/null +++ b/dist/dom/interactive.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"interactive.d.ts","names":[],"sources":["../../src/dom/interactive.ts"],"mappings":";cAEa;cAiBA;iBAQG,kBAAkB,IAAI;;iBAKtB,iBAAiB,OAAO;;iBAMxB,oBAAoB,OAAO;;iBAgB3B,wBAAwB,OAAO"} \ No newline at end of file diff --git a/dist/dom/interactive.js b/dist/dom/interactive.js new file mode 100644 index 00000000..0f0523bc --- /dev/null +++ b/dist/dom/interactive.js @@ -0,0 +1,62 @@ +import { resolveEventTarget } from "./event.js"; +//#region src/dom/interactive.ts +const INTERACTIVE_SELECTOR = [ + "button", + "input", + "select", + "textarea", + "a[href]", + "[role=\"button\"]", + "[role=\"menu\"]", + "[role=\"menuitem\"]", + "[role=\"menuitemcheckbox\"]", + "[role=\"menuitemradio\"]", + "[role=\"slider\"]", + "[data-interactive]" +].join(","); +const EDITABLE_SELECTOR = [ + "textarea", + "select", + "input:not([type])", + ...[ + "text", + "search", + "url", + "tel", + "email", + "password", + "number" + ].map((type) => `input[type="${type}"]`), + "[contenteditable]:not([contenteditable=\"false\"])" +].join(","); +function isEditableElement(el) { + return el.matches(EDITABLE_SELECTOR); +} +/** Whether the keyboard event target is an editable element (input, textarea, etc). */ +function isEditableTarget(event) { + const target = resolveEventTarget(event); + return target instanceof Element && isEditableElement(target); +} +/** Whether the event originated from an interactive control (button, slider, etc). */ +function isInteractiveTarget(event) { + const target = resolveEventTarget(event); + if (!(target instanceof Element)) return false; + return target.closest(INTERACTIVE_SELECTOR) !== null; +} +const ACTIVATION_KEYS = /* @__PURE__ */ new Set([" ", "Enter"]); +/** +* Selector for elements that use Space/Enter as a native activation key. +* Narrower than `INTERACTIVE_SELECTOR` — excludes editable elements like +* `input`, `textarea`, `select` where Space/Enter is text input, not activation. +*/ +const ACTIVATABLE_SELECTOR = "button,a[href],[role=\"slider\"],[role=\"button\"]"; +/** Whether the event is an activation key on an activatable element (button, link, slider). */ +function isInteractiveActivation(event) { + if (!ACTIVATION_KEYS.has(event.key)) return false; + const target = resolveEventTarget(event); + return target instanceof Element && target.matches(ACTIVATABLE_SELECTOR); +} +//#endregion +export { EDITABLE_SELECTOR, INTERACTIVE_SELECTOR, isEditableElement, isEditableTarget, isInteractiveActivation, isInteractiveTarget }; + +//# sourceMappingURL=interactive.js.map \ No newline at end of file diff --git a/dist/dom/interactive.js.map b/dist/dom/interactive.js.map new file mode 100644 index 00000000..b5aef2d5 --- /dev/null +++ b/dist/dom/interactive.js.map @@ -0,0 +1 @@ +{"version":3,"file":"interactive.js","names":[],"sources":["../../src/dom/interactive.ts"],"sourcesContent":["import { resolveEventTarget } from './event';\n\nexport const INTERACTIVE_SELECTOR = [\n 'button',\n 'input',\n 'select',\n 'textarea',\n 'a[href]',\n '[role=\"button\"]',\n '[role=\"menu\"]',\n '[role=\"menuitem\"]',\n '[role=\"menuitemcheckbox\"]',\n '[role=\"menuitemradio\"]',\n '[role=\"slider\"]',\n '[data-interactive]',\n].join(',');\n\nconst EDITABLE_INPUT_TYPES = ['text', 'search', 'url', 'tel', 'email', 'password', 'number'];\n\nexport const EDITABLE_SELECTOR = [\n 'textarea',\n 'select',\n 'input:not([type])',\n ...EDITABLE_INPUT_TYPES.map((type) => `input[type=\"${type}\"]`),\n '[contenteditable]:not([contenteditable=\"false\"])',\n].join(',');\n\nexport function isEditableElement(el: Element): boolean {\n return el.matches(EDITABLE_SELECTOR);\n}\n\n/** Whether the keyboard event target is an editable element (input, textarea, etc). */\nexport function isEditableTarget(event: KeyboardEvent): boolean {\n const target = resolveEventTarget(event);\n return target instanceof Element && isEditableElement(target);\n}\n\n/** Whether the event originated from an interactive control (button, slider, etc). */\nexport function isInteractiveTarget(event: Event): boolean {\n const target = resolveEventTarget(event);\n if (!(target instanceof Element)) return false;\n return target.closest(INTERACTIVE_SELECTOR) !== null;\n}\n\nconst ACTIVATION_KEYS = new Set([' ', 'Enter']);\n\n/**\n * Selector for elements that use Space/Enter as a native activation key.\n * Narrower than `INTERACTIVE_SELECTOR` — excludes editable elements like\n * `input`, `textarea`, `select` where Space/Enter is text input, not activation.\n */\nconst ACTIVATABLE_SELECTOR = 'button,a[href],[role=\"slider\"],[role=\"button\"]';\n\n/** Whether the event is an activation key on an activatable element (button, link, slider). */\nexport function isInteractiveActivation(event: KeyboardEvent): boolean {\n if (!ACTIVATION_KEYS.has(event.key)) return false;\n\n const target = resolveEventTarget(event);\n return target instanceof Element && target.matches(ACTIVATABLE_SELECTOR);\n}\n"],"mappings":";;AAEA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;AAIV,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA,GAAG;EANyB;EAAQ;EAAU;EAAO;EAAO;EAAS;EAAY;CAM9E,CAAA,CAAqB,KAAK,SAAS,eAAe,KAAK,GAAG;CAC7D;AACF,CAAC,CAAC,KAAK,GAAG;AAEV,SAAgB,kBAAkB,IAAsB;CACtD,OAAO,GAAG,QAAQ,iBAAiB;AACrC;;AAGA,SAAgB,iBAAiB,OAA+B;CAC9D,MAAM,SAAS,mBAAmB,KAAK;CACvC,OAAO,kBAAkB,WAAW,kBAAkB,MAAM;AAC9D;;AAGA,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,SAAS,mBAAmB,KAAK;CACvC,IAAI,EAAE,kBAAkB,UAAU,OAAO;CACzC,OAAO,OAAO,QAAQ,oBAAoB,MAAM;AAClD;AAEA,MAAM,kCAAkB,IAAI,IAAI,CAAC,KAAK,OAAO,CAAC;;;;;;AAO9C,MAAM,uBAAuB;;AAG7B,SAAgB,wBAAwB,OAA+B;CACrE,IAAI,CAAC,gBAAgB,IAAI,MAAM,GAAG,GAAG,OAAO;CAE5C,MAAM,SAAS,mBAAmB,KAAK;CACvC,OAAO,kBAAkB,WAAW,OAAO,QAAQ,oBAAoB;AACzE"} \ No newline at end of file diff --git a/dist/dom/listen.d.ts b/dist/dom/listen.d.ts new file mode 100644 index 00000000..110c24be --- /dev/null +++ b/dist/dom/listen.d.ts @@ -0,0 +1,18 @@ +//#region src/dom/listen.d.ts +/** + * Add an event listener and return a cleanup function to remove it. + * + * @example + * ```ts + * const cleanup = listen(video, 'play', () => console.log('playing')); + * cleanup(); // Remove listener + * ``` + */ +declare function listen(target: HTMLMediaElement, type: K, listener: (event: HTMLMediaElementEventMap[K]) => void, options?: AddEventListenerOptions): () => void; +declare function listen(target: HTMLElement, type: K, listener: (event: HTMLElementEventMap[K]) => void, options?: AddEventListenerOptions): () => void; +declare function listen(target: Window, type: K, listener: (event: WindowEventMap[K]) => void, options?: AddEventListenerOptions): () => void; +declare function listen(target: Document, type: K, listener: (event: DocumentEventMap[K]) => void, options?: AddEventListenerOptions): () => void; +declare function listen(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: AddEventListenerOptions): () => void; +//#endregion +export { listen }; +//# sourceMappingURL=listen.d.ts.map \ No newline at end of file diff --git a/dist/dom/listen.d.ts.map b/dist/dom/listen.d.ts.map new file mode 100644 index 00000000..0dc965e2 --- /dev/null +++ b/dist/dom/listen.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"listen.d.ts","names":[],"sources":["../../src/dom/listen.ts"],"mappings":";;;;;;;;;;iBASgB,OAAO,gBAAgB,0BACrC,QAAQ,kBACR,MAAM,GACN,WAAW,OAAO,yBAAyB,aAC3C,UAAU;iBAGI,OAAO,gBAAgB,qBACrC,QAAQ,aACR,MAAM,GACN,WAAW,OAAO,oBAAoB,aACtC,UAAU;iBAGI,OAAO,gBAAgB,gBACrC,QAAQ,QACR,MAAM,GACN,WAAW,OAAO,eAAe,aACjC,UAAU;iBAGI,OAAO,gBAAgB,kBACrC,QAAQ,UACR,MAAM,GACN,WAAW,OAAO,iBAAiB,aACnC,UAAU;iBAGI,OACd,QAAQ,aACR,cACA,UAAU,oCACV,UAAU"} \ No newline at end of file diff --git a/dist/dom/listen.js b/dist/dom/listen.js new file mode 100644 index 00000000..5d4e6375 --- /dev/null +++ b/dist/dom/listen.js @@ -0,0 +1,9 @@ +//#region src/dom/listen.ts +function listen(target, type, listener, options) { + target.addEventListener(type, listener, options); + return () => target.removeEventListener(type, listener, options); +} +//#endregion +export { listen }; + +//# sourceMappingURL=listen.js.map \ No newline at end of file diff --git a/dist/dom/listen.js.map b/dist/dom/listen.js.map new file mode 100644 index 00000000..5a8c7761 --- /dev/null +++ b/dist/dom/listen.js.map @@ -0,0 +1 @@ +{"version":3,"file":"listen.js","names":[],"sources":["../../src/dom/listen.ts"],"sourcesContent":["/**\n * Add an event listener and return a cleanup function to remove it.\n *\n * @example\n * ```ts\n * const cleanup = listen(video, 'play', () => console.log('playing'));\n * cleanup(); // Remove listener\n * ```\n */\nexport function listen(\n target: HTMLMediaElement,\n type: K,\n listener: (event: HTMLMediaElementEventMap[K]) => void,\n options?: AddEventListenerOptions\n): () => void;\n\nexport function listen(\n target: HTMLElement,\n type: K,\n listener: (event: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions\n): () => void;\n\nexport function listen(\n target: Window,\n type: K,\n listener: (event: WindowEventMap[K]) => void,\n options?: AddEventListenerOptions\n): () => void;\n\nexport function listen(\n target: Document,\n type: K,\n listener: (event: DocumentEventMap[K]) => void,\n options?: AddEventListenerOptions\n): () => void;\n\nexport function listen(\n target: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: AddEventListenerOptions\n): () => void;\n\nexport function listen(\n target: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: AddEventListenerOptions\n): () => void {\n target.addEventListener(type, listener, options);\n return () => target.removeEventListener(type, listener, options);\n}\n"],"mappings":";AA4CA,SAAgB,OACd,QACA,MACA,UACA,SACY;CACZ,OAAO,iBAAiB,MAAM,UAAU,OAAO;CAC/C,aAAa,OAAO,oBAAoB,MAAM,UAAU,OAAO;AACjE"} \ No newline at end of file diff --git a/dist/dom/locale/effective-locale.d.ts b/dist/dom/locale/effective-locale.d.ts new file mode 100644 index 00000000..dcfda190 --- /dev/null +++ b/dist/dom/locale/effective-locale.d.ts @@ -0,0 +1,6 @@ +//#region src/dom/locale/effective-locale.d.ts +/** Resolves locale: explicit non-empty value → ambient `lang` → {@link fallback}. */ +declare function effectiveLocale(explicitLocale: Locale | undefined, ambientLang: Locale | undefined, fallback?: Locale): Locale; +//#endregion +export { effectiveLocale }; +//# sourceMappingURL=effective-locale.d.ts.map \ No newline at end of file diff --git a/dist/dom/locale/effective-locale.d.ts.map b/dist/dom/locale/effective-locale.d.ts.map new file mode 100644 index 00000000..9fe03ed1 --- /dev/null +++ b/dist/dom/locale/effective-locale.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"effective-locale.d.ts","names":[],"sources":["../../../src/dom/locale/effective-locale.ts"],"mappings":";;iBAGgB,gBAAgB,gCAC9B,gBAAgB,oBAChB,aAAa,oBACb,WAAmB,SAClB"} \ No newline at end of file diff --git a/dist/dom/locale/effective-locale.js b/dist/dom/locale/effective-locale.js new file mode 100644 index 00000000..7b0d4f00 --- /dev/null +++ b/dist/dom/locale/effective-locale.js @@ -0,0 +1,12 @@ +import { isUndefined } from "../../predicate/predicate.js"; +//#region src/dom/locale/effective-locale.ts +/** Resolves locale: explicit non-empty value → ambient `lang` → {@link fallback}. */ +function effectiveLocale(explicitLocale, ambientLang, fallback = "en") { + if (!isUndefined(explicitLocale) && explicitLocale.trim() !== "") return explicitLocale; + if (!isUndefined(ambientLang) && ambientLang.trim() !== "") return ambientLang; + return fallback; +} +//#endregion +export { effectiveLocale }; + +//# sourceMappingURL=effective-locale.js.map \ No newline at end of file diff --git a/dist/dom/locale/effective-locale.js.map b/dist/dom/locale/effective-locale.js.map new file mode 100644 index 00000000..36223d2e --- /dev/null +++ b/dist/dom/locale/effective-locale.js.map @@ -0,0 +1 @@ +{"version":3,"file":"effective-locale.js","names":[],"sources":["../../../src/dom/locale/effective-locale.ts"],"sourcesContent":["import { isUndefined } from '../../predicate';\n\n/** Resolves locale: explicit non-empty value → ambient `lang` → {@link fallback}. */\nexport function effectiveLocale(\n explicitLocale: Locale | undefined,\n ambientLang: Locale | undefined,\n fallback = 'en' as Locale\n): Locale {\n if (!isUndefined(explicitLocale) && explicitLocale.trim() !== '') {\n return explicitLocale;\n }\n if (!isUndefined(ambientLang) && ambientLang.trim() !== '') {\n return ambientLang;\n }\n return fallback;\n}\n"],"mappings":";;;AAGA,SAAgB,gBACd,gBACA,aACA,WAAW,MACH;CACR,IAAI,CAAC,YAAY,cAAc,KAAK,eAAe,KAAK,MAAM,IAC5D,OAAO;CAET,IAAI,CAAC,YAAY,WAAW,KAAK,YAAY,KAAK,MAAM,IACtD,OAAO;CAET,OAAO;AACT"} \ No newline at end of file diff --git a/dist/dom/locale/find-nearest-lang.d.ts b/dist/dom/locale/find-nearest-lang.d.ts new file mode 100644 index 00000000..b47a37a2 --- /dev/null +++ b/dist/dom/locale/find-nearest-lang.d.ts @@ -0,0 +1,6 @@ +//#region src/dom/locale/find-nearest-lang.d.ts +/** First non-empty `lang` on `start` or an ancestor (HTML language inheritance). */ +declare function findNearestLang(start: Element | null): string | undefined; +//#endregion +export { findNearestLang }; +//# sourceMappingURL=find-nearest-lang.d.ts.map \ No newline at end of file diff --git a/dist/dom/locale/find-nearest-lang.d.ts.map b/dist/dom/locale/find-nearest-lang.d.ts.map new file mode 100644 index 00000000..397c573b --- /dev/null +++ b/dist/dom/locale/find-nearest-lang.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"find-nearest-lang.d.ts","names":[],"sources":["../../../src/dom/locale/find-nearest-lang.ts"],"mappings":";;iBAiBgB,gBAAgB,OAAO"} \ No newline at end of file diff --git a/dist/dom/locale/find-nearest-lang.js b/dist/dom/locale/find-nearest-lang.js new file mode 100644 index 00000000..d1527ac7 --- /dev/null +++ b/dist/dom/locale/find-nearest-lang.js @@ -0,0 +1,18 @@ +import { walkAncestors } from "../walk-ancestors.js"; +//#region src/dom/locale/find-nearest-lang.ts +function getElementLang(node) { + const fromAttribute = node.getAttribute("lang")?.trim(); + if (fromAttribute) return fromAttribute; + if ("lang" in node && typeof node.lang === "string") { + const fromProperty = node.lang.trim(); + if (fromProperty) return fromProperty; + } +} +/** First non-empty `lang` on `start` or an ancestor (HTML language inheritance). */ +function findNearestLang(start) { + return walkAncestors(start, getElementLang); +} +//#endregion +export { findNearestLang }; + +//# sourceMappingURL=find-nearest-lang.js.map \ No newline at end of file diff --git a/dist/dom/locale/find-nearest-lang.js.map b/dist/dom/locale/find-nearest-lang.js.map new file mode 100644 index 00000000..be55ccbd --- /dev/null +++ b/dist/dom/locale/find-nearest-lang.js.map @@ -0,0 +1 @@ +{"version":3,"file":"find-nearest-lang.js","names":[],"sources":["../../../src/dom/locale/find-nearest-lang.ts"],"sourcesContent":["import { walkAncestors } from '../walk-ancestors';\n\nfunction getElementLang(node: Element): string | undefined {\n const fromAttribute = node.getAttribute('lang')?.trim();\n if (fromAttribute) {\n return fromAttribute;\n }\n if ('lang' in node && typeof node.lang === 'string') {\n const fromProperty = node.lang.trim();\n if (fromProperty) {\n return fromProperty;\n }\n }\n return undefined;\n}\n\n/** First non-empty `lang` on `start` or an ancestor (HTML language inheritance). */\nexport function findNearestLang(start: Element | null): string | undefined {\n return walkAncestors(start, getElementLang);\n}\n"],"mappings":";;AAEA,SAAS,eAAe,MAAmC;CACzD,MAAM,gBAAgB,KAAK,aAAa,MAAM,CAAC,EAAE,KAAK;CACtD,IAAI,eACF,OAAO;CAET,IAAI,UAAU,QAAQ,OAAO,KAAK,SAAS,UAAU;EACnD,MAAM,eAAe,KAAK,KAAK,KAAK;EACpC,IAAI,cACF,OAAO;CAEX;AAEF;;AAGA,SAAgB,gBAAgB,OAA2C;CACzE,OAAO,cAAc,OAAO,cAAc;AAC5C"} \ No newline at end of file diff --git a/dist/dom/locale/merge-locale-overlays.d.ts b/dist/dom/locale/merge-locale-overlays.d.ts new file mode 100644 index 00000000..360397fd --- /dev/null +++ b/dist/dom/locale/merge-locale-overlays.d.ts @@ -0,0 +1,12 @@ +//#region src/dom/locale/merge-locale-overlays.d.ts +/** + * Loads overlay layers for each resolved locale key, least-specific first, then merges + * most-specific-last (same semantics as the core i18n registry). + */ +declare function mergeLocaleOverlays(locale: string, load: (tag: string) => Promise | undefined>, findKeys: (locale: string) => string[]): Promise<{ + merged: Partial; + loadedTags: string[]; +}>; +//#endregion +export { mergeLocaleOverlays }; +//# sourceMappingURL=merge-locale-overlays.d.ts.map \ No newline at end of file diff --git a/dist/dom/locale/merge-locale-overlays.d.ts.map b/dist/dom/locale/merge-locale-overlays.d.ts.map new file mode 100644 index 00000000..94533fc6 --- /dev/null +++ b/dist/dom/locale/merge-locale-overlays.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"merge-locale-overlays.d.ts","names":[],"sources":["../../../src/dom/locale/merge-locale-overlays.ts"],"mappings":";;;;;iBAIsB,oBAAoB,wBACxC,gBACA,OAAO,gBAAgB,QAAQ,QAAQ,uBACvC,WAAW,8BACV;EAAU,QAAQ,QAAQ;EAAU"} \ No newline at end of file diff --git a/dist/dom/locale/merge-locale-overlays.js b/dist/dom/locale/merge-locale-overlays.js new file mode 100644 index 00000000..8d3ec34c --- /dev/null +++ b/dist/dom/locale/merge-locale-overlays.js @@ -0,0 +1,27 @@ +//#region src/dom/locale/merge-locale-overlays.ts +/** +* Loads overlay layers for each resolved locale key, least-specific first, then merges +* most-specific-last (same semantics as the core i18n registry). +*/ +async function mergeLocaleOverlays(locale, load, findKeys) { + const chain = findKeys(locale); + const layers = await Promise.all(chain.map((tag) => load(tag))); + const loadedTags = []; + const merged = {}; + for (let i = 0; i < chain.length; i++) { + const layer = layers[i]; + if (layer && Object.keys(layer).length > 0) loadedTags.push(chain[i]); + } + for (let i = chain.length - 1; i >= 0; i--) { + const layer = layers[i]; + if (layer) Object.assign(merged, layer); + } + return { + merged, + loadedTags + }; +} +//#endregion +export { mergeLocaleOverlays }; + +//# sourceMappingURL=merge-locale-overlays.js.map \ No newline at end of file diff --git a/dist/dom/locale/merge-locale-overlays.js.map b/dist/dom/locale/merge-locale-overlays.js.map new file mode 100644 index 00000000..1025fdc7 --- /dev/null +++ b/dist/dom/locale/merge-locale-overlays.js.map @@ -0,0 +1 @@ +{"version":3,"file":"merge-locale-overlays.js","names":[],"sources":["../../../src/dom/locale/merge-locale-overlays.ts"],"sourcesContent":["/**\n * Loads overlay layers for each resolved locale key, least-specific first, then merges\n * most-specific-last (same semantics as the core i18n registry).\n */\nexport async function mergeLocaleOverlays(\n locale: string,\n load: (tag: string) => Promise | undefined>,\n findKeys: (locale: string) => string[]\n): Promise<{ merged: Partial; loadedTags: string[] }> {\n const chain = findKeys(locale);\n const layers = await Promise.all(chain.map((tag) => load(tag)));\n const loadedTags: string[] = [];\n const merged: Partial = {};\n for (let i = 0; i < chain.length; i++) {\n const layer = layers[i];\n if (layer && Object.keys(layer).length > 0) {\n loadedTags.push(chain[i]!);\n }\n }\n for (let i = chain.length - 1; i >= 0; i--) {\n const layer = layers[i];\n if (layer) {\n Object.assign(merged, layer);\n }\n }\n return { merged, loadedTags };\n}\n"],"mappings":";;;;;AAIA,eAAsB,oBACpB,QACA,MACA,UAC6D;CAC7D,MAAM,QAAQ,SAAS,MAAM;CAC7B,MAAM,SAAS,MAAM,QAAQ,IAAI,MAAM,KAAK,QAAQ,KAAK,GAAG,CAAC,CAAC;CAC9D,MAAM,aAAuB,CAAC;CAC9B,MAAM,SAA2B,CAAC;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,QAAQ,OAAO;EACrB,IAAI,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GACvC,WAAW,KAAK,MAAM,EAAG;CAE7B;CACA,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,QAAQ,OAAO;EACrB,IAAI,OACF,OAAO,OAAO,QAAQ,KAAK;CAE/B;CACA,OAAO;EAAE;EAAQ;CAAW;AAC9B"} \ No newline at end of file diff --git a/dist/dom/locale/resolve-lang-attr.d.ts b/dist/dom/locale/resolve-lang-attr.d.ts new file mode 100644 index 00000000..09f0e512 --- /dev/null +++ b/dist/dom/locale/resolve-lang-attr.d.ts @@ -0,0 +1,9 @@ +//#region src/dom/locale/resolve-lang-attr.d.ts +/** + * Normalizes a raw `lang` string (e.g. from {@link findNearestLang}): empty or whitespace-only → + * `undefined`, otherwise the trimmed value. + */ +declare function resolveLangAttr(raw: string | undefined): Locale | undefined; +//#endregion +export { resolveLangAttr }; +//# sourceMappingURL=resolve-lang-attr.d.ts.map \ No newline at end of file diff --git a/dist/dom/locale/resolve-lang-attr.d.ts.map b/dist/dom/locale/resolve-lang-attr.d.ts.map new file mode 100644 index 00000000..0ff91189 --- /dev/null +++ b/dist/dom/locale/resolve-lang-attr.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"resolve-lang-attr.d.ts","names":[],"sources":["../../../src/dom/locale/resolve-lang-attr.ts"],"mappings":";;;;;iBAMgB,gBAAgB,gCAAgC,0BAA0B"} \ No newline at end of file diff --git a/dist/dom/locale/resolve-lang-attr.js b/dist/dom/locale/resolve-lang-attr.js new file mode 100644 index 00000000..a48cf236 --- /dev/null +++ b/dist/dom/locale/resolve-lang-attr.js @@ -0,0 +1,14 @@ +import { isUndefined } from "../../predicate/predicate.js"; +//#region src/dom/locale/resolve-lang-attr.ts +/** +* Normalizes a raw `lang` string (e.g. from {@link findNearestLang}): empty or whitespace-only → +* `undefined`, otherwise the trimmed value. +*/ +function resolveLangAttr(raw) { + if (isUndefined(raw) || raw.trim() === "") return; + return raw.trim(); +} +//#endregion +export { resolveLangAttr }; + +//# sourceMappingURL=resolve-lang-attr.js.map \ No newline at end of file diff --git a/dist/dom/locale/resolve-lang-attr.js.map b/dist/dom/locale/resolve-lang-attr.js.map new file mode 100644 index 00000000..c7d3f28b --- /dev/null +++ b/dist/dom/locale/resolve-lang-attr.js.map @@ -0,0 +1 @@ +{"version":3,"file":"resolve-lang-attr.js","names":[],"sources":["../../../src/dom/locale/resolve-lang-attr.ts"],"sourcesContent":["import { isUndefined } from '../../predicate';\n\n/**\n * Normalizes a raw `lang` string (e.g. from {@link findNearestLang}): empty or whitespace-only →\n * `undefined`, otherwise the trimmed value.\n */\nexport function resolveLangAttr(raw: string | undefined): Locale | undefined {\n if (isUndefined(raw) || raw.trim() === '') {\n return undefined;\n }\n return raw.trim() as Locale;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,gBAAgD,KAA6C;CAC3G,IAAI,YAAY,GAAG,KAAK,IAAI,KAAK,MAAM,IACrC;CAEF,OAAO,IAAI,KAAK;AAClB"} \ No newline at end of file diff --git a/dist/dom/locale/subscribe-ambient-lang.d.ts b/dist/dom/locale/subscribe-ambient-lang.d.ts new file mode 100644 index 00000000..380e58f0 --- /dev/null +++ b/dist/dom/locale/subscribe-ambient-lang.d.ts @@ -0,0 +1,9 @@ +//#region src/dom/locale/subscribe-ambient-lang.d.ts +/** + * Subscribes to DOM updates that can change inherited `lang`: any `lang` attribute edit, + * or subtree structural changes under `` (which can move nodes between labeled ancestors). + */ +declare function subscribeAmbientLang(onStoreChange: () => void): () => void; +//#endregion +export { subscribeAmbientLang }; +//# sourceMappingURL=subscribe-ambient-lang.d.ts.map \ No newline at end of file diff --git a/dist/dom/locale/subscribe-ambient-lang.d.ts.map b/dist/dom/locale/subscribe-ambient-lang.d.ts.map new file mode 100644 index 00000000..6316cfd7 --- /dev/null +++ b/dist/dom/locale/subscribe-ambient-lang.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"subscribe-ambient-lang.d.ts","names":[],"sources":["../../../src/dom/locale/subscribe-ambient-lang.ts"],"mappings":";;;;;iBA8CgB,qBAAqB"} \ No newline at end of file diff --git a/dist/dom/locale/subscribe-ambient-lang.js b/dist/dom/locale/subscribe-ambient-lang.js new file mode 100644 index 00000000..d0c1c558 --- /dev/null +++ b/dist/dom/locale/subscribe-ambient-lang.js @@ -0,0 +1,47 @@ +//#region src/dom/locale/subscribe-ambient-lang.ts +const subscribers = /* @__PURE__ */ new Set(); +let observer; +let queued = false; +const flush = () => { + queued = false; + for (const cb of subscribers) cb(); +}; +const schedule = () => { + if (!queued) { + queued = true; + queueMicrotask(flush); + } +}; +function start() { + if (observer || typeof document === "undefined") return; + observer = new MutationObserver(schedule); + observer.observe(document.documentElement, { + subtree: true, + attributes: true, + attributeFilter: ["lang"], + childList: true + }); +} +function stop() { + if (subscribers.size || !observer) return; + observer.disconnect(); + observer = void 0; + queued = false; +} +/** +* Subscribes to DOM updates that can change inherited `lang`: any `lang` attribute edit, +* or subtree structural changes under `` (which can move nodes between labeled ancestors). +*/ +function subscribeAmbientLang(onStoreChange) { + if (typeof document === "undefined") return () => {}; + subscribers.add(onStoreChange); + start(); + return () => { + subscribers.delete(onStoreChange); + stop(); + }; +} +//#endregion +export { subscribeAmbientLang }; + +//# sourceMappingURL=subscribe-ambient-lang.js.map \ No newline at end of file diff --git a/dist/dom/locale/subscribe-ambient-lang.js.map b/dist/dom/locale/subscribe-ambient-lang.js.map new file mode 100644 index 00000000..fb6727f7 --- /dev/null +++ b/dist/dom/locale/subscribe-ambient-lang.js.map @@ -0,0 +1 @@ +{"version":3,"file":"subscribe-ambient-lang.js","names":[],"sources":["../../../src/dom/locale/subscribe-ambient-lang.ts"],"sourcesContent":["const subscribers = new Set<() => void>();\nlet observer: MutationObserver | undefined;\nlet queued = false;\n\nconst flush = (): void => {\n queued = false;\n for (const cb of subscribers) {\n cb();\n }\n};\n\nconst schedule = (): void => {\n if (!queued) {\n queued = true;\n queueMicrotask(flush);\n }\n};\n\nfunction start(): void {\n if (observer || typeof document === 'undefined') {\n return;\n }\n\n observer = new MutationObserver(schedule);\n observer.observe(document.documentElement, {\n subtree: true,\n attributes: true,\n attributeFilter: ['lang'],\n childList: true,\n });\n}\n\nfunction stop(): void {\n if (subscribers.size || !observer) {\n return;\n }\n\n observer.disconnect();\n observer = undefined;\n queued = false;\n}\n\n/**\n * Subscribes to DOM updates that can change inherited `lang`: any `lang` attribute edit,\n * or subtree structural changes under `` (which can move nodes between labeled ancestors).\n */\nexport function subscribeAmbientLang(onStoreChange: () => void): () => void {\n if (typeof document === 'undefined') {\n return () => {};\n }\n\n subscribers.add(onStoreChange);\n start();\n\n return () => {\n subscribers.delete(onStoreChange);\n stop();\n };\n}\n"],"mappings":";AAAA,MAAM,8BAAc,IAAI,IAAgB;AACxC,IAAI;AACJ,IAAI,SAAS;AAEb,MAAM,cAAoB;CACxB,SAAS;CACT,KAAK,MAAM,MAAM,aACf,GAAG;AAEP;AAEA,MAAM,iBAAuB;CAC3B,IAAI,CAAC,QAAQ;EACX,SAAS;EACT,eAAe,KAAK;CACtB;AACF;AAEA,SAAS,QAAc;CACrB,IAAI,YAAY,OAAO,aAAa,aAClC;CAGF,WAAW,IAAI,iBAAiB,QAAQ;CACxC,SAAS,QAAQ,SAAS,iBAAiB;EACzC,SAAS;EACT,YAAY;EACZ,iBAAiB,CAAC,MAAM;EACxB,WAAW;CACb,CAAC;AACH;AAEA,SAAS,OAAa;CACpB,IAAI,YAAY,QAAQ,CAAC,UACvB;CAGF,SAAS,WAAW;CACpB,WAAW,KAAA;CACX,SAAS;AACX;;;;;AAMA,SAAgB,qBAAqB,eAAuC;CAC1E,IAAI,OAAO,aAAa,aACtB,aAAa,CAAC;CAGhB,YAAY,IAAI,aAAa;CAC7B,MAAM;CAEN,aAAa;EACX,YAAY,OAAO,aAAa;EAChC,KAAK;CACP;AACF"} \ No newline at end of file diff --git a/dist/dom/platform.d.ts b/dist/dom/platform.d.ts new file mode 100644 index 00000000..1dba0594 --- /dev/null +++ b/dist/dom/platform.d.ts @@ -0,0 +1,5 @@ +//#region src/dom/platform.d.ts +declare function isMacOS(): boolean; +//#endregion +export { isMacOS }; +//# sourceMappingURL=platform.d.ts.map \ No newline at end of file diff --git a/dist/dom/platform.d.ts.map b/dist/dom/platform.d.ts.map new file mode 100644 index 00000000..e28a4902 --- /dev/null +++ b/dist/dom/platform.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"platform.d.ts","names":[],"sources":["../../src/dom/platform.ts"],"mappings":";iBAAgB"} \ No newline at end of file diff --git a/dist/dom/platform.js b/dist/dom/platform.js new file mode 100644 index 00000000..bd2089df --- /dev/null +++ b/dist/dom/platform.js @@ -0,0 +1,8 @@ +//#region src/dom/platform.ts +function isMacOS() { + return typeof navigator !== "undefined" && /mac/i.test(navigator.userAgent); +} +//#endregion +export { isMacOS }; + +//# sourceMappingURL=platform.js.map \ No newline at end of file diff --git a/dist/dom/platform.js.map b/dist/dom/platform.js.map new file mode 100644 index 00000000..c1da92bf --- /dev/null +++ b/dist/dom/platform.js.map @@ -0,0 +1 @@ +{"version":3,"file":"platform.js","names":[],"sources":["../../src/dom/platform.ts"],"sourcesContent":["export function isMacOS(): boolean {\n return typeof navigator !== 'undefined' && /mac/i.test(navigator.userAgent);\n}\n"],"mappings":";AAAA,SAAgB,UAAmB;CACjC,OAAO,OAAO,cAAc,eAAe,OAAO,KAAK,UAAU,SAAS;AAC5E"} \ No newline at end of file diff --git a/dist/dom/popover.d.ts b/dist/dom/popover.d.ts new file mode 100644 index 00000000..fce22c0c --- /dev/null +++ b/dist/dom/popover.d.ts @@ -0,0 +1,16 @@ +//#region src/dom/popover.d.ts +type PositionSide = 'top' | 'bottom' | 'left' | 'right'; +interface PositionSideOptions { + side: PositionSide; +} +interface PositionSideOffsets { + sideOffset: number; + boundaryOffset?: number; +} +/** Resolve the preferred side against a positioning boundary. */ +declare function getPositionedSide(triggerRect: DOMRect, positionedRect: DOMRect, boundaryRect: DOMRect, opts: PositionSideOptions, offsets?: PositionSideOffsets): PositionSide; +declare function tryShowPopover(el: HTMLElement | null): void; +declare function tryHidePopover(el: HTMLElement | null): void; +//#endregion +export { PositionSide, PositionSideOffsets, PositionSideOptions, getPositionedSide, tryHidePopover, tryShowPopover }; +//# sourceMappingURL=popover.d.ts.map \ No newline at end of file diff --git a/dist/dom/popover.d.ts.map b/dist/dom/popover.d.ts.map new file mode 100644 index 00000000..3e3b7ef1 --- /dev/null +++ b/dist/dom/popover.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"popover.d.ts","names":[],"sources":["../../src/dom/popover.ts"],"mappings":";KAAY;UAEK;EACf,MAAM;;UAGS;EACf;EACA;;;iBAiCc,kBACd,aAAa,SACb,gBAAgB,SAChB,cAAc,SACd,MAAM,qBACN,UAAS,sBACR;iBAYa,eAAe,IAAI;iBAQnB,eAAe,IAAI"} \ No newline at end of file diff --git a/dist/dom/popover.js b/dist/dom/popover.js new file mode 100644 index 00000000..5c0fa202 --- /dev/null +++ b/dist/dom/popover.js @@ -0,0 +1,43 @@ +//#region src/dom/popover.ts +const ZERO_OFFSETS = { + sideOffset: 0, + boundaryOffset: 0 +}; +const OPPOSITE_SIDE = { + top: "bottom", + bottom: "top", + left: "right", + right: "left" +}; +function getSideAvailable(triggerRect, boundaryRect, side, offsets) { + const boundaryOffset = offsets.boundaryOffset ?? 0; + switch (side) { + case "top": return triggerRect.top - boundaryRect.top - boundaryOffset - offsets.sideOffset; + case "bottom": return boundaryRect.bottom - triggerRect.bottom - boundaryOffset - offsets.sideOffset; + case "left": return triggerRect.left - boundaryRect.left - boundaryOffset - offsets.sideOffset; + case "right": return boundaryRect.right - triggerRect.right - boundaryOffset - offsets.sideOffset; + } +} +/** Resolve the preferred side against a positioning boundary. */ +function getPositionedSide(triggerRect, positionedRect, boundaryRect, opts, offsets = ZERO_OFFSETS) { + const preferred = opts.side; + const opposite = OPPOSITE_SIDE[preferred]; + const size = preferred === "top" || preferred === "bottom" ? positionedRect.height : positionedRect.width; + const preferredSpace = getSideAvailable(triggerRect, boundaryRect, preferred, offsets); + if (preferredSpace >= size) return preferred; + return getSideAvailable(triggerRect, boundaryRect, opposite, offsets) > preferredSpace ? opposite : preferred; +} +function tryShowPopover(el) { + try { + el?.showPopover?.(); + } catch {} +} +function tryHidePopover(el) { + try { + el?.hidePopover?.(); + } catch {} +} +//#endregion +export { getPositionedSide, tryHidePopover, tryShowPopover }; + +//# sourceMappingURL=popover.js.map \ No newline at end of file diff --git a/dist/dom/popover.js.map b/dist/dom/popover.js.map new file mode 100644 index 00000000..c511be07 --- /dev/null +++ b/dist/dom/popover.js.map @@ -0,0 +1 @@ +{"version":3,"file":"popover.js","names":[],"sources":["../../src/dom/popover.ts"],"sourcesContent":["export type PositionSide = 'top' | 'bottom' | 'left' | 'right';\n\nexport interface PositionSideOptions {\n side: PositionSide;\n}\n\nexport interface PositionSideOffsets {\n sideOffset: number;\n boundaryOffset?: number;\n}\n\nconst ZERO_OFFSETS: PositionSideOffsets = { sideOffset: 0, boundaryOffset: 0 };\n\nconst OPPOSITE_SIDE: Record = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n};\n\nfunction getSideAvailable(\n triggerRect: DOMRect,\n boundaryRect: DOMRect,\n side: PositionSide,\n offsets: PositionSideOffsets\n): number {\n const boundaryOffset = offsets.boundaryOffset ?? 0;\n\n switch (side) {\n case 'top':\n return triggerRect.top - boundaryRect.top - boundaryOffset - offsets.sideOffset;\n case 'bottom':\n return boundaryRect.bottom - triggerRect.bottom - boundaryOffset - offsets.sideOffset;\n case 'left':\n return triggerRect.left - boundaryRect.left - boundaryOffset - offsets.sideOffset;\n case 'right':\n return boundaryRect.right - triggerRect.right - boundaryOffset - offsets.sideOffset;\n }\n}\n\n/** Resolve the preferred side against a positioning boundary. */\nexport function getPositionedSide(\n triggerRect: DOMRect,\n positionedRect: DOMRect,\n boundaryRect: DOMRect,\n opts: PositionSideOptions,\n offsets: PositionSideOffsets = ZERO_OFFSETS\n): PositionSide {\n const preferred = opts.side;\n const opposite = OPPOSITE_SIDE[preferred];\n const size = preferred === 'top' || preferred === 'bottom' ? positionedRect.height : positionedRect.width;\n const preferredSpace = getSideAvailable(triggerRect, boundaryRect, preferred, offsets);\n\n if (preferredSpace >= size) return preferred;\n\n const oppositeSpace = getSideAvailable(triggerRect, boundaryRect, opposite, offsets);\n return oppositeSpace > preferredSpace ? opposite : preferred;\n}\n\nexport function tryShowPopover(el: HTMLElement | null): void {\n try {\n el?.showPopover?.();\n } catch {\n // Element may not support popover API or may already be shown\n }\n}\n\nexport function tryHidePopover(el: HTMLElement | null): void {\n try {\n el?.hidePopover?.();\n } catch {\n // Element may not support popover API or may already be hidden\n }\n}\n"],"mappings":";AAWA,MAAM,eAAoC;CAAE,YAAY;CAAG,gBAAgB;AAAE;AAE7E,MAAM,gBAAoD;CACxD,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;AACT;AAEA,SAAS,iBACP,aACA,cACA,MACA,SACQ;CACR,MAAM,iBAAiB,QAAQ,kBAAkB;CAEjD,QAAQ,MAAR;EACE,KAAK,OACH,OAAO,YAAY,MAAM,aAAa,MAAM,iBAAiB,QAAQ;EACvE,KAAK,UACH,OAAO,aAAa,SAAS,YAAY,SAAS,iBAAiB,QAAQ;EAC7E,KAAK,QACH,OAAO,YAAY,OAAO,aAAa,OAAO,iBAAiB,QAAQ;EACzE,KAAK,SACH,OAAO,aAAa,QAAQ,YAAY,QAAQ,iBAAiB,QAAQ;CAC7E;AACF;;AAGA,SAAgB,kBACd,aACA,gBACA,cACA,MACA,UAA+B,cACjB;CACd,MAAM,YAAY,KAAK;CACvB,MAAM,WAAW,cAAc;CAC/B,MAAM,OAAO,cAAc,SAAS,cAAc,WAAW,eAAe,SAAS,eAAe;CACpG,MAAM,iBAAiB,iBAAiB,aAAa,cAAc,WAAW,OAAO;CAErF,IAAI,kBAAkB,MAAM,OAAO;CAGnC,OADsB,iBAAiB,aAAa,cAAc,UAAU,OACzD,IAAI,iBAAiB,WAAW;AACrD;AAEA,SAAgB,eAAe,IAA8B;CAC3D,IAAI;EACF,IAAI,cAAc;CACpB,QAAQ,CAER;AACF;AAEA,SAAgB,eAAe,IAA8B;CAC3D,IAAI;EACF,IAAI,cAAc;CACpB,QAAQ,CAER;AACF"} \ No newline at end of file diff --git a/dist/dom/predicates.d.ts b/dist/dom/predicates.d.ts new file mode 100644 index 00000000..ea264261 --- /dev/null +++ b/dist/dom/predicates.d.ts @@ -0,0 +1,9 @@ +//#region src/dom/predicates.d.ts +declare function isDocument(value: unknown): value is Document; +declare function isShadowRoot(value: unknown): value is ShadowRoot; +declare function isHTMLVideoElement(value: unknown): value is HTMLVideoElement; +declare function isHTMLAudioElement(value: unknown): value is HTMLAudioElement; +declare function isHTMLMediaElement(value: unknown): value is HTMLMediaElement; +//#endregion +export { isDocument, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isShadowRoot }; +//# sourceMappingURL=predicates.d.ts.map \ No newline at end of file diff --git a/dist/dom/predicates.d.ts.map b/dist/dom/predicates.d.ts.map new file mode 100644 index 00000000..b3157c79 --- /dev/null +++ b/dist/dom/predicates.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"predicates.d.ts","names":[],"sources":["../../src/dom/predicates.ts"],"mappings":";iBAAgB,WAAW,iBAAiB,SAAS;iBAIrC,aAAa,iBAAiB,SAAS;iBAIvC,mBAAmB,iBAAiB,SAAS;iBAI7C,mBAAmB,iBAAiB,SAAS;iBAI7C,mBAAmB,iBAAiB,SAAS"} \ No newline at end of file diff --git a/dist/dom/predicates.js b/dist/dom/predicates.js new file mode 100644 index 00000000..d80e4feb --- /dev/null +++ b/dist/dom/predicates.js @@ -0,0 +1,20 @@ +//#region src/dom/predicates.ts +function isDocument(value) { + return value instanceof Node && value.nodeType === 9; +} +function isShadowRoot(value) { + return value instanceof Node && value.nodeType === 11 && "host" in value; +} +function isHTMLVideoElement(value) { + return value instanceof HTMLVideoElement; +} +function isHTMLAudioElement(value) { + return value instanceof HTMLAudioElement; +} +function isHTMLMediaElement(value) { + return value instanceof HTMLMediaElement; +} +//#endregion +export { isDocument, isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement, isShadowRoot }; + +//# sourceMappingURL=predicates.js.map \ No newline at end of file diff --git a/dist/dom/predicates.js.map b/dist/dom/predicates.js.map new file mode 100644 index 00000000..3d190974 --- /dev/null +++ b/dist/dom/predicates.js.map @@ -0,0 +1 @@ +{"version":3,"file":"predicates.js","names":[],"sources":["../../src/dom/predicates.ts"],"sourcesContent":["export function isDocument(value: unknown): value is Document {\n return value instanceof Node && value.nodeType === 9;\n}\n\nexport function isShadowRoot(value: unknown): value is ShadowRoot {\n return value instanceof Node && value.nodeType === 11 && 'host' in value;\n}\n\nexport function isHTMLVideoElement(value: unknown): value is HTMLVideoElement {\n return value instanceof HTMLVideoElement;\n}\n\nexport function isHTMLAudioElement(value: unknown): value is HTMLAudioElement {\n return value instanceof HTMLAudioElement;\n}\n\nexport function isHTMLMediaElement(value: unknown): value is HTMLMediaElement {\n return value instanceof HTMLMediaElement;\n}\n"],"mappings":";AAAA,SAAgB,WAAW,OAAmC;CAC5D,OAAO,iBAAiB,QAAQ,MAAM,aAAa;AACrD;AAEA,SAAgB,aAAa,OAAqC;CAChE,OAAO,iBAAiB,QAAQ,MAAM,aAAa,MAAM,UAAU;AACrE;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,iBAAiB;AAC1B;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,iBAAiB;AAC1B;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,iBAAiB;AAC1B"} \ No newline at end of file diff --git a/dist/dom/raf-throttle.d.ts b/dist/dom/raf-throttle.d.ts new file mode 100644 index 00000000..bab5d042 --- /dev/null +++ b/dist/dom/raf-throttle.d.ts @@ -0,0 +1,12 @@ +//#region src/dom/raf-throttle.d.ts +/** A throttled function that can be cancelled. */ +interface RafThrottled { + (...args: Args): void; + /** Cancel any pending animation frame. */ + cancel(): void; +} +/** Throttle a function to fire at most once per animation frame. */ +declare function rafThrottle(fn: (...args: Args) => void): RafThrottled; +//#endregion +export { RafThrottled, rafThrottle }; +//# sourceMappingURL=raf-throttle.d.ts.map \ No newline at end of file diff --git a/dist/dom/raf-throttle.d.ts.map b/dist/dom/raf-throttle.d.ts.map new file mode 100644 index 00000000..2af0f931 --- /dev/null +++ b/dist/dom/raf-throttle.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"raf-throttle.d.ts","names":[],"sources":["../../src/dom/raf-throttle.ts"],"mappings":";;UACiB,aAAa;MACxB,MAAM;;EAEV;;;iBAIc,YAAY,wBAAwB,QAAQ,MAAM,gBAAgB,aAAa"} \ No newline at end of file diff --git a/dist/dom/raf-throttle.js b/dist/dom/raf-throttle.js new file mode 100644 index 00000000..f9d05846 --- /dev/null +++ b/dist/dom/raf-throttle.js @@ -0,0 +1,25 @@ +//#region src/dom/raf-throttle.ts +/** Throttle a function to fire at most once per animation frame. */ +function rafThrottle(fn) { + let rafId = null; + let latestArgs; + const throttled = (...args) => { + latestArgs = args; + if (rafId !== null) return; + rafId = requestAnimationFrame(() => { + rafId = null; + fn(...latestArgs); + }); + }; + throttled.cancel = () => { + if (rafId !== null) { + cancelAnimationFrame(rafId); + rafId = null; + } + }; + return throttled; +} +//#endregion +export { rafThrottle }; + +//# sourceMappingURL=raf-throttle.js.map \ No newline at end of file diff --git a/dist/dom/raf-throttle.js.map b/dist/dom/raf-throttle.js.map new file mode 100644 index 00000000..69cb8f97 --- /dev/null +++ b/dist/dom/raf-throttle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"raf-throttle.js","names":[],"sources":["../../src/dom/raf-throttle.ts"],"sourcesContent":["/** A throttled function that can be cancelled. */\nexport interface RafThrottled {\n (...args: Args): void;\n /** Cancel any pending animation frame. */\n cancel(): void;\n}\n\n/** Throttle a function to fire at most once per animation frame. */\nexport function rafThrottle(fn: (...args: Args) => void): RafThrottled {\n let rafId: number | null = null;\n let latestArgs: Args;\n\n const throttled = (...args: Args): void => {\n latestArgs = args;\n if (rafId !== null) return;\n rafId = requestAnimationFrame(() => {\n rafId = null;\n fn(...latestArgs);\n });\n };\n\n throttled.cancel = (): void => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n\n return throttled;\n}\n"],"mappings":";;AAQA,SAAgB,YAAoC,IAAiD;CACnG,IAAI,QAAuB;CAC3B,IAAI;CAEJ,MAAM,aAAa,GAAG,SAAqB;EACzC,aAAa;EACb,IAAI,UAAU,MAAM;EACpB,QAAQ,4BAA4B;GAClC,QAAQ;GACR,GAAG,GAAG,UAAU;EAClB,CAAC;CACH;CAEA,UAAU,eAAqB;EAC7B,IAAI,UAAU,MAAM;GAClB,qBAAqB,KAAK;GAC1B,QAAQ;EACV;CACF;CAEA,OAAO;AACT"} \ No newline at end of file diff --git a/dist/dom/script.d.ts b/dist/dom/script.d.ts new file mode 100644 index 00000000..16e55f7a --- /dev/null +++ b/dist/dom/script.d.ts @@ -0,0 +1,10 @@ +//#region src/dom/script.d.ts +/** + * Load a script once. Concurrent and repeat calls for the same `src` share a + * single promise; failed loads are evicted (and the tag removed) so they can + * be retried. + */ +declare function loadScript(src: string): Promise; +//#endregion +export { loadScript }; +//# sourceMappingURL=script.d.ts.map \ No newline at end of file diff --git a/dist/dom/script.d.ts.map b/dist/dom/script.d.ts.map new file mode 100644 index 00000000..50f21db7 --- /dev/null +++ b/dist/dom/script.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"script.d.ts","names":[],"sources":["../../src/dom/script.ts"],"mappings":";;;;;;iBAcgB,WAAW,cAAc"} \ No newline at end of file diff --git a/dist/dom/script.js b/dist/dom/script.js new file mode 100644 index 00000000..b073e395 --- /dev/null +++ b/dist/dom/script.js @@ -0,0 +1,33 @@ +//#region src/dom/script.ts +const cache = /* @__PURE__ */ new Map(); +function hasScript(src) { + for (const script of document.scripts) if (script.getAttribute("src") === src) return true; + return false; +} +/** +* Load a script once. Concurrent and repeat calls for the same `src` share a +* single promise; failed loads are evicted (and the tag removed) so they can +* be retried. +*/ +function loadScript(src) { + let promise = cache.get(src); + if (promise) return promise; + if (hasScript(src)) return Promise.resolve(); + promise = new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = src; + script.onload = () => resolve(); + script.onerror = () => { + script.remove(); + reject(/* @__PURE__ */ new Error(`Failed to load script: ${src}`)); + }; + document.head.appendChild(script); + }); + cache.set(src, promise); + promise.catch(() => cache.delete(src)); + return promise; +} +//#endregion +export { hasScript, loadScript }; + +//# sourceMappingURL=script.js.map \ No newline at end of file diff --git a/dist/dom/script.js.map b/dist/dom/script.js.map new file mode 100644 index 00000000..91482c55 --- /dev/null +++ b/dist/dom/script.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.js","names":[],"sources":["../../src/dom/script.ts"],"sourcesContent":["const cache = new Map>();\n\nexport function hasScript(src: string): boolean {\n for (const script of document.scripts) {\n if (script.getAttribute('src') === src) return true;\n }\n return false;\n}\n\n/**\n * Load a script once. Concurrent and repeat calls for the same `src` share a\n * single promise; failed loads are evicted (and the tag removed) so they can\n * be retried.\n */\nexport function loadScript(src: string): Promise {\n let promise = cache.get(src);\n if (promise) return promise;\n\n // Assume a tag we didn't create (e.g. added directly in HTML) has loaded or will load.\n if (hasScript(src)) return Promise.resolve();\n\n promise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => {\n script.remove();\n reject(new Error(`Failed to load script: ${src}`));\n };\n document.head.appendChild(script);\n });\n\n cache.set(src, promise);\n // Evict on failure so callers can retry; must not propagate the rejection.\n promise.catch(() => cache.delete(src));\n\n return promise;\n}\n"],"mappings":";AAAA,MAAM,wBAAQ,IAAI,IAA2B;AAE7C,SAAgB,UAAU,KAAsB;CAC9C,KAAK,MAAM,UAAU,SAAS,SAC5B,IAAI,OAAO,aAAa,KAAK,MAAM,KAAK,OAAO;CAEjD,OAAO;AACT;;;;;;AAOA,SAAgB,WAAW,KAA4B;CACrD,IAAI,UAAU,MAAM,IAAI,GAAG;CAC3B,IAAI,SAAS,OAAO;CAGpB,IAAI,UAAU,GAAG,GAAG,OAAO,QAAQ,QAAQ;CAE3C,UAAU,IAAI,SAAe,SAAS,WAAW;EAC/C,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,MAAM;EACb,OAAO,eAAe,QAAQ;EAC9B,OAAO,gBAAgB;GACrB,OAAO,OAAO;GACd,uBAAO,IAAI,MAAM,0BAA0B,KAAK,CAAC;EACnD;EACA,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;CAED,MAAM,IAAI,KAAK,OAAO;CAEtB,QAAQ,YAAY,MAAM,OAAO,GAAG,CAAC;CAErC,OAAO;AACT"} \ No newline at end of file diff --git a/dist/dom/shadow-styles.d.ts b/dist/dom/shadow-styles.d.ts new file mode 100644 index 00000000..3afe0864 --- /dev/null +++ b/dist/dom/shadow-styles.d.ts @@ -0,0 +1,11 @@ +//#region src/dom/shadow-styles.d.ts +type ShadowStyle = CSSStyleSheet | string; +/** Inject a `