@hjmds/react 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced-display.d.ts.map +1 -1
- package/dist/advanced-display.js +41 -9
- package/dist/advanced-display.js.map +1 -1
- package/dist/advanced-forms.d.ts.map +1 -1
- package/dist/advanced-forms.js +22 -9
- package/dist/advanced-forms.js.map +1 -1
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +17 -11
- package/dist/agreement.js.map +1 -1
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +15 -2
- package/dist/auth-screen.js.map +1 -1
- package/dist/avatar-blobatar-motion.d.ts.map +1 -1
- package/dist/avatar-blobatar-motion.js +10 -2
- package/dist/avatar-blobatar-motion.js.map +1 -1
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +1 -1
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +10 -2
- package/dist/calendar.js.map +1 -1
- package/dist/carousel-motion.d.ts +1 -0
- package/dist/carousel-motion.d.ts.map +1 -1
- package/dist/carousel-motion.js +4 -3
- package/dist/carousel-motion.js.map +1 -1
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +22 -4
- package/dist/carousel.js.map +1 -1
- package/dist/collapsible.d.ts.map +1 -1
- package/dist/collapsible.js +28 -4
- package/dist/collapsible.js.map +1 -1
- package/dist/collection-rail.d.ts.map +1 -1
- package/dist/collection-rail.js +19 -1
- package/dist/collection-rail.js.map +1 -1
- package/dist/color-picker.d.ts.map +1 -1
- package/dist/color-picker.js +12 -4
- package/dist/color-picker.js.map +1 -1
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +14 -2
- package/dist/command-palette.js.map +1 -1
- package/dist/context-menu.d.ts.map +1 -1
- package/dist/context-menu.js +31 -10
- package/dist/context-menu.js.map +1 -1
- package/dist/date-entry.d.ts +1 -1
- package/dist/date-entry.d.ts.map +1 -1
- package/dist/date-entry.js +20 -7
- package/dist/date-entry.js.map +1 -1
- package/dist/date-picker.d.ts.map +1 -1
- package/dist/date-picker.js +22 -6
- package/dist/date-picker.js.map +1 -1
- package/dist/display.d.ts +5 -1
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +3 -2
- package/dist/display.js.map +1 -1
- package/dist/duration-field.d.ts +2 -2
- package/dist/duration-field.d.ts.map +1 -1
- package/dist/duration-field.js +11 -3
- package/dist/duration-field.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +3 -2
- package/dist/feedback.js.map +1 -1
- package/dist/field-group.d.ts +4 -2
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +4 -3
- package/dist/field-group.js.map +1 -1
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +15 -5
- package/dist/file-picker.js.map +1 -1
- package/dist/forms.d.ts +2 -2
- package/dist/forms.js +1 -1
- package/dist/forms.js.map +1 -1
- package/dist/gravity-letters.d.ts.map +1 -1
- package/dist/gravity-letters.js +9 -1
- package/dist/gravity-letters.js.map +1 -1
- package/dist/grid-reveal.d.ts.map +1 -1
- package/dist/grid-reveal.js +9 -1
- package/dist/grid-reveal.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inline-confirm.d.ts.map +1 -1
- package/dist/inline-confirm.js +25 -4
- package/dist/inline-confirm.js.map +1 -1
- package/dist/internal/document-resource.d.ts.map +1 -1
- package/dist/internal/document-resource.js +2 -2
- package/dist/internal/document-resource.js.map +1 -1
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +2 -1
- package/dist/masonry.js.map +1 -1
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +12 -4
- package/dist/mentions.js.map +1 -1
- package/dist/menu-morph.js +1 -1
- package/dist/menu-morph.js.map +1 -1
- package/dist/menubar.d.ts.map +1 -1
- package/dist/menubar.js +43 -19
- package/dist/menubar.js.map +1 -1
- package/dist/modal.d.ts.map +1 -1
- package/dist/modal.js +15 -4
- package/dist/modal.js.map +1 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +8 -2
- package/dist/navigation.js.map +1 -1
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +3 -1
- package/dist/number-field.js.map +1 -1
- package/dist/overlay-stack.d.ts.map +1 -1
- package/dist/overlay-stack.js +23 -6
- package/dist/overlay-stack.js.map +1 -1
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +8 -0
- package/dist/overlays.js.map +1 -1
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +16 -4
- package/dist/pagination.js.map +1 -1
- package/dist/saved-items.d.ts.map +1 -1
- package/dist/saved-items.js +6 -1
- package/dist/saved-items.js.map +1 -1
- package/dist/screen-flows.d.ts +1 -2
- package/dist/screen-flows.d.ts.map +1 -1
- package/dist/screen-flows.js +120 -16
- package/dist/screen-flows.js.map +1 -1
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +32 -7
- package/dist/screens.js.map +1 -1
- package/dist/select.d.ts.map +1 -1
- package/dist/select.js +6 -1
- package/dist/select.js.map +1 -1
- package/dist/side-panel.d.ts.map +1 -1
- package/dist/side-panel.js +11 -2
- package/dist/side-panel.js.map +1 -1
- package/dist/sidebar.d.ts.map +1 -1
- package/dist/sidebar.js +8 -1
- package/dist/sidebar.js.map +1 -1
- package/dist/skip-nav.d.ts.map +1 -1
- package/dist/skip-nav.js +16 -3
- package/dist/skip-nav.js.map +1 -1
- package/dist/sortable.d.ts.map +1 -1
- package/dist/sortable.js +2 -1
- package/dist/sortable.js.map +1 -1
- package/dist/splitter.d.ts.map +1 -1
- package/dist/splitter.js +23 -11
- package/dist/splitter.js.map +1 -1
- package/dist/statistic-motion.d.ts.map +1 -1
- package/dist/statistic-motion.js +30 -2
- package/dist/statistic-motion.js.map +1 -1
- package/dist/status-text.d.ts +11 -0
- package/dist/status-text.d.ts.map +1 -0
- package/dist/status-text.js +7 -0
- package/dist/status-text.js.map +1 -0
- package/dist/styles.css +19 -4
- package/dist/styles.layered.css +19 -4
- package/dist/supplemental-display.d.ts.map +1 -1
- package/dist/supplemental-display.js +19 -8
- package/dist/supplemental-display.js.map +1 -1
- package/dist/supplemental-navigation.d.ts.map +1 -1
- package/dist/supplemental-navigation.js +10 -1
- package/dist/supplemental-navigation.js.map +1 -1
- package/dist/tags-input.d.ts.map +1 -1
- package/dist/tags-input.js +18 -8
- package/dist/tags-input.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +1 -0
- package/dist/theme.js.map +1 -1
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +31 -16
- package/dist/toast.js.map +1 -1
- package/dist/toggle-group.d.ts.map +1 -1
- package/dist/toggle-group.js +6 -1
- package/dist/toggle-group.js.map +1 -1
- package/dist/tour.d.ts.map +1 -1
- package/dist/tour.js +15 -2
- package/dist/tour.js.map +1 -1
- package/dist/transfer-list.d.ts +1 -1
- package/dist/transfer-list.d.ts.map +1 -1
- package/dist/transfer-list.js +52 -7
- package/dist/transfer-list.js.map +1 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +41 -8
- package/dist/tree.js.map +1 -1
- package/dist/virtual-list.d.ts.map +1 -1
- package/dist/virtual-list.js +44 -7
- package/dist/virtual-list.js.map +1 -1
- package/package.json +8 -3
package/dist/calendar.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/carousel-motion.js
CHANGED
|
@@ -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":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AACpE,OAAO,gBAAgB,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAsC,MAAM,6CAA6C,CAAC;AACzJ,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
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"]}
|
package/dist/carousel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../src/carousel.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
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 [
|
|
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);
|
package/dist/carousel.js.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/collapsible.js
CHANGED
|
@@ -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", {
|
|
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
|
package/dist/collapsible.js.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"collapsible.js","sourceRoot":"","sources":["../src/collapsible.tsx"],"names":[],"mappings":"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","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,+
|
|
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"}
|
package/dist/collection-rail.js
CHANGED
|
@@ -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,
|
|
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,+
|
|
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"}
|
package/dist/color-picker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useId, useState } from "react";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
3
|
import { colorPickerRecipe, normalizePickerColor, pickerOpacity, withPickerOpacity } from "@hjmds/design-contracts/components/color-picker";
|
|
4
4
|
/** Controlled value; invalid text stays local until corrected or escaped. Native color UI supplies RGB only. */
|
|
5
5
|
export function ColorPicker({ label, labels, value, onValueChange, alpha = false, disabled = false, presets = [], layoutStyle }) {
|
|
@@ -11,10 +11,15 @@ export function ColorPicker({ label, labels, value, onValueChange, alpha = false
|
|
|
11
11
|
const id = useId();
|
|
12
12
|
const [draft, setDraft] = useState(color);
|
|
13
13
|
const [invalid, setInvalid] = useState(false);
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Enter consumes this edit; its following blur must not resend a deferred controlled request.
|
|
15
|
+
const draftChanged = useRef(false);
|
|
16
|
+
useEffect(() => { setDraft(color); setInvalid(false); draftChanged.current = false; }, [color]);
|
|
17
|
+
function emit(next) { if (disabled)
|
|
18
|
+
return; draftChanged.current = false; setDraft(next); setInvalid(false); if (next !== color)
|
|
16
19
|
onValueChange(next); }
|
|
17
20
|
function commit() {
|
|
21
|
+
if (disabled)
|
|
22
|
+
return;
|
|
18
23
|
let next;
|
|
19
24
|
try {
|
|
20
25
|
// A typed 3/6-digit HEX names only the hue. Keep the current opacity instead of resetting it to 100%,
|
|
@@ -28,11 +33,14 @@ export function ColorPicker({ label, labels, value, onValueChange, alpha = false
|
|
|
28
33
|
}
|
|
29
34
|
emit(next);
|
|
30
35
|
}
|
|
31
|
-
return _jsxs("fieldset", { className: "hjm-color-picker", style: { ...layoutStyle, "--hjm-color-picker-target": `${colorPickerRecipe.minTargetSize}px` }, "data-hjm-color-picker": true, disabled: disabled, children: [_jsx("legend", { children: label }), _jsxs("div", { className: "hjm-color-picker__row", children: [_jsxs("label", { className: "hjm-color-picker__native", children: [_jsx("span", { children: labels.color }), _jsx("input", { type: "color", value: color.slice(0, 7), onChange: event => emit(event.target.value + (alpha ? color.slice(7) : "")) })] }), _jsxs("label", { className: "hjm-color-picker__hex", children: [_jsx("span", { children: labels.hex }), _jsx("input", { type: "text", dir: "ltr", value: draft, spellCheck: false, autoComplete: "off", "aria-invalid": invalid || undefined, "aria-describedby": invalid ? `${id}-error` : undefined, onChange: event => { setDraft(event.target.value); setInvalid(false); }, onBlur:
|
|
36
|
+
return _jsxs("fieldset", { className: "hjm-color-picker", style: { ...layoutStyle, "--hjm-color-picker-target": `${colorPickerRecipe.minTargetSize}px` }, "data-hjm-color-picker": true, disabled: disabled, children: [_jsx("legend", { children: label }), _jsxs("div", { className: "hjm-color-picker__row", children: [_jsxs("label", { className: "hjm-color-picker__native", children: [_jsx("span", { children: labels.color }), _jsx("input", { type: "color", value: color.slice(0, 7), onChange: event => emit(event.target.value + (alpha ? color.slice(7) : "")) })] }), _jsxs("label", { className: "hjm-color-picker__hex", children: [_jsx("span", { children: labels.hex }), _jsx("input", { type: "text", dir: "ltr", value: draft, spellCheck: false, autoComplete: "off", "aria-invalid": invalid || undefined, "aria-describedby": invalid ? `${id}-error` : undefined, onChange: event => { draftChanged.current = true; setDraft(event.target.value); setInvalid(false); }, onBlur: () => { if (draftChanged.current)
|
|
37
|
+
commit(); }, onKeyDown: event => { if (event.nativeEvent.isComposing || event.keyCode === 229)
|
|
38
|
+
return; if (event.key === "Enter") {
|
|
32
39
|
event.preventDefault();
|
|
33
40
|
commit();
|
|
34
41
|
} if (event.key === "Escape") {
|
|
35
42
|
event.preventDefault();
|
|
43
|
+
draftChanged.current = false;
|
|
36
44
|
setDraft(color);
|
|
37
45
|
setInvalid(false);
|
|
38
46
|
} } })] })] }), invalid && _jsx("p", { id: `${id}-error`, role: "alert", className: "hjm-color-picker__error", children: labels.invalid }), alpha && _jsxs("label", { className: "hjm-color-picker__opacity", children: [_jsxs("span", { children: [labels.opacity, " ", _jsxs("output", { children: [pickerOpacity(color), "%"] })] }), _jsx("input", { type: "range", min: 0, max: 100, step: 1, value: pickerOpacity(color), "aria-label": labels.opacity, "aria-valuetext": `${pickerOpacity(color)}%`, onChange: event => emit(withPickerOpacity(color, Number(event.target.value))) })] }), _jsx("div", { className: "hjm-color-picker__preview", "aria-hidden": "true", style: { backgroundColor: color } }), palette.length > 0 && _jsx("div", { className: "hjm-color-picker__presets", children: palette.map(preset => _jsxs("button", { type: "button", "aria-pressed": preset === color, onClick: () => emit(preset), children: [_jsx("span", { "aria-hidden": "true", className: "hjm-color-picker__swatch", style: { backgroundColor: preset } }), _jsx("bdi", { dir: "ltr", children: preset })] }, preset)) })] });
|