@brytdesigns/web-component-accordion 1.1.7 → 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.
- package/dist/main/components/accordion-content.js +55 -111
- package/dist/main/components/accordion-content.js.map +1 -1
- package/dist/main/components/accordion-context.js +27 -19
- package/dist/main/components/accordion-context.js.map +1 -1
- package/dist/main/components/accordion-item.js +9 -13
- package/dist/main/components/accordion-item.js.map +1 -1
- package/dist/main/components/accordion-trigger.js +32 -25
- package/dist/main/components/accordion-trigger.js.map +1 -1
- package/dist/main/components/index.js +4 -10
- package/dist/main/hooks/index.js +3 -4
- package/dist/main/hooks/useAccordion.js +90 -77
- package/dist/main/hooks/useAccordion.js.map +1 -1
- package/dist/main/hooks/useAccordionItem.js +77 -99
- package/dist/main/hooks/useAccordionItem.js.map +1 -1
- package/dist/main/index.d.ts +52 -17
- package/dist/main/index.js +23 -24
- package/dist/main/index.js.map +1 -1
- package/dist/styles.css +35 -0
- package/package.json +10 -10
- package/dist/esm/index.js +0 -391
- package/dist/esm/index.js.map +0 -1
- package/dist/main/components/index.js.map +0 -1
- package/dist/main/hooks/index.js.map +0 -1
|
@@ -1,114 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import { useAccordion } from "../hooks/useAccordion.js";
|
|
2
|
+
import { useAccordionItem } from "../hooks/useAccordionItem.js";
|
|
3
|
+
import "../hooks/index.js";
|
|
4
|
+
import { createGuardedEffect } from "@brytdesigns/web-component-core/utils";
|
|
5
|
+
import { collapseContent, controlsToPromise, expandContent, hideContent, onContentRevealed, shouldReduceMotion, showContent } from "@brytdesigns/web-component-core/animation";
|
|
6
|
+
import { onCleanup, untrack } from "solid-js";
|
|
7
|
+
//#region src/components/accordion-content.ts
|
|
8
|
+
var Name = `accordion-content`;
|
|
9
|
+
var Component = (props, { element }) => {
|
|
10
|
+
const [state, itemMethods] = useAccordionItem(element);
|
|
11
|
+
const [accordionState, accordionMethods] = useAccordion(element);
|
|
12
|
+
let isFirstRender = true;
|
|
13
|
+
let seenRevealCount = untrack(() => accordionState.revealCount);
|
|
14
|
+
onCleanup(onContentRevealed(element, () => accordionMethods.reveal(itemMethods.expand)));
|
|
15
|
+
const expand = (element, isStale) => {
|
|
16
|
+
const animation = expandContent(element, isStale);
|
|
17
|
+
animation.then(() => {
|
|
18
|
+
if (isStale()) return;
|
|
19
|
+
if (props.shouldScrollIntoView) element.scrollIntoView({
|
|
20
|
+
behavior: shouldReduceMotion() ? "instant" : "smooth",
|
|
21
|
+
block: "start",
|
|
22
|
+
inline: "nearest"
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
return animation;
|
|
26
|
+
};
|
|
27
|
+
createGuardedEffect(() => state.isExpanded, (isExpanded) => {
|
|
28
|
+
const revealCount = untrack(() => accordionState.revealCount);
|
|
29
|
+
const isRevealing = revealCount !== seenRevealCount;
|
|
30
|
+
seenRevealCount = revealCount;
|
|
31
|
+
if (isFirstRender || isRevealing) {
|
|
32
|
+
isFirstRender = false;
|
|
33
|
+
if (isExpanded) showContent(element);
|
|
34
|
+
else hideContent(element);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let stale = false;
|
|
38
|
+
const isStale = () => stale;
|
|
39
|
+
if (!isExpanded) {
|
|
40
|
+
const animation = collapseContent(element, isStale);
|
|
41
|
+
accordionMethods.updateAnimationQueue(controlsToPromise(animation));
|
|
42
|
+
return () => {
|
|
43
|
+
stale = true;
|
|
44
|
+
animation.complete();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const animation = expand(element, isStale);
|
|
48
|
+
accordionMethods.updateAnimationQueue(controlsToPromise(animation));
|
|
49
|
+
return () => {
|
|
50
|
+
stale = true;
|
|
51
|
+
animation.complete();
|
|
52
|
+
};
|
|
53
|
+
});
|
|
31
54
|
};
|
|
32
|
-
|
|
33
|
-
const paddingY = parseInt(getComputedStyle(element).paddingBottom);
|
|
34
|
-
return paddingY;
|
|
35
|
-
};
|
|
36
|
-
const Name = `accordion-content`;
|
|
37
|
-
const Component = (props, { element }) => {
|
|
38
|
-
const [state] = useAccordionItem(element);
|
|
39
|
-
const [_, accordionMethods] = useAccordion(element);
|
|
40
|
-
let isFirstRender = true;
|
|
41
|
-
const expand = (element2) => {
|
|
42
|
-
showElement(element2);
|
|
43
|
-
const height = getHeight(element2), pt = getPaddingTop(element2), pb = getPaddingBottom(element2);
|
|
44
|
-
Array.from(element2.children).forEach(
|
|
45
|
-
(child) => child.style.opacity = "1"
|
|
46
|
-
);
|
|
47
|
-
const animation = animate(
|
|
48
|
-
element2,
|
|
49
|
-
{
|
|
50
|
-
maxHeight: ["0px", `${height}px`],
|
|
51
|
-
paddingTop: ["0px", `${pt}px`],
|
|
52
|
-
paddingBottom: ["0px", `${pb}px`]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
duration: 0.35
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
animation.then(() => {
|
|
59
|
-
element2.style.maxHeight = "max-content";
|
|
60
|
-
element2.style.overflow = null;
|
|
61
|
-
if (props.shouldScrollIntoView)
|
|
62
|
-
element2.scrollIntoView({
|
|
63
|
-
behavior: "smooth",
|
|
64
|
-
block: "start",
|
|
65
|
-
inline: "nearest"
|
|
66
|
-
});
|
|
67
|
-
return;
|
|
68
|
-
});
|
|
69
|
-
return animation;
|
|
70
|
-
};
|
|
71
|
-
const collapse = (element2) => {
|
|
72
|
-
const height = getHeight(element2), pt = getPaddingTop(element2), pb = getPaddingBottom(element2);
|
|
73
|
-
element2.style.overflow = "hidden";
|
|
74
|
-
Array.from(element2.children).forEach(
|
|
75
|
-
(child) => child.style.opacity = "0"
|
|
76
|
-
);
|
|
77
|
-
const animation = animate(
|
|
78
|
-
element2,
|
|
79
|
-
{
|
|
80
|
-
maxHeight: [`${height}px`, "0px"],
|
|
81
|
-
paddingTop: [`${pt}px`, "0px"],
|
|
82
|
-
paddingBottom: [`${pb}px`, "0px"]
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
duration: 0.35
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
animation.then(() => hideElement(element2));
|
|
89
|
-
return animation;
|
|
90
|
-
};
|
|
91
|
-
createEffect(
|
|
92
|
-
on(
|
|
93
|
-
() => state.isExpanded,
|
|
94
|
-
(isExpanded) => {
|
|
95
|
-
if (!isExpanded) {
|
|
96
|
-
const animation2 = collapse(element);
|
|
97
|
-
accordionMethods.updateAnimationQueue(controlsToPromise(animation2));
|
|
98
|
-
return onCleanup(() => animation2.complete());
|
|
99
|
-
}
|
|
100
|
-
if (isFirstRender) {
|
|
101
|
-
isFirstRender = false;
|
|
102
|
-
showElement(element);
|
|
103
|
-
}
|
|
104
|
-
const animation = expand(element);
|
|
105
|
-
accordionMethods.updateAnimationQueue(controlsToPromise(animation));
|
|
106
|
-
return onCleanup(() => animation.complete());
|
|
107
|
-
}
|
|
108
|
-
)
|
|
109
|
-
);
|
|
110
|
-
};
|
|
111
|
-
|
|
55
|
+
//#endregion
|
|
112
56
|
export { Component, Name };
|
|
113
|
-
|
|
57
|
+
|
|
114
58
|
//# sourceMappingURL=accordion-content.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"accordion-content.js","names":[],"sources":["../../../src/components/accordion-content.ts"],"sourcesContent":["import {\n\tcollapseContent,\n\tcontrolsToPromise,\n\texpandContent,\n\thideContent,\n\tonContentRevealed,\n\tshouldReduceMotion,\n\tshowContent,\n} from \"@brytdesigns/web-component-core/animation\";\nimport type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport { createGuardedEffect } from \"@brytdesigns/web-component-core/utils\";\nimport { onCleanup, untrack } from \"solid-js\";\n\nimport { useAccordion, useAccordionItem } from \"../hooks/index.js\";\n\nexport type Props = {\n\tshouldScrollIntoView: boolean;\n};\n\nexport const Name = `accordion-content`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tconst [state, itemMethods] = useAccordionItem(element);\n\tconst [accordionState, accordionMethods] = useAccordion(element);\n\n\tlet isFirstRender = true;\n\t// The reveal counter as it stood when this content last applied a state.\n\t// See `revealCount` in useAccordion for why this is a counter rather than a\n\t// flag held across the write.\n\tlet seenRevealCount = untrack(() => accordionState.revealCount);\n\n\t// Component-level `onCleanup` does register and fire; inside an effect's\n\t// apply phase it would not, because the apply phase is not a computation.\n\tonCleanup(\n\t\tonContentRevealed(element, () =>\n\t\t\taccordionMethods.reveal(itemMethods.expand),\n\t\t),\n\t);\n\n\tconst expand = (element: HTMLElement, isStale: () => boolean) => {\n\t\tconst animation = expandContent(element, isStale);\n\n\t\tanimation.then(() => {\n\t\t\tif (isStale()) return;\n\t\t\tif (props.shouldScrollIntoView)\n\t\t\t\telement.scrollIntoView({\n\t\t\t\t\tbehavior: shouldReduceMotion() ? \"instant\" : \"smooth\",\n\t\t\t\t\tblock: \"start\",\n\t\t\t\t\tinline: \"nearest\",\n\t\t\t\t});\n\t\t});\n\n\t\treturn animation;\n\t};\n\n\tcreateGuardedEffect(\n\t\t() => state.isExpanded,\n\t\t(isExpanded) => {\n\t\t\t// The apply phase is untracked, so this reads the counter as it\n\t\t\t// stands at flush time without subscribing to it. Spelled with\n\t\t\t// `untrack` rather than left implicit: Solid reports a bare read\n\t\t\t// here (STRICT_READ_UNTRACKED) because it cannot tell a deliberate\n\t\t\t// snapshot from a dependency someone forgot, and the whole value of\n\t\t\t// that diagnostic is that every remaining one is unreviewed.\n\t\t\tconst revealCount = untrack(() => accordionState.revealCount);\n\t\t\tconst isRevealing = revealCount !== seenRevealCount;\n\t\t\tseenRevealCount = revealCount;\n\n\t\t\t// The first render applies the starting state without animating, as\n\t\t\t// does a find-in-page reveal: the match is scrolled to straight after,\n\t\t\t// so the layout has to be settled by then.\n\t\t\tif (isFirstRender || isRevealing) {\n\t\t\t\tisFirstRender = false;\n\t\t\t\tif (isExpanded) showContent(element);\n\t\t\t\telse hideContent(element);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Flipped by this run's cleanup, so a completed-but-still-pending\n\t\t\t// animation cannot write styles for a direction we have left.\n\t\t\tlet stale = false;\n\t\t\tconst isStale = () => stale;\n\n\t\t\tif (!isExpanded) {\n\t\t\t\tconst animation = collapseContent(element, isStale);\n\t\t\t\taccordionMethods.updateAnimationQueue(controlsToPromise(animation));\n\t\t\t\t// v2 treats the apply phase's return value as the cleanup, so the\n\t\t\t\t// function is returned directly rather than via `onCleanup`.\n\t\t\t\treturn () => {\n\t\t\t\t\tstale = true;\n\t\t\t\t\tanimation.complete();\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst animation = expand(element, isStale);\n\t\t\taccordionMethods.updateAnimationQueue(controlsToPromise(animation));\n\t\t\treturn () => {\n\t\t\t\tstale = true;\n\t\t\t\tanimation.complete();\n\t\t\t};\n\t\t},\n\t);\n};\n"],"mappings":";;;;;;;AAmBA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,MAAM,CAAC,OAAO,eAAe,iBAAiB,OAAO;CACrD,MAAM,CAAC,gBAAgB,oBAAoB,aAAa,OAAO;CAE/D,IAAI,gBAAgB;CAIpB,IAAI,kBAAkB,cAAc,eAAe,WAAW;CAI9D,UACC,kBAAkB,eACjB,iBAAiB,OAAO,YAAY,MAAM,CAC3C,CACD;CAEA,MAAM,UAAU,SAAsB,YAA2B;EAChE,MAAM,YAAY,cAAc,SAAS,OAAO;EAEhD,UAAU,WAAW;GACpB,IAAI,QAAQ,GAAG;GACf,IAAI,MAAM,sBACT,QAAQ,eAAe;IACtB,UAAU,mBAAmB,IAAI,YAAY;IAC7C,OAAO;IACP,QAAQ;GACT,CAAC;EACH,CAAC;EAED,OAAO;CACR;CAEA,0BACO,MAAM,aACX,eAAe;EAOf,MAAM,cAAc,cAAc,eAAe,WAAW;EAC5D,MAAM,cAAc,gBAAgB;EACpC,kBAAkB;EAKlB,IAAI,iBAAiB,aAAa;GACjC,gBAAgB;GAChB,IAAI,YAAY,YAAY,OAAO;QAC9B,YAAY,OAAO;GACxB;EACD;EAIA,IAAI,QAAQ;EACZ,MAAM,gBAAgB;EAEtB,IAAI,CAAC,YAAY;GAChB,MAAM,YAAY,gBAAgB,SAAS,OAAO;GAClD,iBAAiB,qBAAqB,kBAAkB,SAAS,CAAC;GAGlE,aAAa;IACZ,QAAQ;IACR,UAAU,SAAS;GACpB;EACD;EAEA,MAAM,YAAY,OAAO,SAAS,OAAO;EACzC,iBAAiB,qBAAqB,kBAAkB,SAAS,CAAC;EAClE,aAAa;GACZ,QAAQ;GACR,UAAU,SAAS;EACpB;CACD,CACD;AACD"}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { provideAccordionContext, useAccordionContext } from "../hooks/useAccordion.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { toHyphenated } from "@brytdesigns/web-component-core/utils";
|
|
4
|
+
import { untrack } from "solid-js";
|
|
5
|
+
//#region src/components/accordion-context.ts
|
|
6
|
+
var Name = `accordion-context`;
|
|
7
|
+
var Component = (props, { element }) => {
|
|
8
|
+
const context = provideAccordionContext(props, element);
|
|
9
|
+
const [_, methods] = useAccordionContext(context);
|
|
10
|
+
const accordionItems = Array.from(element.children).filter((child) => child.tagName === "ACCORDION-ITEM");
|
|
11
|
+
const allowMultiple = untrack(() => props.allowMultiple);
|
|
12
|
+
const startIndex = untrack(() => props.activeIndex);
|
|
13
|
+
accordionItems.forEach((accordionItem, index) => {
|
|
14
|
+
accordionItem.setAttribute("index", `${index + 1}`);
|
|
15
|
+
if (index + 1 === startIndex) accordionItem.setAttribute(toHyphenated("isExpanded"), "true");
|
|
16
|
+
});
|
|
17
|
+
queueMicrotask(() => {
|
|
18
|
+
if (allowMultiple) {
|
|
19
|
+
methods.item.update(-1);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const expanded = accordionItems.findIndex((item) => item.getAttribute(toHyphenated("isExpanded")) === "true");
|
|
23
|
+
if (expanded === -1) return;
|
|
24
|
+
methods.item.update(expanded + 1);
|
|
25
|
+
});
|
|
18
26
|
};
|
|
19
|
-
|
|
27
|
+
//#endregion
|
|
20
28
|
export { Component, Name };
|
|
21
|
-
|
|
29
|
+
|
|
22
30
|
//# sourceMappingURL=accordion-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"accordion-context.js","names":[],"sources":["../../../src/components/accordion-context.ts"],"sourcesContent":["import {\n\ttype CorrectComponentType,\n\ttoHyphenated,\n} from \"@brytdesigns/web-component-core/utils\";\nimport { untrack } from \"solid-js\";\n\nimport {\n\tprovideAccordionContext,\n\tuseAccordionContext,\n} from \"../hooks/index.js\";\n\nexport type Props = {\n\tactiveIndex: number;\n\tisAnimating: boolean;\n\tallowMultiple: boolean;\n};\n\nexport const Name = `accordion-context`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tconst context = provideAccordionContext(props, element);\n\tconst [_, methods] = useAccordionContext(context);\n\tconst children = Array.from(element.children) as HTMLElement[];\n\tconst accordionItems = children.filter(\n\t\t(child) => child.tagName === \"ACCORDION-ITEM\",\n\t);\n\t// Both read once: changing allow-multiple after startup has no effect, and\n\t// active-index only picks the item that starts open.\n\tconst allowMultiple = untrack(() => props.allowMultiple);\n\tconst startIndex = untrack(() => props.activeIndex);\n\n\taccordionItems.forEach((accordionItem, index) => {\n\t\taccordionItem.setAttribute(\"index\", `${index + 1}`);\n\n\t\t// `active-index` is turned into the item's own `is-expanded` here, before\n\t\t// the items initialise, rather than left for the item to follow once its\n\t\t// effects run. By then its content has already applied a closed starting\n\t\t// state, so following it would animate the content open on load. Writing\n\t\t// a child's attribute from a component body is fine; writing *our own*\n\t\t// prop is what Solid 2 forbids, which is why the rest is deferred below.\n\t\tif (index + 1 === startIndex)\n\t\t\taccordionItem.setAttribute(toHyphenated(\"isExpanded\"), \"true\");\n\t});\n\n\t// Seeding `activeIndex` from markup is deferred by a microtask. Writing it\n\t// here would be a write inside a component body: `setElementState` calls\n\t// `setAttribute`, component-register turns that straight back into a signal\n\t// write, and Solid 2 forbids that from an owned scope — with no opt-out,\n\t// since the signal belongs to @solidjs/element's prop bridge rather than to\n\t// us. A microtask is the smallest move out of the owner; initialisation\n\t// lands one tick later than it used to.\n\tqueueMicrotask(() => {\n\t\t// With several items open, no single index describes the state, so\n\t\t// `active-index` is reset and then ignored for the rest of the session.\n\t\tif (allowMultiple) {\n\t\t\tmethods.item.update(-1);\n\t\t\treturn;\n\t\t}\n\n\t\tconst expanded = accordionItems.findIndex(\n\t\t\t(item) => item.getAttribute(toHyphenated(\"isExpanded\")) === \"true\",\n\t\t);\n\t\tif (expanded === -1) return;\n\t\tmethods.item.update(expanded + 1);\n\t});\n};\n"],"mappings":";;;;;AAiBA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,MAAM,UAAU,wBAAwB,OAAO,OAAO;CACtD,MAAM,CAAC,GAAG,WAAW,oBAAoB,OAAO;CAEhD,MAAM,iBADW,MAAM,KAAK,QAAQ,QACb,CAAA,CAAS,QAC9B,UAAU,MAAM,YAAY,gBAC9B;CAGA,MAAM,gBAAgB,cAAc,MAAM,aAAa;CACvD,MAAM,aAAa,cAAc,MAAM,WAAW;CAElD,eAAe,SAAS,eAAe,UAAU;EAChD,cAAc,aAAa,SAAS,GAAG,QAAQ,GAAG;EAQlD,IAAI,QAAQ,MAAM,YACjB,cAAc,aAAa,aAAa,YAAY,GAAG,MAAM;CAC/D,CAAC;CASD,qBAAqB;EAGpB,IAAI,eAAe;GAClB,QAAQ,KAAK,OAAO,EAAE;GACtB;EACD;EAEA,MAAM,WAAW,eAAe,WAC9B,SAAS,KAAK,aAAa,aAAa,YAAY,CAAC,MAAM,MAC7D;EACA,IAAI,aAAa,IAAI;EACrB,QAAQ,KAAK,OAAO,WAAW,CAAC;CACjC,CAAC;AACF"}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`${Name}: must be an immediate child of accordion-context`,
|
|
9
|
-
element
|
|
10
|
-
);
|
|
11
|
-
provideAccordionItemContext(props, element);
|
|
1
|
+
import { provideAccordionItemContext } from "../hooks/useAccordionItem.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
//#region src/components/accordion-item.ts
|
|
4
|
+
var Name = `accordion-item`;
|
|
5
|
+
var Component = (props, { element }) => {
|
|
6
|
+
if (element.parentElement?.tagName !== "ACCORDION-CONTEXT") return console.warn(`${Name}: must be an immediate child of accordion-context`, element);
|
|
7
|
+
provideAccordionItemContext(props, element);
|
|
12
8
|
};
|
|
13
|
-
|
|
9
|
+
//#endregion
|
|
14
10
|
export { Component, Name };
|
|
15
|
-
|
|
11
|
+
|
|
16
12
|
//# sourceMappingURL=accordion-item.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"accordion-item.js","names":[],"sources":["../../../src/components/accordion-item.ts"],"sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\n\nimport { provideAccordionItemContext } from \"../hooks/index.js\";\n\nexport type Props = {\n\tindex: number;\n\tisExpanded: boolean;\n};\n\nexport const Name = `accordion-item`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tif (element.parentElement?.tagName !== \"ACCORDION-CONTEXT\")\n\t\treturn console.warn(\n\t\t\t`${Name}: must be an immediate child of accordion-context`,\n\t\t\telement,\n\t\t);\n\n\tprovideAccordionItemContext(props, element);\n};\n"],"mappings":";;;AASA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,IAAI,QAAQ,eAAe,YAAY,qBACtC,OAAO,QAAQ,KACd,GAAG,KAAK,oDACR,OACD;CAED,4BAA4B,OAAO,OAAO;AAC3C"}
|
|
@@ -1,28 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
import { useAccordionItem } from "../hooks/useAccordionItem.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { createGuardedEffect, findDisclosureContent, findDisclosureControl, linkDisclosureControl } from "@brytdesigns/web-component-core/utils";
|
|
4
|
+
//#region src/components/accordion-trigger.ts
|
|
5
|
+
var Name = `accordion-trigger`;
|
|
6
|
+
var Component = (props, { element }) => {
|
|
7
|
+
const [state, methods] = useAccordionItem(element);
|
|
8
|
+
const control = findDisclosureControl(element);
|
|
9
|
+
if (!control) console.warn(`${Name}: put a <button> inside the trigger so assistive tech can operate it`, element);
|
|
10
|
+
else {
|
|
11
|
+
let isLinked = false;
|
|
12
|
+
createGuardedEffect(() => state.isExpanded, (isExpanded) => {
|
|
13
|
+
control.setAttribute("aria-expanded", isExpanded ? "true" : "false");
|
|
14
|
+
if (!isLinked) isLinked = linkDisclosureControl(control, findDisclosureContent(element, "accordion-item", "accordion-content"), "accordion-content");
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
createGuardedEffect(() => ({
|
|
18
|
+
isAnimating: state.isAnimating,
|
|
19
|
+
preventDefault: props.preventDefault
|
|
20
|
+
}), ({ isAnimating, preventDefault }) => {
|
|
21
|
+
if (isAnimating) return;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
element.addEventListener("click", (event) => {
|
|
24
|
+
if (preventDefault) event.preventDefault();
|
|
25
|
+
methods.toggle();
|
|
26
|
+
}, { signal: controller.signal });
|
|
27
|
+
return () => {
|
|
28
|
+
controller.abort();
|
|
29
|
+
};
|
|
30
|
+
});
|
|
24
31
|
};
|
|
25
|
-
|
|
32
|
+
//#endregion
|
|
26
33
|
export { Component, Name };
|
|
27
|
-
|
|
34
|
+
|
|
28
35
|
//# sourceMappingURL=accordion-trigger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"accordion-trigger.js","names":[],"sources":["../../../src/components/accordion-trigger.ts"],"sourcesContent":["import {\n\ttype CorrectComponentType,\n\tcreateGuardedEffect,\n\tfindDisclosureContent,\n\tfindDisclosureControl,\n\tlinkDisclosureControl,\n} from \"@brytdesigns/web-component-core/utils\";\n\nimport { useAccordionItem } from \"../hooks/index.js\";\n\nexport type Props = {\n\tpreventDefault: boolean;\n};\n\nexport const Name = `accordion-trigger`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tconst [state, methods] = useAccordionItem(element);\n\n\t// `aria-expanded` belongs on the element with the button role, not on\n\t// `accordion-trigger`, which has no role at all — assistive tech ignored it\n\t// there.\n\tconst control = findDisclosureControl(element);\n\tif (!control) {\n\t\tconsole.warn(\n\t\t\t`${Name}: put a <button> inside the trigger so assistive tech can operate it`,\n\t\t\telement,\n\t\t);\n\t} else {\n\t\tlet isLinked = false;\n\t\tcreateGuardedEffect(\n\t\t\t() => state.isExpanded,\n\t\t\t(isExpanded) => {\n\t\t\t\tcontrol.setAttribute(\"aria-expanded\", isExpanded ? \"true\" : \"false\");\n\t\t\t\t// The content may not be parsed yet when the trigger first renders.\n\t\t\t\tif (!isLinked)\n\t\t\t\t\tisLinked = linkDisclosureControl(\n\t\t\t\t\t\tcontrol,\n\t\t\t\t\t\tfindDisclosureContent(\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\t\"accordion-item\",\n\t\t\t\t\t\t\t\"accordion-content\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"accordion-content\",\n\t\t\t\t\t);\n\t\t\t},\n\t\t);\n\t}\n\n\tcreateGuardedEffect(\n\t\t// `preventDefault` belongs in the compute: read in apply it is untracked,\n\t\t// so changing the attribute would not rewire the listener.\n\t\t() => ({\n\t\t\tisAnimating: state.isAnimating,\n\t\t\tpreventDefault: props.preventDefault,\n\t\t}),\n\t\t({ isAnimating, preventDefault }) => {\n\t\t\tif (isAnimating) return;\n\n\t\t\tconst controller = new AbortController();\n\n\t\t\telement.addEventListener(\n\t\t\t\t\"click\",\n\t\t\t\t(event) => {\n\t\t\t\t\tif (preventDefault) event.preventDefault();\n\t\t\t\t\tmethods.toggle();\n\t\t\t\t},\n\t\t\t\t{ signal: controller.signal },\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"],"mappings":";;;;AAcA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,MAAM,CAAC,OAAO,WAAW,iBAAiB,OAAO;CAKjD,MAAM,UAAU,sBAAsB,OAAO;CAC7C,IAAI,CAAC,SACJ,QAAQ,KACP,GAAG,KAAK,uEACR,OACD;MACM;EACN,IAAI,WAAW;EACf,0BACO,MAAM,aACX,eAAe;GACf,QAAQ,aAAa,iBAAiB,aAAa,SAAS,OAAO;GAEnE,IAAI,CAAC,UACJ,WAAW,sBACV,SACA,sBACC,SACA,kBACA,mBACD,GACA,mBACD;EACF,CACD;CACD;CAEA,2BAGQ;EACN,aAAa,MAAM;EACnB,gBAAgB,MAAM;CACvB,KACC,EAAE,aAAa,qBAAqB;EACpC,IAAI,aAAa;EAEjB,MAAM,aAAa,IAAI,gBAAgB;EAEvC,QAAQ,iBACP,UACC,UAAU;GACV,IAAI,gBAAgB,MAAM,eAAe;GACzC,QAAQ,OAAO;EAChB,GACA,EAAE,QAAQ,WAAW,OAAO,CAC7B;EAEA,aAAa;GACZ,WAAW,MAAM;EAClB;CACD,CACD;AACD"}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import * as accordionTrigger_js from './accordion-trigger.js';
|
|
6
|
-
export { accordionTrigger_js as AccordionTrigger };
|
|
7
|
-
import * as accordionContext_js from './accordion-context.js';
|
|
8
|
-
export { accordionContext_js as AccordionContext };
|
|
9
|
-
//# sourceMappingURL=index.js.map
|
|
10
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import "./accordion-content.js";
|
|
2
|
+
import "./accordion-context.js";
|
|
3
|
+
import "./accordion-item.js";
|
|
4
|
+
import "./accordion-trigger.js";
|
package/dist/main/hooks/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import { getAccordionContext, provideAccordionContext, useAccordion, useAccordionAt, useAccordionContext } from "./useAccordion.js";
|
|
2
|
+
import { getAccordionItemContext, provideAccordionItemContext, useAccordionItem, useAccordionItemAt, useAccordionItemContext } from "./useAccordionItem.js";
|
|
3
|
+
export { getAccordionContext, getAccordionItemContext, provideAccordionContext, provideAccordionItemContext, useAccordion, useAccordionAt, useAccordionContext, useAccordionItem, useAccordionItemAt, useAccordionItemContext };
|
|
@@ -1,85 +1,98 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { createGuardedEffect, createRemoteContext, getContextFromProvider, toHyphenated } from "@brytdesigns/web-component-core/utils";
|
|
2
|
+
import { createSignal, createStore, merge, omit, untrack } from "solid-js";
|
|
3
|
+
import { consume, createContext, provide } from "component-register";
|
|
4
|
+
//#region src/hooks/useAccordion.ts
|
|
6
5
|
function initializeAccordionContext(props) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
6
|
+
const stateProps = omit(props, "root");
|
|
7
|
+
const [store, setStore] = createStore({ revealCount: 0 });
|
|
8
|
+
/**
|
|
9
|
+
* The animation queue is a SIGNAL, not part of the store above.
|
|
10
|
+
*
|
|
11
|
+
* A store deep-proxies what you put in it, and what goes in here is a list
|
|
12
|
+
* of live promises. Proxied, every `.then` on them is a reactive read from
|
|
13
|
+
* a non-tracking scope — Solid reports it (STRICT_READ_UNTRACKED), and it
|
|
14
|
+
* is a proxy hop on every access of an object that is not state at all.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here needs per-property reactivity: the queue is replaced
|
|
17
|
+
* wholesale and read wholesale. `revealCount` stays in the store, because
|
|
18
|
+
* that genuinely is state.
|
|
19
|
+
*/
|
|
20
|
+
const [animationQueue, setAnimationQueue] = createSignal([]);
|
|
21
|
+
createGuardedEffect(animationQueue, (queue) => {
|
|
22
|
+
if (!queue.length) return;
|
|
23
|
+
const animations = Promise.allSettled(queue);
|
|
24
|
+
setElementState("isAnimating", true);
|
|
25
|
+
animations.finally(() => {
|
|
26
|
+
setElementState("isAnimating", false);
|
|
27
|
+
setAnimationQueue([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
function setElementState(key, value) {
|
|
31
|
+
const currentValue = untrack(() => props[key]);
|
|
32
|
+
if (typeof value === "function") {
|
|
33
|
+
let result = false;
|
|
34
|
+
if (typeof currentValue === "number") result = value(currentValue || 0);
|
|
35
|
+
else if (typeof currentValue === "boolean") result = value(currentValue || false);
|
|
36
|
+
if (`${currentValue}` === `${result}`) return;
|
|
37
|
+
props.root.setAttribute(toHyphenated(key), `${result}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (`${currentValue}` === `${value}`) return;
|
|
41
|
+
return props.root.setAttribute(toHyphenated(key), `${value}`);
|
|
42
|
+
}
|
|
43
|
+
return [merge(store, stateProps, { get animationQueue() {
|
|
44
|
+
return animationQueue();
|
|
45
|
+
} }), {
|
|
46
|
+
setElementState,
|
|
47
|
+
setStore,
|
|
48
|
+
setAnimationQueue
|
|
49
|
+
}];
|
|
45
50
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
var AccordionContextState = createContext(initializeAccordionContext);
|
|
52
|
+
var provideAccordionContext = (initialState, element) => {
|
|
53
|
+
const props = merge(initialState, { root: element });
|
|
54
|
+
return provide(AccordionContextState, props, element);
|
|
50
55
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
var useAccordionContext = (context) => {
|
|
57
|
+
const [state, { setElementState, setStore: setState, setAnimationQueue }] = context;
|
|
58
|
+
function updateAnimationQueue(animation) {
|
|
59
|
+
setAnimationQueue((queue) => [...queue, animation]);
|
|
60
|
+
}
|
|
61
|
+
function updateActiveIndex(index) {
|
|
62
|
+
setElementState("activeIndex", index);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Runs `open` with content snapping to its new state instead of animating.
|
|
66
|
+
* Used when find-in-page reveals closed content: the item it opens, and the
|
|
67
|
+
* item that closes in its place, settle before the browser scrolls to the
|
|
68
|
+
* match, so the match does not move out of view.
|
|
69
|
+
*/
|
|
70
|
+
function reveal(open) {
|
|
71
|
+
setState((draft) => {
|
|
72
|
+
draft.revealCount += 1;
|
|
73
|
+
});
|
|
74
|
+
open();
|
|
75
|
+
}
|
|
76
|
+
const isActive = (index) => state.activeIndex === index;
|
|
77
|
+
return [state, {
|
|
78
|
+
updateAnimationQueue,
|
|
79
|
+
reveal,
|
|
80
|
+
item: {
|
|
81
|
+
update: updateActiveIndex,
|
|
82
|
+
isActive
|
|
83
|
+
}
|
|
84
|
+
}];
|
|
64
85
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"AccordionContext not found! Please ensure to wrap your custom element with accordion-context element."
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return useAccordionContext(context);
|
|
86
|
+
var useAccordion = (element) => {
|
|
87
|
+
const context = consume(AccordionContextState, element);
|
|
88
|
+
if (!context) throw new Error("AccordionContext not found! Please ensure to wrap your custom element with accordion-context element.");
|
|
89
|
+
return useAccordionContext(context);
|
|
73
90
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
AccordionContextState,
|
|
78
|
-
element
|
|
79
|
-
);
|
|
80
|
-
return useAccordionContext(context);
|
|
91
|
+
var useAccordionAt = createRemoteContext(AccordionContextState);
|
|
92
|
+
var getAccordionContext = (element) => {
|
|
93
|
+
return useAccordionContext(getContextFromProvider(AccordionContextState, element));
|
|
81
94
|
};
|
|
95
|
+
//#endregion
|
|
96
|
+
export { getAccordionContext, provideAccordionContext, useAccordion, useAccordionAt, useAccordionContext };
|
|
82
97
|
|
|
83
|
-
export { getAccordionContext, provideAccordionContext, useAccordion, useAccordionContext, withAccordionElementContext };
|
|
84
|
-
//# sourceMappingURL=useAccordion.js.map
|
|
85
98
|
//# sourceMappingURL=useAccordion.js.map
|