@hjmds/react 1.18.0 → 1.20.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 (188) hide show
  1. package/dist/advanced-display.d.ts.map +1 -1
  2. package/dist/advanced-display.js +41 -9
  3. package/dist/advanced-display.js.map +1 -1
  4. package/dist/advanced-forms.d.ts.map +1 -1
  5. package/dist/advanced-forms.js +22 -9
  6. package/dist/advanced-forms.js.map +1 -1
  7. package/dist/agreement.d.ts.map +1 -1
  8. package/dist/agreement.js +17 -11
  9. package/dist/agreement.js.map +1 -1
  10. package/dist/auth-screen.d.ts.map +1 -1
  11. package/dist/auth-screen.js +15 -2
  12. package/dist/auth-screen.js.map +1 -1
  13. package/dist/avatar-blobatar-motion.d.ts.map +1 -1
  14. package/dist/avatar-blobatar-motion.js +10 -2
  15. package/dist/avatar-blobatar-motion.js.map +1 -1
  16. package/dist/bottom-navigation.d.ts.map +1 -1
  17. package/dist/bottom-navigation.js +1 -1
  18. package/dist/bottom-navigation.js.map +1 -1
  19. package/dist/calendar.d.ts.map +1 -1
  20. package/dist/calendar.js +10 -2
  21. package/dist/calendar.js.map +1 -1
  22. package/dist/carousel-motion.d.ts +1 -0
  23. package/dist/carousel-motion.d.ts.map +1 -1
  24. package/dist/carousel-motion.js +4 -3
  25. package/dist/carousel-motion.js.map +1 -1
  26. package/dist/carousel.d.ts.map +1 -1
  27. package/dist/carousel.js +22 -4
  28. package/dist/carousel.js.map +1 -1
  29. package/dist/collapsible.d.ts.map +1 -1
  30. package/dist/collapsible.js +28 -4
  31. package/dist/collapsible.js.map +1 -1
  32. package/dist/collection-rail.d.ts.map +1 -1
  33. package/dist/collection-rail.js +19 -1
  34. package/dist/collection-rail.js.map +1 -1
  35. package/dist/color-picker.d.ts.map +1 -1
  36. package/dist/color-picker.js +12 -4
  37. package/dist/color-picker.js.map +1 -1
  38. package/dist/command-palette.d.ts.map +1 -1
  39. package/dist/command-palette.js +14 -2
  40. package/dist/command-palette.js.map +1 -1
  41. package/dist/context-menu.d.ts.map +1 -1
  42. package/dist/context-menu.js +31 -10
  43. package/dist/context-menu.js.map +1 -1
  44. package/dist/date-entry.d.ts +1 -1
  45. package/dist/date-entry.d.ts.map +1 -1
  46. package/dist/date-entry.js +20 -7
  47. package/dist/date-entry.js.map +1 -1
  48. package/dist/date-picker.d.ts.map +1 -1
  49. package/dist/date-picker.js +22 -6
  50. package/dist/date-picker.js.map +1 -1
  51. package/dist/display.d.ts +5 -1
  52. package/dist/display.d.ts.map +1 -1
  53. package/dist/display.js +3 -2
  54. package/dist/display.js.map +1 -1
  55. package/dist/duration-field.d.ts +2 -2
  56. package/dist/duration-field.d.ts.map +1 -1
  57. package/dist/duration-field.js +11 -3
  58. package/dist/duration-field.js.map +1 -1
  59. package/dist/evidence.d.ts +1 -1
  60. package/dist/evidence.js +1 -1
  61. package/dist/evidence.js.map +1 -1
  62. package/dist/feedback.d.ts.map +1 -1
  63. package/dist/feedback.js +3 -2
  64. package/dist/feedback.js.map +1 -1
  65. package/dist/field-group.d.ts +4 -2
  66. package/dist/field-group.d.ts.map +1 -1
  67. package/dist/field-group.js +4 -3
  68. package/dist/field-group.js.map +1 -1
  69. package/dist/file-picker.d.ts.map +1 -1
  70. package/dist/file-picker.js +15 -5
  71. package/dist/file-picker.js.map +1 -1
  72. package/dist/forms.d.ts +2 -2
  73. package/dist/forms.js +1 -1
  74. package/dist/forms.js.map +1 -1
  75. package/dist/gravity-letters.d.ts.map +1 -1
  76. package/dist/gravity-letters.js +9 -1
  77. package/dist/gravity-letters.js.map +1 -1
  78. package/dist/grid-reveal.d.ts.map +1 -1
  79. package/dist/grid-reveal.js +9 -1
  80. package/dist/grid-reveal.js.map +1 -1
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/inline-confirm.d.ts.map +1 -1
  86. package/dist/inline-confirm.js +25 -4
  87. package/dist/inline-confirm.js.map +1 -1
  88. package/dist/internal/document-resource.d.ts.map +1 -1
  89. package/dist/internal/document-resource.js +2 -2
  90. package/dist/internal/document-resource.js.map +1 -1
  91. package/dist/masonry.d.ts.map +1 -1
  92. package/dist/masonry.js +2 -1
  93. package/dist/masonry.js.map +1 -1
  94. package/dist/mentions.d.ts.map +1 -1
  95. package/dist/mentions.js +12 -4
  96. package/dist/mentions.js.map +1 -1
  97. package/dist/menu-morph.js +1 -1
  98. package/dist/menu-morph.js.map +1 -1
  99. package/dist/menubar.d.ts.map +1 -1
  100. package/dist/menubar.js +43 -19
  101. package/dist/menubar.js.map +1 -1
  102. package/dist/modal.d.ts.map +1 -1
  103. package/dist/modal.js +15 -4
  104. package/dist/modal.js.map +1 -1
  105. package/dist/navigation.d.ts.map +1 -1
  106. package/dist/navigation.js +8 -2
  107. package/dist/navigation.js.map +1 -1
  108. package/dist/number-field.d.ts.map +1 -1
  109. package/dist/number-field.js +3 -1
  110. package/dist/number-field.js.map +1 -1
  111. package/dist/overlay-stack.d.ts.map +1 -1
  112. package/dist/overlay-stack.js +23 -6
  113. package/dist/overlay-stack.js.map +1 -1
  114. package/dist/overlays.d.ts.map +1 -1
  115. package/dist/overlays.js +8 -0
  116. package/dist/overlays.js.map +1 -1
  117. package/dist/pagination.d.ts.map +1 -1
  118. package/dist/pagination.js +16 -4
  119. package/dist/pagination.js.map +1 -1
  120. package/dist/saved-items.d.ts.map +1 -1
  121. package/dist/saved-items.js +6 -1
  122. package/dist/saved-items.js.map +1 -1
  123. package/dist/screen-flows.d.ts +1 -2
  124. package/dist/screen-flows.d.ts.map +1 -1
  125. package/dist/screen-flows.js +120 -16
  126. package/dist/screen-flows.js.map +1 -1
  127. package/dist/screens.d.ts.map +1 -1
  128. package/dist/screens.js +32 -7
  129. package/dist/screens.js.map +1 -1
  130. package/dist/select.d.ts.map +1 -1
  131. package/dist/select.js +6 -1
  132. package/dist/select.js.map +1 -1
  133. package/dist/side-panel.d.ts.map +1 -1
  134. package/dist/side-panel.js +11 -2
  135. package/dist/side-panel.js.map +1 -1
  136. package/dist/sidebar.d.ts.map +1 -1
  137. package/dist/sidebar.js +8 -1
  138. package/dist/sidebar.js.map +1 -1
  139. package/dist/skip-nav.d.ts.map +1 -1
  140. package/dist/skip-nav.js +16 -3
  141. package/dist/skip-nav.js.map +1 -1
  142. package/dist/sortable.d.ts.map +1 -1
  143. package/dist/sortable.js +2 -1
  144. package/dist/sortable.js.map +1 -1
  145. package/dist/splitter.d.ts.map +1 -1
  146. package/dist/splitter.js +23 -11
  147. package/dist/splitter.js.map +1 -1
  148. package/dist/statistic-motion.d.ts.map +1 -1
  149. package/dist/statistic-motion.js +30 -2
  150. package/dist/statistic-motion.js.map +1 -1
  151. package/dist/status-text.d.ts +11 -0
  152. package/dist/status-text.d.ts.map +1 -0
  153. package/dist/status-text.js +7 -0
  154. package/dist/status-text.js.map +1 -0
  155. package/dist/styles.css +19 -4
  156. package/dist/styles.layered.css +19 -4
  157. package/dist/supplemental-display.d.ts.map +1 -1
  158. package/dist/supplemental-display.js +19 -8
  159. package/dist/supplemental-display.js.map +1 -1
  160. package/dist/supplemental-navigation.d.ts.map +1 -1
  161. package/dist/supplemental-navigation.js +10 -1
  162. package/dist/supplemental-navigation.js.map +1 -1
  163. package/dist/tags-input.d.ts.map +1 -1
  164. package/dist/tags-input.js +18 -8
  165. package/dist/tags-input.js.map +1 -1
  166. package/dist/theme.d.ts.map +1 -1
  167. package/dist/theme.js +1 -0
  168. package/dist/theme.js.map +1 -1
  169. package/dist/toast.d.ts.map +1 -1
  170. package/dist/toast.js +31 -16
  171. package/dist/toast.js.map +1 -1
  172. package/dist/toggle-group.d.ts.map +1 -1
  173. package/dist/toggle-group.js +6 -1
  174. package/dist/toggle-group.js.map +1 -1
  175. package/dist/tour.d.ts.map +1 -1
  176. package/dist/tour.js +15 -2
  177. package/dist/tour.js.map +1 -1
  178. package/dist/transfer-list.d.ts +1 -1
  179. package/dist/transfer-list.d.ts.map +1 -1
  180. package/dist/transfer-list.js +52 -7
  181. package/dist/transfer-list.js.map +1 -1
  182. package/dist/tree.d.ts.map +1 -1
  183. package/dist/tree.js +41 -8
  184. package/dist/tree.js.map +1 -1
  185. package/dist/virtual-list.d.ts.map +1 -1
  186. package/dist/virtual-list.js +44 -7
  187. package/dist/virtual-list.js.map +1 -1
  188. package/package.json +8 -3
