@hjmds/react 1.8.0 → 1.10.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.
Files changed (65) hide show
  1. package/README.md +4 -0
  2. package/THIRD_PARTY_NOTICES.md +29 -0
  3. package/dist/affix.d.ts +10 -0
  4. package/dist/affix.d.ts.map +1 -0
  5. package/dist/affix.js +51 -0
  6. package/dist/affix.js.map +1 -0
  7. package/dist/carousel-motion.d.ts +13 -0
  8. package/dist/carousel-motion.d.ts.map +1 -0
  9. package/dist/carousel-motion.js +35 -0
  10. package/dist/carousel-motion.js.map +1 -0
  11. package/dist/celebration.d.ts +8 -0
  12. package/dist/celebration.d.ts.map +1 -0
  13. package/dist/celebration.js +51 -0
  14. package/dist/celebration.js.map +1 -0
  15. package/dist/color-picker.d.ts +18 -0
  16. package/dist/color-picker.d.ts.map +1 -0
  17. package/dist/color-picker.js +40 -0
  18. package/dist/color-picker.js.map +1 -0
  19. package/dist/content-transition.d.ts +18 -0
  20. package/dist/content-transition.d.ts.map +1 -0
  21. package/dist/content-transition.js +33 -0
  22. package/dist/content-transition.js.map +1 -0
  23. package/dist/date-picker.d.ts.map +1 -1
  24. package/dist/date-picker.js +39 -1
  25. package/dist/date-picker.js.map +1 -1
  26. package/dist/evidence.d.ts +46 -1
  27. package/dist/evidence.d.ts.map +1 -1
  28. package/dist/evidence.js +5 -2
  29. package/dist/evidence.js.map +1 -1
  30. package/dist/menu-morph.d.ts.map +1 -1
  31. package/dist/menu-morph.js +2 -1
  32. package/dist/menu-morph.js.map +1 -1
  33. package/dist/menubar.d.ts.map +1 -1
  34. package/dist/menubar.js +24 -15
  35. package/dist/menubar.js.map +1 -1
  36. package/dist/modal.d.ts +1 -0
  37. package/dist/modal.d.ts.map +1 -1
  38. package/dist/modal.js.map +1 -1
  39. package/dist/overlays.d.ts.map +1 -1
  40. package/dist/overlays.js +33 -2
  41. package/dist/overlays.js.map +1 -1
  42. package/dist/sortable.d.ts +14 -0
  43. package/dist/sortable.d.ts.map +1 -0
  44. package/dist/sortable.js +87 -0
  45. package/dist/sortable.js.map +1 -0
  46. package/dist/styles.css +47 -13
  47. package/dist/styles.layered.css +47 -13
  48. package/dist/swipe-actions.d.ts +13 -0
  49. package/dist/swipe-actions.d.ts.map +1 -0
  50. package/dist/swipe-actions.js +25 -0
  51. package/dist/swipe-actions.js.map +1 -0
  52. package/dist/theme.d.ts.map +1 -1
  53. package/dist/theme.js +9 -0
  54. package/dist/theme.js.map +1 -1
  55. package/dist/toast.d.ts.map +1 -1
  56. package/dist/toast.js +12 -7
  57. package/dist/toast.js.map +1 -1
  58. package/dist/tree.d.ts.map +1 -1
  59. package/dist/tree.js +14 -3
  60. package/dist/tree.js.map +1 -1
  61. package/dist/watermark.d.ts +12 -0
  62. package/dist/watermark.d.ts.map +1 -0
  63. package/dist/watermark.js +10 -0
  64. package/dist/watermark.js.map +1 -0
  65. package/package.json +65 -4
package/README.md CHANGED
@@ -161,3 +161,7 @@ Masonry and VirtualList use the granular `masonry` and `virtual-list` entries.
161
161
  Supply measured item heights for Masonry and a fixed row height for VirtualList.
162
162
  The optional `qr-code` entry requires `qrcode-generator@2.0.4` and a visible alternative
163
163
  action alongside the accessible code.
164
+
165
+ ## Optional interaction adapters
166
+
167
+ Stable `sortable`, `swipe-actions`, `content-transition`, `carousel-motion` and `celebration` entries are available through explicit subpath imports. See [installation, localized labels and behavior contracts](../../docs/interaction-adapters.md), including pinned peers and native verification limits. These adapters are not root exports.
@@ -26,3 +26,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
26
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
28
  SOFTWARE.
