@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.
Files changed (156) hide show
  1. package/README.md +14 -0
  2. package/THIRD_PARTY_NOTICES.md +78 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +1 -1
  5. package/dist/actions.js.map +1 -1
  6. package/dist/activity-heatmap.d.ts +10 -0
  7. package/dist/activity-heatmap.d.ts.map +1 -0
  8. package/dist/activity-heatmap.js +6 -0
  9. package/dist/activity-heatmap.js.map +1 -0
  10. package/dist/advanced-display.d.ts +3 -0
  11. package/dist/advanced-display.d.ts.map +1 -1
  12. package/dist/advanced-display.js +9 -7
  13. package/dist/advanced-display.js.map +1 -1
  14. package/dist/advanced-forms.js +1 -1
  15. package/dist/advanced-forms.js.map +1 -1
  16. package/dist/auth-screen.d.ts +8 -0
  17. package/dist/auth-screen.d.ts.map +1 -1
  18. package/dist/auth-screen.js +5 -2
  19. package/dist/auth-screen.js.map +1 -1
  20. package/dist/avatar-blobatar-motion.d.ts +6 -0
  21. package/dist/avatar-blobatar-motion.d.ts.map +1 -0
  22. package/dist/avatar-blobatar-motion.js +22 -0
  23. package/dist/avatar-blobatar-motion.js.map +1 -0
  24. package/dist/avatar-blobatar.d.ts +6 -0
  25. package/dist/avatar-blobatar.d.ts.map +1 -0
  26. package/dist/avatar-blobatar.js +10 -0
  27. package/dist/avatar-blobatar.js.map +1 -0
  28. package/dist/bottom-navigation.d.ts.map +1 -1
  29. package/dist/bottom-navigation.js +3 -3
  30. package/dist/bottom-navigation.js.map +1 -1
  31. package/dist/carousel-motion.d.ts +4 -1
  32. package/dist/carousel-motion.d.ts.map +1 -1
  33. package/dist/carousel-motion.js +12 -3
  34. package/dist/carousel-motion.js.map +1 -1
  35. package/dist/code-block.d.ts +8 -0
  36. package/dist/code-block.d.ts.map +1 -0
  37. package/dist/code-block.js +6 -0
  38. package/dist/code-block.js.map +1 -0
  39. package/dist/content-transition.d.ts +5 -2
  40. package/dist/content-transition.d.ts.map +1 -1
  41. package/dist/content-transition.js +6 -4
  42. package/dist/content-transition.js.map +1 -1
  43. package/dist/context-menu.d.ts.map +1 -1
  44. package/dist/context-menu.js +10 -11
  45. package/dist/context-menu.js.map +1 -1
  46. package/dist/data-table.d.ts.map +1 -1
  47. package/dist/data-table.js +2 -1
  48. package/dist/data-table.js.map +1 -1
  49. package/dist/duration-field.d.ts +11 -0
  50. package/dist/duration-field.d.ts.map +1 -0
  51. package/dist/duration-field.js +9 -0
  52. package/dist/duration-field.js.map +1 -0
  53. package/dist/effect-surface.d.ts +10 -0
  54. package/dist/effect-surface.d.ts.map +1 -0
  55. package/dist/effect-surface.js +57 -0
  56. package/dist/effect-surface.js.map +1 -0
  57. package/dist/evidence.d.ts +1 -1
  58. package/dist/evidence.js +1 -1
  59. package/dist/evidence.js.map +1 -1
  60. package/dist/feedback.js +1 -1
  61. package/dist/feedback.js.map +1 -1
  62. package/dist/folder-preview.d.ts +12 -0
  63. package/dist/folder-preview.d.ts.map +1 -0
  64. package/dist/folder-preview.js +7 -0
  65. package/dist/folder-preview.js.map +1 -0
  66. package/dist/forms.d.ts +3 -1
  67. package/dist/forms.d.ts.map +1 -1
  68. package/dist/forms.js +5 -3
  69. package/dist/forms.js.map +1 -1
  70. package/dist/gravity-letters.d.ts +8 -0
  71. package/dist/gravity-letters.d.ts.map +1 -0
  72. package/dist/gravity-letters.js +24 -0
  73. package/dist/gravity-letters.js.map +1 -0
  74. package/dist/grid-reveal.d.ts +9 -0
  75. package/dist/grid-reveal.d.ts.map +1 -0
  76. package/dist/grid-reveal.js +21 -0
  77. package/dist/grid-reveal.js.map +1 -0
  78. package/dist/icon-lucide.d.ts +11 -0
  79. package/dist/icon-lucide.d.ts.map +1 -0
  80. package/dist/icon-lucide.js +11 -0
  81. package/dist/icon-lucide.js.map +1 -0
  82. package/dist/inline-confirm.d.ts +14 -0
  83. package/dist/inline-confirm.d.ts.map +1 -0
  84. package/dist/inline-confirm.js +37 -0
  85. package/dist/inline-confirm.js.map +1 -0
  86. package/dist/menu-morph.d.ts.map +1 -1
  87. package/dist/menu-morph.js +9 -6
  88. package/dist/menu-morph.js.map +1 -1
  89. package/dist/menu-typeahead.d.ts +13 -0
  90. package/dist/menu-typeahead.d.ts.map +1 -0
  91. package/dist/menu-typeahead.js +17 -0
  92. package/dist/menu-typeahead.js.map +1 -0
  93. package/dist/navigation-bar.d.ts +10 -0
  94. package/dist/navigation-bar.d.ts.map +1 -0
  95. package/dist/navigation-bar.js +10 -0
  96. package/dist/navigation-bar.js.map +1 -0
  97. package/dist/navigation.d.ts +3 -3
  98. package/dist/navigation.d.ts.map +1 -1
  99. package/dist/navigation.js +63 -22
  100. package/dist/navigation.js.map +1 -1
  101. package/dist/notification-bell.d.ts +11 -0
  102. package/dist/notification-bell.d.ts.map +1 -0
  103. package/dist/notification-bell.js +27 -0
  104. package/dist/notification-bell.js.map +1 -0
  105. package/dist/overlays.d.ts +0 -2
  106. package/dist/overlays.d.ts.map +1 -1
  107. package/dist/overlays.js +5 -18
  108. package/dist/overlays.js.map +1 -1
  109. package/dist/provider-button.d.ts.map +1 -1
  110. package/dist/provider-button.js +2 -2
  111. package/dist/provider-button.js.map +1 -1
  112. package/dist/reaction-picker.d.ts +11 -0
  113. package/dist/reaction-picker.d.ts.map +1 -0
  114. package/dist/reaction-picker.js +9 -0
  115. package/dist/reaction-picker.js.map +1 -0
  116. package/dist/scroll-progress.d.ts +10 -0
  117. package/dist/scroll-progress.d.ts.map +1 -0
  118. package/dist/scroll-progress.js +34 -0
  119. package/dist/scroll-progress.js.map +1 -0
  120. package/dist/select.d.ts +0 -1
  121. package/dist/select.d.ts.map +1 -1
  122. package/dist/select.js +2 -4
  123. package/dist/select.js.map +1 -1
  124. package/dist/sidebar.d.ts +3 -0
  125. package/dist/sidebar.d.ts.map +1 -1
  126. package/dist/sidebar.js +23 -4
  127. package/dist/sidebar.js.map +1 -1
  128. package/dist/step-player.d.ts +24 -0
  129. package/dist/step-player.d.ts.map +1 -0
  130. package/dist/step-player.js +16 -0
  131. package/dist/step-player.js.map +1 -0
  132. package/dist/styles.css +94 -1
  133. package/dist/styles.layered.css +94 -1
  134. package/dist/supplemental-display.d.ts +10 -2
  135. package/dist/supplemental-display.d.ts.map +1 -1
  136. package/dist/supplemental-display.js +2 -2
  137. package/dist/supplemental-display.js.map +1 -1
  138. package/dist/supplemental-navigation.js +1 -1
  139. package/dist/supplemental-navigation.js.map +1 -1
  140. package/dist/table-sort-button.d.ts +15 -0
  141. package/dist/table-sort-button.d.ts.map +1 -0
  142. package/dist/table-sort-button.js +7 -0
  143. package/dist/table-sort-button.js.map +1 -0
  144. package/dist/task-list.d.ts +16 -0
  145. package/dist/task-list.d.ts.map +1 -0
  146. package/dist/task-list.js +17 -0
  147. package/dist/task-list.js.map +1 -0
  148. package/dist/thinking-orb.d.ts +1 -1
  149. package/dist/thinking-orb.d.ts.map +1 -1
  150. package/dist/thinking-orb.js +4 -4
  151. package/dist/thinking-orb.js.map +1 -1
  152. package/dist/voice-note.d.ts +24 -0
  153. package/dist/voice-note.d.ts.map +1 -0
  154. package/dist/voice-note.js +13 -0
  155. package/dist/voice-note.js.map +1 -0
  156. package/package.json +110 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-block.js","sourceRoot":"","sources":["../src/code-block.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,gBAAgB,EAA0B,MAAM,oCAAoC,CAAC;AAE7F,MAAM,MAAM,GAAC,EAAC,KAAK,EAAC,uBAAuB,EAAC,OAAO,EAAC,gCAAgC,EAAC,MAAM,EAAC,uBAAuB,EAAC,OAAO,EAAC,6BAA6B,EAAC,MAAM,EAAC,gCAAgC,EAAC,CAAC;AACnM,mEAAmE;AACnE,MAAM,UAAU,SAAS,CAAC,EAAC,UAAU,EAAC,GAAG,UAAU,EAAgB,IAAE,MAAM,IAAI,GAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAA,OAAO,iCAAqB,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAC,QAAQ,EAAC,CAAC,EAAC,YAAY,EAAC,sBAAsB,EAAC,UAAU,EAAC,8BAA8B,EAAC,KAAK,EAAC,uBAAuB,EAAC,QAAQ,EAAC,QAAQ,EAAC,aAAE,eAAK,KAAK,EAAE,EAAC,OAAO,EAAC,MAAM,EAAC,UAAU,EAAC,QAAQ,EAAC,cAAc,EAAC,eAAe,EAAC,GAAG,EAAC,qBAAqB,EAAC,OAAO,EAAC,qBAAqB,EAAC,aAAE,yBAAO,IAAI,CAAC,QAAQ,IAAE,IAAI,CAAC,KAAK,GAAQ,EAAC,UAAU,IAAO,EAAA,cAAK,QAAQ,EAAE,CAAC,gBAAc,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAC,MAAM,EAAC,CAAC,EAAC,OAAO,EAAC,qBAAqB,EAAC,SAAS,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,CAAC,IAAI,CAAA,CAAC,CAAA,UAAU,CAAA,CAAC,CAAA,KAAK,EAAC,YAAY,EAAC,IAAI,CAAC,IAAI,CAAA,CAAC,CAAA,UAAU,CAAA,CAAC,CAAA,SAAS,EAAC,YAAE,yBAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE,CAAA,eAAkB,KAAK,EAAE,EAAC,KAAK,EAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAE,OAAO,CAAC,EAAC,YAAG,KAAK,CAAC,IAAI,IAA9D,KAAK,CAAiE,CAAC,GAAQ,GAAM,IAAU,CAAC,CAAA,CAAC","sourcesContent":["import type {ReactNode} from 'react';\nimport {resolveCodeBlock,type CodeBlockDescriptor} from '@hjmds/design-contracts/code-block';\nexport type CodeBlockProps=CodeBlockDescriptor & Readonly<{copyAction?:ReactNode}>;\nconst colors={plain:'var(--hjm-color-text)',keyword:'var(--hjm-color-content-brand)',string:'var(--hjm-color-text)',comment:'var(--hjm-color-text-muted)',number:'var(--hjm-color-content-brand)'};\n/** Token text is rendered as React text, never executable HTML. */\nexport function CodeBlock({copyAction,...descriptor}:CodeBlockProps){const spec=resolveCodeBlock(descriptor);return <section aria-label={spec.label} style={{minWidth:0,borderRadius:'var(--hjm-radius-lg)',background:'var(--hjm-color-surface-alt)',color:'var(--hjm-color-text)',overflow:'hidden'}}><div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:'var(--hjm-space-sm)',padding:'var(--hjm-space-md)'}}><span>{spec.language??spec.label}</span>{copyAction}</div><pre tabIndex={0} aria-label={spec.label} style={{margin:0,padding:'var(--hjm-space-md)',overflowX:'auto',whiteSpace:spec.wrap?'pre-wrap':'pre',overflowWrap:spec.wrap?'anywhere':undefined}}><code>{spec.tokens.map((token,index)=><span key={index} style={{color:colors[token.tone??'plain']}}>{token.text}</span>)}</code></pre></section>;}\n"]}
