@hjmds/react 1.17.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/activity-chip.d.ts +33 -0
  2. package/dist/activity-chip.d.ts.map +1 -0
  3. package/dist/activity-chip.js +24 -0
  4. package/dist/activity-chip.js.map +1 -0
  5. package/dist/advanced-display.d.ts.map +1 -1
  6. package/dist/advanced-display.js +41 -9
  7. package/dist/advanced-display.js.map +1 -1
  8. package/dist/advanced-forms.d.ts.map +1 -1
  9. package/dist/advanced-forms.js +22 -9
  10. package/dist/advanced-forms.js.map +1 -1
  11. package/dist/agreement.d.ts.map +1 -1
  12. package/dist/agreement.js +17 -11
  13. package/dist/agreement.js.map +1 -1
  14. package/dist/auth-screen.d.ts.map +1 -1
  15. package/dist/auth-screen.js +15 -2
  16. package/dist/auth-screen.js.map +1 -1
  17. package/dist/avatar-blobatar-motion.d.ts.map +1 -1
  18. package/dist/avatar-blobatar-motion.js +10 -2
  19. package/dist/avatar-blobatar-motion.js.map +1 -1
  20. package/dist/bottom-navigation.d.ts.map +1 -1
  21. package/dist/bottom-navigation.js +2 -2
  22. package/dist/bottom-navigation.js.map +1 -1
  23. package/dist/calendar.d.ts.map +1 -1
  24. package/dist/calendar.js +10 -2
  25. package/dist/calendar.js.map +1 -1
  26. package/dist/carousel-motion.d.ts +1 -0
  27. package/dist/carousel-motion.d.ts.map +1 -1
  28. package/dist/carousel-motion.js +4 -3
  29. package/dist/carousel-motion.js.map +1 -1
  30. package/dist/carousel.d.ts.map +1 -1
  31. package/dist/carousel.js +22 -4
  32. package/dist/carousel.js.map +1 -1
  33. package/dist/collapsible.d.ts.map +1 -1
  34. package/dist/collapsible.js +28 -4
  35. package/dist/collapsible.js.map +1 -1
  36. package/dist/collection-rail.d.ts.map +1 -1
  37. package/dist/collection-rail.js +19 -1
  38. package/dist/collection-rail.js.map +1 -1
  39. package/dist/color-picker.d.ts.map +1 -1
  40. package/dist/color-picker.js +12 -4
  41. package/dist/color-picker.js.map +1 -1
  42. package/dist/command-palette.d.ts.map +1 -1
  43. package/dist/command-palette.js +14 -2
  44. package/dist/command-palette.js.map +1 -1
  45. package/dist/content-transition.d.ts +2 -1
  46. package/dist/content-transition.d.ts.map +1 -1
  47. package/dist/content-transition.js +2 -2
  48. package/dist/content-transition.js.map +1 -1
  49. package/dist/context-menu.d.ts.map +1 -1
  50. package/dist/context-menu.js +31 -10
  51. package/dist/context-menu.js.map +1 -1
  52. package/dist/date-entry.d.ts +1 -1
  53. package/dist/date-entry.d.ts.map +1 -1
  54. package/dist/date-entry.js +20 -7
  55. package/dist/date-entry.js.map +1 -1
  56. package/dist/date-picker.d.ts.map +1 -1
  57. package/dist/date-picker.js +22 -6
  58. package/dist/date-picker.js.map +1 -1
  59. package/dist/display.d.ts +11 -1
  60. package/dist/display.d.ts.map +1 -1
  61. package/dist/display.js +13 -4
  62. package/dist/display.js.map +1 -1
  63. package/dist/duration-field.d.ts +2 -2
  64. package/dist/duration-field.d.ts.map +1 -1
  65. package/dist/duration-field.js +11 -3
  66. package/dist/duration-field.js.map +1 -1
  67. package/dist/evidence.d.ts +1 -1
  68. package/dist/evidence.js +1 -1
  69. package/dist/evidence.js.map +1 -1
  70. package/dist/feedback.d.ts +1 -0
  71. package/dist/feedback.d.ts.map +1 -1
  72. package/dist/feedback.js +7 -2
  73. package/dist/feedback.js.map +1 -1
  74. package/dist/field-group.d.ts +4 -2
  75. package/dist/field-group.d.ts.map +1 -1
  76. package/dist/field-group.js +4 -3
  77. package/dist/field-group.js.map +1 -1
  78. package/dist/file-picker.d.ts.map +1 -1
  79. package/dist/file-picker.js +15 -5
  80. package/dist/file-picker.js.map +1 -1
  81. package/dist/forms.d.ts +2 -2
  82. package/dist/forms.js +1 -1
  83. package/dist/forms.js.map +1 -1
  84. package/dist/gravity-letters.d.ts.map +1 -1
  85. package/dist/gravity-letters.js +9 -1
  86. package/dist/gravity-letters.js.map +1 -1
  87. package/dist/grid-reveal.d.ts.map +1 -1
  88. package/dist/grid-reveal.js +9 -1
  89. package/dist/grid-reveal.js.map +1 -1
  90. package/dist/index.d.ts +1 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +1 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/inline-confirm.d.ts.map +1 -1
  95. package/dist/inline-confirm.js +25 -4
  96. package/dist/inline-confirm.js.map +1 -1
  97. package/dist/internal/document-resource.d.ts.map +1 -1
  98. package/dist/internal/document-resource.js +2 -2
  99. package/dist/internal/document-resource.js.map +1 -1
  100. package/dist/layout.d.ts +8 -2
  101. package/dist/layout.d.ts.map +1 -1
  102. package/dist/layout.js.map +1 -1
  103. package/dist/masonry.d.ts.map +1 -1
  104. package/dist/masonry.js +2 -1
  105. package/dist/masonry.js.map +1 -1
  106. package/dist/mentions.d.ts.map +1 -1
  107. package/dist/mentions.js +12 -4
  108. package/dist/mentions.js.map +1 -1
  109. package/dist/menu-morph.js +1 -1
  110. package/dist/menu-morph.js.map +1 -1
  111. package/dist/menubar.d.ts.map +1 -1
  112. package/dist/menubar.js +43 -19
  113. package/dist/menubar.js.map +1 -1
  114. package/dist/modal.d.ts.map +1 -1
  115. package/dist/modal.js +15 -4
  116. package/dist/modal.js.map +1 -1
  117. package/dist/navigation.d.ts.map +1 -1
  118. package/dist/navigation.js +8 -2
  119. package/dist/navigation.js.map +1 -1
  120. package/dist/number-field.d.ts.map +1 -1
  121. package/dist/number-field.js +3 -1
  122. package/dist/number-field.js.map +1 -1
  123. package/dist/overlay-stack.d.ts.map +1 -1
  124. package/dist/overlay-stack.js +23 -6
  125. package/dist/overlay-stack.js.map +1 -1
  126. package/dist/overlays.d.ts.map +1 -1
  127. package/dist/overlays.js +8 -0
  128. package/dist/overlays.js.map +1 -1
  129. package/dist/pagination.d.ts.map +1 -1
  130. package/dist/pagination.js +16 -4
  131. package/dist/pagination.js.map +1 -1
  132. package/dist/saved-items.d.ts.map +1 -1
  133. package/dist/saved-items.js +6 -1
  134. package/dist/saved-items.js.map +1 -1
  135. package/dist/screen-flows.d.ts +1 -2
  136. package/dist/screen-flows.d.ts.map +1 -1
  137. package/dist/screen-flows.js +120 -16
  138. package/dist/screen-flows.js.map +1 -1
  139. package/dist/screens.d.ts +1 -1
  140. package/dist/screens.d.ts.map +1 -1
  141. package/dist/screens.js +38 -10
  142. package/dist/screens.js.map +1 -1
  143. package/dist/select.d.ts.map +1 -1
  144. package/dist/select.js +6 -1
  145. package/dist/select.js.map +1 -1
  146. package/dist/side-panel.d.ts.map +1 -1
  147. package/dist/side-panel.js +11 -2
  148. package/dist/side-panel.js.map +1 -1
  149. package/dist/sidebar.d.ts.map +1 -1
  150. package/dist/sidebar.js +8 -1
  151. package/dist/sidebar.js.map +1 -1
  152. package/dist/skip-nav.d.ts.map +1 -1
  153. package/dist/skip-nav.js +16 -3
  154. package/dist/skip-nav.js.map +1 -1
  155. package/dist/sortable.d.ts.map +1 -1
  156. package/dist/sortable.js +2 -1
  157. package/dist/sortable.js.map +1 -1
  158. package/dist/splitter.d.ts.map +1 -1
  159. package/dist/splitter.js +23 -11
  160. package/dist/splitter.js.map +1 -1
  161. package/dist/statistic-motion.d.ts.map +1 -1
  162. package/dist/statistic-motion.js +30 -2
  163. package/dist/statistic-motion.js.map +1 -1
  164. package/dist/status-text.d.ts +11 -0
  165. package/dist/status-text.d.ts.map +1 -0
  166. package/dist/status-text.js +7 -0
  167. package/dist/status-text.js.map +1 -0
  168. package/dist/styles.css +53 -7
  169. package/dist/styles.layered.css +53 -7
  170. package/dist/supplemental-display.d.ts.map +1 -1
  171. package/dist/supplemental-display.js +19 -8
  172. package/dist/supplemental-display.js.map +1 -1
  173. package/dist/supplemental-navigation.d.ts.map +1 -1
  174. package/dist/supplemental-navigation.js +10 -1
  175. package/dist/supplemental-navigation.js.map +1 -1
  176. package/dist/tags-input.d.ts.map +1 -1
  177. package/dist/tags-input.js +18 -8
  178. package/dist/tags-input.js.map +1 -1
  179. package/dist/theme.d.ts.map +1 -1
  180. package/dist/theme.js +11 -0
  181. package/dist/theme.js.map +1 -1
  182. package/dist/toast.d.ts.map +1 -1
  183. package/dist/toast.js +41 -16
  184. package/dist/toast.js.map +1 -1
  185. package/dist/toggle-group.d.ts.map +1 -1
  186. package/dist/toggle-group.js +6 -1
  187. package/dist/toggle-group.js.map +1 -1
  188. package/dist/tour.d.ts.map +1 -1
  189. package/dist/tour.js +15 -2
  190. package/dist/tour.js.map +1 -1
  191. package/dist/transfer-list.d.ts +1 -1
  192. package/dist/transfer-list.d.ts.map +1 -1
  193. package/dist/transfer-list.js +52 -7
  194. package/dist/transfer-list.js.map +1 -1
  195. package/dist/tree.d.ts.map +1 -1
  196. package/dist/tree.js +41 -8
  197. package/dist/tree.js.map +1 -1
  198. package/dist/virtual-list.d.ts.map +1 -1
  199. package/dist/virtual-list.js +44 -7
  200. package/dist/virtual-list.js.map +1 -1
  201. package/package.json +13 -3
