@brytdesigns/web-component-modal 1.1.8 → 3.0.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 +1 @@
1
- {"version":3,"sources":["../../../src/components/modal-panel.ts"],"names":["element"],"mappings":";;;;;AAUO,MAAM,IAAO,GAAA;AAEb,MAAM,SAAyC,GAAA,CAAC,CAAG,EAAA,EAAE,SAAc,KAAA;AACxE,EAAA,MAAM,CAAC,KAAO,EAAA,EAAE,sBAAsB,CAAA,GAAI,SAAS,OAAO,CAAA;AAE1D,EAAA,IAAI,aAAgB,GAAA,IAAA;AAEpB,EAAA,YAAA;AAAA,IACE,EAAA;AAAA,MACE,MAAM,KAAM,CAAA,MAAA;AAAA,MACZ,CAAC,MAAW,KAAA;AACV,QAAI,IAAA,CAAC,UAAU,aAAe,EAAA;AAC9B,QAAM,MAAA,SAAA,GAAY,MAAM,OAAO,CAAA;AAC/B,QAAA,oBAAA,CAAqB,SAAS,CAAA;AAC9B,QAAA,OAAO,UAAU,MAAM;AACrB,UAAA,IAAI,SAAU,CAAA,KAAA,KAAU,UAAY,EAAA,SAAA,CAAU,QAAS,EAAA;AAAA,SACxD,CAAA;AAAA;AACH;AACF,GACF;AAEA,EAAA,YAAA;AAAA,IACE,EAAA;AAAA,MACE,MAAM,KAAM,CAAA,MAAA;AAAA,MACZ,CAAC,MAAW,KAAA;AACV,QAAA,IAAI,UAAU,aAAe,EAAA;AAC7B,QAAM,MAAA,SAAA,GAAY,KAAK,OAAO,CAAA;AAC9B,QAAA,oBAAA,CAAqB,SAAS,CAAA;AAC9B,QAAA,OAAO,UAAU,MAAM;AACrB,UAAA,IAAI,SAAU,CAAA,KAAA,KAAU,UAAY,EAAA,SAAA,CAAU,QAAS,EAAA;AAAA,SACxD,CAAA;AAAA;AACH;AACF,GACF;AAEA,EAAA,OAAA,CAAQ,MAAM;AACZ,IAAgB,aAAA,GAAA,KAAA;AAAA,GACjB,CAAA;AAED,EAAA,SAAS,MAAMA,QAAsB,EAAA;AACnC,IAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,gBAAA,CAAiBA,QAAO,CAAA;AAC7C,IAAM,MAAA,UAAA,GAAa,oBAAoB,KAAK,CAAA;AAE5C,IAAO,OAAA,OAAA;AAAA,MACLA,QAAAA;AAAA,MACA;AAAA,QACE,GAAG,CAAC,CAAA,MAAA,EAAS,IAAI,CAAmB,eAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAe,aAAA,CAAA,CAAA;AAAA,QAChE,GAAG,CAAC,CAAA,MAAA,EAAS,IAAI,CAAmB,eAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAe,aAAA,CAAA,CAAA;AAAA,QAChE,QAAQ,CAAC,CAAA,MAAA,EAAS,IAAI,CAAmB,eAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAe,aAAA,CAAA,CAAA;AAAA,QACrE,QAAQ,CAAC,CAAA,MAAA,EAAS,IAAI,CAAmB,eAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAe,aAAA,CAAA,CAAA;AAAA,QACrE,SAAS,CAAC,CAAA,MAAA,EAAS,IAAI,CAAmB,eAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAe,aAAA,CAAA;AAAA,OACxE;AAAA,MACA;AAAA,KACF;AAAA;AAGF,EAAA,SAAS,KAAKA,QAAsB,EAAA;AAClC,IAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,gBAAA,CAAiBA,QAAO,CAAA;AAC7C,IAAM,MAAA,UAAA,GAAa,oBAAoB,KAAK,CAAA;AAC5C,IAAO,OAAA,OAAA;AAAA,MACLA,QAAAA;AAAA,MACA;AAAA,QACE,GAAG,CAAC,CAAA,MAAA,EAAS,IAAI,CAAiB,aAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAiB,eAAA,CAAA,CAAA;AAAA,QAChE,GAAG,CAAC,CAAA,MAAA,EAAS,IAAI,CAAiB,aAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAiB,eAAA,CAAA,CAAA;AAAA,QAChE,QAAQ,CAAC,CAAA,MAAA,EAAS,IAAI,CAAiB,aAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAiB,eAAA,CAAA,CAAA;AAAA,QACrE,QAAQ,CAAC,CAAA,MAAA,EAAS,IAAI,CAAiB,aAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAiB,eAAA,CAAA,CAAA;AAAA,QACrE,SAAS,CAAC,CAAA,MAAA,EAAS,IAAI,CAAiB,aAAA,CAAA,EAAA,CAAA,MAAA,EAAS,IAAI,CAAiB,eAAA,CAAA;AAAA,OACxE;AAAA,MACA;AAAA,KACF;AAAA;AAEJ","file":"modal-panel.js","sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\n\nimport { createEffect, onMount, on, onCleanup } from \"solid-js\";\nimport { animate } from \"motion\";\nimport { getTransitionConfig } from \"@brytdesigns/web-component-core/animation\";\n\nimport { useModal } from \"../hooks/index.js\";\n\nexport type Props = {};\n\nexport const Name = \"modal-panel\";\n\nexport const Component: CorrectComponentType<Props> = (_, { element }) => {\n const [state, { updateAnimationQueue }] = useModal(element);\n\n let isFirstRender = true;\n\n createEffect(\n on(\n () => state.isOpen,\n (isOpen) => {\n if (!isOpen || isFirstRender) return;\n const animation = enter(element);\n updateAnimationQueue(animation);\n return onCleanup(() => {\n if (animation.state !== \"finished\") animation.complete();\n });\n },\n ),\n );\n\n createEffect(\n on(\n () => state.isOpen,\n (isOpen) => {\n if (isOpen || isFirstRender) return;\n const animation = exit(element);\n updateAnimationQueue(animation);\n return onCleanup(() => {\n if (animation.state !== \"finished\") animation.complete();\n });\n },\n ),\n );\n\n onMount(() => {\n isFirstRender = false;\n });\n\n function enter(element: HTMLElement) {\n const style = window.getComputedStyle(element);\n const transition = getTransitionConfig(style);\n\n return animate(\n element,\n {\n x: [`var(--${Name}--slide-x-from)`, `var(--${Name}--slide-x-to)`],\n y: [`var(--${Name}--slide-y-from)`, `var(--${Name}--slide-y-to)`],\n scaleY: [`var(--${Name}--scale-y-from)`, `var(--${Name}--scale-y-to)`],\n scaleX: [`var(--${Name}--scale-x-from)`, `var(--${Name}--scale-x-to)`],\n opacity: [`var(--${Name}--opacity-from)`, `var(--${Name}--opacity-to)`],\n },\n transition,\n );\n }\n\n function exit(element: HTMLElement) {\n const style = window.getComputedStyle(element);\n const transition = getTransitionConfig(style);\n return animate(\n element,\n {\n x: [`var(--${Name}--slide-x-to)`, `var(--${Name}--slide-x-from)`],\n y: [`var(--${Name}--slide-y-to)`, `var(--${Name}--slide-y-from)`],\n scaleY: [`var(--${Name}--scale-y-to)`, `var(--${Name}--scale-y-from)`],\n scaleX: [`var(--${Name}--scale-x-to)`, `var(--${Name}--scale-x-from)`],\n opacity: [`var(--${Name}--opacity-to)`, `var(--${Name}--opacity-from)`],\n },\n transition,\n );\n }\n};\n"]}
1
+ {"version":3,"file":"modal-panel.js","names":[],"sources":["../../../src/components/modal-panel.ts"],"sourcesContent":["import { getTransitionConfig } from \"@brytdesigns/web-component-core/animation\";\nimport type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport { createGuardedEffect } from \"@brytdesigns/web-component-core/utils\";\nimport { animate } from \"motion\";\nimport { onSettled } from \"solid-js\";\n\nimport { useModal } from \"../hooks/index.js\";\n\nexport type Props = {};\n\nexport const Name = \"modal-panel\";\n\nexport const Component: CorrectComponentType<Props> = (_, { element }) => {\n\tconst [state, { updateAnimationQueue }] = useModal(element);\n\n\tlet isFirstRender = true;\n\n\t// The element that *is* the dialog, as far as assistive tech is concerned.\n\t// Until now this published nothing: no role, no name, no aria-modal, so a\n\t// screen reader saw an unlabelled div that happened to hold the focus.\n\t//\n\t// An authored role or tabindex is left alone — a theme that already made\n\t// this an alertdialog knows something this component does not.\n\tif (!element.hasAttribute(\"role\")) element.setAttribute(\"role\", \"dialog\");\n\tif (!element.hasAttribute(\"tabindex\")) element.setAttribute(\"tabindex\", \"-1\");\n\n\tonSettled(() => {\n\t\tconst named =\n\t\t\telement.hasAttribute(\"aria-labelledby\") ||\n\t\t\telement.hasAttribute(\"aria-label\");\n\t\tif (!named) {\n\t\t\tconsole.warn(\n\t\t\t\t\"modal-panel: no accessible name. Add an aria-label, or an aria-labelledby pointing at the heading inside it.\",\n\t\t\t\telement,\n\t\t\t);\n\t\t}\n\t});\n\n\tcreateGuardedEffect(\n\t\t() => state.isOpen,\n\t\t(isOpen) => {\n\t\t\t// `aria-modal` claims focus is confined to this subtree, which is only\n\t\t\t// true while it is open. Left on a closed overlay it tells assistive\n\t\t\t// tech the rest of the page is unavailable when it is not.\n\t\t\tif (isOpen) element.setAttribute(\"aria-modal\", \"true\");\n\t\t\telse element.removeAttribute(\"aria-modal\");\n\t\t},\n\t);\n\n\tcreateGuardedEffect(\n\t\t() => state.isOpen,\n\t\t(isOpen) => {\n\t\t\tif (!isOpen || isFirstRender) return;\n\t\t\tconst animation = enter(element);\n\t\t\tupdateAnimationQueue(animation);\n\t\t\treturn () => {\n\t\t\t\tif (animation.state !== \"finished\") animation.complete();\n\t\t\t};\n\t\t},\n\t);\n\n\tcreateGuardedEffect(\n\t\t() => state.isOpen,\n\t\t(isOpen) => {\n\t\t\tif (isOpen || isFirstRender) return;\n\t\t\tconst animation = exit(element);\n\t\t\tupdateAnimationQueue(animation);\n\t\t\treturn () => {\n\t\t\t\tif (animation.state !== \"finished\") animation.complete();\n\t\t\t};\n\t\t},\n\t);\n\n\tonSettled(() => {\n\t\tisFirstRender = false;\n\t});\n\n\tfunction enter(element: HTMLElement) {\n\t\tconst style = window.getComputedStyle(element);\n\t\tconst transition = getTransitionConfig(style);\n\n\t\treturn animate(\n\t\t\telement,\n\t\t\t{\n\t\t\t\tx: [`var(--${Name}--slide-x-from)`, `var(--${Name}--slide-x-to)`],\n\t\t\t\ty: [`var(--${Name}--slide-y-from)`, `var(--${Name}--slide-y-to)`],\n\t\t\t\tscaleY: [`var(--${Name}--scale-y-from)`, `var(--${Name}--scale-y-to)`],\n\t\t\t\tscaleX: [`var(--${Name}--scale-x-from)`, `var(--${Name}--scale-x-to)`],\n\t\t\t\topacity: [`var(--${Name}--opacity-from)`, `var(--${Name}--opacity-to)`],\n\t\t\t},\n\t\t\ttransition,\n\t\t);\n\t}\n\n\tfunction exit(element: HTMLElement) {\n\t\tconst style = window.getComputedStyle(element);\n\t\tconst transition = getTransitionConfig(style);\n\t\treturn animate(\n\t\t\telement,\n\t\t\t{\n\t\t\t\tx: [`var(--${Name}--slide-x-to)`, `var(--${Name}--slide-x-from)`],\n\t\t\t\ty: [`var(--${Name}--slide-y-to)`, `var(--${Name}--slide-y-from)`],\n\t\t\t\tscaleY: [`var(--${Name}--scale-y-to)`, `var(--${Name}--scale-y-from)`],\n\t\t\t\tscaleX: [`var(--${Name}--scale-x-to)`, `var(--${Name}--scale-x-from)`],\n\t\t\t\topacity: [`var(--${Name}--opacity-to)`, `var(--${Name}--opacity-from)`],\n\t\t\t},\n\t\t\ttransition,\n\t\t);\n\t}\n};\n"],"mappings":";;;;;;;AAUA,IAAa,OAAO;AAEpB,IAAa,aAA0C,GAAG,EAAE,cAAc;CACzE,MAAM,CAAC,OAAO,EAAE,0BAA0B,SAAS,OAAO;CAE1D,IAAI,gBAAgB;CAQpB,IAAI,CAAC,QAAQ,aAAa,MAAM,GAAG,QAAQ,aAAa,QAAQ,QAAQ;CACxE,IAAI,CAAC,QAAQ,aAAa,UAAU,GAAG,QAAQ,aAAa,YAAY,IAAI;CAE5E,gBAAgB;EAIf,IAAI,EAFH,QAAQ,aAAa,iBAAiB,KACtC,QAAQ,aAAa,YAAY,IAEjC,QAAQ,KACP,gHACA,OACD;CAEF,CAAC;CAED,0BACO,MAAM,SACX,WAAW;EAIX,IAAI,QAAQ,QAAQ,aAAa,cAAc,MAAM;OAChD,QAAQ,gBAAgB,YAAY;CAC1C,CACD;CAEA,0BACO,MAAM,SACX,WAAW;EACX,IAAI,CAAC,UAAU,eAAe;EAC9B,MAAM,YAAY,MAAM,OAAO;EAC/B,qBAAqB,SAAS;EAC9B,aAAa;GACZ,IAAI,UAAU,UAAU,YAAY,UAAU,SAAS;EACxD;CACD,CACD;CAEA,0BACO,MAAM,SACX,WAAW;EACX,IAAI,UAAU,eAAe;EAC7B,MAAM,YAAY,KAAK,OAAO;EAC9B,qBAAqB,SAAS;EAC9B,aAAa;GACZ,IAAI,UAAU,UAAU,YAAY,UAAU,SAAS;EACxD;CACD,CACD;CAEA,gBAAgB;EACf,gBAAgB;CACjB,CAAC;CAED,SAAS,MAAM,SAAsB;EACpC,MAAM,QAAQ,OAAO,iBAAiB,OAAO;EAC7C,MAAM,aAAa,oBAAoB,KAAK;EAE5C,OAAO,QACN,SACA;GACC,GAAG,CAAC,SAAS,KAAK,kBAAkB,SAAS,KAAK,cAAc;GAChE,GAAG,CAAC,SAAS,KAAK,kBAAkB,SAAS,KAAK,cAAc;GAChE,QAAQ,CAAC,SAAS,KAAK,kBAAkB,SAAS,KAAK,cAAc;GACrE,QAAQ,CAAC,SAAS,KAAK,kBAAkB,SAAS,KAAK,cAAc;GACrE,SAAS,CAAC,SAAS,KAAK,kBAAkB,SAAS,KAAK,cAAc;EACvE,GACA,UACD;CACD;CAEA,SAAS,KAAK,SAAsB;EACnC,MAAM,QAAQ,OAAO,iBAAiB,OAAO;EAC7C,MAAM,aAAa,oBAAoB,KAAK;EAC5C,OAAO,QACN,SACA;GACC,GAAG,CAAC,SAAS,KAAK,gBAAgB,SAAS,KAAK,gBAAgB;GAChE,GAAG,CAAC,SAAS,KAAK,gBAAgB,SAAS,KAAK,gBAAgB;GAChE,QAAQ,CAAC,SAAS,KAAK,gBAAgB,SAAS,KAAK,gBAAgB;GACrE,QAAQ,CAAC,SAAS,KAAK,gBAAgB,SAAS,KAAK,gBAAgB;GACrE,SAAS,CAAC,SAAS,KAAK,gBAAgB,SAAS,KAAK,gBAAgB;EACvE,GACA,UACD;CACD;AACD"}
@@ -1,77 +1,86 @@
1
- import { createEffect, onCleanup } from 'solid-js';
2
- import { observeElementInViewport } from 'observe-element-in-viewport';
3
- import { withModalElementContext, useModalContext } from '../hooks/index.js';
4
-
5
- const Name = "modal-trigger";
6
- const Component = (props, { element }) => {
7
- withModalElementContext(
8
- () => props.target || "",
9
- () => ({
10
- on: props.on,
11
- action: props.action,
12
- preventDefault: props.preventDefault
13
- }),
14
- (context, { on, action, preventDefault }) => {
15
- const controller = new AbortController();
16
- const [state, { open, close, toggle }] = useModalContext(context);
17
- switch (on) {
18
- case "enter": {
19
- let unsubscribe = null;
20
- if (action === "toggle") {
21
- unsubscribe = observeElementInViewport(element, open, close);
22
- }
23
- if (unsubscribe)
24
- controller.signal.addEventListener("abort", unsubscribe, {
25
- once: true
26
- });
27
- break;
28
- }
29
- case "exit": {
30
- let unsubscribe = null;
31
- if (action === "toggle") {
32
- unsubscribe = observeElementInViewport(element, close, open);
33
- }
34
- if (unsubscribe)
35
- controller.signal.addEventListener("abort", unsubscribe, {
36
- once: true
37
- });
38
- break;
39
- }
40
- default: {
41
- element.addEventListener(
42
- //@ts-ignore
43
- on,
44
- (event) => {
45
- if (preventDefault) event.preventDefault();
46
- if (state.isAnimating) return;
47
- switch (action) {
48
- case "close":
49
- close();
50
- break;
51
- case "open":
52
- open();
53
- break;
54
- case "toggle":
55
- toggle();
56
- break;
57
- }
58
- },
59
- { signal: controller.signal }
60
- );
61
- break;
62
- }
63
- }
64
- createEffect(() => {
65
- element.setAttribute("is-open", `${state.isOpen}`);
66
- element.setAttribute("is-animating", `${state.isAnimating}`);
67
- });
68
- onCleanup(() => {
69
- controller.abort();
70
- });
71
- }
72
- );
1
+ import { useModalAt, useModalContext } from "../hooks/useModal.js";
2
+ import "../hooks/index.js";
3
+ import { createGuardedEffect, ensureId, findDisclosureControl } from "@brytdesigns/web-component-core/utils";
4
+ import { observeElementInViewport } from "observe-element-in-viewport";
5
+ //#region src/components/modal-trigger.ts
6
+ var Name = "modal-trigger";
7
+ var Component = (props, { element }) => {
8
+ const modal = useModalAt(() => props.target, element);
9
+ createGuardedEffect(() => ({
10
+ context: modal(),
11
+ on: props.on,
12
+ action: props.action,
13
+ preventDefault: props.preventDefault
14
+ }), ({ context, on, action, preventDefault }) => {
15
+ const controller = new AbortController();
16
+ const [state, { open, close, toggle }] = useModalContext(context);
17
+ switch (on) {
18
+ case "enter":
19
+ if (action === "toggle") {
20
+ const unsubscribe = observeElementInViewport(element, open, close);
21
+ controller.signal.addEventListener("abort", unsubscribe, { once: true });
22
+ }
23
+ break;
24
+ case "exit":
25
+ if (action === "toggle") {
26
+ const unsubscribe = observeElementInViewport(element, close, open);
27
+ controller.signal.addEventListener("abort", unsubscribe, { once: true });
28
+ }
29
+ break;
30
+ default: element.addEventListener(on, (event) => {
31
+ if (preventDefault) event.preventDefault();
32
+ if (state.isAnimating) return;
33
+ switch (action) {
34
+ case "close":
35
+ close();
36
+ break;
37
+ case "open":
38
+ open();
39
+ break;
40
+ case "toggle": toggle();
41
+ }
42
+ }, { signal: controller.signal });
43
+ }
44
+ return () => {
45
+ controller.abort();
46
+ };
47
+ });
48
+ /**
49
+ * ARIA for the control that opens the overlay.
50
+ *
51
+ * `aria-haspopup="dialog"` rather than `aria-expanded`: this opens an
52
+ * overlay and moves focus into it, which is not the disclosure pattern
53
+ * `aria-expanded` describes. None of this was published before.
54
+ *
55
+ * A trigger with no button inside warns and sets nothing, matching
56
+ * `dialog-trigger` — ARIA on a non-interactive wrapper would claim a
57
+ * control that assistive tech cannot operate.
58
+ */
59
+ createGuardedEffect(() => ({
60
+ context: modal(),
61
+ selector: props.target
62
+ }), ({ selector }) => {
63
+ const control = findDisclosureControl(element);
64
+ if (!control) {
65
+ console.warn("modal-trigger: no <button> inside it, so no ARIA was applied. Wrap the trigger content in a <button type=\"button\">.", element);
66
+ return;
67
+ }
68
+ control.setAttribute("aria-haspopup", "dialog");
69
+ const root = selector ? document.querySelector(selector) : null;
70
+ if (root && !control.hasAttribute("aria-controls")) control.setAttribute("aria-controls", ensureId(root, "modal"));
71
+ });
72
+ createGuardedEffect(() => {
73
+ const [state] = useModalContext(modal());
74
+ return {
75
+ isOpen: state.isOpen,
76
+ isAnimating: state.isAnimating
77
+ };
78
+ }, ({ isOpen, isAnimating }) => {
79
+ element.setAttribute("is-open", `${isOpen}`);
80
+ element.setAttribute("is-animating", `${isAnimating}`);
81
+ });
73
82
  };
74
-
83
+ //#endregion
75
84
  export { Component, Name };
76
- //# sourceMappingURL=modal-trigger.js.map
85
+
77
86
  //# sourceMappingURL=modal-trigger.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/modal-trigger.ts"],"names":[],"mappings":";;;;AAeO,MAAM,IAAO,GAAA;AAEb,MAAM,SAAyC,GAAA,CAAC,KAAO,EAAA,EAAE,SAAc,KAAA;AAC5E,EAAA,uBAAA;AAAA,IACE,MAAM,MAAM,MAAU,IAAA,EAAA;AAAA,IACtB,OAAO;AAAA,MACL,IAAI,KAAM,CAAA,EAAA;AAAA,MACV,QAAQ,KAAM,CAAA,MAAA;AAAA,MACd,gBAAgB,KAAM,CAAA;AAAA,KACxB,CAAA;AAAA,IACA,CAAC,OAAS,EAAA,EAAE,EAAI,EAAA,MAAA,EAAQ,gBAAqB,KAAA;AAC3C,MAAM,MAAA,UAAA,GAAa,IAAI,eAAgB,EAAA;AACvC,MAAM,MAAA,CAAC,OAAO,EAAE,IAAA,EAAM,OAAO,MAAO,EAAC,CAAI,GAAA,eAAA,CAAgB,OAAO,CAAA;AAChE,MAAA,QAAQ,EAAI;AAAA,QACV,KAAK,OAAS,EAAA;AACZ,UAAA,IAAI,WAAc,GAAA,IAAA;AAClB,UAAA,IAAI,WAAW,QAAU,EAAA;AACvB,YAAc,WAAA,GAAA,wBAAA,CAAyB,OAAS,EAAA,IAAA,EAAM,KAAK,CAAA;AAAA;AAG7D,UAAI,IAAA,WAAA;AACF,YAAW,UAAA,CAAA,MAAA,CAAO,gBAAiB,CAAA,OAAA,EAAS,WAAa,EAAA;AAAA,cACvD,IAAM,EAAA;AAAA,aACP,CAAA;AACH,UAAA;AAAA;AACF,QACA,KAAK,MAAQ,EAAA;AACX,UAAA,IAAI,WAAc,GAAA,IAAA;AAClB,UAAA,IAAI,WAAW,QAAU,EAAA;AACvB,YAAc,WAAA,GAAA,wBAAA,CAAyB,OAAS,EAAA,KAAA,EAAO,IAAI,CAAA;AAAA;AAG7D,UAAI,IAAA,WAAA;AACF,YAAW,UAAA,CAAA,MAAA,CAAO,gBAAiB,CAAA,OAAA,EAAS,WAAa,EAAA;AAAA,cACvD,IAAM,EAAA;AAAA,aACP,CAAA;AACH,UAAA;AAAA;AACF,QAEA,SAAS;AACP,UAAQ,OAAA,CAAA,gBAAA;AAAA;AAAA,YAEN,EAAA;AAAA,YACA,CAAC,KAAU,KAAA;AACT,cAAI,IAAA,cAAA,QAAsB,cAAe,EAAA;AACzC,cAAA,IAAI,MAAM,WAAa,EAAA;AACvB,cAAA,QAAQ,MAAQ;AAAA,gBACd,KAAK,OAAA;AACH,kBAAM,KAAA,EAAA;AACN,kBAAA;AAAA,gBACF,KAAK,MAAA;AACH,kBAAK,IAAA,EAAA;AACL,kBAAA;AAAA,gBACF,KAAK,QAAA;AACH,kBAAO,MAAA,EAAA;AACP,kBAAA;AAAA;AACJ,aACF;AAAA,YACA,EAAE,MAAQ,EAAA,UAAA,CAAW,MAAO;AAAA,WAC9B;AACA,UAAA;AAAA;AACF;AAGF,MAAA,YAAA,CAAa,MAAM;AACjB,QAAA,OAAA,CAAQ,YAAa,CAAA,SAAA,EAAW,CAAG,EAAA,KAAA,CAAM,MAAM,CAAE,CAAA,CAAA;AACjD,QAAA,OAAA,CAAQ,YAAa,CAAA,cAAA,EAAgB,CAAG,EAAA,KAAA,CAAM,WAAW,CAAE,CAAA,CAAA;AAAA,OAC5D,CAAA;AAED,MAAA,SAAA,CAAU,MAAM;AACd,QAAA,UAAA,CAAW,KAAM,EAAA;AAAA,OAClB,CAAA;AAAA;AACH,GACF;AACF","file":"modal-trigger.js","sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport type { Action } from \"../consts\";\n\nimport { createEffect, onCleanup } from \"solid-js\";\nimport { observeElementInViewport } from \"observe-element-in-viewport\";\n\nimport { useModalContext, withModalElementContext } from \"../hooks/index.js\";\n\nexport type Props = {\n target?: string;\n action?: Action | \"\";\n on?: keyof HTMLElementEventMap | \"enter\" | \"exit\";\n preventDefault: boolean;\n};\n\nexport const Name = \"modal-trigger\";\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n withModalElementContext(\n () => props.target || \"\",\n () => ({\n on: props.on,\n action: props.action,\n preventDefault: props.preventDefault,\n }),\n (context, { on, action, preventDefault }) => {\n const controller = new AbortController();\n const [state, { open, close, toggle }] = useModalContext(context);\n switch (on) {\n case \"enter\": {\n let unsubscribe = null;\n if (action === \"toggle\") {\n unsubscribe = observeElementInViewport(element, open, close);\n }\n\n if (unsubscribe)\n controller.signal.addEventListener(\"abort\", unsubscribe, {\n once: true,\n });\n break;\n }\n case \"exit\": {\n let unsubscribe = null;\n if (action === \"toggle\") {\n unsubscribe = observeElementInViewport(element, close, open);\n }\n\n if (unsubscribe)\n controller.signal.addEventListener(\"abort\", unsubscribe, {\n once: true,\n });\n break;\n }\n\n default: {\n element.addEventListener(\n //@ts-ignore\n on,\n (event) => {\n if (preventDefault) event.preventDefault();\n if (state.isAnimating) return;\n switch (action) {\n case \"close\":\n close();\n break;\n case \"open\":\n open();\n break;\n case \"toggle\":\n toggle();\n break;\n }\n },\n { signal: controller.signal },\n );\n break;\n }\n }\n\n createEffect(() => {\n element.setAttribute(\"is-open\", `${state.isOpen}`);\n element.setAttribute(\"is-animating\", `${state.isAnimating}`);\n });\n\n onCleanup(() => {\n controller.abort();\n });\n },\n );\n};\n"]}
1
+ {"version":3,"file":"modal-trigger.js","names":[],"sources":["../../../src/components/modal-trigger.ts"],"sourcesContent":["import {\n\ttype CorrectComponentType,\n\tcreateGuardedEffect,\n\tensureId,\n\tfindDisclosureControl,\n} from \"@brytdesigns/web-component-core/utils\";\nimport { observeElementInViewport } from \"observe-element-in-viewport\";\n\nimport type { Action } from \"../consts\";\n\nimport { useModalAt, useModalContext } from \"../hooks/index.js\";\n\nexport type Props = {\n\ttarget?: string;\n\taction?: Action | \"\";\n\ton?: keyof HTMLElementEventMap | \"enter\" | \"exit\";\n\tpreventDefault: boolean;\n};\n\nexport const Name = \"modal-trigger\";\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\t// Lateral lookup: the modal is not an ancestor. Reading this in an effect's\n\t// compute phase defers the apply until it resolves, and re-runs if the\n\t// provider is replaced.\n\tconst modal = useModalAt(() => props.target, element);\n\n\tcreateGuardedEffect(\n\t\t() => ({\n\t\t\tcontext: modal(),\n\t\t\ton: props.on,\n\t\t\taction: props.action,\n\t\t\tpreventDefault: props.preventDefault,\n\t\t}),\n\t\t({ context, on, action, preventDefault }) => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst [state, { open, close, toggle }] = useModalContext(context);\n\n\t\t\tswitch (on) {\n\t\t\t\tcase \"enter\": {\n\t\t\t\t\tif (action === \"toggle\") {\n\t\t\t\t\t\tconst unsubscribe = observeElementInViewport(element, open, close);\n\t\t\t\t\t\tcontroller.signal.addEventListener(\"abort\", unsubscribe, {\n\t\t\t\t\t\t\tonce: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"exit\": {\n\t\t\t\t\tif (action === \"toggle\") {\n\t\t\t\t\t\tconst unsubscribe = observeElementInViewport(element, close, open);\n\t\t\t\t\t\tcontroller.signal.addEventListener(\"abort\", unsubscribe, {\n\t\t\t\t\t\t\tonce: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault: {\n\t\t\t\t\telement.addEventListener(\n\t\t\t\t\t\t//@ts-expect-error `on` is a free-form attribute\n\t\t\t\t\t\ton,\n\t\t\t\t\t\t(event) => {\n\t\t\t\t\t\t\tif (preventDefault) event.preventDefault();\n\t\t\t\t\t\t\tif (state.isAnimating) return;\n\t\t\t\t\t\t\tswitch (action) {\n\t\t\t\t\t\t\t\tcase \"close\":\n\t\t\t\t\t\t\t\t\tclose();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"open\":\n\t\t\t\t\t\t\t\t\topen();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"toggle\":\n\t\t\t\t\t\t\t\t\ttoggle();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ signal: controller.signal },\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn () => {\n\t\t\t\tcontroller.abort();\n\t\t\t};\n\t\t},\n\t);\n\n\t/**\n\t * ARIA for the control that opens the overlay.\n\t *\n\t * `aria-haspopup=\"dialog\"` rather than `aria-expanded`: this opens an\n\t * overlay and moves focus into it, which is not the disclosure pattern\n\t * `aria-expanded` describes. None of this was published before.\n\t *\n\t * A trigger with no button inside warns and sets nothing, matching\n\t * `dialog-trigger` — ARIA on a non-interactive wrapper would claim a\n\t * control that assistive tech cannot operate.\n\t */\n\tcreateGuardedEffect(\n\t\t// `target` belongs in the dependency list. It was read from the apply\n\t\t// phase, which does not track, so a trigger whose `target` changed kept\n\t\t// publishing `aria-controls` for the element it used to point at — the\n\t\t// same shape of defect as `product-variant-input` writing a stale\n\t\t// variant id, and just as quiet.\n\t\t() => ({ context: modal(), selector: props.target }),\n\t\t({ selector }) => {\n\t\t\tconst control = findDisclosureControl(element);\n\t\t\tif (!control) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t'modal-trigger: no <button> inside it, so no ARIA was applied. Wrap the trigger content in a <button type=\"button\">.',\n\t\t\t\t\telement,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcontrol.setAttribute(\"aria-haspopup\", \"dialog\");\n\t\t\tconst root = selector\n\t\t\t\t? document.querySelector<HTMLElement>(selector)\n\t\t\t\t: null;\n\t\t\t// An authored aria-controls is left alone.\n\t\t\tif (root && !control.hasAttribute(\"aria-controls\")) {\n\t\t\t\tcontrol.setAttribute(\"aria-controls\", ensureId(root, \"modal\"));\n\t\t\t}\n\t\t},\n\t);\n\n\t// Independent of which listener is wired, so it is its own effect.\n\tcreateGuardedEffect(\n\t\t() => {\n\t\t\tconst [state] = useModalContext(modal());\n\t\t\treturn { isOpen: state.isOpen, isAnimating: state.isAnimating };\n\t\t},\n\t\t({ isOpen, isAnimating }) => {\n\t\t\telement.setAttribute(\"is-open\", `${isOpen}`);\n\t\t\telement.setAttribute(\"is-animating\", `${isAnimating}`);\n\t\t},\n\t);\n};\n"],"mappings":";;;;;AAmBA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAI7E,MAAM,QAAQ,iBAAiB,MAAM,QAAQ,OAAO;CAEpD,2BACQ;EACN,SAAS,MAAM;EACf,IAAI,MAAM;EACV,QAAQ,MAAM;EACd,gBAAgB,MAAM;CACvB,KACC,EAAE,SAAS,IAAI,QAAQ,qBAAqB;EAC5C,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,CAAC,OAAO,EAAE,MAAM,OAAO,YAAY,gBAAgB,OAAO;EAEhE,QAAQ,IAAR;GACC,KAAK;IACJ,IAAI,WAAW,UAAU;KACxB,MAAM,cAAc,yBAAyB,SAAS,MAAM,KAAK;KACjE,WAAW,OAAO,iBAAiB,SAAS,aAAa,EACxD,MAAM,KACP,CAAC;IACF;IACA;GAED,KAAK;IACJ,IAAI,WAAW,UAAU;KACxB,MAAM,cAAc,yBAAyB,SAAS,OAAO,IAAI;KACjE,WAAW,OAAO,iBAAiB,SAAS,aAAa,EACxD,MAAM,KACP,CAAC;IACF;IACA;GAGD,SACC,QAAQ,iBAEP,KACC,UAAU;IACV,IAAI,gBAAgB,MAAM,eAAe;IACzC,IAAI,MAAM,aAAa;IACvB,QAAQ,QAAR;KACC,KAAK;MACJ,MAAM;MACN;KACD,KAAK;MACJ,KAAK;MACL;KACD,KAAK,UACJ,OAAO;IAET;GACD,GACA,EAAE,QAAQ,WAAW,OAAO,CAC7B;EAGF;EAEA,aAAa;GACZ,WAAW,MAAM;EAClB;CACD,CACD;;;;;;;;;;;;CAaA,2BAMQ;EAAE,SAAS,MAAM;EAAG,UAAU,MAAM;CAAO,KACjD,EAAE,eAAe;EACjB,MAAM,UAAU,sBAAsB,OAAO;EAC7C,IAAI,CAAC,SAAS;GACb,QAAQ,KACP,yHACA,OACD;GACA;EACD;EACA,QAAQ,aAAa,iBAAiB,QAAQ;EAC9C,MAAM,OAAO,WACV,SAAS,cAA2B,QAAQ,IAC5C;EAEH,IAAI,QAAQ,CAAC,QAAQ,aAAa,eAAe,GAChD,QAAQ,aAAa,iBAAiB,SAAS,MAAM,OAAO,CAAC;CAE/D,CACD;CAGA,0BACO;EACL,MAAM,CAAC,SAAS,gBAAgB,MAAM,CAAC;EACvC,OAAO;GAAE,QAAQ,MAAM;GAAQ,aAAa,MAAM;EAAY;CAC/D,IACC,EAAE,QAAQ,kBAAkB;EAC5B,QAAQ,aAAa,WAAW,GAAG,QAAQ;EAC3C,QAAQ,aAAa,gBAAgB,GAAG,aAAa;CACtD,CACD;AACD"}
@@ -1,3 +1,2 @@
1
- export * from './useModal.js';
2
- //# sourceMappingURL=index.js.map
3
- //# sourceMappingURL=index.js.map
1
+ import { getModalContext, provideModalContext, useModal, useModalAt, useModalContext } from "./useModal.js";
2
+ export { getModalContext, provideModalContext, useModal, useModalAt, useModalContext };
@@ -1,93 +1,94 @@
1
- import { createContext, provide, consume } from 'component-register';
2
- import { splitProps, createEffect, on, batch, onCleanup, untrack, mergeProps } from 'solid-js';
3
- import { createStore } from 'solid-js/store';
4
- import { abortablePromise } from '@brytdesigns/web-component-core/promise';
5
- import { awaitAllAnimations } from '@brytdesigns/web-component-core/animation';
6
- import { createWithElementContext, toHyphenated, getContextFromProvider } from '@brytdesigns/web-component-core/utils';
7
-
1
+ import { createGuardedEffect, createRemoteContext, getContextFromProvider, toHyphenated } from "@brytdesigns/web-component-core/utils";
2
+ import { awaitAllAnimations } from "@brytdesigns/web-component-core/animation";
3
+ import { createSignal, merge, omit, untrack } from "solid-js";
4
+ import { abortablePromise } from "@brytdesigns/web-component-core/promise";
5
+ import { consume, createContext, provide } from "component-register";
6
+ //#region src/hooks/useModal.ts
8
7
  function initializeModalContext(props) {
9
- const [element, stateProps] = splitProps(props, ["root"]);
10
- const [store, setStore] = createStore({ animationQueue: [] });
11
- createEffect(
12
- on(
13
- () => store.animationQueue,
14
- (animationQueue) => {
15
- if (!animationQueue.length) return;
16
- const controller = new AbortController();
17
- const animations = awaitAllAnimations(animationQueue);
18
- const abortableAnimations = abortablePromise(
19
- animations,
20
- controller.signal
21
- );
22
- setElementState("isAnimating", true);
23
- abortableAnimations.finally(() => {
24
- batch(() => {
25
- setElementState("isAnimating", false);
26
- setStore("animationQueue", []);
27
- });
28
- });
29
- return onCleanup(() => {
30
- controller.abort("Aborting modal animations");
31
- });
32
- }
33
- )
34
- );
35
- function setElementState(key, value) {
36
- const currentValue = untrack(() => props[key]);
37
- if (typeof value === "function") {
38
- const currentValue2 = untrack(() => props[key]);
39
- const result = value(currentValue2 || false);
40
- if (`${currentValue2}` === `${value}`) return;
41
- element.root.setAttribute(toHyphenated(key), `${result}`);
42
- return;
43
- }
44
- if (`${currentValue}` === `${value}`) return;
45
- return element.root.setAttribute(toHyphenated(key), `${value}`);
46
- }
47
- return [
48
- mergeProps(store, stateProps),
49
- { setElementState, setStore }
50
- ];
8
+ const stateProps = omit(props, "root");
9
+ /**
10
+ * The animation queue is a SIGNAL, not a store.
11
+ *
12
+ * A store deep-proxies what you put in it, and what goes in here is a list of
13
+ * live Motion playback controls. Proxied, every `.finished`, `.state` and
14
+ * `.then` on them became a reactive read from a non-tracking scope — hundreds
15
+ * of STRICT_READ_UNTRACKED reports under any interaction, and a proxy hop on
16
+ * every property access of an object that is not state at all.
17
+ *
18
+ * Nothing here needs per-property reactivity: the queue is replaced wholesale
19
+ * and read wholesale. A signal is the right primitive, and it hands the
20
+ * controls back exactly as they went in.
21
+ */
22
+ const [animationQueue, setAnimationQueue] = createSignal([]);
23
+ createGuardedEffect(animationQueue, (queue) => {
24
+ if (!queue.length) return;
25
+ const controller = new AbortController();
26
+ const animations = awaitAllAnimations(queue);
27
+ const abortableAnimations = abortablePromise(animations, controller.signal);
28
+ setElementState("isAnimating", true);
29
+ abortableAnimations.finally(() => {
30
+ setElementState("isAnimating", false);
31
+ setAnimationQueue([]);
32
+ });
33
+ return () => {
34
+ controller.abort("Aborting modal animations");
35
+ };
36
+ });
37
+ function setElementState(key, value) {
38
+ const currentValue = untrack(() => props[key]);
39
+ if (typeof value === "function") {
40
+ const currentValue = untrack(() => props[key]);
41
+ const result = value(currentValue || false);
42
+ if (`${currentValue}` === `${result}`) return;
43
+ props.root.setAttribute(toHyphenated(key), `${result}`);
44
+ return;
45
+ }
46
+ if (`${currentValue}` === `${value}`) return;
47
+ return props.root.setAttribute(toHyphenated(key), `${value}`);
48
+ }
49
+ return [merge(stateProps, { get animationQueue() {
50
+ return animationQueue();
51
+ } }), {
52
+ setElementState,
53
+ setAnimationQueue
54
+ }];
51
55
  }
52
- const ModalContextState = createContext(initializeModalContext);
53
- const provideModalContext = (initialState, element) => {
54
- const props = mergeProps(initialState, { root: element });
55
- return provide(ModalContextState, props, element);
56
+ var ModalContextState = createContext(initializeModalContext);
57
+ var provideModalContext = (initialState, element) => {
58
+ const props = merge(initialState, { root: element });
59
+ return provide(ModalContextState, props, element);
56
60
  };
57
- const useModalContext = (context) => {
58
- const [state, { setElementState, setStore: setState }] = context;
59
- function updateAnimationQueue(animation) {
60
- setState("animationQueue", (state2) => [...state2, animation]);
61
- }
62
- function close() {
63
- setElementState("isOpen", false);
64
- }
65
- function open() {
66
- setElementState("isOpen", true);
67
- }
68
- function toggle() {
69
- setElementState("isOpen", (open2) => !open2);
70
- }
71
- return [state, { updateAnimationQueue, close, open, toggle }];
61
+ var useModalContext = (context) => {
62
+ const [state, { setElementState, setAnimationQueue }] = context;
63
+ function updateAnimationQueue(animation) {
64
+ setAnimationQueue((queue) => [...queue, animation]);
65
+ }
66
+ function close() {
67
+ setElementState("isOpen", false);
68
+ }
69
+ function open() {
70
+ setElementState("isOpen", true);
71
+ }
72
+ function toggle() {
73
+ setElementState("isOpen", (open) => !open);
74
+ }
75
+ return [state, {
76
+ updateAnimationQueue,
77
+ close,
78
+ open,
79
+ toggle
80
+ }];
72
81
  };
73
- const useModal = (element) => {
74
- const context = consume(ModalContextState, element);
75
- if (!context) {
76
- throw console.error(
77
- "ModalContext not found! Please ensure to wrap your custom element with modal-context element."
78
- );
79
- }
80
- return useModalContext(context);
82
+ var useModal = (element) => {
83
+ const context = consume(ModalContextState, element);
84
+ if (!context) throw new Error("ModalContext not found! Please ensure to wrap your custom element with modal-context element.");
85
+ return useModalContext(context);
81
86
  };
82
- const withModalElementContext = createWithElementContext(ModalContextState);
83
- const getModalContext = (element) => {
84
- const context = getContextFromProvider(
85
- ModalContextState,
86
- element
87
- );
88
- return useModalContext(context);
87
+ var useModalAt = createRemoteContext(ModalContextState);
88
+ var getModalContext = (element) => {
89
+ return useModalContext(getContextFromProvider(ModalContextState, element));
89
90
  };
91
+ //#endregion
92
+ export { getModalContext, provideModalContext, useModal, useModalAt, useModalContext };
90
93
 
91
- export { getModalContext, provideModalContext, useModal, useModalContext, withModalElementContext };
92
- //# sourceMappingURL=useModal.js.map
93
94
  //# sourceMappingURL=useModal.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/useModal.ts"],"names":["currentValue","state","open"],"mappings":";;;;;;;AAwCA,SAAS,uBAAuB,KAA6B,EAAA;AAC3D,EAAM,MAAA,CAAC,SAAS,UAAU,CAAA,GAAI,WAAW,KAAO,EAAA,CAAC,MAAM,CAAC,CAAA;AACxD,EAAM,MAAA,CAAC,OAAO,QAAQ,CAAA,GAAI,YAA0B,EAAE,cAAA,EAAgB,EAAC,EAAG,CAAA;AAE1E,EAAA,YAAA;AAAA,IACE,EAAA;AAAA,MACE,MAAM,KAAM,CAAA,cAAA;AAAA,MACZ,CAAC,cAAmB,KAAA;AAClB,QAAI,IAAA,CAAC,eAAe,MAAQ,EAAA;AAC5B,QAAM,MAAA,UAAA,GAAa,IAAI,eAAgB,EAAA;AACvC,QAAM,MAAA,UAAA,GAAa,mBAAmB,cAAc,CAAA;AACpD,QAAA,MAAM,mBAAsB,GAAA,gBAAA;AAAA,UAC1B,UAAA;AAAA,UACA,UAAW,CAAA;AAAA,SACb;AACA,QAAA,eAAA,CAAgB,eAAe,IAAI,CAAA;AACnC,QAAA,mBAAA,CAAoB,QAAQ,MAAM;AAChC,UAAA,KAAA,CAAM,MAAM;AACV,YAAA,eAAA,CAAgB,eAAe,KAAK,CAAA;AACpC,YAAS,QAAA,CAAA,gBAAA,EAAkB,EAAE,CAAA;AAAA,WAC9B,CAAA;AAAA,SACF,CAAA;AAED,QAAA,OAAO,UAAU,MAAM;AACrB,UAAA,UAAA,CAAW,MAAM,2BAA2B,CAAA;AAAA,SAC7C,CAAA;AAAA;AACH;AACF,GACF;AAEA,EAAS,SAAA,eAAA,CACP,KACA,KACA,EAAA;AACA,IAAA,MAAM,YAAe,GAAA,OAAA,CAAQ,MAAM,KAAA,CAAM,GAAG,CAAC,CAAA;AAC7C,IAAI,IAAA,OAAO,UAAU,UAAY,EAAA;AAC/B,MAAA,MAAMA,aAAe,GAAA,OAAA,CAAQ,MAAM,KAAA,CAAM,GAAG,CAAC,CAAA;AAC7C,MAAM,MAAA,MAAA,GAAS,KAAMA,CAAAA,aAAAA,IAAgB,KAAK,CAAA;AAC1C,MAAA,IAAI,CAAGA,EAAAA,aAAY,CAAO,CAAA,KAAA,CAAA,EAAG,KAAK,CAAI,CAAA,EAAA;AACtC,MAAA,OAAA,CAAQ,KAAK,YAAa,CAAA,YAAA,CAAa,GAAG,CAAG,EAAA,CAAA,EAAG,MAAM,CAAE,CAAA,CAAA;AACxD,MAAA;AAAA;AAEF,IAAA,IAAI,CAAG,EAAA,YAAY,CAAO,CAAA,KAAA,CAAA,EAAG,KAAK,CAAI,CAAA,EAAA;AACtC,IAAO,OAAA,OAAA,CAAQ,KAAK,YAAa,CAAA,YAAA,CAAa,GAAG,CAAG,EAAA,CAAA,EAAG,KAAK,CAAE,CAAA,CAAA;AAAA;AAGhE,EAAO,OAAA;AAAA,IACL,UAAA,CAAW,OAAO,UAAU,CAAA;AAAA,IAC5B,EAAE,iBAAiB,QAAS;AAAA,GAC9B;AACF;AAEA,MAAM,iBAAA,GAAoB,cAAc,sBAAsB,CAAA;AAEjD,MAAA,mBAAA,GAAsB,CACjC,YAAA,EACA,OACiB,KAAA;AACjB,EAAA,MAAM,QAAQ,UAAW,CAAA,YAAA,EAAc,EAAE,IAAA,EAAM,SAAS,CAAA;AACxD,EAAO,OAAA,OAAA,CAAQ,iBAAmB,EAAA,KAAA,EAAO,OAAO,CAAA;AAClD;AAEa,MAAA,eAAA,GAAkB,CAAC,OAA0B,KAAA;AACxD,EAAA,MAAM,CAAC,KAAO,EAAA,EAAE,iBAAiB,QAAU,EAAA,QAAA,EAAU,CAAI,GAAA,OAAA;AAEzD,EAAA,SAAS,qBAAqB,SAA8C,EAAA;AAC1E,IAAA,QAAA,CAAS,kBAAkB,CAACC,MAAAA,KAAU,CAAC,GAAGA,MAAAA,EAAO,SAAS,CAAC,CAAA;AAAA;AAG7D,EAAA,SAAS,KAAQ,GAAA;AACf,IAAA,eAAA,CAAgB,UAAU,KAAK,CAAA;AAAA;AAGjC,EAAA,SAAS,IAAO,GAAA;AACd,IAAA,eAAA,CAAgB,UAAU,IAAI,CAAA;AAAA;AAGhC,EAAA,SAAS,MAAS,GAAA;AAChB,IAAA,eAAA,CAAgB,QAAU,EAAA,CAACC,KAAS,KAAA,CAACA,KAAI,CAAA;AAAA;AAG3C,EAAA,OAAO,CAAC,KAAO,EAAA,EAAE,sBAAsB,KAAO,EAAA,IAAA,EAAM,QAAQ,CAAA;AAC9D;AAEa,MAAA,QAAA,GAAW,CAAC,OAA0C,KAAA;AACjE,EAAM,MAAA,OAAA,GAAwB,OAAQ,CAAA,iBAAA,EAAmB,OAAO,CAAA;AAEhE,EAAA,IAAI,CAAC,OAAS,EAAA;AACZ,IAAA,MAAM,OAAQ,CAAA,KAAA;AAAA,MACZ;AAAA,KACF;AAAA;AAGF,EAAA,OAAO,gBAAgB,OAAO,CAAA;AAChC;AAEa,MAAA,uBAAA,GAA0B,yBAGrC,iBAAiB;AAEN,MAAA,eAAA,GAAkB,CAAC,OAAqB,KAAA;AACnD,EAAA,MAAM,OAAU,GAAA,sBAAA;AAAA,IACd,iBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO,gBAAgB,OAAO,CAAA;AAChC","file":"useModal.js","sourcesContent":["import {\n type ICustomElement,\n createContext,\n provide,\n consume,\n} from \"component-register\";\nimport {\n createEffect,\n mergeProps,\n on,\n batch,\n splitProps,\n untrack,\n onCleanup,\n} from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\n\nimport type { AnimationPlaybackControlsWithThen } from \"motion\";\nimport { abortablePromise } from \"@brytdesigns/web-component-core/promise\";\nimport { awaitAllAnimations } from \"@brytdesigns/web-component-core/animation\";\nimport {\n toHyphenated,\n getContextFromProvider,\n createWithElementContext,\n} from \"@brytdesigns/web-component-core/utils\";\n\ntype StoreContext = {\n animationQueue: AnimationPlaybackControlsWithThen[];\n};\n\ntype CreateContextOptions = {\n root: HTMLElement & ICustomElement;\n isOpen: boolean;\n isAnimating: boolean;\n};\n\ntype WalkableNode = Parameters<typeof provide>[2];\n\ntype ModalContext = ReturnType<typeof initializeModalContext>;\n\nfunction initializeModalContext(props: CreateContextOptions) {\n const [element, stateProps] = splitProps(props, [\"root\"]);\n const [store, setStore] = createStore<StoreContext>({ animationQueue: [] });\n\n createEffect(\n on(\n () => store.animationQueue,\n (animationQueue) => {\n if (!animationQueue.length) return;\n const controller = new AbortController();\n const animations = awaitAllAnimations(animationQueue);\n const abortableAnimations = abortablePromise(\n animations,\n controller.signal,\n );\n setElementState(\"isAnimating\", true);\n abortableAnimations.finally(() => {\n batch(() => {\n setElementState(\"isAnimating\", false);\n setStore(\"animationQueue\", []);\n });\n });\n\n return onCleanup(() => {\n controller.abort(\"Aborting modal animations\");\n });\n },\n ),\n );\n\n function setElementState(\n key: keyof Omit<CreateContextOptions, \"root\">,\n value: boolean | ((v: boolean) => boolean),\n ) {\n const currentValue = untrack(() => props[key]);\n if (typeof value === \"function\") {\n const currentValue = untrack(() => props[key]);\n const result = value(currentValue || false);\n if (`${currentValue}` === `${value}`) return;\n element.root.setAttribute(toHyphenated(key), `${result}`);\n return;\n }\n if (`${currentValue}` === `${value}`) return;\n return element.root.setAttribute(toHyphenated(key), `${value}`);\n }\n\n return [\n mergeProps(store, stateProps),\n { setElementState, setStore },\n ] as const;\n}\n\nconst ModalContextState = createContext(initializeModalContext);\n\nexport const provideModalContext = (\n initialState: Omit<CreateContextOptions, \"root\">,\n element: WalkableNode,\n): ModalContext => {\n const props = mergeProps(initialState, { root: element });\n return provide(ModalContextState, props, element);\n};\n\nexport const useModalContext = (context: ModalContext) => {\n const [state, { setElementState, setStore: setState }] = context;\n\n function updateAnimationQueue(animation: AnimationPlaybackControlsWithThen) {\n setState(\"animationQueue\", (state) => [...state, animation]);\n }\n\n function close() {\n setElementState(\"isOpen\", false);\n }\n\n function open() {\n setElementState(\"isOpen\", true);\n }\n\n function toggle() {\n setElementState(\"isOpen\", (open) => !open);\n }\n\n return [state, { updateAnimationQueue, close, open, toggle }] as const;\n};\n\nexport const useModal = (element: HTMLElement & ICustomElement) => {\n const context: ModalContext = consume(ModalContextState, element);\n\n if (!context) {\n throw console.error(\n \"ModalContext not found! Please ensure to wrap your custom element with modal-context element.\",\n );\n }\n\n return useModalContext(context);\n};\n\nexport const withModalElementContext = createWithElementContext<\n typeof ModalContextState,\n ModalContext\n>(ModalContextState);\n\nexport const getModalContext = (element: Element) => {\n const context = getContextFromProvider<ModalContext>(\n ModalContextState,\n element,\n );\n return useModalContext(context);\n};\n"]}
1
+ {"version":3,"file":"useModal.js","names":[],"sources":["../../../src/hooks/useModal.ts"],"sourcesContent":["import { awaitAllAnimations } from \"@brytdesigns/web-component-core/animation\";\nimport { abortablePromise } from \"@brytdesigns/web-component-core/promise\";\nimport {\n\tcreateGuardedEffect,\n\tcreateRemoteContext,\n\tgetContextFromProvider,\n\ttoHyphenated,\n} from \"@brytdesigns/web-component-core/utils\";\nimport {\n\tconsume,\n\tcreateContext,\n\ttype ICustomElement,\n\tprovide,\n} from \"component-register\";\nimport type { AnimationPlaybackControlsWithThen } from \"motion\";\nimport { createSignal, merge, omit, untrack } from \"solid-js\";\n\ntype StoreContext = {\n\tanimationQueue: AnimationPlaybackControlsWithThen[];\n};\n\ntype CreateContextOptions = {\n\troot: HTMLElement & ICustomElement;\n\tisOpen: boolean;\n\tisAnimating: boolean;\n};\n\ntype WalkableNode = Parameters<typeof provide>[2];\n\ntype ModalContext = ReturnType<typeof initializeModalContext>;\n\nfunction initializeModalContext(props: CreateContextOptions) {\n\t// `omit` returns only the rest; `root` is read straight off `props`.\n\tconst stateProps = omit(props, \"root\");\n\t/**\n\t * The animation queue is a SIGNAL, not a store.\n\t *\n\t * A store deep-proxies what you put in it, and what goes in here is a list of\n\t * live Motion playback controls. Proxied, every `.finished`, `.state` and\n\t * `.then` on them became a reactive read from a non-tracking scope — hundreds\n\t * of STRICT_READ_UNTRACKED reports under any interaction, and a proxy hop on\n\t * every property access of an object that is not state at all.\n\t *\n\t * Nothing here needs per-property reactivity: the queue is replaced wholesale\n\t * and read wholesale. A signal is the right primitive, and it hands the\n\t * controls back exactly as they went in.\n\t */\n\tconst [animationQueue, setAnimationQueue] = createSignal<\n\t\tAnimationPlaybackControlsWithThen[]\n\t>([]);\n\n\tcreateGuardedEffect(animationQueue, (queue) => {\n\t\tif (!queue.length) return;\n\t\tconst controller = new AbortController();\n\t\tconst animations = awaitAllAnimations(queue);\n\t\tconst abortableAnimations = abortablePromise(animations, controller.signal);\n\t\tsetElementState(\"isAnimating\", true);\n\t\tabortableAnimations.finally(() => {\n\t\t\t// No `batch` in v2 — writes stage and flush together by default.\n\t\t\tsetElementState(\"isAnimating\", false);\n\t\t\tsetAnimationQueue([]);\n\t\t});\n\n\t\treturn () => {\n\t\t\tcontroller.abort(\"Aborting modal animations\");\n\t\t};\n\t});\n\n\tfunction setElementState(\n\t\tkey: keyof Omit<CreateContextOptions, \"root\">,\n\t\tvalue: boolean | ((v: boolean) => boolean),\n\t) {\n\t\tconst currentValue = untrack(() => props[key]);\n\t\tif (typeof value === \"function\") {\n\t\t\tconst currentValue = untrack(() => props[key]);\n\t\t\tconst result = value(currentValue || false);\n\t\t\t// Compare against the computed result, not `value` — `value` is the\n\t\t\t// updater function here, and its source text never equals a state\n\t\t\t// value, so this guard never fired on the updater path. Latent\n\t\t\t// rather than active today: every updater in the repo is a\n\t\t\t// negation, so the result always differs anyway.\n\t\t\tif (`${currentValue}` === `${result}`) return;\n\t\t\tprops.root.setAttribute(toHyphenated(key), `${result}`);\n\t\t\treturn;\n\t\t}\n\t\tif (`${currentValue}` === `${value}`) return;\n\t\treturn props.root.setAttribute(toHyphenated(key), `${value}`);\n\t}\n\n\treturn [\n\t\tmerge(stateProps, {\n\t\t\tget animationQueue() {\n\t\t\t\treturn animationQueue();\n\t\t\t},\n\t\t}),\n\t\t{ setElementState, setAnimationQueue },\n\t] as const;\n}\n\nconst ModalContextState = createContext(initializeModalContext);\n\nexport const provideModalContext = (\n\tinitialState: Omit<CreateContextOptions, \"root\">,\n\telement: WalkableNode,\n): ModalContext => {\n\tconst props = merge(initialState, { root: element });\n\treturn provide(ModalContextState, props, element);\n};\n\nexport const useModalContext = (context: ModalContext) => {\n\tconst [state, { setElementState, setAnimationQueue }] = context;\n\n\tfunction updateAnimationQueue(animation: AnimationPlaybackControlsWithThen) {\n\t\t// v2 store setters are draft-first, but this one REPLACES the array\n\t\t// rather than pushing into it. The effect above tracks\n\t\t// `store.animationQueue` — the property, not its contents — and an\n\t\t// in-place `push` leaves that property pointing at the same array, so\n\t\t// the effect never re-ran and `is-animating` was never published at\n\t\t// any point, open or closed.\n\t\tsetAnimationQueue((queue) => [...queue, animation]);\n\t}\n\n\tfunction close() {\n\t\tsetElementState(\"isOpen\", false);\n\t}\n\n\tfunction open() {\n\t\tsetElementState(\"isOpen\", true);\n\t}\n\n\tfunction toggle() {\n\t\tsetElementState(\"isOpen\", (open) => !open);\n\t}\n\n\treturn [state, { updateAnimationQueue, close, open, toggle }] as const;\n};\n\nexport const useModal = (element: HTMLElement & ICustomElement) => {\n\tconst context: ModalContext = consume(ModalContextState, element);\n\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"ModalContext not found! Please ensure to wrap your custom element with modal-context element.\",\n\t\t);\n\t}\n\n\treturn useModalContext(context);\n};\n\nexport const useModalAt = createRemoteContext<ModalContext>(ModalContextState);\n\nexport const getModalContext = (element: Element) => {\n\tconst context = getContextFromProvider<ModalContext>(\n\t\tModalContextState,\n\t\telement,\n\t);\n\treturn useModalContext(context);\n};\n"],"mappings":";;;;;;AA+BA,SAAS,uBAAuB,OAA6B;CAE5D,MAAM,aAAa,KAAK,OAAO,MAAM;;;;;;;;;;;;;;CAcrC,MAAM,CAAC,gBAAgB,qBAAqB,aAE1C,CAAC,CAAC;CAEJ,oBAAoB,iBAAiB,UAAU;EAC9C,IAAI,CAAC,MAAM,QAAQ;EACnB,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,aAAa,mBAAmB,KAAK;EAC3C,MAAM,sBAAsB,iBAAiB,YAAY,WAAW,MAAM;EAC1E,gBAAgB,eAAe,IAAI;EACnC,oBAAoB,cAAc;GAEjC,gBAAgB,eAAe,KAAK;GACpC,kBAAkB,CAAC,CAAC;EACrB,CAAC;EAED,aAAa;GACZ,WAAW,MAAM,2BAA2B;EAC7C;CACD,CAAC;CAED,SAAS,gBACR,KACA,OACC;EACD,MAAM,eAAe,cAAc,MAAM,IAAI;EAC7C,IAAI,OAAO,UAAU,YAAY;GAChC,MAAM,eAAe,cAAc,MAAM,IAAI;GAC7C,MAAM,SAAS,MAAM,gBAAgB,KAAK;GAM1C,IAAI,GAAG,mBAAmB,GAAG,UAAU;GACvC,MAAM,KAAK,aAAa,aAAa,GAAG,GAAG,GAAG,QAAQ;GACtD;EACD;EACA,IAAI,GAAG,mBAAmB,GAAG,SAAS;EACtC,OAAO,MAAM,KAAK,aAAa,aAAa,GAAG,GAAG,GAAG,OAAO;CAC7D;CAEA,OAAO,CACN,MAAM,YAAY,EACjB,IAAI,iBAAiB;EACpB,OAAO,eAAe;CACvB,EACD,CAAC,GACD;EAAE;EAAiB;CAAkB,CACtC;AACD;AAEA,IAAM,oBAAoB,cAAc,sBAAsB;AAE9D,IAAa,uBACZ,cACA,YACkB;CAClB,MAAM,QAAQ,MAAM,cAAc,EAAE,MAAM,QAAQ,CAAC;CACnD,OAAO,QAAQ,mBAAmB,OAAO,OAAO;AACjD;AAEA,IAAa,mBAAmB,YAA0B;CACzD,MAAM,CAAC,OAAO,EAAE,iBAAiB,uBAAuB;CAExD,SAAS,qBAAqB,WAA8C;EAO3E,mBAAmB,UAAU,CAAC,GAAG,OAAO,SAAS,CAAC;CACnD;CAEA,SAAS,QAAQ;EAChB,gBAAgB,UAAU,KAAK;CAChC;CAEA,SAAS,OAAO;EACf,gBAAgB,UAAU,IAAI;CAC/B;CAEA,SAAS,SAAS;EACjB,gBAAgB,WAAW,SAAS,CAAC,IAAI;CAC1C;CAEA,OAAO,CAAC,OAAO;EAAE;EAAsB;EAAO;EAAM;CAAO,CAAC;AAC7D;AAEA,IAAa,YAAY,YAA0C;CAClE,MAAM,UAAwB,QAAQ,mBAAmB,OAAO;CAEhE,IAAI,CAAC,SACJ,MAAM,IAAI,MACT,+FACD;CAGD,OAAO,gBAAgB,OAAO;AAC/B;AAEA,IAAa,aAAa,oBAAkC,iBAAiB;AAE7E,IAAa,mBAAmB,YAAqB;CAKpD,OAAO,gBAJS,uBACf,mBACA,OAEsB,CAAO;AAC/B"}
@@ -3,43 +3,45 @@
3
3
  import { ICustomElement } from 'component-register';
4
4
  import { AnimationPlaybackControlsWithThen } from 'motion';
5
5
 
6
+ /**
7
+ * `component-register` types `options.element` as a bare `ICustomElement`, but
8
+ * at runtime it is always also an `HTMLElement`. Every component in this repo
9
+ * needs the DOM half.
10
+ */
6
11
  export type CorrectComponentOptions = {
7
12
  element: HTMLElement & ICustomElement;
8
13
  };
9
14
  export type CorrectComponentType<T> = (props: T, options: CorrectComponentOptions) => unknown;
10
15
  export type Props = {};
11
- declare const Name = "modal-panel";
16
+ declare const Name = "modal-backdrop";
12
17
  declare const Component: CorrectComponentType<Props>;
18
+ type Props$1 = {
19
+ isOpen: boolean;
20
+ id: string;
21
+ closeOnEscape: boolean;
22
+ shouldTrapFocus: boolean;
23
+ isAnimating: boolean;
24
+ delay: number;
25
+ };
26
+ declare const Name$1 = "modal-context";
27
+ declare const Component$1: CorrectComponentType<Props$1>;
28
+ type Props$2 = {};
29
+ declare const Name$2 = "modal-panel";
30
+ declare const Component$2: CorrectComponentType<Props$2>;
13
31
  export type Action = (typeof ACTION)[keyof typeof ACTION];
14
32
  declare const ACTION: {
15
33
  OPEN: string;
16
34
  CLOSE: string;
17
35
  TOGGLE: string;
18
36
  };
19
- type Props$1 = {
37
+ type Props$3 = {
20
38
  target?: string;
21
39
  action?: Action | "";
22
40
  on?: keyof HTMLElementEventMap | "enter" | "exit";
23
41
  preventDefault: boolean;
24
42
  };
25
- declare const Name$1 = "modal-trigger";
26
- declare const Component$1: CorrectComponentType<Props$1>;
27
- type Props$2 = {};
28
- declare const Name$2 = "modal-backdrop";
29
- declare const Component$2: CorrectComponentType<Props$2>;
30
- type Props$3 = {
31
- isOpen: boolean;
32
- id: string;
33
- closeOnEscape: boolean;
34
- shouldTrapFocus: boolean;
35
- isAnimating: boolean;
36
- delay: number;
37
- };
38
- declare const Name$3 = "modal-context";
43
+ declare const Name$3 = "modal-trigger";
39
44
  declare const Component$3: CorrectComponentType<Props$3>;
40
- export type StoreContext = {
41
- animationQueue: AnimationPlaybackControlsWithThen[];
42
- };
43
45
  export type CreateContextOptions = {
44
46
  root: HTMLElement & ICustomElement;
45
47
  isOpen: boolean;
@@ -47,9 +49,9 @@ export type CreateContextOptions = {
47
49
  };
48
50
  export declare const useModal: (element: HTMLElement & ICustomElement) => readonly [
49
51
  {
50
- animationQueue: AnimationPlaybackControlsWithThen[];
51
52
  isOpen: boolean;
52
53
  isAnimating: boolean;
54
+ readonly animationQueue: AnimationPlaybackControlsWithThen[];
53
55
  },
54
56
  {
55
57
  readonly updateAnimationQueue: (animation: AnimationPlaybackControlsWithThen) => void;
@@ -58,22 +60,22 @@ export declare const useModal: (element: HTMLElement & ICustomElement) => readon
58
60
  readonly toggle: () => void;
59
61
  }
60
62
  ];
61
- export declare const withModalElementContext: <D extends unknown>(target: string | import("solid-js").Accessor<string> | import("solid-js").Accessor<() => Element>, dependencies: import("solid-js").Accessor<D> | undefined, cb: (context: readonly [
63
+ export declare const useModalAt: (target: () => string | undefined, host: HTMLElement) => import("solid-js").SourceAccessor<readonly [
62
64
  {
63
- animationQueue: AnimationPlaybackControlsWithThen[];
64
65
  isOpen: boolean;
65
66
  isAnimating: boolean;
67
+ readonly animationQueue: AnimationPlaybackControlsWithThen[];
66
68
  },
67
69
  {
68
70
  readonly setElementState: (key: keyof Omit<CreateContextOptions, "root">, value: boolean | ((v: boolean) => boolean)) => void;
69
- readonly setStore: import("solid-js/store").SetStoreFunction<StoreContext>;
71
+ readonly setAnimationQueue: import("solid-js").Setter<AnimationPlaybackControlsWithThen[]>;
70
72
  }
71
- ], dependencies: D) => void | (() => void)) => void;
73
+ ]>;
72
74
  export declare const getModalContext: (element: Element) => readonly [
73
75
  {
74
- animationQueue: AnimationPlaybackControlsWithThen[];
75
76
  isOpen: boolean;
76
77
  isAnimating: boolean;
78
+ readonly animationQueue: AnimationPlaybackControlsWithThen[];
77
79
  },
78
80
  {
79
81
  readonly updateAnimationQueue: (animation: AnimationPlaybackControlsWithThen) => void;
@@ -82,12 +84,12 @@ export declare const getModalContext: (element: Element) => readonly [
82
84
  readonly toggle: () => void;
83
85
  }
84
86
  ];
85
- export type ModalTriggerProps = Props$1;
86
- export type ModalBackdropProps = Props$2;
87
- export type ModalPanelProps = Props;
88
- export type ModalContextProps = ModalContext.Props;
87
+ export type ModalTriggerProps = ModalTrigger.Props;
88
+ export type ModalBackdropProps = Props;
89
+ export type ModalPanelProps = Props$2;
90
+ export type ModalContextProps = Props$1;
89
91
 
90
- declare namespace ModalContext {
92
+ declare namespace ModalTrigger {
91
93
  export { Component$3 as Component, Name$3 as Name, Props$3 as Props };
92
94
  }
93
95