@@ -1,5 +1,7 @@
1
+ import { type ContentTransitionPreset } from "@hjmds/design-contracts/content-transition";
1
2
  import { type ReactNode, type RefObject } from "react";
2
3
  export type ContentTransitionProps = {
4
+ preset?: ContentTransitionPreset;
3
5
  stateKey: string;
4
6
  children: ReactNode;
5
7
  motion?: "system" | "none";
@@ -8,11 +10,12 @@ export type ContentTransitionProps = {
8
10
  };
9
11
  /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
10
12
  * 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;
13
+ export declare function ContentTransition({ stateKey, children, motion: preference, preset, focusTarget }: ContentTransitionProps): import("react").JSX.Element;
12
14
  export type TextTransitionProps = {
15
+ preset?: ContentTransitionPreset;
13
16
  text: string;
14
17
  motion?: "system" | "none";
15
18
  };
16
19
  /** 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;
20
+ export declare function TextTransition({ text, motion: preference, preset }: TextTransitionProps): import("react").JSX.Element;
18
21
  //# sourceMappingURL=content-transition.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"content-transition.d.ts","sourceRoot":"","sources":["../src/content-transition.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAEpH,OAAO,EAAwC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI7F,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,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,MAAe,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BAoB5I;AACD,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;CAAE,CAAC;AACjH,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,mBAAmB,+BAEvF"}
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { resolveContentTransition } from "@hjmds/design-contracts/content-transition";
2
3
  import { motion } from "framer-motion";
3
4
  import { useCallback, useRef, useLayoutEffect } from "react";
4
5
  import { easing, motion as timing } from "@hjmds/design-contracts/foundations";
5
6
  import { useHjmTheme } from "./provider.js";
6
7
  /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
7
8
  * 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
+ export function ContentTransition({ stateKey, children, motion: preference = "system", preset = "fade", focusTarget }) {
9
10
  const { environment } = useHjmTheme();
11
+ const from = resolveContentTransition(preset, environment.direction);
10
12
  const enabled = preference !== "none" && !environment.reducedMotion;
11
13
  const first = useRef(true);
12
14
  const host = useRef(null);
@@ -24,10 +26,10 @@ export function ContentTransition({ stateKey, children, motion: preference = "sy
24
26
  restore.current = false;
25
27
  first.current = false;
26
28
  }, [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) });
29
+ return _jsx("div", { children: _jsx(motion.div, { ref: capturePanel, initial: enabled && !first.current ? { opacity: from.opacity, x: from.translateX, y: from.translateY, scale: from.scale } : false, animate: { opacity: 1, x: 0, y: 0, scale: 1 }, transition: { duration: enabled ? timing.normal / 1000 : 0, ease: [...easing.enter] }, children: children }, stateKey) });
28
30
  }
29
31
  /** 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
+ export function TextTransition({ text, motion: preference, preset }) {
33
+ return _jsx(ContentTransition, { stateKey: text, ...(preset ? { preset } : {}), ...(preference ? { motion: preference } : {}), children: text });
32
34
  }
33
35
  //# sourceMappingURL=content-transition.js.map
@@ -1 +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
+ {"version":3,"file":"content-transition.js","sourceRoot":"","sources":["../src/content-transition.tsx"],"names":[],"mappings":"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","sourcesContent":["import { resolveContentTransition, type ContentTransitionPreset } from \"@hjmds/design-contracts/content-transition\";\nimport { 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 preset?: ContentTransitionPreset;\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\", preset = \"fade\", focusTarget }: ContentTransitionProps) {\n const { environment } = useHjmTheme();\n const from = resolveContentTransition(preset, environment.direction);\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: from.opacity, x: from.translateX, y: from.translateY, scale: from.scale } : false}\n animate={{ opacity: 1, x: 0, y: 0, scale: 1 }} transition={{ duration: enabled ? timing.normal / 1000 : 0, ease: [...easing.enter] }}>{children}</motion.div></div>;\n}\nexport type TextTransitionProps = { preset?: ContentTransitionPreset; 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, preset }: TextTransitionProps) {\n return <ContentTransition stateKey={text} {...(preset ? { preset } : {})} {...(preference ? { motion: preference } : {})}>{text}</ContentTransition>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../src/context-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,yBAAyB,EAE/B,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,MAAM,MAAM,gBAAgB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACnE,iFAAiF;IACjF,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC;IACjD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAIH,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EACvD,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACV,EAAE,gBAAgB,CAAC,GAAG,CAAC,+BAyKvB"}
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../src/context-menu.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,yBAAyB,EAE/B,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,MAAM,MAAM,gBAAgB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACnE,iFAAiF;IACjF,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC;IACjD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAIH,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EACvD,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACV,EAAE,gBAAgB,CAAC,GAAG,CAAC,+BAwKvB"}
@@ -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 { resolveContextMenuAnchor, } from "@hjmds/design-contracts/components/context-menu";
3
4
  import { menuRecipe } from "@hjmds/design-contracts/recipes";
4
5
  import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, } from "react";
@@ -14,8 +15,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
14
15
  const [anchor, setAnchor] = useState(null);
15
16
  const [activeIndex, setActiveIndex] = useState(0);
16
17
  const longPress = useRef(undefined);
17
- const typeahead = useRef("");
18
- const typeaheadTimer = useRef(undefined);
18
+ const typeahead = useRef({ value: "", time: 0 });
19
19
  const enabled = items.filter((item) => !item.disabled);
20
20
  const open = useCallback((reason, pointer) => {
21
21
  const activeElement = document.activeElement;
@@ -25,6 +25,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
25
25
  const rect = hostRef.current?.getBoundingClientRect() ?? null;
26
26
  setAnchor(resolveContextMenuAnchor(reason, pointer, rect === null ? null : { left: rect.left, bottom: rect.bottom }));
27
27
  setActiveIndex(0);
28
+ typeahead.current = { value: "", time: 0 };
28
29
  }, []);
29
30
  const setMenuRef = useCallback((node) => {
30
31
  menuRef.current = node;
@@ -65,15 +66,13 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
65
66
  setActiveIndex(event.key === "Home" ? 0 : Math.max(enabled.length - 1, 0));
66
67
  return;
67
68
  }
68
- if (event.key.length === 1 && event.key !== " ") {
69
- // Typeahead matches `textValue`, not the rendered label: a label may hold
70
- // markup or a formatted number that nobody would type.
71
- const query = (typeahead.current += event.key.toLowerCase());
72
- clearTimeout(typeaheadTimer.current);
73
- typeaheadTimer.current = setTimeout(() => { typeahead.current = ""; }, 700);
74
- const match = enabled.findIndex((item) => item.textValue.toLowerCase().startsWith(query));
75
- if (match >= 0)
76
- setActiveIndex(match);
69
+ if (event.key.length === 1 && event.key !== " " && !event.ctrlKey && !event.metaKey && !event.altKey) {
70
+ const result = resolveMenuTypeahead(enabled, activeIndex, event.key, typeahead.current, Date.now());
71
+ typeahead.current = result.state;
72
+ if (result.index !== undefined) {
73
+ event.preventDefault();
74
+ setActiveIndex(result.index);
75
+ }
77
76
  return;
78
77
  }
79
78
  if (event.key === "Enter" || event.key === " ") {
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../src/context-menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveContextMenuAnchor,\n type ContextMenuAnchor,\n type ContextMenuItemDescriptor,\n type ContextMenuOpenReason,\n} from \"@hjmds/design-contracts/components/context-menu\";\nimport { menuRecipe } from \"@hjmds/design-contracts/recipes\";\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { classNames } from \"./internal.js\";\nimport { HjmPortal, getModalLayer } from \"./modal.js\";\n\nexport type ContextMenuProps<Key extends string = string> = Readonly<{\n /** The region the menu belongs to; right-click and long-press are bound here. */\n children: ReactNode;\n items: readonly ContextMenuItemDescriptor<Key>[];\n accessibilityLabel: string;\n onAction: (id: Key) => void;\n className?: string;\n}>;\n\nconst longPressDelay = 500;\n\nexport function ContextMenu<Key extends string = string>({\n children,\n items,\n accessibilityLabel,\n onAction,\n className,\n}: ContextMenuProps<Key>) {\n const hostRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [menuMounted, setMenuMounted] = useState(false);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const menuId = useId();\n const [anchor, setAnchor] = useState<ContextMenuAnchor | null>(null);\n const [activeIndex, setActiveIndex] = useState(0);\n const longPress = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const typeahead = useRef(\"\");\n const typeaheadTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const enabled = items.filter((item) => !item.disabled);\n\n const open = useCallback((reason: ContextMenuOpenReason, pointer: ContextMenuAnchor | null) => {\n const activeElement = document.activeElement;\n returnFocusRef.current = activeElement instanceof HTMLElement && hostRef.current?.contains(activeElement)\n ? activeElement\n : hostRef.current;\n const rect = hostRef.current?.getBoundingClientRect() ?? null;\n setAnchor(resolveContextMenuAnchor(\n reason,\n pointer,\n rect === null ? null : { left: rect.left, bottom: rect.bottom },\n ));\n setActiveIndex(0);\n }, []);\n\n const setMenuRef = useCallback((node: HTMLDivElement | null) => {\n menuRef.current = node;\n setMenuMounted(node !== null);\n }, []);\n\n useLayoutEffect(() => {\n if (anchor === null || !menuMounted || menuRef.current === null) return;\n menuRef.current.focus();\n const rect = menuRef.current.getBoundingClientRect();\n const x = Math.min(Math.max(anchor.x, 0), Math.max(window.innerWidth - rect.width, 0));\n const y = Math.min(Math.max(anchor.y, 0), Math.max(window.innerHeight - rect.height, 0));\n // The contract promises viewport coordinates; post-render measurement keeps long labels onscreen, where guessed dimensions could not account for wrapping.\n if (x !== anchor.x || y !== anchor.y) setAnchor({ x, y });\n }, [anchor, menuMounted]);\n\n useEffect(() => {\n if (anchor === null) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n setAnchor(null);\n // Focus goes back where the menu was opened from, not to the body.\n returnFocusRef.current?.focus();\n return;\n }\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((previous) => {\n const next = event.key === \"ArrowDown\" ? previous + 1 : previous - 1;\n return (next + enabled.length) % Math.max(enabled.length, 1);\n });\n return;\n }\n if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n setActiveIndex(event.key === \"Home\" ? 0 : Math.max(enabled.length - 1, 0));\n return;\n }\n if (event.key.length === 1 && event.key !== \" \") {\n // Typeahead matches `textValue`, not the rendered label: a label may hold\n // markup or a formatted number that nobody would type.\n const query = (typeahead.current += event.key.toLowerCase());\n clearTimeout(typeaheadTimer.current);\n typeaheadTimer.current = setTimeout(() => { typeahead.current = \"\"; }, 700);\n const match = enabled.findIndex((item) => item.textValue.toLowerCase().startsWith(query));\n if (match >= 0) setActiveIndex(match);\n return;\n }\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n const item = enabled[activeIndex];\n if (item) { onAction(item.id as Key); setAnchor(null); returnFocusRef.current?.focus(); }\n return;\n }\n if (event.key === \"Tab\") {\n setAnchor(null);\n }\n };\n const onPointerDown = (event: PointerEvent) => {\n if (!(event.target instanceof Node) || !menuRef.current?.contains(event.target)) {\n setAnchor(null);\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"pointerdown\", onPointerDown);\n };\n }, [anchor, activeIndex, enabled, onAction]);\n\n return (\n <div\n ref={hostRef}\n tabIndex={0}\n className={classNames(\"hjm-context-menu-host\", className)}\n onContextMenu={(event) => {\n event.preventDefault();\n open(\"pointer\", { x: event.clientX, y: event.clientY });\n }}\n onKeyDown={(event) => {\n // Shift+F10 and the Menu key are how a keyboard opens a context menu;\n // without them the feature does not exist without a pointer.\n if ((event.shiftKey && event.key === \"F10\") || event.key === \"ContextMenu\") {\n event.preventDefault();\n open(\"keyboard\", null);\n }\n }}\n onPointerDown={(event) => {\n if (event.pointerType !== \"touch\") return;\n longPress.current = setTimeout(() => open(\"longPress\", { x: event.clientX, y: event.clientY }), longPressDelay);\n }}\n onPointerUp={() => { if (longPress.current !== undefined) clearTimeout(longPress.current); }}\n onPointerCancel={() => { if (longPress.current !== undefined) clearTimeout(longPress.current); }}\n >\n {children}\n {anchor !== null ? (\n <HjmPortal>\n <div\n role=\"menu\"\n id={menuId}\n ref={setMenuRef}\n tabIndex={-1}\n aria-activedescendant={enabled[activeIndex] ? `${menuId}-item-${activeIndex}` : undefined}\n aria-label={accessibilityLabel}\n className=\"hjm-context-menu\"\n style={{\n top: anchor.y,\n left: anchor.x,\n zIndex: getModalLayer(0),\n minInlineSize: menuRecipe.minWidth,\n } as CSSProperties}\n >\n {items.map((item) => {\n const index = enabled.findIndex((candidate) => candidate.id === item.id);\n return (\n <div\n key={item.id}\n id={index >= 0 ? `${menuId}-item-${index}` : undefined}\n role=\"menuitem\"\n aria-disabled={item.disabled || undefined}\n data-active={index === activeIndex && !item.disabled ? \"\" : undefined}\n data-tone={item.tone}\n className=\"hjm-context-menu__item\"\n onMouseEnter={() => { if (index >= 0) setActiveIndex(index); }}\n onClick={() => {\n if (item.disabled) return;\n onAction(item.id as Key);\n setAnchor(null);\n returnFocusRef.current?.focus();\n }}\n >\n <span>{item.label}</span>\n {item.shortcut ? <kbd className=\"hjm-context-menu__shortcut\">{item.shortcut}</kbd> : null}\n </div>\n );\n })}\n </div>\n </HjmPortal>\n ) : null}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../src/context-menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import { resolveMenuTypeahead } from \"./menu-typeahead.js\";\nimport {\n resolveContextMenuAnchor,\n type ContextMenuAnchor,\n type ContextMenuItemDescriptor,\n type ContextMenuOpenReason,\n} from \"@hjmds/design-contracts/components/context-menu\";\nimport { menuRecipe } from \"@hjmds/design-contracts/recipes\";\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { classNames } from \"./internal.js\";\nimport { HjmPortal, getModalLayer } from \"./modal.js\";\n\nexport type ContextMenuProps<Key extends string = string> = Readonly<{\n /** The region the menu belongs to; right-click and long-press are bound here. */\n children: ReactNode;\n items: readonly ContextMenuItemDescriptor<Key>[];\n accessibilityLabel: string;\n onAction: (id: Key) => void;\n className?: string;\n}>;\n\nconst longPressDelay = 500;\n\nexport function ContextMenu<Key extends string = string>({\n children,\n items,\n accessibilityLabel,\n onAction,\n className,\n}: ContextMenuProps<Key>) {\n const hostRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [menuMounted, setMenuMounted] = useState(false);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const menuId = useId();\n const [anchor, setAnchor] = useState<ContextMenuAnchor | null>(null);\n const [activeIndex, setActiveIndex] = useState(0);\n const longPress = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const typeahead = useRef({ value: \"\", time: 0 });\n const enabled = items.filter((item) => !item.disabled);\n\n const open = useCallback((reason: ContextMenuOpenReason, pointer: ContextMenuAnchor | null) => {\n const activeElement = document.activeElement;\n returnFocusRef.current = activeElement instanceof HTMLElement && hostRef.current?.contains(activeElement)\n ? activeElement\n : hostRef.current;\n const rect = hostRef.current?.getBoundingClientRect() ?? null;\n setAnchor(resolveContextMenuAnchor(\n reason,\n pointer,\n rect === null ? null : { left: rect.left, bottom: rect.bottom },\n ));\n setActiveIndex(0);\n typeahead.current = { value: \"\", time: 0 };\n }, []);\n\n const setMenuRef = useCallback((node: HTMLDivElement | null) => {\n menuRef.current = node;\n setMenuMounted(node !== null);\n }, []);\n\n useLayoutEffect(() => {\n if (anchor === null || !menuMounted || menuRef.current === null) return;\n menuRef.current.focus();\n const rect = menuRef.current.getBoundingClientRect();\n const x = Math.min(Math.max(anchor.x, 0), Math.max(window.innerWidth - rect.width, 0));\n const y = Math.min(Math.max(anchor.y, 0), Math.max(window.innerHeight - rect.height, 0));\n // The contract promises viewport coordinates; post-render measurement keeps long labels onscreen, where guessed dimensions could not account for wrapping.\n if (x !== anchor.x || y !== anchor.y) setAnchor({ x, y });\n }, [anchor, menuMounted]);\n\n useEffect(() => {\n if (anchor === null) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n setAnchor(null);\n // Focus goes back where the menu was opened from, not to the body.\n returnFocusRef.current?.focus();\n return;\n }\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((previous) => {\n const next = event.key === \"ArrowDown\" ? previous + 1 : previous - 1;\n return (next + enabled.length) % Math.max(enabled.length, 1);\n });\n return;\n }\n if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n setActiveIndex(event.key === \"Home\" ? 0 : Math.max(enabled.length - 1, 0));\n return;\n }\n if (event.key.length === 1 && event.key !== \" \" && !event.ctrlKey && !event.metaKey && !event.altKey) {\n const result = resolveMenuTypeahead(enabled, activeIndex, event.key, typeahead.current, Date.now());\n typeahead.current = result.state;\n if (result.index !== undefined) {\n event.preventDefault();\n setActiveIndex(result.index);\n }\n return;\n }\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n const item = enabled[activeIndex];\n if (item) { onAction(item.id as Key); setAnchor(null); returnFocusRef.current?.focus(); }\n return;\n }\n if (event.key === \"Tab\") {\n setAnchor(null);\n }\n };\n const onPointerDown = (event: PointerEvent) => {\n if (!(event.target instanceof Node) || !menuRef.current?.contains(event.target)) {\n setAnchor(null);\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"pointerdown\", onPointerDown);\n };\n }, [anchor, activeIndex, enabled, onAction]);\n\n return (\n <div\n ref={hostRef}\n tabIndex={0}\n className={classNames(\"hjm-context-menu-host\", className)}\n onContextMenu={(event) => {\n event.preventDefault();\n open(\"pointer\", { x: event.clientX, y: event.clientY });\n }}\n onKeyDown={(event) => {\n // Shift+F10 and the Menu key are how a keyboard opens a context menu;\n // without them the feature does not exist without a pointer.\n if ((event.shiftKey && event.key === \"F10\") || event.key === \"ContextMenu\") {\n event.preventDefault();\n open(\"keyboard\", null);\n }\n }}\n onPointerDown={(event) => {\n if (event.pointerType !== \"touch\") return;\n longPress.current = setTimeout(() => open(\"longPress\", { x: event.clientX, y: event.clientY }), longPressDelay);\n }}\n onPointerUp={() => { if (longPress.current !== undefined) clearTimeout(longPress.current); }}\n onPointerCancel={() => { if (longPress.current !== undefined) clearTimeout(longPress.current); }}\n >\n {children}\n {anchor !== null ? (\n <HjmPortal>\n <div\n role=\"menu\"\n id={menuId}\n ref={setMenuRef}\n tabIndex={-1}\n aria-activedescendant={enabled[activeIndex] ? `${menuId}-item-${activeIndex}` : undefined}\n aria-label={accessibilityLabel}\n className=\"hjm-context-menu\"\n style={{\n top: anchor.y,\n left: anchor.x,\n zIndex: getModalLayer(0),\n minInlineSize: menuRecipe.minWidth,\n } as CSSProperties}\n >\n {items.map((item) => {\n const index = enabled.findIndex((candidate) => candidate.id === item.id);\n return (\n <div\n key={item.id}\n id={index >= 0 ? `${menuId}-item-${index}` : undefined}\n role=\"menuitem\"\n aria-disabled={item.disabled || undefined}\n data-active={index === activeIndex && !item.disabled ? \"\" : undefined}\n data-tone={item.tone}\n className=\"hjm-context-menu__item\"\n onMouseEnter={() => { if (index >= 0) setActiveIndex(index); }}\n onClick={() => {\n if (item.disabled) return;\n onAction(item.id as Key);\n setAnchor(null);\n returnFocusRef.current?.focus();\n }}\n >\n <span>{item.label}</span>\n {item.shortcut ? <kbd className=\"hjm-context-menu__shortcut\">{item.shortcut}</kbd> : null}\n </div>\n );\n })}\n </div>\n </HjmPortal>\n ) : null}\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iFAAiF;IACjF,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;CAC/E,CAAC,CAAC;AAEH,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACvG,OAAO,EAAE,SAAS,yBAAyB,CAAC,SAAS,CAAC,EAAE,CAAC;IACzD,IAAI,EAAE,SAAS,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;IAChD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,eAAO,MAAM,SAAS,EA+IhB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,EACtE,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;CAAE,KAC7E,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iFAAiF;IACjF,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;CAC/E,CAAC,CAAC;AAEH,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACvG,OAAO,EAAE,SAAS,yBAAyB,CAAC,SAAS,CAAC,EAAE,CAAC;IACzD,IAAI,EAAE,SAAS,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;IAChD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,eAAO,MAAM,SAAS,EA8IhB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,EACtE,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;CAAE,KAC7E,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TableSortButton } from "./table-sort-button.js";
2
3
  import { dataTableColumnDefaults, dataTableDefaults, getNextDataTableSortState, resolveDataTableSelectAllState, validateDataTableColumns, validateDataTableRows, validateDataTableSortState, } from "@hjmds/design-contracts/components/data-table";
3
4
  import { forwardRef } from "react";
4
5
  import { useHjmDensityDefault } from "./provider.js";
@@ -51,7 +52,7 @@ export const DataTable = forwardRef(function DataTable({ columns, rows, labels,
51
52
  const sorted = sortState?.columnId === column.id ? sortState.direction : null;
52
53
  return (_jsx("th", { scope: "col", "aria-sort": sorted ?? undefined, style: { inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align }, children: column.sortable ? (
53
54
  // A button inside the header, never the header itself.
54
- _jsxs("button", { type: "button", className: "hjm-data-table__sort", "aria-label": labels.sortColumn(column.header, sortState), onClick: () => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle)), children: [_jsx("span", { children: column.header }), _jsx("span", { "aria-hidden": "true", children: sorted === "ascending" ? "▲" : sorted === "descending" ? "▼" : "↕" })] })) : column.header }, column.id));
55
+ _jsx(TableSortButton, { header: column.header, direction: sorted, glyphs: { ascending: "▲", descending: "▼", none: "↕" }, className: "hjm-data-table__sort", accessibleName: labels.sortColumn(column.header, sortState), onSort: () => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle)) })) : column.header }, column.id));
55
56
  })] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { "aria-selected": selection && selection.mode !== "none" ? selectedKeys.has(row.id) : undefined, "data-disabled": row.disabled || undefined, children: [selection && selection.mode !== "none" ? (_jsx("td", { className: "hjm-data-table__selector", children: _jsx("button", { type: "button", role: multiple ? "checkbox" : "radio", "aria-checked": selectedKeys.has(row.id), "aria-label": labels.selectRow(row.id), "aria-disabled": row.disabled || undefined, className: "hjm-data-table__check", onClick: () => toggleRow(row), children: selectedKeys.has(row.id) ? "✓" : "" }) })) : null, columns.map((column) => (_jsx("td", { style: { textAlign: column.align ?? dataTableColumnDefaults.align }, children: renderCell(row.id, column.id) }, column.id)))] }, row.id))) })] }), footer ? _jsx("div", { className: "hjm-data-table__footer", children: footer }) : null] }));