package/dist/calendar.js CHANGED
@@ -51,8 +51,16 @@ export function Calendar({ descriptor, composeAccessibleName, previousMonth, nex
51
51
  "--hjm-calendar-disabled-opacity": recipe.day.disabledOpacity,
52
52
  "--hjm-calendar-outside-opacity": recipe.day.outsideFocusedMonthOpacity }, children: [_jsxs("div", { className: "hjm-calendar__header", children: [previousMonth ? _jsx("button", { type: "button", "aria-label": previousMonth.label, disabled: !descriptor.onFocusedMonthChange, onClick: () => descriptor.onFocusedMonthChange?.(previousMonth.month, "previous"), children: _jsx("span", { "aria-hidden": "true", children: theme?.environment.direction === "rtl" ? "›" : "‹" }) }) : _jsx("span", {}), _jsx("strong", { id: titleId, "aria-live": "polite", "aria-atomic": "true", children: descriptor.monthLabel }), nextMonth ? _jsx("button", { type: "button", "aria-label": nextMonth.label, disabled: !descriptor.onFocusedMonthChange, onClick: () => descriptor.onFocusedMonthChange?.(nextMonth.month, "next"), children: _jsx("span", { "aria-hidden": "true", children: theme?.environment.direction === "rtl" ? "‹" : "›" }) }) : _jsx("span", {})] }), _jsx("div", { className: "hjm-calendar__viewport", children: _jsxs("div", { role: "grid", "aria-labelledby": titleId, className: "hjm-calendar__grid", children: [_jsx("div", { role: "row", className: "hjm-calendar__week", children: descriptor.grid.weekdayLabels.map((label, index) => _jsx("span", { role: "columnheader", className: "hjm-calendar__weekday", children: label }, index)) }), Array.from({ length: cells.length / 7 }, (_, row) => _jsx("div", { role: "row", className: "hjm-calendar__week", children: cells.slice(row * 7, row * 7 + 7).map((cell, column) => "filler" in cell
53
53
  ? _jsx("span", { role: "gridcell", "aria-hidden": "true" }, `filler-${column}`)
54
- : _jsxs("button", { type: "button", role: "gridcell", className: "hjm-calendar__day", "data-date": cell.date, "data-focus-date": cell.date === active || undefined, "data-today": cell.isToday || undefined, "data-selected": cell.isSelected || undefined, "data-disabled": !cell.selectable || undefined, "data-outside": cell.outsideFocusedMonth || undefined, "aria-label": cell.accessibleName, "aria-selected": cell.isSelected, "aria-disabled": !cell.selectable || undefined, "aria-current": cell.isToday ? "date" : undefined, tabIndex: cell.date === active ? 0 : -1, onFocus: () => setFocused(cell.date), onClick: () => { if (cell.selectable)
55
- setSelected(cell.date); }, onKeyDown: (event) => {
54
+ : _jsxs("button", { type: "button", role: "gridcell", className: "hjm-calendar__day", "data-date": cell.date, "data-focus-date": cell.date === active || undefined, "data-today": cell.isToday || undefined, "data-selected": cell.isSelected || undefined, "data-disabled": !cell.selectable || undefined, "data-outside": cell.outsideFocusedMonth || undefined, "aria-label": cell.accessibleName, "aria-selected": cell.isSelected, "aria-disabled": !cell.selectable || undefined, "aria-current": cell.isToday ? "date" : undefined, tabIndex: cell.date === active ? 0 : -1, onFocus: () => setFocused(cell.date), onClick: () => {
55
+ if (!cell.selectable)
56
+ return;
57
+ // Range completion and DatePicker confirmation still need activation
58
+ // when the date already equals Calendar's single selected value.
59
+ if (cell.date === selected)
60
+ descriptor.onSelectionChange?.(cell.date);
61
+ else
62
+ setSelected(cell.date);
63
+ }, onKeyDown: (event) => {
56
64
  if (event.altKey || event.ctrlKey || event.metaKey)
57
65
  return;
58
66
  const intent = getCalendarNavigationIntent(event.key, theme?.environment.direction ?? "ltr");
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../src/calendar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useImperativeHandle, useRef, useState, type CSSProperties, type ReactNode, type Ref } from \"react\";\nimport {\n calendarRecipe as recipe, assertIsoCalendarMonth, validateCalendarDescriptor, resolveCalendarGridDescriptor,\n getCalendarNavigationIntent, getCalendarNavigationTarget,\n type CalendarDescriptor, type CalendarSize, type CalendarNavigationIntent,\n type ComposeCalendarAccessibleName, type ResolvedCalendarDateCell,\n} from \"@hjmds/design-contracts/components/calendar\";\nimport { classNames, useControllableState } from \"./internal.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CalendarMonthAction = Readonly<{ month: string; label: string }>;\nexport type CalendarHandle = Readonly<{ focusDate(date: string): void }>;\nexport type CalendarOverflow = Readonly<{ date: string; intent: CalendarNavigationIntent; overflow: \"before\" | \"after\" }>;\nexport type CalendarProps<Content = unknown> = Readonly<{\n descriptor: CalendarDescriptor<Content>;\n composeAccessibleName: ComposeCalendarAccessibleName<Content>;\n previousMonth?: CalendarMonthAction;\n nextMonth?: CalendarMonthAction;\n /** The product owns adjacent month/date arithmetic. Call focusDate after requesting its page. */\n onNavigateBeyondGrid?: (detail: CalendarOverflow, focusDate: CalendarHandle[\"focusDate\"]) => void;\n renderCellContent?: (cell: ResolvedCalendarDateCell<Content>) => ReactNode;\n size?: CalendarSize;\n autoFocus?: boolean;\n className?: string;\n ref?: Ref<CalendarHandle>;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Inline single-date grid shared with DatePicker; month data remains product-owned. */\nexport function Calendar<Content>({ descriptor, composeAccessibleName, previousMonth, nextMonth,\n onNavigateBeyondGrid, renderCellContent, size = \"medium\", autoFocus = false, className, layoutStyle, ref }: CalendarProps<Content>) {\n validateCalendarDescriptor(descriptor);\n for (const action of [previousMonth, nextMonth]) {\n if (!action) continue;\n assertIsoCalendarMonth(action.month, \"month action\");\n if (!action.label.trim()) throw new TypeError(\"Calendar month action label must not be empty\");\n }\n const theme = useOptionalHjmTheme();\n const [selected, setSelected] = useControllableState<string | null>({\n ...(descriptor.selectedDate === undefined ? {} : { value: descriptor.selectedDate }),\n defaultValue: descriptor.defaultSelectedDate ?? null,\n ...(descriptor.onSelectionChange ? { onChange: descriptor.onSelectionChange } : {}),\n });\n const cells = resolveCalendarGridDescriptor(descriptor.grid, selected, { composeAccessibleName });\n const dates = cells.filter((cell): cell is ResolvedCalendarDateCell<Content> => !(\"filler\" in cell));\n const initial = dates.find((cell) => cell.isSelected) ?? dates.find((cell) => cell.isToday) ?? dates[0];\n const [focused, setFocused] = useState(initial?.date);\n const active = dates.some((cell) => cell.date === focused) ? focused : initial?.date;\n const root = useRef<HTMLDivElement>(null);\n const pending = useRef<string | undefined>(undefined);\n const [request, setRequest] = useState(0);\n const focusDate = (date: string) => { pending.current = date; setFocused(date); setRequest((value) => value + 1); };\n useImperativeHandle(ref, () => ({ focusDate }), []);\n const gridKey = dates.map((cell) => cell.date).join(\",\");\n const mountedFocus = useRef(false);\n useEffect(() => {\n if (autoFocus && !mountedFocus.current && active) pending.current = active;\n mountedFocus.current = true;\n const date = pending.current;\n if (!date) return;\n // A requested adjacent date may arrive in a later render; retain it until that page exists.\n const button = Array.from(root.current?.querySelectorAll<HTMLButtonElement>(\"[data-date]\") ?? []).find((node) => node.dataset.date === date);\n if (button) { button.focus(); setFocused(date); pending.current = undefined; }\n }, [gridKey, request, autoFocus, active]);\n const titleId = `hjm-calendar-${useId()}`;\n return <div ref={root} className={classNames(\"hjm-calendar\", className)} data-size={size}\n style={{ ...layoutStyle, \"--hjm-calendar-cell-size\": `${recipe.sizes[size].cellDiameter}px`,\n \"--hjm-calendar-disabled-opacity\": recipe.day.disabledOpacity,\n \"--hjm-calendar-outside-opacity\": recipe.day.outsideFocusedMonthOpacity } as CSSProperties}>\n <div className=\"hjm-calendar__header\">\n {previousMonth ? <button type=\"button\" aria-label={previousMonth.label} disabled={!descriptor.onFocusedMonthChange}\n onClick={() => descriptor.onFocusedMonthChange?.(previousMonth.month, \"previous\")}><span aria-hidden=\"true\">{theme?.environment.direction === \"rtl\" ? \"›\" : \"‹\"}</span></button> : <span />}\n <strong id={titleId} aria-live=\"polite\" aria-atomic=\"true\">{descriptor.monthLabel}</strong>\n {nextMonth ? <button type=\"button\" aria-label={nextMonth.label} disabled={!descriptor.onFocusedMonthChange}\n onClick={() => descriptor.onFocusedMonthChange?.(nextMonth.month, \"next\")}><span aria-hidden=\"true\">{theme?.environment.direction === \"rtl\" ? \"‹\" : \"›\"}</span></button> : <span />}\n </div>\n <div className=\"hjm-calendar__viewport\">\n <div role=\"grid\" aria-labelledby={titleId} className=\"hjm-calendar__grid\">\n <div role=\"row\" className=\"hjm-calendar__week\">\n {descriptor.grid.weekdayLabels.map((label, index) => <span role=\"columnheader\" className=\"hjm-calendar__weekday\" key={index}>{label}</span>)}\n </div>\n {Array.from({ length: cells.length / 7 }, (_, row) => <div role=\"row\" className=\"hjm-calendar__week\" key={row}>\n {cells.slice(row * 7, row * 7 + 7).map((cell, column) => \"filler\" in cell\n ? <span role=\"gridcell\" aria-hidden=\"true\" key={`filler-${column}`} />\n : <button type=\"button\" role=\"gridcell\" key={cell.date} className=\"hjm-calendar__day\"\n data-date={cell.date} data-focus-date={cell.date === active || undefined}\n data-today={cell.isToday || undefined} data-selected={cell.isSelected || undefined}\n data-disabled={!cell.selectable || undefined} data-outside={cell.outsideFocusedMonth || undefined}\n aria-label={cell.accessibleName} aria-selected={cell.isSelected} aria-disabled={!cell.selectable || undefined}\n aria-current={cell.isToday ? \"date\" : undefined} tabIndex={cell.date === active ? 0 : -1}\n onFocus={() => setFocused(cell.date)} onClick={() => { if (cell.selectable) setSelected(cell.date); }}\n onKeyDown={(event) => {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n const intent = getCalendarNavigationIntent(event.key as Parameters<typeof getCalendarNavigationIntent>[0], theme?.environment.direction ?? \"ltr\");\n if (!intent) return;\n event.preventDefault();\n const target = getCalendarNavigationTarget(descriptor.grid, cell.date, intent);\n if (target.date) focusDate(target.date);\n else if (target.overflow) onNavigateBeyondGrid?.({ date: cell.date, intent, overflow: target.overflow }, focusDate);\n }}>\n <span aria-hidden=\"true\" className=\"hjm-calendar__day-label\">{Number(cell.date.slice(-2))}</span>\n {renderCellContent ? <span aria-hidden=\"true\" className=\"hjm-calendar__content\">{renderCellContent(cell)}</span> : null}\n </button>)}\n </div>)}\n </div>\n </div>\n </div>;\n}\n"]}
1
+ {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../src/calendar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useImperativeHandle, useRef, useState, type CSSProperties, type ReactNode, type Ref } from \"react\";\nimport {\n calendarRecipe as recipe, assertIsoCalendarMonth, validateCalendarDescriptor, resolveCalendarGridDescriptor,\n getCalendarNavigationIntent, getCalendarNavigationTarget,\n type CalendarDescriptor, type CalendarSize, type CalendarNavigationIntent,\n type ComposeCalendarAccessibleName, type ResolvedCalendarDateCell,\n} from \"@hjmds/design-contracts/components/calendar\";\nimport { classNames, useControllableState } from \"./internal.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CalendarMonthAction = Readonly<{ month: string; label: string }>;\nexport type CalendarHandle = Readonly<{ focusDate(date: string): void }>;\nexport type CalendarOverflow = Readonly<{ date: string; intent: CalendarNavigationIntent; overflow: \"before\" | \"after\" }>;\nexport type CalendarProps<Content = unknown> = Readonly<{\n descriptor: CalendarDescriptor<Content>;\n composeAccessibleName: ComposeCalendarAccessibleName<Content>;\n previousMonth?: CalendarMonthAction;\n nextMonth?: CalendarMonthAction;\n /** The product owns adjacent month/date arithmetic. Call focusDate after requesting its page. */\n onNavigateBeyondGrid?: (detail: CalendarOverflow, focusDate: CalendarHandle[\"focusDate\"]) => void;\n renderCellContent?: (cell: ResolvedCalendarDateCell<Content>) => ReactNode;\n size?: CalendarSize;\n autoFocus?: boolean;\n className?: string;\n ref?: Ref<CalendarHandle>;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Inline single-date grid shared with DatePicker; month data remains product-owned. */\nexport function Calendar<Content>({ descriptor, composeAccessibleName, previousMonth, nextMonth,\n onNavigateBeyondGrid, renderCellContent, size = \"medium\", autoFocus = false, className, layoutStyle, ref }: CalendarProps<Content>) {\n validateCalendarDescriptor(descriptor);\n for (const action of [previousMonth, nextMonth]) {\n if (!action) continue;\n assertIsoCalendarMonth(action.month, \"month action\");\n if (!action.label.trim()) throw new TypeError(\"Calendar month action label must not be empty\");\n }\n const theme = useOptionalHjmTheme();\n const [selected, setSelected] = useControllableState<string | null>({\n ...(descriptor.selectedDate === undefined ? {} : { value: descriptor.selectedDate }),\n defaultValue: descriptor.defaultSelectedDate ?? null,\n ...(descriptor.onSelectionChange ? { onChange: descriptor.onSelectionChange } : {}),\n });\n const cells = resolveCalendarGridDescriptor(descriptor.grid, selected, { composeAccessibleName });\n const dates = cells.filter((cell): cell is ResolvedCalendarDateCell<Content> => !(\"filler\" in cell));\n const initial = dates.find((cell) => cell.isSelected) ?? dates.find((cell) => cell.isToday) ?? dates[0];\n const [focused, setFocused] = useState(initial?.date);\n const active = dates.some((cell) => cell.date === focused) ? focused : initial?.date;\n const root = useRef<HTMLDivElement>(null);\n const pending = useRef<string | undefined>(undefined);\n const [request, setRequest] = useState(0);\n const focusDate = (date: string) => { pending.current = date; setFocused(date); setRequest((value) => value + 1); };\n useImperativeHandle(ref, () => ({ focusDate }), []);\n const gridKey = dates.map((cell) => cell.date).join(\",\");\n const mountedFocus = useRef(false);\n useEffect(() => {\n if (autoFocus && !mountedFocus.current && active) pending.current = active;\n mountedFocus.current = true;\n const date = pending.current;\n if (!date) return;\n // A requested adjacent date may arrive in a later render; retain it until that page exists.\n const button = Array.from(root.current?.querySelectorAll<HTMLButtonElement>(\"[data-date]\") ?? []).find((node) => node.dataset.date === date);\n if (button) { button.focus(); setFocused(date); pending.current = undefined; }\n }, [gridKey, request, autoFocus, active]);\n const titleId = `hjm-calendar-${useId()}`;\n return <div ref={root} className={classNames(\"hjm-calendar\", className)} data-size={size}\n style={{ ...layoutStyle, \"--hjm-calendar-cell-size\": `${recipe.sizes[size].cellDiameter}px`,\n \"--hjm-calendar-disabled-opacity\": recipe.day.disabledOpacity,\n \"--hjm-calendar-outside-opacity\": recipe.day.outsideFocusedMonthOpacity } as CSSProperties}>\n <div className=\"hjm-calendar__header\">\n {previousMonth ? <button type=\"button\" aria-label={previousMonth.label} disabled={!descriptor.onFocusedMonthChange}\n onClick={() => descriptor.onFocusedMonthChange?.(previousMonth.month, \"previous\")}><span aria-hidden=\"true\">{theme?.environment.direction === \"rtl\" ? \"›\" : \"‹\"}</span></button> : <span />}\n <strong id={titleId} aria-live=\"polite\" aria-atomic=\"true\">{descriptor.monthLabel}</strong>\n {nextMonth ? <button type=\"button\" aria-label={nextMonth.label} disabled={!descriptor.onFocusedMonthChange}\n onClick={() => descriptor.onFocusedMonthChange?.(nextMonth.month, \"next\")}><span aria-hidden=\"true\">{theme?.environment.direction === \"rtl\" ? \"‹\" : \"›\"}</span></button> : <span />}\n </div>\n <div className=\"hjm-calendar__viewport\">\n <div role=\"grid\" aria-labelledby={titleId} className=\"hjm-calendar__grid\">\n <div role=\"row\" className=\"hjm-calendar__week\">\n {descriptor.grid.weekdayLabels.map((label, index) => <span role=\"columnheader\" className=\"hjm-calendar__weekday\" key={index}>{label}</span>)}\n </div>\n {Array.from({ length: cells.length / 7 }, (_, row) => <div role=\"row\" className=\"hjm-calendar__week\" key={row}>\n {cells.slice(row * 7, row * 7 + 7).map((cell, column) => \"filler\" in cell\n ? <span role=\"gridcell\" aria-hidden=\"true\" key={`filler-${column}`} />\n : <button type=\"button\" role=\"gridcell\" key={cell.date} className=\"hjm-calendar__day\"\n data-date={cell.date} data-focus-date={cell.date === active || undefined}\n data-today={cell.isToday || undefined} data-selected={cell.isSelected || undefined}\n data-disabled={!cell.selectable || undefined} data-outside={cell.outsideFocusedMonth || undefined}\n aria-label={cell.accessibleName} aria-selected={cell.isSelected} aria-disabled={!cell.selectable || undefined}\n aria-current={cell.isToday ? \"date\" : undefined} tabIndex={cell.date === active ? 0 : -1}\n onFocus={() => setFocused(cell.date)} onClick={() => {\n if (!cell.selectable) return;\n // Range completion and DatePicker confirmation still need activation\n // when the date already equals Calendar's single selected value.\n if (cell.date === selected) descriptor.onSelectionChange?.(cell.date);\n else setSelected(cell.date);\n }}\n onKeyDown={(event) => {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n const intent = getCalendarNavigationIntent(event.key as Parameters<typeof getCalendarNavigationIntent>[0], theme?.environment.direction ?? \"ltr\");\n if (!intent) return;\n event.preventDefault();\n const target = getCalendarNavigationTarget(descriptor.grid, cell.date, intent);\n if (target.date) focusDate(target.date);\n else if (target.overflow) onNavigateBeyondGrid?.({ date: cell.date, intent, overflow: target.overflow }, focusDate);\n }}>\n <span aria-hidden=\"true\" className=\"hjm-calendar__day-label\">{Number(cell.date.slice(-2))}</span>\n {renderCellContent ? <span aria-hidden=\"true\" className=\"hjm-calendar__content\">{renderCellContent(cell)}</span> : null}\n </button>)}\n </div>)}\n </div>\n </div>\n </div>;\n}\n"]}
@@ -3,6 +3,7 @@ import type { SortableItem } from "@hjmds/design-contracts/components/interactio
3
3
  import { type ComposeCarouselAccessibleName } from "@hjmds/design-contracts/components/carousel";
4
4
  import type { HjmCompositionStyleProp } from "./composition-style.js";
5
5
  export type CarouselMotionProps = {
6
+ navigation?: "buttons" | "counter";
6
7
  slides: readonly SortableItem[];
7
8
  currentKey: string;
8
9
  onCurrentKeyChange(key: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"carousel-motion.d.ts","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yDAAyD,CAAC;AAC5F,OAAO,EAA0D,KAAK,6BAA6B,EAAE,MAAM,6CAA6C,CAAC;AAGzJ,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3F,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;IACtD,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IACpG,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC;AACF,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAwCxD"}
1
+ {"version":3,"file":"carousel-motion.d.ts","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yDAAyD,CAAC;AAC5F,OAAO,EAA0D,KAAK,6BAA6B,EAAE,MAAM,6CAA6C,CAAC;AAIzJ,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACnC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3F,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;IACtD,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IACpG,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC;AACF,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BA8CxD"}
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import useEmblaCarousel from "embla-carousel-react";
4
4
  import { resolveCarouselDescriptor, getCarouselNavigationTarget } from "@hjmds/design-contracts/components/carousel";
5
- import { Button } from "./actions.js";
5
+ import { Button, IconButton } from "./actions.js";
6
+ import { Text } from "./layout.js";
6
7
  import { useHjmTheme } from "./provider.js";
7
8
  export function CarouselMotion(props) {
8
9
  const descriptor = { slides: props.slides, currentKey: props.currentKey };
@@ -39,6 +40,6 @@ export function CarouselMotion(props) {
39
40
  return () => { api.off("select", select); };
40
41
  }, [api]);
41
42
  useEffect(() => { syncing.current = true; api?.scrollTo(index, environment.reducedMotion); syncing.current = false; }, [api, index, environment.reducedMotion]);
42
- return _jsxs("section", { "aria-label": props.label, "aria-roledescription": "carousel", style: props.layoutStyle, children: [_jsx("div", { ref: viewport, style: { overflow: "hidden" }, children: _jsx("div", { style: { display: "flex", touchAction: "pan-y pinch-zoom" }, children: props.slides.map((slide, i) => _jsx("div", { role: "group", "aria-roledescription": "slide", "aria-label": resolved[i].accessibleName, "aria-hidden": resolved[i].inert, inert: resolved[i].inert, style: { flex: "0 0 100%", minWidth: 0 }, children: props.renderSlide(slide) }, slide.id)) }) }), _jsx(Button, { tone: "ghost", disabled: index === 0, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "previous")), children: props.previousLabel }), _jsx(Button, { tone: "ghost", disabled: index === props.slides.length - 1, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "next")), children: props.nextLabel })] });
43
+ return _jsxs("section", { "aria-label": props.label, "aria-roledescription": "carousel", style: props.layoutStyle, children: [_jsx("div", { ref: viewport, style: { overflow: "hidden" }, children: _jsx("div", { style: { display: "flex", touchAction: "pan-y pinch-zoom" }, children: props.slides.map((slide, i) => _jsx("div", { role: "group", "aria-roledescription": "slide", "aria-label": resolved[i].accessibleName, "aria-hidden": resolved[i].inert, inert: resolved[i].inert, style: { flex: "0 0 100%", minWidth: 0 }, children: props.renderSlide(slide) }, slide.id)) }) }), props.navigation === "counter" ? _jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [_jsx(IconButton, { label: props.previousLabel, tone: "ghost", disabled: index === 0, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "previous")), children: _jsx("span", { "aria-hidden": "true", children: environment.direction === "rtl" ? "›" : "‹" }) }), _jsxs(Text, { variant: "caption", "aria-live": "polite", children: [index + 1, "/", props.slides.length] }), _jsx(IconButton, { label: props.nextLabel, tone: "ghost", disabled: index === props.slides.length - 1, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "next")), children: _jsx("span", { "aria-hidden": "true", children: environment.direction === "rtl" ? "‹" : "›" }) })] }) : _jsxs(_Fragment, { children: [_jsx(Button, { tone: "ghost", disabled: index === 0, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "previous")), children: props.previousLabel }), _jsx(Button, { tone: "ghost", disabled: index === props.slides.length - 1, onClick: () => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, "next")), children: props.nextLabel })] })] });
43
44
  }
