@hjmds/react 1.17.2 → 1.19.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 (201) hide show
  1. package/dist/activity-chip.d.ts +33 -0
  2. package/dist/activity-chip.d.ts.map +1 -0
  3. package/dist/activity-chip.js +24 -0
  4. package/dist/activity-chip.js.map +1 -0
  5. package/dist/advanced-display.d.ts.map +1 -1
  6. package/dist/advanced-display.js +41 -9
  7. package/dist/advanced-display.js.map +1 -1
  8. package/dist/advanced-forms.d.ts.map +1 -1
  9. package/dist/advanced-forms.js +22 -9
  10. package/dist/advanced-forms.js.map +1 -1
  11. package/dist/agreement.d.ts.map +1 -1
  12. package/dist/agreement.js +17 -11
  13. package/dist/agreement.js.map +1 -1
  14. package/dist/auth-screen.d.ts.map +1 -1
  15. package/dist/auth-screen.js +15 -2
  16. package/dist/auth-screen.js.map +1 -1
  17. package/dist/avatar-blobatar-motion.d.ts.map +1 -1
  18. package/dist/avatar-blobatar-motion.js +10 -2
  19. package/dist/avatar-blobatar-motion.js.map +1 -1
  20. package/dist/bottom-navigation.d.ts.map +1 -1
  21. package/dist/bottom-navigation.js +2 -2
  22. package/dist/bottom-navigation.js.map +1 -1
  23. package/dist/calendar.d.ts.map +1 -1
  24. package/dist/calendar.js +10 -2
  25. package/dist/calendar.js.map +1 -1
  26. package/dist/carousel-motion.d.ts +1 -0
  27. package/dist/carousel-motion.d.ts.map +1 -1
  28. package/dist/carousel-motion.js +4 -3
  29. package/dist/carousel-motion.js.map +1 -1
  30. package/dist/carousel.d.ts.map +1 -1
  31. package/dist/carousel.js +22 -4
  32. package/dist/carousel.js.map +1 -1
  33. package/dist/collapsible.d.ts.map +1 -1
  34. package/dist/collapsible.js +28 -4
  35. package/dist/collapsible.js.map +1 -1
  36. package/dist/collection-rail.d.ts.map +1 -1
  37. package/dist/collection-rail.js +19 -1
  38. package/dist/collection-rail.js.map +1 -1
  39. package/dist/color-picker.d.ts.map +1 -1
  40. package/dist/color-picker.js +12 -4
  41. package/dist/color-picker.js.map +1 -1
  42. package/dist/command-palette.d.ts.map +1 -1
  43. package/dist/command-palette.js +14 -2
  44. package/dist/command-palette.js.map +1 -1
  45. package/dist/content-transition.d.ts +2 -1
  46. package/dist/content-transition.d.ts.map +1 -1
  47. package/dist/content-transition.js +2 -2
  48. package/dist/content-transition.js.map +1 -1
  49. package/dist/context-menu.d.ts.map +1 -1
  50. package/dist/context-menu.js +31 -10
  51. package/dist/context-menu.js.map +1 -1
  52. package/dist/date-entry.d.ts +1 -1
  53. package/dist/date-entry.d.ts.map +1 -1
  54. package/dist/date-entry.js +20 -7
  55. package/dist/date-entry.js.map +1 -1
  56. package/dist/date-picker.d.ts.map +1 -1
  57. package/dist/date-picker.js +22 -6
  58. package/dist/date-picker.js.map +1 -1
  59. package/dist/display.d.ts +11 -1
  60. package/dist/display.d.ts.map +1 -1
  61. package/dist/display.js +13 -4
  62. package/dist/display.js.map +1 -1
  63. package/dist/duration-field.d.ts +2 -2
  64. package/dist/duration-field.d.ts.map +1 -1
  65. package/dist/duration-field.js +11 -3
  66. package/dist/duration-field.js.map +1 -1
  67. package/dist/evidence.d.ts +1 -1
  68. package/dist/evidence.js +1 -1
  69. package/dist/evidence.js.map +1 -1
  70. package/dist/feedback.d.ts +1 -0
  71. package/dist/feedback.d.ts.map +1 -1
  72. package/dist/feedback.js +7 -2
  73. package/dist/feedback.js.map +1 -1
  74. package/dist/field-group.d.ts +4 -2
  75. package/dist/field-group.d.ts.map +1 -1
  76. package/dist/field-group.js +4 -3
  77. package/dist/field-group.js.map +1 -1
  78. package/dist/file-picker.d.ts.map +1 -1
  79. package/dist/file-picker.js +15 -5
  80. package/dist/file-picker.js.map +1 -1
  81. package/dist/forms.d.ts +2 -2
  82. package/dist/forms.js +1 -1
  83. package/dist/forms.js.map +1 -1
  84. package/dist/gravity-letters.d.ts.map +1 -1
  85. package/dist/gravity-letters.js +9 -1
  86. package/dist/gravity-letters.js.map +1 -1
  87. package/dist/grid-reveal.d.ts.map +1 -1
  88. package/dist/grid-reveal.js +9 -1
  89. package/dist/grid-reveal.js.map +1 -1
  90. package/dist/index.d.ts +1 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +1 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/inline-confirm.d.ts.map +1 -1
  95. package/dist/inline-confirm.js +25 -4
  96. package/dist/inline-confirm.js.map +1 -1
  97. package/dist/internal/document-resource.d.ts.map +1 -1
  98. package/dist/internal/document-resource.js +2 -2
  99. package/dist/internal/document-resource.js.map +1 -1
  100. package/dist/layout.d.ts +8 -2
  101. package/dist/layout.d.ts.map +1 -1
  102. package/dist/layout.js.map +1 -1
  103. package/dist/masonry.d.ts.map +1 -1
  104. package/dist/masonry.js +2 -1
  105. package/dist/masonry.js.map +1 -1
  106. package/dist/mentions.d.ts.map +1 -1
  107. package/dist/mentions.js +12 -4
  108. package/dist/mentions.js.map +1 -1
  109. package/dist/menu-morph.js +1 -1
  110. package/dist/menu-morph.js.map +1 -1
  111. package/dist/menubar.d.ts.map +1 -1
  112. package/dist/menubar.js +43 -19
  113. package/dist/menubar.js.map +1 -1
  114. package/dist/modal.d.ts.map +1 -1
  115. package/dist/modal.js +15 -4
  116. package/dist/modal.js.map +1 -1
  117. package/dist/navigation.d.ts.map +1 -1
  118. package/dist/navigation.js +8 -2
  119. package/dist/navigation.js.map +1 -1
  120. package/dist/number-field.d.ts.map +1 -1
  121. package/dist/number-field.js +3 -1
  122. package/dist/number-field.js.map +1 -1
  123. package/dist/overlay-stack.d.ts.map +1 -1
  124. package/dist/overlay-stack.js +23 -6
  125. package/dist/overlay-stack.js.map +1 -1
  126. package/dist/overlays.d.ts.map +1 -1
  127. package/dist/overlays.js +8 -0
  128. package/dist/overlays.js.map +1 -1
  129. package/dist/pagination.d.ts.map +1 -1
  130. package/dist/pagination.js +16 -4
  131. package/dist/pagination.js.map +1 -1
  132. package/dist/saved-items.d.ts.map +1 -1
  133. package/dist/saved-items.js +6 -1
  134. package/dist/saved-items.js.map +1 -1
  135. package/dist/screen-flows.d.ts +1 -2
  136. package/dist/screen-flows.d.ts.map +1 -1
  137. package/dist/screen-flows.js +120 -16
  138. package/dist/screen-flows.js.map +1 -1
  139. package/dist/screens.d.ts +1 -1
  140. package/dist/screens.d.ts.map +1 -1
  141. package/dist/screens.js +38 -10
  142. package/dist/screens.js.map +1 -1
  143. package/dist/select.d.ts.map +1 -1
  144. package/dist/select.js +6 -1
  145. package/dist/select.js.map +1 -1
  146. package/dist/side-panel.d.ts.map +1 -1
  147. package/dist/side-panel.js +11 -2
  148. package/dist/side-panel.js.map +1 -1
  149. package/dist/sidebar.d.ts.map +1 -1
  150. package/dist/sidebar.js +8 -1
  151. package/dist/sidebar.js.map +1 -1
  152. package/dist/skip-nav.d.ts.map +1 -1
  153. package/dist/skip-nav.js +16 -3
  154. package/dist/skip-nav.js.map +1 -1
  155. package/dist/sortable.d.ts.map +1 -1
  156. package/dist/sortable.js +2 -1
  157. package/dist/sortable.js.map +1 -1
  158. package/dist/splitter.d.ts.map +1 -1
  159. package/dist/splitter.js +23 -11
  160. package/dist/splitter.js.map +1 -1
  161. package/dist/statistic-motion.d.ts.map +1 -1
  162. package/dist/statistic-motion.js +30 -2
  163. package/dist/statistic-motion.js.map +1 -1
  164. package/dist/status-text.d.ts +11 -0
  165. package/dist/status-text.d.ts.map +1 -0
  166. package/dist/status-text.js +7 -0
  167. package/dist/status-text.js.map +1 -0
  168. package/dist/styles.css +53 -7
  169. package/dist/styles.layered.css +53 -7
  170. package/dist/supplemental-display.d.ts.map +1 -1
  171. package/dist/supplemental-display.js +19 -8
  172. package/dist/supplemental-display.js.map +1 -1
  173. package/dist/supplemental-navigation.d.ts.map +1 -1
  174. package/dist/supplemental-navigation.js +10 -1
  175. package/dist/supplemental-navigation.js.map +1 -1
  176. package/dist/tags-input.d.ts.map +1 -1
  177. package/dist/tags-input.js +18 -8
  178. package/dist/tags-input.js.map +1 -1
  179. package/dist/theme.d.ts.map +1 -1
  180. package/dist/theme.js +11 -0
  181. package/dist/theme.js.map +1 -1
  182. package/dist/toast.d.ts.map +1 -1
  183. package/dist/toast.js +41 -16
  184. package/dist/toast.js.map +1 -1
  185. package/dist/toggle-group.d.ts.map +1 -1
  186. package/dist/toggle-group.js +6 -1
  187. package/dist/toggle-group.js.map +1 -1
  188. package/dist/tour.d.ts.map +1 -1
  189. package/dist/tour.js +15 -2
  190. package/dist/tour.js.map +1 -1
  191. package/dist/transfer-list.d.ts +1 -1
  192. package/dist/transfer-list.d.ts.map +1 -1
  193. package/dist/transfer-list.js +52 -7
  194. package/dist/transfer-list.js.map +1 -1
  195. package/dist/tree.d.ts.map +1 -1
  196. package/dist/tree.js +41 -8
  197. package/dist/tree.js.map +1 -1
  198. package/dist/virtual-list.d.ts.map +1 -1
  199. package/dist/virtual-list.js +44 -7
  200. package/dist/virtual-list.js.map +1 -1
  201. package/package.json +13 -3
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useId, useState } from "react";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
3
  import { colorPickerRecipe, normalizePickerColor, pickerOpacity, withPickerOpacity } from "@hjmds/design-contracts/components/color-picker";