56
57
  });
57
58
  //# sourceMappingURL=data-table.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n dataTableColumnDefaults,\n dataTableDefaults,\n getNextDataTableSortState,\n resolveDataTableSelectAllState,\n validateDataTableColumns,\n validateDataTableRows,\n validateDataTableSortState,\n type DataTableAsyncState,\n type DataTableColumnDescriptor,\n type DataTableDensity,\n type DataTableRowDescriptor,\n type DataTableSelection,\n type DataTableSortCycle,\n type DataTableSortState,\n} from \"@hjmds/design-contracts/components/data-table\";\nimport { forwardRef, type CSSProperties, type ReactNode } from \"react\";\nimport { useHjmDensityDefault } from \"./provider.js\";\nimport { classNames } from \"./internal.js\";\n\nexport type DataTableLabels = Readonly<{\n /** Accessible name for the table itself. */\n table: string;\n selectAll: string;\n /** Composes one row's selection control name from its id. */\n selectRow: (rowId: string) => string;\n /** Composes a sortable header's action name, including its current direction. */\n sortColumn: (header: string, direction: DataTableSortState<string>) => string;\n}>;\n\nexport type DataTableProps<ColumnKey extends string = string, RowKey extends string = string> = Readonly<{\n columns: readonly DataTableColumnDescriptor<ColumnKey>[];\n rows: readonly DataTableRowDescriptor<RowKey>[];\n labels: DataTableLabels;\n renderCell: (rowId: RowKey, columnId: ColumnKey) => ReactNode;\n selection?: DataTableSelection<RowKey>;\n sortState?: DataTableSortState<ColumnKey>;\n sortCycle?: DataTableSortCycle;\n onSortChange?: (next: DataTableSortState<ColumnKey>) => void;\n asyncState?: DataTableAsyncState;\n density?: DataTableDensity;\n /** Composed beneath the table by the product — pagination, load more, totals. */\n footer?: ReactNode;\n className?: string;\n}>;\n\nexport const DataTable = forwardRef(function DataTable<\n ColumnKey extends string = string,\n RowKey extends string = string,\n>(\n {\n columns,\n rows,\n labels,\n renderCell,\n selection,\n sortState = null,\n sortCycle = dataTableDefaults.sortCycle,\n onSortChange,\n asyncState = { status: \"idle\" },\n density: densityProp,\n footer,\n className,\n }: DataTableProps<ColumnKey, RowKey>,\n forwardedRef: React.Ref<HTMLTableElement>,\n) {\n // Unconditional: `??` on the hook call would break the rules of hooks.\n const densityDefault = useHjmDensityDefault({ comfortable: \"regular\", compact: \"compact\" });\n const density = densityProp ?? densityDefault;\n validateDataTableColumns(columns);\n validateDataTableRows(rows);\n validateDataTableSortState(sortState, columns);\n const multiple = selection?.mode === \"multiple\";\n const single = selection?.mode === \"single\";\n const selectedKeys: ReadonlySet<RowKey> = multiple\n ? selection.selectedKeys ?? selection.defaultSelectedKeys ?? new Set<RowKey>()\n : single\n ? new Set<RowKey>(((selection.selectedKey ?? selection.defaultSelectedKey) ?? null) === null\n ? []\n : [(selection.selectedKey ?? selection.defaultSelectedKey)!])\n : new Set<RowKey>();\n const selectAllState = multiple ? resolveDataTableSelectAllState(rows, selectedKeys) : false;\n\n const toggleRow = (row: DataTableRowDescriptor<RowKey>) => {\n if (row.disabled || !selection || selection.mode === \"none\") return;\n if (selection.mode === \"single\") {\n const current = selection.selectedKey ?? selection.defaultSelectedKey ?? null;\n const next = current === row.id && selection.disallowEmptySelection !== true ? null : row.id;\n selection.onSelectionChange?.(next);\n return;\n }\n const next = new Set(selectedKeys);\n if (next.has(row.id)) next.delete(row.id); else next.add(row.id);\n selection.onSelectionChange?.(next);\n };\n const toggleAll = () => {\n if (!multiple) return;\n // Disabled rows stay out of both the denominator and the result, matching\n // the shared select-all accounting.\n const selectable = rows.filter((row) => !row.disabled);\n const next = selectAllState === true\n ? new Set([...selectedKeys].filter((id) => !selectable.some((row) => row.id === id)))\n : new Set([...selectedKeys, ...selectable.map((row) => row.id)]);\n selection.onSelectionChange?.(next);\n };\n\n return (\n <div className={classNames(\"hjm-data-table\", className)} data-density={density}>\n {asyncState.status === \"idle\" ? null : (\n <p className=\"hjm-data-table__state\" role={asyncState.status === \"error\" ? \"alert\" : \"status\"}>{asyncState.message}</p>\n )}\n <table ref={forwardedRef} className=\"hjm-data-table__table\" aria-label={labels.table} aria-busy={asyncState.status === \"loading\" || undefined}>\n <thead>\n <tr>\n {selection && selection.mode !== \"none\" ? (\n <th scope=\"col\" className=\"hjm-data-table__selector\">\n {multiple ? (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selectAllState === \"mixed\" ? \"mixed\" : String(selectAllState === true) as \"true\" | \"false\"}\n aria-label={labels.selectAll}\n className=\"hjm-data-table__check\"\n onClick={toggleAll}\n >\n {selectAllState === true ? \"✓\" : selectAllState === \"mixed\" ? \"–\" : \"\"}\n </button>\n ) : null}\n </th>\n ) : null}\n {columns.map((column) => {\n const sorted = sortState?.columnId === column.id ? sortState.direction : null;\n return (\n <th\n key={column.id}\n scope=\"col\"\n // The direction passes straight through to aria-sort; the\n // contract already speaks that vocabulary.\n aria-sort={sorted ?? undefined}\n style={{ inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}\n >\n {column.sortable ? (\n // A button inside the header, never the header itself.\n <button\n type=\"button\"\n className=\"hjm-data-table__sort\"\n aria-label={labels.sortColumn(column.header, sortState as DataTableSortState<string>)}\n onClick={() => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle))}\n >\n <span>{column.header}</span>\n <span aria-hidden=\"true\">{sorted === \"ascending\" ? \"▲\" : sorted === \"descending\" ? \"▼\" : \"↕\"}</span>\n </button>\n ) : column.header}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.id} aria-selected={selection && selection.mode !== \"none\" ? selectedKeys.has(row.id) : undefined} data-disabled={row.disabled || undefined}>\n {selection && selection.mode !== \"none\" ? (\n <td className=\"hjm-data-table__selector\">\n <button\n type=\"button\"\n role={multiple ? \"checkbox\" : \"radio\"}\n aria-checked={selectedKeys.has(row.id)}\n aria-label={labels.selectRow(row.id)}\n aria-disabled={row.disabled || undefined}\n className=\"hjm-data-table__check\"\n onClick={() => toggleRow(row)}\n >\n {selectedKeys.has(row.id) ? \"✓\" : \"\"}\n </button>\n </td>\n ) : null}\n {columns.map((column) => (\n <td key={column.id} style={{ textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}>\n {renderCell(row.id, column.id)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {/* Pagination or load-more is composed beneath the table, never owned by it. */}\n {footer ? <div className=\"hjm-data-table__footer\">{footer}</div> : null}\n </div>\n );\n}) as <ColumnKey extends string = string, RowKey extends string = string>(\n props: DataTableProps<ColumnKey, RowKey> & { ref?: React.Ref<HTMLTableElement> },\n) => React.ReactElement | null;\n"]}