@@ -6,21 +6,58 @@ export function VirtualList({ items, keyExtractor, renderItem, rowHeight, height
6
6
  const keys = useMemo(() => items.map(keyExtractor), [items, keyExtractor]);
7
7
  validateListKeys(keys, label);
8
8
  const [scrollTop, setScrollTop] = useState(0);
9
- const [active, setActive] = useState(0);
10
- const [focused, setFocused] = useState(null);
9
+ const [active, setActive] = useState({ key: null, index: 0 });
10
+ const [focusedKey, setFocusedKey] = useState(null);
11
11
  const host = useRef(null);
12
12
  const moveFocus = useRef(false);
13
13
  const id = useId();
14
14
  const range = resolveVirtualWindow(items.length, rowHeight, height, scrollTop, overscan);
15
- const selected = Math.min(active, Math.max(0, items.length - 1));
15
+ // Stable keys preserve the focused item when filtering/reordering moves it outside the window.
16
+ const activeIndex = active.key === null ? -1 : keys.indexOf(active.key);
17
+ const selected = activeIndex >= 0 ? activeIndex : Math.min(active.index, Math.max(0, items.length - 1));
18
+ const focused = focusedKey === null ? -1 : keys.indexOf(focusedKey);
16
19
  useLayoutEffect(() => { if (host.current && host.current.scrollTop !== range.offset)
17
20
  host.current.scrollTop = range.offset; }, [range.offset]);
18
21
  useLayoutEffect(() => { if (moveFocus.current) {
19
22
  document.getElementById(`${id}-${selected}`)?.focus({ preventScroll: true });
20
23
  moveFocus.current = false;
21
24
  } }, [selected, id]);
25
+ const previousKeys = useRef(keys);
26
+ useLayoutEffect(() => {
27
+ const previous = previousKeys.current;
28
+ previousKeys.current = keys;
29
+ if (focusedKey === null || keys.includes(focusedKey) || document.activeElement !== document.body)
30
+ return;
31
+ // Removing a focused DOM subtree sends focus to body without a blur event.
32
+ // Continue at its former position (or the previous last row), never steal
33
+ // focus from a control the user deliberately reached outside this list.
34
+ const index = Math.min(Math.max(0, previous.indexOf(focusedKey)), Math.max(0, keys.length - 1));
35
+ setFocusedKey(null);
36
+ setActive({ key: keys[index] ?? null, index });
37
+ const viewport = host.current;
38
+ if (!viewport)
39
+ return;
40
+ if (!keys.length) {
41
+ moveFocus.current = false;
42
+ viewport.focus({ preventScroll: true });
43
+ return;
44
+ }
45
+ const top = index * rowHeight;
46
+ if (top < viewport.scrollTop)
47
+ viewport.scrollTop = top;
48
+ else if (top + rowHeight > viewport.scrollTop + height)
49
+ viewport.scrollTop = top + rowHeight - height;
50
+ setScrollTop(viewport.scrollTop);
51
+ moveFocus.current = true;
52
+ const row = document.getElementById(`${id}-${index}`);
53
+ if (row) {
54
+ row.focus({ preventScroll: true });
55
+ moveFocus.current = false;
56
+ }
57
+ }, [keys, focusedKey, id, rowHeight, height]);
22
58
  // Keep keyboard/descendant focus mounted when wheel scrolling moves it outside the window.
23
- const indices = [...new Set([...Array.from({ length: range.end - range.start }, (_, i) => i + range.start), ...(items.length ? [selected] : []), ...(focused !== null && focused < items.length ? [focused] : [])])].sort((a, b) => a - b);
59
+ const indices = [...new Set([...Array.from({ length: range.end - range.start }, (_, i) => i + range.start), ...(items.length ? [selected] : []), ...(focused >= 0 ? [focused] : [])])].sort((a, b) => a - b);
60
+ // Pointer or programmatic focus must become the keyboard origin too.
24
61
  return _jsx("div", { "data-hjm-virtual-list": true, className: "hjm-virtual-list", ref: host, role: "list", "aria-label": label, tabIndex: 0, style: { ...layoutStyle, overflowY: "auto", height, position: "relative" }, onScroll: event => setScrollTop(event.currentTarget.scrollTop), onKeyDown: event => {
25
62
  if (!items.length || !(event.target instanceof HTMLElement) || (event.target !== event.currentTarget && !event.target.hasAttribute("data-virtual-row")))
26
63
  return;
@@ -29,7 +66,7 @@ export function VirtualList({ items, keyExtractor, renderItem, rowHeight, height
29
66
  return;
30
67
  event.preventDefault();
31
68
  moveFocus.current = true;
32
- setActive(next);
69
+ setActive({ key: keys[next], index: next });
33
70
  const top = next * rowHeight;
34
71
  const viewport = event.currentTarget;
35
72
  if (top < viewport.scrollTop)
@@ -42,7 +79,7 @@ export function VirtualList({ items, keyExtractor, renderItem, rowHeight, height
42
79
  rendered.focus({ preventScroll: true });
43
80
  moveFocus.current = false;
44
81
  }
45
- }, children: !items.length ? empty : _jsx("div", { style: { position: "relative", height: range.totalHeight }, children: indices.map(index => _jsx("div", { id: `${id}-${index}`, role: "listitem", "data-virtual-row": true, className: "hjm-virtual-list__row", tabIndex: index === selected ? 0 : -1, "aria-setsize": items.length, "aria-posinset": index + 1, onFocusCapture: () => setFocused(index), onBlurCapture: event => { if (!event.currentTarget.contains(event.relatedTarget))
46
- setFocused(null); }, style: { position: "absolute", top: index * rowHeight, height: rowHeight, width: "100%" }, children: renderItem(items[index], index) }, keys[index])) }) });
82
+ }, children: !items.length ? _jsx("div", { style: { minHeight: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", textAlign: "center" }, children: empty }) : _jsx("div", { style: { position: "relative", height: range.totalHeight }, children: indices.map(index => _jsx("div", { id: `${id}-${index}`, role: "listitem", "data-virtual-row": true, className: "hjm-virtual-list__row", tabIndex: index === selected ? 0 : -1, "aria-setsize": items.length, "aria-posinset": index + 1, onFocusCapture: () => { setFocusedKey(keys[index]); setActive({ key: keys[index], index }); }, onBlurCapture: event => { if (!event.currentTarget.contains(event.relatedTarget))
83
+ setFocusedKey(null); }, style: { position: "absolute", top: index * rowHeight, height: rowHeight, width: "100%" }, children: renderItem(items[index], index) }, keys[index])) }) });
47
84
  }