29
+
30
+ ## Motion Primitives — keyed content transition
31
+
32
+ Source: https://github.com/ibelick/motion-primitives/tree/120f64f6ca60348e251f929e9c81f11ccbe45eda
33
+
34
+ TransitionPanel pattern adapted to a single active HJM subtree, shared timing and host-owned focus. TextTransition uses whole-text fade instead of splitting graphemes.
35
+
36
+ MIT License
37
+
38
+ Copyright (c) 2024 ibelick
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is
45
+ furnished to do so, subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in all
48
+ copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
56
+ SOFTWARE.
57
+ app/page.tsx
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ export type AffixProps = Readonly<{
3
+ children: ReactNode;
4
+ offset?: number;
5
+ disabled?: boolean;
6
+ onChange?: (affixed: boolean) => void;
7
+ }>;
8
+ /** CSS keeps natural flow, parent boundaries and focus intact; JS only observes state and oversize content. */
9
+ export declare function Affix({ children, offset, disabled, onChange }: AffixProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=affix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"affix.d.ts","sourceRoot":"","sources":["../src/affix.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,CAAC,CAAC;AACvI,+GAA+G;AAC/G,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,MAA2B,EAAE,QAAgB,EAAE,QAAQ,EAAE,EAAE,UAAU,+BAkCtG"}
package/dist/affix.js ADDED
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { affixRecipe, validateAffixOffset } from "@hjmds/design-contracts/components/affix";
4
+ /** CSS keeps natural flow, parent boundaries and focus intact; JS only observes state and oversize content. */
5
+ export function Affix({ children, offset = affixRecipe.offset, disabled = false, onChange }) {
6
+ validateAffixOffset(offset);
7
+ const marker = useRef(null);
8
+ const content = useRef(null);
9
+ const callback = useRef(onChange);
10
+ callback.current = onChange;
11
+ const [affixed, setAffixed] = useState(false);
12
+ const [oversize, setOversize] = useState(false);
13
+ const reported = useRef(undefined);
14
+ useEffect(() => {
15
+ const node = content.current;
16
+ const sentinel = marker.current;
17
+ if (!node || !sentinel)
18
+ return;
19
+ let frame = 0;
20
+ function measure() {
21
+ frame = 0;
22
+ let scrollRoot = node.parentElement;
23
+ while (scrollRoot && !/(auto|scroll|hidden|overlay)/.test(getComputedStyle(scrollRoot).overflowY))
24
+ scrollRoot = scrollRoot.parentElement;
25
+ const viewportHeight = scrollRoot?.clientHeight ?? document.documentElement.clientHeight;
26
+ const edge = (scrollRoot ? scrollRoot.getBoundingClientRect().top + scrollRoot.clientTop : 0) + offset;
27
+ const rect = node.getBoundingClientRect();
28
+ const tooTall = rect.height > viewportHeight - offset;
29
+ setOversize(tooTall);
30
+ // One CSS pixel tolerates subpixel scroll rounding, not a second positioning system.
31
+ const next = !disabled && !tooTall && sentinel.getBoundingClientRect().top < edge && Math.abs(rect.top - edge) <= 1;
32
+ setAffixed(next);
33
+ if (reported.current !== next) {
34
+ reported.current = next;
35
+ callback.current?.(next);
36
+ }
37
+ }
38
+ const schedule = () => { if (!frame)
39
+ frame = requestAnimationFrame(measure); };
40
+ const resize = new ResizeObserver(schedule);
41
+ resize.observe(node);
42
+ for (let parent = node.parentElement; parent; parent = parent.parentElement)
43
+ resize.observe(parent);
44
+ measure();
45
+ document.addEventListener("scroll", schedule, true);
46
+ window.addEventListener("resize", schedule);
47
+ return () => { cancelAnimationFrame(frame); resize.disconnect(); document.removeEventListener("scroll", schedule, true); window.removeEventListener("resize", schedule); };
48
+ }, [offset, disabled, oversize]);
49
+ return _jsxs(_Fragment, { children: [_jsx("span", { ref: marker, className: "hjm-affix__marker", "aria-hidden": "true" }), _jsx("div", { ref: content, "data-hjm-affix": true, "data-affixed": affixed, "data-oversize": oversize, className: "hjm-affix", style: { position: disabled || oversize ? "relative" : affixRecipe.position, top: disabled || oversize ? undefined : offset }, children: children })] });
50
+ }
51
+ //# sourceMappingURL=affix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"affix.js","sourceRoot":"","sources":["../src/affix.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { affixRecipe, validateAffixOffset } from \"@hjmds/design-contracts/components/affix\";\nexport type AffixProps = Readonly<{ children: ReactNode; offset?: number; disabled?: boolean; onChange?: (affixed: boolean) => void }>;\n/** CSS keeps natural flow, parent boundaries and focus intact; JS only observes state and oversize content. */\nexport function Affix({ children, offset = affixRecipe.offset, disabled = false, onChange }: AffixProps) {\n validateAffixOffset(offset);\n const marker = useRef<HTMLSpanElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const callback = useRef(onChange); callback.current = onChange;\n const [affixed, setAffixed] = useState(false);\n const [oversize, setOversize] = useState(false);\n const reported = useRef<boolean | undefined>(undefined);\n useEffect(() => {\n const node = content.current; const sentinel = marker.current;\n if (!node || !sentinel) return;\n let frame = 0;\n function measure() {\n frame = 0;\n let scrollRoot = node!.parentElement;\n while (scrollRoot && !/(auto|scroll|hidden|overlay)/.test(getComputedStyle(scrollRoot).overflowY)) scrollRoot = scrollRoot.parentElement;\n const viewportHeight = scrollRoot?.clientHeight ?? document.documentElement.clientHeight;\n const edge = (scrollRoot ? scrollRoot.getBoundingClientRect().top + scrollRoot.clientTop : 0) + offset;\n const rect = node!.getBoundingClientRect();\n const tooTall = rect.height > viewportHeight - offset;\n setOversize(tooTall);\n // One CSS pixel tolerates subpixel scroll rounding, not a second positioning system.\n const next = !disabled && !tooTall && sentinel!.getBoundingClientRect().top < edge && Math.abs(rect.top - edge) <= 1;\n setAffixed(next);\n if (reported.current !== next) { reported.current = next; callback.current?.(next); }\n }\n const schedule = () => { if (!frame) frame = requestAnimationFrame(measure); };\n const resize = new ResizeObserver(schedule);\n resize.observe(node);\n for (let parent = node.parentElement; parent; parent = parent.parentElement) resize.observe(parent);\n measure(); document.addEventListener(\"scroll\", schedule, true); window.addEventListener(\"resize\", schedule);\n return () => { cancelAnimationFrame(frame); resize.disconnect(); document.removeEventListener(\"scroll\", schedule, true); window.removeEventListener(\"resize\", schedule); };\n }, [offset, disabled, oversize]);\n return <><span ref={marker} className=\"hjm-affix__marker\" aria-hidden=\"true\" /><div ref={content} data-hjm-affix data-affixed={affixed} data-oversize={oversize} className=\"hjm-affix\" style={{ position: disabled || oversize ? \"relative\" : affixRecipe.position, top: disabled || oversize ? undefined : offset }}>{children}</div></>;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ import { type SortableItem } from "@hjmds/design-contracts/components/interaction-adapters";
3
+ export type CarouselMotionProps = {
4
+ slides: readonly SortableItem[];
5
+ currentKey: string;
6
+ onCurrentKeyChange(key: string): void;
7
+ renderSlide(item: SortableItem): ReactNode;
8
+ label: string;
9
+ previousLabel: string;
10
+ nextLabel: string;
11
+ };
12
+ export declare function CarouselMotion(props: CarouselMotionProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=carousel-motion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel-motion.d.ts","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,yDAAyD,CAAC;AAI9G,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3F,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;CACrG,CAAC;AACF,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BA+BxD"}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import useEmblaCarousel from "embla-carousel-react";
4
+ import { validateCarousel } from "@hjmds/design-contracts/components/interaction-adapters";
5
+ import { Button } from "./actions.js";
6
+ import { useHjmTheme } from "./provider.js";
7
+ export function CarouselMotion(props) {
8
+ const index = validateCarousel(props.slides, props.currentKey);
9
+ const { environment } = useHjmTheme();
10
+ // Embla deep-compares options and reInits on change; a live startIndex restarted the engine at the target on
11
+ // every selection, so navigation snapped instead of animating (2026-09-30 review). Only the first index seeds it.
12
+ const [startIndex] = useState(index);
13
+ const [viewport, api] = useEmblaCarousel({ loop: false, direction: environment.direction, startIndex,
14
+ // HJM owns selection; upstream focus scrolling must not independently change slides.
15
+ // Embla's default 25 is a physics duration factor, not an HJM millisecond token.
16
+ watchFocus: false, duration: environment.reducedMotion ? 0 : 25 });
17
+ const latest = useRef(props);
18
+ latest.current = props;
19
+ const syncing = useRef(false);
20
+ useEffect(() => {
21
+ if (!api)
22
+ return;
23
+ const select = () => {
24
+ const current = latest.current;
25
+ const item = current.slides[api.selectedScrollSnap()];
26
+ if (!syncing.current && item && item.id !== current.currentKey)
27
+ current.onCurrentKeyChange(item.id);
28
+ };
29
+ api.on("select", select);
30
+ return () => { api.off("select", select); };
31
+ }, [api]);
32
+ useEffect(() => { syncing.current = true; api?.scrollTo(index, environment.reducedMotion); syncing.current = false; }, [api, index, environment.reducedMotion]);
33
+ return _jsxs("section", { "aria-label": props.label, "aria-roledescription": "carousel", children: [_jsx("div", { ref: viewport, style: { overflow: "hidden" }, children: _jsx("div", { style: { display: "flex", touchAction: "pan-y pinch-zoom" }, children: props.slides.map((slide, i) => _jsx("div", { role: "group", "aria-roledescription": "slide", "aria-label": slide.label, "aria-hidden": i !== index, inert: i !== index, style: { flex: "0 0 100%", minWidth: 0 }, children: props.renderSlide(slide) }, slide.id)) }) }), _jsx(Button, { tone: "ghost", disabled: index === 0, onClick: () => props.onCurrentKeyChange(props.slides[index - 1].id), children: props.previousLabel }), _jsx(Button, { tone: "ghost", disabled: index === props.slides.length - 1, onClick: () => props.onCurrentKeyChange(props.slides[index + 1].id), children: props.nextLabel })] });
34
+ }
35
+ //# sourceMappingURL=carousel-motion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel-motion.js","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport useEmblaCarousel from \"embla-carousel-react\";\nimport { validateCarousel, type SortableItem } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { Button } from \"./actions.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type CarouselMotionProps = {\n slides: readonly SortableItem[]; currentKey: string; onCurrentKeyChange(key: string): void;\n renderSlide(item: SortableItem): ReactNode; label: string; previousLabel: string; nextLabel: string;\n};\nexport function CarouselMotion(props: CarouselMotionProps) {\n const index = validateCarousel(props.slides, props.currentKey);\n const { environment } = useHjmTheme();\n // Embla deep-compares options and reInits on change; a live startIndex restarted the engine at the target on\n // every selection, so navigation snapped instead of animating (2026-09-30 review). Only the first index seeds it.\n const [startIndex] = useState(index);\n const [viewport, api] = useEmblaCarousel({ loop: false, direction: environment.direction, startIndex,\n // HJM owns selection; upstream focus scrolling must not independently change slides.\n // Embla's default 25 is a physics duration factor, not an HJM millisecond token.\n watchFocus: false, duration: environment.reducedMotion ? 0 : 25 });\n const latest = useRef(props); latest.current = props;\n const syncing = useRef(false);\n useEffect(() => {\n if (!api) return;\n const select = () => {\n const current = latest.current;\n const item = current.slides[api.selectedScrollSnap()];\n if (!syncing.current && item && item.id !== current.currentKey) current.onCurrentKeyChange(item.id);\n };\n api.on(\"select\", select);\n return () => { api.off(\"select\", select); };\n }, [api]);\n useEffect(() => { syncing.current = true; api?.scrollTo(index, environment.reducedMotion); syncing.current = false; }, [api, index, environment.reducedMotion]);\n return <section aria-label={props.label} aria-roledescription=\"carousel\">\n <div ref={viewport} style={{ overflow: \"hidden\" }}><div style={{ display: \"flex\", touchAction: \"pan-y pinch-zoom\" }}>\n {props.slides.map((slide, i) => <div key={slide.id} role=\"group\" aria-roledescription=\"slide\" aria-label={slide.label} aria-hidden={i !== index} inert={i !== index}\n style={{ flex: \"0 0 100%\", minWidth: 0 }}>{props.renderSlide(slide)}</div>)}\n </div></div>\n <Button tone=\"ghost\" disabled={index === 0} onClick={() => props.onCurrentKeyChange(props.slides[index - 1]!.id)}>{props.previousLabel}</Button>\n <Button tone=\"ghost\" disabled={index === props.slides.length - 1} onClick={() => props.onCurrentKeyChange(props.slides[index + 1]!.id)}>{props.nextLabel}</Button>\n </section>;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { type CelebrationPreset } from "@hjmds/design-contracts/components/interaction-adapters";
2
+ export type CelebrationProps = {
3
+ eventId: string;
4
+ preset?: CelebrationPreset;
5
+ onComplete?(): void;
6
+ };
7
+ export declare function Celebration({ eventId, preset, onComplete }: CelebrationProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=celebration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"celebration.d.ts","sourceRoot":"","sources":["../src/celebration.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAyD,KAAK,iBAAiB,EAAE,MAAM,yDAAyD,CAAC;AAGxJ,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAAC,UAAU,CAAC,IAAI,IAAI,CAAA;CAAE,CAAC;AACpG,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,MAAsB,EAAE,UAAU,EAAE,EAAE,gBAAgB,+BA+B5F"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import confetti from "canvas-confetti";
4
+ import { celebrationColors, celebrationRecipe, validateEventId } from "@hjmds/design-contracts/components/interaction-adapters";
5
+ import { useHjmTheme } from "./provider.js";
6
+ export function Celebration({ eventId, preset = "small-burst", onComplete }) {
7
+ validateEventId(eventId);
8
+ const canvas = useRef(null);
9
+ const seen = useRef(new Set());
10
+ const { environment, palette } = useHjmTheme();
11
+ const latest = useRef({ preset, onComplete, environment, palette });
12
+ latest.current = { preset, onComplete, environment, palette };
13
+ const stop = useRef(() => { });
14
+ useEffect(() => {
15
+ let disposed = false;
16
+ let done = false;
17
+ let started = false;
18
+ let fire;
19
+ let timer;
20
+ const finish = () => { fire?.reset(); if (!disposed && !done && started) {
21
+ done = true;
22
+ latest.current.onComplete?.();
23
+ } };
24
+ stop.current = finish;
25
+ // Wait until after Strict Mode's probe cleanup before consuming an event.
26
+ queueMicrotask(() => {
27
+ if (disposed || seen.current.has(eventId))
28
+ return;
29
+ seen.current.add(eventId);
30
+ started = true;
31
+ const current = latest.current;
32
+ if (current.environment.reducedMotion || document.hidden) {
33
+ finish();
34
+ return;
35
+ }
36
+ fire = confetti.create(canvas.current, { resize: true, disableForReducedMotion: true });
37
+ // A private canvas avoids cancelling another screen's animation on cleanup.
38
+ void fire({ particleCount: celebrationRecipe[current.preset].count,
39
+ colors: celebrationColors(current.palette.theme.primary, current.palette.statusAccents), origin: { y: 0.7 } });
40
+ timer = setTimeout(finish, celebrationRecipe[current.preset].duration);
41
+ });
42
+ const visibility = () => { if (document.hidden)
43
+ finish(); };
44
+ document.addEventListener("visibilitychange", visibility);
45
+ return () => { disposed = true; clearTimeout(timer); document.removeEventListener("visibilitychange", visibility); fire?.reset(); };
46
+ }, [eventId]);
47
+ useEffect(() => { if (environment.reducedMotion)
48
+ stop.current(); }, [environment.reducedMotion]);
49
+ return _jsx("canvas", { ref: canvas, "aria-hidden": true, style: { position: "fixed", inset: 0, width: "100%", height: "100%", pointerEvents: "none" } });
50
+ }
51
+ //# sourceMappingURL=celebration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"celebration.js","sourceRoot":"","sources":["../src/celebration.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef } from \"react\";\nimport confetti from \"canvas-confetti\";\nimport { celebrationColors, celebrationRecipe, validateEventId, type CelebrationPreset } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type CelebrationProps = { eventId: string; preset?: CelebrationPreset; onComplete?(): void };\nexport function Celebration({ eventId, preset = \"small-burst\", onComplete }: CelebrationProps) {\n validateEventId(eventId);\n const canvas = useRef<HTMLCanvasElement>(null);\n const seen = useRef(new Set<string>());\n const { environment, palette } = useHjmTheme();\n const latest = useRef({ preset, onComplete, environment, palette }); latest.current = { preset, onComplete, environment, palette };\n const stop = useRef<() => void>(() => {});\n useEffect(() => {\n let disposed = false; let done = false; let started = false;\n let fire: ReturnType<typeof confetti.create> | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const finish = () => { fire?.reset(); if (!disposed && !done && started) { done = true; latest.current.onComplete?.(); } };\n stop.current = finish;\n // Wait until after Strict Mode's probe cleanup before consuming an event.\n queueMicrotask(() => {\n if (disposed || seen.current.has(eventId)) return;\n seen.current.add(eventId); started = true;\n const current = latest.current;\n if (current.environment.reducedMotion || document.hidden) { finish(); return; }\n fire = confetti.create(canvas.current!, { resize: true, disableForReducedMotion: true });\n // A private canvas avoids cancelling another screen's animation on cleanup.\n void fire({ particleCount: celebrationRecipe[current.preset].count,\n colors: celebrationColors(current.palette.theme.primary, current.palette.statusAccents), origin: { y: 0.7 } });\n timer = setTimeout(finish, celebrationRecipe[current.preset].duration);\n });\n const visibility = () => { if (document.hidden) finish(); };\n document.addEventListener(\"visibilitychange\", visibility);\n return () => { disposed = true; clearTimeout(timer); document.removeEventListener(\"visibilitychange\", visibility); fire?.reset(); };\n }, [eventId]);\n useEffect(() => { if (environment.reducedMotion) stop.current(); }, [environment.reducedMotion]);\n return <canvas ref={canvas} aria-hidden style={{ position: \"fixed\", inset: 0, width: \"100%\", height: \"100%\", pointerEvents: \"none\" }} />;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ export type ColorPickerLabels = Readonly<{
2
+ color: string;
3
+ hex: string;
4
+ opacity: string;
5
+ invalid: string;
6
+ }>;
7
+ export type ColorPickerProps = Readonly<{
8
+ label: string;
9
+ labels: ColorPickerLabels;
10
+ value: string;
11
+ onValueChange: (value: string) => void;
12
+ alpha?: boolean;
13
+ disabled?: boolean;
14
+ presets?: readonly string[];
15
+ }>;
16
+ /** Controlled value; invalid text stays local until corrected or escaped. Native color UI supplies RGB only. */
17
+ export declare function ColorPicker({ label, labels, value, onValueChange, alpha, disabled, presets }: ColorPickerProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=color-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-picker.d.ts","sourceRoot":"","sources":["../src/color-picker.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChG,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClE,CAAC,CAAC;AACH,gHAAgH;AAChH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAa,EAAE,QAAgB,EAAE,OAAY,EAAE,EAAE,gBAAgB,+BAiCnI"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useState } from "react";
3
+ import { colorPickerRecipe, normalizePickerColor, pickerOpacity, withPickerOpacity } from "@hjmds/design-contracts/components/color-picker";
4
+ /** Controlled value; invalid text stays local until corrected or escaped. Native color UI supplies RGB only. */
5
+ export function ColorPicker({ label, labels, value, onValueChange, alpha = false, disabled = false, presets = [] }) {
6
+ const color = normalizePickerColor(value, alpha);
7
+ // HEX is an ordered code, so isolate its LTR text even when the surrounding labels use RTL.
8
+ const palette = [...new Set(presets.map(preset => normalizePickerColor(preset, alpha)))];
9
+ if (![label, ...Object.values(labels)].every(text => text.trim()))
10
+ throw new TypeError("ColorPicker labels must not be empty");
11
+ const id = useId();
12
+ const [draft, setDraft] = useState(color);
13
+ const [invalid, setInvalid] = useState(false);
14
+ useEffect(() => { setDraft(color); setInvalid(false); }, [color]);
15
+ function emit(next) { setDraft(next); setInvalid(false); if (next !== color)
16
+ onValueChange(next); }
17
+ function commit() {
18
+ let next;
19
+ try {
20
+ // A typed 3/6-digit HEX names only the hue. Keep the current opacity instead of resetting it to 100%,
21
+ // matching the native color input; an 8-digit HEX still sets alpha explicitly (2026-09-30 review).
22
+ const digits = draft.trim().replace(/^#/, "").length;
23
+ next = alpha && (digits === 3 || digits === 6) ? normalizePickerColor(draft, false) + color.slice(7) : normalizePickerColor(draft, alpha);
24
+ }
25
+ catch {
26
+ setInvalid(true);
27
+ return;
28
+ }
29
+ emit(next);
30
+ }
31
+ return _jsxs("fieldset", { className: "hjm-color-picker", style: { "--hjm-color-picker-target": `${colorPickerRecipe.minTargetSize}px` }, "data-hjm-color-picker": true, disabled: disabled, children: [_jsx("legend", { children: label }), _jsxs("div", { className: "hjm-color-picker__row", children: [_jsxs("label", { className: "hjm-color-picker__native", children: [_jsx("span", { children: labels.color }), _jsx("input", { type: "color", value: color.slice(0, 7), onChange: event => emit(event.target.value + (alpha ? color.slice(7) : "")) })] }), _jsxs("label", { className: "hjm-color-picker__hex", children: [_jsx("span", { children: labels.hex }), _jsx("input", { type: "text", dir: "ltr", value: draft, spellCheck: false, autoComplete: "off", "aria-invalid": invalid || undefined, "aria-describedby": invalid ? `${id}-error` : undefined, onChange: event => { setDraft(event.target.value); setInvalid(false); }, onBlur: commit, onKeyDown: event => { if (event.key === "Enter") {
32
+ event.preventDefault();
33
+ commit();
34
+ } if (event.key === "Escape") {
35
+ event.preventDefault();
36
+ setDraft(color);
37
+ setInvalid(false);
38
+ } } })] })] }), invalid && _jsx("p", { id: `${id}-error`, role: "alert", className: "hjm-color-picker__error", children: labels.invalid }), alpha && _jsxs("label", { className: "hjm-color-picker__opacity", children: [_jsxs("span", { children: [labels.opacity, " ", _jsxs("output", { children: [pickerOpacity(color), "%"] })] }), _jsx("input", { type: "range", min: 0, max: 100, step: 1, value: pickerOpacity(color), "aria-label": labels.opacity, "aria-valuetext": `${pickerOpacity(color)}%`, onChange: event => emit(withPickerOpacity(color, Number(event.target.value))) })] }), _jsx("div", { className: "hjm-color-picker__preview", "aria-hidden": "true", style: { backgroundColor: color } }), palette.length > 0 && _jsx("div", { className: "hjm-color-picker__presets", children: palette.map(preset => _jsxs("button", { type: "button", "aria-pressed": preset === color, onClick: () => emit(preset), children: [_jsx("span", { "aria-hidden": "true", className: "hjm-color-picker__swatch", style: { backgroundColor: preset } }), _jsx("bdi", { dir: "ltr", children: preset })] }, preset)) })] });
39
+ }
40
+ //# sourceMappingURL=color-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-picker.js","sourceRoot":"","sources":["../src/color-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useState, type CSSProperties } from \"react\";\nimport { colorPickerRecipe, normalizePickerColor, pickerOpacity, withPickerOpacity } from \"@hjmds/design-contracts/components/color-picker\";\nexport type ColorPickerLabels = Readonly<{ color: string; hex: string; opacity: string; invalid: string }>;\nexport type ColorPickerProps = Readonly<{\n label: string; labels: ColorPickerLabels; value: string; onValueChange: (value: string) => void;\n alpha?: boolean; disabled?: boolean; presets?: readonly string[];\n}>;\n/** Controlled value; invalid text stays local until corrected or escaped. Native color UI supplies RGB only. */\nexport function ColorPicker({ label, labels, value, onValueChange, alpha = false, disabled = false, presets = [] }: ColorPickerProps) {\n const color = normalizePickerColor(value, alpha);\n // HEX is an ordered code, so isolate its LTR text even when the surrounding labels use RTL.\n const palette = [...new Set(presets.map(preset => normalizePickerColor(preset, alpha)))];\n if (![label, ...Object.values(labels)].every(text => text.trim())) throw new TypeError(\"ColorPicker labels must not be empty\");\n const id = useId();\n const [draft, setDraft] = useState(color);\n const [invalid, setInvalid] = useState(false);\n useEffect(() => { setDraft(color); setInvalid(false); }, [color]);\n function emit(next: string) { setDraft(next); setInvalid(false); if (next !== color) onValueChange(next); }\n function commit() {\n let next: string;\n try {\n // A typed 3/6-digit HEX names only the hue. Keep the current opacity instead of resetting it to 100%,\n // matching the native color input; an 8-digit HEX still sets alpha explicitly (2026-09-30 review).\n const digits = draft.trim().replace(/^#/, \"\").length;\n next = alpha && (digits === 3 || digits === 6) ? normalizePickerColor(draft, false) + color.slice(7) : normalizePickerColor(draft, alpha);\n } catch { setInvalid(true); return; }\n emit(next);\n }\n return <fieldset className=\"hjm-color-picker\" style={{ \"--hjm-color-picker-target\": `${colorPickerRecipe.minTargetSize}px` } as CSSProperties} data-hjm-color-picker disabled={disabled}>\n <legend>{label}</legend>\n <div className=\"hjm-color-picker__row\">\n <label className=\"hjm-color-picker__native\"><span>{labels.color}</span><input type=\"color\" value={color.slice(0, 7)} onChange={event => emit(event.target.value + (alpha ? color.slice(7) : \"\"))} /></label>\n <label className=\"hjm-color-picker__hex\"><span>{labels.hex}</span><input type=\"text\" dir=\"ltr\" value={draft} spellCheck={false} autoComplete=\"off\" aria-invalid={invalid || undefined} aria-describedby={invalid ? `${id}-error` : undefined}\n onChange={event => { setDraft(event.target.value); setInvalid(false); }} onBlur={commit}\n onKeyDown={event => { if (event.key === \"Enter\") { event.preventDefault(); commit(); } if (event.key === \"Escape\") { event.preventDefault(); setDraft(color); setInvalid(false); } }} /></label>\n </div>\n {invalid && <p id={`${id}-error`} role=\"alert\" className=\"hjm-color-picker__error\">{labels.invalid}</p>}\n {alpha && <label className=\"hjm-color-picker__opacity\"><span>{labels.opacity} <output>{pickerOpacity(color)}%</output></span><input type=\"range\" min={0} max={100} step={1} value={pickerOpacity(color)} aria-label={labels.opacity} aria-valuetext={`${pickerOpacity(color)}%`} onChange={event => emit(withPickerOpacity(color, Number(event.target.value)))} /></label>}\n <div className=\"hjm-color-picker__preview\" aria-hidden=\"true\" style={{ backgroundColor: color }} />\n {palette.length > 0 && <div className=\"hjm-color-picker__presets\">{palette.map(preset => <button type=\"button\" key={preset} aria-pressed={preset === color} onClick={() => emit(preset)}><span aria-hidden=\"true\" className=\"hjm-color-picker__swatch\" style={{ backgroundColor: preset }} /><bdi dir=\"ltr\">{preset}</bdi></button>)}</div>}\n </fieldset>;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { type ReactNode, type RefObject } from "react";
2
+ export type ContentTransitionProps = {
3
+ stateKey: string;
4
+ children: ReactNode;
5
+ motion?: "system" | "none";
6
+ /** Host chooses a meaningful focus destination, e.g. the new panel heading. */
7
+ focusTarget?: RefObject<HTMLElement | null>;
8
+ };
9
+ /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
10
+ * No exiting interactive copy: it would duplicate fields and focus targets. See THIRD_PARTY_NOTICES. */
11
+ export declare function ContentTransition({ stateKey, children, motion: preference, focusTarget }: ContentTransitionProps): import("react").JSX.Element;
12
+ export type TextTransitionProps = {
13
+ text: string;
14
+ motion?: "system" | "none";
15
+ };
16
+ /** Whole-text fade preserves graphemes, text wrapping, selection and a single spoken value. */
17
+ export declare function TextTransition({ text, motion: preference }: TextTransitionProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=content-transition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-transition.d.ts","sourceRoot":"","sources":["../src/content-transition.tsx"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI7F,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAClE,+EAA+E;IAC/E,WAAW,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC7C,CAAC;AACF;wGACwG;AACxG,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAqB,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BAmB3H;AACD,MAAM,MAAM,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;CAAE,CAAC;AAC/E,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,mBAAmB,+BAE/E"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { motion } from "framer-motion";
3
+ import { useCallback, useRef, useLayoutEffect } from "react";
4
+ import { easing, motion as timing } from "@hjmds/design-contracts/foundations";
5
+ import { useHjmTheme } from "./provider.js";
6
+ /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
7
+ * No exiting interactive copy: it would duplicate fields and focus targets. See THIRD_PARTY_NOTICES. */
8
+ export function ContentTransition({ stateKey, children, motion: preference = "system", focusTarget }) {
9
+ const { environment } = useHjmTheme();
10
+ const enabled = preference !== "none" && !environment.reducedMotion;
11
+ const first = useRef(true);
12
+ const host = useRef(null);
13
+ const restore = useRef(false);
14
+ const capturePanel = useCallback((node) => {
15
+ // The keyed node detaches before DOM removal. Parent effect cleanup runs too
16
+ // late: the browser has already moved focus to body by then.
17
+ if (!node)
18
+ restore.current = Boolean(host.current?.contains(document.activeElement));
19
+ host.current = node;
20
+ }, []);
21
+ useLayoutEffect(() => {
22
+ if (restore.current)
23
+ focusTarget?.current?.focus();
24
+ restore.current = false;
25
+ first.current = false;
26
+ }, [stateKey, focusTarget]);
27
+ return _jsx("div", { children: _jsx(motion.div, { ref: capturePanel, initial: enabled && !first.current ? { opacity: 0 } : false, animate: { opacity: 1 }, transition: { duration: enabled ? timing.normal / 1000 : 0, ease: [...easing.enter] }, children: children }, stateKey) });
28
+ }
29
+ /** Whole-text fade preserves graphemes, text wrapping, selection and a single spoken value. */
30
+ export function TextTransition({ text, motion: preference }) {
31
+ return _jsx(ContentTransition, { stateKey: text, ...(preference ? { motion: preference } : {}), children: text });
32
+ }
33
+ //# sourceMappingURL=content-transition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-transition.js","sourceRoot":"","sources":["../src/content-transition.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAkC,MAAM,OAAO,CAAC;AAC7F,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAO5C;wGACwG;AACxG,MAAM,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,QAAQ,EAAE,WAAW,EAA0B;IAC1H,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,UAAU,KAAK,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QAC/D,6EAA6E;QAC7E,6DAA6D;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO,CAAC,OAAO;YAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACnD,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;QACxB,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;IACxB,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5B,OAAO,wBAAK,KAAC,MAAM,CAAC,GAAG,IAAC,GAAG,EAAE,YAAY,EAAiB,OAAO,EAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EACnH,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,YAAG,QAAQ,IAD3E,QAAQ,CACiF,GAAM,CAAC;AAClJ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAuB;IAC9E,OAAO,KAAC,iBAAiB,IAAC,QAAQ,EAAE,IAAI,KAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAAG,IAAI,GAAqB,CAAC;AACvH,CAAC","sourcesContent":["import { motion } from \"framer-motion\";\nimport { useCallback, useRef, useLayoutEffect, type ReactNode, type RefObject } from \"react\";\nimport { easing, motion as timing } from \"@hjmds/design-contracts/foundations\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type ContentTransitionProps = {\n stateKey: string; children: ReactNode; motion?: \"system\" | \"none\";\n /** Host chooses a meaningful focus destination, e.g. the new panel heading. */\n focusTarget?: RefObject<HTMLElement | null>;\n};\n/** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.\n * No exiting interactive copy: it would duplicate fields and focus targets. See THIRD_PARTY_NOTICES. */\nexport function ContentTransition({ stateKey, children, motion: preference = \"system\", focusTarget }: ContentTransitionProps) {\n const { environment } = useHjmTheme();\n const enabled = preference !== \"none\" && !environment.reducedMotion;\n const first = useRef(true);\n const host = useRef<HTMLDivElement>(null);\n const restore = useRef(false);\n const capturePanel = useCallback((node: HTMLDivElement | null) => {\n // The keyed node detaches before DOM removal. Parent effect cleanup runs too\n // late: the browser has already moved focus to body by then.\n if (!node) restore.current = Boolean(host.current?.contains(document.activeElement));\n host.current = node;\n }, []);\n useLayoutEffect(() => {\n if (restore.current) focusTarget?.current?.focus();\n restore.current = false;\n first.current = false;\n }, [stateKey, focusTarget]);\n return <div><motion.div ref={capturePanel} key={stateKey} initial={enabled && !first.current ? { opacity: 0 } : false}\n animate={{ opacity: 1 }} transition={{ duration: enabled ? timing.normal / 1000 : 0, ease: [...easing.enter] }}>{children}</motion.div></div>;\n}\nexport type TextTransitionProps = { text: string; motion?: \"system\" | \"none\" };\n/** Whole-text fade preserves graphemes, text wrapping, selection and a single spoken value. */\nexport function TextTransition({ text, motion: preference }: TextTransitionProps) {\n return <ContentTransition stateKey={text} {...(preference ? { motion: preference } : {})}>{text}</ContentTransition>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"date-picker.d.ts","sourceRoot":"","sources":["../src/date-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC9B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,cAAc,EACpB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAY,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAGrF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,CAAC,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC;IACxD,UAAU,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB,EAAE,6BAA6B,CAAC,OAAO,CAAC,CAAC;IAC9D,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClG,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,OAAO,EAAE,EAClC,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,UAAU,EACV,UAAU,EACV,IAAe,EACf,WAAW,EACX,KAAK,EACL,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,GACV,EAAE,eAAe,CAAC,OAAO,CAAC,+BAyF1B"}
1
+ {"version":3,"file":"date-picker.d.ts","sourceRoot":"","sources":["../src/date-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC9B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,cAAc,EACpB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAY,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAKrF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,CAAC,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC;IACxD,UAAU,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB,EAAE,6BAA6B,CAAC,OAAO,CAAC,CAAC;IAC9D,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClG,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,OAAO,EAAE,EAClC,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,UAAU,EACV,UAAU,EACV,IAAe,EACf,WAAW,EACX,KAAK,EACL,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,GACV,EAAE,eAAe,CAAC,OAAO,CAAC,+BA6H1B"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import {} from "@hjmds/design-contracts/components/calendar";
3
3
  import { resolveDatePickerTriggerText, validateDatePickerDescriptor, } from "@hjmds/design-contracts/components/date-picker";
4
- import { useEffect, useId, useRef, useState, } from "react";
4
+ import { useEffect, useId, useLayoutEffect, useRef, useState, } from "react";
5
5
  import { Calendar } from "./calendar.js";
6
6
  import { classNames } from "./internal.js";
7
+ const popoverViewportInset = 16;
7
8
  /** Single-date field with an anchored, focus-bearing calendar dialog. */
8
9
  export function DatePicker({ descriptor, monthLabel, composeAccessibleName, previousMonth, nextMonth, clearLabel, closeLabel, size = "medium", description, error, renderCellContent, onNavigateBeyondGrid, className, }) {
9
10
  validateDatePickerDescriptor(descriptor);
@@ -51,6 +52,43 @@ export function DatePicker({ descriptor, monthLabel, composeAccessibleName, prev
51
52
  document.removeEventListener("pointerdown", outside);
52
53
  };
53
54
  }, [open]);
55
+ // Keep the popover inside the viewport (2026-09-30 audit WR-0930-3): shift inline, flip up
56
+ // when short. Not portaled: Calendar autoFocus runs before a portal popup is measured.
57
+ useLayoutEffect(() => {
58
+ const popover = dialogRef.current;
59
+ if (!open || !popover)
60
+ return;
61
+ const place = () => {
62
+ const viewport = window.visualViewport;
63
+ const left = (viewport?.offsetLeft ?? 0) + popoverViewportInset;
64
+ const right = (viewport?.offsetLeft ?? 0) + (viewport?.width ?? document.documentElement.clientWidth) - popoverViewportInset;
65
+ // Layout offset, not translate: mobile Chromium widened the page for a translated box.
66
+ popover.style.insetInlineStart = "";
67
+ popover.style.maxInlineSize = `${Math.max(0, right - left)}px`;
68
+ const rect = popover.getBoundingClientRect();
69
+ let shift = rect.right > right ? Math.floor(right - rect.right) : 0;
70
+ if (rect.left + shift < left)
71
+ shift = Math.ceil(left - rect.left);
72
+ const logical = getComputedStyle(popover).direction === "rtl" ? -shift : shift;
73
+ popover.style.insetInlineStart = shift === 0 ? "" : `${logical}px`;
74
+ popover.style.insetBlockStart = "";
75
+ popover.style.insetBlockEnd = "";
76
+ const box = popover.getBoundingClientRect();
77
+ const bottom = (viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight) - popoverViewportInset;
78
+ const anchor = popover.parentElement?.getBoundingClientRect();
79
+ if (anchor && box.bottom > bottom && anchor.top - box.height - popoverViewportInset >= (viewport?.offsetTop ?? 0)) {
80
+ popover.style.insetBlockStart = "auto";
81
+ popover.style.insetBlockEnd = "calc(100% + var(--hjm-space-xs))";
82
+ }
83
+ };
84
+ place();
85
+ window.addEventListener("resize", place);
86
+ window.visualViewport?.addEventListener("resize", place);
87
+ return () => {
88
+ window.removeEventListener("resize", place);
89
+ window.visualViewport?.removeEventListener("resize", place);
90
+ };
91
+ }, [open]);
54
92
  // The outer field owns selection; the shared grid receives only its resolved controlled value.
55
93
  const { defaultSelectedDate: _defaultSelectedDate, ...calendarDescriptor } = descriptor;
56
94
  const calendarGrid = descriptor.disabled || descriptor.readOnly
@@ -1 +1 @@
1
- {"version":3,"file":"date-picker.js","sourceRoot":"","sources":["../src/date-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n type ComposeCalendarAccessibleName,\n type ResolvedCalendarDateCell,\n} from \"@hjmds/design-contracts/components/calendar\";\nimport {\n resolveDatePickerTriggerText,\n validateDatePickerDescriptor,\n type DatePickerDescriptor,\n type DatePickerOpenChangeReason,\n type DatePickerSize,\n} from \"@hjmds/design-contracts/components/date-picker\";\nimport {\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport { Calendar, type CalendarOverflow, type CalendarHandle } from \"./calendar.js\";\nimport { classNames } from \"./internal.js\";\n\nexport type DatePickerMonthAction = Readonly<{ month: string; label: string }>;\n\nexport type DatePickerProps<Content = unknown> = Readonly<{\n descriptor: DatePickerDescriptor<Content>;\n monthLabel: string;\n composeAccessibleName: ComposeCalendarAccessibleName<Content>;\n previousMonth?: DatePickerMonthAction;\n nextMonth?: DatePickerMonthAction;\n clearLabel: string;\n closeLabel: string;\n size?: DatePickerSize;\n description?: ReactNode;\n error?: ReactNode;\n onNavigateBeyondGrid?: (detail: CalendarOverflow, focusDate: CalendarHandle[\"focusDate\"]) => void;\n renderCellContent?: (cell: ResolvedCalendarDateCell<Content>) => ReactNode;\n className?: string;\n}>;\n\n/** Single-date field with an anchored, focus-bearing calendar dialog. */\nexport function DatePicker<Content>({\n descriptor,\n monthLabel,\n composeAccessibleName,\n previousMonth,\n nextMonth,\n clearLabel,\n closeLabel,\n size = \"medium\",\n description,\n error,\n renderCellContent,\n onNavigateBeyondGrid,\n className,\n}: DatePickerProps<Content>) {\n validateDatePickerDescriptor(descriptor);\n const generatedId = useId().replaceAll(\":\", \"\");\n const dialogId = `hjm-date-picker-${generatedId}`;\n const triggerRef = useRef<HTMLButtonElement>(null);\n const dialogRef = useRef<HTMLDivElement>(null);\n const controlledOpen = descriptor.open !== undefined;\n const [internalOpen, setInternalOpen] = useState(descriptor.defaultOpen ?? false);\n const open = controlledOpen ? descriptor.open === true : internalOpen;\n const controlledSelection = descriptor.selectedDate !== undefined;\n const [internalSelection, setInternalSelection] = useState(\n descriptor.defaultSelectedDate ?? null,\n );\n const selectedDate = controlledSelection ? descriptor.selectedDate ?? null : internalSelection;\n\n const requestOpen = (next: boolean, reason: DatePickerOpenChangeReason) => {\n if (!controlledOpen) setInternalOpen(next);\n descriptor.onOpenChange?.(next, reason);\n };\n const commit = (date: string | null, reason: \"activate\" | \"clear\") => {\n // A controlled open field can become read-only while its grid remains mounted.\n if (descriptor.disabled || descriptor.readOnly) return;\n if (!controlledSelection) setInternalSelection(date);\n descriptor.onSelectionChange?.(date, reason);\n requestOpen(false, reason === \"activate\" ? \"selection\" : \"clear\");\n };\n\n const wasOpenRef = useRef(open);\n useEffect(() => {\n const wasOpen = wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n if (wasOpen) triggerRef.current?.focus();\n return;\n }\n const outside = (event: PointerEvent) => {\n const target = event.target as Node;\n if (!dialogRef.current?.contains(target) && !triggerRef.current?.contains(target)) {\n requestOpen(false, \"outside\");\n }\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => {\n document.removeEventListener(\"pointerdown\", outside);\n };\n }, [open]);\n\n // The outer field owns selection; the shared grid receives only its resolved controlled value.\n const { defaultSelectedDate: _defaultSelectedDate, ...calendarDescriptor } = descriptor;\n const calendarGrid = descriptor.disabled || descriptor.readOnly\n ? { ...descriptor.grid, cells: descriptor.grid.cells.map((cell) => cell.date ? { ...cell, disabled: true } : cell) }\n : descriptor.grid;\n const label = descriptor.label ?? descriptor.accessibilityLabel;\n const triggerText = resolveDatePickerTriggerText(descriptor);\n return (\n <div className={classNames(\"hjm-date-picker\", className)} data-invalid={descriptor.invalid || error !== undefined || undefined} data-size={size}>\n {descriptor.label === undefined ? null : <span className=\"hjm-date-picker__label\">{descriptor.label}</span>}\n <div className=\"hjm-date-picker__anchor\">\n <button\n aria-controls={open ? dialogId : undefined}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n aria-label={descriptor.accessibilityLabel}\n className=\"hjm-date-picker__trigger\"\n disabled={descriptor.disabled}\n onClick={() => !descriptor.readOnly && requestOpen(!open, \"trigger\")}\n ref={triggerRef}\n type=\"button\"\n >\n <span aria-hidden=\"true\">▣</span>\n <span data-placeholder={descriptor.displayValue === null || undefined}>{triggerText}</span>\n </button>\n {selectedDate === null ? null : (\n <button aria-label={clearLabel} className=\"hjm-date-picker__clear\" disabled={descriptor.disabled || descriptor.readOnly} onClick={() => commit(null, \"clear\")} type=\"button\">×</button>\n )}\n {open ? (\n <div aria-label={label} className=\"hjm-date-picker__popover\" id={dialogId} ref={dialogRef} role=\"dialog\" onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); requestOpen(false, \"escape\"); } }}>\n <button aria-label={closeLabel} className=\"hjm-date-picker__calendar-close\" onClick={() => requestOpen(false, \"trigger\")} type=\"button\">×</button>\n <Calendar descriptor={{ ...calendarDescriptor, grid: calendarGrid, monthLabel, selectedDate, onSelectionChange: (date) => commit(date, \"activate\") }}\n composeAccessibleName={composeAccessibleName} size={size} autoFocus\n {...(previousMonth ? { previousMonth } : {})} {...(nextMonth ? { nextMonth } : {})}\n {...(renderCellContent ? { renderCellContent } : {})} {...(onNavigateBeyondGrid ? { onNavigateBeyondGrid } : {})} />\n </div>\n ) : null}\n </div>\n {description === undefined ? null : <span className=\"hjm-date-picker__description\">{description}</span>}\n {error === undefined ? null : <span className=\"hjm-date-picker__error\" role=\"alert\">{error}</span>}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"date-picker.js","sourceRoot":"","sources":["../src/date-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n type ComposeCalendarAccessibleName,\n type ResolvedCalendarDateCell,\n} from \"@hjmds/design-contracts/components/calendar\";\nimport {\n resolveDatePickerTriggerText,\n validateDatePickerDescriptor,\n type DatePickerDescriptor,\n type DatePickerOpenChangeReason,\n type DatePickerSize,\n} from \"@hjmds/design-contracts/components/date-picker\";\nimport {\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport { Calendar, type CalendarOverflow, type CalendarHandle } from \"./calendar.js\";\nimport { classNames } from \"./internal.js\";\n\nconst popoverViewportInset = 16;\n\nexport type DatePickerMonthAction = Readonly<{ month: string; label: string }>;\n\nexport type DatePickerProps<Content = unknown> = Readonly<{\n descriptor: DatePickerDescriptor<Content>;\n monthLabel: string;\n composeAccessibleName: ComposeCalendarAccessibleName<Content>;\n previousMonth?: DatePickerMonthAction;\n nextMonth?: DatePickerMonthAction;\n clearLabel: string;\n closeLabel: string;\n size?: DatePickerSize;\n description?: ReactNode;\n error?: ReactNode;\n onNavigateBeyondGrid?: (detail: CalendarOverflow, focusDate: CalendarHandle[\"focusDate\"]) => void;\n renderCellContent?: (cell: ResolvedCalendarDateCell<Content>) => ReactNode;\n className?: string;\n}>;\n\n/** Single-date field with an anchored, focus-bearing calendar dialog. */\nexport function DatePicker<Content>({\n descriptor,\n monthLabel,\n composeAccessibleName,\n previousMonth,\n nextMonth,\n clearLabel,\n closeLabel,\n size = \"medium\",\n description,\n error,\n renderCellContent,\n onNavigateBeyondGrid,\n className,\n}: DatePickerProps<Content>) {\n validateDatePickerDescriptor(descriptor);\n const generatedId = useId().replaceAll(\":\", \"\");\n const dialogId = `hjm-date-picker-${generatedId}`;\n const triggerRef = useRef<HTMLButtonElement>(null);\n const dialogRef = useRef<HTMLDivElement>(null);\n const controlledOpen = descriptor.open !== undefined;\n const [internalOpen, setInternalOpen] = useState(descriptor.defaultOpen ?? false);\n const open = controlledOpen ? descriptor.open === true : internalOpen;\n const controlledSelection = descriptor.selectedDate !== undefined;\n const [internalSelection, setInternalSelection] = useState(\n descriptor.defaultSelectedDate ?? null,\n );\n const selectedDate = controlledSelection ? descriptor.selectedDate ?? null : internalSelection;\n\n const requestOpen = (next: boolean, reason: DatePickerOpenChangeReason) => {\n if (!controlledOpen) setInternalOpen(next);\n descriptor.onOpenChange?.(next, reason);\n };\n const commit = (date: string | null, reason: \"activate\" | \"clear\") => {\n // A controlled open field can become read-only while its grid remains mounted.\n if (descriptor.disabled || descriptor.readOnly) return;\n if (!controlledSelection) setInternalSelection(date);\n descriptor.onSelectionChange?.(date, reason);\n requestOpen(false, reason === \"activate\" ? \"selection\" : \"clear\");\n };\n\n const wasOpenRef = useRef(open);\n useEffect(() => {\n const wasOpen = wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n if (wasOpen) triggerRef.current?.focus();\n return;\n }\n const outside = (event: PointerEvent) => {\n const target = event.target as Node;\n if (!dialogRef.current?.contains(target) && !triggerRef.current?.contains(target)) {\n requestOpen(false, \"outside\");\n }\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => {\n document.removeEventListener(\"pointerdown\", outside);\n };\n }, [open]);\n\n // Keep the popover inside the viewport (2026-09-30 audit WR-0930-3): shift inline, flip up\n // when short. Not portaled: Calendar autoFocus runs before a portal popup is measured.\n useLayoutEffect(() => {\n const popover = dialogRef.current;\n if (!open || !popover) return;\n const place = () => {\n const viewport = window.visualViewport;\n const left = (viewport?.offsetLeft ?? 0) + popoverViewportInset;\n const right = (viewport?.offsetLeft ?? 0) + (viewport?.width ?? document.documentElement.clientWidth) - popoverViewportInset;\n // Layout offset, not translate: mobile Chromium widened the page for a translated box.\n popover.style.insetInlineStart = \"\";\n popover.style.maxInlineSize = `${Math.max(0, right - left)}px`;\n const rect = popover.getBoundingClientRect();\n let shift = rect.right > right ? Math.floor(right - rect.right) : 0;\n if (rect.left + shift < left) shift = Math.ceil(left - rect.left);\n const logical = getComputedStyle(popover).direction === \"rtl\" ? -shift : shift;\n popover.style.insetInlineStart = shift === 0 ? \"\" : `${logical}px`;\n popover.style.insetBlockStart = \"\";\n popover.style.insetBlockEnd = \"\";\n const box = popover.getBoundingClientRect();\n const bottom = (viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight) - popoverViewportInset;\n const anchor = popover.parentElement?.getBoundingClientRect();\n if (anchor && box.bottom > bottom && anchor.top - box.height - popoverViewportInset >= (viewport?.offsetTop ?? 0)) {\n popover.style.insetBlockStart = \"auto\";\n popover.style.insetBlockEnd = \"calc(100% + var(--hjm-space-xs))\";\n }\n };\n place();\n window.addEventListener(\"resize\", place);\n window.visualViewport?.addEventListener(\"resize\", place);\n return () => {\n window.removeEventListener(\"resize\", place);\n window.visualViewport?.removeEventListener(\"resize\", place);\n };\n }, [open]);\n\n // The outer field owns selection; the shared grid receives only its resolved controlled value.\n const { defaultSelectedDate: _defaultSelectedDate, ...calendarDescriptor } = descriptor;\n const calendarGrid = descriptor.disabled || descriptor.readOnly\n ? { ...descriptor.grid, cells: descriptor.grid.cells.map((cell) => cell.date ? { ...cell, disabled: true } : cell) }\n : descriptor.grid;\n const label = descriptor.label ?? descriptor.accessibilityLabel;\n const triggerText = resolveDatePickerTriggerText(descriptor);\n return (\n <div className={classNames(\"hjm-date-picker\", className)} data-invalid={descriptor.invalid || error !== undefined || undefined} data-size={size}>\n {descriptor.label === undefined ? null : <span className=\"hjm-date-picker__label\">{descriptor.label}</span>}\n <div className=\"hjm-date-picker__anchor\">\n <button\n aria-controls={open ? dialogId : undefined}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n aria-label={descriptor.accessibilityLabel}\n className=\"hjm-date-picker__trigger\"\n disabled={descriptor.disabled}\n onClick={() => !descriptor.readOnly && requestOpen(!open, \"trigger\")}\n ref={triggerRef}\n type=\"button\"\n >\n <span aria-hidden=\"true\">▣</span>\n <span data-placeholder={descriptor.displayValue === null || undefined}>{triggerText}</span>\n </button>\n {selectedDate === null ? null : (\n <button aria-label={clearLabel} className=\"hjm-date-picker__clear\" disabled={descriptor.disabled || descriptor.readOnly} onClick={() => commit(null, \"clear\")} type=\"button\">×</button>\n )}\n {open ? (\n <div aria-label={label} className=\"hjm-date-picker__popover\" id={dialogId} ref={dialogRef} role=\"dialog\" onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); requestOpen(false, \"escape\"); } }}>\n <button aria-label={closeLabel} className=\"hjm-date-picker__calendar-close\" onClick={() => requestOpen(false, \"trigger\")} type=\"button\">×</button>\n <Calendar descriptor={{ ...calendarDescriptor, grid: calendarGrid, monthLabel, selectedDate, onSelectionChange: (date) => commit(date, \"activate\") }}\n composeAccessibleName={composeAccessibleName} size={size} autoFocus\n {...(previousMonth ? { previousMonth } : {})} {...(nextMonth ? { nextMonth } : {})}\n {...(renderCellContent ? { renderCellContent } : {})} {...(onNavigateBeyondGrid ? { onNavigateBeyondGrid } : {})} />\n </div>\n ) : null}\n </div>\n {description === undefined ? null : <span className=\"hjm-date-picker__description\">{description}</span>}\n {error === undefined ? null : <span className=\"hjm-date-picker__error\" role=\"alert\">{error}</span>}\n </div>\n );\n}\n"]}