1
+ {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"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","sourcesContent":["import { TableSortButton } from \"./table-sort-button.js\";\nimport {\n dataTableColumnDefaults,\n dataTableDefaults,\n getNextDataTableSortState,\n resolveDataTableSelectAllState,\n validateDataTableColumns,\n validateDataTableRows,\n validateDataTableSortState,\n type DataTableAsyncState,\n type DataTableColumnDescriptor,\n type DataTableDensity,\n type DataTableRowDescriptor,\n type DataTableSelection,\n type DataTableSortCycle,\n type DataTableSortState,\n} from \"@hjmds/design-contracts/components/data-table\";\nimport { forwardRef, type CSSProperties, type ReactNode } from \"react\";\nimport { useHjmDensityDefault } from \"./provider.js\";\nimport { classNames } from \"./internal.js\";\n\nexport type DataTableLabels = Readonly<{\n /** Accessible name for the table itself. */\n table: string;\n selectAll: string;\n /** Composes one row's selection control name from its id. */\n selectRow: (rowId: string) => string;\n /** Composes a sortable header's action name, including its current direction. */\n sortColumn: (header: string, direction: DataTableSortState<string>) => string;\n}>;\n\nexport type DataTableProps<ColumnKey extends string = string, RowKey extends string = string> = Readonly<{\n columns: readonly DataTableColumnDescriptor<ColumnKey>[];\n rows: readonly DataTableRowDescriptor<RowKey>[];\n labels: DataTableLabels;\n renderCell: (rowId: RowKey, columnId: ColumnKey) => ReactNode;\n selection?: DataTableSelection<RowKey>;\n sortState?: DataTableSortState<ColumnKey>;\n sortCycle?: DataTableSortCycle;\n onSortChange?: (next: DataTableSortState<ColumnKey>) => void;\n asyncState?: DataTableAsyncState;\n density?: DataTableDensity;\n /** Composed beneath the table by the product — pagination, load more, totals. */\n footer?: ReactNode;\n className?: string;\n}>;\n\nexport const DataTable = forwardRef(function DataTable<\n ColumnKey extends string = string,\n RowKey extends string = string,\n>(\n {\n columns,\n rows,\n labels,\n renderCell,\n selection,\n sortState = null,\n sortCycle = dataTableDefaults.sortCycle,\n onSortChange,\n asyncState = { status: \"idle\" },\n density: densityProp,\n footer,\n className,\n }: DataTableProps<ColumnKey, RowKey>,\n forwardedRef: React.Ref<HTMLTableElement>,\n) {\n // Unconditional: `??` on the hook call would break the rules of hooks.\n const densityDefault = useHjmDensityDefault({ comfortable: \"regular\", compact: \"compact\" });\n const density = densityProp ?? densityDefault;\n validateDataTableColumns(columns);\n validateDataTableRows(rows);\n validateDataTableSortState(sortState, columns);\n const multiple = selection?.mode === \"multiple\";\n const single = selection?.mode === \"single\";\n const selectedKeys: ReadonlySet<RowKey> = multiple\n ? selection.selectedKeys ?? selection.defaultSelectedKeys ?? new Set<RowKey>()\n : single\n ? new Set<RowKey>(((selection.selectedKey ?? selection.defaultSelectedKey) ?? null) === null\n ? []\n : [(selection.selectedKey ?? selection.defaultSelectedKey)!])\n : new Set<RowKey>();\n const selectAllState = multiple ? resolveDataTableSelectAllState(rows, selectedKeys) : false;\n\n const toggleRow = (row: DataTableRowDescriptor<RowKey>) => {\n if (row.disabled || !selection || selection.mode === \"none\") return;\n if (selection.mode === \"single\") {\n const current = selection.selectedKey ?? selection.defaultSelectedKey ?? null;\n const next = current === row.id && selection.disallowEmptySelection !== true ? null : row.id;\n selection.onSelectionChange?.(next);\n return;\n }\n const next = new Set(selectedKeys);\n if (next.has(row.id)) next.delete(row.id); else next.add(row.id);\n selection.onSelectionChange?.(next);\n };\n const toggleAll = () => {\n if (!multiple) return;\n // Disabled rows stay out of both the denominator and the result, matching\n // the shared select-all accounting.\n const selectable = rows.filter((row) => !row.disabled);\n const next = selectAllState === true\n ? new Set([...selectedKeys].filter((id) => !selectable.some((row) => row.id === id)))\n : new Set([...selectedKeys, ...selectable.map((row) => row.id)]);\n selection.onSelectionChange?.(next);\n };\n\n return (\n <div className={classNames(\"hjm-data-table\", className)} data-density={density}>\n {asyncState.status === \"idle\" ? null : (\n <p className=\"hjm-data-table__state\" role={asyncState.status === \"error\" ? \"alert\" : \"status\"}>{asyncState.message}</p>\n )}\n <table ref={forwardedRef} className=\"hjm-data-table__table\" aria-label={labels.table} aria-busy={asyncState.status === \"loading\" || undefined}>\n <thead>\n <tr>\n {selection && selection.mode !== \"none\" ? (\n <th scope=\"col\" className=\"hjm-data-table__selector\">\n {multiple ? (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selectAllState === \"mixed\" ? \"mixed\" : String(selectAllState === true) as \"true\" | \"false\"}\n aria-label={labels.selectAll}\n className=\"hjm-data-table__check\"\n onClick={toggleAll}\n >\n {selectAllState === true ? \"✓\" : selectAllState === \"mixed\" ? \"–\" : \"\"}\n </button>\n ) : null}\n </th>\n ) : null}\n {columns.map((column) => {\n const sorted = sortState?.columnId === column.id ? sortState.direction : null;\n return (\n <th\n key={column.id}\n scope=\"col\"\n // The direction passes straight through to aria-sort; the\n // contract already speaks that vocabulary.\n aria-sort={sorted ?? undefined}\n style={{ inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}\n >\n {column.sortable ? (\n // A button inside the header, never the header itself.\n <TableSortButton\n header={column.header}\n direction={sorted}\n glyphs={{ ascending: \"▲\", descending: \"▼\", none: \"↕\" }}\n className=\"hjm-data-table__sort\"\n accessibleName={labels.sortColumn(column.header, sortState as DataTableSortState<string>)}\n onSort={() => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle))}\n />\n ) : column.header}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.id} aria-selected={selection && selection.mode !== \"none\" ? selectedKeys.has(row.id) : undefined} data-disabled={row.disabled || undefined}>\n {selection && selection.mode !== \"none\" ? (\n <td className=\"hjm-data-table__selector\">\n <button\n type=\"button\"\n role={multiple ? \"checkbox\" : \"radio\"}\n aria-checked={selectedKeys.has(row.id)}\n aria-label={labels.selectRow(row.id)}\n aria-disabled={row.disabled || undefined}\n className=\"hjm-data-table__check\"\n onClick={() => toggleRow(row)}\n >\n {selectedKeys.has(row.id) ? \"✓\" : \"\"}\n </button>\n </td>\n ) : null}\n {columns.map((column) => (\n <td key={column.id} style={{ textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}>\n {renderCell(row.id, column.id)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {/* Pagination or load-more is composed beneath the table, never owned by it. */}\n {footer ? <div className=\"hjm-data-table__footer\">{footer}</div> : null}\n </div>\n );\n}) as <ColumnKey extends string = string, RowKey extends string = string>(\n props: DataTableProps<ColumnKey, RowKey> & { ref?: React.Ref<HTMLTableElement> },\n) => React.ReactElement | null;\n"]}
@@ -0,0 +1,11 @@
1
+ import { type DurationFieldLabels, type DurationRange } from "@hjmds/design-contracts/duration-field";
2
+ export type DurationFieldProps = DurationRange & Readonly<{
3
+ value: number;
4
+ onValueChange: (seconds: number) => void;
5
+ labels: DurationFieldLabels;
6
+ disabled?: boolean;
7
+ className?: string;
8
+ }>;
9
+ /** A NumberField composition; value and limits always use whole seconds. */
10
+ export declare function DurationField({ value, onValueChange, labels, min, max, disabled, className }: DurationFieldProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=duration-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-field.d.ts","sourceRoot":"","sources":["../src/duration-field.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAE3I,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC5L,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,GAAO,EAAE,GAAG,EAAE,QAAgB,EAAE,SAAS,EAAE,EAAE,kBAAkB,+BAW5H"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { changeDurationUnit, resolveDuration } from "@hjmds/design-contracts/duration-field";
3
+ import { NumberField } from "./number-field.js";
4
+ /** A NumberField composition; value and limits always use whole seconds. */
5
+ export function DurationField({ value, onValueChange, labels, min = 0, max, disabled = false, className }) {
6
+ const parts = resolveDuration(value, { min, max });
7
+ return _jsxs("fieldset", { className: className, disabled: disabled, style: { border: 0, margin: 0, padding: 0, minWidth: 0 }, children: [_jsx("legend", { style: { marginBlockEnd: "var(--hjm-space-sm)" }, children: labels.label }), _jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 10ch), 1fr))", gap: "var(--hjm-space-md)" }, children: ["hours", "minutes", "seconds"].map(unit => _jsx(NumberField, { label: labels[unit], min: 0, max: unit === "hours" ? Math.max(1, Math.floor(max / 3600)) : 59, value: parts[unit], disabled: disabled || (unit === "hours" && max < 3600), step: 1, incrementLabel: labels.increment(unit), decrementLabel: labels.decrement(unit), onValueChange: next => onValueChange(changeDurationUnit(value, unit, next, { min, max })) }, unit)) })] });
8
+ }
9
+ //# sourceMappingURL=duration-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-field.js","sourceRoot":"","sources":["../src/duration-field.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAgD,MAAM,wCAAwC,CAAC;AAC3I,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,QAAQ,GAAG,KAAK,EAAE,SAAS,EAAsB;IAC3H,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IACnD,OAAO,oBAAU,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,aACjH,iBAAQ,KAAK,EAAE,EAAE,cAAc,EAAE,qBAAqB,EAAE,YAAG,MAAM,CAAC,KAAK,GAAU,EACjF,cAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,gDAAgD,EAAE,GAAG,EAAE,qBAAqB,EAAE,YAC9H,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAC,WAAW,IAAY,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAC3K,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EACnF,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAC9E,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,IAHjB,IAAI,CAGiB,CAAC,GAC5F,IACG,CAAC;AACd,CAAC","sourcesContent":["import { changeDurationUnit, resolveDuration, type DurationFieldLabels, type DurationRange } from \"@hjmds/design-contracts/duration-field\";\nimport { NumberField } from \"./number-field.js\";\nexport type DurationFieldProps = DurationRange & Readonly<{ value: number; onValueChange: (seconds: number) => void; labels: DurationFieldLabels; disabled?: boolean; className?: string }>;\n/** A NumberField composition; value and limits always use whole seconds. */\nexport function DurationField({ value, onValueChange, labels, min = 0, max, disabled = false, className }: DurationFieldProps) {\n const parts = resolveDuration(value, { min, max });\n return <fieldset className={className} disabled={disabled} style={{ border: 0, margin: 0, padding: 0, minWidth: 0 }}>\n <legend style={{ marginBlockEnd: \"var(--hjm-space-sm)\" }}>{labels.label}</legend>\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(100%, 10ch), 1fr))\", gap: \"var(--hjm-space-md)\" }}>\n {([\"hours\", \"minutes\", \"seconds\"] as const).map(unit => <NumberField key={unit} label={labels[unit]} min={0} max={unit === \"hours\" ? Math.max(1, Math.floor(max / 3600)) : 59}\n value={parts[unit]} disabled={disabled || (unit === \"hours\" && max < 3600)} step={1}\n incrementLabel={labels.increment(unit)} decrementLabel={labels.decrement(unit)}\n onValueChange={next => onValueChange(changeDurationUnit(value, unit, next, { min, max }))} />)}\n </div>\n </fieldset>;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ import { type EffectSurfaceDescriptor } from "@hjmds/design-contracts/effect-surface";
3
+ export type EffectSurfaceProps = Readonly<{
4
+ descriptor?: EffectSurfaceDescriptor;
5
+ children: ReactNode;
6
+ className?: string;
7
+ }>;
8
+ /** Decorative layers never receive pointer events or own the content's name. */
9
+ export declare function EffectSurface({ descriptor, children, className }: EffectSurfaceProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=effect-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-surface.d.ts","sourceRoot":"","sources":["../src/effect-surface.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAG5G,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,UAAU,CAAC,EAAE,uBAAuB,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC7H,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,EAAE,UAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,kBAAkB,+BA2CzF"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ import { resolveEffectSurface } from "@hjmds/design-contracts/effect-surface";
4
+ import { resolveColorReference } from "@hjmds/design-contracts/color-references";
5
+ import { useHjmTheme } from "./provider.js";
6
+ /** Decorative layers never receive pointer events or own the content's name. */
7
+ export function EffectSurface({ descriptor = {}, children, className }) {
8
+ const { palette, environment } = useHjmTheme();
9
+ const effect = resolveEffectSurface(descriptor);
10
+ const id = useId().replace(/:/g, "");
11
+ const host = useRef(null);
12
+ const layer = useRef(null);
13
+ const [visible, setVisible] = useState(false);
14
+ useEffect(() => {
15
+ if (!host.current)
16
+ return;
17
+ if (typeof IntersectionObserver === "undefined") {
18
+ setVisible(true);
19
+ return;
20
+ }
21
+ const observer = new IntersectionObserver(([entry]) => setVisible(entry?.isIntersecting ?? false));
22
+ observer.observe(host.current);
23
+ return () => observer.disconnect();
24
+ }, []);
25
+ useEffect(() => {
26
+ const node = layer.current;
27
+ if (!node || !effect.active || environment.reducedMotion || !visible || typeof node.animate !== "function")
28
+ return;
29
+ // WAAPI stays outside React renders; visibility changes cancel the loop and
30
+ // expose its static composition rather than scheduling invisible frames.
31
+ let animation;
32
+ let failed = false;
33
+ const sync = () => {
34
+ animation?.cancel();
35
+ if (document.hidden || failed)
36
+ return;
37
+ // A host may expose WAAPI but reject animation creation. Keep the static SVG
38
+ // and content instead of propagating a decorative failure to the application.
39
+ try {
40
+ animation = node.animate([
41
+ { transform: "scale(1.08) translate(-1%, 0%)" },
42
+ { transform: "scale(1.12) translate(1%, 1%)" },
43
+ { transform: "scale(1.08) translate(-1%, 0%)" },
44
+ ], { duration: effect.period * 1000, iterations: Infinity, easing: "ease-in-out" });
45
+ }
46
+ catch {
47
+ failed = true;
48
+ }
49
+ };
50
+ sync();
51
+ document.addEventListener("visibilitychange", sync);
52
+ return () => { animation?.cancel(); document.removeEventListener("visibilitychange", sync); };
53
+ }, [effect.active, effect.period, environment.reducedMotion, visible]);
54
+ const colors = effect.colors.map(color => resolveColorReference(color, palette));
55
+ return _jsxs("div", { ref: host, className: className, "data-hjm-effect-surface": "", style: { position: "relative", isolation: "isolate", overflow: "hidden", background: palette.theme.bg }, children: [_jsxs("svg", { ref: layer, "aria-hidden": "true", focusable: "false", viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none", opacity: effect.intensity }, children: [_jsxs("defs", { children: [colors.map((color, index) => _jsxs("radialGradient", { id: `${id}-${index}`, children: [_jsx("stop", { offset: "0%", stopColor: color }), _jsx("stop", { offset: "100%", stopColor: color, stopOpacity: 0 })] }, index)), _jsx("pattern", { id: `${id}-grain`, width: 4, height: 4, patternUnits: "userSpaceOnUse", children: effect.points.map((point, index) => _jsx("circle", { cx: point.x / 25, cy: point.y / 25, r: point.radius / 10, fill: palette.theme.text, opacity: 0.28 }, index)) })] }), effect.layers.includes("mesh") && effect.anchors.map((anchor, index) => _jsx("ellipse", { cx: anchor.x, cy: anchor.y, rx: 65, ry: 70, fill: `url(#${id}-${index})` }, index)), effect.layers.includes("glow") && _jsx("ellipse", { cx: 50, cy: 10, rx: 70, ry: 95, fill: `url(#${id}-0)` }), effect.layers.includes("grain") && _jsx("rect", { width: 100, height: 100, fill: `url(#${id}-grain)` })] }), _jsx("div", { style: { position: "relative" }, children: children })] });
56
+ }
57
+ //# sourceMappingURL=effect-surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-surface.js","sourceRoot":"","sources":["../src/effect-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { resolveEffectSurface, type EffectSurfaceDescriptor } from \"@hjmds/design-contracts/effect-surface\";\nimport { resolveColorReference } from \"@hjmds/design-contracts/color-references\";\nimport { useHjmTheme } from \"./provider.js\";\nexport type EffectSurfaceProps = Readonly<{ descriptor?: EffectSurfaceDescriptor; children: ReactNode; className?: string }>;\n/** Decorative layers never receive pointer events or own the content's name. */\nexport function EffectSurface({ descriptor = {}, children, className }: EffectSurfaceProps) {\n const { palette, environment } = useHjmTheme();\n const effect = resolveEffectSurface(descriptor);\n const id = useId().replace(/:/g, \"\");\n const host = useRef<HTMLDivElement>(null);\n const layer = useRef<SVGSVGElement>(null);\n const [visible, setVisible] = useState(false);\n useEffect(() => {\n if (!host.current) return;\n if (typeof IntersectionObserver === \"undefined\") { setVisible(true); return; }\n const observer = new IntersectionObserver(([entry]) => setVisible(entry?.isIntersecting ?? false));\n observer.observe(host.current); return () => observer.disconnect();\n }, []);\n useEffect(() => {\n const node = layer.current;\n if (!node || !effect.active || environment.reducedMotion || !visible || typeof node.animate !== \"function\") return;\n // WAAPI stays outside React renders; visibility changes cancel the loop and\n // expose its static composition rather than scheduling invisible frames.\n let animation: Animation | undefined;\n let failed = false;\n const sync = () => { animation?.cancel(); if (document.hidden || failed) return;\n // A host may expose WAAPI but reject animation creation. Keep the static SVG\n // and content instead of propagating a decorative failure to the application.\n try { animation = node.animate([\n { transform: \"scale(1.08) translate(-1%, 0%)\" },\n { transform: \"scale(1.12) translate(1%, 1%)\" },\n { transform: \"scale(1.08) translate(-1%, 0%)\" },\n ], { duration: effect.period * 1000, iterations: Infinity, easing: \"ease-in-out\" }); } catch { failed = true; } };\n sync(); document.addEventListener(\"visibilitychange\", sync);\n return () => { animation?.cancel(); document.removeEventListener(\"visibilitychange\", sync); };\n }, [effect.active, effect.period, environment.reducedMotion, visible]);\n const colors = effect.colors.map(color => resolveColorReference(color, palette));\n return <div ref={host} className={className} data-hjm-effect-surface=\"\" style={{ position: \"relative\", isolation: \"isolate\", overflow: \"hidden\", background: palette.theme.bg }}>\n <svg ref={layer} aria-hidden=\"true\" focusable=\"false\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" style={{ position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\", pointerEvents: \"none\", opacity: effect.intensity }}>\n <defs>{colors.map((color, index) => <radialGradient id={`${id}-${index}`} key={index}><stop offset=\"0%\" stopColor={color} /><stop offset=\"100%\" stopColor={color} stopOpacity={0} /></radialGradient>)}\n <pattern id={`${id}-grain`} width={4} height={4} patternUnits=\"userSpaceOnUse\">{effect.points.map((point, index) => <circle key={index} cx={point.x / 25} cy={point.y / 25} r={point.radius / 10} fill={palette.theme.text} opacity={0.28} />)}</pattern>\n </defs>\n {effect.layers.includes(\"mesh\") && effect.anchors.map((anchor, index) => <ellipse key={index} cx={anchor.x} cy={anchor.y} rx={65} ry={70} fill={`url(#${id}-${index})`} />)}\n {effect.layers.includes(\"glow\") && <ellipse cx={50} cy={10} rx={70} ry={95} fill={`url(#${id}-0)`} />}\n {effect.layers.includes(\"grain\") && <rect width={100} height={100} fill={`url(#${id}-grain)`} />}\n </svg>\n <div style={{ position: \"relative\" }}>{children}</div>\n </div>;\n}\n"]}
@@ -34,7 +34,7 @@ export type ReactRendererEvidenceManifest = Readonly<{
34
34
  export declare const reactRendererEvidence: {
35
35
  readonly schemaVersion: 2;
36
36
  readonly packageName: "@hjmds/react";
37
- readonly packageVersion: "1.10.0";
37
+ readonly packageVersion: "1.12.0";
38
38
  readonly surface: "web";
39
39
  readonly components: readonly [Readonly<{
40
40
  /** Canonical component id from the design-contracts catalog. */
package/dist/evidence.js CHANGED
@@ -155,7 +155,7 @@ function toastClaim() {
155
155
  export const reactRendererEvidence = {
156
156
  schemaVersion: reactRendererEvidenceSchemaVersion,
157
157
  packageName: "@hjmds/react",
158
- packageVersion: "1.10.0",
158
+ packageVersion: "1.12.0",
159
159
  surface: "web",
160
160
  components: [
161
161
  defaultClaim("top-bar", ["TopBar"], "./top-bar"),