44
45
  //# sourceMappingURL=carousel-motion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"carousel-motion.js","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport useEmblaCarousel from \"embla-carousel-react\";\nimport type { SortableItem } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { resolveCarouselDescriptor, getCarouselNavigationTarget, type ComposeCarouselAccessibleName } from \"@hjmds/design-contracts/components/carousel\";\nimport { Button } from \"./actions.js\";\nimport { useHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CarouselMotionProps = {\n slides: readonly SortableItem[]; currentKey: string; onCurrentKeyChange(key: string): void;\n /** Optional localized position/name composer shared with the base Carousel. */\n composeAccessibleName?: ComposeCarouselAccessibleName;\n renderSlide(item: SortableItem): ReactNode; label: string; previousLabel: string; nextLabel: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n};\nexport function CarouselMotion(props: CarouselMotionProps) {\n const descriptor = { slides: props.slides, currentKey: props.currentKey };\n // Share finite keyed selection and names with Carousel; the optional peer owns\n // only swipe/motion. No autoplay is implied by this controlled presentation.\n const resolved = resolveCarouselDescriptor(descriptor, {\n composeAccessibleName: props.composeAccessibleName ?? ((info) => info.label),\n });\n const index = resolved.findIndex(slide => slide.current);\n if (![props.label, props.previousLabel, props.nextLabel].every(label => label.trim())) {\n throw new TypeError(\"CarouselMotion labels must not be empty\");\n }\n const { environment } = useHjmTheme();\n // Embla deep-compares options and reInits on change; a live startIndex restarted the engine at the target on\n // every selection, so navigation snapped instead of animating (2026-09-30 review). Only the first index seeds it.\n const [startIndex] = useState(index);\n const [viewport, api] = useEmblaCarousel({ loop: false, direction: environment.direction, startIndex,\n // HJM owns selection; upstream focus scrolling must not independently change slides.\n // Embla's default 25 is a physics duration factor, not an HJM millisecond token.\n watchFocus: false, duration: environment.reducedMotion ? 0 : 25 });\n const latest = useRef(props); latest.current = props;\n const syncing = useRef(false);\n useEffect(() => {\n if (!api) return;\n const select = () => {\n const current = latest.current;\n const item = current.slides[api.selectedScrollSnap()];\n if (!syncing.current && item && item.id !== current.currentKey) current.onCurrentKeyChange(item.id);\n };\n api.on(\"select\", select);\n return () => { api.off(\"select\", select); };\n }, [api]);\n useEffect(() => { syncing.current = true; api?.scrollTo(index, environment.reducedMotion); syncing.current = false; }, [api, index, environment.reducedMotion]);\n return <section aria-label={props.label} aria-roledescription=\"carousel\" style={props.layoutStyle}>\n <div ref={viewport} style={{ overflow: \"hidden\" }}><div style={{ display: \"flex\", touchAction: \"pan-y pinch-zoom\" }}>\n {props.slides.map((slide, i) => <div key={slide.id} role=\"group\" aria-roledescription=\"slide\" aria-label={resolved[i]!.accessibleName} aria-hidden={resolved[i]!.inert} inert={resolved[i]!.inert}\n style={{ flex: \"0 0 100%\", minWidth: 0 }}>{props.renderSlide(slide)}</div>)}\n </div></div>\n <Button tone=\"ghost\" disabled={index === 0} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"previous\"))}>{props.previousLabel}</Button>\n <Button tone=\"ghost\" disabled={index === props.slides.length - 1} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"next\"))}>{props.nextLabel}</Button>\n </section>;\n}\n"]}