4
4
  /** Controlled value; invalid text stays local until corrected or escaped. Native color UI supplies RGB only. */
5
5
  export function ColorPicker({ label, labels, value, onValueChange, alpha = false, disabled = false, presets = [], layoutStyle }) {
@@ -11,10 +11,15 @@ export function ColorPicker({ label, labels, value, onValueChange, alpha = false
11
11
  const id = useId();
12
12
  const [draft, setDraft] = useState(color);
13
13
  const [invalid, setInvalid] = useState(false);
14
- useEffect(() => { setDraft(color); setInvalid(false); }, [color]);
15
- function emit(next) { setDraft(next); setInvalid(false); if (next !== color)
14
+ // Enter consumes this edit; its following blur must not resend a deferred controlled request.
15
+ const draftChanged = useRef(false);
16
+ useEffect(() => { setDraft(color); setInvalid(false); draftChanged.current = false; }, [color]);
17
+ function emit(next) { if (disabled)
18
+ return; draftChanged.current = false; setDraft(next); setInvalid(false); if (next !== color)
16
19
  onValueChange(next); }
17
20
  function commit() {
21
+ if (disabled)
22
+ return;
18
23
  let next;
19
24
  try {
20
25
  // A typed 3/6-digit HEX names only the hue. Keep the current opacity instead of resetting it to 100%,
@@ -28,11 +33,14 @@ export function ColorPicker({ label, labels, value, onValueChange, alpha = false
28
33
  }
29
34
  emit(next);
30
35
  }
31
- return _jsxs("fieldset", { className: "hjm-color-picker", style: { ...layoutStyle, "--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") {
36
+ return _jsxs("fieldset", { className: "hjm-color-picker", style: { ...layoutStyle, "--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 => { draftChanged.current = true; setDraft(event.target.value); setInvalid(false); }, onBlur: () => { if (draftChanged.current)
37
+ commit(); }, onKeyDown: event => { if (event.nativeEvent.isComposing || event.keyCode === 229)
38
+ return; if (event.key === "Enter") {
32
39
  event.preventDefault();
33
40
  commit();
34
41
  } if (event.key === "Escape") {
35
42
  event.preventDefault();
43
+ draftChanged.current = false;
36
44
  setDraft(color);
37
45
  setInvalid(false);
38
46
  } } })] })] }), 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)) })] });
