@hjmds/react 1.10.0 → 1.12.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/README.md +14 -0
- package/THIRD_PARTY_NOTICES.md +78 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +1 -1
- package/dist/actions.js.map +1 -1
- package/dist/activity-heatmap.d.ts +10 -0
- package/dist/activity-heatmap.d.ts.map +1 -0
- package/dist/activity-heatmap.js +6 -0
- package/dist/activity-heatmap.js.map +1 -0
- package/dist/advanced-display.d.ts +3 -0
- package/dist/advanced-display.d.ts.map +1 -1
- package/dist/advanced-display.js +9 -7
- package/dist/advanced-display.js.map +1 -1
- package/dist/advanced-forms.js +1 -1
- package/dist/advanced-forms.js.map +1 -1
- package/dist/auth-screen.d.ts +8 -0
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +5 -2
- package/dist/auth-screen.js.map +1 -1
- package/dist/avatar-blobatar-motion.d.ts +6 -0
- package/dist/avatar-blobatar-motion.d.ts.map +1 -0
- package/dist/avatar-blobatar-motion.js +22 -0
- package/dist/avatar-blobatar-motion.js.map +1 -0
- package/dist/avatar-blobatar.d.ts +6 -0
- package/dist/avatar-blobatar.d.ts.map +1 -0
- package/dist/avatar-blobatar.js +10 -0
- package/dist/avatar-blobatar.js.map +1 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +3 -3
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/carousel-motion.d.ts +4 -1
- package/dist/carousel-motion.d.ts.map +1 -1
- package/dist/carousel-motion.js +12 -3
- package/dist/carousel-motion.js.map +1 -1
- package/dist/code-block.d.ts +8 -0
- package/dist/code-block.d.ts.map +1 -0
- package/dist/code-block.js +6 -0
- package/dist/code-block.js.map +1 -0
- package/dist/content-transition.d.ts +5 -2
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +6 -4
- package/dist/content-transition.js.map +1 -1
- package/dist/context-menu.d.ts.map +1 -1
- package/dist/context-menu.js +10 -11
- package/dist/context-menu.js.map +1 -1
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/duration-field.d.ts +11 -0
- package/dist/duration-field.d.ts.map +1 -0
- package/dist/duration-field.js +9 -0
- package/dist/duration-field.js.map +1 -0
- package/dist/effect-surface.d.ts +10 -0
- package/dist/effect-surface.d.ts.map +1 -0
- package/dist/effect-surface.js +57 -0
- package/dist/effect-surface.js.map +1 -0
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/feedback.js +1 -1
- package/dist/feedback.js.map +1 -1
- package/dist/folder-preview.d.ts +12 -0
- package/dist/folder-preview.d.ts.map +1 -0
- package/dist/folder-preview.js +7 -0
- package/dist/folder-preview.js.map +1 -0
- package/dist/forms.d.ts +3 -1
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +5 -3
- package/dist/forms.js.map +1 -1
- package/dist/gravity-letters.d.ts +8 -0
- package/dist/gravity-letters.d.ts.map +1 -0
- package/dist/gravity-letters.js +24 -0
- package/dist/gravity-letters.js.map +1 -0
- package/dist/grid-reveal.d.ts +9 -0
- package/dist/grid-reveal.d.ts.map +1 -0
- package/dist/grid-reveal.js +21 -0
- package/dist/grid-reveal.js.map +1 -0
- package/dist/icon-lucide.d.ts +11 -0
- package/dist/icon-lucide.d.ts.map +1 -0
- package/dist/icon-lucide.js +11 -0
- package/dist/icon-lucide.js.map +1 -0
- package/dist/inline-confirm.d.ts +14 -0
- package/dist/inline-confirm.d.ts.map +1 -0
- package/dist/inline-confirm.js +37 -0
- package/dist/inline-confirm.js.map +1 -0
- package/dist/menu-morph.d.ts.map +1 -1
- package/dist/menu-morph.js +9 -6
- package/dist/menu-morph.js.map +1 -1
- package/dist/menu-typeahead.d.ts +13 -0
- package/dist/menu-typeahead.d.ts.map +1 -0
- package/dist/menu-typeahead.js +17 -0
- package/dist/menu-typeahead.js.map +1 -0
- package/dist/navigation-bar.d.ts +10 -0
- package/dist/navigation-bar.d.ts.map +1 -0
- package/dist/navigation-bar.js +10 -0
- package/dist/navigation-bar.js.map +1 -0
- package/dist/navigation.d.ts +3 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +63 -22
- package/dist/navigation.js.map +1 -1
- package/dist/notification-bell.d.ts +11 -0
- package/dist/notification-bell.d.ts.map +1 -0
- package/dist/notification-bell.js +27 -0
- package/dist/notification-bell.js.map +1 -0
- package/dist/overlays.d.ts +0 -2
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +5 -18
- package/dist/overlays.js.map +1 -1
- package/dist/provider-button.d.ts.map +1 -1
- package/dist/provider-button.js +2 -2
- package/dist/provider-button.js.map +1 -1
- package/dist/reaction-picker.d.ts +11 -0
- package/dist/reaction-picker.d.ts.map +1 -0
- package/dist/reaction-picker.js +9 -0
- package/dist/reaction-picker.js.map +1 -0
- package/dist/scroll-progress.d.ts +10 -0
- package/dist/scroll-progress.d.ts.map +1 -0
- package/dist/scroll-progress.js +34 -0
- package/dist/scroll-progress.js.map +1 -0
- package/dist/select.d.ts +0 -1
- package/dist/select.d.ts.map +1 -1
- package/dist/select.js +2 -4
- package/dist/select.js.map +1 -1
- package/dist/sidebar.d.ts +3 -0
- package/dist/sidebar.d.ts.map +1 -1
- package/dist/sidebar.js +23 -4
- package/dist/sidebar.js.map +1 -1
- package/dist/step-player.d.ts +24 -0
- package/dist/step-player.d.ts.map +1 -0
- package/dist/step-player.js +16 -0
- package/dist/step-player.js.map +1 -0
- package/dist/styles.css +94 -1
- package/dist/styles.layered.css +94 -1
- package/dist/supplemental-display.d.ts +10 -2
- package/dist/supplemental-display.d.ts.map +1 -1
- package/dist/supplemental-display.js +2 -2
- package/dist/supplemental-display.js.map +1 -1
- package/dist/supplemental-navigation.js +1 -1
- package/dist/supplemental-navigation.js.map +1 -1
- package/dist/table-sort-button.d.ts +15 -0
- package/dist/table-sort-button.d.ts.map +1 -0
- package/dist/table-sort-button.js +7 -0
- package/dist/table-sort-button.js.map +1 -0
- package/dist/task-list.d.ts +16 -0
- package/dist/task-list.d.ts.map +1 -0
- package/dist/task-list.js +17 -0
- package/dist/task-list.js.map +1 -0
- package/dist/thinking-orb.d.ts +1 -1
- package/dist/thinking-orb.d.ts.map +1 -1
- package/dist/thinking-orb.js +4 -4
- package/dist/thinking-orb.js.map +1 -1
- package/dist/voice-note.d.ts +24 -0
- package/dist/voice-note.d.ts.map +1 -0
- package/dist/voice-note.js +13 -0
- package/dist/voice-note.js.map +1 -0
- package/package.json +110 -5
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from "react";
|
|
3
|
+
import { gravityLetterMotion, resolveGravityLetters } from "@hjmds/design-contracts/gravity-letters";
|
|
4
|
+
import { useHjmTheme } from "./provider.js";
|
|
5
|
+
/** Decorative only: provide the meaningful heading outside this hidden presentation. */
|
|
6
|
+
export function GravityLetters({ glyphs, active = false, replayKey = 0 }) {
|
|
7
|
+
const { environment } = useHjmTheme();
|
|
8
|
+
const root = useRef(null);
|
|
9
|
+
const signature = JSON.stringify(glyphs);
|
|
10
|
+
const units = resolveGravityLetters(glyphs);
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
if (!active || environment.reducedMotion || document.hidden)
|
|
13
|
+
return;
|
|
14
|
+
const recipe = resolveGravityLetters(JSON.parse(signature));
|
|
15
|
+
const animations = [...root.current.children].map((node, index) => node.animate(gravityLetterMotion.input.map((offset, step) => ({ offset, transform: `translateY(${gravityLetterMotion.y[step]}px) rotate(${step === 0 ? recipe[index].rotation : 0}deg)` })), { duration: gravityLetterMotion.duration, delay: recipe[index].delay, fill: "backwards", easing: "ease-out" }));
|
|
16
|
+
const stop = () => animations.forEach(animation => animation.cancel());
|
|
17
|
+
const visibility = () => { if (document.hidden)
|
|
18
|
+
stop(); };
|
|
19
|
+
document.addEventListener("visibilitychange", visibility);
|
|
20
|
+
return () => { stop(); document.removeEventListener("visibilitychange", visibility); };
|
|
21
|
+
}, [signature, active, replayKey, environment.reducedMotion]);
|
|
22
|
+
return _jsx("span", { ref: root, "aria-hidden": "true", style: { display: "flex", flexWrap: "wrap", pointerEvents: "none", paddingTop: 36 }, children: units.map((unit, index) => _jsx("span", { style: { display: "inline-block", whiteSpace: "pre" }, children: unit.glyph }, index)) });
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=gravity-letters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gravity-letters.js","sourceRoot":"","sources":["../src/gravity-letters.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,wFAAwF;AACxF,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,SAAS,GAAG,CAAC,EAAuB;IAC3F,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC5C,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,aAAa,IAAI,QAAQ,CAAC,MAAM;YAAE,OAAO;QACpE,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAa,CAAC,CAAC;QACxE,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,OAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAC9E,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAC/K,EAAE,QAAQ,EAAE,mBAAmB,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAE,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAC/G,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,GAAG,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM;YAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1D,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;QAC1D,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9D,OAAO,eAAM,GAAG,EAAE,IAAI,iBAAc,MAAM,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,YAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,eAAkB,KAAK,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,YAAG,IAAI,CAAC,KAAK,IAAzE,KAAK,CAA4E,CAAC,GAAQ,CAAC;AACpQ,CAAC","sourcesContent":["import { useLayoutEffect, useRef } from \"react\";\nimport { gravityLetterMotion, resolveGravityLetters } from \"@hjmds/design-contracts/gravity-letters\";\nimport { useHjmTheme } from \"./provider.js\";\nexport type GravityLettersProps = Readonly<{ glyphs: readonly string[]; active?: boolean; replayKey?: string | number }>;\n/** Decorative only: provide the meaningful heading outside this hidden presentation. */\nexport function GravityLetters({ glyphs, active = false, replayKey = 0 }: GravityLettersProps) {\n const { environment } = useHjmTheme();\n const root = useRef<HTMLSpanElement>(null);\n const signature = JSON.stringify(glyphs);\n const units = resolveGravityLetters(glyphs);\n useLayoutEffect(() => {\n if (!active || environment.reducedMotion || document.hidden) return;\n const recipe = resolveGravityLetters(JSON.parse(signature) as string[]);\n const animations = [...root.current!.children].map((node, index) => node.animate(\n gravityLetterMotion.input.map((offset, step) => ({ offset, transform: `translateY(${gravityLetterMotion.y[step]}px) rotate(${step === 0 ? recipe[index]!.rotation : 0}deg)` })),\n { duration: gravityLetterMotion.duration, delay: recipe[index]!.delay, fill: \"backwards\", easing: \"ease-out\" },\n ));\n const stop = () => animations.forEach(animation => animation.cancel());\n const visibility = () => { if (document.hidden) stop(); };\n document.addEventListener(\"visibilitychange\", visibility);\n return () => { stop(); document.removeEventListener(\"visibilitychange\", visibility); };\n }, [signature, active, replayKey, environment.reducedMotion]);\n return <span ref={root} aria-hidden=\"true\" style={{ display: \"flex\", flexWrap: \"wrap\", pointerEvents: \"none\", paddingTop: 36 }}>{units.map((unit, index) => <span key={index} style={{ display: \"inline-block\", whiteSpace: \"pre\" }}>{unit.glyph}</span>)}</span>;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type GridRevealProps = Readonly<{
|
|
3
|
+
ready: boolean;
|
|
4
|
+
active?: boolean;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}>;
|
|
7
|
+
/** Connect ready to Image.onLoadStatusChange; the image retains loading/error/accessibility. */
|
|
8
|
+
export declare function GridReveal({ ready, active, children }: GridRevealProps): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=grid-reveal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-reveal.d.ts","sourceRoot":"","sources":["../src/grid-reveal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhE,MAAM,MAAM,eAAe,GAAC,QAAQ,CAAC;IAAC,KAAK,EAAC,OAAO,CAAC;IAAA,MAAM,CAAC,EAAC,OAAO,CAAC;IAAA,QAAQ,EAAC,SAAS,CAAA;CAAC,CAAC,CAAC;AACzF,gGAAgG;AAChG,wBAAgB,UAAU,CAAC,EAAC,KAAK,EAAC,MAAW,EAAC,QAAQ,EAAC,EAAC,eAAe,+BAUtE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from "react";
|
|
3
|
+
import { gridRevealTiles, gridRevealDuration } from "@hjmds/design-contracts/grid-reveal";
|
|
4
|
+
import { useHjmTheme } from "./provider.js";
|
|
5
|
+
/** Connect ready to Image.onLoadStatusChange; the image retains loading/error/accessibility. */
|
|
6
|
+
export function GridReveal({ ready, active = true, children }) {
|
|
7
|
+
const { environment } = useHjmTheme();
|
|
8
|
+
const overlay = useRef(null);
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
if (!ready || !active || environment.reducedMotion || document.hidden)
|
|
11
|
+
return;
|
|
12
|
+
const animations = [...overlay.current.children].map((node, index) => node.animate([{ opacity: 1 }, { opacity: 0 }], { duration: gridRevealDuration, delay: gridRevealTiles[index].delay, fill: "backwards", easing: "ease-out" }));
|
|
13
|
+
const stop = () => animations.forEach(animation => animation.cancel());
|
|
14
|
+
const visibility = () => { if (document.hidden)
|
|
15
|
+
stop(); };
|
|
16
|
+
document.addEventListener("visibilitychange", visibility);
|
|
17
|
+
return () => { stop(); document.removeEventListener("visibilitychange", visibility); };
|
|
18
|
+
}, [ready, active, environment.reducedMotion]);
|
|
19
|
+
return _jsxs("div", { style: { position: "relative" }, children: [children, _jsx("div", { ref: overlay, "aria-hidden": "true", style: { position: "absolute", inset: 0, pointerEvents: "none", display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gridTemplateRows: "repeat(4, 1fr)" }, children: gridRevealTiles.map(tile => _jsx("span", { style: { opacity: 0, background: "var(--hjm-color-bg)" } }, tile.id)) })] });
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=grid-reveal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-reveal.js","sourceRoot":"","sources":["../src/grid-reveal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,gGAAgG;AAChG,MAAM,UAAU,UAAU,CAAC,EAAC,KAAK,EAAC,MAAM,GAAC,IAAI,EAAC,QAAQ,EAAiB;IACtE,MAAK,EAAC,WAAW,EAAC,GAAC,WAAW,EAAE,CAAC;IAAA,MAAM,OAAO,GAAC,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC5E,eAAe,CAAC,GAAE,EAAE;QACnB,IAAG,CAAC,KAAK,IAAE,CAAC,MAAM,IAAE,WAAW,CAAC,aAAa,IAAE,QAAQ,CAAC,MAAM;YAAC,OAAO;QACtE,MAAM,UAAU,GAAC,CAAC,GAAG,OAAO,CAAC,OAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAC,KAAK,EAAC,EAAE,CAAA,IAAI,CAAC,OAAO,CAAC,CAAC,EAAC,OAAO,EAAC,CAAC,EAAC,EAAC,EAAC,OAAO,EAAC,CAAC,EAAC,CAAC,EAAC,EAAC,QAAQ,EAAC,kBAAkB,EAAC,KAAK,EAAC,eAAe,CAAC,KAAK,CAAE,CAAC,KAAK,EAAC,IAAI,EAAC,WAAW,EAAC,MAAM,EAAC,UAAU,EAAC,CAAC,CAAC,CAAC;QAChN,MAAM,IAAI,GAAC,GAAE,EAAE,CAAA,UAAU,CAAC,OAAO,CAAC,SAAS,CAAA,EAAE,CAAA,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,MAAM,UAAU,GAAC,GAAE,EAAE,GAAC,IAAG,QAAQ,CAAC,MAAM;YAAC,IAAI,EAAE,CAAC,CAAA,CAAC,CAAC;QAAA,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAC,UAAU,CAAC,CAAC;QAC3G,OAAM,GAAE,EAAE,GAAC,IAAI,EAAE,CAAC,CAAA,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAC,UAAU,CAAC,CAAC,CAAA,CAAC,CAAC;IACjF,CAAC,EAAC,CAAC,KAAK,EAAC,MAAM,EAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5C,OAAO,eAAK,KAAK,EAAE,EAAC,QAAQ,EAAC,UAAU,EAAC,aAAG,QAAQ,EAAC,cAAK,GAAG,EAAE,OAAO,iBAAc,MAAM,EAAC,KAAK,EAAE,EAAC,QAAQ,EAAC,UAAU,EAAC,KAAK,EAAC,CAAC,EAAC,aAAa,EAAC,MAAM,EAAC,OAAO,EAAC,MAAM,EAAC,mBAAmB,EAAC,gBAAgB,EAAC,gBAAgB,EAAC,gBAAgB,EAAC,YAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAA,EAAE,CAAA,eAAoB,KAAK,EAAE,EAAC,OAAO,EAAC,CAAC,EAAC,UAAU,EAAC,qBAAqB,EAAC,IAA5D,IAAI,CAAC,EAAE,CAAwD,CAAC,GAAO,IAAM,CAAC;AAChW,CAAC","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\";\nimport { gridRevealTiles, gridRevealDuration } from \"@hjmds/design-contracts/grid-reveal\";\nimport { useHjmTheme } from \"./provider.js\";\nexport type GridRevealProps=Readonly<{ready:boolean;active?:boolean;children:ReactNode}>;\n/** Connect ready to Image.onLoadStatusChange; the image retains loading/error/accessibility. */\nexport function GridReveal({ready,active=true,children}:GridRevealProps){\n const{environment}=useHjmTheme();const overlay=useRef<HTMLDivElement>(null);\n useLayoutEffect(()=>{\n if(!ready||!active||environment.reducedMotion||document.hidden)return;\n const animations=[...overlay.current!.children].map((node,index)=>node.animate([{opacity:1},{opacity:0}],{duration:gridRevealDuration,delay:gridRevealTiles[index]!.delay,fill:\"backwards\",easing:\"ease-out\"}));\n const stop=()=>animations.forEach(animation=>animation.cancel());\n const visibility=()=>{if(document.hidden)stop();};document.addEventListener(\"visibilitychange\",visibility);\n return()=>{stop();document.removeEventListener(\"visibilitychange\",visibility);};\n },[ready,active,environment.reducedMotion]);\n return <div style={{position:\"relative\"}}>{children}<div ref={overlay} aria-hidden=\"true\" style={{position:\"absolute\",inset:0,pointerEvents:\"none\",display:\"grid\",gridTemplateColumns:\"repeat(4, 1fr)\",gridTemplateRows:\"repeat(4, 1fr)\"}}>{gridRevealTiles.map(tile=><span key={tile.id} style={{opacity:0,background:\"var(--hjm-color-bg)\"}}/>)}</div></div>;\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LucideIcon } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export type LucideGlyphAppearance = Readonly<{
|
|
4
|
+
name: string;
|
|
5
|
+
size: number;
|
|
6
|
+
color: string;
|
|
7
|
+
strokeWidth: number;
|
|
8
|
+
}>;
|
|
9
|
+
/** Pass named Lucide imports only; no dynamic icon dictionary enters the graph. */
|
|
10
|
+
export declare function createLucideGlyph(icons: Readonly<Record<string, LucideIcon>>): (props: LucideGlyphAppearance) => ReactNode;
|
|
11
|
+
//# sourceMappingURL=icon-lucide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon-lucide.d.ts","sourceRoot":"","sources":["../src/icon-lucide.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACjH,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,SAAS,CAM1H"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/** Pass named Lucide imports only; no dynamic icon dictionary enters the graph. */
|
|
3
|
+
export function createLucideGlyph(icons) {
|
|
4
|
+
return ({ name, size, color, strokeWidth }) => {
|
|
5
|
+
const Glyph = Object.hasOwn(icons, name) ? icons[name] : undefined;
|
|
6
|
+
if (!Glyph)
|
|
7
|
+
throw new TypeError(`No Lucide glyph registered for ${name}`);
|
|
8
|
+
return _jsx(Glyph, { size: size, color: color, strokeWidth: strokeWidth, "aria-hidden": "true", focusable: "false" });
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=icon-lucide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon-lucide.js","sourceRoot":"","sources":["../src/icon-lucide.tsx"],"names":[],"mappings":";AAGA,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,KAA2C;IAC3E,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;QAC1E,OAAO,KAAC,KAAK,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,iBAAc,MAAM,EAAC,SAAS,EAAC,OAAO,GAAG,CAAC;IAC5G,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\nexport type LucideGlyphAppearance = Readonly<{ name: string; size: number; color: string; strokeWidth: number }>;\n/** Pass named Lucide imports only; no dynamic icon dictionary enters the graph. */\nexport function createLucideGlyph(icons: Readonly<Record<string, LucideIcon>>): (props: LucideGlyphAppearance) => ReactNode {\n return ({ name, size, color, strokeWidth }) => {\n const Glyph = Object.hasOwn(icons, name) ? icons[name] : undefined;\n if (!Glyph) throw new TypeError(`No Lucide glyph registered for ${name}`);\n return <Glyph size={size} color={color} strokeWidth={strokeWidth} aria-hidden=\"true\" focusable=\"false\" />;\n };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type InlineConfirmProps = Readonly<{
|
|
2
|
+
label: string;
|
|
3
|
+
prompt: string;
|
|
4
|
+
confirmLabel: string;
|
|
5
|
+
cancelLabel: string;
|
|
6
|
+
pendingLabel: string;
|
|
7
|
+
successLabel: string;
|
|
8
|
+
errorLabel: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
onConfirm: () => void | Promise<void>;
|
|
11
|
+
}>;
|
|
12
|
+
/** Low-complexity confirmation using the existing async dialog session semantics. */
|
|
13
|
+
export declare function InlineConfirm(props: InlineConfirmProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=inline-confirm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-confirm.d.ts","sourceRoot":"","sources":["../src/inline-confirm.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAAC;AAGnP,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BAwBtD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { createAlertDialogSession } from "@hjmds/design-contracts/components/alert-dialog";
|
|
4
|
+
import { Button } from "./actions.js";
|
|
5
|
+
const emptySubscribe = () => () => { };
|
|
6
|
+
const emptySnapshot = () => null;
|
|
7
|
+
/** Low-complexity confirmation using the existing async dialog session semantics. */
|
|
8
|
+
export function InlineConfirm(props) {
|
|
9
|
+
const [session, setSession] = useState(null);
|
|
10
|
+
const [done, setDone] = useState(false);
|
|
11
|
+
const phase = useSyncExternalStore(session?.subscribe ?? emptySubscribe, session?.getSnapshot ?? emptySnapshot, emptySnapshot);
|
|
12
|
+
const trigger = useRef(null);
|
|
13
|
+
const cancel = useRef(null);
|
|
14
|
+
useEffect(() => { if (session)
|
|
15
|
+
cancel.current?.focus(); }, [session]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (phase?.status !== "closing")
|
|
18
|
+
return;
|
|
19
|
+
const confirmed = phase.result.outcome === "confirmed";
|
|
20
|
+
session?.completeExit();
|
|
21
|
+
setDone(confirmed);
|
|
22
|
+
setSession(null);
|
|
23
|
+
// Cancel returns focus to the original action; success leaves a live result.
|
|
24
|
+
if (!confirmed)
|
|
25
|
+
queueMicrotask(() => trigger.current?.focus());
|
|
26
|
+
}, [phase, session]);
|
|
27
|
+
if (done)
|
|
28
|
+
return _jsx("span", { role: "status", children: props.successLabel });
|
|
29
|
+
if (!session)
|
|
30
|
+
return _jsx(Button, { ref: trigger, tone: "danger", disabled: props.disabled ?? false, onClick: () => setSession(createAlertDialogSession({ mode: "confirm", tone: "danger", title: props.label, description: props.prompt, confirmLabel: props.confirmLabel, cancelLabel: props.cancelLabel, onConfirm: props.onConfirm, fallbackErrorMessage: props.errorLabel })), children: props.label });
|
|
31
|
+
const busy = phase?.status === "busy";
|
|
32
|
+
return _jsxs("div", { role: "group", "aria-label": props.prompt, onKeyDown: event => { if (event.key === "Escape" && !busy) {
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
session.cancel("escape");
|
|
35
|
+
} }, style: { display: "grid", gap: "var(--hjm-space-sm)" }, children: [_jsx("span", { children: props.prompt }), phase?.status === "error" && _jsx("span", { role: "alert", children: phase.message }), _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "var(--hjm-space-sm)" }, children: [_jsx(Button, { ref: cancel, tone: "ghost", disabled: busy, onClick: () => session.cancel("cancel-action"), children: props.cancelLabel }), _jsx(Button, { tone: "danger", loading: busy, disabled: busy || (props.disabled ?? false), onClick: () => { void session.confirm(); }, children: busy ? props.pendingLabel : props.confirmLabel })] })] });
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=inline-confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-confirm.js","sourceRoot":"","sources":["../src/inline-confirm.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport { createAlertDialogSession, type AlertDialogSession } from \"@hjmds/design-contracts/components/alert-dialog\";\nimport { Button } from \"./actions.js\";\nexport type InlineConfirmProps = Readonly<{ label: string; prompt: string; confirmLabel: string; cancelLabel: string; pendingLabel: string; successLabel: string; errorLabel: string; disabled?: boolean; onConfirm: () => void | Promise<void> }>;\nconst emptySubscribe = () => () => {};\nconst emptySnapshot = () => null;\n/** Low-complexity confirmation using the existing async dialog session semantics. */\nexport function InlineConfirm(props: InlineConfirmProps) {\n const [session, setSession] = useState<AlertDialogSession | null>(null);\n const [done, setDone] = useState(false);\n const phase = useSyncExternalStore(session?.subscribe ?? emptySubscribe, session?.getSnapshot ?? emptySnapshot, emptySnapshot);\n const trigger = useRef<HTMLButtonElement>(null);\n const cancel = useRef<HTMLButtonElement>(null);\n useEffect(() => { if (session) cancel.current?.focus(); }, [session]);\n useEffect(() => {\n if (phase?.status !== \"closing\") return;\n const confirmed = phase.result.outcome === \"confirmed\";\n session?.completeExit();setDone(confirmed);setSession(null);\n // Cancel returns focus to the original action; success leaves a live result.\n if (!confirmed) queueMicrotask(() => trigger.current?.focus());\n }, [phase, session]);\n if (done) return <span role=\"status\">{props.successLabel}</span>;\n if (!session) return <Button ref={trigger} tone=\"danger\" disabled={props.disabled ?? false} onClick={() => setSession(createAlertDialogSession({ mode: \"confirm\", tone: \"danger\", title: props.label, description: props.prompt, confirmLabel: props.confirmLabel, cancelLabel: props.cancelLabel, onConfirm: props.onConfirm, fallbackErrorMessage: props.errorLabel }))}>{props.label}</Button>;\n const busy = phase?.status === \"busy\";\n return <div role=\"group\" aria-label={props.prompt} onKeyDown={event => { if (event.key === \"Escape\" && !busy) { event.stopPropagation();session.cancel(\"escape\"); } }} style={{ display: \"grid\", gap: \"var(--hjm-space-sm)\" }}>\n <span>{props.prompt}</span>{phase?.status === \"error\" && <span role=\"alert\">{phase.message}</span>}\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"var(--hjm-space-sm)\" }}>\n <Button ref={cancel} tone=\"ghost\" disabled={busy} onClick={() => session.cancel(\"cancel-action\")}>{props.cancelLabel}</Button>\n <Button tone=\"danger\" loading={busy} disabled={busy || (props.disabled ?? false)} onClick={() => { void session.confirm(); }}>{busy ? props.pendingLabel : props.confirmLabel}</Button>\n </div>\n </div>;\n}\n"]}
|
package/dist/menu-morph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-morph.d.ts","sourceRoot":"","sources":["../src/menu-morph.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-morph.d.ts","sourceRoot":"","sources":["../src/menu-morph.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGpD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAgB,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,iBAAiB,+BAmE3H"}
|
package/dist/menu-morph.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { resolveMenuTypeahead } from "./menu-typeahead.js";
|
|
2
3
|
import { Menu as Bloom } from "bloom-menu";
|
|
3
4
|
import { useEffect, useRef, useState } from "react";
|
|
4
5
|
import { Button } from "./actions.js";
|
|
@@ -12,7 +13,7 @@ export function MorphingMenu({ label, items, onAction, disabled = false, open: c
|
|
|
12
13
|
const root = useRef(null);
|
|
13
14
|
const previousOpen = useRef(false);
|
|
14
15
|
const restoreFocus = useRef(true);
|
|
15
|
-
const search = useRef({
|
|
16
|
+
const search = useRef({ value: "", time: 0 });
|
|
16
17
|
const change = (value) => { if (value)
|
|
17
18
|
restoreFocus.current = true; setLocalOpen(value); onOpenChange?.(value); };
|
|
18
19
|
if (!label.trim() || !items.length || new Set(items.map(item => item.id)).size !== items.length ||
|
|
@@ -22,6 +23,7 @@ export function MorphingMenu({ label, items, onAction, disabled = false, open: c
|
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
const host = root.current;
|
|
24
25
|
if (open) {
|
|
26
|
+
search.current = { value: "", time: 0 };
|
|
25
27
|
host?.querySelector('[role="menu"]')?.setAttribute("aria-label", label);
|
|
26
28
|
host?.querySelector('[role="menuitem"]:not([disabled])')?.focus();
|
|
27
29
|
}
|
|
@@ -60,10 +62,12 @@ export function MorphingMenu({ label, items, onAction, disabled = false, open: c
|
|
|
60
62
|
if (event.key === "Escape")
|
|
61
63
|
event.preventDefault();
|
|
62
64
|
}
|
|
63
|
-
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && event.key !== " ") {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey && event.key !== " ") {
|
|
66
|
+
const result = resolveMenuTypeahead(items.filter(item => !item.disabled).map(item => ({
|
|
67
|
+
textValue: item.textValue ?? (typeof item.label === "string" ? item.label : ""),
|
|
68
|
+
})), index, event.key, search.current, Date.now());
|
|
69
|
+
search.current = result.state;
|
|
70
|
+
next = result.index;
|
|
67
71
|
}
|
|
68
72
|
if (next !== undefined && next >= 0) {
|
|
69
73
|
event.preventDefault();
|
|
@@ -73,7 +77,6 @@ export function MorphingMenu({ label, items, onAction, disabled = false, open: c
|
|
|
73
77
|
};
|
|
74
78
|
return _jsx("div", { ref: root, className: "hjm-menu-morph", onKeyDownCapture: keyDown, children: _jsx(Bloom.Root, { direction: "bottom", open: open, onOpenChange: change, modal: false, children: _jsxs(Bloom.Container, { buttonSize: { width: 160, height: 44 }, menuWidth: 240, style: { background: theme.palette.theme.bg, color: theme.palette.theme.text }, children: [_jsx(Bloom.Trigger, { disabled: disabled, children: label }), _jsx(Bloom.Content, { children: items.map(item => _jsxs("button", { type: "button", role: "menuitem", tabIndex: -1, className: "hjm-menu-morph__item", disabled: !open || (item.disabled ?? false), "data-tone": item.tone, "aria-label": item.textValue, onClick: () => { if (!item.disabled) {
|
|
75
79
|
change(false);
|
|
76
|
-
item.onSelect?.();
|
|
77
80
|
onAction?.(item.id);
|
|
78
81
|
} }, children: [item.leading, item.label, item.trailing] }, item.id)) })] }) }) });
|
|
79
82
|
}
|
package/dist/menu-morph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-morph.js","sourceRoot":"","sources":["../src/menu-morph.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Menu as Bloom } from \"bloom-menu\";\nimport { useEffect, useRef, useState, type KeyboardEvent } from \"react\";\nimport { Button } from \"./actions.js\";\nimport { Menu, type MenuItem } from \"./overlays.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type MorphingMenuProps = {\n label: string;\n items: readonly MenuItem[];\n onAction?: (id: string) => void;\n disabled?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\n/** Action-only morph presentation. Selection/async menus retain the full Menu. */\nexport function MorphingMenu({ label, items, onAction, disabled = false, open: controlled, onOpenChange }: MorphingMenuProps) {\n const theme = useHjmTheme();\n const [localOpen, setLocalOpen] = useState(false);\n const open = controlled ?? localOpen;\n const root = useRef<HTMLDivElement>(null);\n const previousOpen = useRef(false);\n const restoreFocus = useRef(true);\n const search = useRef({ text: \"\", time: 0 });\n const change = (value: boolean) => { if (value) restoreFocus.current = true; setLocalOpen(value); onOpenChange?.(value); };\n if (!label.trim() || !items.length || new Set(items.map(item => item.id)).size !== items.length ||\n items.some(item => !item.id.trim() || !(item.textValue ?? (typeof item.label === \"string\" ? item.label : \"\")).trim())) {\n throw new TypeError(\"MorphingMenu needs a label and unique, named items\");\n }\n useEffect(() => {\n const host = root.current;\n if (open) {\n host?.querySelector('[role=\"menu\"]')?.setAttribute(\"aria-label\", label);\n host?.querySelector<HTMLElement>('[role=\"menuitem\"]:not([disabled])')?.focus();\n } else if (previousOpen.current && restoreFocus.current && (host?.contains(document.activeElement) || document.activeElement === document.body)) host?.querySelector<HTMLElement>('[role=\"button\"]')?.focus();\n previousOpen.current = open;\n }, [open, label]);\n // The canonical Menu owns static/RTL behavior; Bloom's left/right geometry is physical.\n if (theme.environment.reducedMotion || theme.environment.direction === \"rtl\") {\n return <Menu label={label} trigger={<Button tone=\"secondary\">{label}</Button>} items={items} disabled={disabled} open={open}\n onOpenChange={change} {...(onAction ? { onAction } : {})} />;\n }\n const keyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!open) {\n if (!disabled && [\"ArrowDown\", \"ArrowUp\"].includes(event.key)) { event.preventDefault(); event.stopPropagation(); change(true); }\n return;\n }\n const nodes = Array.from(root.current?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]:not([disabled])') ?? []);\n const index = nodes.indexOf(document.activeElement as HTMLElement);\n let next: number | undefined;\n if (event.key === \"ArrowDown\") next = (index + 1) % nodes.length;\n if (event.key === \"ArrowUp\") next = (index - 1 + nodes.length) % nodes.length;\n if (event.key === \"Home\") next = 0;\n if (event.key === \"End\") next = nodes.length - 1;\n if (event.key === \"Escape\" || event.key === \"Tab\") { if (event.key === \"Tab\") restoreFocus.current = false; change(false); if (event.key === \"Escape\") event.preventDefault(); }\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && event.key !== \" \") {\n const now = Date.now(); search.current = { text: (now - search.current.time < 600 ? search.current.text : \"\") + event.key.toLocaleLowerCase(), time: now };\n next = nodes.findIndex(node => node.textContent?.trim().toLocaleLowerCase().startsWith(search.current.text));\n }\n if (next !== undefined && next >= 0) { event.preventDefault(); event.stopPropagation(); nodes[next]?.focus(); }\n };\n return <div ref={root} className=\"hjm-menu-morph\" onKeyDownCapture={keyDown}>\n {/* Open toward following content; upstream defaults upward and can cover preceding values. */}\n <Bloom.Root direction=\"bottom\" open={open} onOpenChange={change} modal={false}>\n <Bloom.Container buttonSize={{ width: 160, height: 44 }} menuWidth={240}\n style={{ background: theme.palette.theme.bg, color: theme.palette.theme.text }}>\n <Bloom.Trigger disabled={disabled}>{label}</Bloom.Trigger>\n <Bloom.Content>\n {items.map(item => <button type=\"button\" role=\"menuitem\" tabIndex={-1} className=\"hjm-menu-morph__item\" key={item.id} disabled={!open || (item.disabled ?? false)}\n data-tone={item.tone} aria-label={item.textValue}\n onClick={() => { if (!item.disabled) { change(false); item.onSelect?.(); onAction?.(item.id); } }}>\n {item.leading}{item.label}{item.trailing}\n </button>)}\n </Bloom.Content>\n </Bloom.Container>\n </Bloom.Root>\n </div>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"menu-morph.js","sourceRoot":"","sources":["../src/menu-morph.tsx"],"names":[],"mappings":"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","sourcesContent":["import { resolveMenuTypeahead } from \"./menu-typeahead.js\";\nimport { Menu as Bloom } from \"bloom-menu\";\nimport { useEffect, useRef, useState, type KeyboardEvent } from \"react\";\nimport { Button } from \"./actions.js\";\nimport { Menu, type MenuItem } from \"./overlays.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type MorphingMenuProps = {\n label: string;\n items: readonly MenuItem[];\n onAction?: (id: string) => void;\n disabled?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\n/** Action-only morph presentation. Selection/async menus retain the full Menu. */\nexport function MorphingMenu({ label, items, onAction, disabled = false, open: controlled, onOpenChange }: MorphingMenuProps) {\n const theme = useHjmTheme();\n const [localOpen, setLocalOpen] = useState(false);\n const open = controlled ?? localOpen;\n const root = useRef<HTMLDivElement>(null);\n const previousOpen = useRef(false);\n const restoreFocus = useRef(true);\n const search = useRef({ value: \"\", time: 0 });\n const change = (value: boolean) => { if (value) restoreFocus.current = true; setLocalOpen(value); onOpenChange?.(value); };\n if (!label.trim() || !items.length || new Set(items.map(item => item.id)).size !== items.length ||\n items.some(item => !item.id.trim() || !(item.textValue ?? (typeof item.label === \"string\" ? item.label : \"\")).trim())) {\n throw new TypeError(\"MorphingMenu needs a label and unique, named items\");\n }\n useEffect(() => {\n const host = root.current;\n if (open) {\n search.current = { value: \"\", time: 0 };\n host?.querySelector('[role=\"menu\"]')?.setAttribute(\"aria-label\", label);\n host?.querySelector<HTMLElement>('[role=\"menuitem\"]:not([disabled])')?.focus();\n } else if (previousOpen.current && restoreFocus.current && (host?.contains(document.activeElement) || document.activeElement === document.body)) host?.querySelector<HTMLElement>('[role=\"button\"]')?.focus();\n previousOpen.current = open;\n }, [open, label]);\n // The canonical Menu owns static/RTL behavior; Bloom's left/right geometry is physical.\n if (theme.environment.reducedMotion || theme.environment.direction === \"rtl\") {\n return <Menu label={label} trigger={<Button tone=\"secondary\">{label}</Button>} items={items} disabled={disabled} open={open}\n onOpenChange={change} {...(onAction ? { onAction } : {})} />;\n }\n const keyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!open) {\n if (!disabled && [\"ArrowDown\", \"ArrowUp\"].includes(event.key)) { event.preventDefault(); event.stopPropagation(); change(true); }\n return;\n }\n const nodes = Array.from(root.current?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]:not([disabled])') ?? []);\n const index = nodes.indexOf(document.activeElement as HTMLElement);\n let next: number | undefined;\n if (event.key === \"ArrowDown\") next = (index + 1) % nodes.length;\n if (event.key === \"ArrowUp\") next = (index - 1 + nodes.length) % nodes.length;\n if (event.key === \"Home\") next = 0;\n if (event.key === \"End\") next = nodes.length - 1;\n if (event.key === \"Escape\" || event.key === \"Tab\") { if (event.key === \"Tab\") restoreFocus.current = false; change(false); if (event.key === \"Escape\") event.preventDefault(); }\n if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey && event.key !== \" \") {\n const result = resolveMenuTypeahead(\n items.filter(item => !item.disabled).map(item => ({\n textValue: item.textValue ?? (typeof item.label === \"string\" ? item.label : \"\"),\n })), index, event.key, search.current, Date.now(),\n );\n search.current = result.state;\n next = result.index;\n }\n if (next !== undefined && next >= 0) { event.preventDefault(); event.stopPropagation(); nodes[next]?.focus(); }\n };\n return <div ref={root} className=\"hjm-menu-morph\" onKeyDownCapture={keyDown}>\n {/* Open toward following content; upstream defaults upward and can cover preceding values. */}\n <Bloom.Root direction=\"bottom\" open={open} onOpenChange={change} modal={false}>\n <Bloom.Container buttonSize={{ width: 160, height: 44 }} menuWidth={240}\n style={{ background: theme.palette.theme.bg, color: theme.palette.theme.text }}>\n <Bloom.Trigger disabled={disabled}>{label}</Bloom.Trigger>\n <Bloom.Content>\n {items.map(item => <button type=\"button\" role=\"menuitem\" tabIndex={-1} className=\"hjm-menu-morph__item\" key={item.id} disabled={!open || (item.disabled ?? false)}\n data-tone={item.tone} aria-label={item.textValue}\n onClick={() => { if (!item.disabled) { change(false); onAction?.(item.id); } }}>\n {item.leading}{item.label}{item.trailing}\n </button>)}\n </Bloom.Content>\n </Bloom.Container>\n </Bloom.Root>\n </div>;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MenuSearchState = Readonly<{
|
|
2
|
+
value: string;
|
|
3
|
+
time: number;
|
|
4
|
+
}>;
|
|
5
|
+
export type MenuSearchItem = Readonly<{
|
|
6
|
+
textValue: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function resolveMenuTypeahead(items: readonly MenuSearchItem[], currentIndex: number, key: string, previous: MenuSearchState, now: number): Readonly<{
|
|
10
|
+
state: MenuSearchState;
|
|
11
|
+
index: number | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=menu-typeahead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-typeahead.d.ts","sourceRoot":"","sources":["../src/menu-typeahead.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxE,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAMjF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,GACV,QAAQ,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAYjE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The base Menu used 500ms; alternate presentations drifted to 600/700ms and
|
|
2
|
+
// searched DOM decorations. Share the base policy instead of changing each host.
|
|
3
|
+
const menuSearchResetMs = 500;
|
|
4
|
+
export function resolveMenuTypeahead(items, currentIndex, key, previous, now) {
|
|
5
|
+
const prefix = now - previous.time < menuSearchResetMs ? previous.value : "";
|
|
6
|
+
const value = `${prefix}${key}`.toLocaleLowerCase();
|
|
7
|
+
const query = new Set(value).size === 1 ? key.toLocaleLowerCase() : value;
|
|
8
|
+
for (let offset = 1; offset <= items.length; offset += 1) {
|
|
9
|
+
const index = (currentIndex + offset + items.length) % items.length;
|
|
10
|
+
const item = items[index];
|
|
11
|
+
if (item && !item.disabled && item.textValue.toLocaleLowerCase().startsWith(query)) {
|
|
12
|
+
return { state: { value, time: now }, index };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { state: { value, time: now }, index: undefined };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=menu-typeahead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-typeahead.js","sourceRoot":"","sources":["../src/menu-typeahead.ts"],"names":[],"mappings":"AAGA,6EAA6E;AAC7E,iFAAiF;AACjF,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,oBAAoB,CAClC,KAAgC,EAChC,YAAoB,EACpB,GAAW,EACX,QAAyB,EACzB,GAAW;IAEX,MAAM,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,IAAI,GAAG,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,KAAK,GAAG,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1E,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QACpE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC3D,CAAC","sourcesContent":["export type MenuSearchState = Readonly<{ value: string; time: number }>;\nexport type MenuSearchItem = Readonly<{ textValue: string; disabled?: boolean }>;\n\n// The base Menu used 500ms; alternate presentations drifted to 600/700ms and\n// searched DOM decorations. Share the base policy instead of changing each host.\nconst menuSearchResetMs = 500;\n\nexport function resolveMenuTypeahead(\n items: readonly MenuSearchItem[],\n currentIndex: number,\n key: string,\n previous: MenuSearchState,\n now: number,\n): Readonly<{ state: MenuSearchState; index: number | undefined }> {\n const prefix = now - previous.time < menuSearchResetMs ? previous.value : \"\";\n const value = `${prefix}${key}`.toLocaleLowerCase();\n const query = new Set(value).size === 1 ? key.toLocaleLowerCase() : value;\n for (let offset = 1; offset <= items.length; offset += 1) {\n const index = (currentIndex + offset + items.length) % items.length;\n const item = items[index];\n if (item && !item.disabled && item.textValue.toLocaleLowerCase().startsWith(query)) {\n return { state: { value, time: now }, index };\n }\n }\n return { state: { value, time: now }, index: undefined };\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type NavigationBarProps = Readonly<{
|
|
3
|
+
label: string;
|
|
4
|
+
brand: ReactNode;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
actions?: ReactNode;
|
|
7
|
+
}>;
|
|
8
|
+
/** Site navigation composition; Menu/SearchField continue to own their behavior. */
|
|
9
|
+
export declare function NavigationBar({ label, brand, children, actions }: NavigationBarProps): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=navigation-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-bar.d.ts","sourceRoot":"","sources":["../src/navigation-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC,CAAC;AAEH,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,kBAAkB,+BAQpF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOptionalHjmTheme } from "./provider.js";
|
|
3
|
+
/** Site navigation composition; Menu/SearchField continue to own their behavior. */
|
|
4
|
+
export function NavigationBar({ label, brand, children, actions }) {
|
|
5
|
+
const theme = useOptionalHjmTheme();
|
|
6
|
+
if (!label.trim())
|
|
7
|
+
throw new TypeError("NavigationBar requires an accessible label");
|
|
8
|
+
return _jsxs("nav", { "aria-label": label, className: "hjm-navigation-bar", dir: theme?.environment.direction, children: [_jsx("div", { className: "hjm-navigation-bar__brand", children: brand }), _jsx("div", { className: "hjm-navigation-bar__destinations", children: children }), actions && _jsx("div", { className: "hjm-navigation-bar__actions", children: actions })] });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=navigation-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-bar.js","sourceRoot":"","sources":["../src/navigation-bar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AASpD,oFAAoF;AACpF,MAAM,UAAU,aAAa,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAsB;IACnF,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAC;IACpC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACrF,OAAO,6BAAiB,KAAK,EAAE,SAAS,EAAC,oBAAoB,EAAC,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,SAAS,aAC7F,cAAK,SAAS,EAAC,2BAA2B,YAAE,KAAK,GAAO,EACxD,cAAK,SAAS,EAAC,kCAAkC,YAAE,QAAQ,GAAO,EACjE,OAAO,IAAI,cAAK,SAAS,EAAC,6BAA6B,YAAE,OAAO,GAAO,IACpE,CAAC;AACT,CAAC","sourcesContent":["import type { ReactNode } from \"react\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\n\nexport type NavigationBarProps = Readonly<{\n label: string;\n brand: ReactNode;\n children: ReactNode;\n actions?: ReactNode;\n}>;\n\n/** Site navigation composition; Menu/SearchField continue to own their behavior. */\nexport function NavigationBar({ label, brand, children, actions }: NavigationBarProps) {\n const theme = useOptionalHjmTheme();\n if (!label.trim()) throw new TypeError(\"NavigationBar requires an accessible label\");\n return <nav aria-label={label} className=\"hjm-navigation-bar\" dir={theme?.environment.direction}>\n <div className=\"hjm-navigation-bar__brand\">{brand}</div>\n <div className=\"hjm-navigation-bar__destinations\">{children}</div>\n {actions && <div className=\"hjm-navigation-bar__actions\">{actions}</div>}\n </nav>;\n}\n"]}
|
package/dist/navigation.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type TabsAppearance } from "@hjmds/design-contracts/gooey-navigation";
|
|
2
|
+
export type { TabsAppearance };
|
|
1
3
|
import { type TabsActivationMode, type TabsDirection, type TabsMountPolicy, type TabsOrientation, type TabsPanelMode } from "@hjmds/design-contracts/behaviors";
|
|
2
4
|
import { type TabSize, type TabsLayout, type TabsOverflow } from "@hjmds/design-contracts/recipes";
|
|
3
5
|
import { type HTMLAttributes, type ReactNode } from "react";
|
|
@@ -7,8 +9,6 @@ export type TabLeadingRenderProps = Readonly<{
|
|
|
7
9
|
color: "currentColor";
|
|
8
10
|
/** Pixel size resolved from `tabsRecipe.icon.glyph`. */
|
|
9
11
|
size: number;
|
|
10
|
-
/** Compatibility alias for product icon libraries that name this value explicitly. */
|
|
11
|
-
glyphSize: number;
|
|
12
12
|
}>;
|
|
13
13
|
export type TabItem = Readonly<{
|
|
14
14
|
id: string;
|
|
@@ -32,6 +32,7 @@ export type TabsProps = Omit<HTMLAttributes<HTMLDivElement>, "dir" | "onChange">
|
|
|
32
32
|
activationMode?: TabsActivationMode;
|
|
33
33
|
mountPolicy?: TabsMountPolicy;
|
|
34
34
|
panelMode?: TabsPanelMode;
|
|
35
|
+
appearance?: TabsAppearance;
|
|
35
36
|
orientation?: TabsOrientation;
|
|
36
37
|
direction?: TabsDirection;
|
|
37
38
|
loop?: boolean;
|
|
@@ -61,5 +62,4 @@ export type TabPanelProps = ExternalTabPanelBaseProps & (Readonly<{
|
|
|
61
62
|
/** External panel host for products that keep routing, query, or scroll state outside Tabs. */
|
|
62
63
|
export declare function TabPanel(props: TabPanelProps): import("react").JSX.Element | null;
|
|
63
64
|
export declare const Tabs: import("react").ForwardRefExoticComponent<TabsProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
64
|
-
export {};
|
|
65
65
|
//# sourceMappingURL=navigation.d.ts.map
|
package/dist/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.tsx"],"names":[],"mappings":"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"}
|
package/dist/navigation.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { resolveGooeyIndicator } from "@hjmds/design-contracts/gooey-navigation";
|
|
2
3
|
import { getTabNavigationIntent, getTabNavigationTarget, resolveInitialTabValue, tabsBehaviorDefaults, } from "@hjmds/design-contracts/behaviors";
|
|
3
4
|
import { iconRecipe, tabsRecipe, } from "@hjmds/design-contracts/recipes";
|
|
4
|
-
import { forwardRef, useEffect, useId, useRef, useState, } from "react";
|
|
5
|
+
import { forwardRef, useEffect, useLayoutEffect, useId, useRef, useState, } from "react";
|
|
5
6
|
import { classNames, useControllableState } from "./internal.js";
|
|
6
7
|
import { useOptionalHjmTheme } from "./provider.js";
|
|
7
8
|
function validateItems(items) {
|
|
@@ -79,7 +80,7 @@ export function TabPanel(props) {
|
|
|
79
80
|
return (_jsx(TabPanelHost, { ...hostProps, id: getTabPanelId(tabsId, value, dynamic ? "dynamic" : "keyed"), labelledBy: getTabId(tabsId, dynamic ? activeValue : value), selected: selected, dynamic: dynamic, children: children }));
|
|
80
81
|
}
|
|
81
82
|
export const Tabs = forwardRef(function Tabs(props, ref) {
|
|
82
|
-
const { label, items, activationMode = tabsBehaviorDefaults.activationMode, mountPolicy = tabsBehaviorDefaults.mountPolicy, panelMode = tabsBehaviorDefaults.panelMode, orientation = tabsBehaviorDefaults.orientation, direction: directionProp, loop = tabsBehaviorDefaults.loop, size = tabsRecipe.defaults.size, layout = tabsRecipe.defaults.layout, overflow = tabsRecipe.defaults.overflow, renderPanels = true, className, id, value: valueProp, defaultValue, onValueChange, ...rest } = props;
|
|
83
|
+
const { label, items, activationMode = tabsBehaviorDefaults.activationMode, mountPolicy = tabsBehaviorDefaults.mountPolicy, panelMode = tabsBehaviorDefaults.panelMode, appearance = "standard", orientation = tabsBehaviorDefaults.orientation, direction: directionProp, loop = tabsBehaviorDefaults.loop, size = tabsRecipe.defaults.size, layout = tabsRecipe.defaults.layout, overflow = tabsRecipe.defaults.overflow, renderPanels = true, className, id, value: valueProp, defaultValue, onValueChange, ...rest } = props;
|
|
83
84
|
validateItems(items);
|
|
84
85
|
if (label.trim().length === 0)
|
|
85
86
|
throw new TypeError("Tabs label must not be empty");
|
|
@@ -123,6 +124,47 @@ export const Tabs = forwardRef(function Tabs(props, ref) {
|
|
|
123
124
|
const baseId = id ?? `hjm-${generatedId}`;
|
|
124
125
|
const theme = useOptionalHjmTheme();
|
|
125
126
|
const direction = directionProp ?? theme?.environment.direction ?? tabsBehaviorDefaults.direction;
|
|
127
|
+
const gooey = appearance === "gooey" && orientation === "horizontal";
|
|
128
|
+
const indicatorRef = useRef(null);
|
|
129
|
+
const previousIndicator = useRef(null);
|
|
130
|
+
useLayoutEffect(() => {
|
|
131
|
+
const indicator = indicatorRef.current;
|
|
132
|
+
const tab = tabRefs.current.get(value);
|
|
133
|
+
if (!gooey || !indicator || !tab) {
|
|
134
|
+
previousIndicator.current = null;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let animation;
|
|
138
|
+
const place = (animate) => {
|
|
139
|
+
animation?.cancel();
|
|
140
|
+
const rect = { x: tab.offsetLeft, width: tab.offsetWidth };
|
|
141
|
+
if (rect.width <= 0)
|
|
142
|
+
return;
|
|
143
|
+
indicator.style.left = `${rect.x}px`;
|
|
144
|
+
indicator.style.width = `${rect.width}px`;
|
|
145
|
+
const previous = previousIndicator.current;
|
|
146
|
+
if (animate && previous && previous.value !== value && !theme?.environment.reducedMotion && !document.hidden) {
|
|
147
|
+
const recipe = resolveGooeyIndicator(previous.rect, rect);
|
|
148
|
+
animation = indicator.animate(recipe.input.map((offset, index) => ({ offset, left: `${recipe.x[index]}px`, width: `${recipe.width[index]}px` })), { duration: recipe.duration, easing: "ease-out" });
|
|
149
|
+
}
|
|
150
|
+
previousIndicator.current = { value, rect };
|
|
151
|
+
};
|
|
152
|
+
place(true);
|
|
153
|
+
// Font loading, resizing and large text change measurements without changing selection.
|
|
154
|
+
const observer = new ResizeObserver(() => {
|
|
155
|
+
const previous = previousIndicator.current?.rect;
|
|
156
|
+
if (previous?.x !== tab.offsetLeft || previous.width !== tab.offsetWidth)
|
|
157
|
+
place(false);
|
|
158
|
+
});
|
|
159
|
+
const list = tab.parentElement;
|
|
160
|
+
for (const child of list.children)
|
|
161
|
+
if (child !== indicator)
|
|
162
|
+
observer.observe(child);
|
|
163
|
+
const visibility = () => { if (document.hidden)
|
|
164
|
+
animation?.cancel(); };
|
|
165
|
+
document.addEventListener("visibilitychange", visibility);
|
|
166
|
+
return () => { animation?.cancel(); observer.disconnect(); document.removeEventListener("visibilitychange", visibility); };
|
|
167
|
+
}, [gooey, value, items, theme?.environment.reducedMotion]);
|
|
126
168
|
useEffect(() => {
|
|
127
169
|
if (!controlled && !storedSelectionIsValid)
|
|
128
170
|
setValue(collectionFallback);
|
|
@@ -178,26 +220,25 @@ export const Tabs = forwardRef(function Tabs(props, ref) {
|
|
|
178
220
|
}
|
|
179
221
|
};
|
|
180
222
|
const panelId = (value) => getTabPanelId(baseId, value, panelMode);
|
|
181
|
-
return (_jsxs("div", { ...rest, ref: ref, id: baseId, className: classNames("hjm-tabs", className), "data-size": size, "data-layout": layout, "data-overflow": overflow, "data-orientation": orientation, "data-mount-policy": mountPolicy, "data-panel-mode": panelMode, "data-state": "ready", dir: direction, children: [
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}) }), !renderPanels ? null : panelMode === "dynamic" ? (_jsx(TabPanelHost, { id: panelId(value), labelledBy: getTabId(baseId, value), selected: true, dynamic: true, children: items.find((item) => item.id === value)?.panel })) : items.map((item) => {
|
|
223
|
+
return (_jsxs("div", { ...rest, ref: ref, id: baseId, className: classNames("hjm-tabs", className), "data-size": size, "data-layout": layout, "data-overflow": overflow, "data-appearance": gooey ? "gooey" : "standard", "data-orientation": orientation, "data-mount-policy": mountPolicy, "data-panel-mode": panelMode, "data-state": "ready", dir: direction, children: [_jsxs("div", { className: "hjm-tabs__list", role: "tablist", "aria-label": label, "aria-orientation": orientation, children: [gooey ? _jsx("span", { ref: indicatorRef, "aria-hidden": "true", className: "hjm-tabs__gooey" }) : null, items.map((item) => {
|
|
224
|
+
const selected = item.id === value;
|
|
225
|
+
const tabId = getTabId(baseId, item.id);
|
|
226
|
+
const leadingSize = iconRecipe.sizes[tabsRecipe.icon.glyph];
|
|
227
|
+
return (_jsxs("button", { ref: (node) => {
|
|
228
|
+
if (node)
|
|
229
|
+
tabRefs.current.set(item.id, node);
|
|
230
|
+
else
|
|
231
|
+
tabRefs.current.delete(item.id);
|
|
232
|
+
}, id: tabId, type: "button", role: "tab", className: "hjm-tabs__tab", "data-state": selected ? "selected" : "idle", "aria-selected": selected, "aria-controls": panelId(item.id), tabIndex: item.id === resolvedFocusValue ? 0 : -1, disabled: item.disabled, onClick: () => {
|
|
233
|
+
setFocusValue(item.id);
|
|
234
|
+
setValue(item.id);
|
|
235
|
+
}, onKeyDown: (event) => handleKeyDown(event, item.id), children: [item.renderLeading ? (_jsx("span", { "aria-hidden": "true", className: "hjm-tabs__leading", children: item.renderLeading({
|
|
236
|
+
selected,
|
|
237
|
+
disabled: item.disabled ?? false,
|
|
238
|
+
color: "currentColor",
|
|
239
|
+
size: leadingSize,
|
|
240
|
+
}) })) : null, item.label] }, item.id));
|
|
241
|
+
})] }), !renderPanels ? null : panelMode === "dynamic" ? (_jsx(TabPanelHost, { id: panelId(value), labelledBy: getTabId(baseId, value), selected: true, dynamic: true, children: items.find((item) => item.id === value)?.panel })) : items.map((item) => {
|
|
201
242
|
const selected = item.id === value;
|
|
202
243
|
const mounted = mountPolicy === "always" ||
|
|
203
244
|
(mountPolicy === "visited" && (visited.has(item.id) || selected)) ||
|