1
+ {"version":3,"file":"carousel-motion.js","sourceRoot":"","sources":["../src/carousel-motion.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport useEmblaCarousel from \"embla-carousel-react\";\nimport type { SortableItem } from \"@hjmds/design-contracts/components/interaction-adapters\";\nimport { resolveCarouselDescriptor, getCarouselNavigationTarget, type ComposeCarouselAccessibleName } from \"@hjmds/design-contracts/components/carousel\";\nimport { Button, IconButton } from \"./actions.js\";\nimport { Text } from \"./layout.js\";\nimport { useHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CarouselMotionProps = {\n navigation?: \"buttons\" | \"counter\";\n slides: readonly SortableItem[]; currentKey: string; onCurrentKeyChange(key: string): void;\n /** Optional localized position/name composer shared with the base Carousel. */\n composeAccessibleName?: ComposeCarouselAccessibleName;\n renderSlide(item: SortableItem): ReactNode; label: string; previousLabel: string; nextLabel: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n};\nexport function CarouselMotion(props: CarouselMotionProps) {\n const descriptor = { slides: props.slides, currentKey: props.currentKey };\n // Share finite keyed selection and names with Carousel; the optional peer owns\n // only swipe/motion. No autoplay is implied by this controlled presentation.\n const resolved = resolveCarouselDescriptor(descriptor, {\n composeAccessibleName: props.composeAccessibleName ?? ((info) => info.label),\n });\n const index = resolved.findIndex(slide => slide.current);\n if (![props.label, props.previousLabel, props.nextLabel].every(label => label.trim())) {\n throw new TypeError(\"CarouselMotion labels must not be empty\");\n }\n const { environment } = useHjmTheme();\n // Embla deep-compares options and reInits on change; a live startIndex restarted the engine at the target on\n // every selection, so navigation snapped instead of animating (2026-09-30 review). Only the first index seeds it.\n const [startIndex] = useState(index);\n const [viewport, api] = useEmblaCarousel({ loop: false, direction: environment.direction, startIndex,\n // HJM owns selection; upstream focus scrolling must not independently change slides.\n // Embla's default 25 is a physics duration factor, not an HJM millisecond token.\n watchFocus: false, duration: environment.reducedMotion ? 0 : 25 });\n const latest = useRef(props); latest.current = props;\n const syncing = useRef(false);\n useEffect(() => {\n if (!api) return;\n const select = () => {\n const current = latest.current;\n const item = current.slides[api.selectedScrollSnap()];\n if (!syncing.current && item && item.id !== current.currentKey) current.onCurrentKeyChange(item.id);\n };\n api.on(\"select\", select);\n return () => { api.off(\"select\", select); };\n }, [api]);\n useEffect(() => { syncing.current = true; api?.scrollTo(index, environment.reducedMotion); syncing.current = false; }, [api, index, environment.reducedMotion]);\n return <section aria-label={props.label} aria-roledescription=\"carousel\" style={props.layoutStyle}>\n <div ref={viewport} style={{ overflow: \"hidden\" }}><div style={{ display: \"flex\", touchAction: \"pan-y pinch-zoom\" }}>\n {props.slides.map((slide, i) => <div key={slide.id} role=\"group\" aria-roledescription=\"slide\" aria-label={resolved[i]!.accessibleName} aria-hidden={resolved[i]!.inert} inert={resolved[i]!.inert}\n style={{ flex: \"0 0 100%\", minWidth: 0 }}>{props.renderSlide(slide)}</div>)}\n </div></div>\n {props.navigation === \"counter\" ? <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\" }}>\n <IconButton label={props.previousLabel} tone=\"ghost\" disabled={index === 0} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"previous\"))}><span aria-hidden=\"true\">{environment.direction === \"rtl\" ? \"›\" : \"‹\"}</span></IconButton>\n <Text variant=\"caption\" aria-live=\"polite\">{index + 1}/{props.slides.length}</Text>\n <IconButton label={props.nextLabel} tone=\"ghost\" disabled={index === props.slides.length - 1} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"next\"))}><span aria-hidden=\"true\">{environment.direction === \"rtl\" ? \"‹\" : \"›\"}</span></IconButton>\n </div> : <>\n <Button tone=\"ghost\" disabled={index === 0} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"previous\"))}>{props.previousLabel}</Button>\n <Button tone=\"ghost\" disabled={index === props.slides.length - 1} onClick={() => props.onCurrentKeyChange(getCarouselNavigationTarget(descriptor, \"next\"))}>{props.nextLabel}</Button>\n </>}\n </section>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../src/carousel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACrG,OAAO,EAEL,KAAK,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,KAAK,sBAAsB,EACjF,KAAK,6BAA6B,EACnC,MAAM,6CAA6C,CAAC;AAKrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC7H,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG,iBAAiB,GAAG,QAAQ,CAAC;IACvH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC3C,WAAW,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IAC3D,qBAAqB,EAAE,6BAA6B,CAAC;IACrD,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,0GAgFnB,CAAC"}
1
+ {"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../src/carousel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4D,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtH,OAAO,EAEL,KAAK,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,KAAK,sBAAsB,EACjF,KAAK,6BAA6B,EACnC,MAAM,6CAA6C,CAAC;AAKrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC7H,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG,iBAAiB,GAAG,QAAQ,CAAC;IACvH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC3C,WAAW,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IAC3D,qBAAqB,EAAE,6BAA6B,CAAC;IACrD,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,0GAkGnB,CAAC"}
package/dist/carousel.js CHANGED
@@ -1,18 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState, forwardRef } from "react";
2
+ import { useEffect, useLayoutEffect, useRef, useState, forwardRef } from "react";
3
3
  import { resolveCarouselDescriptor, getCarouselNavigationTarget, isCarouselAutoplayActive, } from "@hjmds/design-contracts/components/carousel";
4
4
  import { Button } from "./actions.js";
5
5
  import { isLargeTextScale } from "@hjmds/design-contracts/components/design-system-provider";
6
- import { classNames, useControllableState } from "./internal.js";
6
+ import { assignRef, classNames, useControllableState } from "./internal.js";
7
7
  import { useOptionalHjmTheme } from "./provider.js";
8
8
  /** Finite keyed cards. Hidden slides stay mounted but cannot receive focus. */