48
85
  //# sourceMappingURL=virtual-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-list.js","sourceRoot":"","sources":["../src/virtual-list.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useId, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { resolveVirtualWindow, validateListKeys } from \"@hjmds/design-contracts/components/virtual-list\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\nexport type VirtualListProps<T> = { items: readonly T[]; keyExtractor: (item: T) => string; renderItem: (item: T, index: number) => ReactNode; rowHeight: number; height: number; label: string; empty?: ReactNode; overscan?: number;\n /** Canonical layout-only placement on the scroll viewport. `height` stays the prop's: the window math reads it. */\n layoutStyle?: HjmCompositionStyleProp };\n/** Fixed-height rows are an explicit host contract; use List for unconstrained flowing copy. */\nexport function VirtualList<T>({ items, keyExtractor, renderItem, rowHeight, height, label, empty, overscan = 3, layoutStyle }: VirtualListProps<T>) {\n const keys = useMemo(() => items.map(keyExtractor), [items, keyExtractor]); validateListKeys(keys, label);\n const [scrollTop, setScrollTop] = useState(0);\n const [active, setActive] = useState(0);\n const [focused, setFocused] = useState<number | null>(null);\n const host = useRef<HTMLDivElement>(null); const moveFocus = useRef(false); const id = useId();\n const range = resolveVirtualWindow(items.length, rowHeight, height, scrollTop, overscan);\n const selected = Math.min(active, Math.max(0, items.length - 1));\n useLayoutEffect(() => { if (host.current && host.current.scrollTop !== range.offset) host.current.scrollTop = range.offset; }, [range.offset]);\n useLayoutEffect(() => { if (moveFocus.current) { document.getElementById(`${id}-${selected}`)?.focus({ preventScroll: true }); moveFocus.current = false; } }, [selected, id]);\n // Keep keyboard/descendant focus mounted when wheel scrolling moves it outside the window.\n const indices = [...new Set([...Array.from({ length: range.end - range.start }, (_, i) => i + range.start), ...(items.length ? [selected] : []), ...(focused !== null && focused < items.length ? [focused] : [])])].sort((a,b) => a-b);\n return <div data-hjm-virtual-list className=\"hjm-virtual-list\" ref={host} role=\"list\" aria-label={label} tabIndex={0}\n style={{ ...layoutStyle, overflowY: \"auto\", height, position: \"relative\" }} onScroll={event => setScrollTop(event.currentTarget.scrollTop)}\n onKeyDown={event => {\n if (!items.length || !(event.target instanceof HTMLElement) || (event.target !== event.currentTarget && !event.target.hasAttribute(\"data-virtual-row\"))) return;\n const next = event.key === \"ArrowDown\" ? Math.min(selected + 1, items.length - 1) : event.key === \"ArrowUp\" ? Math.max(0, selected - 1) : event.key === \"Home\" ? 0 : event.key === \"End\" ? items.length - 1 : null;\n if (next === null) return; event.preventDefault(); moveFocus.current = true; setActive(next);\n const top = next * rowHeight; const viewport = event.currentTarget;\n if (top < viewport.scrollTop) viewport.scrollTop = top;\n else if (top + rowHeight > viewport.scrollTop + height) viewport.scrollTop = top + rowHeight - height;\n setScrollTop(viewport.scrollTop);\n const rendered = document.getElementById(`${id}-${next}`);\n if (rendered) { rendered.focus({ preventScroll: true }); moveFocus.current = false; }\n }}>\n {!items.length ? empty : <div style={{ position: \"relative\", height: range.totalHeight }}>\n {indices.map(index => <div key={keys[index]} id={`${id}-${index}`} role=\"listitem\" data-virtual-row className=\"hjm-virtual-list__row\" tabIndex={index === selected ? 0 : -1} aria-setsize={items.length} aria-posinset={index + 1}\n onFocusCapture={() => setFocused(index)} onBlurCapture={event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocused(null); }}\n style={{ position: \"absolute\", top: index * rowHeight, height: rowHeight, width: \"100%\" }}>{renderItem(items[index]!, index)}</div>)}\n </div>}\n </div>;\n}\n"]}