@@ -1 +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 type { HjmCompositionStyleProp } from \"./composition-style.js\";\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 /** Canonical layout-only placement on the root `<fieldset>`. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\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 = [], layoutStyle }: 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={{ ...layoutStyle, \"--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"]}
1
+ {"version":3,"file":"color-picker.js","sourceRoot":"","sources":["../src/color-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useRef, useState, type CSSProperties } from \"react\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\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 /** Canonical layout-only placement on the root `<fieldset>`. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\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 = [], layoutStyle }: 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 // Enter consumes this edit; its following blur must not resend a deferred controlled request.\n const draftChanged = useRef(false);\n useEffect(() => { setDraft(color); setInvalid(false); draftChanged.current = false; }, [color]);\n function emit(next: string) { if (disabled) return; draftChanged.current = false; setDraft(next); setInvalid(false); if (next !== color) onValueChange(next); }\n function commit() {\n if (disabled) return;\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={{ ...layoutStyle, \"--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 => { draftChanged.current = true; setDraft(event.target.value); setInvalid(false); }} onBlur={() => { if (draftChanged.current) commit(); }}\n onKeyDown={event => { if (event.nativeEvent.isComposing || event.keyCode === 229) return; if (event.key === \"Enter\") { event.preventDefault(); commit(); } if (event.key === \"Escape\") { event.preventDefault(); draftChanged.current = false; 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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../src/command-palette.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,6BAA6B,EAElC,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAEhC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EAC1B,MAAM,oDAAoD,CAAC;AAO5D,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAA0D,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AA4CzG,MAAM,MAAM,mBAAmB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACtE,UAAU,EAAE,wBAAwB,CAAC;IACrC,MAAM,EAAE,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,EAAE,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAC/C,iFAAiF;IACjF,sBAAsB,CAAC,EAAE,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,aAAa,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,KAAK,IAAI,CAAC;IACjF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,eAAO,MAAM,cAAc,EAwMrB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAChC,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,KAClE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../src/command-palette.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,6BAA6B,EAElC,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAEhC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EAC1B,MAAM,oDAAoD,CAAC;AAO5D,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAA0D,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AA4CzG,MAAM,MAAM,mBAAmB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACtE,UAAU,EAAE,wBAAwB,CAAC;IACrC,MAAM,EAAE,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,EAAE,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAC/C,iFAAiF;IACjF,sBAAsB,CAAC,EAAE,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,aAAa,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,KAAK,IAAI,CAAC;IACjF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,eAAO,MAAM,cAAc,EAmNrB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAChC,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,KAClE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
@@ -40,6 +40,7 @@ export const CommandPalette = forwardRef(function CommandPalette({ descriptor, s
40
40
  // Stale external rows stay visible but inert, as in the Native Combobox.
41
41
  const enabled = results.current ? results.items.filter((item) => !item.disabled) : [];
42
42
  const [activeId, setActiveId] = useState(null);
43
+ const pendingActivation = useRef(null);
43
44
  const contentRef = useRef(null);
44
45
  const triggerRef = useRef(null);
45
46
  const inputRef = useRef(null);
@@ -68,9 +69,8 @@ export const CommandPalette = forwardRef(function CommandPalette({ descriptor, s
68
69
  onActivate(itemId, reason);
69
70
  // Running a command always closes the palette — `activation` is a dismiss
70
71
  // reason no policy can veto — and the follow-up surface opens after that.
72
+ pendingActivation.current = { itemId, reason };
71
73
  change(false, "activation");
72
- if (onActivateAfterDismiss)
73
- queueMicrotask(() => onActivateAfterDismiss(itemId, reason));
74
74
  };
75
75
  useModalFocus({
76
76
  active: open,
@@ -79,7 +79,19 @@ export const CommandPalette = forwardRef(function CommandPalette({ descriptor, s
79
79
  ...(trigger === undefined ? {} : { fallbackReturnRef: triggerRef }),
80
80
  onEscape: () => requestClose("escape"),
81
81
  });
82
+ useEffect(() => {
83
+ if (open || !pendingActivation.current)
84
+ return;
85
+ const { itemId, reason } = pendingActivation.current;
86
+ pendingActivation.current = null;
87
+ // A controlled owner may delay closing; wait for teardown and focus release.
88
+ if (onActivateAfterDismiss)
89
+ queueMicrotask(() => onActivateAfterDismiss(itemId, reason));
90
+ }, [open, onActivateAfterDismiss]);
82
91
  const onKeyDown = (event) => {
92
+ // IME confirmation and candidate navigation belong to the text input.
93
+ if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
94
+ return;
83
95
  const intent = getCollectionNavigationIntent(event.key);
84
96
  if (intent) {
85
97
  const target = getCollectionNavigationTarget({ items: enabled }, active?.id ?? null, intent, true);
@@ -1 +1 @@
1
- {"version":3,"file":"command-palette.js","sourceRoot":"","sources":["../src/command-palette.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n canDismissCommandPalette,\n commandPaletteBehaviorDefaults,\n commandPaletteRecipe,\n validateCommandPaletteDescriptor,\n type CommandPaletteActivateHandler,\n type CommandPaletteActivateReason,\n type CommandPaletteDescriptor,\n type CommandPaletteDismissPolicy,\n type CommandPaletteDismissReason,\n type CommandPaletteOpenChangeDetails,\n type CommandPaletteQueryState,\n type CommandPaletteSource,\n} from \"@hjmds/design-contracts/components/command-palette\";\nimport {\n getCollectionNavigationIntent,\n getCollectionNavigationTarget,\n isComboboxResultCurrent,\n} from \"@hjmds/design-contracts/components/collection\";\nimport type { CommandPaletteItemDescriptor } from \"@hjmds/design-contracts/components/command-palette\";\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { classNames, composeRefs } from \"./internal.js\";\nimport { getModalLayer, HjmPortal, renderTrigger, useModalFocus, type OverlayTrigger } from \"./modal.js\";\n\ntype ResultSection<Key extends string> = Readonly<{\n id: string;\n label: string | undefined;\n name: string | undefined;\n items: readonly CommandPaletteItemDescriptor<Key>[];\n}>;\n\n// Local filter = Native Combobox substring rule; why not prefix/contracts:\n// docs/command-palette.md \"2026-10-06\" (comments ship in dist byte budgets).\nfunction resolveResults<Key extends string>(\n source: CommandPaletteSource<Key, string>,\n query: string,\n queryState: CommandPaletteQueryState | undefined,\n emptyMessage: string | undefined,\n) {\n const external = queryState?.filtering === \"external\";\n const needle = query.trim().toLocaleLowerCase();\n const keep = (item: CommandPaletteItemDescriptor<Key>) =>\n external || `${item.label} ${item.textValue}`.toLocaleLowerCase().includes(needle);\n const sections: ResultSection<Key>[] = (source.sections\n ? source.sections.map((section) => ({\n id: section.id,\n label: section.label,\n // validateCollection's precedence.\n name: section.accessibilityLabel?.trim() || section.label?.trim() || undefined,\n items: section.items.filter(keep),\n }))\n : [{ id: \"__all\", label: undefined, name: undefined, items: source.items.filter(keep) }]\n ).filter((section) => section.items.length > 0);\n const items = sections.flatMap((section) => section.items);\n const asyncState = queryState?.asyncState ?? { status: \"idle\" as const };\n const status = asyncState.status !== \"idle\"\n ? { kind: asyncState.status, message: asyncState.message }\n : items.length > 0 || emptyMessage === undefined ? null : { kind: \"empty\" as const, message: emptyMessage };\n return {\n sections,\n items,\n current: !external || isComboboxResultCurrent(queryState.queryValue, queryState.resultQuery),\n status,\n };\n}\n\nexport type CommandPaletteProps<Key extends string = string> = Readonly<{\n descriptor: CommandPaletteDescriptor;\n source: CommandPaletteSource<Key, string>;\n query: string;\n onQueryChange: (query: string) => void;\n onActivate: CommandPaletteActivateHandler<Key>;\n /** Runs after the palette is gone, for a command that opens the next surface. */\n onActivateAfterDismiss?: CommandPaletteActivateHandler<Key>;\n /**\n * Combobox's collection state. Omitted or `filtering: \"local\"` filters\n * `source` by `query` in the renderer (label/textValue substring); pass\n * `filtering: \"external\"` with `queryValue`/`resultQuery` when the product\n * already filtered or ranked the results (server or fuzzy search).\n */\n queryState?: CommandPaletteQueryState;\n dismissPolicy?: Partial<CommandPaletteDismissPolicy>;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, details: CommandPaletteOpenChangeDetails) => void;\n trigger?: OverlayTrigger;\n renderLeading?: (itemId: Key) => ReactNode;\n portalContainer?: HTMLElement;\n className?: string;\n}>;\n\nexport const CommandPalette = forwardRef(function CommandPalette<Key extends string = string>(\n {\n descriptor,\n source,\n query,\n onQueryChange,\n onActivate,\n onActivateAfterDismiss,\n queryState,\n dismissPolicy,\n open: openProp,\n defaultOpen,\n onOpenChange,\n trigger,\n renderLeading,\n portalContainer,\n className,\n }: CommandPaletteProps<Key>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n validateCommandPaletteDescriptor(descriptor);\n const policy: CommandPaletteDismissPolicy = { ...commandPaletteBehaviorDefaults, ...dismissPolicy };\n const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);\n const open = openProp ?? internalOpen;\n const results = useMemo(\n () => resolveResults<Key>(source, query, queryState, descriptor.emptyMessage),\n [source, query, queryState, descriptor.emptyMessage],\n );\n // Stale external rows stay visible but inert, as in the Native Combobox.\n const enabled = results.current ? results.items.filter((item) => !item.disabled) : [];\n const [activeId, setActiveId] = useState<Key | null>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const id = `${useId().replaceAll(\":\", \"\")}-command-palette`;\n const active = enabled.find((item) => item.id === activeId) ?? enabled[0] ?? null;\n\n useEffect(() => {\n // A new query builds a new result list; the first enabled row becomes active\n // so Enter always has an unambiguous target. Only the query resets it: products\n // often pass `source`/`queryState` as inline objects, and keying on their\n // identity snapped the active row back to the first item on every render\n // (2026-10-06 usage-guide audit). A row that disappears from the results\n // already falls back to the first enabled row through `active` above.\n setActiveId(null);\n }, [query]);\n\n const change = (next: boolean, reason: CommandPaletteOpenChangeDetails[\"reason\"]) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next, { reason });\n };\n const requestClose = (reason: CommandPaletteDismissReason) => {\n if (!canDismissCommandPalette(reason, policy)) return;\n change(false, reason);\n };\n const activate = (itemId: Key, reason: CommandPaletteActivateReason) => {\n onActivate(itemId, reason);\n // Running a command always closes the palette — `activation` is a dismiss\n // reason no policy can veto — and the follow-up surface opens after that.\n change(false, \"activation\");\n if (onActivateAfterDismiss) queueMicrotask(() => onActivateAfterDismiss(itemId, reason));\n };\n\n useModalFocus({\n active: open,\n contentRef,\n initialFocusRef: inputRef,\n ...(trigger === undefined ? {} : { fallbackReturnRef: triggerRef }),\n onEscape: () => requestClose(\"escape\"),\n });\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n const intent = getCollectionNavigationIntent(event.key as never);\n if (intent) {\n const target = getCollectionNavigationTarget({ items: enabled }, active?.id ?? null, intent, true);\n if (target === undefined) return;\n event.preventDefault();\n setActiveId(target as Key);\n return;\n }\n if (event.key === \"Enter\" && active) {\n event.preventDefault();\n activate(active.id as Key, \"keyboard\");\n }\n };\n\n const searchInput = (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={`${id}-results`}\n aria-activedescendant={active ? `${id}-${active.id}` : undefined}\n aria-label={descriptor.accessibilityLabel}\n placeholder={descriptor.searchPlaceholder}\n className=\"hjm-command-palette__search\"\n value={query}\n onChange={(event) => onQueryChange(event.target.value)}\n onKeyDown={onKeyDown}\n />\n );\n\n const renderedTrigger = trigger === undefined\n ? null\n : renderTrigger(trigger, triggerRef, open, id, \"dialog\", () => change(true, \"trigger\"));\n if (!open) return renderedTrigger;\n\n return (\n <>\n {renderedTrigger}\n <HjmPortal {...(portalContainer === undefined ? {} : { container: portalContainer })}>\n <div\n className=\"hjm-overlay hjm-command-palette-positioner\"\n data-kind=\"command-palette\"\n style={{ zIndex: getModalLayer(0) }}\n onMouseDown={(event) => { if (event.target === event.currentTarget) requestClose(\"outside\"); }}\n >\n <div\n ref={composeRefs(contentRef, forwardedRef)}\n id={id}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={descriptor.accessibilityLabel}\n data-hjm-modal-content=\"\"\n className={classNames(\"hjm-command-palette\", className)}\n style={{ maxInlineSize: commandPaletteRecipe.content.maxWidth, maxBlockSize: commandPaletteRecipe.content.maxHeight } as CSSProperties}\n >\n {/*\n Opt-in through descriptor.closeLabel because the renderer must not\n invent untranslated copy. Reuses Dialog's close chrome instead of a\n second close-button style (styles.css is at its byte budget).\n */}\n {descriptor.closeLabel ? (\n <div className=\"hjm-command-palette__search-row\">\n {searchInput}\n <button\n type=\"button\"\n className=\"hjm-dialog__close hjm-command-palette__close\"\n aria-label={descriptor.closeLabel}\n onClick={() => requestClose(\"close-action\")}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n ) : searchInput}\n <div id={`${id}-results`} role=\"listbox\" aria-label={descriptor.accessibilityLabel} className=\"hjm-command-palette__viewport\">\n {results.status === null ? null : (\n // Once for the whole list, never per section.\n <p\n className=\"hjm-command-palette__state\"\n data-kind={results.status.kind}\n role={results.status.kind === \"error\" ? \"alert\" : \"status\"}\n >\n {results.status.message}\n </p>\n )}\n {results.sections.map((section) => (\n <div\n key={section.id}\n className=\"hjm-command-palette__section\"\n {...(section.name === undefined ? { role: \"presentation\" } : { role: \"group\", \"aria-label\": section.name })}\n >\n {section.label ? <p className=\"hjm-command-palette__section-label\" aria-hidden=\"true\">{section.label}</p> : null}\n {section.items.map((item) => {\n // Positioning or filtering can enter a stationary cursor;\n // only mouse movement should override keyboard navigation.\n const inert = item.disabled === true || !results.current;\n return (\n <div\n key={item.id}\n id={`${id}-${item.id}`}\n role=\"option\"\n aria-selected={item.id === active?.id}\n aria-disabled={inert || undefined}\n className=\"hjm-command-palette__item\"\n data-tone={item.tone}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!inert) activate(item.id as Key, \"pointer\");\n }}\n onMouseMove={() => { if (!inert) setActiveId(item.id as Key); }}\n >\n {renderLeading?.(item.id as Key)}\n <span className=\"hjm-command-palette__copy\">\n <span>{item.label}</span>\n {item.description ? <span className=\"hjm-command-palette__description\">{item.description}</span> : null}\n </span>\n {item.shortcut ? <kbd className=\"hjm-command-palette__shortcut\">{item.shortcut}</kbd> : null}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n </div>\n </HjmPortal>\n </>\n );\n}) as <Key extends string = string>(\n props: CommandPaletteProps<Key> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement | null;\n"]}
1
+ {"version":3,"file":"command-palette.js","sourceRoot":"","sources":["../src/command-palette.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n canDismissCommandPalette,\n commandPaletteBehaviorDefaults,\n commandPaletteRecipe,\n validateCommandPaletteDescriptor,\n type CommandPaletteActivateHandler,\n type CommandPaletteActivateReason,\n type CommandPaletteDescriptor,\n type CommandPaletteDismissPolicy,\n type CommandPaletteDismissReason,\n type CommandPaletteOpenChangeDetails,\n type CommandPaletteQueryState,\n type CommandPaletteSource,\n} from \"@hjmds/design-contracts/components/command-palette\";\nimport {\n getCollectionNavigationIntent,\n getCollectionNavigationTarget,\n isComboboxResultCurrent,\n} from \"@hjmds/design-contracts/components/collection\";\nimport type { CommandPaletteItemDescriptor } from \"@hjmds/design-contracts/components/command-palette\";\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { classNames, composeRefs } from \"./internal.js\";\nimport { getModalLayer, HjmPortal, renderTrigger, useModalFocus, type OverlayTrigger } from \"./modal.js\";\n\ntype ResultSection<Key extends string> = Readonly<{\n id: string;\n label: string | undefined;\n name: string | undefined;\n items: readonly CommandPaletteItemDescriptor<Key>[];\n}>;\n\n// Local filter = Native Combobox substring rule; why not prefix/contracts:\n// docs/command-palette.md \"2026-10-06\" (comments ship in dist byte budgets).\nfunction resolveResults<Key extends string>(\n source: CommandPaletteSource<Key, string>,\n query: string,\n queryState: CommandPaletteQueryState | undefined,\n emptyMessage: string | undefined,\n) {\n const external = queryState?.filtering === \"external\";\n const needle = query.trim().toLocaleLowerCase();\n const keep = (item: CommandPaletteItemDescriptor<Key>) =>\n external || `${item.label} ${item.textValue}`.toLocaleLowerCase().includes(needle);\n const sections: ResultSection<Key>[] = (source.sections\n ? source.sections.map((section) => ({\n id: section.id,\n label: section.label,\n // validateCollection's precedence.\n name: section.accessibilityLabel?.trim() || section.label?.trim() || undefined,\n items: section.items.filter(keep),\n }))\n : [{ id: \"__all\", label: undefined, name: undefined, items: source.items.filter(keep) }]\n ).filter((section) => section.items.length > 0);\n const items = sections.flatMap((section) => section.items);\n const asyncState = queryState?.asyncState ?? { status: \"idle\" as const };\n const status = asyncState.status !== \"idle\"\n ? { kind: asyncState.status, message: asyncState.message }\n : items.length > 0 || emptyMessage === undefined ? null : { kind: \"empty\" as const, message: emptyMessage };\n return {\n sections,\n items,\n current: !external || isComboboxResultCurrent(queryState.queryValue, queryState.resultQuery),\n status,\n };\n}\n\nexport type CommandPaletteProps<Key extends string = string> = Readonly<{\n descriptor: CommandPaletteDescriptor;\n source: CommandPaletteSource<Key, string>;\n query: string;\n onQueryChange: (query: string) => void;\n onActivate: CommandPaletteActivateHandler<Key>;\n /** Runs after the palette is gone, for a command that opens the next surface. */\n onActivateAfterDismiss?: CommandPaletteActivateHandler<Key>;\n /**\n * Combobox's collection state. Omitted or `filtering: \"local\"` filters\n * `source` by `query` in the renderer (label/textValue substring); pass\n * `filtering: \"external\"` with `queryValue`/`resultQuery` when the product\n * already filtered or ranked the results (server or fuzzy search).\n */\n queryState?: CommandPaletteQueryState;\n dismissPolicy?: Partial<CommandPaletteDismissPolicy>;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, details: CommandPaletteOpenChangeDetails) => void;\n trigger?: OverlayTrigger;\n renderLeading?: (itemId: Key) => ReactNode;\n portalContainer?: HTMLElement;\n className?: string;\n}>;\n\nexport const CommandPalette = forwardRef(function CommandPalette<Key extends string = string>(\n {\n descriptor,\n source,\n query,\n onQueryChange,\n onActivate,\n onActivateAfterDismiss,\n queryState,\n dismissPolicy,\n open: openProp,\n defaultOpen,\n onOpenChange,\n trigger,\n renderLeading,\n portalContainer,\n className,\n }: CommandPaletteProps<Key>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n validateCommandPaletteDescriptor(descriptor);\n const policy: CommandPaletteDismissPolicy = { ...commandPaletteBehaviorDefaults, ...dismissPolicy };\n const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);\n const open = openProp ?? internalOpen;\n const results = useMemo(\n () => resolveResults<Key>(source, query, queryState, descriptor.emptyMessage),\n [source, query, queryState, descriptor.emptyMessage],\n );\n // Stale external rows stay visible but inert, as in the Native Combobox.\n const enabled = results.current ? results.items.filter((item) => !item.disabled) : [];\n const [activeId, setActiveId] = useState<Key | null>(null);\n const pendingActivation = useRef<{ itemId: Key; reason: CommandPaletteActivateReason } | null>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const id = `${useId().replaceAll(\":\", \"\")}-command-palette`;\n const active = enabled.find((item) => item.id === activeId) ?? enabled[0] ?? null;\n\n useEffect(() => {\n // A new query builds a new result list; the first enabled row becomes active\n // so Enter always has an unambiguous target. Only the query resets it: products\n // often pass `source`/`queryState` as inline objects, and keying on their\n // identity snapped the active row back to the first item on every render\n // (2026-10-06 usage-guide audit). A row that disappears from the results\n // already falls back to the first enabled row through `active` above.\n setActiveId(null);\n }, [query]);\n\n const change = (next: boolean, reason: CommandPaletteOpenChangeDetails[\"reason\"]) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next, { reason });\n };\n const requestClose = (reason: CommandPaletteDismissReason) => {\n if (!canDismissCommandPalette(reason, policy)) return;\n change(false, reason);\n };\n const activate = (itemId: Key, reason: CommandPaletteActivateReason) => {\n onActivate(itemId, reason);\n // Running a command always closes the palette — `activation` is a dismiss\n // reason no policy can veto — and the follow-up surface opens after that.\n pendingActivation.current = { itemId, reason };\n change(false, \"activation\");\n };\n\n useModalFocus({\n active: open,\n contentRef,\n initialFocusRef: inputRef,\n ...(trigger === undefined ? {} : { fallbackReturnRef: triggerRef }),\n onEscape: () => requestClose(\"escape\"),\n });\n\n useEffect(() => {\n if (open || !pendingActivation.current) return;\n const { itemId, reason } = pendingActivation.current;\n pendingActivation.current = null;\n // A controlled owner may delay closing; wait for teardown and focus release.\n if (onActivateAfterDismiss) queueMicrotask(() => onActivateAfterDismiss(itemId, reason));\n }, [open, onActivateAfterDismiss]);\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n // IME confirmation and candidate navigation belong to the text input.\n if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;\n const intent = getCollectionNavigationIntent(event.key as never);\n if (intent) {\n const target = getCollectionNavigationTarget({ items: enabled }, active?.id ?? null, intent, true);\n if (target === undefined) return;\n event.preventDefault();\n setActiveId(target as Key);\n return;\n }\n if (event.key === \"Enter\" && active) {\n event.preventDefault();\n activate(active.id as Key, \"keyboard\");\n }\n };\n\n const searchInput = (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={`${id}-results`}\n aria-activedescendant={active ? `${id}-${active.id}` : undefined}\n aria-label={descriptor.accessibilityLabel}\n placeholder={descriptor.searchPlaceholder}\n className=\"hjm-command-palette__search\"\n value={query}\n onChange={(event) => onQueryChange(event.target.value)}\n onKeyDown={onKeyDown}\n />\n );\n\n const renderedTrigger = trigger === undefined\n ? null\n : renderTrigger(trigger, triggerRef, open, id, \"dialog\", () => change(true, \"trigger\"));\n if (!open) return renderedTrigger;\n\n return (\n <>\n {renderedTrigger}\n <HjmPortal {...(portalContainer === undefined ? {} : { container: portalContainer })}>\n <div\n className=\"hjm-overlay hjm-command-palette-positioner\"\n data-kind=\"command-palette\"\n style={{ zIndex: getModalLayer(0) }}\n onMouseDown={(event) => { if (event.target === event.currentTarget) requestClose(\"outside\"); }}\n >\n <div\n ref={composeRefs(contentRef, forwardedRef)}\n id={id}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={descriptor.accessibilityLabel}\n data-hjm-modal-content=\"\"\n className={classNames(\"hjm-command-palette\", className)}\n style={{ maxInlineSize: commandPaletteRecipe.content.maxWidth, maxBlockSize: commandPaletteRecipe.content.maxHeight } as CSSProperties}\n >\n {/*\n Opt-in through descriptor.closeLabel because the renderer must not\n invent untranslated copy. Reuses Dialog's close chrome instead of a\n second close-button style (styles.css is at its byte budget).\n */}\n {descriptor.closeLabel ? (\n <div className=\"hjm-command-palette__search-row\">\n {searchInput}\n <button\n type=\"button\"\n className=\"hjm-dialog__close hjm-command-palette__close\"\n aria-label={descriptor.closeLabel}\n onClick={() => requestClose(\"close-action\")}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n ) : searchInput}\n <div id={`${id}-results`} role=\"listbox\" aria-label={descriptor.accessibilityLabel} className=\"hjm-command-palette__viewport\">\n {results.status === null ? null : (\n // Once for the whole list, never per section.\n <p\n className=\"hjm-command-palette__state\"\n data-kind={results.status.kind}\n role={results.status.kind === \"error\" ? \"alert\" : \"status\"}\n >\n {results.status.message}\n </p>\n )}\n {results.sections.map((section) => (\n <div\n key={section.id}\n className=\"hjm-command-palette__section\"\n {...(section.name === undefined ? { role: \"presentation\" } : { role: \"group\", \"aria-label\": section.name })}\n >\n {section.label ? <p className=\"hjm-command-palette__section-label\" aria-hidden=\"true\">{section.label}</p> : null}\n {section.items.map((item) => {\n // Positioning or filtering can enter a stationary cursor;\n // only mouse movement should override keyboard navigation.\n const inert = item.disabled === true || !results.current;\n return (\n <div\n key={item.id}\n id={`${id}-${item.id}`}\n role=\"option\"\n aria-selected={item.id === active?.id}\n aria-disabled={inert || undefined}\n className=\"hjm-command-palette__item\"\n data-tone={item.tone}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!inert) activate(item.id as Key, \"pointer\");\n }}\n onMouseMove={() => { if (!inert) setActiveId(item.id as Key); }}\n >\n {renderLeading?.(item.id as Key)}\n <span className=\"hjm-command-palette__copy\">\n <span>{item.label}</span>\n {item.description ? <span className=\"hjm-command-palette__description\">{item.description}</span> : null}\n </span>\n {item.shortcut ? <kbd className=\"hjm-command-palette__shortcut\">{item.shortcut}</kbd> : null}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n </div>\n </HjmPortal>\n </>\n );\n}) as <Key extends string = string>(\n props: CommandPaletteProps<Key> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement | null;\n"]}
@@ -3,6 +3,7 @@ import { type ReactNode, type RefObject } from "react";
3
3
  import type { HjmCompositionStyleProp } from "./composition-style.js";
4
4
  export type ContentTransitionProps = {
5
5
  preset?: ContentTransitionPreset;
6
+ travelDirection?: "forward" | "backward";
6
7
  stateKey: string;
7
8
  children: ReactNode;
8
9
  motion?: "system" | "none";
@@ -17,7 +18,7 @@ export type ContentTransitionProps = {
17
18
  };
18
19
  /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
19
20
  * No exiting interactive copy: it would duplicate fields and focus targets. See THIRD_PARTY_NOTICES. */
20
- export declare function ContentTransition({ stateKey, children, motion: preference, preset: suppliedPreset, focusTarget, animateHeight, enterOnMount, layoutStyle }: ContentTransitionProps): import("react").JSX.Element;
21
+ export declare function ContentTransition({ stateKey, children, motion: preference, preset: suppliedPreset, travelDirection, focusTarget, animateHeight, enterOnMount, layoutStyle }: ContentTransitionProps): import("react").JSX.Element;
21
22
  export type TextTransitionProps = {
22
23
  preset?: ContentTransitionPreset;
23
24
  text: string;
@@ -1 +1 @@
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;AAG7F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,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,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qEAAqE;IACrE,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,+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,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,aAAqB,EAAE,YAAoB,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BAmD7M;AACD,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,uBAAuB,CAAA;CAAE,CAAC;AACxJ,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,mBAAmB,+BAEpG"}
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;AAG7F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAAC,eAAe,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAC3E,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAClE,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qEAAqE;IACrE,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,+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,MAAM,EAAE,cAAc,EAAE,eAA2B,EAAE,WAAW,EAAE,aAAqB,EAAE,YAAoB,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BAmD1O;AACD,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,uBAAuB,CAAA;CAAE,CAAC;AACxJ,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,mBAAmB,+BAEpG"}
@@ -6,10 +6,10 @@ import { easing, motion as timing } from "@hjmds/design-contracts/foundations";
6
6
  import { useHjmTheme } from "./provider.js";
7
7
  /** Motion Primitives' keyed transition pattern, adapted to HJM's single active subtree.
8
8
  * No exiting interactive copy: it would duplicate fields and focus targets. See THIRD_PARTY_NOTICES. */
9
- export function ContentTransition({ stateKey, children, motion: preference = "system", preset: suppliedPreset, focusTarget, animateHeight = false, enterOnMount = false, layoutStyle }) {
9
+ export function ContentTransition({ stateKey, children, motion: preference = "system", preset: suppliedPreset, travelDirection = "forward", focusTarget, animateHeight = false, enterOnMount = false, layoutStyle }) {
10
10
  const { environment, designProfile } = useHjmTheme();
11
11
  const preset = suppliedPreset ?? designProfile?.interactions.contentTransition ?? "fade";
12
- const from = resolveContentTransition(preset, environment.direction);
12
+ const from = resolveContentTransition(preset, environment.direction, travelDirection);
13
13
  const enabled = preference !== "none" && !environment.reducedMotion;
14
14
  const frame = useRef(null);
15
15
  const measure = useRef(null);
@@ -1 +1 @@
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\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type ContentTransitionProps = {\n preset?: ContentTransitionPreset;\n stateKey: string; children: ReactNode; motion?: \"system\" | \"none\";\n /** Opt-in measured height transition; never renders a second interactive panel. */\n animateHeight?: boolean;\n /** Animate newly inserted content without changing its stable key or delaying its data. */\n enterOnMount?: boolean;\n /** Layout belongs to the stable outer frame, not the keyed panel. */\n layoutStyle?: HjmCompositionStyleProp;\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: suppliedPreset, focusTarget, animateHeight = false, enterOnMount = false, layoutStyle }: ContentTransitionProps) {\n const { environment, designProfile } = useHjmTheme();\n const preset = suppliedPreset ?? designProfile?.interactions.contentTransition ?? \"fade\";\n const from = resolveContentTransition(preset, environment.direction);\n const enabled = preference !== \"none\" && !environment.reducedMotion;\n const frame = useRef<HTMLDivElement>(null);\n const measure = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const outer = frame.current; const inner = measure.current;\n if (!outer || !inner || !animateHeight || !enabled || typeof ResizeObserver === \"undefined\") return;\n let previous = inner.getBoundingClientRect().height;\n let animation: Animation | undefined;\n const settle = () => { animation?.cancel(); animation = undefined; outer.style.height = \"\"; outer.style.overflow = \"\"; previous = inner.getBoundingClientRect().height; };\n const observer = new ResizeObserver(() => {\n const next = inner.getBoundingClientRect().height;\n if (Math.abs(next - previous) < 0.5) return;\n const fromHeight = animation ? outer.getBoundingClientRect().height : previous;\n animation?.cancel(); previous = next;\n if (document.hidden || typeof outer.animate !== \"function\") { settle(); return; }\n // Measure the unconfined inner flow; transform/layout projection would scale\n // text or duplicate input subtrees. WAAPI affects only the surrounding height.\n outer.style.height = `${next}px`; outer.style.overflow = \"hidden\";\n try {\n const current = outer.animate([{ height: `${fromHeight}px` }, { height: `${next}px` }], { duration: timing.normal, easing: `cubic-bezier(${easing.enter.join(\",\")})` });\n animation = current;\n current.onfinish = () => { if (animation === current) settle(); };\n } catch { settle(); }\n });\n observer.observe(inner);\n const visibility = () => { if (document.hidden) settle(); };\n document.addEventListener(\"visibilitychange\", visibility);\n return () => { observer.disconnect(); document.removeEventListener(\"visibilitychange\", visibility); settle(); };\n }, [animateHeight, enabled]);\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 // Caller placement stays on a separate stable wrapper so measuring and\n // animating the inner flow does not include the product margin or flex placement.\n return <div style={layoutStyle}><div ref={frame}><div ref={measure} style={{ display: \"flow-root\" }}><motion.div ref={capturePanel} key={stateKey} initial={enabled && (enterOnMount || !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></div></div>;\n}\nexport type TextTransitionProps = { preset?: ContentTransitionPreset; text: string; motion?: \"system\" | \"none\"; layoutStyle?: HjmCompositionStyleProp };\n/** Whole-text fade preserves graphemes, text wrapping, selection and a single spoken value. */\nexport function TextTransition({ text, motion: preference, preset, layoutStyle }: TextTransitionProps) {\n return <ContentTransition stateKey={text} {...(preset ? { preset } : {})} {...(preference ? { motion: preference } : {})} {...(layoutStyle ? { layoutStyle } : {})}>{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\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type ContentTransitionProps = {\n preset?: ContentTransitionPreset; travelDirection?: \"forward\" | \"backward\";\n stateKey: string; children: ReactNode; motion?: \"system\" | \"none\";\n /** Opt-in measured height transition; never renders a second interactive panel. */\n animateHeight?: boolean;\n /** Animate newly inserted content without changing its stable key or delaying its data. */\n enterOnMount?: boolean;\n /** Layout belongs to the stable outer frame, not the keyed panel. */\n layoutStyle?: HjmCompositionStyleProp;\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: suppliedPreset, travelDirection = \"forward\", focusTarget, animateHeight = false, enterOnMount = false, layoutStyle }: ContentTransitionProps) {\n const { environment, designProfile } = useHjmTheme();\n const preset = suppliedPreset ?? designProfile?.interactions.contentTransition ?? \"fade\";\n const from = resolveContentTransition(preset, environment.direction, travelDirection);\n const enabled = preference !== \"none\" && !environment.reducedMotion;\n const frame = useRef<HTMLDivElement>(null);\n const measure = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const outer = frame.current; const inner = measure.current;\n if (!outer || !inner || !animateHeight || !enabled || typeof ResizeObserver === \"undefined\") return;\n let previous = inner.getBoundingClientRect().height;\n let animation: Animation | undefined;\n const settle = () => { animation?.cancel(); animation = undefined; outer.style.height = \"\"; outer.style.overflow = \"\"; previous = inner.getBoundingClientRect().height; };\n const observer = new ResizeObserver(() => {\n const next = inner.getBoundingClientRect().height;\n if (Math.abs(next - previous) < 0.5) return;\n const fromHeight = animation ? outer.getBoundingClientRect().height : previous;\n animation?.cancel(); previous = next;\n if (document.hidden || typeof outer.animate !== \"function\") { settle(); return; }\n // Measure the unconfined inner flow; transform/layout projection would scale\n // text or duplicate input subtrees. WAAPI affects only the surrounding height.\n outer.style.height = `${next}px`; outer.style.overflow = \"hidden\";\n try {\n const current = outer.animate([{ height: `${fromHeight}px` }, { height: `${next}px` }], { duration: timing.normal, easing: `cubic-bezier(${easing.enter.join(\",\")})` });\n animation = current;\n current.onfinish = () => { if (animation === current) settle(); };\n } catch { settle(); }\n });\n observer.observe(inner);\n const visibility = () => { if (document.hidden) settle(); };\n document.addEventListener(\"visibilitychange\", visibility);\n return () => { observer.disconnect(); document.removeEventListener(\"visibilitychange\", visibility); settle(); };\n }, [animateHeight, enabled]);\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 // Caller placement stays on a separate stable wrapper so measuring and\n // animating the inner flow does not include the product margin or flex placement.\n return <div style={layoutStyle}><div ref={frame}><div ref={measure} style={{ display: \"flow-root\" }}><motion.div ref={capturePanel} key={stateKey} initial={enabled && (enterOnMount || !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></div></div>;\n}\nexport type TextTransitionProps = { preset?: ContentTransitionPreset; text: string; motion?: \"system\" | \"none\"; layoutStyle?: HjmCompositionStyleProp };\n/** Whole-text fade preserves graphemes, text wrapping, selection and a single spoken value. */\nexport function TextTransition({ text, motion: preference, preset, layoutStyle }: TextTransitionProps) {\n return <ContentTransition stateKey={text} {...(preset ? { preset } : {})} {...(preference ? { motion: preference } : {})} {...(layoutStyle ? { layoutStyle } : {})}>{text}</ContentTransition>;\n}\n"]}
@@ -1 +1 @@
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;AAGf,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,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;IACnB,0GAA0G;IAC1G,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAIH,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EACvD,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,EAAE,gBAAgB,CAAC,GAAG,CAAC,+BA2KvB"}
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,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,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;IACnB,0GAA0G;IAC1G,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAIH,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EACvD,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,EAAE,gBAAgB,CAAC,GAAG,CAAC,+BA+LvB"}
@@ -4,7 +4,8 @@ import { resolveContextMenuAnchor, } from "@hjmds/design-contracts/components/co
4
4
  import { menuRecipe } from "@hjmds/design-contracts/recipes";
5
5
  import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, } from "react";
6
6
  import { classNames } from "./internal.js";
7
- import { HjmPortal, getModalLayer } from "./modal.js";
7
+ import { AnchoredPortal } from "./portal.js";
8
+ import { getModalLayer } from "./modal.js";
8
9
  const longPressDelay = 500;
9
10
  export function ContextMenu({ children, items, accessibilityLabel, onAction, className, layoutStyle, }) {
10
11
  const hostRef = useRef(null);
@@ -15,9 +16,23 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
15
16
  const [anchor, setAnchor] = useState(null);
16
17
  const [activeIndex, setActiveIndex] = useState(0);
17
18
  const longPress = useRef(undefined);
19
+ const pressOrigin = useRef(null);
20
+ const cancelLongPress = useCallback(() => {
21
+ if (longPress.current !== undefined)
22
+ clearTimeout(longPress.current);
23
+ longPress.current = undefined;
24
+ pressOrigin.current = null;
25
+ }, []);
26
+ useEffect(() => cancelLongPress, [cancelLongPress]);
18
27
  const typeahead = useRef({ value: "", time: 0 });
19
28
  const enabled = items.filter((item) => !item.disabled);
29
+ const resolvedActiveIndex = Math.min(activeIndex, Math.max(0, enabled.length - 1));
30
+ useLayoutEffect(() => {
31
+ if (activeIndex !== resolvedActiveIndex)
32
+ setActiveIndex(resolvedActiveIndex);
33
+ }, [activeIndex, resolvedActiveIndex]);
20
34
  const open = useCallback((reason, pointer) => {
35
+ cancelLongPress();
21
36
  const activeElement = document.activeElement;
22
37
  returnFocusRef.current = activeElement instanceof HTMLElement && hostRef.current?.contains(activeElement)
23
38
  ? activeElement
@@ -26,7 +41,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
26
41
  setAnchor(resolveContextMenuAnchor(reason, pointer, rect === null ? null : { left: rect.left, bottom: rect.bottom }));
27
42
  setActiveIndex(0);
28
43
  typeahead.current = { value: "", time: 0 };
29
- }, []);
44
+ }, [cancelLongPress]);
30
45
  const setMenuRef = useCallback((node) => {
31
46
  menuRef.current = node;
32
47
  setMenuMounted(node !== null);
@@ -67,7 +82,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
67
82
  return;
68
83
  }
69
84
  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());
85
+ const result = resolveMenuTypeahead(enabled, resolvedActiveIndex, event.key, typeahead.current, Date.now());
71
86
  typeahead.current = result.state;
72
87
  if (result.index !== undefined) {
73
88
  event.preventDefault();
@@ -77,7 +92,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
77
92
  }
78
93
  if (event.key === "Enter" || event.key === " ") {
79
94
  event.preventDefault();
80
- const item = enabled[activeIndex];
95
+ const item = enabled[resolvedActiveIndex];
81
96
  if (item) {
82
97
  onAction(item.id);
83
98
  setAnchor(null);
@@ -100,7 +115,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
100
115
  document.removeEventListener("keydown", onKeyDown);
101
116
  document.removeEventListener("pointerdown", onPointerDown);
102
117
  };
103
- }, [anchor, activeIndex, enabled, onAction]);
118
+ }, [anchor, resolvedActiveIndex, enabled, onAction]);
104
119
  return (_jsxs("div", { ref: hostRef, tabIndex: 0, className: classNames("hjm-context-menu-host", className), style: layoutStyle, onContextMenu: (event) => {
105
120
  event.preventDefault();
106
121
  open("pointer", { x: event.clientX, y: event.clientY });
@@ -114,10 +129,16 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
114
129
  }, onPointerDown: (event) => {
115
130
  if (event.pointerType !== "touch")
116
131
  return;
117
- longPress.current = setTimeout(() => open("longPress", { x: event.clientX, y: event.clientY }), longPressDelay);
118
- }, onPointerUp: () => { if (longPress.current !== undefined)
119
- clearTimeout(longPress.current); }, onPointerCancel: () => { if (longPress.current !== undefined)
120
- clearTimeout(longPress.current); }, children: [children, anchor !== null ? (_jsx(HjmPortal, { children: _jsx("div", { role: "menu", id: menuId, ref: setMenuRef, tabIndex: -1, "aria-activedescendant": enabled[activeIndex] ? `${menuId}-item-${activeIndex}` : undefined, "aria-label": accessibilityLabel, className: "hjm-context-menu", style: {
132
+ cancelLongPress();
133
+ const origin = { x: event.clientX, y: event.clientY };
134
+ pressOrigin.current = origin;
135
+ longPress.current = setTimeout(() => open("longPress", origin), longPressDelay);
136
+ }, onPointerMove: (event) => {
137
+ const origin = pressOrigin.current;
138
+ // Treat a 10px move as scrolling, while tolerating small touch jitter.
139
+ if (origin && Math.hypot(event.clientX - origin.x, event.clientY - origin.y) >= 10)
140
+ cancelLongPress();
141
+ }, onPointerUp: cancelLongPress, onPointerCancel: cancelLongPress, children: [children, anchor !== null ? (_jsx(AnchoredPortal, { anchorRef: hostRef, children: _jsx("div", { role: "menu", id: menuId, ref: setMenuRef, tabIndex: -1, "aria-activedescendant": enabled[resolvedActiveIndex] ? `${menuId}-item-${resolvedActiveIndex}` : undefined, "aria-label": accessibilityLabel, className: "hjm-context-menu", style: {
121
142
  top: anchor.y,
122
143
  left: anchor.x,
123
144
  zIndex: getModalLayer(0),
@@ -126,7 +147,7 @@ export function ContextMenu({ children, items, accessibilityLabel, onAction, cla
126
147
  // A newly positioned menu can enter a stationary cursor; that
127
148
  // boundary event must not undo the user's keyboard selection.
128
149
  const index = enabled.findIndex((candidate) => candidate.id === item.id);
129
- return (_jsxs("div", { id: index >= 0 ? `${menuId}-item-${index}` : undefined, role: "menuitem", "aria-disabled": item.disabled || undefined, "data-active": index === activeIndex && !item.disabled ? "" : undefined, "data-tone": item.tone, className: "hjm-context-menu__item", onMouseMove: () => { if (index >= 0)
150
+ return (_jsxs("div", { id: index >= 0 ? `${menuId}-item-${index}` : undefined, role: "menuitem", "aria-disabled": item.disabled || undefined, "data-active": index === resolvedActiveIndex && !item.disabled ? "" : undefined, "data-tone": item.tone, className: "hjm-context-menu__item", onMouseMove: () => { if (index >= 0)
130
151
  setActiveIndex(index); }, onClick: () => {
131
152
  if (item.disabled)
132
153
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../src/context-menu.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,wBAAwB,GAIzB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EACL,WAAW,EACX,SAAS,EACT,KAAK,EACL,eAAe,EACf,MAAM,EACN,QAAQ,GAGT,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AActD,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,MAAM,UAAU,WAAW,CAA8B,EACvD,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,WAAW,GACW;IACtB,MAAM,OAAO,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,cAAc,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC;IACvB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA2B,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAA4C,SAAS,CAAC,CAAC;IAC/E,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,MAA6B,EAAE,OAAiC,EAAE,EAAE;QAC5F,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC;QAC7C,cAAc,CAAC,OAAO,GAAG,aAAa,YAAY,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC;YACvG,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACpB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,qBAAqB,EAAE,IAAI,IAAI,CAAC;QAC9D,SAAS,CAAC,wBAAwB,CAChC,MAAM,EACN,OAAO,EACP,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAChE,CAAC,CAAC;QACH,cAAc,CAAC,CAAC,CAAC,CAAC;QAClB,SAAS,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC7C,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QAC7D,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,cAAc,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QACxE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;QACrD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACvF,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACzF,2JAA2J;QAC3J,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;YAAE,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAE1B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,SAAS,GAAG,CAAC,KAAoB,EAAE,EAAE;YACzC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC3B,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,SAAS,CAAC,IAAI,CAAC,CAAC;gBAChB,mEAAmE;gBACnE,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;gBAChC,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBACzD,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,cAAc,CAAC,CAAC,QAAQ,EAAE,EAAE;oBAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;oBACrE,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC/D,CAAC,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;gBAChD,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,cAAc,CAAC,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC3E,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACrG,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;gBACpG,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;gBACjC,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC/B,KAAK,CAAC,cAAc,EAAE,CAAC;oBACvB,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC/B,CAAC;gBACD,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAC/C,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;gBAClC,IAAI,IAAI,EAAE,CAAC;oBAAC,QAAQ,CAAC,IAAI,CAAC,EAAS,CAAC,CAAC;oBAAC,SAAS,CAAC,IAAI,CAAC,CAAC;oBAAC,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;gBAAC,CAAC;gBACzF,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;gBACxB,SAAS,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,aAAa,GAAG,CAAC,KAAmB,EAAE,EAAE;YAC5C,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChF,SAAS,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC;QACF,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAChD,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACxD,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACnD,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAC7D,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE7C,OAAO,CACL,eACE,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,UAAU,CAAC,uBAAuB,EAAE,SAAS,CAAC,EACzD,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;YACvB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC,EACD,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,sEAAsE;YACtE,6DAA6D;YAC7D,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,aAAa,EAAE,CAAC;gBAC3E,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,EACD,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;YACvB,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO;gBAAE,OAAO;YAC1C,SAAS,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;QAClH,CAAC,EACD,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS;YAAE,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC5F,eAAe,EAAE,GAAG,EAAE,GAAG,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS;YAAE,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAE/F,QAAQ,EACR,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CACjB,KAAC,SAAS,cACR,cACE,IAAI,EAAC,MAAM,EACX,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,CAAC,CAAC,2BACW,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,SAAS,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,gBAC7E,kBAAkB,EAC9B,SAAS,EAAC,kBAAkB,EAC5B,KAAK,EAAE;wBACL,GAAG,EAAE,MAAM,CAAC,CAAC;wBACb,IAAI,EAAE,MAAM,CAAC,CAAC;wBACd,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;wBACxB,aAAa,EAAE,UAAU,CAAC,QAAQ;qBAClB,YAEjB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;wBAClB,8DAA8D;wBAC9D,8DAA8D;wBAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;wBACzE,OAAO,CACL,eAEE,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,EACtD,IAAI,EAAC,UAAU,mBACA,IAAI,CAAC,QAAQ,IAAI,SAAS,iBAC5B,KAAK,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,eAC1D,IAAI,CAAC,IAAI,EACpB,SAAS,EAAC,wBAAwB,EAClC,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;gCAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAC7D,OAAO,EAAE,GAAG,EAAE;gCACZ,IAAI,IAAI,CAAC,QAAQ;oCAAE,OAAO;gCAC1B,QAAQ,CAAC,IAAI,CAAC,EAAS,CAAC,CAAC;gCACzB,SAAS,CAAC,IAAI,CAAC,CAAC;gCAChB,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;4BAClC,CAAC,aAED,yBAAO,IAAI,CAAC,KAAK,GAAQ,EACxB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,4BAA4B,YAAE,IAAI,CAAC,QAAQ,GAAO,CAAC,CAAC,CAAC,IAAI,KAhBpF,IAAI,CAAC,EAAE,CAiBR,CACP,CAAC;oBACJ,CAAC,CAAC,GACE,GACI,CACb,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC","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\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.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 /** Canonical layout-only placement on the in-flow host; the menu popup itself is anchored, not placed. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nconst longPressDelay = 500;\n\nexport function ContextMenu<Key extends string = string>({\n children,\n items,\n accessibilityLabel,\n onAction,\n className,\n layoutStyle,\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 style={layoutStyle}\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 // A newly positioned menu can enter a stationary cursor; that\n // boundary event must not undo the user's keyboard selection.\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 onMouseMove={() => { 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 { AnchoredPortal } from \"./portal.js\";\nimport { getModalLayer } from \"./modal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.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 /** Canonical layout-only placement on the in-flow host; the menu popup itself is anchored, not placed. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nconst longPressDelay = 500;\n\nexport function ContextMenu<Key extends string = string>({\n children,\n items,\n accessibilityLabel,\n onAction,\n className,\n layoutStyle,\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 pressOrigin = useRef<ContextMenuAnchor | null>(null);\n const cancelLongPress = useCallback(() => {\n if (longPress.current !== undefined) clearTimeout(longPress.current);\n longPress.current = undefined;\n pressOrigin.current = null;\n }, []);\n useEffect(() => cancelLongPress, [cancelLongPress]);\n const typeahead = useRef({ value: \"\", time: 0 });\n const enabled = items.filter((item) => !item.disabled);\n const resolvedActiveIndex = Math.min(activeIndex, Math.max(0, enabled.length - 1));\n useLayoutEffect(() => {\n if (activeIndex !== resolvedActiveIndex) setActiveIndex(resolvedActiveIndex);\n }, [activeIndex, resolvedActiveIndex]);\n\n const open = useCallback((reason: ContextMenuOpenReason, pointer: ContextMenuAnchor | null) => {\n cancelLongPress();\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 }, [cancelLongPress]);\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, resolvedActiveIndex, 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[resolvedActiveIndex];\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, resolvedActiveIndex, enabled, onAction]);\n\n return (\n <div\n ref={hostRef}\n tabIndex={0}\n className={classNames(\"hjm-context-menu-host\", className)}\n style={layoutStyle}\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 cancelLongPress();\n const origin = { x: event.clientX, y: event.clientY };\n pressOrigin.current = origin;\n longPress.current = setTimeout(() => open(\"longPress\", origin), longPressDelay);\n }}\n onPointerMove={(event) => {\n const origin = pressOrigin.current;\n // Treat a 10px move as scrolling, while tolerating small touch jitter.\n if (origin && Math.hypot(event.clientX - origin.x, event.clientY - origin.y) >= 10) cancelLongPress();\n }}\n onPointerUp={cancelLongPress}\n onPointerCancel={cancelLongPress}\n >\n {children}\n {anchor !== null ? (\n <AnchoredPortal anchorRef={hostRef}>\n <div\n role=\"menu\"\n id={menuId}\n ref={setMenuRef}\n tabIndex={-1}\n aria-activedescendant={enabled[resolvedActiveIndex] ? `${menuId}-item-${resolvedActiveIndex}` : 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 // A newly positioned menu can enter a stationary cursor; that\n // boundary event must not undo the user's keyboard selection.\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 === resolvedActiveIndex && !item.disabled ? \"\" : undefined}\n data-tone={item.tone}\n className=\"hjm-context-menu__item\"\n onMouseMove={() => { 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 </AnchoredPortal>\n ) : null}\n </div>\n );\n}\n"]}
@@ -4,6 +4,6 @@ export type DateEntryProps = DateEntryControlProps & Readonly<{
4
4
  className?: string;
5
5
  layoutStyle?: HjmCompositionStyleProp;
6
6
  }>;
7
- /** Direct date drafts reuse TextField; Calendar/DatePicker continue to own calendar selection. */
7
+ /** Date policy stays here; FieldGroup owns related-input semantics, feedback and edit lifetime. */
8
8
  export declare function DateEntry(props: DateEntryProps): import("react").JSX.Element;
9
9
  //# sourceMappingURL=date-entry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"date-entry.d.ts","sourceRoot":"","sources":["../src/date-entry.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE/H,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAAC;AAC7H,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAoB9C"}
1
+ {"version":3,"file":"date-entry.d.ts","sourceRoot":"","sources":["../src/date-entry.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAG/H,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAAC;AAC7H,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAuB9C"}
@@ -1,13 +1,26 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId } from "react";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
3
  import { resolveDateEntryControl, updateDateEntryDraft } from "@hjmds/design-contracts/date-entry";
4
+ import { FieldGroup } from "./field-group.js";
4
5
  import { TextField } from "./forms.js";
5
- /** Direct date drafts reuse TextField; Calendar/DatePicker continue to own calendar selection. */
6
+ /** Date policy stays here; FieldGroup owns related-input semantics, feedback and edit lifetime. */
6
7
  export function DateEntry(props) {
7
- const id = useId();
8
8
  const resolved = resolveDateEntryControl(props);
9
- const support = [props.description ? `${id}-hint` : undefined, resolved.error ? `${id}-error` : undefined].filter(Boolean).join(" ") || undefined;
10
- return _jsxs("fieldset", { className: props.className, disabled: props.disabled, "aria-describedby": support, style: { border: 0, margin: 0, padding: 0, minWidth: 0, ...props.layoutStyle }, children: [_jsx("legend", { style: { marginBlockEnd: "var(--hjm-space-sm)" }, children: props.labels.label }), props.description ? _jsx("p", { id: `${id}-hint`, children: props.description }) : null, resolved.error ? _jsx("p", { id: `${id}-error`, role: "alert", style: { color: "var(--hjm-color-danger)" }, children: resolved.error }) : null, _jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 10ch), 1fr))", gap: "var(--hjm-space-md)" }, children: resolved.fields.map(field => _jsx(TextField, { id: `${id}-${field.part}`, label: props.labels[field.part], value: field.value, type: "text", inputMode: field.part === "month" && props.monthInput !== "numeric" ? "text" : "numeric", autoComplete: props.purpose === "birthdate" ? `bday-${field.part}` : "off", required: props.required ?? false, disabled: props.disabled ?? false, readOnly: props.readOnly ?? false, "aria-describedby": support, "aria-invalid": Boolean(resolved.error && field.invalid), onBlur: () => props.onBlur?.(field.part), onValueChange: value => { if (!props.disabled && !props.readOnly)
11
- props.onValueChange(updateDateEntryDraft(props.value, field.part, value)); } }, field.part)) })] });
9
+ const current = useRef(props);
10
+ current.current = props;
11
+ return _jsx(FieldGroup, { layout: "inline", ...(props.className === undefined ? {} : { className: props.className }), ...(props.layoutStyle === undefined ? {} : { layoutStyle: props.layoutStyle }), descriptor: { label: props.labels.label, disabled: props.disabled ?? false,
12
+ ...(props.description ? { description: props.description } : {}),
13
+ ...(resolved.error ? { error: { message: resolved.error, fieldIds: resolved.fields.filter(field => field.invalid).map(field => field.part) } } : {}),
14
+ fields: resolved.fields.map(field => ({ id: field.part, label: props.labels[field.part] })) }, renderField: ({ id, controlProps, guardChange }) => {
15
+ const field = resolved.fields.find(field => field.part === id);
16
+ if (!field)
17
+ return null;
18
+ return _jsx(TextField, { ...controlProps, value: field.value, type: "text", inputMode: field.part === "month" && props.monthInput !== "numeric" ? "text" : "numeric", autoComplete: props.purpose === "birthdate" ? `bday-${field.part}` : "off", required: props.required ?? false, readOnly: props.readOnly ?? false, onBlur: () => current.current.onBlur?.(field.part), onValueChange: guardChange((value) => {
19
+ // Retained host callbacks must not edit an old draft or bypass a new read-only policy.
20
+ const latest = current.current;
21
+ if (!latest.readOnly)
22
+ latest.onValueChange(updateDateEntryDraft(latest.value, field.part, value));
23
+ }) });
24
+ } });
12
25
  }
13
26
  //# sourceMappingURL=date-entry.js.map