9
9
  export const Carousel = forwardRef(function Carousel({ label, slides, renderSlide, composeAccessibleName, labels, autoplay, currentKey, defaultCurrentKey, onCurrentKeyChange, className, onFocusCapture, onMouseEnter, onMouseLeave, onKeyDown, layoutStyle, ...props }, ref) {
10
10
  const theme = useOptionalHjmTheme();
11
- const [current, setCurrent] = useControllableState({
11
+ const [storedCurrent, setCurrent] = useControllableState({
12
12
  ...(currentKey === undefined ? {} : { value: currentKey }),
13
13
  defaultValue: defaultCurrentKey ?? slides[0]?.id ?? "",
14
14
  ...(onCurrentKeyChange === undefined ? {} : { onChange: onCurrentKeyChange }),
15
15
  });
16
+ const previousIndex = useRef(null);
17
+ const host = useRef(null);
18
+ const lastFocused = useRef(null);
19
+ // A host can delete the current uncontrolled slide after mount. Keep its place
20
+ // in the collection (or the new last slide); explicit invalid keys still fail validation.
21
+ const recovering = currentKey === undefined && previousIndex.current !== null && !slides.some(slide => slide.id === storedCurrent);
22
+ const current = recovering ? slides[Math.min(previousIndex.current, slides.length - 1)]?.id ?? storedCurrent : storedCurrent;
23
+ useLayoutEffect(() => {
24
+ previousIndex.current = slides.findIndex(slide => slide.id === current);
25
+ if (recovering) {
26
+ setCurrent(current);
27
+ // A removed dot/card can strand focus on body; retained controls keep their focus.
28
+ if (lastFocused.current && !lastFocused.current.isConnected && document.activeElement === document.body) {
29
+ host.current?.querySelector('.hjm-carousel__dot[data-current="true"]')?.focus();
30
+ }
31
+ }
32
+ }, [current, recovering, slides, setCurrent]);
16
33
  const descriptor = { slides, currentKey: current, ...(autoplay ? { autoplay } : {}) };
17
34
  const resolved = resolveCarouselDescriptor(descriptor, { composeAccessibleName });
18
35
  if (!label.trim() || Object.values(labels).some((value) => !value.trim()))
@@ -42,7 +59,8 @@ export const Carousel = forwardRef(function Carousel({ label, slides, renderSlid
42
59
  setCurrent(key); }
43
60
  function move(intent) { select(getCarouselNavigationTarget(descriptor, intent)); }
44
61
  const rotationRequested = rotating && !last && !theme?.environment.reducedMotion;
45
- return _jsxs("div", { ...props, style: { ...props.style, ...layoutStyle }, ref: ref, role: "region", "aria-label": label, "aria-roledescription": "carousel", className: classNames("hjm-carousel", className), "data-large-text": isLargeTextScale(theme?.environment.textScale ?? 1), onFocusCapture: (event) => {
62
+ return _jsxs("div", { ...props, style: { ...props.style, ...layoutStyle }, ref: node => { host.current = node; assignRef(ref, node); }, role: "region", "aria-label": label, "aria-roledescription": "carousel", className: classNames("hjm-carousel", className), "data-large-text": isLargeTextScale(theme?.environment.textScale ?? 1), onFocusCapture: (event) => {
63
+ lastFocused.current = event.target instanceof HTMLElement ? event.target : null;
46
64
  // APG: leaving focus must not silently restart rotation. Explicit resume is required.
47
65
  if (!event.currentTarget.contains(event.relatedTarget))
48
66
  setRotating(false);
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.js","sourceRoot":"","sources":["../src/carousel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport {\n resolveCarouselDescriptor, getCarouselNavigationTarget, isCarouselAutoplayActive,\n type CarouselSlideDescriptor, type CarouselSelection, type CarouselAutoplayConfig,\n type ComposeCarouselAccessibleName,\n} from \"@hjmds/design-contracts/components/carousel\";\nimport { Button } from \"./actions.js\";\nimport { isLargeTextScale } from \"@hjmds/design-contracts/components/design-system-provider\";\nimport { classNames, useControllableState } from \"./internal.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CarouselLabels = Readonly<{ previous: string; next: string; pause: string; resume: string; navigation: string }>;\nexport type CarouselProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"onChange\"> & CarouselSelection & Readonly<{\n label: string;\n slides: readonly CarouselSlideDescriptor[];\n renderSlide: (slide: CarouselSlideDescriptor) => ReactNode;\n composeAccessibleName: ComposeCarouselAccessibleName;\n labels: CarouselLabels;\n autoplay?: CarouselAutoplayConfig;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Finite keyed cards. Hidden slides stay mounted but cannot receive focus. */\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(function Carousel({\n label, slides, renderSlide, composeAccessibleName, labels, autoplay,\n currentKey, defaultCurrentKey, onCurrentKeyChange, className,\n onFocusCapture, onMouseEnter, onMouseLeave, onKeyDown, layoutStyle, ...props\n}, ref) {\n const theme = useOptionalHjmTheme();\n const [current, setCurrent] = useControllableState({\n ...(currentKey === undefined ? {} : { value: currentKey }),\n defaultValue: defaultCurrentKey ?? slides[0]?.id ?? \"\",\n ...(onCurrentKeyChange === undefined ? {} : { onChange: onCurrentKeyChange }),\n });\n const descriptor = { slides, currentKey: current, ...(autoplay ? { autoplay } : {}) };\n const resolved = resolveCarouselDescriptor(descriptor, { composeAccessibleName });\n if (!label.trim() || Object.values(labels).some((value) => !value.trim())) throw new TypeError(\"Carousel labels must not be empty\");\n const [rotating, setRotating] = useState(autoplay !== undefined);\n const [hovered, setHovered] = useState(false);\n const [hidden, setHidden] = useState(false);\n const pointerRotation = useRef<boolean | null>(null);\n const last = current === slides.at(-1)!.id;\n const playing = isCarouselAutoplayActive(autoplay, {\n reducedMotion: theme?.environment.reducedMotion ?? false,\n paused: !rotating || hovered || hidden || last,\n });\n useEffect(() => {\n const update = () => setHidden(document.hidden);\n update(); document.addEventListener(\"visibilitychange\", update);\n return () => document.removeEventListener(\"visibilitychange\", update);\n }, []);\n useEffect(() => {\n if (!playing || !autoplay) return;\n const timer = setTimeout(() => setCurrent(getCarouselNavigationTarget(descriptor, \"next\")), autoplay.intervalMs);\n return () => clearTimeout(timer);\n }, [playing, autoplay?.intervalMs, current, slides, setCurrent]);\n function select(key: string) { setRotating(false); if (key !== current) setCurrent(key); }\n function move(intent: \"next\" | \"previous\") { select(getCarouselNavigationTarget(descriptor, intent)); }\n const rotationRequested = rotating && !last && !theme?.environment.reducedMotion;\n return <div {...props} style={{ ...props.style, ...layoutStyle }} ref={ref} role=\"region\" aria-label={label} aria-roledescription=\"carousel\"\n className={classNames(\"hjm-carousel\", className)} data-large-text={isLargeTextScale(theme?.environment.textScale ?? 1)}\n onFocusCapture={(event) => {\n // APG: leaving focus must not silently restart rotation. Explicit resume is required.\n if (!event.currentTarget.contains(event.relatedTarget)) setRotating(false);\n onFocusCapture?.(event);\n }}\n onMouseEnter={(event) => { setHovered(true); onMouseEnter?.(event); }}\n onMouseLeave={(event) => { setHovered(false); onMouseLeave?.(event); }}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n // Do not steal editing/navigation keys from links, inputs or widgets inside a card.\n if (!(event.target instanceof Element) || !event.target.closest(\"[data-carousel-controls]\")) return;\n if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return;\n event.preventDefault();\n move((event.key === \"ArrowRight\") !== (theme?.environment.direction === \"rtl\") ? \"next\" : \"previous\");\n }}>\n {autoplay ? <Button tone=\"ghost\" className=\"hjm-carousel__rotation\" disabled={theme?.environment.reducedMotion}\n onPointerDown={() => { pointerRotation.current = !rotationRequested; }}\n onPointerCancel={() => { pointerRotation.current = null; }}\n onClick={(event) => {\n // An abandoned pointer gesture must not override a later keyboard activation.\n const next = event.detail > 0 ? pointerRotation.current ?? !rotationRequested : !rotationRequested;\n pointerRotation.current = null;\n if (next && last) setCurrent(slides[0]!.id);\n setRotating(next);\n }}>{rotationRequested ? labels.pause : labels.resume}</Button> : null}\n <div className=\"hjm-carousel__track\" aria-live={playing ? \"off\" : \"polite\"} aria-atomic={false}>\n {resolved.map((slide) => <div key={slide.id} className=\"hjm-carousel__slide\" role=\"group\"\n aria-roledescription=\"slide\" aria-label={slide.accessibleName} hidden={slide.inert} inert={slide.inert}>\n {renderSlide(slide)}\n </div>)}\n </div>\n <div className=\"hjm-carousel__controls\" data-carousel-controls role=\"group\" aria-label={labels.navigation}>\n <Button tone=\"ghost\" aria-disabled={current === slides[0]!.id} onClick={() => move(\"previous\")}>{labels.previous}</Button>\n <div className=\"hjm-carousel__dots\">\n {resolved.map((slide) => <button key={slide.id} type=\"button\" aria-label={slide.accessibleName}\n aria-disabled={slide.current} data-current={slide.current} className=\"hjm-carousel__dot\"\n onClick={() => select(slide.id)}><span aria-hidden=\"true\" /></button>)}\n </div>\n <Button tone=\"ghost\" aria-disabled={last} onClick={() => move(\"next\")}>{labels.next}</Button>\n </div>\n </div>;\n});\n"]}
1
+ {"version":3,"file":"carousel.js","sourceRoot":"","sources":["../src/carousel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport {\n resolveCarouselDescriptor, getCarouselNavigationTarget, isCarouselAutoplayActive,\n type CarouselSlideDescriptor, type CarouselSelection, type CarouselAutoplayConfig,\n type ComposeCarouselAccessibleName,\n} from \"@hjmds/design-contracts/components/carousel\";\nimport { Button } from \"./actions.js\";\nimport { isLargeTextScale } from \"@hjmds/design-contracts/components/design-system-provider\";\nimport { assignRef, classNames, useControllableState } from \"./internal.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CarouselLabels = Readonly<{ previous: string; next: string; pause: string; resume: string; navigation: string }>;\nexport type CarouselProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"onChange\"> & CarouselSelection & Readonly<{\n label: string;\n slides: readonly CarouselSlideDescriptor[];\n renderSlide: (slide: CarouselSlideDescriptor) => ReactNode;\n composeAccessibleName: ComposeCarouselAccessibleName;\n labels: CarouselLabels;\n autoplay?: CarouselAutoplayConfig;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Finite keyed cards. Hidden slides stay mounted but cannot receive focus. */\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(function Carousel({\n label, slides, renderSlide, composeAccessibleName, labels, autoplay,\n currentKey, defaultCurrentKey, onCurrentKeyChange, className,\n onFocusCapture, onMouseEnter, onMouseLeave, onKeyDown, layoutStyle, ...props\n}, ref) {\n const theme = useOptionalHjmTheme();\n const [storedCurrent, setCurrent] = useControllableState({\n ...(currentKey === undefined ? {} : { value: currentKey }),\n defaultValue: defaultCurrentKey ?? slides[0]?.id ?? \"\",\n ...(onCurrentKeyChange === undefined ? {} : { onChange: onCurrentKeyChange }),\n });\n const previousIndex = useRef<number | null>(null);\n const host = useRef<HTMLDivElement | null>(null);\n const lastFocused = useRef<HTMLElement | null>(null);\n // A host can delete the current uncontrolled slide after mount. Keep its place\n // in the collection (or the new last slide); explicit invalid keys still fail validation.\n const recovering = currentKey === undefined && previousIndex.current !== null && !slides.some(slide => slide.id === storedCurrent);\n const current = recovering ? slides[Math.min(previousIndex.current!, slides.length - 1)]?.id ?? storedCurrent : storedCurrent;\n useLayoutEffect(() => {\n previousIndex.current = slides.findIndex(slide => slide.id === current);\n if (recovering) {\n setCurrent(current);\n // A removed dot/card can strand focus on body; retained controls keep their focus.\n if (lastFocused.current && !lastFocused.current.isConnected && document.activeElement === document.body) {\n host.current?.querySelector<HTMLButtonElement>('.hjm-carousel__dot[data-current=\"true\"]')?.focus();\n }\n }\n }, [current, recovering, slides, setCurrent]);\n const descriptor = { slides, currentKey: current, ...(autoplay ? { autoplay } : {}) };\n const resolved = resolveCarouselDescriptor(descriptor, { composeAccessibleName });\n if (!label.trim() || Object.values(labels).some((value) => !value.trim())) throw new TypeError(\"Carousel labels must not be empty\");\n const [rotating, setRotating] = useState(autoplay !== undefined);\n const [hovered, setHovered] = useState(false);\n const [hidden, setHidden] = useState(false);\n const pointerRotation = useRef<boolean | null>(null);\n const last = current === slides.at(-1)!.id;\n const playing = isCarouselAutoplayActive(autoplay, {\n reducedMotion: theme?.environment.reducedMotion ?? false,\n paused: !rotating || hovered || hidden || last,\n });\n useEffect(() => {\n const update = () => setHidden(document.hidden);\n update(); document.addEventListener(\"visibilitychange\", update);\n return () => document.removeEventListener(\"visibilitychange\", update);\n }, []);\n useEffect(() => {\n if (!playing || !autoplay) return;\n const timer = setTimeout(() => setCurrent(getCarouselNavigationTarget(descriptor, \"next\")), autoplay.intervalMs);\n return () => clearTimeout(timer);\n }, [playing, autoplay?.intervalMs, current, slides, setCurrent]);\n function select(key: string) { setRotating(false); if (key !== current) setCurrent(key); }\n function move(intent: \"next\" | \"previous\") { select(getCarouselNavigationTarget(descriptor, intent)); }\n const rotationRequested = rotating && !last && !theme?.environment.reducedMotion;\n return <div {...props} style={{ ...props.style, ...layoutStyle }} ref={node => { host.current = node; assignRef(ref, node); }} role=\"region\" aria-label={label} aria-roledescription=\"carousel\"\n className={classNames(\"hjm-carousel\", className)} data-large-text={isLargeTextScale(theme?.environment.textScale ?? 1)}\n onFocusCapture={(event) => {\n lastFocused.current = event.target instanceof HTMLElement ? event.target : null;\n // APG: leaving focus must not silently restart rotation. Explicit resume is required.\n if (!event.currentTarget.contains(event.relatedTarget)) setRotating(false);\n onFocusCapture?.(event);\n }}\n onMouseEnter={(event) => { setHovered(true); onMouseEnter?.(event); }}\n onMouseLeave={(event) => { setHovered(false); onMouseLeave?.(event); }}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n // Do not steal editing/navigation keys from links, inputs or widgets inside a card.\n if (!(event.target instanceof Element) || !event.target.closest(\"[data-carousel-controls]\")) return;\n if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return;\n event.preventDefault();\n move((event.key === \"ArrowRight\") !== (theme?.environment.direction === \"rtl\") ? \"next\" : \"previous\");\n }}>\n {autoplay ? <Button tone=\"ghost\" className=\"hjm-carousel__rotation\" disabled={theme?.environment.reducedMotion}\n onPointerDown={() => { pointerRotation.current = !rotationRequested; }}\n onPointerCancel={() => { pointerRotation.current = null; }}\n onClick={(event) => {\n // An abandoned pointer gesture must not override a later keyboard activation.\n const next = event.detail > 0 ? pointerRotation.current ?? !rotationRequested : !rotationRequested;\n pointerRotation.current = null;\n if (next && last) setCurrent(slides[0]!.id);\n setRotating(next);\n }}>{rotationRequested ? labels.pause : labels.resume}</Button> : null}\n <div className=\"hjm-carousel__track\" aria-live={playing ? \"off\" : \"polite\"} aria-atomic={false}>\n {resolved.map((slide) => <div key={slide.id} className=\"hjm-carousel__slide\" role=\"group\"\n aria-roledescription=\"slide\" aria-label={slide.accessibleName} hidden={slide.inert} inert={slide.inert}>\n {renderSlide(slide)}\n </div>)}\n </div>\n <div className=\"hjm-carousel__controls\" data-carousel-controls role=\"group\" aria-label={labels.navigation}>\n <Button tone=\"ghost\" aria-disabled={current === slides[0]!.id} onClick={() => move(\"previous\")}>{labels.previous}</Button>\n <div className=\"hjm-carousel__dots\">\n {resolved.map((slide) => <button key={slide.id} type=\"button\" aria-label={slide.accessibleName}\n aria-disabled={slide.current} data-current={slide.current} className=\"hjm-carousel__dot\"\n onClick={() => select(slide.id)}><span aria-hidden=\"true\" /></button>)}\n </div>\n <Button tone=\"ghost\" aria-disabled={last} onClick={() => move(\"next\")}>{labels.next}</Button>\n </div>\n </div>;\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"collapsible.d.ts","sourceRoot":"","sources":["../src/collapsible.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAmD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAExF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GACjD,QAAQ,CAAC;IACP,oEAAoE;IACpE,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;IACvC,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEL,eAAO,MAAM,WAAW,6GAuCtB,CAAC"}
1
+ {"version":3,"file":"collapsible.d.ts","sourceRoot":"","sources":["../src/collapsible.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAA4E,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GACjD,QAAQ,CAAC;IACP,oEAAoE;IACpE,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;IACvC,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEL,eAAO,MAAM,WAAW,6GA6DtB,CAAC"}
@@ -1,18 +1,42 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { collapsibleRecipe, validateCollapsibleOpenState, } from "@hjmds/design-contracts/components/collapsible";
3
- import { forwardRef, useId, useState } from "react";
4
- import { classNames } from "./internal.js";
3
+ import { forwardRef, useId, useLayoutEffect, useRef, useState } from "react";
4
+ import { assignRef, classNames } from "./internal.js";
5
5
  export const Collapsible = forwardRef(function Collapsible({ trigger, children, disabled = false, presentation = "disclosure", keepMounted = false, className, layoutStyle, ...openState }, forwardedRef) {
6
6
  validateCollapsibleOpenState(openState);
7
7
  const controlled = openState.open !== undefined;
8
8
  const [internalOpen, setInternalOpen] = useState(openState.defaultOpen ?? false);
9
9
  const open = presentation === "inline" || (openState.open ?? internalOpen);
10
+ const host = useRef(null);
11
+ const button = useRef(null);
12
+ const content = useRef(null);
13
+ const focusedContent = useRef(false);
14
+ const wasOpen = useRef(open);
15
+ useLayoutEffect(() => {
16
+ // A controlled close can remove or hide the currently focused input without
17
+ // activating the trigger. Recover only that focus, never an outside control.
18
+ const active = host.current?.ownerDocument.activeElement;
19
+ if (wasOpen.current && !open && focusedContent.current &&
20
+ (content.current?.contains(active ?? null) || active === host.current?.ownerDocument.body)) {
21
+ (disabled ? host.current : button.current)?.focus();
22
+ }
23
+ if (!open)
24
+ focusedContent.current = false;
25
+ wasOpen.current = open;
26
+ }, [open, disabled]);
10
27
  const id = `${useId().replaceAll(":", "")}-collapsible`;
11
- return (_jsxs("div", { ref: forwardedRef, className: classNames("hjm-collapsible", className), "data-state": open ? "open" : "closed", style: { ...layoutStyle, "--hjm-collapsible-gap": `${collapsibleRecipe.gap}px` }, children: [_jsxs("button", { hidden: presentation === "inline", type: "button", className: "hjm-collapsible__trigger", "aria-expanded": open, "aria-controls": `${id}-content`, disabled: disabled, onClick: () => {
28
+ return (_jsxs("div", { ref: node => { host.current = node; assignRef(forwardedRef, node); }, tabIndex: -1, className: classNames("hjm-collapsible", className), "data-state": open ? "open" : "closed", style: { ...layoutStyle, "--hjm-collapsible-gap": `${collapsibleRecipe.gap}px` }, children: [_jsxs("button", { ref: button, hidden: presentation === "inline", type: "button", className: "hjm-collapsible__trigger", "aria-expanded": open, "aria-controls": `${id}-content`, disabled: disabled, onClick: event => {
29
+ if (disabled || presentation === "inline")
30
+ return;
31
+ // Safari does not focus buttons on pointer activation; keep the closing
32
+ // disclosure as the keyboard origin before its focused content disappears.
33
+ if (open)
34
+ event.currentTarget.focus();
12
35
  const next = !open;
13
36
  if (!controlled)
14
37
  setInternalOpen(next);
15
38
  openState.onOpenChange?.(next);
16
- }, children: [trigger, _jsx("span", { "aria-hidden": "true", className: "hjm-collapsible__marker", children: open ? "▾" : "▸" })] }), open || keepMounted ? (_jsx("div", { hidden: !open, id: `${id}-content`, role: "region", className: "hjm-collapsible__content", children: children })) : null] }));
39
+ }, children: [trigger, _jsx("span", { "aria-hidden": "true", className: "hjm-collapsible__marker", children: open ? "▾" : "▸" })] }), open || keepMounted ? (_jsx("div", { ref: content, onFocusCapture: () => { focusedContent.current = true; }, onBlurCapture: event => { if (event.relatedTarget instanceof Node && !event.currentTarget.contains(event.relatedTarget))
40
+ focusedContent.current = false; }, hidden: !open, id: `${id}-content`, role: "region", className: "hjm-collapsible__content", children: children })) : null] }));
17
41
  });
18
42
  //# sourceMappingURL=collapsible.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collapsible.js","sourceRoot":"","sources":["../src/collapsible.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,GAE7B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAsC,MAAM,OAAO,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAkB3C,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAmC,SAAS,WAAW,CAC1F,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE,EAC/H,YAAY;IAEZ,4BAA4B,CAAC,SAAiC,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC;IAChD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,YAAY,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,YAAY,CAAC,CAAC;IAC3E,MAAM,EAAE,GAAG,GAAG,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC;IACxD,OAAO,CACL,eACE,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,UAAU,CAAC,iBAAiB,EAAE,SAAS,CAAC,gBACvC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EACpC,KAAK,EAAE,EAAE,GAAG,WAAW,EAAE,uBAAuB,EAAE,GAAG,iBAAiB,CAAC,GAAG,IAAI,EAAmB,aAEjG,kBACE,MAAM,EAAE,YAAY,KAAK,QAAQ,EACjC,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,0BAA0B,mBACrB,IAAI,mBACJ,GAAG,EAAE,UAAU,EAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,GAAG,EAAE;oBACZ,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC;oBACnB,IAAI,CAAC,UAAU;wBAAE,eAAe,CAAC,IAAI,CAAC,CAAC;oBACvC,SAAS,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC;gBACjC,CAAC,aAEA,OAAO,EACR,8BAAkB,MAAM,EAAC,SAAS,EAAC,yBAAyB,YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAQ,IAC/E,EAGR,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,CACrB,cAAK,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,0BAA0B,YAAE,QAAQ,GAAO,CAC7G,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n collapsibleRecipe,\n validateCollapsibleOpenState,\n type CollapsibleOpenState,\n} from \"@hjmds/design-contracts/components/collapsible\";\nimport { forwardRef, useId, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CollapsibleProps = CollapsibleOpenState &\n Readonly<{\n /** The control's label; it also tells the user what will appear. */\n trigger: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n /** Inline tools stay expanded without a disclosure trigger. */\n presentation?: \"disclosure\" | \"inline\";\n /** Preserve local input state while hidden; hidden content stays inaccessible. */\n keepMounted?: boolean;\n className?: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n }>;\n\nexport const Collapsible = forwardRef<HTMLDivElement, CollapsibleProps>(function Collapsible(\n { trigger, children, disabled = false, presentation = \"disclosure\", keepMounted = false, className, layoutStyle, ...openState },\n forwardedRef,\n) {\n validateCollapsibleOpenState(openState as CollapsibleOpenState);\n const controlled = openState.open !== undefined;\n const [internalOpen, setInternalOpen] = useState(openState.defaultOpen ?? false);\n const open = presentation === \"inline\" || (openState.open ?? internalOpen);\n const id = `${useId().replaceAll(\":\", \"\")}-collapsible`;\n return (\n <div\n ref={forwardedRef}\n className={classNames(\"hjm-collapsible\", className)}\n data-state={open ? \"open\" : \"closed\"}\n style={{ ...layoutStyle, \"--hjm-collapsible-gap\": `${collapsibleRecipe.gap}px` } as CSSProperties}\n >\n <button\n hidden={presentation === \"inline\"}\n type=\"button\"\n className=\"hjm-collapsible__trigger\"\n aria-expanded={open}\n aria-controls={`${id}-content`}\n disabled={disabled}\n onClick={() => {\n const next = !open;\n if (!controlled) setInternalOpen(next);\n openState.onOpenChange?.(next);\n }}\n >\n {trigger}\n <span aria-hidden=\"true\" className=\"hjm-collapsible__marker\">{open ? \"▾\" : \"▸\"}</span>\n </button>\n {/* Optional persistence keeps theme changes and closing tools from erasing local\n drafts. The HTML hidden boundary removes closed content from focus and speech. */}\n {open || keepMounted ? (\n <div hidden={!open} id={`${id}-content`} role=\"region\" className=\"hjm-collapsible__content\">{children}</div>\n ) : null}\n </div>\n );\n});\n"]}
1
+ {"version":3,"file":"collapsible.js","sourceRoot":"","sources":["../src/collapsible.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,GAE7B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAsC,MAAM,OAAO,CAAC;AACjH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAkBtD,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAmC,SAAS,WAAW,CAC1F,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE,EAC/H,YAAY;IAEZ,4BAA4B,CAAC,SAAiC,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC;IAChD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,YAAY,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,YAAY,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,CAAC,GAAG,EAAE;QACnB,4EAA4E;QAC5E,6EAA6E;QAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC;QACzD,IAAI,OAAO,CAAC,OAAO,IAAI,CAAC,IAAI,IAAI,cAAc,CAAC,OAAO;YACpD,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7F,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC;QAC1C,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IACzB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,GAAG,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC;IACxD,OAAO,CACL,eACE,GAAG,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EACpE,QAAQ,EAAE,CAAC,CAAC,EACZ,SAAS,EAAE,UAAU,CAAC,iBAAiB,EAAE,SAAS,CAAC,gBACvC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EACpC,KAAK,EAAE,EAAE,GAAG,WAAW,EAAE,uBAAuB,EAAE,GAAG,iBAAiB,CAAC,GAAG,IAAI,EAAmB,aAEjG,kBACE,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,KAAK,QAAQ,EACjC,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,0BAA0B,mBACrB,IAAI,mBACJ,GAAG,EAAE,UAAU,EAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,KAAK,CAAC,EAAE;oBACf,IAAI,QAAQ,IAAI,YAAY,KAAK,QAAQ;wBAAE,OAAO;oBAClD,wEAAwE;oBACxE,2EAA2E;oBAC3E,IAAI,IAAI;wBAAE,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;oBACtC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC;oBACnB,IAAI,CAAC,UAAU;wBAAE,eAAe,CAAC,IAAI,CAAC,CAAC;oBACvC,SAAS,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC;gBACjC,CAAC,aAEA,OAAO,EACR,8BAAkB,MAAM,EAAC,SAAS,EAAC,yBAAyB,YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAQ,IAC/E,EAGR,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,CACrB,cAAK,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,aAAa,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC;oBAAE,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,0BAA0B,YAAE,QAAQ,GAAO,CACjV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n collapsibleRecipe,\n validateCollapsibleOpenState,\n type CollapsibleOpenState,\n} from \"@hjmds/design-contracts/components/collapsible\";\nimport { forwardRef, useId, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { assignRef, classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CollapsibleProps = CollapsibleOpenState &\n Readonly<{\n /** The control's label; it also tells the user what will appear. */\n trigger: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n /** Inline tools stay expanded without a disclosure trigger. */\n presentation?: \"disclosure\" | \"inline\";\n /** Preserve local input state while hidden; hidden content stays inaccessible. */\n keepMounted?: boolean;\n className?: string;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n }>;\n\nexport const Collapsible = forwardRef<HTMLDivElement, CollapsibleProps>(function Collapsible(\n { trigger, children, disabled = false, presentation = \"disclosure\", keepMounted = false, className, layoutStyle, ...openState },\n forwardedRef,\n) {\n validateCollapsibleOpenState(openState as CollapsibleOpenState);\n const controlled = openState.open !== undefined;\n const [internalOpen, setInternalOpen] = useState(openState.defaultOpen ?? false);\n const open = presentation === \"inline\" || (openState.open ?? internalOpen);\n const host = useRef<HTMLDivElement>(null);\n const button = useRef<HTMLButtonElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const focusedContent = useRef(false);\n const wasOpen = useRef(open);\n useLayoutEffect(() => {\n // A controlled close can remove or hide the currently focused input without\n // activating the trigger. Recover only that focus, never an outside control.\n const active = host.current?.ownerDocument.activeElement;\n if (wasOpen.current && !open && focusedContent.current &&\n (content.current?.contains(active ?? null) || active === host.current?.ownerDocument.body)) {\n (disabled ? host.current : button.current)?.focus();\n }\n if (!open) focusedContent.current = false;\n wasOpen.current = open;\n }, [open, disabled]);\n const id = `${useId().replaceAll(\":\", \"\")}-collapsible`;\n return (\n <div\n ref={node => { host.current = node; assignRef(forwardedRef, node); }}\n tabIndex={-1}\n className={classNames(\"hjm-collapsible\", className)}\n data-state={open ? \"open\" : \"closed\"}\n style={{ ...layoutStyle, \"--hjm-collapsible-gap\": `${collapsibleRecipe.gap}px` } as CSSProperties}\n >\n <button\n ref={button}\n hidden={presentation === \"inline\"}\n type=\"button\"\n className=\"hjm-collapsible__trigger\"\n aria-expanded={open}\n aria-controls={`${id}-content`}\n disabled={disabled}\n onClick={event => {\n if (disabled || presentation === \"inline\") return;\n // Safari does not focus buttons on pointer activation; keep the closing\n // disclosure as the keyboard origin before its focused content disappears.\n if (open) event.currentTarget.focus();\n const next = !open;\n if (!controlled) setInternalOpen(next);\n openState.onOpenChange?.(next);\n }}\n >\n {trigger}\n <span aria-hidden=\"true\" className=\"hjm-collapsible__marker\">{open ? \"▾\" : \"▸\"}</span>\n </button>\n {/* Optional persistence keeps theme changes and closing tools from erasing local\n drafts. The HTML hidden boundary removes closed content from focus and speech. */}\n {open || keepMounted ? (\n <div ref={content} onFocusCapture={() => { focusedContent.current = true; }} onBlurCapture={event => { if (event.relatedTarget instanceof Node && !event.currentTarget.contains(event.relatedTarget)) focusedContent.current = false; }} hidden={!open} id={`${id}-content`} role=\"region\" className=\"hjm-collapsible__content\">{children}</div>\n ) : null}\n </div>\n );\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"collection-rail.d.ts","sourceRoot":"","sources":["../src/collection-rail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/F,OAAO,EAG4B,KAAK,kBAAkB,EAAE,KAAK,qBAAqB,EACrF,MAAM,yCAAyC,CAAC;AAGjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC;IAC5F,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACzG,MAAM,EAAE,oBAAoB,CAAC;IAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,CAAC;IACxF,6FAA6F;IAC7F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAA+C,EAChH,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,+BAwG9F"}
1
+ {"version":3,"file":"collection-rail.d.ts","sourceRoot":"","sources":["../src/collection-rail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/F,OAAO,EAG4B,KAAK,kBAAkB,EAAE,KAAK,qBAAqB,EACrF,MAAM,yCAAyC,CAAC;AAGjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC;IAC5F,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACzG,MAAM,EAAE,oBAAoB,CAAC;IAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,CAAC;IACxF,6FAA6F;IAC7F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAA+C,EAChH,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,+BAyH9F"}
@@ -11,6 +11,16 @@ export function CollectionRail({ label, items, renderItem, labels, density = col
11
11
  const theme = useOptionalHjmTheme(), direction = theme?.environment.direction ?? "ltr";
12
12
  const gap = collectionRailRecipe.gap;
13
13
  const viewportRef = useRef(null);
14
+ const detachedFocus = useRef(null);
15
+ useLayoutEffect(() => {
16
+ const old = detachedFocus.current;
17
+ detachedFocus.current = null;
18
+ // A removed card must leave a keyboard origin in this collection, without
19
+ // moving focus away from a surviving input or an external control.
20
+ if (old && !old.isConnected && old.ownerDocument.activeElement === old.ownerDocument.body) {
21
+ viewportRef.current?.focus({ preventScroll: true });
22
+ }
23
+ });
14
24
  const anchor = useRef(defaultStartKey ?? items[0]?.id ?? null);
15
25
  const initialized = useRef(false);
16
26
  if (!initialized.current) {
@@ -104,6 +114,14 @@ export function CollectionRail({ label, items, renderItem, labels, density = col
104
114
  if (pendingOffset.current !== null && Math.abs(offset - pendingOffset.current) <= 0.5)
105
115
  pendingOffset.current = null;
106
116
  publish(current.current.layout, offset);
107
- }, children: [_jsx("div", { style: { display: "flex", gap, width: "max-content", minWidth: "100%", paddingBlock: "var(--hjm-space-xs)" }, children: items.map((item, index) => _jsx("div", { role: "listitem", "data-collection-rail-item": item.id, style: { flex: "0 0 auto", width: position.layout.itemWidth || "auto", minWidth: 0 }, onFocusCapture: () => scrollToOffset(getCollectionRailRevealOffset(current.current.layout, current.current.offset, index), false), children: renderItem(item) }, item.id)) }), items.length === 0 ? emptyContent : null] }), _jsxs("div", { "data-collection-rail-controls": true, role: "group", "aria-label": labels.navigation, style: { display: "flex", flexWrap: "wrap", justifyContent: "flex-end", gap: "var(--hjm-space-sm)", marginBlockStart: "var(--hjm-space-sm)" }, children: [_jsx(Button, { tone: "ghost", "aria-disabled": resolved.atStart, onClick: () => navigate("previous"), children: labels.previous }), _jsx(Button, { tone: "ghost", "aria-disabled": resolved.atEnd, onClick: () => navigate("next"), children: labels.next })] })] });
117
+ }, children: [_jsx("div", { style: { display: "flex", gap, width: "max-content", minWidth: "100%", paddingBlock: "var(--hjm-space-xs)" }, children: items.map((item, index) => _jsx("div", { role: "listitem", "data-collection-rail-item": item.id, ref: node => {
118
+ if (!node)
119
+ return;
120
+ return () => {
121
+ const active = node.ownerDocument.activeElement;
122
+ if (active instanceof HTMLElement && node.contains(active))
123
+ detachedFocus.current = active;
124
+ };
125
+ }, style: { flex: "0 0 auto", width: position.layout.itemWidth || "auto", minWidth: 0 }, onFocusCapture: () => scrollToOffset(getCollectionRailRevealOffset(current.current.layout, current.current.offset, index), false), children: renderItem(item) }, item.id)) }), items.length === 0 ? emptyContent : null] }), _jsxs("div", { "data-collection-rail-controls": true, role: "group", "aria-label": labels.navigation, style: { display: "flex", flexWrap: "wrap", justifyContent: "flex-end", gap: "var(--hjm-space-sm)", marginBlockStart: "var(--hjm-space-sm)" }, children: [_jsx(Button, { tone: "ghost", "aria-disabled": resolved.atStart, onClick: () => navigate("previous"), children: labels.previous }), _jsx(Button, { tone: "ghost", "aria-disabled": resolved.atEnd, onClick: () => navigate("next"), children: labels.next })] })] });
108
126
  }
109
127
  //# sourceMappingURL=collection-rail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection-rail.js","sourceRoot":"","sources":["../src/collection-rail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport {\n collectionRailRecipe, validateCollectionRail, resolveCollectionRailLayout, resolveCollectionRailViewport,\n getCollectionRailTargetOffset, getCollectionRailItemOffset, getCollectionRailRevealOffset,\n getCollectionRailKeyboardIntent, type CollectionRailItem, type CollectionRailDensity, type CollectionRailLayout,\n} from \"@hjmds/design-contracts/collection-rail\";\nimport { Button } from \"./actions.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CollectionRailLabels = Readonly<{ previous: string; next: string; navigation: string }>;\nexport type CollectionRailProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & Readonly<{\n label: string; items: readonly CollectionRailItem[]; renderItem: (item: CollectionRailItem) => ReactNode;\n labels: CollectionRailLabels; density?: CollectionRailDensity; emptyContent?: ReactNode;\n /** Initial scroll anchor, not selection. Later collection changes reconcile by stable ID. */\n defaultStartKey?: string;\n /** Observed leading item, independent of product selection/detail state. Empty lists report null. */\n onStartKeyChange?: (key: string | null) => void;\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Finite all-mounted list placement; Card, inputs and Dialog keep their own state. */\nexport function CollectionRail({ label, items, renderItem, labels, density = collectionRailRecipe.defaults.density,\n emptyContent, defaultStartKey, onStartKeyChange, layoutStyle, ...props }: CollectionRailProps) {\n validateCollectionRail(items);\n if (!label.trim() || Object.values(labels).some(value => !value.trim())) throw new TypeError(\"CollectionRail labels must not be empty\");\n const theme = useOptionalHjmTheme(), direction = theme?.environment.direction ?? \"ltr\";\n const gap = collectionRailRecipe.gap;\n const viewportRef = useRef<HTMLDivElement>(null);\n const anchor = useRef<string | null>(defaultStartKey ?? items[0]?.id ?? null);\n const initialized = useRef(false);\n if (!initialized.current) { validateCollectionRail(items, defaultStartKey); initialized.current = true; }\n const callback = useRef(onStartKeyChange); callback.current = onStartKeyChange;\n const current = useRef({ layout: resolveCollectionRailLayout(items.length, 0, density, gap), offset: 0 });\n const pendingOffset = useRef<number | null>(null);\n const [position, setPosition] = useState(current.current);\n const identity = JSON.stringify(items.map(item => item.id));\n const itemsRef = useRef(items); itemsRef.current = items;\n\n function publish(layout: CollectionRailLayout, offset: number) {\n const resolved = resolveCollectionRailViewport(layout, offset);\n const key = itemsRef.current[resolved.startIndex]?.id ?? null;\n if (key !== anchor.current) { anchor.current = key; callback.current?.(key); }\n current.current = { layout, offset: resolved.offset };\n setPosition(current.current);\n }\n function scrollToOffset(offset: number, animated: boolean) {\n const viewport = viewportRef.current;\n if (!viewport) return;\n const resolved = resolveCollectionRailViewport(current.current.layout, offset);\n // CSS RTL scrollLeft is negative; clamp Safari rubber-banding through the\n // shared logical resolver rather than reversing data or selection keys.\n viewport.scrollTo({ left: direction === \"rtl\" ? -resolved.offset : resolved.offset,\n behavior: animated && !theme?.environment.reducedMotion ? \"smooth\" : \"instant\" });\n pendingOffset.current = animated && !theme?.environment.reducedMotion ? resolved.offset : null;\n // Smooth scroll is a request, not an observed viewport. Publishing its\n // destination now would disable the end action and report an unseen anchor.\n publish(current.current.layout, direction === \"rtl\" ? -viewport.scrollLeft : viewport.scrollLeft);\n }\n useLayoutEffect(() => {\n const viewport = viewportRef.current;\n if (!viewport) return;\n let initialMeasurement = true;\n function measure() {\n const next = resolveCollectionRailLayout(itemsRef.current.length, viewport!.clientWidth, density, gap);\n // ResizeObserver also delivers an initial unchanged box. Re-aligning then\n // would undo a focus reveal that happened after the layout effect.\n if (!initialMeasurement && next.viewport === current.current.layout.viewport) return;\n initialMeasurement = false;\n pendingOffset.current = null;\n const index = Math.max(0, itemsRef.current.findIndex(item => item.id === anchor.current));\n let offset = next.count ? getCollectionRailItemOffset(next, index) : 0;\n const focused = document.activeElement?.closest(\"[data-collection-rail-item]\");\n const focusedIndex = focused && viewport!.contains(focused)\n ? itemsRef.current.findIndex(item => item.id === focused.getAttribute(\"data-collection-rail-item\")) : -1;\n // A narrower viewport cannot keep a distant focused action and the old\n // leading item visible together; focus exposure takes priority over anchor alignment.\n if (focusedIndex >= 0) offset = getCollectionRailRevealOffset(next, offset, focusedIndex);\n current.current = { layout: next, offset };\n // Width/direction changes keep the leading item's identity, and never\n // key/remount its input or detail owner (docs/collection-rail.md).\n viewport!.scrollTo({ left: direction === \"rtl\" ? -offset : offset, behavior: \"instant\" });\n publish(next, offset);\n }\n measure();\n const observer = new ResizeObserver(measure); observer.observe(viewport);\n return () => observer.disconnect();\n }, [identity, density, gap, direction]);\n\n function navigate(intent: \"previous\" | \"next\" | \"first\" | \"last\") {\n scrollToOffset(getCollectionRailTargetOffset(current.current.layout, pendingOffset.current ?? current.current.offset, intent), true);\n }\n const resolved = resolveCollectionRailViewport(position.layout, position.offset);\n return <div {...props} style={{ ...props.style, ...layoutStyle, minWidth: 0 }}\n data-collection-rail data-start-key={items[resolved.startIndex]?.id} dir={direction}\n onKeyDown={event => {\n props.onKeyDown?.(event);\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;\n // Item widgets own arrows/Home/End. Rail navigation belongs only to its\n // viewport and explicit controls, not an input nested in a visible card.\n if (event.target !== viewportRef.current && !(event.target instanceof Element && event.target.closest(\"[data-collection-rail-controls]\"))) return;\n const intent = getCollectionRailKeyboardIntent(event.key, direction);\n if (intent) { event.preventDefault(); navigate(intent); }\n }}>\n <div ref={viewportRef} role=\"list\" aria-label={label} tabIndex={0} data-collection-rail-viewport\n style={{ overflowX: \"auto\", overscrollBehaviorX: \"contain\", minWidth: 0 }}\n onPointerDown={() => { pendingOffset.current = null; }} onWheel={() => { pendingOffset.current = null; }}\n onScroll={event => {\n const offset = direction === \"rtl\" ? -event.currentTarget.scrollLeft : event.currentTarget.scrollLeft;\n if (pendingOffset.current !== null && Math.abs(offset - pendingOffset.current) <= 0.5) pendingOffset.current = null;\n publish(current.current.layout, offset);\n }}>\n <div style={{ display: \"flex\", gap, width: \"max-content\", minWidth: \"100%\", paddingBlock: \"var(--hjm-space-xs)\" }}>\n {items.map((item, index) => <div key={item.id} role=\"listitem\" data-collection-rail-item={item.id}\n style={{ flex: \"0 0 auto\", width: position.layout.itemWidth || \"auto\", minWidth: 0 }}\n onFocusCapture={() => scrollToOffset(getCollectionRailRevealOffset(current.current.layout, current.current.offset, index), false)}>\n {renderItem(item)}\n </div>)}\n </div>\n {items.length === 0 ? emptyContent : null}\n </div>\n <div data-collection-rail-controls role=\"group\" aria-label={labels.navigation}\n style={{ display: \"flex\", flexWrap: \"wrap\", justifyContent: \"flex-end\", gap: \"var(--hjm-space-sm)\", marginBlockStart: \"var(--hjm-space-sm)\" }}>\n <Button tone=\"ghost\" aria-disabled={resolved.atStart} onClick={() => navigate(\"previous\")}>{labels.previous}</Button>\n <Button tone=\"ghost\" aria-disabled={resolved.atEnd} onClick={() => navigate(\"next\")}>{labels.next}</Button>\n </div>\n </div>;\n}\n"]}
1
+ {"version":3,"file":"collection-rail.js","sourceRoot":"","sources":["../src/collection-rail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport {\n collectionRailRecipe, validateCollectionRail, resolveCollectionRailLayout, resolveCollectionRailViewport,\n getCollectionRailTargetOffset, getCollectionRailItemOffset, getCollectionRailRevealOffset,\n getCollectionRailKeyboardIntent, type CollectionRailItem, type CollectionRailDensity, type CollectionRailLayout,\n} from \"@hjmds/design-contracts/collection-rail\";\nimport { Button } from \"./actions.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type CollectionRailLabels = Readonly<{ previous: string; next: string; navigation: string }>;\nexport type CollectionRailProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & Readonly<{\n label: string; items: readonly CollectionRailItem[]; renderItem: (item: CollectionRailItem) => ReactNode;\n labels: CollectionRailLabels; density?: CollectionRailDensity; emptyContent?: ReactNode;\n /** Initial scroll anchor, not selection. Later collection changes reconcile by stable ID. */\n defaultStartKey?: string;\n /** Observed leading item, independent of product selection/detail state. Empty lists report null. */\n onStartKeyChange?: (key: string | null) => void;\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Finite all-mounted list placement; Card, inputs and Dialog keep their own state. */\nexport function CollectionRail({ label, items, renderItem, labels, density = collectionRailRecipe.defaults.density,\n emptyContent, defaultStartKey, onStartKeyChange, layoutStyle, ...props }: CollectionRailProps) {\n validateCollectionRail(items);\n if (!label.trim() || Object.values(labels).some(value => !value.trim())) throw new TypeError(\"CollectionRail labels must not be empty\");\n const theme = useOptionalHjmTheme(), direction = theme?.environment.direction ?? \"ltr\";\n const gap = collectionRailRecipe.gap;\n const viewportRef = useRef<HTMLDivElement>(null);\n const detachedFocus = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const old = detachedFocus.current;\n detachedFocus.current = null;\n // A removed card must leave a keyboard origin in this collection, without\n // moving focus away from a surviving input or an external control.\n if (old && !old.isConnected && old.ownerDocument.activeElement === old.ownerDocument.body) {\n viewportRef.current?.focus({ preventScroll: true });\n }\n });\n const anchor = useRef<string | null>(defaultStartKey ?? items[0]?.id ?? null);\n const initialized = useRef(false);\n if (!initialized.current) { validateCollectionRail(items, defaultStartKey); initialized.current = true; }\n const callback = useRef(onStartKeyChange); callback.current = onStartKeyChange;\n const current = useRef({ layout: resolveCollectionRailLayout(items.length, 0, density, gap), offset: 0 });\n const pendingOffset = useRef<number | null>(null);\n const [position, setPosition] = useState(current.current);\n const identity = JSON.stringify(items.map(item => item.id));\n const itemsRef = useRef(items); itemsRef.current = items;\n\n function publish(layout: CollectionRailLayout, offset: number) {\n const resolved = resolveCollectionRailViewport(layout, offset);\n const key = itemsRef.current[resolved.startIndex]?.id ?? null;\n if (key !== anchor.current) { anchor.current = key; callback.current?.(key); }\n current.current = { layout, offset: resolved.offset };\n setPosition(current.current);\n }\n function scrollToOffset(offset: number, animated: boolean) {\n const viewport = viewportRef.current;\n if (!viewport) return;\n const resolved = resolveCollectionRailViewport(current.current.layout, offset);\n // CSS RTL scrollLeft is negative; clamp Safari rubber-banding through the\n // shared logical resolver rather than reversing data or selection keys.\n viewport.scrollTo({ left: direction === \"rtl\" ? -resolved.offset : resolved.offset,\n behavior: animated && !theme?.environment.reducedMotion ? \"smooth\" : \"instant\" });\n pendingOffset.current = animated && !theme?.environment.reducedMotion ? resolved.offset : null;\n // Smooth scroll is a request, not an observed viewport. Publishing its\n // destination now would disable the end action and report an unseen anchor.\n publish(current.current.layout, direction === \"rtl\" ? -viewport.scrollLeft : viewport.scrollLeft);\n }\n useLayoutEffect(() => {\n const viewport = viewportRef.current;\n if (!viewport) return;\n let initialMeasurement = true;\n function measure() {\n const next = resolveCollectionRailLayout(itemsRef.current.length, viewport!.clientWidth, density, gap);\n // ResizeObserver also delivers an initial unchanged box. Re-aligning then\n // would undo a focus reveal that happened after the layout effect.\n if (!initialMeasurement && next.viewport === current.current.layout.viewport) return;\n initialMeasurement = false;\n pendingOffset.current = null;\n const index = Math.max(0, itemsRef.current.findIndex(item => item.id === anchor.current));\n let offset = next.count ? getCollectionRailItemOffset(next, index) : 0;\n const focused = document.activeElement?.closest(\"[data-collection-rail-item]\");\n const focusedIndex = focused && viewport!.contains(focused)\n ? itemsRef.current.findIndex(item => item.id === focused.getAttribute(\"data-collection-rail-item\")) : -1;\n // A narrower viewport cannot keep a distant focused action and the old\n // leading item visible together; focus exposure takes priority over anchor alignment.\n if (focusedIndex >= 0) offset = getCollectionRailRevealOffset(next, offset, focusedIndex);\n current.current = { layout: next, offset };\n // Width/direction changes keep the leading item's identity, and never\n // key/remount its input or detail owner (docs/collection-rail.md).\n viewport!.scrollTo({ left: direction === \"rtl\" ? -offset : offset, behavior: \"instant\" });\n publish(next, offset);\n }\n measure();\n const observer = new ResizeObserver(measure); observer.observe(viewport);\n return () => observer.disconnect();\n }, [identity, density, gap, direction]);\n\n function navigate(intent: \"previous\" | \"next\" | \"first\" | \"last\") {\n scrollToOffset(getCollectionRailTargetOffset(current.current.layout, pendingOffset.current ?? current.current.offset, intent), true);\n }\n const resolved = resolveCollectionRailViewport(position.layout, position.offset);\n return <div {...props} style={{ ...props.style, ...layoutStyle, minWidth: 0 }}\n data-collection-rail data-start-key={items[resolved.startIndex]?.id} dir={direction}\n onKeyDown={event => {\n props.onKeyDown?.(event);\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;\n // Item widgets own arrows/Home/End. Rail navigation belongs only to its\n // viewport and explicit controls, not an input nested in a visible card.\n if (event.target !== viewportRef.current && !(event.target instanceof Element && event.target.closest(\"[data-collection-rail-controls]\"))) return;\n const intent = getCollectionRailKeyboardIntent(event.key, direction);\n if (intent) { event.preventDefault(); navigate(intent); }\n }}>\n <div ref={viewportRef} role=\"list\" aria-label={label} tabIndex={0} data-collection-rail-viewport\n style={{ overflowX: \"auto\", overscrollBehaviorX: \"contain\", minWidth: 0 }}\n onPointerDown={() => { pendingOffset.current = null; }} onWheel={() => { pendingOffset.current = null; }}\n onScroll={event => {\n const offset = direction === \"rtl\" ? -event.currentTarget.scrollLeft : event.currentTarget.scrollLeft;\n if (pendingOffset.current !== null && Math.abs(offset - pendingOffset.current) <= 0.5) pendingOffset.current = null;\n publish(current.current.layout, offset);\n }}>\n <div style={{ display: \"flex\", gap, width: \"max-content\", minWidth: \"100%\", paddingBlock: \"var(--hjm-space-xs)\" }}>\n {items.map((item, index) => <div key={item.id} role=\"listitem\" data-collection-rail-item={item.id}\n ref={node => {\n if (!node) return;\n return () => {\n const active = node.ownerDocument.activeElement;\n if (active instanceof HTMLElement && node.contains(active)) detachedFocus.current = active;\n };\n }}\n style={{ flex: \"0 0 auto\", width: position.layout.itemWidth || \"auto\", minWidth: 0 }}\n onFocusCapture={() => scrollToOffset(getCollectionRailRevealOffset(current.current.layout, current.current.offset, index), false)}>\n {renderItem(item)}\n </div>)}\n </div>\n {items.length === 0 ? emptyContent : null}\n </div>\n <div data-collection-rail-controls role=\"group\" aria-label={labels.navigation}\n style={{ display: \"flex\", flexWrap: \"wrap\", justifyContent: \"flex-end\", gap: \"var(--hjm-space-sm)\", marginBlockStart: \"var(--hjm-space-sm)\" }}>\n <Button tone=\"ghost\" aria-disabled={resolved.atStart} onClick={() => navigate(\"previous\")}>{labels.previous}</Button>\n <Button tone=\"ghost\" aria-disabled={resolved.atEnd} onClick={() => navigate(\"next\")}>{labels.next}</Button>\n </div>\n </div>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"color-picker.d.ts","sourceRoot":"","sources":["../src/color-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChG,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjE,qGAAqG;IACrG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AACH,gHAAgH;AAChH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAa,EAAE,QAAgB,EAAE,OAAY,EAAE,WAAW,EAAE,EAAE,gBAAgB,+BAiChJ"}
1
+ {"version":3,"file":"color-picker.d.ts","sourceRoot":"","sources":["../src/color-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChG,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjE,qGAAqG;IACrG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AACH,gHAAgH;AAChH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAa,EAAE,QAAgB,EAAE,OAAY,EAAE,WAAW,EAAE,EAAE,gBAAgB,+BAoChJ"}