1
+ {"version":3,"file":"virtual-list.js","sourceRoot":"","sources":["../src/virtual-list.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useId, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { resolveVirtualWindow, validateListKeys } from \"@hjmds/design-contracts/components/virtual-list\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\nexport type VirtualListProps<T> = { items: readonly T[]; keyExtractor: (item: T) => string; renderItem: (item: T, index: number) => ReactNode; rowHeight: number; height: number; label: string; empty?: ReactNode; overscan?: number;\n /** Canonical layout-only placement on the scroll viewport. `height` stays the prop's: the window math reads it. */\n layoutStyle?: HjmCompositionStyleProp };\n/** Fixed-height rows are an explicit host contract; use List for unconstrained flowing copy. */\nexport function VirtualList<T>({ items, keyExtractor, renderItem, rowHeight, height, label, empty, overscan = 3, layoutStyle }: VirtualListProps<T>) {\n const keys = useMemo(() => items.map(keyExtractor), [items, keyExtractor]); validateListKeys(keys, label);\n const [scrollTop, setScrollTop] = useState(0);\n const [active, setActive] = useState<{ key: string | null; index: number }>({ key: null, index: 0 });\n const [focusedKey, setFocusedKey] = useState<string | null>(null);\n const host = useRef<HTMLDivElement>(null); const moveFocus = useRef(false); const id = useId();\n const range = resolveVirtualWindow(items.length, rowHeight, height, scrollTop, overscan);\n // Stable keys preserve the focused item when filtering/reordering moves it outside the window.\n const activeIndex = active.key === null ? -1 : keys.indexOf(active.key);\n const selected = activeIndex >= 0 ? activeIndex : Math.min(active.index, Math.max(0, items.length - 1));\n const focused = focusedKey === null ? -1 : keys.indexOf(focusedKey);\n useLayoutEffect(() => { if (host.current && host.current.scrollTop !== range.offset) host.current.scrollTop = range.offset; }, [range.offset]);\n useLayoutEffect(() => { if (moveFocus.current) { document.getElementById(`${id}-${selected}`)?.focus({ preventScroll: true }); moveFocus.current = false; } }, [selected, id]);\n const previousKeys = useRef(keys);\n useLayoutEffect(() => {\n const previous = previousKeys.current;\n previousKeys.current = keys;\n if (focusedKey === null || keys.includes(focusedKey) || document.activeElement !== document.body) return;\n // Removing a focused DOM subtree sends focus to body without a blur event.\n // Continue at its former position (or the previous last row), never steal\n // focus from a control the user deliberately reached outside this list.\n const index = Math.min(Math.max(0, previous.indexOf(focusedKey)), Math.max(0, keys.length - 1));\n setFocusedKey(null);\n setActive({ key: keys[index] ?? null, index });\n const viewport = host.current;\n if (!viewport) return;\n if (!keys.length) { moveFocus.current = false; viewport.focus({ preventScroll: true }); return; }\n const top = index * rowHeight;\n if (top < viewport.scrollTop) viewport.scrollTop = top;\n else if (top + rowHeight > viewport.scrollTop + height) viewport.scrollTop = top + rowHeight - height;\n setScrollTop(viewport.scrollTop);\n moveFocus.current = true;\n const row = document.getElementById(`${id}-${index}`);\n if (row) { row.focus({ preventScroll: true }); moveFocus.current = false; }\n }, [keys, focusedKey, id, rowHeight, height]);\n // Keep keyboard/descendant focus mounted when wheel scrolling moves it outside the window.\n const indices = [...new Set([...Array.from({ length: range.end - range.start }, (_, i) => i + range.start), ...(items.length ? [selected] : []), ...(focused >= 0 ? [focused] : [])])].sort((a,b) => a-b);\n // Pointer or programmatic focus must become the keyboard origin too.\n return <div data-hjm-virtual-list className=\"hjm-virtual-list\" ref={host} role=\"list\" aria-label={label} tabIndex={0}\n style={{ ...layoutStyle, overflowY: \"auto\", height, position: \"relative\" }} onScroll={event => setScrollTop(event.currentTarget.scrollTop)}\n onKeyDown={event => {\n if (!items.length || !(event.target instanceof HTMLElement) || (event.target !== event.currentTarget && !event.target.hasAttribute(\"data-virtual-row\"))) return;\n const next = event.key === \"ArrowDown\" ? Math.min(selected + 1, items.length - 1) : event.key === \"ArrowUp\" ? Math.max(0, selected - 1) : event.key === \"Home\" ? 0 : event.key === \"End\" ? items.length - 1 : null;\n if (next === null) return; event.preventDefault(); moveFocus.current = true; setActive({ key: keys[next]!, index: next });\n const top = next * rowHeight; const viewport = event.currentTarget;\n if (top < viewport.scrollTop) viewport.scrollTop = top;\n else if (top + rowHeight > viewport.scrollTop + height) viewport.scrollTop = top + rowHeight - height;\n setScrollTop(viewport.scrollTop);\n const rendered = document.getElementById(`${id}-${next}`);\n if (rendered) { rendered.focus({ preventScroll: true }); moveFocus.current = false; }\n }}>\n {!items.length ? <div style={{ minHeight: \"100%\", display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", textAlign: \"center\" }}>{empty}</div> : <div style={{ position: \"relative\", height: range.totalHeight }}>\n {indices.map(index => <div key={keys[index]} id={`${id}-${index}`} role=\"listitem\" data-virtual-row className=\"hjm-virtual-list__row\" tabIndex={index === selected ? 0 : -1} aria-setsize={items.length} aria-posinset={index + 1}\n onFocusCapture={() => { setFocusedKey(keys[index]!); setActive({ key: keys[index]!, index }); }} onBlurCapture={event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocusedKey(null); }}\n style={{ position: \"absolute\", top: index * rowHeight, height: rowHeight, width: \"100%\" }}>{renderItem(items[index]!, index)}</div>)}\n </div>}\n </div>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/react",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "Accessible React 19 renderers for HJM design contracts.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -23,6 +23,11 @@
23
23
  "import": "./dist/index.js",
24
24
  "default": "./dist/index.js"
25
25
  },
26
+ "./status-text": {
27
+ "types": "./dist/status-text.d.ts",
28
+ "import": "./dist/status-text.js",
29
+ "default": "./dist/status-text.js"
30
+ },
26
31
  "./provider": {
27
32
  "types": "./dist/provider.d.ts",
28
33
  "import": "./dist/provider.js",
@@ -526,7 +531,7 @@
526
531
  "THIRD_PARTY_NOTICES.md"
527
532
  ],
528
533
  "peerDependencies": {
529
- "@hjmds/design-contracts": ">=1.18.0 <1.19.0",
534
+ "@hjmds/design-contracts": ">=1.20.0 <1.21.0",
530
535
  "react": ">=19.0.0",
531
536
  "react-dom": ">=19.0.0",
532
537
  "@number-flow/react": "0.6.2",
@@ -563,7 +568,7 @@
563
568
  "blobatar": "2.7.0",
564
569
  "@blobatar/react": "2.7.0",
565
570
  "lucide-react": "1.49.0",
566
- "@hjmds/design-contracts": "1.18.0"
571
+ "@hjmds/design-contracts": "1.20.0"
567
572
  },
568
573
  "peerDependenciesMeta": {
569
574
  "@number-flow/react": {