@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
package/dist/sidebar.js CHANGED
@@ -35,7 +35,14 @@ export const Sidebar = forwardRef(function Sidebar({ descriptor, appearance = "s
35
35
  "--hjm-sidebar-group-gap": `${sidebarRecipe.groupGap}px`,
36
36
  }, children: [collapseLabels ? (_jsx("button", { type: "button", className: "hjm-sidebar__toggle", "aria-expanded": !collapsed, "aria-label": collapsed ? collapseLabels.expand : collapseLabels.collapse, onClick: () => setCollapsed(!collapsed), children: _jsx("span", { "aria-hidden": "true", children: collapsed ? "»" : "«" }) })) : null, descriptor.groups.map((group) => (_jsxs("div", { className: "hjm-sidebar__group", role: "group", "aria-label": group.label, children: [group.label ? _jsx("p", { className: "hjm-sidebar__group-label", "aria-hidden": "true", children: group.label }) : null, _jsx("ul", { className: "hjm-sidebar__list", children: group.items.map((item) => {
37
37
  const current = descriptor.currentId === item.id;
38
- return (_jsx("li", { children: _jsxs("a", { className: "hjm-sidebar__item", href: item.destination?.kind === "internal" ? item.destination.href : item.destination?.href, "aria-current": current ? "page" : undefined, "data-bounce": current && activatedId === item.id || undefined, "aria-disabled": item.disabled || undefined, "aria-label": collapsed ? item.label : undefined, tabIndex: item.disabled ? -1 : undefined, onClick: (event) => {
38
+ // Router-owned destinations may omit href; role/tabIndex/Enter keep
39
+ // their link keyboard path without inventing a URL.
40
+ return (_jsx("li", { children: _jsxs("a", { className: "hjm-sidebar__item", href: item.destination?.kind === "internal" ? item.destination.href : item.destination?.href, "aria-current": current ? "page" : undefined, "data-bounce": current && activatedId === item.id || undefined, "aria-disabled": item.disabled || undefined, "aria-label": collapsed ? item.label : undefined, role: item.destination === undefined ? "link" : undefined, tabIndex: item.disabled ? -1 : item.destination === undefined ? 0 : undefined, onKeyDown: (event) => {
41
+ if (item.destination === undefined && !item.disabled && event.key === "Enter") {
42
+ event.preventDefault();
43
+ event.currentTarget.click();
44
+ }
45
+ }, onClick: (event) => {
39
46
  if (item.disabled) {
40
47
  event.preventDefault();
41
48
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n sidebarDefaults,\n sidebarRecipe,\n validateSidebarDescriptor,\n type SidebarDescriptor,\n type SidebarItemDescriptor,\n} from \"@hjmds/design-contracts/components/sidebar\";\nimport {\n forwardRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport { classNames, useControllableState } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SidebarAppearance = \"standard\" | \"bounce\" | \"hook\" | \"proximity\";\n\nexport type SidebarProps<Id extends string = string, GroupId extends string = string> = Readonly<{\n descriptor: SidebarDescriptor<Id, GroupId>;\n /** Decoration only; link hit areas and document-order keyboard navigation stay fixed. */\n appearance?: SidebarAppearance;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n /** Localized names for the collapse control in both states. */\n collapseLabels?: Readonly<{ collapse: string; expand: string }>;\n onNavigate?: (id: Id) => void;\n /** Product-owned icon per item; required for the collapsed rail to stay usable. */\n renderIcon?: (item: SidebarItemDescriptor<Id>) => ReactNode;\n renderBadge?: (count: number, item: SidebarItemDescriptor<Id>) => ReactNode;\n className?: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nexport const Sidebar = forwardRef(function Sidebar<Id extends string = string, GroupId extends string = string>(\n {\n descriptor,\n appearance = \"standard\",\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange,\n collapseLabels,\n onNavigate,\n renderIcon,\n renderBadge,\n className,\n layoutStyle,\n }: SidebarProps<Id, GroupId>,\n forwardedRef: React.Ref<HTMLElement>,\n) {\n validateSidebarDescriptor(descriptor);\n const theme = useOptionalHjmTheme();\n const animate = theme !== null && !theme.environment.reducedMotion;\n const [activatedId, setActivatedId] = useState<string | null>(null);\n const [collapsed, setCollapsed] = useControllableState<boolean>({\n ...(controlledCollapsed === undefined ? {} : { value: controlledCollapsed }),\n defaultValue: defaultCollapsed ?? sidebarDefaults.collapsed,\n ...(onCollapsedChange === undefined ? {} : { onChange: onCollapsedChange }),\n });\n return (\n <nav\n ref={forwardedRef}\n aria-label={descriptor.accessibilityLabel}\n className={classNames(\"hjm-sidebar\", className)}\n data-collapsed={collapsed || undefined}\n data-appearance={appearance}\n data-animate={animate || undefined}\n onPointerMove={event => {\n if (appearance !== \"proximity\" || !animate || event.pointerType === \"touch\") return;\n // Only icon decoration responds to distance; moving links would shift click/focus targets.\n for (const item of event.currentTarget.querySelectorAll<HTMLElement>(\".hjm-sidebar__item\")) {\n const bounds = item.getBoundingClientRect();\n const distance = Math.abs(event.clientY - (bounds.top + bounds.height / 2));\n const proximity = item.getAttribute(\"aria-disabled\") === \"true\" ? 0 : Math.max(0, 1 - distance / (bounds.height * 2));\n item.style.setProperty(\"--hjm-sidebar-proximity\", String(proximity));\n }\n }}\n onPointerLeave={event => {\n for (const item of event.currentTarget.querySelectorAll<HTMLElement>(\".hjm-sidebar__item\")) item.style.removeProperty(\"--hjm-sidebar-proximity\");\n }}\n style={{ ...layoutStyle,\n \"--hjm-sidebar-width\": `${collapsed ? sidebarRecipe.widths.collapsed : sidebarRecipe.widths.expanded}px`,\n \"--hjm-sidebar-item-height\": `${sidebarRecipe.itemMinHeight}px`,\n // CSS inheritance preserves nested profiles without adding a Provider dependency.\n \"--hjm-sidebar-item-radius\": `var(--hjm-radius-md, ${sidebarRecipe.itemRadius}px)`,\n \"--hjm-sidebar-gap\": `${sidebarRecipe.gap}px`,\n \"--hjm-sidebar-group-gap\": `${sidebarRecipe.groupGap}px`,\n } as CSSProperties}\n >\n {collapseLabels ? (\n <button\n type=\"button\"\n className=\"hjm-sidebar__toggle\"\n aria-expanded={!collapsed}\n aria-label={collapsed ? collapseLabels.expand : collapseLabels.collapse}\n onClick={() => setCollapsed(!collapsed)}\n >\n <span aria-hidden=\"true\">{collapsed ? \"»\" : \"«\"}</span>\n </button>\n ) : null}\n {descriptor.groups.map((group) => (\n <div key={group.id} className=\"hjm-sidebar__group\" role=\"group\" aria-label={group.label}>\n {/*\n Collapsing is density, not content: the group label is hidden\n visually but the group keeps its accessible name.\n */}\n {group.label ? <p className=\"hjm-sidebar__group-label\" aria-hidden=\"true\">{group.label}</p> : null}\n <ul className=\"hjm-sidebar__list\">\n {group.items.map((item) => {\n const current = descriptor.currentId === item.id;\n return (\n <li key={item.id}>\n <a\n className=\"hjm-sidebar__item\"\n href={item.destination?.kind === \"internal\" ? item.destination.href : item.destination?.href}\n aria-current={current ? \"page\" : undefined}\n data-bounce={current && activatedId === item.id || undefined}\n aria-disabled={item.disabled || undefined}\n // Collapsed labels are hidden visually, so the name comes\n // from the attribute instead of disappearing with the text.\n aria-label={collapsed ? item.label : undefined}\n tabIndex={item.disabled ? -1 : undefined}\n onClick={(event) => {\n if (item.disabled) { event.preventDefault(); return; }\n if (appearance === \"bounce\" && animate) setActivatedId(item.id);\n onNavigate?.(item.id);\n }}\n >\n {renderIcon ? <span aria-hidden=\"true\" className=\"hjm-sidebar__icon\">{renderIcon(item)}</span> : null}\n <span className=\"hjm-sidebar__label\">{item.label}</span>\n {item.badgeCount !== undefined ? (\n <span className=\"hjm-sidebar__badge\">{renderBadge?.(item.badgeCount, item) ?? item.badgeCount}</span>\n ) : null}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </nav>\n );\n}) as <Id extends string = string, GroupId extends string = string>(\n props: SidebarProps<Id, GroupId> & { ref?: React.Ref<HTMLElement> },\n) => React.ReactElement | null;\n"]}
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n sidebarDefaults,\n sidebarRecipe,\n validateSidebarDescriptor,\n type SidebarDescriptor,\n type SidebarItemDescriptor,\n} from \"@hjmds/design-contracts/components/sidebar\";\nimport {\n forwardRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport { classNames, useControllableState } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SidebarAppearance = \"standard\" | \"bounce\" | \"hook\" | \"proximity\";\n\nexport type SidebarProps<Id extends string = string, GroupId extends string = string> = Readonly<{\n descriptor: SidebarDescriptor<Id, GroupId>;\n /** Decoration only; link hit areas and document-order keyboard navigation stay fixed. */\n appearance?: SidebarAppearance;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n /** Localized names for the collapse control in both states. */\n collapseLabels?: Readonly<{ collapse: string; expand: string }>;\n onNavigate?: (id: Id) => void;\n /** Product-owned icon per item; required for the collapsed rail to stay usable. */\n renderIcon?: (item: SidebarItemDescriptor<Id>) => ReactNode;\n renderBadge?: (count: number, item: SidebarItemDescriptor<Id>) => ReactNode;\n className?: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nexport const Sidebar = forwardRef(function Sidebar<Id extends string = string, GroupId extends string = string>(\n {\n descriptor,\n appearance = \"standard\",\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange,\n collapseLabels,\n onNavigate,\n renderIcon,\n renderBadge,\n className,\n layoutStyle,\n }: SidebarProps<Id, GroupId>,\n forwardedRef: React.Ref<HTMLElement>,\n) {\n validateSidebarDescriptor(descriptor);\n const theme = useOptionalHjmTheme();\n const animate = theme !== null && !theme.environment.reducedMotion;\n const [activatedId, setActivatedId] = useState<string | null>(null);\n const [collapsed, setCollapsed] = useControllableState<boolean>({\n ...(controlledCollapsed === undefined ? {} : { value: controlledCollapsed }),\n defaultValue: defaultCollapsed ?? sidebarDefaults.collapsed,\n ...(onCollapsedChange === undefined ? {} : { onChange: onCollapsedChange }),\n });\n return (\n <nav\n ref={forwardedRef}\n aria-label={descriptor.accessibilityLabel}\n className={classNames(\"hjm-sidebar\", className)}\n data-collapsed={collapsed || undefined}\n data-appearance={appearance}\n data-animate={animate || undefined}\n onPointerMove={event => {\n if (appearance !== \"proximity\" || !animate || event.pointerType === \"touch\") return;\n // Only icon decoration responds to distance; moving links would shift click/focus targets.\n for (const item of event.currentTarget.querySelectorAll<HTMLElement>(\".hjm-sidebar__item\")) {\n const bounds = item.getBoundingClientRect();\n const distance = Math.abs(event.clientY - (bounds.top + bounds.height / 2));\n const proximity = item.getAttribute(\"aria-disabled\") === \"true\" ? 0 : Math.max(0, 1 - distance / (bounds.height * 2));\n item.style.setProperty(\"--hjm-sidebar-proximity\", String(proximity));\n }\n }}\n onPointerLeave={event => {\n for (const item of event.currentTarget.querySelectorAll<HTMLElement>(\".hjm-sidebar__item\")) item.style.removeProperty(\"--hjm-sidebar-proximity\");\n }}\n style={{ ...layoutStyle,\n \"--hjm-sidebar-width\": `${collapsed ? sidebarRecipe.widths.collapsed : sidebarRecipe.widths.expanded}px`,\n \"--hjm-sidebar-item-height\": `${sidebarRecipe.itemMinHeight}px`,\n // CSS inheritance preserves nested profiles without adding a Provider dependency.\n \"--hjm-sidebar-item-radius\": `var(--hjm-radius-md, ${sidebarRecipe.itemRadius}px)`,\n \"--hjm-sidebar-gap\": `${sidebarRecipe.gap}px`,\n \"--hjm-sidebar-group-gap\": `${sidebarRecipe.groupGap}px`,\n } as CSSProperties}\n >\n {collapseLabels ? (\n <button\n type=\"button\"\n className=\"hjm-sidebar__toggle\"\n aria-expanded={!collapsed}\n aria-label={collapsed ? collapseLabels.expand : collapseLabels.collapse}\n onClick={() => setCollapsed(!collapsed)}\n >\n <span aria-hidden=\"true\">{collapsed ? \"»\" : \"«\"}</span>\n </button>\n ) : null}\n {descriptor.groups.map((group) => (\n <div key={group.id} className=\"hjm-sidebar__group\" role=\"group\" aria-label={group.label}>\n {/*\n Collapsing is density, not content: the group label is hidden\n visually but the group keeps its accessible name.\n */}\n {group.label ? <p className=\"hjm-sidebar__group-label\" aria-hidden=\"true\">{group.label}</p> : null}\n <ul className=\"hjm-sidebar__list\">\n {group.items.map((item) => {\n const current = descriptor.currentId === item.id;\n // Router-owned destinations may omit href; role/tabIndex/Enter keep\n // their link keyboard path without inventing a URL.\n return (\n <li key={item.id}>\n <a\n className=\"hjm-sidebar__item\"\n href={item.destination?.kind === \"internal\" ? item.destination.href : item.destination?.href}\n aria-current={current ? \"page\" : undefined}\n data-bounce={current && activatedId === item.id || undefined}\n aria-disabled={item.disabled || undefined}\n // Collapsed labels are hidden visually, so the name comes\n // from the attribute instead of disappearing with the text.\n aria-label={collapsed ? item.label : undefined}\n role={item.destination === undefined ? \"link\" : undefined}\n tabIndex={item.disabled ? -1 : item.destination === undefined ? 0 : undefined}\n onKeyDown={(event) => {\n if (item.destination === undefined && !item.disabled && event.key === \"Enter\") {\n event.preventDefault(); event.currentTarget.click();\n }\n }}\n onClick={(event) => {\n if (item.disabled) { event.preventDefault(); return; }\n if (appearance === \"bounce\" && animate) setActivatedId(item.id);\n onNavigate?.(item.id);\n }}\n >\n {renderIcon ? <span aria-hidden=\"true\" className=\"hjm-sidebar__icon\">{renderIcon(item)}</span> : null}\n <span className=\"hjm-sidebar__label\">{item.label}</span>\n {item.badgeCount !== undefined ? (\n <span className=\"hjm-sidebar__badge\">{renderBadge?.(item.badgeCount, item) ?? item.badgeCount}</span>\n ) : null}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </nav>\n );\n}) as <Id extends string = string, GroupId extends string = string>(\n props: SidebarProps<Id, GroupId> & { ref?: React.Ref<HTMLElement> },\n) => React.ReactElement | null;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"skip-nav.d.ts","sourceRoot":"","sources":["../src/skip-nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAc,KAAK,oBAAoB,EAAsB,MAAM,OAAO,CAAC;AAIlF,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,GAC3F,iBAAiB,GACjB,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO;;;;gBAFK,MAAM;sDAqC7B,CAAC"}
1
+ {"version":3,"file":"skip-nav.d.ts","sourceRoot":"","sources":["../src/skip-nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAiC,KAAK,oBAAoB,EAAsB,MAAM,OAAO,CAAC;AAIrG,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,GAC3F,iBAAiB,GACjB,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO;;;;gBAFK,MAAM;sDAiD7B,CAAC"}
package/dist/skip-nav.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { skipNavRecipe, validateSkipNavDescriptor, } from "@hjmds/design-contracts/components/skip-nav";
3
- import { forwardRef } from "react";
3
+ import { forwardRef, useEffect, useRef } from "react";
4
4
  import { classNames } from "./internal.js";
5
5
  export const SkipNav = forwardRef(function SkipNav({ targetId, label, className, onClick, ...props }, forwardedRef) {
6
6
  validateSkipNavDescriptor({ targetId, label });
7
+ const restorations = useRef(new Map());
8
+ useEffect(() => () => { for (const restore of [...restorations.current.values()])
9
+ restore(); }, []);
7
10
  return (_jsx("a", { ...props, ref: forwardedRef, href: `#${targetId}`, className: classNames("hjm-skip-nav", className), style: {
8
11
  "--hjm-skip-nav-min-height": `${skipNavRecipe.minHeight}px`,
9
12
  "--hjm-skip-nav-padding": `${skipNavRecipe.paddingHorizontal}px`,
@@ -12,7 +15,7 @@ export const SkipNav = forwardRef(function SkipNav({ targetId, label, className,
12
15
  "--hjm-skip-nav-offset": `${skipNavRecipe.offset}px`,
13
16
  }, onClick: (event) => {
14
17
  onClick?.(event);
15
- if (event.defaultPrevented)
18
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
16
19
  return;
17
20
  const target = document.getElementById(targetId);
18
21
  if (!target)
@@ -22,8 +25,18 @@ export const SkipNav = forwardRef(function SkipNav({ targetId, label, className,
22
25
  the next Tab returns to the links the user just skipped. Making the
23
26
  target programmatically focusable moves the caret with the view.
24
27
  */
25
- if (!target.hasAttribute("tabindex"))
28
+ if (!target.hasAttribute("tabindex")) {
26
29
  target.setAttribute("tabindex", "-1");
30
+ // The target belongs to the page; temporary focusability must not outlive this navigation.
31
+ const restore = () => {
32
+ if (target.getAttribute("tabindex") === "-1")
33
+ target.removeAttribute("tabindex");
34
+ target.removeEventListener("blur", restore);
35
+ restorations.current.delete(target);
36
+ };
37
+ restorations.current.set(target, restore);
38
+ target.addEventListener("blur", restore, { once: true });
39
+ }
27
40
  target.focus({ preventScroll: false });
28
41
  }, children: label }));
29
42
  });
@@ -1 +1 @@
1
- {"version":3,"file":"skip-nav.js","sourceRoot":"","sources":["../src/skip-nav.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EACb,yBAAyB,GAE1B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,UAAU,EAAiD,MAAM,OAAO,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAkC,SAAS,OAAO,CACjF,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EACjD,YAAY;IAEZ,yBAAyB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,OAAO,CACL,eACM,KAAK,EACT,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,IAAI,QAAQ,EAAE,EACpB,SAAS,EAAE,UAAU,CAAC,cAAc,EAAE,SAAS,CAAC,EAChD,KAAK,EAAE;YACL,2BAA2B,EAAE,GAAG,aAAa,CAAC,SAAS,IAAI;YAC3D,wBAAwB,EAAE,GAAG,aAAa,CAAC,iBAAiB,IAAI;YAChE,qFAAqF;YACrF,uBAAuB,EAAE,wBAAwB,aAAa,CAAC,MAAM,KAAK;YAC1E,uBAAuB,EAAE,GAAG,aAAa,CAAC,MAAM,IAAI;SACpC,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,gBAAgB;gBAAE,OAAO;YACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB;;;;cAIE;YACF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;gBAAE,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAC5E,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,CAAC,YAEA,KAAK,GACJ,CACL,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n skipNavRecipe,\n validateSkipNavDescriptor,\n type SkipNavDescriptor,\n} from \"@hjmds/design-contracts/components/skip-nav\";\nimport { forwardRef, type AnchorHTMLAttributes, type CSSProperties } from \"react\";\nimport { classNames } from \"./internal.js\";\n\n// No `layoutStyle`: off-screen until focused, then pinned by the recipe offset; moving it would hide the focus target.\nexport type SkipNavProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\" | \"children\"> &\n SkipNavDescriptor &\n Readonly<{ className?: string }>;\n\nexport const SkipNav = forwardRef<HTMLAnchorElement, SkipNavProps>(function SkipNav(\n { targetId, label, className, onClick, ...props },\n forwardedRef,\n) {\n validateSkipNavDescriptor({ targetId, label });\n return (\n <a\n {...props}\n ref={forwardedRef}\n href={`#${targetId}`}\n className={classNames(\"hjm-skip-nav\", className)}\n style={{\n \"--hjm-skip-nav-min-height\": `${skipNavRecipe.minHeight}px`,\n \"--hjm-skip-nav-padding\": `${skipNavRecipe.paddingHorizontal}px`,\n // The corner follows the nearest profile; pinned focus placement stays recipe-owned.\n \"--hjm-skip-nav-radius\": `var(--hjm-radius-md, ${skipNavRecipe.radius}px)`,\n \"--hjm-skip-nav-offset\": `${skipNavRecipe.offset}px`,\n } as CSSProperties}\n onClick={(event) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n const target = document.getElementById(targetId);\n if (!target) return;\n /*\n The hash alone only scrolls; focus stays behind in the navigation, so\n the next Tab returns to the links the user just skipped. Making the\n target programmatically focusable moves the caret with the view.\n */\n if (!target.hasAttribute(\"tabindex\")) target.setAttribute(\"tabindex\", \"-1\");\n target.focus({ preventScroll: false });\n }}\n >\n {label}\n </a>\n );\n});\n"]}
1
+ {"version":3,"file":"skip-nav.js","sourceRoot":"","sources":["../src/skip-nav.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EACb,yBAAyB,GAE1B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAiD,MAAM,OAAO,CAAC;AACrG,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAkC,SAAS,OAAO,CACjF,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EACjD,YAAY;IAEZ,yBAAyB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,EAA2B,CAAC,CAAC;IAChE,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpG,OAAO,CACL,eACM,KAAK,EACT,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,IAAI,QAAQ,EAAE,EACpB,SAAS,EAAE,UAAU,CAAC,cAAc,EAAE,SAAS,CAAC,EAChD,KAAK,EAAE;YACL,2BAA2B,EAAE,GAAG,aAAa,CAAC,SAAS,IAAI;YAC3D,wBAAwB,EAAE,GAAG,aAAa,CAAC,iBAAiB,IAAI;YAChE,qFAAqF;YACrF,uBAAuB,EAAE,wBAAwB,aAAa,CAAC,MAAM,KAAK;YAC1E,uBAAuB,EAAE,GAAG,aAAa,CAAC,MAAM,IAAI;SACpC,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ;gBAAE,OAAO;YAC7H,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB;;;;cAIE;YACF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;gBACrC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACtC,2FAA2F;gBAC3F,MAAM,OAAO,GAAG,GAAG,EAAE;oBACnB,IAAI,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI;wBAAE,MAAM,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;oBACjF,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;oBAC5C,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACtC,CAAC,CAAC;gBACF,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC1C,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,CAAC,YAEA,KAAK,GACJ,CACL,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n skipNavRecipe,\n validateSkipNavDescriptor,\n type SkipNavDescriptor,\n} from \"@hjmds/design-contracts/components/skip-nav\";\nimport { forwardRef, useEffect, useRef, type AnchorHTMLAttributes, type CSSProperties } from \"react\";\nimport { classNames } from \"./internal.js\";\n\n// No `layoutStyle`: off-screen until focused, then pinned by the recipe offset; moving it would hide the focus target.\nexport type SkipNavProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\" | \"children\"> &\n SkipNavDescriptor &\n Readonly<{ className?: string }>;\n\nexport const SkipNav = forwardRef<HTMLAnchorElement, SkipNavProps>(function SkipNav(\n { targetId, label, className, onClick, ...props },\n forwardedRef,\n) {\n validateSkipNavDescriptor({ targetId, label });\n const restorations = useRef(new Map<HTMLElement, () => void>());\n useEffect(() => () => { for (const restore of [...restorations.current.values()]) restore(); }, []);\n return (\n <a\n {...props}\n ref={forwardedRef}\n href={`#${targetId}`}\n className={classNames(\"hjm-skip-nav\", className)}\n style={{\n \"--hjm-skip-nav-min-height\": `${skipNavRecipe.minHeight}px`,\n \"--hjm-skip-nav-padding\": `${skipNavRecipe.paddingHorizontal}px`,\n // The corner follows the nearest profile; pinned focus placement stays recipe-owned.\n \"--hjm-skip-nav-radius\": `var(--hjm-radius-md, ${skipNavRecipe.radius}px)`,\n \"--hjm-skip-nav-offset\": `${skipNavRecipe.offset}px`,\n } as CSSProperties}\n onClick={(event) => {\n onClick?.(event);\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n const target = document.getElementById(targetId);\n if (!target) return;\n /*\n The hash alone only scrolls; focus stays behind in the navigation, so\n the next Tab returns to the links the user just skipped. Making the\n target programmatically focusable moves the caret with the view.\n */\n if (!target.hasAttribute(\"tabindex\")) {\n target.setAttribute(\"tabindex\", \"-1\");\n // The target belongs to the page; temporary focusability must not outlive this navigation.\n const restore = () => {\n if (target.getAttribute(\"tabindex\") === \"-1\") target.removeAttribute(\"tabindex\");\n target.removeEventListener(\"blur\", restore);\n restorations.current.delete(target);\n };\n restorations.current.set(target, restore);\n target.addEventListener(\"blur\", restore, { once: true });\n }\n target.focus({ preventScroll: false });\n }}\n >\n {label}\n </a>\n );\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../src/sortable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIhG,OAAO,EAAgC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,yDAAyD,CAAC;AAGnK,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IACtE,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC7E,4FAA4F;IAC5F,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC;AAkBF,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BAiEhE"}
1
+ {"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../src/sortable.tsx"],"names":[],"mappings":"AACA,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIhG,OAAO,EAAgC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,yDAAyD,CAAC;AAGnK,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IACtE,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC7E,4FAA4F;IAC5F,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC;AAkBF,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BAiEhE"}
package/dist/sortable.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { control } from "@hjmds/design-contracts/foundations";
2
3
  import { useCallback, useLayoutEffect, useRef, useState, useMemo } from "react";
3
4
  import { Accessibility } from "@dnd-kit/dom";
4
5
  import { DragDropProvider } from "@dnd-kit/react";
@@ -9,7 +10,7 @@ import { useHjmTheme } from "./provider.js";
9
10
  function Row({ item, index, children, disabled, reduced, labels, move, count }) {
10
11
  const { ref, handleRef } = useSortable({ id: item.id, index, disabled: disabled || item.disabled === true,
11
12
  ...(reduced ? { transition: null } : {}) });
12
- return _jsxs("li", { ref: ref, "data-item-id": item.id, style: { display: "grid", gridTemplateColumns: "auto minmax(0, 1fr)", alignItems: "center", gap: "var(--hjm-space-sm)", paddingBlock: "var(--hjm-space-xs)" }, children: [_jsx(Button, { ref: handleRef, tone: "ghost", type: "button", disabled: disabled || item.disabled, "aria-label": labels.handle(item), style: { minWidth: 44, minHeight: 44, touchAction: "none", cursor: "grab" }, children: "\u283F" }), _jsx("div", { style: { minWidth: 0, overflowWrap: "anywhere" }, children: children }), _jsxs("div", { style: { gridColumn: "1 / -1", display: "flex", flexWrap: "wrap", gap: "var(--hjm-space-xs)" }, children: [_jsx(Button, { tone: "ghost", disabled: disabled || item.disabled || index === 0, onClick: () => move(index - 1), children: labels.previous(item) }), _jsx(Button, { tone: "ghost", disabled: disabled || item.disabled || index === count - 1, onClick: () => move(index + 1), children: labels.next(item) })] })] });
13
+ return _jsxs("li", { ref: ref, "data-item-id": item.id, style: { display: "grid", gridTemplateColumns: "auto minmax(0, 1fr)", alignItems: "center", gap: "var(--hjm-space-sm)", paddingBlock: "var(--hjm-space-xs)" }, children: [_jsx(Button, { ref: handleRef, tone: "ghost", type: "button", disabled: disabled || item.disabled, "aria-label": labels.handle(item), style: { minWidth: control.minTouchTarget, minHeight: control.minTouchTarget, touchAction: "none", cursor: "grab" }, children: "\u283F" }), _jsx("div", { style: { minWidth: 0, overflowWrap: "anywhere" }, children: children }), _jsxs("div", { style: { gridColumn: "1 / -1", display: "flex", flexWrap: "wrap", gap: "var(--hjm-space-xs)" }, children: [_jsx(Button, { tone: "ghost", disabled: disabled || item.disabled || index === 0, onClick: () => move(index - 1), children: labels.previous(item) }), _jsx(Button, { tone: "ghost", disabled: disabled || item.disabled || index === count - 1, onClick: () => move(index + 1), children: labels.next(item) })] })] });
13
14
  }
14
15
  /** Small controlled collections only; persistence and rollback belong to the host. */
15
16
  export function SortableCollection(props) {
@@ -1 +1 @@
1
- {"version":3,"file":"sortable.js","sourceRoot":"","sources":["../src/sortable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useLayoutEffect, useRef, useState, useMemo, type ReactNode } from \"react\";\nimport { Accessibility, type DragStartEvent, type DragEndEvent } from \"@dnd-kit/dom\";\nimport { DragDropProvider } from \"@dnd-kit/react\";\nimport { useSortable, isSortable } from \"@dnd-kit/react/sortable\";\nimport { reorderIntent, validateItems, type SortableItem, type SortableLabels, type ReorderIntent } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { Button } from \"./actions.js\";\nimport { useHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SortableCollectionProps = {\n items: readonly SortableItem[]; label: string; labels: SortableLabels;\n renderItem(item: SortableItem): ReactNode;\n onCommit(intent: ReorderIntent): void; onCancel?(): void; disabled?: boolean;\n /** Canonical layout-only placement on the list (DragDropProvider has no box of its own). */\n layoutStyle?: HjmCompositionStyleProp;\n};\nfunction Row({ item, index, children, disabled, reduced, labels, move, count }: {\n item: SortableItem; index: number; children: ReactNode; disabled: boolean; reduced: boolean;\n labels: SortableLabels; move(to: number): void; count: number;\n}) {\n const { ref, handleRef } = useSortable({ id: item.id, index, disabled: disabled || item.disabled === true,\n ...(reduced ? { transition: null } : {}) });\n return <li ref={ref} data-item-id={item.id} style={{ display: \"grid\", gridTemplateColumns: \"auto minmax(0, 1fr)\", alignItems: \"center\", gap: \"var(--hjm-space-sm)\", paddingBlock: \"var(--hjm-space-xs)\" }}>\n <Button ref={handleRef} tone=\"ghost\" type=\"button\" disabled={disabled || item.disabled} aria-label={labels.handle(item)}\n style={{ minWidth: 44, minHeight: 44, touchAction: \"none\", cursor: \"grab\" }}>⠿</Button>\n <div style={{ minWidth: 0, overflowWrap: \"anywhere\" }}>{children}</div>\n {/* Separate action line preserves readable item labels at 200% text in narrow hosts. */}\n <div style={{ gridColumn: \"1 / -1\", display: \"flex\", flexWrap: \"wrap\", gap: \"var(--hjm-space-xs)\" }}>\n <Button tone=\"ghost\" disabled={disabled || item.disabled || index === 0} onClick={() => move(index - 1)}>{labels.previous(item)}</Button>\n <Button tone=\"ghost\" disabled={disabled || item.disabled || index === count - 1} onClick={() => move(index + 1)}>{labels.next(item)}</Button>\n </div>\n </li>;\n}\n/** Small controlled collections only; persistence and rollback belong to the host. */\nexport function SortableCollection(props: SortableCollectionProps) {\n validateItems(props.items);\n if (!props.label.trim()) throw new TypeError(\"SortableCollection needs a label\");\n const { environment } = useHjmTheme();\n const [announcement, setAnnouncement] = useState(\"\");\n const latest = useRef(props); latest.current = props;\n const accessibility = useMemo(() => Accessibility.configure({\n screenReaderInstructions: { draggable: props.labels.instructions },\n announcements: {\n dragstart: ({ operation }: DragStartEvent) => {\n const item = latest.current.items.find(i => i.id === operation.source?.id);\n return item ? latest.current.labels.dragStart(item) : undefined;\n },\n dragend: ({ canceled }: DragEndEvent) => canceled ? latest.current.labels.dragCancel : undefined,\n // HJM emits the accepted controlled position, never raw upstream IDs.\n dragover: () => undefined, dragmove: () => undefined,\n },\n }), [props.labels.instructions]);\n const plugins = useCallback((defaults: readonly unknown[]) => [...defaults.filter(plugin => plugin !== Accessibility), accessibility],\n // A fresh function every render made the provider reassign manager.plugins on each render (2026-09-30 review).\n [accessibility]) as unknown as NonNullable<Parameters<typeof DragDropProvider>[0][\"plugins\"]>;\n const list = useRef<HTMLUListElement>(null);\n // Moving a row re-parents its DOM node, and an edge move disables the pressed button: both dropped focus to\n // <body> after a keyboard reorder. Refocus the moved row once the host has accepted the new order.\n const refocus = useRef<{ id: string; direction: \"previous\" | \"next\" } | null>(null);\n const snapshot = useRef(\"\");\n const signature = JSON.stringify(props.items);\n useLayoutEffect(() => {\n const pending = refocus.current;\n if (!pending) return;\n const row = list.current?.querySelector<HTMLElement>(`[data-item-id=\"${CSS.escape(pending.id)}\"]`);\n if (!row) return;\n refocus.current = null;\n const [handle, previous, next] = [...row.querySelectorAll<HTMLButtonElement>(\"button\")];\n const preferred = pending.direction === \"previous\" ? previous : next;\n const fallback = pending.direction === \"previous\" ? next : previous;\n (preferred && !preferred.disabled ? preferred : fallback && !fallback.disabled ? fallback : handle)?.focus();\n // Announce only once the controlled order reflects the move, never a proposed position.\n const index = props.items.findIndex(item => item.id === pending.id);\n if (index >= 0) setAnnouncement(props.labels.position(props.items[index]!, index + 1, props.items.length));\n }, [signature]); // eslint-disable-line react-hooks/exhaustive-deps\n const commit = (id: string, to: number, source: ReorderIntent[\"source\"]) => {\n if (props.disabled) return;\n const intent = reorderIntent(props.items, id, to, source);\n if (!intent) {\n // A rejected drag (fixed row in range, same index) must still end the host's drag session.\n if (source === \"drag\") props.onCancel?.();\n return;\n }\n if (source === \"keyboard\") refocus.current = { id, direction: intent.toIndex < intent.fromIndex ? \"previous\" : \"next\" };\n else refocus.current = { id, direction: \"next\" };\n props.onCommit(intent);\n };\n return <DragDropProvider plugins={plugins} onDragStart={() => { snapshot.current = signature; }} onDragEnd={event => {\n if (event.canceled || snapshot.current !== signature) { props.onCancel?.(); return; }\n const source = event.operation.source;\n if (source && isSortable(source)) commit(String(source.id), source.index, \"drag\");\n }}>\n <ul ref={list} aria-label={props.label} style={{ listStyle: \"none\", margin: 0, padding: 0, ...props.layoutStyle }}>\n {props.items.map((item, index) => <Row key={item.id} item={item} index={index} count={props.items.length}\n disabled={props.disabled ?? false} reduced={environment.reducedMotion} labels={props.labels}\n move={to => commit(item.id, to, \"keyboard\")}>{props.renderItem(item)}</Row>)}\n </ul>\n <span role=\"status\" style={{ position: \"absolute\", width: 1, height: 1, overflow: \"hidden\", clipPath: \"inset(50%)\" }}>{announcement}</span>\n </DragDropProvider>;\n}\n"]}
1
+ {"version":3,"file":"sortable.js","sourceRoot":"","sources":["../src/sortable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { control } from \"@hjmds/design-contracts/foundations\";\nimport { useCallback, useLayoutEffect, useRef, useState, useMemo, type ReactNode } from \"react\";\nimport { Accessibility, type DragStartEvent, type DragEndEvent } from \"@dnd-kit/dom\";\nimport { DragDropProvider } from \"@dnd-kit/react\";\nimport { useSortable, isSortable } from \"@dnd-kit/react/sortable\";\nimport { reorderIntent, validateItems, type SortableItem, type SortableLabels, type ReorderIntent } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { Button } from \"./actions.js\";\nimport { useHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SortableCollectionProps = {\n items: readonly SortableItem[]; label: string; labels: SortableLabels;\n renderItem(item: SortableItem): ReactNode;\n onCommit(intent: ReorderIntent): void; onCancel?(): void; disabled?: boolean;\n /** Canonical layout-only placement on the list (DragDropProvider has no box of its own). */\n layoutStyle?: HjmCompositionStyleProp;\n};\nfunction Row({ item, index, children, disabled, reduced, labels, move, count }: {\n item: SortableItem; index: number; children: ReactNode; disabled: boolean; reduced: boolean;\n labels: SortableLabels; move(to: number): void; count: number;\n}) {\n const { ref, handleRef } = useSortable({ id: item.id, index, disabled: disabled || item.disabled === true,\n ...(reduced ? { transition: null } : {}) });\n return <li ref={ref} data-item-id={item.id} style={{ display: \"grid\", gridTemplateColumns: \"auto minmax(0, 1fr)\", alignItems: \"center\", gap: \"var(--hjm-space-sm)\", paddingBlock: \"var(--hjm-space-xs)\" }}>\n <Button ref={handleRef} tone=\"ghost\" type=\"button\" disabled={disabled || item.disabled} aria-label={labels.handle(item)}\n style={{ minWidth: control.minTouchTarget, minHeight: control.minTouchTarget, touchAction: \"none\", cursor: \"grab\" }}>⠿</Button>\n <div style={{ minWidth: 0, overflowWrap: \"anywhere\" }}>{children}</div>\n {/* Separate action line preserves readable item labels at 200% text in narrow hosts. */}\n <div style={{ gridColumn: \"1 / -1\", display: \"flex\", flexWrap: \"wrap\", gap: \"var(--hjm-space-xs)\" }}>\n <Button tone=\"ghost\" disabled={disabled || item.disabled || index === 0} onClick={() => move(index - 1)}>{labels.previous(item)}</Button>\n <Button tone=\"ghost\" disabled={disabled || item.disabled || index === count - 1} onClick={() => move(index + 1)}>{labels.next(item)}</Button>\n </div>\n </li>;\n}\n/** Small controlled collections only; persistence and rollback belong to the host. */\nexport function SortableCollection(props: SortableCollectionProps) {\n validateItems(props.items);\n if (!props.label.trim()) throw new TypeError(\"SortableCollection needs a label\");\n const { environment } = useHjmTheme();\n const [announcement, setAnnouncement] = useState(\"\");\n const latest = useRef(props); latest.current = props;\n const accessibility = useMemo(() => Accessibility.configure({\n screenReaderInstructions: { draggable: props.labels.instructions },\n announcements: {\n dragstart: ({ operation }: DragStartEvent) => {\n const item = latest.current.items.find(i => i.id === operation.source?.id);\n return item ? latest.current.labels.dragStart(item) : undefined;\n },\n dragend: ({ canceled }: DragEndEvent) => canceled ? latest.current.labels.dragCancel : undefined,\n // HJM emits the accepted controlled position, never raw upstream IDs.\n dragover: () => undefined, dragmove: () => undefined,\n },\n }), [props.labels.instructions]);\n const plugins = useCallback((defaults: readonly unknown[]) => [...defaults.filter(plugin => plugin !== Accessibility), accessibility],\n // A fresh function every render made the provider reassign manager.plugins on each render (2026-09-30 review).\n [accessibility]) as unknown as NonNullable<Parameters<typeof DragDropProvider>[0][\"plugins\"]>;\n const list = useRef<HTMLUListElement>(null);\n // Moving a row re-parents its DOM node, and an edge move disables the pressed button: both dropped focus to\n // <body> after a keyboard reorder. Refocus the moved row once the host has accepted the new order.\n const refocus = useRef<{ id: string; direction: \"previous\" | \"next\" } | null>(null);\n const snapshot = useRef(\"\");\n const signature = JSON.stringify(props.items);\n useLayoutEffect(() => {\n const pending = refocus.current;\n if (!pending) return;\n const row = list.current?.querySelector<HTMLElement>(`[data-item-id=\"${CSS.escape(pending.id)}\"]`);\n if (!row) return;\n refocus.current = null;\n const [handle, previous, next] = [...row.querySelectorAll<HTMLButtonElement>(\"button\")];\n const preferred = pending.direction === \"previous\" ? previous : next;\n const fallback = pending.direction === \"previous\" ? next : previous;\n (preferred && !preferred.disabled ? preferred : fallback && !fallback.disabled ? fallback : handle)?.focus();\n // Announce only once the controlled order reflects the move, never a proposed position.\n const index = props.items.findIndex(item => item.id === pending.id);\n if (index >= 0) setAnnouncement(props.labels.position(props.items[index]!, index + 1, props.items.length));\n }, [signature]); // eslint-disable-line react-hooks/exhaustive-deps\n const commit = (id: string, to: number, source: ReorderIntent[\"source\"]) => {\n if (props.disabled) return;\n const intent = reorderIntent(props.items, id, to, source);\n if (!intent) {\n // A rejected drag (fixed row in range, same index) must still end the host's drag session.\n if (source === \"drag\") props.onCancel?.();\n return;\n }\n if (source === \"keyboard\") refocus.current = { id, direction: intent.toIndex < intent.fromIndex ? \"previous\" : \"next\" };\n else refocus.current = { id, direction: \"next\" };\n props.onCommit(intent);\n };\n return <DragDropProvider plugins={plugins} onDragStart={() => { snapshot.current = signature; }} onDragEnd={event => {\n if (event.canceled || snapshot.current !== signature) { props.onCancel?.(); return; }\n const source = event.operation.source;\n if (source && isSortable(source)) commit(String(source.id), source.index, \"drag\");\n }}>\n <ul ref={list} aria-label={props.label} style={{ listStyle: \"none\", margin: 0, padding: 0, ...props.layoutStyle }}>\n {props.items.map((item, index) => <Row key={item.id} item={item} index={index} count={props.items.length}\n disabled={props.disabled ?? false} reduced={environment.reducedMotion} labels={props.labels}\n move={to => commit(item.id, to, \"keyboard\")}>{props.renderItem(item)}</Row>)}\n </ul>\n <span role=\"status\" style={{ position: \"absolute\", width: 1, height: 1, overflow: \"hidden\", clipPath: \"inset(50%)\" }}>{announcement}</span>\n </DragDropProvider>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,YAAY,EAClB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAKL,KAAK,aAAa,EAGlB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,WAAW,EAAE,SAAS,CAAC;IACvB,aAAa,EAAE,SAAS,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAyCH,eAAO,MAAM,QAAQ;IA9DnB,kDAAkD;WAC3C,MAAM;SACR,MAAM;SACN,MAAM;WACJ,MAAM;WACN,YAAY;YACX,MAAM;mBACC,MAAM;oBACL,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;IACvC,wEAAwE;uBACrD,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;IAC1C,6EAA6E;mBAC9D,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM;eAC7B,OAAO;IAClB,6EAA6E;iBAChE,SAAS;mBACP,SAAS;gBACZ,MAAM;YACV,aAAa;IACrB,gGAAgG;kBAClF,uBAAuB;mDAsKrC,CAAC"}
1
+ {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,YAAY,EAClB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAML,KAAK,aAAa,EAGlB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,WAAW,EAAE,SAAS,CAAC;IACvB,aAAa,EAAE,SAAS,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAyCH,eAAO,MAAM,QAAQ;IA9DnB,kDAAkD;WAC3C,MAAM;SACR,MAAM;SACN,MAAM;WACJ,MAAM;WACN,YAAY;YACX,MAAM;mBACC,MAAM;oBACL,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;IACvC,wEAAwE;uBACrD,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;IAC1C,6EAA6E;mBAC9D,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM;eAC7B,OAAO;IAClB,6EAA6E;iBAChE,SAAS;mBACP,SAAS;gBACZ,MAAM;YACV,aAAa;IACrB,gGAAgG;kBAClF,uBAAuB;mDA+KrC,CAAC"}
package/dist/splitter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { getNextSplitterValue, resolveSplitterBoundaryValue, resolveSplitterDragValue, resolveSplitterSeparatorOrientation, splitterDefaults, splitterRecipe, validateSplitterDescriptor, } from "@hjmds/design-contracts/components/splitter";
3
- import { forwardRef, useEffect, useRef, useState, } from "react";
3
+ import { forwardRef, useCallback, useEffect, useRef, useState, } from "react";
4
4
  import { classNames, composeRefs, useControllableState } from "./internal.js";
5
5
  /*
6
6
  The value is a percentage of the root box: `min`/`max`/`step` stay in the
@@ -61,6 +61,22 @@ export const Splitter = forwardRef(function Splitter({ label, min, max, step, ax
61
61
  validateSplitterDescriptor(descriptor);
62
62
  const rootRef = useRef(null);
63
63
  const [dragging, setDragging] = useState(false);
64
+ const drag = useRef(null);
65
+ const currentValue = useRef(value);
66
+ currentValue.current = value;
67
+ const finishDrag = useCallback(() => {
68
+ const session = drag.current;
69
+ if (!session)
70
+ return;
71
+ // Release can synchronously produce lostpointercapture; retire ownership first.
72
+ drag.current = null;
73
+ setDragging(false);
74
+ if (session.target.hasPointerCapture(session.pointerId))
75
+ session.target.releasePointerCapture(session.pointerId);
76
+ onValueChangeEnd?.(currentValue.current);
77
+ }, [onValueChangeEnd]);
78
+ useEffect(() => { if (disabled)
79
+ finishDrag(); }, [disabled, finishDrag]);
64
80
  const commit = (next) => {
65
81
  if (next !== value)
66
82
  setValue(next);
@@ -93,32 +109,28 @@ export const Splitter = forwardRef(function Splitter({ label, min, max, step, ax
93
109
  onValueChangeEnd?.(next);
94
110
  };
95
111
  const onPointerDown = (event) => {
96
- if (disabled || event.button !== 0)
112
+ if (disabled || event.button !== 0 || drag.current)
97
113
  return;
98
114
  event.preventDefault();
115
+ drag.current = { pointerId: event.pointerId, target: event.currentTarget };
99
116
  event.currentTarget.setPointerCapture(event.pointerId);
100
117
  setDragging(true);
101
118
  };
102
119
  const resize = (event) => {
103
120
  const root = rootRef.current;
104
- if (!dragging || !root)
121
+ if (disabled || drag.current?.pointerId !== event.pointerId || !root)
105
122
  return;
106
123
  const fraction = fractionFromPointer(root, axis, event.clientX, event.clientY);
107
124
  commit(resolveSplitterDragValue(descriptor, min + fraction * (max - min)));
108
125
  };
109
126
  const endDrag = (event) => {
110
- if (!dragging)
111
- return;
112
- if (event.currentTarget.hasPointerCapture(event.pointerId)) {
113
- event.currentTarget.releasePointerCapture(event.pointerId);
114
- }
115
- setDragging(false);
116
- onValueChangeEnd?.(value);
127
+ if (drag.current?.pointerId === event.pointerId)
128
+ finishDrag();
117
129
  };
118
130
  const [primaryScrollable, primaryPaneRef] = useScrollableTabStop();
119
131
  const [secondaryScrollable, secondaryPaneRef] = useScrollableTabStop();
120
132
  const fraction = max === min ? 0 : (value - min) / (max - min);
121
- return (_jsxs("div", { ref: composeRefs(rootRef, forwardedRef), className: classNames("hjm-splitter", className), "data-axis": axis, "data-dragging": dragging || undefined, style: { ...style, ...layoutStyle, "--hjm-splitter-primary": `${fraction * 100}%` }, children: [_jsx("div", { ref: primaryPaneRef, className: "hjm-splitter__pane", tabIndex: primaryScrollable ? 0 : undefined, "data-pane": "primary", children: primaryPane }), _jsx("div", { role: "separator", "aria-orientation": resolveSplitterSeparatorOrientation(axis), "aria-label": label, "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-valuetext": descriptor.valueText, "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 0, className: "hjm-splitter__separator", style: { "--hjm-splitter-hit-target": `${splitterRecipe.separator.hitTarget}px`, "--hjm-splitter-thickness": `${splitterRecipe.separator.thickness}px` }, onKeyDown: onKeyDown, onPointerDown: onPointerDown, onPointerMove: resize, onPointerUp: endDrag, onPointerCancel: endDrag, children: _jsx("span", { className: "hjm-splitter__handle", "aria-hidden": "true" }) }), _jsx("div", { ref: secondaryPaneRef, className: "hjm-splitter__pane", tabIndex: secondaryScrollable ? 0 : undefined, "data-pane": "secondary", children: secondaryPane })] }));
133
+ return (_jsxs("div", { ref: composeRefs(rootRef, forwardedRef), className: classNames("hjm-splitter", className), "data-axis": axis, "data-dragging": dragging || undefined, style: { ...style, ...layoutStyle, "--hjm-splitter-primary": `${fraction * 100}%` }, children: [_jsx("div", { ref: primaryPaneRef, className: "hjm-splitter__pane", tabIndex: primaryScrollable ? 0 : undefined, "data-pane": "primary", children: primaryPane }), _jsx("div", { role: "separator", "aria-orientation": resolveSplitterSeparatorOrientation(axis), "aria-label": label, "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-valuetext": descriptor.valueText, "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 0, className: "hjm-splitter__separator", style: { "--hjm-splitter-hit-target": `${splitterRecipe.separator.hitTarget}px`, "--hjm-splitter-thickness": `${splitterRecipe.separator.thickness}px` }, onKeyDown: onKeyDown, onPointerDown: onPointerDown, onPointerMove: resize, onPointerUp: endDrag, onPointerCancel: endDrag, onLostPointerCapture: endDrag, children: _jsx("span", { className: "hjm-splitter__handle", "aria-hidden": "true" }) }), _jsx("div", { ref: secondaryPaneRef, className: "hjm-splitter__pane", tabIndex: secondaryScrollable ? 0 : undefined, "data-pane": "secondary", children: secondaryPane })] }));
122
134
  });
123
135
  function arrowIntent(key, axis, flipped) {
124
136
  // Only the axis the panes are arranged along resizes; the other pair of arrow
@@ -1 +1 @@
1
- {"version":3,"file":"splitter.js","sourceRoot":"","sources":["../src/splitter.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n getNextSplitterValue,\n resolveSplitterBoundaryValue,\n resolveSplitterDragValue,\n resolveSplitterSeparatorOrientation,\n splitterDefaults,\n splitterRecipe,\n validateSplitterDescriptor,\n type SplitterAxis,\n} from \"@hjmds/design-contracts/components/splitter\";\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\nimport { classNames, composeRefs, useControllableState } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SplitterProps = Readonly<{\n /** Required accessible name for the separator. */\n label: string;\n min: number;\n max: number;\n step?: number;\n axis?: SplitterAxis;\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n /** Fires once when a drag settles, for owners that persist the size. */\n onValueChangeEnd?: (value: number) => void;\n /** Product-owned announcement of the current size, e.g. \"35%\" or \"320px\". */\n getValueText?: (value: number) => string;\n disabled?: boolean;\n /** The pane the value sizes, and the pane that takes the remaining space. */\n primaryPane: ReactNode;\n secondaryPane: ReactNode;\n className?: string;\n style?: CSSProperties;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/*\n The value is a percentage of the root box: `min`/`max`/`step` stay in the\n product's own unit as the contract requires, but the pointer has to be turned\n into that unit somehow, and only a fraction of the container is meaningful\n without knowing the product's unit. Products that size in px pass min/max in\n px and read the same fraction back through their own formatting.\n*/\nfunction fractionFromPointer(root: HTMLElement, axis: SplitterAxis, clientX: number, clientY: number): number {\n const box = root.getBoundingClientRect();\n const horizontal = axis === \"horizontal\";\n const span = horizontal ? box.width : box.height;\n if (span === 0) return 0;\n const offset = horizontal ? clientX - box.left : clientY - box.top;\n // Logical, not physical: in RTL the primary pane starts at the right edge.\n const flipped = horizontal && getComputedStyle(root).direction === \"rtl\";\n return (flipped ? span - offset : offset) / span;\n}\n\n// Scrolling panes need a keyboard stop (axe scrollable-region-focusable), but only while they\n// overflow: always-on tabIndex added two dead stops. Pane labels would need a breaking prop.\nfunction useScrollableTabStop(): readonly [boolean, (node: HTMLDivElement | null) => void] {\n const [node, setNode] = useState<HTMLDivElement | null>(null);\n const [scrollable, setScrollable] = useState(false);\n useEffect(() => {\n if (!node) return;\n const measure = () => setScrollable(node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth);\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n // Content can grow without resizing the pane: watch children and mutations too.\n const resize = new ResizeObserver(measure);\n const observe = () => { resize.disconnect(); resize.observe(node); for (const child of node.children) resize.observe(child); };\n observe();\n const mutation = new MutationObserver(() => { observe(); measure(); });\n mutation.observe(node, { childList: true, subtree: true, characterData: true });\n return () => { resize.disconnect(); mutation.disconnect(); };\n }, [node]);\n return [scrollable, setNode] as const;\n}\n\nexport const Splitter = forwardRef<HTMLDivElement, SplitterProps>(function Splitter(\n {\n label,\n min,\n max,\n step,\n axis = splitterDefaults.axis,\n value: controlledValue,\n defaultValue,\n onValueChange,\n onValueChangeEnd,\n getValueText,\n disabled = false,\n primaryPane,\n secondaryPane,\n className,\n layoutStyle,\n style,\n },\n forwardedRef,\n) {\n const [value, setValue] = useControllableState<number>({\n ...(controlledValue === undefined ? {} : { value: controlledValue }),\n defaultValue: defaultValue ?? min,\n ...(onValueChange === undefined ? {} : { onChange: onValueChange }),\n });\n const descriptor = {\n value,\n min,\n max,\n ...(step === undefined ? {} : { step }),\n axis,\n label,\n ...(getValueText === undefined ? {} : { valueText: getValueText(value) }),\n };\n validateSplitterDescriptor(descriptor);\n const rootRef = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n\n const commit = (next: number) => {\n if (next !== value) setValue(next);\n return next;\n };\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n const root = rootRef.current;\n const flipped = axis === \"horizontal\" && root !== null && getComputedStyle(root).direction === \"rtl\";\n const intent = arrowIntent(event.key, axis, flipped);\n const next =\n event.key === \"Home\" || event.key === \"End\"\n ? resolveSplitterBoundaryValue(descriptor, event.key === \"Home\" ? \"min\" : \"max\")\n : intent === undefined\n ? undefined\n : getNextSplitterValue(descriptor, intent);\n if (next === undefined) return;\n event.preventDefault();\n // Keyboard resize settles immediately: there is no drag to release, but an\n // owner persisting the size still needs the same end signal a drag gives.\n if (next === value) return;\n // Commit before notifying, never as the argument of an optional call:\n // `onValueChangeEnd?.(commit(next))` skips the argument entirely when no\n // handler is passed, so keyboard resize would silently do nothing.\n commit(next);\n // A step already at the boundary changes nothing, so it is not a settlement\n // — an owner persisting on every end would otherwise write a no-op.\n onValueChangeEnd?.(next);\n };\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (disabled || event.button !== 0) return;\n event.preventDefault();\n event.currentTarget.setPointerCapture(event.pointerId);\n setDragging(true);\n };\n const resize = (event: PointerEvent<HTMLDivElement>) => {\n const root = rootRef.current;\n if (!dragging || !root) return;\n const fraction = fractionFromPointer(root, axis, event.clientX, event.clientY);\n commit(resolveSplitterDragValue(descriptor, min + fraction * (max - min)));\n };\n const endDrag = (event: PointerEvent<HTMLDivElement>) => {\n if (!dragging) return;\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n setDragging(false);\n onValueChangeEnd?.(value);\n };\n\n const [primaryScrollable, primaryPaneRef] = useScrollableTabStop();\n const [secondaryScrollable, secondaryPaneRef] = useScrollableTabStop();\n const fraction = max === min ? 0 : (value - min) / (max - min);\n return (\n <div\n ref={composeRefs(rootRef, forwardedRef)}\n className={classNames(\"hjm-splitter\", className)}\n data-axis={axis}\n data-dragging={dragging || undefined}\n style={{ ...style, ...layoutStyle, \"--hjm-splitter-primary\": `${fraction * 100}%` } as CSSProperties}\n >\n <div ref={primaryPaneRef} className=\"hjm-splitter__pane\" tabIndex={primaryScrollable ? 0 : undefined} data-pane=\"primary\">{primaryPane}</div>\n <div\n role=\"separator\"\n // Perpendicular by contract: side-by-side panes have a vertical bar.\n aria-orientation={resolveSplitterSeparatorOrientation(axis)}\n aria-label={label}\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={descriptor.valueText}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n className=\"hjm-splitter__separator\"\n style={{ \"--hjm-splitter-hit-target\": `${splitterRecipe.separator.hitTarget}px`, \"--hjm-splitter-thickness\": `${splitterRecipe.separator.thickness}px` } as CSSProperties}\n onKeyDown={onKeyDown}\n onPointerDown={onPointerDown}\n onPointerMove={resize}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n >\n <span className=\"hjm-splitter__handle\" aria-hidden=\"true\" />\n </div>\n <div ref={secondaryPaneRef} className=\"hjm-splitter__pane\" tabIndex={secondaryScrollable ? 0 : undefined} data-pane=\"secondary\">{secondaryPane}</div>\n </div>\n );\n});\n\nfunction arrowIntent(\n key: string,\n axis: SplitterAxis,\n flipped: boolean,\n): \"increment\" | \"decrement\" | undefined {\n // Only the axis the panes are arranged along resizes; the other pair of arrow\n // keys belongs to whatever the pane content does with them. `flipped` keeps the\n // keyboard on the same logical direction the drag uses: in RTL the primary pane\n // grows toward the left, so ArrowLeft is the increment.\n if (axis === \"horizontal\") {\n if (key === (flipped ? \"ArrowLeft\" : \"ArrowRight\")) return \"increment\";\n if (key === (flipped ? \"ArrowRight\" : \"ArrowLeft\")) return \"decrement\";\n return undefined;\n }\n if (key === \"ArrowDown\") return \"increment\";\n if (key === \"ArrowUp\") return \"decrement\";\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"splitter.js","sourceRoot":"","sources":["../src/splitter.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n getNextSplitterValue,\n resolveSplitterBoundaryValue,\n resolveSplitterDragValue,\n resolveSplitterSeparatorOrientation,\n splitterDefaults,\n splitterRecipe,\n validateSplitterDescriptor,\n type SplitterAxis,\n} from \"@hjmds/design-contracts/components/splitter\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\nimport { classNames, composeRefs, useControllableState } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type SplitterProps = Readonly<{\n /** Required accessible name for the separator. */\n label: string;\n min: number;\n max: number;\n step?: number;\n axis?: SplitterAxis;\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n /** Fires once when a drag settles, for owners that persist the size. */\n onValueChangeEnd?: (value: number) => void;\n /** Product-owned announcement of the current size, e.g. \"35%\" or \"320px\". */\n getValueText?: (value: number) => string;\n disabled?: boolean;\n /** The pane the value sizes, and the pane that takes the remaining space. */\n primaryPane: ReactNode;\n secondaryPane: ReactNode;\n className?: string;\n style?: CSSProperties;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/*\n The value is a percentage of the root box: `min`/`max`/`step` stay in the\n product's own unit as the contract requires, but the pointer has to be turned\n into that unit somehow, and only a fraction of the container is meaningful\n without knowing the product's unit. Products that size in px pass min/max in\n px and read the same fraction back through their own formatting.\n*/\nfunction fractionFromPointer(root: HTMLElement, axis: SplitterAxis, clientX: number, clientY: number): number {\n const box = root.getBoundingClientRect();\n const horizontal = axis === \"horizontal\";\n const span = horizontal ? box.width : box.height;\n if (span === 0) return 0;\n const offset = horizontal ? clientX - box.left : clientY - box.top;\n // Logical, not physical: in RTL the primary pane starts at the right edge.\n const flipped = horizontal && getComputedStyle(root).direction === \"rtl\";\n return (flipped ? span - offset : offset) / span;\n}\n\n// Scrolling panes need a keyboard stop (axe scrollable-region-focusable), but only while they\n// overflow: always-on tabIndex added two dead stops. Pane labels would need a breaking prop.\nfunction useScrollableTabStop(): readonly [boolean, (node: HTMLDivElement | null) => void] {\n const [node, setNode] = useState<HTMLDivElement | null>(null);\n const [scrollable, setScrollable] = useState(false);\n useEffect(() => {\n if (!node) return;\n const measure = () => setScrollable(node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth);\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n // Content can grow without resizing the pane: watch children and mutations too.\n const resize = new ResizeObserver(measure);\n const observe = () => { resize.disconnect(); resize.observe(node); for (const child of node.children) resize.observe(child); };\n observe();\n const mutation = new MutationObserver(() => { observe(); measure(); });\n mutation.observe(node, { childList: true, subtree: true, characterData: true });\n return () => { resize.disconnect(); mutation.disconnect(); };\n }, [node]);\n return [scrollable, setNode] as const;\n}\n\nexport const Splitter = forwardRef<HTMLDivElement, SplitterProps>(function Splitter(\n {\n label,\n min,\n max,\n step,\n axis = splitterDefaults.axis,\n value: controlledValue,\n defaultValue,\n onValueChange,\n onValueChangeEnd,\n getValueText,\n disabled = false,\n primaryPane,\n secondaryPane,\n className,\n layoutStyle,\n style,\n },\n forwardedRef,\n) {\n const [value, setValue] = useControllableState<number>({\n ...(controlledValue === undefined ? {} : { value: controlledValue }),\n defaultValue: defaultValue ?? min,\n ...(onValueChange === undefined ? {} : { onChange: onValueChange }),\n });\n const descriptor = {\n value,\n min,\n max,\n ...(step === undefined ? {} : { step }),\n axis,\n label,\n ...(getValueText === undefined ? {} : { valueText: getValueText(value) }),\n };\n validateSplitterDescriptor(descriptor);\n const rootRef = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n const drag = useRef<{ pointerId: number; target: HTMLDivElement } | null>(null);\n const currentValue = useRef(value); currentValue.current = value;\n const finishDrag = useCallback(() => {\n const session = drag.current;\n if (!session) return;\n // Release can synchronously produce lostpointercapture; retire ownership first.\n drag.current = null;\n setDragging(false);\n if (session.target.hasPointerCapture(session.pointerId)) session.target.releasePointerCapture(session.pointerId);\n onValueChangeEnd?.(currentValue.current);\n }, [onValueChangeEnd]);\n useEffect(() => { if (disabled) finishDrag(); }, [disabled, finishDrag]);\n\n const commit = (next: number) => {\n if (next !== value) setValue(next);\n return next;\n };\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n const root = rootRef.current;\n const flipped = axis === \"horizontal\" && root !== null && getComputedStyle(root).direction === \"rtl\";\n const intent = arrowIntent(event.key, axis, flipped);\n const next =\n event.key === \"Home\" || event.key === \"End\"\n ? resolveSplitterBoundaryValue(descriptor, event.key === \"Home\" ? \"min\" : \"max\")\n : intent === undefined\n ? undefined\n : getNextSplitterValue(descriptor, intent);\n if (next === undefined) return;\n event.preventDefault();\n // Keyboard resize settles immediately: there is no drag to release, but an\n // owner persisting the size still needs the same end signal a drag gives.\n if (next === value) return;\n // Commit before notifying, never as the argument of an optional call:\n // `onValueChangeEnd?.(commit(next))` skips the argument entirely when no\n // handler is passed, so keyboard resize would silently do nothing.\n commit(next);\n // A step already at the boundary changes nothing, so it is not a settlement\n // — an owner persisting on every end would otherwise write a no-op.\n onValueChangeEnd?.(next);\n };\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (disabled || event.button !== 0 || drag.current) return;\n event.preventDefault();\n drag.current = { pointerId: event.pointerId, target: event.currentTarget };\n event.currentTarget.setPointerCapture(event.pointerId);\n setDragging(true);\n };\n const resize = (event: PointerEvent<HTMLDivElement>) => {\n const root = rootRef.current;\n if (disabled || drag.current?.pointerId !== event.pointerId || !root) return;\n const fraction = fractionFromPointer(root, axis, event.clientX, event.clientY);\n commit(resolveSplitterDragValue(descriptor, min + fraction * (max - min)));\n };\n const endDrag = (event: PointerEvent<HTMLDivElement>) => {\n if (drag.current?.pointerId === event.pointerId) finishDrag();\n };\n\n const [primaryScrollable, primaryPaneRef] = useScrollableTabStop();\n const [secondaryScrollable, secondaryPaneRef] = useScrollableTabStop();\n const fraction = max === min ? 0 : (value - min) / (max - min);\n return (\n <div\n ref={composeRefs(rootRef, forwardedRef)}\n className={classNames(\"hjm-splitter\", className)}\n data-axis={axis}\n data-dragging={dragging || undefined}\n style={{ ...style, ...layoutStyle, \"--hjm-splitter-primary\": `${fraction * 100}%` } as CSSProperties}\n >\n <div ref={primaryPaneRef} className=\"hjm-splitter__pane\" tabIndex={primaryScrollable ? 0 : undefined} data-pane=\"primary\">{primaryPane}</div>\n <div\n role=\"separator\"\n // Perpendicular by contract: side-by-side panes have a vertical bar.\n aria-orientation={resolveSplitterSeparatorOrientation(axis)}\n aria-label={label}\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={descriptor.valueText}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n className=\"hjm-splitter__separator\"\n style={{ \"--hjm-splitter-hit-target\": `${splitterRecipe.separator.hitTarget}px`, \"--hjm-splitter-thickness\": `${splitterRecipe.separator.thickness}px` } as CSSProperties}\n onKeyDown={onKeyDown}\n onPointerDown={onPointerDown}\n onPointerMove={resize}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onLostPointerCapture={endDrag}\n >\n <span className=\"hjm-splitter__handle\" aria-hidden=\"true\" />\n </div>\n <div ref={secondaryPaneRef} className=\"hjm-splitter__pane\" tabIndex={secondaryScrollable ? 0 : undefined} data-pane=\"secondary\">{secondaryPane}</div>\n </div>\n );\n});\n\nfunction arrowIntent(\n key: string,\n axis: SplitterAxis,\n flipped: boolean,\n): \"increment\" | \"decrement\" | undefined {\n // Only the axis the panes are arranged along resizes; the other pair of arrow\n // keys belongs to whatever the pane content does with them. `flipped` keeps the\n // keyboard on the same logical direction the drag uses: in RTL the primary pane\n // grows toward the left, so ArrowLeft is the increment.\n if (axis === \"horizontal\") {\n if (key === (flipped ? \"ArrowLeft\" : \"ArrowRight\")) return \"increment\";\n if (key === (flipped ? \"ArrowRight\" : \"ArrowLeft\")) return \"decrement\";\n return undefined;\n }\n if (key === \"ArrowDown\") return \"increment\";\n if (key === \"ArrowUp\") return \"decrement\";\n return undefined;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"statistic-motion.d.ts","sourceRoot":"","sources":["../src/statistic-motion.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,aAAa,CAAC,GAAG;IACxF,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAe,EAAE,GAAG,KAAK,EAAE,EAAE,sBAAsB,+BAgBzH"}
1
+ {"version":3,"file":"statistic-motion.d.ts","sourceRoot":"","sources":["../src/statistic-motion.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,aAAa,CAAC,GAAG;IACxF,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAe,EAAE,GAAG,KAAK,EAAE,EAAE,sBAAsB,+BAqCzH"}
@@ -1,10 +1,38 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import NumberFlow from "@number-flow/react";
2
+ import { useCallback, useState } from "react";
3
+ import NumberFlow, {} from "@number-flow/react";
3
4
  import { Statistic } from "./advanced-display.js";
4
5
  import { useHjmTheme } from "./provider.js";
5
6
  /** NumberFlow stays outside the base Statistic graph and its accessible text. */
6
7
  export function AnimatedStatistic({ descriptor, value, locale, format, animated = true, ...props }) {
7
8
  const theme = useHjmTheme();
9
+ const [visible, setVisible] = useState(true);
10
+ const motionRef = useCallback((node) => {
11
+ if (!(node instanceof HTMLElement))
12
+ return;
13
+ // NumberFlow skips new updates while hidden, but does not settle an already
14
+ // running transition on visibility changes or disconnection. Finish rather
15
+ // than cancel so its completion promises and latest digits settle together.
16
+ const finish = () => node.shadowRoot?.getAnimations?.().forEach(animation => animation.finish());
17
+ const visibility = () => { if (node.ownerDocument.visibilityState !== "visible")
18
+ finish(); };
19
+ const document = node.ownerDocument;
20
+ document.addEventListener("visibilitychange", visibility);
21
+ visibility();
22
+ // NumberFlow checks display:none, but not viewport intersection. Keep its
23
+ // host mounted so offscreen updates retain final values and can resume.
24
+ const observer = typeof IntersectionObserver === "undefined" ? undefined : new IntersectionObserver(([entry]) => {
25
+ const intersecting = entry?.isIntersecting ?? false;
26
+ if (!intersecting)
27
+ finish();
28
+ setVisible(intersecting);
29
+ });
30
+ if (observer)
31
+ observer.observe(node);
32
+ else
33
+ setVisible(false);
34
+ return () => { observer?.disconnect(); document.removeEventListener("visibilitychange", visibility); finish(); };
35
+ }, []);
8
36
  if (!Number.isFinite(value))
9
37
  throw new TypeError("AnimatedStatistic value must be finite");
10
38
  const formatter = new Intl.NumberFormat(locale, format);
@@ -17,7 +45,7 @@ export function AnimatedStatistic({ descriptor, value, locale, format, animated
17
45
  !["scientific", "engineering"].includes(format?.notation ?? "standard");
18
46
  const animatedFormat = { ...format, notation: format?.notation === "compact" ? "compact" : "standard" };
19
47
  return _jsx(Statistic, { ...props, descriptor: { ...descriptor, value: text }, renderValue: () => canAnimate
20
- ? _jsx(NumberFlow, { value: value, locales: locale, format: animatedFormat, respectMotionPreference: true })
48
+ ? _jsx(NumberFlow, { ref: motionRef, animated: visible, value: value, locales: locale, format: animatedFormat, respectMotionPreference: true })
21
49
  : text });
22
50
  }
23
51
  //# sourceMappingURL=statistic-motion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"statistic-motion.js","sourceRoot":"","sources":["../src/statistic-motion.tsx"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAuB,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAW5C,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,KAAK,EAA0B;IACxH,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC,CAAC;IAC3F,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,+EAA+E;IAC/E,4EAA4E;IAC5E,MAAM,UAAU,GAAG,QAAQ,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,aAAa;QAC7D,KAAK,CAAC,WAAW,CAAC,SAAS,KAAK,KAAK,IAAI,SAAS,CAAC,eAAe,EAAE,CAAC,eAAe,KAAK,MAAM;QAC/F,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,CAAC,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,IAAI,UAAU,CAAC,CAAC;IAC1E,MAAM,cAAc,GAAG,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAkB,CAAC,CAAC,CAAC,UAAmB,EAAE,CAAC;IAC1H,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EACrE,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU;YAC3B,CAAC,CAAC,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,uBAAuB,SAAG;YAC/F,CAAC,CAAC,IAAI,GAAI,CAAC;AACjB,CAAC","sourcesContent":["import NumberFlow from \"@number-flow/react\";\nimport { Statistic, type StatisticProps } from \"./advanced-display.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type AnimatedStatisticProps = Omit<StatisticProps, \"descriptor\" | \"renderValue\"> & {\n descriptor: Omit<StatisticProps[\"descriptor\"], \"value\">;\n value: number;\n /** Explicit locale keeps server/client formatting deterministic. */\n locale: string;\n format?: Intl.NumberFormatOptions;\n animated?: boolean;\n};\n\n/** NumberFlow stays outside the base Statistic graph and its accessible text. */\nexport function AnimatedStatistic({ descriptor, value, locale, format, animated = true, ...props }: AnimatedStatisticProps) {\n const theme = useHjmTheme();\n if (!Number.isFinite(value)) throw new TypeError(\"AnimatedStatistic value must be finite\");\n const formatter = new Intl.NumberFormat(locale, format);\n const text = formatter.format(value);\n // Upstream cannot animate non-Latin digits, RTL or exponent notation reliably.\n // Preserve Intl output instead of transliterating the user's number format.\n const canAnimate = animated && !theme.environment.reducedMotion &&\n theme.environment.direction !== \"rtl\" && formatter.resolvedOptions().numberingSystem === \"latn\" &&\n !/^(ar|fa|he|ur)(-|$)/i.test(locale) &&\n ![\"scientific\", \"engineering\"].includes(format?.notation ?? \"standard\");\n const animatedFormat = { ...format, notation: format?.notation === \"compact\" ? \"compact\" as const : \"standard\" as const };\n return <Statistic {...props} descriptor={{ ...descriptor, value: text }}\n renderValue={() => canAnimate\n ? <NumberFlow value={value} locales={locale} format={animatedFormat} respectMotionPreference />\n : text} />;\n}\n"]}
1
+ {"version":3,"file":"statistic-motion.js","sourceRoot":"","sources":["../src/statistic-motion.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useState } from \"react\";\nimport NumberFlow, { type NumberFlowElement } from \"@number-flow/react\";\nimport { Statistic, type StatisticProps } from \"./advanced-display.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type AnimatedStatisticProps = Omit<StatisticProps, \"descriptor\" | \"renderValue\"> & {\n descriptor: Omit<StatisticProps[\"descriptor\"], \"value\">;\n value: number;\n /** Explicit locale keeps server/client formatting deterministic. */\n locale: string;\n format?: Intl.NumberFormatOptions;\n animated?: boolean;\n};\n\n/** NumberFlow stays outside the base Statistic graph and its accessible text. */\nexport function AnimatedStatistic({ descriptor, value, locale, format, animated = true, ...props }: AnimatedStatisticProps) {\n const theme = useHjmTheme();\n const [visible, setVisible] = useState(true);\n const motionRef = useCallback((node: NumberFlowElement | null) => {\n if (!(node instanceof HTMLElement)) return;\n // NumberFlow skips new updates while hidden, but does not settle an already\n // running transition on visibility changes or disconnection. Finish rather\n // than cancel so its completion promises and latest digits settle together.\n const finish = () => node.shadowRoot?.getAnimations?.().forEach(animation => animation.finish());\n const visibility = () => { if (node.ownerDocument.visibilityState !== \"visible\") finish(); };\n const document = node.ownerDocument;\n document.addEventListener(\"visibilitychange\", visibility);\n visibility();\n // NumberFlow checks display:none, but not viewport intersection. Keep its\n // host mounted so offscreen updates retain final values and can resume.\n const observer = typeof IntersectionObserver === \"undefined\" ? undefined : new IntersectionObserver(([entry]) => {\n const intersecting = entry?.isIntersecting ?? false;\n if (!intersecting) finish();\n setVisible(intersecting);\n });\n if (observer) observer.observe(node); else setVisible(false);\n return () => { observer?.disconnect(); document.removeEventListener(\"visibilitychange\", visibility); finish(); };\n }, []);\n if (!Number.isFinite(value)) throw new TypeError(\"AnimatedStatistic value must be finite\");\n const formatter = new Intl.NumberFormat(locale, format);\n const text = formatter.format(value);\n // Upstream cannot animate non-Latin digits, RTL or exponent notation reliably.\n // Preserve Intl output instead of transliterating the user's number format.\n const canAnimate = animated && !theme.environment.reducedMotion &&\n theme.environment.direction !== \"rtl\" && formatter.resolvedOptions().numberingSystem === \"latn\" &&\n !/^(ar|fa|he|ur)(-|$)/i.test(locale) &&\n ![\"scientific\", \"engineering\"].includes(format?.notation ?? \"standard\");\n const animatedFormat = { ...format, notation: format?.notation === \"compact\" ? \"compact\" as const : \"standard\" as const };\n return <Statistic {...props} descriptor={{ ...descriptor, value: text }}\n renderValue={() => canAnimate\n ? <NumberFlow ref={motionRef} animated={visible} value={value} locales={locale} format={animatedFormat} respectMotionPreference />\n : text} />;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { type TextProps } from "./layout.js";
2
+ /** A short inline result; use Notice for a titled message/action and Toast for transient overlays. */
3
+ export type StatusTextProps = Omit<TextProps, "children" | "role" | "aria-live" | "aria-atomic"> & Readonly<{
4
+ children: string;
5
+ announcement?: "none" | "polite" | "assertive";
6
+ }>;
7
+ export declare const StatusText: import("react").ForwardRefExoticComponent<Omit<TextProps, "children" | "role" | "aria-atomic" | "aria-live"> & Readonly<{
8
+ children: string;
9
+ announcement?: "none" | "polite" | "assertive";
10
+ }> & import("react").RefAttributes<HTMLElement>>;
11
+ //# sourceMappingURL=status-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-text.d.ts","sourceRoot":"","sources":["../src/status-text.tsx"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,sGAAsG;AACtG,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC1G,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;CAChD,CAAC,CAAC;AAEH,eAAO,MAAM,UAAU;cAJX,MAAM;mBACD,MAAM,GAAG,QAAQ,GAAG,WAAW;gDAW9C,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { Text } from "./layout.js";
4
+ export const StatusText = forwardRef(function StatusText({ children, announcement = "polite", ...props }, ref) {
5
+ return _jsx(Text, { ...props, ref: ref, role: announcement === "none" ? undefined : announcement === "assertive" ? "alert" : "status", "aria-live": announcement === "none" ? "off" : announcement, "aria-atomic": announcement === "none" ? undefined : true, children: children });
6
+ });
7
+ //# sourceMappingURL=status-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-text.js","sourceRoot":"","sources":["../src/status-text.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,IAAI,EAAkB,MAAM,aAAa,CAAC;AAQnD,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAA+B,SAAS,UAAU,CACpF,EAAE,QAAQ,EAAE,YAAY,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG;IAEpD,OAAO,KAAC,IAAI,OAAK,KAAK,EAAE,GAAG,EAAE,GAAG,EAC9B,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,eAClF,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,iBAC5C,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,YACvD,QAAQ,GAAQ,CAAC;AACrB,CAAC,CAAC,CAAC","sourcesContent":["import { forwardRef } from \"react\";\nimport { Text, type TextProps } from \"./layout.js\";\n\n/** A short inline result; use Notice for a titled message/action and Toast for transient overlays. */\nexport type StatusTextProps = Omit<TextProps, \"children\" | \"role\" | \"aria-live\" | \"aria-atomic\"> & Readonly<{\n children: string;\n announcement?: \"none\" | \"polite\" | \"assertive\";\n}>;\n\nexport const StatusText = forwardRef<HTMLElement, StatusTextProps>(function StatusText(\n { children, announcement = \"polite\", ...props }, ref,\n) {\n return <Text {...props} ref={ref}\n role={announcement === \"none\" ? undefined : announcement === \"assertive\" ? \"alert\" : \"status\"}\n aria-live={announcement === \"none\" ? \"off\" : announcement}\n aria-atomic={announcement === \"none\" ? undefined : true}\n >{children}</Text>;\n});\n"]}