@astryxdesign/core 0.5.2-canary.2da7a39 → 0.5.2-canary.2e51213
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/AppShell/AppShell.d.ts +2 -2
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +10 -7
- package/dist/Banner/Banner.d.ts +5 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +7 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +22 -15
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatComposer.d.ts +2 -2
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +21 -2
- package/dist/Chat/ChatToolCalls.d.ts +1 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +12 -34
- package/dist/Chat/useChatNewMessages.js +3 -3
- package/dist/CheckboxList/CheckboxListItem.d.ts +5 -3
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +47 -13
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +269 -162
- package/dist/DateTimeInput/NativeDateSegment.d.ts +24 -0
- package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -0
- package/dist/DateTimeInput/NativeDateSegment.js +235 -0
- package/dist/DateTimeInput/NativeTimeSegment.d.ts +30 -0
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -0
- package/dist/DateTimeInput/NativeTimeSegment.js +248 -0
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +95 -0
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -0
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +103 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +2 -4
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +10 -4
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +3 -5
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
- package/dist/Layer/useTouchTrigger.js +2 -4
- package/dist/Layout/Layout.d.ts +7 -8
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +7 -8
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +89 -45
- package/dist/RadioList/RadioListItem.d.ts +30 -5
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +17 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +102 -44
- package/dist/Resizable/index.d.ts +2 -0
- package/dist/Resizable/index.d.ts.map +1 -1
- package/dist/Resizable/index.js +1 -0
- package/dist/Resizable/useResizable.d.ts +89 -8
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +521 -38
- package/dist/Resizable/utils.d.ts +35 -0
- package/dist/Resizable/utils.d.ts.map +1 -0
- package/dist/Resizable/utils.js +27 -0
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +105 -57
- package/dist/Selector/useSelectorPresentation.d.ts +1 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
- package/dist/Selector/useSelectorPresentation.js +17 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +3 -5
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +9 -10
- package/dist/Stepper/Step.d.ts +2 -1
- package/dist/Stepper/Step.d.ts.map +1 -1
- package/dist/Stepper/Step.js +75 -11
- package/dist/Stepper/Stepper.d.ts.map +1 -1
- package/dist/Stepper/Stepper.js +1 -0
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +2 -3
- package/dist/Table/index.d.ts +16 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +4 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +8 -9
- package/dist/Table/plugins/rowStatus/index.d.ts +1 -1
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +11 -30
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +108 -62
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +9 -1
- package/dist/Text/useTruncation.d.ts +1 -1
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +8 -7
- package/dist/TimeInput/TimeInput.d.ts +16 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +98 -60
- package/dist/TimeInput/index.d.ts +1 -1
- package/dist/TimeInput/index.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.d.ts +2 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +4 -145
- package/dist/Timestamp/formatRelativeTime.d.ts +20 -0
- package/dist/Timestamp/formatRelativeTime.d.ts.map +1 -0
- package/dist/Timestamp/formatRelativeTime.js +81 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +125 -77
- package/dist/Tokenizer/Tokenizer.d.ts +0 -38
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +94 -26
- package/dist/Tokenizer/useEndLaneReserve.d.ts +31 -0
- package/dist/Tokenizer/useEndLaneReserve.d.ts.map +1 -0
- package/dist/Tokenizer/useEndLaneReserve.js +136 -0
- package/dist/Tooltip/useTooltip.d.ts +1 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +53 -42
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +85 -30
- package/dist/Typeahead/Typeahead.d.ts +0 -21
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +108 -67
- package/dist/Typeahead/busyIndicatorLane.d.ts +62 -0
- package/dist/Typeahead/busyIndicatorLane.d.ts.map +1 -0
- package/dist/Typeahead/busyIndicatorLane.js +98 -0
- package/dist/astryx.css +25 -7
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -1
- package/dist/hooks/useFocusReturnVisibility.js +3 -5
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +11 -11
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +7 -4
- package/dist/theme/defineTheme.d.ts +13 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +7 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/localTokens.d.ts +18 -0
- package/dist/theme/localTokens.d.ts.map +1 -0
- package/dist/theme/localTokens.js +162 -0
- package/dist/utils/interactionModality.d.ts +6 -28
- package/dist/utils/interactionModality.d.ts.map +1 -1
- package/dist/utils/interactionModality.js +72 -37
- package/dist/utils/sharedResizeObserver.d.ts +17 -9
- package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
- package/dist/utils/sharedResizeObserver.js +54 -12
- package/locales/en.json +8 -0
- package/locales/pseudo.json +6 -0
- package/package.json +7 -2
- package/src/AlertDialog/useImperativeAlertDialog.test.tsx +163 -0
- package/src/AppShell/AppShell.doc.mjs +4 -3
- package/src/AppShell/AppShell.test.tsx +21 -0
- package/src/AppShell/AppShell.tsx +12 -7
- package/src/Banner/Banner.doc.mjs +5 -1
- package/src/Banner/Banner.tsx +12 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +31 -0
- package/src/BottomSheet/BottomSheet.spec.md +178 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +18 -9
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +12 -0
- package/src/Button/Button.doc.mjs +56 -0
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Calendar/hooks/useCalendarConstraints.test.ts +153 -0
- package/src/Calendar/hooks/useCalendarDays.test.ts +265 -0
- package/src/Calendar/hooks/useCalendarNavigation.test.ts +252 -0
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatComposer.doc.mjs +7 -4
- package/src/Chat/ChatComposer.test.tsx +84 -2
- package/src/Chat/ChatComposer.tsx +45 -3
- package/src/Chat/ChatSendButton.test.tsx +189 -0
- package/src/Chat/ChatToolCalls.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +33 -1
- package/src/Chat/ChatToolCalls.tsx +13 -34
- package/src/Chat/useChatNewMessages.ts +3 -3
- package/src/Chat/useChatPasteAsToken.test.ts +191 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
- package/src/CheckboxList/CheckboxList.spec.md +215 -0
- package/src/CheckboxList/CheckboxList.test.tsx +45 -0
- package/src/CheckboxList/CheckboxListItem.doc.mjs +11 -9
- package/src/CheckboxList/CheckboxListItem.tsx +5 -3
- package/src/Collapsible/useCollapsible.test.tsx +236 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +224 -0
- package/src/CommandPalette/CommandPaletteEmpty.test.tsx +101 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.spec.md +190 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +48 -0
- package/src/ContextMenu/ContextMenu.spec.md +207 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +43 -23
- package/src/DateTimeInput/DateTimeInput.tsx +388 -181
- package/src/DateTimeInput/DateTimeInputTouch.test.tsx +10 -2
- package/src/DateTimeInput/NativeDateSegment.tsx +308 -0
- package/src/DateTimeInput/NativePickerSegments.test.tsx +626 -0
- package/src/DateTimeInput/NativeTimeSegment.tsx +331 -0
- package/src/DateTimeInput/index.ts +1 -0
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +120 -0
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +157 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +104 -0
- package/src/DropdownMenu/DropdownMenu.spec.md +246 -0
- package/src/DropdownMenu/DropdownMenu.tsx +2 -4
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/Field.test.tsx +39 -0
- package/src/Field/Field.tsx +18 -1
- package/src/Field/InputClearButton.test.tsx +68 -6
- package/src/Field/PanelSearchInput.tsx +3 -5
- package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
- package/src/FieldStatus/FieldStatus.spec.md +213 -0
- package/src/FieldStatus/FieldStatus.test.tsx +17 -0
- package/src/FieldStatus/FieldStatus.tsx +7 -2
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +162 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +192 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +142 -0
- package/src/Layer/useTouchTrigger.ts +2 -4
- package/src/Layout/Layout.doc.mjs +57 -17
- package/src/Layout/Layout.spec.md +235 -0
- package/src/Layout/Layout.test.tsx +1 -1
- package/src/Layout/Layout.tsx +7 -8
- package/src/Layout/LayoutHeader.doc.mjs +9 -0
- package/src/Layout/LayoutPanel.doc.mjs +4 -3
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +198 -0
- package/src/Lightbox/useLightbox.test.tsx +263 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +207 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +209 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +42 -0
- package/src/MoreMenu/MoreMenu.spec.md +222 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +124 -1
- package/src/MultiSelector/MultiSelector.spec.md +293 -0
- package/src/MultiSelector/MultiSelector.test.tsx +305 -2
- package/src/MultiSelector/MultiSelector.tsx +124 -55
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +134 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +210 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +162 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +157 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +226 -0
- package/src/Popover/Popover.doc.mjs +264 -65
- package/src/Popover/Popover.spec.md +421 -0
- package/src/Popover/usePopover.doc.mjs +208 -33
- package/src/PowerSearch/PowerSearch.spec.md +291 -0
- package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +253 -0
- package/src/PowerSearch/PowerSearchToken.test.tsx +228 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +261 -0
- package/src/RadioList/RadioList.test.tsx +136 -2
- package/src/RadioList/RadioListItem.doc.mjs +30 -11
- package/src/RadioList/RadioListItem.tsx +44 -8
- package/src/Resizable/Resizable.doc.mjs +130 -24
- package/src/Resizable/Resizable.spec.md +246 -0
- package/src/Resizable/ResizeHandle.test.tsx +177 -56
- package/src/Resizable/ResizeHandle.tsx +128 -45
- package/src/Resizable/index.ts +2 -0
- package/src/Resizable/shippedDirections.test.ts +462 -0
- package/src/Resizable/useResizable.doc.mjs +124 -29
- package/src/Resizable/useResizable.test.ts +1277 -3
- package/src/Resizable/useResizable.ts +760 -48
- package/src/Resizable/utils.test.ts +74 -0
- package/src/Resizable/utils.ts +35 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +139 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +152 -0
- package/src/Selector/Selector.doc.mjs +133 -97
- package/src/Selector/Selector.spec.md +300 -0
- package/src/Selector/Selector.test.tsx +318 -1
- package/src/Selector/Selector.tsx +127 -60
- package/src/Selector/useSelectorPresentation.ts +24 -4
- package/src/SideNav/SideNav.test.tsx +3 -3
- package/src/SideNav/SideNavCollapseButton.doc.mjs +8 -0
- package/src/SideNav/SideNavItem.doc.mjs +1 -1
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +127 -0
- package/src/Slider/Slider.doc.mjs +184 -30
- package/src/Slider/Slider.spec.md +224 -0
- package/src/Slider/Slider.tsx +2 -5
- package/src/Spinner/Spinner.doc.mjs +5 -5
- package/src/Spinner/Spinner.test.tsx +32 -7
- package/src/Spinner/Spinner.tsx +46 -14
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +155 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +138 -0
- package/src/Stepper/Step.doc.mjs +1 -1
- package/src/Stepper/Step.tsx +118 -5
- package/src/Stepper/Stepper.doc.mjs +51 -1
- package/src/Stepper/Stepper.spec.md +245 -0
- package/src/Stepper/Stepper.test.tsx +315 -0
- package/src/Stepper/Stepper.tsx +8 -0
- package/src/Switch/Switch.doc.mjs +40 -0
- package/src/Switch/Switch.spec.md +175 -0
- package/src/TabList/TabList.tsx +2 -3
- package/src/Table/Table.doc.mjs +102 -24
- package/src/Table/Table.perf.test.tsx +51 -1
- package/src/Table/Table.spec.md +325 -0
- package/src/Table/TableHeaderCell.test.tsx +291 -0
- package/src/Table/index.ts +19 -4
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -12
- package/src/Table/plugins/filtering/useTableFilterState.test.tsx +143 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +2 -1
- package/src/Table/plugins/rowStatus/index.ts +1 -4
- package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +92 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.spec.md +354 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +431 -56
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +155 -84
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +72 -0
- package/src/Table/plugins/sortable/useTableSortable.tsx +18 -1
- package/src/Table/useTableGroupedRows.doc.mjs +2 -2
- package/src/Table/useTableRowStatus.doc.mjs +5 -5
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +206 -0
- package/src/Text/useTruncation.ts +8 -7
- package/src/TextArea/TextArea.doc.mjs +255 -45
- package/src/TextArea/TextArea.spec.md +205 -0
- package/src/TimeInput/NativeTimeInput.test.tsx +269 -0
- package/src/TimeInput/TimeInput.doc.mjs +47 -23
- package/src/TimeInput/TimeInput.tsx +109 -50
- package/src/TimeInput/index.ts +1 -0
- package/src/Timestamp/Timestamp.doc.mjs +2 -2
- package/src/Timestamp/Timestamp.test.tsx +75 -3
- package/src/Timestamp/Timestamp.tsx +7 -153
- package/src/Timestamp/formatRelativeTime.ts +96 -0
- package/src/Toast/ToastViewport.test.tsx +194 -0
- package/src/Toast/ToastViewport.tsx +136 -58
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -0
- package/src/Tokenizer/Tokenizer.spec.md +293 -0
- package/src/Tokenizer/Tokenizer.test.tsx +335 -1
- package/src/Tokenizer/Tokenizer.tsx +129 -46
- package/src/Tokenizer/useEndLaneReserve.ts +146 -0
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +174 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +142 -0
- package/src/Tooltip/Tooltip.test.tsx +21 -1
- package/src/Tooltip/useTooltip.test.tsx +318 -0
- package/src/Tooltip/useTooltip.tsx +60 -39
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +224 -0
- package/src/Typeahead/BaseTypeahead.tsx +96 -25
- package/src/Typeahead/Typeahead.doc.mjs +89 -1
- package/src/Typeahead/Typeahead.spec.md +257 -0
- package/src/Typeahead/Typeahead.test.tsx +502 -1
- package/src/Typeahead/Typeahead.tsx +187 -69
- package/src/Typeahead/TypeaheadItem.test.tsx +93 -2
- package/src/Typeahead/busyIndicatorLane.tsx +109 -0
- package/src/__tests__/stylexDeclarations.ts +51 -0
- package/src/hooks/useFocusReturnVisibility.ts +3 -5
- package/src/hooks/useOverflow.ts +11 -11
- package/src/hooks/useScrollLock.test.ts +103 -0
- package/src/hooks/useScrollOverflow.ts +7 -4
- package/src/i18n/useTranslator.test.tsx +197 -0
- package/src/theme/defineTheme.test.ts +227 -0
- package/src/theme/defineTheme.ts +30 -0
- package/src/theme/derivedVarRegistry.test.ts +6 -0
- package/src/theme/generateThemeRules.ts +4 -1
- package/src/theme/localTokens.ts +272 -0
- package/src/theme/themingTargets.test.ts +126 -28
- package/src/utils/interactionModality.test.ts +83 -0
- package/src/utils/interactionModality.ts +88 -36
- package/src/utils/sharedResizeObserver.test.ts +124 -5
- package/src/utils/sharedResizeObserver.ts +60 -14
|
@@ -4,17 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useResizable.ts
|
|
7
|
-
* @input Resize configuration (defaultSize,
|
|
8
|
-
* collapse configuration (collapsible,
|
|
7
|
+
* @input Resize configuration (defaultSize, minSize, maxSize, legacy pixel
|
|
8
|
+
* aliases, snaps) and collapse configuration (collapsible,
|
|
9
|
+
* defaultIsCollapsed, isCollapsed)
|
|
9
10
|
* @output Hook return: size, isCollapsed, collapse/expand/resize methods, props for handle
|
|
10
11
|
* @position Public hook; consumed by layout components via `resizable` prop
|
|
11
12
|
*/
|
|
12
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
14
|
+
import { observeResize } from "../utils/sharedResizeObserver.js";
|
|
15
|
+
import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
|
|
16
|
+
import { devWarn } from "../utils/devWarning.js";
|
|
13
17
|
|
|
14
18
|
// =============================================================================
|
|
15
19
|
// Types
|
|
16
20
|
// =============================================================================
|
|
17
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The minimum, in one of two spellings. Exactly one may be supplied: the union
|
|
24
|
+
* makes passing both a type error, so a migration cannot leave a stale
|
|
25
|
+
* deprecated value shadowing the new one (AST-010 API4).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** The maximum, in one of two spellings. See {@link ResizableMinConfig}. */
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* Shared config shape for any component that integrates built-in resize
|
|
20
32
|
* (e.g. SideNav `resizable` prop). Provides a simplified API surface
|
|
@@ -111,21 +123,217 @@ function persistState(key, state) {
|
|
|
111
123
|
/* ignore */
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
|
|
127
|
+
/** The released compatibility basis when there is no window to measure. */
|
|
128
|
+
const SERVER_BASIS = 1200;
|
|
129
|
+
const DEFAULT_SIZE_FALLBACK = 250;
|
|
130
|
+
const RESIZABLE_SIZE_GUIDANCE = 'Use a non-negative number of pixels, an exact "Npx" string, an exact ' + '"N%" string from 0% to 100%, pixel(value), or ' + 'percent(value, {min: pixel(value)}) / percent(value, {max: pixel(value)}).';
|
|
131
|
+
const LEGACY_PIXEL_GUIDANCE = 'Use a non-negative number of pixels.';
|
|
132
|
+
const LEGACY_MAX_PIXEL_GUIDANCE = 'Use a non-negative number of pixels or explicit Infinity.';
|
|
133
|
+
|
|
134
|
+
/** Parsed, validated input. */
|
|
135
|
+
|
|
136
|
+
const PX_PATTERN = /^(\d+(?:\.\d+)?)px$/;
|
|
137
|
+
const PERCENT_PATTERN = /^(\d+(?:\.\d+)?)%$/;
|
|
138
|
+
function parseSizeString(value) {
|
|
139
|
+
// Preserve the released atomic fast path exactly.
|
|
140
|
+
const px = PX_PATTERN.exec(value);
|
|
141
|
+
if (px) {
|
|
142
|
+
const parsed = Number(px[1]);
|
|
143
|
+
return Number.isFinite(parsed) ? {
|
|
144
|
+
kind: 'px',
|
|
145
|
+
value: parsed
|
|
146
|
+
} : null;
|
|
147
|
+
}
|
|
148
|
+
const percentValue = PERCENT_PATTERN.exec(value);
|
|
149
|
+
if (percentValue) {
|
|
150
|
+
const parsed = Number(percentValue[1]);
|
|
151
|
+
return Number.isFinite(parsed) && parsed <= 100 ? {
|
|
152
|
+
kind: 'percent',
|
|
153
|
+
value: parsed
|
|
154
|
+
} : null;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function parsePixelSize(value) {
|
|
159
|
+
if (typeof value !== 'object' || value == null || Array.isArray(value)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const candidate = value;
|
|
163
|
+
return candidate.type === 'pixel' && typeof candidate.value === 'number' && Number.isFinite(candidate.value) && candidate.value >= 0 ? candidate.value : null;
|
|
164
|
+
}
|
|
165
|
+
function parsePercentSize(value) {
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const candidate = value;
|
|
170
|
+
if (candidate.type !== 'percent' || typeof candidate.value !== 'number' || !Number.isFinite(candidate.value) || candidate.value < 0 || candidate.value > 100) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const hasMin = candidate.min !== undefined;
|
|
174
|
+
const hasMax = candidate.max !== undefined;
|
|
175
|
+
// Exactly one bound is required. The helper's public type enforces this for
|
|
176
|
+
// typed callers; this branch preserves it for JavaScript and `any`.
|
|
177
|
+
if (hasMin === hasMax) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (hasMin) {
|
|
181
|
+
const min = parsePixelSize(candidate.min);
|
|
182
|
+
return min == null ? null : {
|
|
183
|
+
kind: 'percent',
|
|
184
|
+
value: candidate.value,
|
|
185
|
+
min
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const max = parsePixelSize(candidate.max);
|
|
189
|
+
return max == null ? null : {
|
|
190
|
+
kind: 'percent',
|
|
191
|
+
value: candidate.value,
|
|
192
|
+
max
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function parseSize(value) {
|
|
196
|
+
if (typeof value === 'number') {
|
|
197
|
+
return Number.isFinite(value) && value >= 0 ? {
|
|
198
|
+
kind: 'px',
|
|
199
|
+
value
|
|
200
|
+
} : null;
|
|
201
|
+
}
|
|
202
|
+
if (typeof value === 'string') {
|
|
203
|
+
return parseSizeString(value);
|
|
204
|
+
}
|
|
205
|
+
if (typeof value === 'object' && value != null) {
|
|
206
|
+
const pixelValue = parsePixelSize(value);
|
|
207
|
+
return pixelValue == null ? parsePercentSize(value) : {
|
|
208
|
+
kind: 'px',
|
|
209
|
+
value: pixelValue
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function parseLegacyPixel(value, allowInfinity) {
|
|
215
|
+
if (allowInfinity && value === Infinity) {
|
|
216
|
+
return {
|
|
217
|
+
kind: 'px',
|
|
218
|
+
value
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (typeof value === 'number') {
|
|
222
|
+
return Number.isFinite(value) && value >= 0 ? {
|
|
223
|
+
kind: 'px',
|
|
224
|
+
value
|
|
225
|
+
} : null;
|
|
226
|
+
}
|
|
227
|
+
// Preserve the released runtime compatibility for untyped callers. The
|
|
228
|
+
// public aliases remain number-only, but exact atomic strings worked before
|
|
229
|
+
// they were deprecated and continue to resolve through the same fast path.
|
|
230
|
+
return typeof value === 'string' ? parseSizeString(value) : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Whether this parsed value must be recomputed when its basis changes. */
|
|
234
|
+
function dependsOnBasis(parsed) {
|
|
235
|
+
return parsed?.kind === 'percent';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Resolve a parsed size against its basis, then apply its one pixel bound. */
|
|
239
|
+
function toPixels(parsed, basis) {
|
|
240
|
+
if (parsed.kind === 'px') {
|
|
241
|
+
return parsed.value;
|
|
242
|
+
}
|
|
243
|
+
if (basis == null) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const percentagePx = Math.round(parsed.value / 100 * basis);
|
|
247
|
+
if (parsed.min !== undefined) {
|
|
248
|
+
return Math.max(percentagePx, parsed.min);
|
|
249
|
+
}
|
|
250
|
+
if (parsed.max !== undefined) {
|
|
251
|
+
return Math.min(percentagePx, parsed.max);
|
|
252
|
+
}
|
|
253
|
+
return percentagePx;
|
|
254
|
+
}
|
|
255
|
+
function describeUnknown(value) {
|
|
256
|
+
if (typeof value === 'number' && !Number.isFinite(value)) {
|
|
257
|
+
return String(value);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
return JSON.stringify(value) ?? String(value);
|
|
261
|
+
} catch {
|
|
262
|
+
return String(value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function describePixelCandidate(value) {
|
|
266
|
+
if (typeof value !== 'object' || value == null || Array.isArray(value)) {
|
|
267
|
+
return describeUnknown(value);
|
|
268
|
+
}
|
|
269
|
+
const candidate = value;
|
|
270
|
+
return candidate.type === 'pixel' ? `{"type":"pixel","value":${describeUnknown(candidate.value)}}` : describeUnknown(value);
|
|
271
|
+
}
|
|
272
|
+
function describeSize(value) {
|
|
273
|
+
if (typeof value === 'object' && value != null && !Array.isArray(value)) {
|
|
274
|
+
const candidate = value;
|
|
275
|
+
if (candidate.type === 'pixel') {
|
|
276
|
+
return describePixelCandidate(value);
|
|
277
|
+
}
|
|
278
|
+
if (candidate.type === 'percent') {
|
|
279
|
+
const fields = ['"type":"percent"', `"value":${describeUnknown(candidate.value)}`];
|
|
280
|
+
if (candidate.min !== undefined) {
|
|
281
|
+
fields.push(`"min":${describePixelCandidate(candidate.min)}`);
|
|
282
|
+
}
|
|
283
|
+
if (candidate.max !== undefined) {
|
|
284
|
+
fields.push(`"max":${describePixelCandidate(candidate.max)}`);
|
|
285
|
+
}
|
|
286
|
+
return `{${fields.join(',')}}`;
|
|
126
287
|
}
|
|
127
288
|
}
|
|
128
|
-
return
|
|
289
|
+
return describeUnknown(value);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Resolves one configured value, applying its role's fallback when the input is
|
|
294
|
+
* not accepted. The warning remains development-only through `devWarn`; every
|
|
295
|
+
* build takes the same deterministic fallback.
|
|
296
|
+
*/
|
|
297
|
+
function resolveBound(raw, parsed, basis, fallback, label, accepted, didWarnRef) {
|
|
298
|
+
if (raw === undefined) {
|
|
299
|
+
return fallback;
|
|
300
|
+
}
|
|
301
|
+
// Preserve the released explicit unbounded maximum on both the unified and
|
|
302
|
+
// deprecated spellings. Other roles still reject non-finite numbers.
|
|
303
|
+
if (raw === Infinity && fallback === Infinity) {
|
|
304
|
+
return Infinity;
|
|
305
|
+
}
|
|
306
|
+
if (parsed == null) {
|
|
307
|
+
if (!didWarnRef.current) {
|
|
308
|
+
didWarnRef.current = true;
|
|
309
|
+
devWarn('useResizable', `${label}: ${describeSize(raw)} is not a size. ${accepted} ` + `Falling back to ${fallback}.`);
|
|
310
|
+
}
|
|
311
|
+
return fallback;
|
|
312
|
+
}
|
|
313
|
+
return toPixels(parsed, basis) ?? fallback;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The container's content-box size on the active axis.
|
|
318
|
+
*
|
|
319
|
+
* Content box, not border box: a bordered or padded container would otherwise
|
|
320
|
+
* report a basis a few pixels larger than the space a percentage is actually a
|
|
321
|
+
* share of, and that error lands in `aria-valuemax`. The observer entry
|
|
322
|
+
* carries `contentBoxSize` natively, which is already writing-mode aware;
|
|
323
|
+
* measuring the element is the fallback for the synthetic entry
|
|
324
|
+
* `observeResize` fires on registration.
|
|
325
|
+
*/
|
|
326
|
+
function measureContentBox(element, direction, entry) {
|
|
327
|
+
const box = entry?.contentBoxSize?.[0];
|
|
328
|
+
if (box) {
|
|
329
|
+
return direction === 'vertical' ? box.blockSize : box.inlineSize;
|
|
330
|
+
}
|
|
331
|
+
const style = typeof window === 'undefined' ? null : window.getComputedStyle(element);
|
|
332
|
+
if (style == null) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const size = direction === 'vertical' ? element.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) : element.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
|
|
336
|
+
return Number.isFinite(size) ? size : null;
|
|
129
337
|
}
|
|
130
338
|
|
|
131
339
|
// =============================================================================
|
|
@@ -135,8 +343,12 @@ function resolveDefaultSize(defaultSize) {
|
|
|
135
343
|
function useSingleResizable(config) {
|
|
136
344
|
const {
|
|
137
345
|
defaultSize,
|
|
138
|
-
|
|
139
|
-
|
|
346
|
+
minSize,
|
|
347
|
+
maxSize,
|
|
348
|
+
minSizePx,
|
|
349
|
+
maxSizePx,
|
|
350
|
+
containerRef,
|
|
351
|
+
direction = 'horizontal',
|
|
140
352
|
collapsible = false,
|
|
141
353
|
collapsedSize = DEFAULT_COLLAPSED_SIZE,
|
|
142
354
|
snaps: configuredSnaps,
|
|
@@ -148,14 +360,226 @@ function useSingleResizable(config) {
|
|
|
148
360
|
} = config;
|
|
149
361
|
const emptySnapsRef = useRef([]);
|
|
150
362
|
const snaps = configuredSnaps ?? emptySnapsRef.current;
|
|
151
|
-
|
|
363
|
+
|
|
364
|
+
// The unified prop wins over its deprecated alias. The types make supplying
|
|
365
|
+
// both an error, so reaching here means untyped JavaScript, an `any`, or a
|
|
366
|
+
// spread — exactly the case where a stale alias hidden in an object would
|
|
367
|
+
// otherwise silently beat the explicit migration target.
|
|
368
|
+
const hasUnifiedMin = minSize !== undefined;
|
|
369
|
+
const hasUnifiedMax = maxSize !== undefined;
|
|
370
|
+
const minConflict = hasUnifiedMin && minSizePx !== undefined;
|
|
371
|
+
const maxConflict = hasUnifiedMax && maxSizePx !== undefined;
|
|
372
|
+
const rawMin = hasUnifiedMin ? minSize : minSizePx;
|
|
373
|
+
const rawMax = hasUnifiedMax ? maxSize : maxSizePx;
|
|
374
|
+
const parsedDefault = parseSize(defaultSize);
|
|
375
|
+
const parsedMin = hasUnifiedMin ? parseSize(minSize) : parseLegacyPixel(minSizePx, false);
|
|
376
|
+
const parsedMax = hasUnifiedMax ? parseSize(maxSize) : parseLegacyPixel(maxSizePx, true);
|
|
377
|
+
const hasContainer = containerRef != null;
|
|
152
378
|
const persisted = autoSaveId ? loadPersistedState(autoSaveId) : null;
|
|
153
|
-
const
|
|
379
|
+
const initialDefaultRef = useRef(null);
|
|
380
|
+
const [, setDefaultBasisCleanupTick] = useState(0);
|
|
381
|
+
const defaultDependsOnBasis = dependsOnBasis(parsedDefault);
|
|
382
|
+
const boundsDependOnBasis = dependsOnBasis(parsedMin) || dependsOnBasis(parsedMax);
|
|
383
|
+
// A default needs a live container only until its initial pixel choice is
|
|
384
|
+
// final. A persisted pixel choice wins without measuring the unused default.
|
|
385
|
+
// Bounds are different: percentages keep following the basis so they can
|
|
386
|
+
// re-clamp the selected pixel size.
|
|
387
|
+
const defaultNeedsInitialBasis = persisted?.size == null && defaultDependsOnBasis && initialDefaultRef.current?.isFinal !== true;
|
|
388
|
+
const observeContainerBasis = hasContainer && (boundsDependOnBasis || defaultNeedsInitialBasis);
|
|
389
|
+
const observeViewportBasis = !hasContainer && boundsDependOnBasis;
|
|
390
|
+
|
|
391
|
+
// The container basis, read through the shared observer. `useSyncExternalStore`
|
|
392
|
+
// rather than an effect: the store IS the measurement, so there is no render
|
|
393
|
+
// that paints before the basis is known and no `setState` in an effect.
|
|
394
|
+
//
|
|
395
|
+
// The subscription depends on the ELEMENT, not on the ref object. A ref is
|
|
396
|
+
// stable across an element swap, so `containerRef.current` can become a
|
|
397
|
+
// different node — a remount behind the same ref, a conditional branch, a
|
|
398
|
+
// portal moving — without any input to this hook changing. Keyed on the ref,
|
|
399
|
+
// the old detached node stayed observed (and a detached node measures 0,
|
|
400
|
+
// which is how a replacement zeroed the bounds) and the new one was never
|
|
401
|
+
// observed at all.
|
|
402
|
+
const containerElementRef = useRef(null);
|
|
403
|
+
// DOM ref changes are invisible to React. This state is only a version tick
|
|
404
|
+
// while basis observation is active; the element itself lives in a ref so it
|
|
405
|
+
// can be released without adding a cleanup render.
|
|
406
|
+
const [, setContainerElementVersion] = useState(0);
|
|
407
|
+
const containerBasisRef = useRef(null);
|
|
408
|
+
const containerElement = observeContainerBasis ? containerElementRef.current : null;
|
|
409
|
+
|
|
410
|
+
// Follow the ref only while some configuration needs its basis. A numeric-only
|
|
411
|
+
// region never reads the ref. Once observation stops, drop both the element
|
|
412
|
+
// and its measurement without a state update; a later percentage starts from
|
|
413
|
+
// the ref's then-current element and a fresh basis.
|
|
414
|
+
useIsomorphicLayoutEffect(() => {
|
|
415
|
+
if (!observeContainerBasis) {
|
|
416
|
+
containerElementRef.current = null;
|
|
417
|
+
containerBasisRef.current = null;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const element = containerRef?.current ?? null;
|
|
421
|
+
if (element !== containerElementRef.current) {
|
|
422
|
+
containerBasisRef.current = null;
|
|
423
|
+
containerElementRef.current = element;
|
|
424
|
+
setContainerElementVersion(version => version + 1);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
const subscribeToContainer = useCallback(onStoreChange => {
|
|
428
|
+
if (!observeContainerBasis || containerElement == null || containerElement !== containerRef?.current) {
|
|
429
|
+
return () => {};
|
|
430
|
+
}
|
|
431
|
+
// Unsubscribe by callback: several regions share one container, and any
|
|
432
|
+
// number of unrelated hooks may observe the same node.
|
|
433
|
+
return observeResize(containerElement, entry => {
|
|
434
|
+
containerBasisRef.current = measureContentBox(containerElement, direction, entry);
|
|
435
|
+
onStoreChange();
|
|
436
|
+
});
|
|
437
|
+
}, [containerElement, observeContainerBasis, direction, containerRef]);
|
|
438
|
+
const readContainerBasis = useCallback(() => {
|
|
439
|
+
if (!observeContainerBasis || containerElement == null) {
|
|
440
|
+
// An inactive subscription owns no current measurement. Clearing this is
|
|
441
|
+
// important when a percentage bound is added later: the first active
|
|
442
|
+
// render must measure the container as it is now, not clamp against the
|
|
443
|
+
// last basis seen by a default-only subscription.
|
|
444
|
+
containerBasisRef.current = null;
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
// Cached in a ref so the snapshot is referentially stable between resizes;
|
|
448
|
+
// returning a fresh measurement every call would loop the store.
|
|
449
|
+
if (containerBasisRef.current == null) {
|
|
450
|
+
containerBasisRef.current = measureContentBox(containerElement, direction);
|
|
451
|
+
}
|
|
452
|
+
return containerBasisRef.current;
|
|
453
|
+
}, [containerElement, observeContainerBasis, direction]);
|
|
454
|
+
const containerBasis = useSyncExternalStore(subscribeToContainer, readContainerBasis, () => null);
|
|
455
|
+
|
|
456
|
+
// The viewport basis is always readable synchronously. Percentage-dependent
|
|
457
|
+
// BOUNDS subscribe to later resizes (FR6); a default reads the initial
|
|
458
|
+
// snapshot only, because it becomes a pixel choice immediately.
|
|
459
|
+
const subscribeToViewport = useCallback(onStoreChange => {
|
|
460
|
+
if (hasContainer || !observeViewportBasis || typeof window === 'undefined') {
|
|
461
|
+
return () => {};
|
|
462
|
+
}
|
|
463
|
+
window.addEventListener('resize', onStoreChange);
|
|
464
|
+
return () => window.removeEventListener('resize', onStoreChange);
|
|
465
|
+
}, [hasContainer, observeViewportBasis]);
|
|
466
|
+
const viewportBasis = useSyncExternalStore(subscribeToViewport, () => typeof window === 'undefined' ? SERVER_BASIS : window.innerWidth, () => SERVER_BASIS);
|
|
467
|
+
|
|
468
|
+
// With a container, its content box is the basis; without one, the released
|
|
469
|
+
// viewport basis. Before a supplied container has been measured — the first
|
|
470
|
+
// render, the server, and any moment it is display:none or detached — the
|
|
471
|
+
// deterministic 1200px stands in, and the first real measurement replaces it.
|
|
472
|
+
//
|
|
473
|
+
// A zero measurement is not a measurement. A hidden or detached container
|
|
474
|
+
// reports 0, and resolving `'50%'` against it produced a real maximum of 0:
|
|
475
|
+
// the panel collapsed and, with `autoSaveId`, that 0 was written over a
|
|
476
|
+
// perfectly good saved width. Treating 0 as unmeasured keeps the temporary
|
|
477
|
+
// basis until the container is actually laid out (AST-010 Platform support).
|
|
478
|
+
const needsContainerBasis = observeContainerBasis;
|
|
479
|
+
const hasPositiveMeasurement = containerBasis != null && containerBasis > 0;
|
|
480
|
+
const liveBasis = hasContainer ? hasPositiveMeasurement ? containerBasis : SERVER_BASIS : viewportBasis;
|
|
481
|
+
const isBasisMeasured = needsContainerBasis ? hasPositiveMeasurement : true;
|
|
154
482
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
const
|
|
483
|
+
// One stable basis per gesture. A container that resizes mid-drag would
|
|
484
|
+
// otherwise move the bound under the pointer; the new basis applies once the
|
|
485
|
+
// gesture ends.
|
|
486
|
+
const gestureBasisRef = useRef(null);
|
|
487
|
+
// Only forces the render that releases the frozen basis when a gesture ends.
|
|
488
|
+
const [gestureTick, setGestureTick] = useState(0);
|
|
489
|
+
void gestureTick;
|
|
490
|
+
const basis = gestureBasisRef.current ?? liveBasis;
|
|
491
|
+
const didWarnMinRef = useRef(false);
|
|
492
|
+
const didWarnMaxRef = useRef(false);
|
|
493
|
+
const didWarnDefaultRef = useRef(false);
|
|
494
|
+
const didWarnInvertedRef = useRef(false);
|
|
495
|
+
const didWarnMinAliasRef = useRef(false);
|
|
496
|
+
const didWarnMaxAliasRef = useRef(false);
|
|
497
|
+
|
|
498
|
+
// Bounds re-resolve from the live basis on every render — that is the whole
|
|
499
|
+
// point of a percentage bound — and clamp the pixel selection below.
|
|
500
|
+
const resolvedMin = resolveBound(rawMin, parsedMin, basis, DEFAULT_MIN, hasUnifiedMin ? 'minSize' : 'minSizePx', hasUnifiedMin ? RESIZABLE_SIZE_GUIDANCE : LEGACY_PIXEL_GUIDANCE, didWarnMinRef);
|
|
501
|
+
const resolvedMax = resolveBound(rawMax, parsedMax, basis, Infinity, hasUnifiedMax ? 'maxSize' : 'maxSizePx', hasUnifiedMax ? RESIZABLE_SIZE_GUIDANCE : LEGACY_MAX_PIXEL_GUIDANCE, didWarnMaxRef);
|
|
502
|
+
|
|
503
|
+
// A basis-dependent default is resolved ONCE into a pixel size. Latched in a
|
|
504
|
+
// ref (React's sanctioned lazy-init shape) rather than recomputed, because a
|
|
505
|
+
// default that kept tracking its basis would be a second, responsive sizing
|
|
506
|
+
// mode — the thing this API deliberately does not have.
|
|
507
|
+
//
|
|
508
|
+
// Not final while a supplied container is still unmeasured: that render used
|
|
509
|
+
// the temporary 1200px stand-in, and the first real measurement replaces it.
|
|
510
|
+
// Once final, a default-only subscription is removed. The correction is not a
|
|
511
|
+
// user interaction, so it neither persists the placeholder nor fires
|
|
512
|
+
// `onSizeChange`.
|
|
513
|
+
if (initialDefaultRef.current == null || !initialDefaultRef.current.isFinal) {
|
|
514
|
+
initialDefaultRef.current = {
|
|
515
|
+
px: resolveBound(defaultSize, parsedDefault, basis, DEFAULT_SIZE_FALLBACK, 'defaultSize', RESIZABLE_SIZE_GUIDANCE, didWarnDefaultRef),
|
|
516
|
+
isFinal: isBasisMeasured
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const initialDefaultPx = initialDefaultRef.current.px;
|
|
520
|
+
|
|
521
|
+
// The size the user has settled on, or null while a container-relative
|
|
522
|
+
// default is still waiting for its first positive measurement. Once that
|
|
523
|
+
// basis is real, the default becomes an ordinary pixel selection — including
|
|
524
|
+
// any clamp applied at initialization. Leaving it represented by `null`
|
|
525
|
+
// allowed the raw default to be re-read later: 321px first clamped to 200px
|
|
526
|
+
// in a 400px container, then revived to 321px when the container grew.
|
|
527
|
+
//
|
|
528
|
+
// Initialized outright whenever the basis is already real on the first
|
|
529
|
+
// render — every pixel-only configuration, and the no-container percentage
|
|
530
|
+
// path. Only a supplied container needs a measurement that does not exist
|
|
531
|
+
// until after commit, so only it starts null. Initializing lazily in all
|
|
532
|
+
// cases cost every legacy pixel-only mount a second render pass, because the
|
|
533
|
+
// adjustment below then had to run on the first render to fill the value in.
|
|
534
|
+
const [chosenSize, setChosenSize] = useState(() => {
|
|
535
|
+
if (persisted?.size != null) {
|
|
536
|
+
return persisted.size;
|
|
537
|
+
}
|
|
538
|
+
return isBasisMeasured ? clampSize(initialDefaultPx, resolvedMin, resolvedMax, snaps) : null;
|
|
539
|
+
});
|
|
540
|
+
if (hasContainer && !boundsDependOnBasis && isBasisMeasured && defaultNeedsInitialBasis && chosenSize != null) {
|
|
541
|
+
// A programmatic resize may have selected pixels while the container was
|
|
542
|
+
// still unmeasured, so chosenSize can already be non-null here. Force the
|
|
543
|
+
// render that re-evaluates and removes the now-finished default subscription.
|
|
544
|
+
setDefaultBasisCleanupTick(tick => tick + 1);
|
|
545
|
+
}
|
|
546
|
+
if (isBasisMeasured && chosenSize == null) {
|
|
547
|
+
// The supplied container has now been measured, so the selection latched
|
|
548
|
+
// against the temporary basis is replaced by one against the real one.
|
|
549
|
+
// Render-phase adjustment is deliberate: React re-runs this render before
|
|
550
|
+
// paint, so the corrected selection, paint, persistence, and ARIA all see
|
|
551
|
+
// the same pixel value. It fires no interaction callback.
|
|
552
|
+
setChosenSize(clampSize(initialDefaultPx, resolvedMin, resolvedMax, snaps));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// A re-resolved bound clamps the SELECTION, not just the paint. Deriving
|
|
556
|
+
// alone was not enough: the stored choice survived, so a container that
|
|
557
|
+
// shrank and grew again revived the pre-clamp size — measured 320px
|
|
558
|
+
// reviving to 560px. The clamp has to be committed to be permanent.
|
|
559
|
+
//
|
|
560
|
+
// Adjusting state during render (React's documented shape for "a prop
|
|
561
|
+
// changed, so state must follow") rather than in an effect: React re-runs
|
|
562
|
+
// this render before painting, so nothing shows at the stale size, and no
|
|
563
|
+
// interaction callback fires for what is only a layout correction (FR10).
|
|
564
|
+
//
|
|
565
|
+
// Held until the basis is final. Before a supplied container is measured the
|
|
566
|
+
// bounds come from the temporary 1200px stand-in, and committing against
|
|
567
|
+
// that would move a persisted size to fit a basis that was never real.
|
|
568
|
+
const [committedBounds, setCommittedBounds] = useState({
|
|
569
|
+
min: resolvedMin,
|
|
570
|
+
max: resolvedMax
|
|
571
|
+
});
|
|
572
|
+
if (isBasisMeasured && gestureBasisRef.current == null && (committedBounds.min !== resolvedMin || committedBounds.max !== resolvedMax)) {
|
|
573
|
+
setCommittedBounds({
|
|
574
|
+
min: resolvedMin,
|
|
575
|
+
max: resolvedMax
|
|
576
|
+
});
|
|
577
|
+
setChosenSize(current => current == null ? null : clampSize(current, resolvedMin, resolvedMax, snaps));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Derived for paint, so the clamp above and the rendered size agree on the
|
|
581
|
+
// very render that observes a new basis.
|
|
582
|
+
const size = clampSize(chosenSize ?? initialDefaultPx, resolvedMin, resolvedMax, snaps);
|
|
159
583
|
const [uncontrolledIsCollapsed, setUncontrolledIsCollapsed] = useState(() => persisted?.isCollapsed ?? defaultIsCollapsed ?? false);
|
|
160
584
|
const isControlled = controlledIsCollapsed !== undefined;
|
|
161
585
|
const isCollapsed = collapsible && (isControlled ? controlledIsCollapsed : uncontrolledIsCollapsed);
|
|
@@ -177,14 +601,37 @@ function useSingleResizable(config) {
|
|
|
177
601
|
setUncontrolledIsCollapsed(value);
|
|
178
602
|
}
|
|
179
603
|
}, [isControlled]);
|
|
604
|
+
|
|
605
|
+
// Configuration warnings live in an effect, not in render: render runs twice
|
|
606
|
+
// under StrictMode and must stay free of side effects.
|
|
180
607
|
useEffect(() => {
|
|
181
|
-
if (
|
|
608
|
+
if (minConflict && !didWarnMinAliasRef.current) {
|
|
609
|
+
didWarnMinAliasRef.current = true;
|
|
610
|
+
devWarn('useResizable', 'both `minSize` and `minSizePx` were supplied. `minSize` wins; the ' + 'deprecated `minSizePx` is ignored.');
|
|
611
|
+
}
|
|
612
|
+
if (maxConflict && !didWarnMaxAliasRef.current) {
|
|
613
|
+
didWarnMaxAliasRef.current = true;
|
|
614
|
+
devWarn('useResizable', 'both `maxSize` and `maxSizePx` were supplied. `maxSize` wins; the ' + 'deprecated `maxSizePx` is ignored.');
|
|
615
|
+
}
|
|
616
|
+
}, [minConflict, maxConflict]);
|
|
617
|
+
useEffect(() => {
|
|
618
|
+
if (resolvedMin > resolvedMax && !didWarnInvertedRef.current) {
|
|
619
|
+
didWarnInvertedRef.current = true;
|
|
620
|
+
devWarn('useResizable', `the resolved minimum (${resolvedMin}px) is above the resolved ` + `maximum (${resolvedMax}px). The maximum wins, as it always has.`);
|
|
621
|
+
}
|
|
622
|
+
}, [resolvedMin, resolvedMax]);
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
// Nothing is written while the basis is still the temporary stand-in. The
|
|
625
|
+
// size on such a render is a placeholder, and persisting it overwrites the
|
|
626
|
+
// real saved size with a value derived from a basis that was never real
|
|
627
|
+
// (AST-010 Platform support, FR9).
|
|
628
|
+
if (autoSaveId && isBasisMeasured) {
|
|
182
629
|
persistState(autoSaveId, {
|
|
183
630
|
size,
|
|
184
631
|
isCollapsed
|
|
185
632
|
});
|
|
186
633
|
}
|
|
187
|
-
}, [size, isCollapsed, autoSaveId]);
|
|
634
|
+
}, [size, isCollapsed, autoSaveId, isBasisMeasured]);
|
|
188
635
|
const collapse = useCallback(() => {
|
|
189
636
|
// The already-collapsed guard keeps a repeated call from re-notifying a
|
|
190
637
|
// transition that already happened.
|
|
@@ -204,9 +651,16 @@ function useSingleResizable(config) {
|
|
|
204
651
|
onSizeChange?.(size);
|
|
205
652
|
}, [setCollapsed, size, onCollapseChange, onSizeChange]);
|
|
206
653
|
const resize = useCallback(newSize => {
|
|
207
|
-
|
|
654
|
+
// An invalid programmatic size repairs nothing — it must not replace a
|
|
655
|
+
// legal selection with NaN, which would then reach paint, storage and
|
|
656
|
+
// `aria-valuenow`.
|
|
657
|
+
if (!Number.isFinite(newSize) || newSize < 0) {
|
|
658
|
+
devWarn('useResizable', `resize(${String(newSize)}) is not a pixel size. Keeping the ` + 'current size. Percentages configure the hook; they are not a ' + 'programmatic input.');
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const clamped = clampSize(newSize, resolvedMin, resolvedMax, snaps);
|
|
208
662
|
const wasCollapsed = isCollapsedRef.current;
|
|
209
|
-
|
|
663
|
+
setChosenSize(clamped);
|
|
210
664
|
setCollapsed(false);
|
|
211
665
|
// Resizing out of the collapsed state is an implicit expand — notify
|
|
212
666
|
// like the drag path does when it crosses back over the threshold.
|
|
@@ -214,10 +668,13 @@ function useSingleResizable(config) {
|
|
|
214
668
|
onCollapseChange?.(false);
|
|
215
669
|
}
|
|
216
670
|
onSizeChange?.(clamped);
|
|
217
|
-
}, [
|
|
671
|
+
}, [resolvedMin, resolvedMax, snaps, setCollapsed, onCollapseChange, onSizeChange]);
|
|
218
672
|
const onResizeStart = useCallback(() => {
|
|
673
|
+
// Freeze the basis for the whole gesture so a container resizing mid-drag
|
|
674
|
+
// cannot move the bound out from under the pointer.
|
|
675
|
+
gestureBasisRef.current = liveBasis;
|
|
219
676
|
dragStartSizeRef.current = isCollapsedRef.current ? 0 : size;
|
|
220
|
-
}, [size]);
|
|
677
|
+
}, [size, liveBasis]);
|
|
221
678
|
const onResizeMove = useCallback(delta => {
|
|
222
679
|
const raw = dragStartSizeRef.current + delta;
|
|
223
680
|
if (collapsible && raw < collapsedSize) {
|
|
@@ -232,24 +689,43 @@ function useSingleResizable(config) {
|
|
|
232
689
|
setCollapsed(false);
|
|
233
690
|
onCollapseChange?.(false);
|
|
234
691
|
}
|
|
235
|
-
const clamped = clampSize(raw,
|
|
236
|
-
|
|
692
|
+
const clamped = clampSize(raw, resolvedMin, resolvedMax, snaps);
|
|
693
|
+
setChosenSize(clamped);
|
|
237
694
|
onSizeChange?.(clamped);
|
|
238
|
-
}, [collapsible, collapsedSize, setCollapsed,
|
|
239
|
-
|
|
240
|
-
|
|
695
|
+
}, [collapsible, collapsedSize, setCollapsed, resolvedMin, resolvedMax, snaps, onSizeChange, onCollapseChange]);
|
|
696
|
+
|
|
697
|
+
// Releasing the frozen basis needs a render to apply whatever the basis
|
|
698
|
+
// became during the gesture.
|
|
699
|
+
const releaseGestureBasis = useCallback(() => {
|
|
700
|
+
if (gestureBasisRef.current != null) {
|
|
701
|
+
gestureBasisRef.current = null;
|
|
702
|
+
setGestureTick(tick => tick + 1);
|
|
703
|
+
}
|
|
241
704
|
}, []);
|
|
705
|
+
const onResizeEnd = useCallback(() => {
|
|
706
|
+
// Sizes are already committed during move; only the basis is held.
|
|
707
|
+
releaseGestureBasis();
|
|
708
|
+
}, [releaseGestureBasis]);
|
|
709
|
+
|
|
710
|
+
// A gesture that is cancelled rather than completed — pointercancel, a lost
|
|
711
|
+
// capture, a handle unmounted mid-drag — is still over. It does not signal a
|
|
712
|
+
// resize end (that is #5297's contract, and the sizes were committed during
|
|
713
|
+
// move anyway), but the basis it froze has to come back, or every later
|
|
714
|
+
// percentage bound stays pinned to the container size at grab time.
|
|
715
|
+
const onResizeCancel = releaseGestureBasis;
|
|
242
716
|
const props = {
|
|
243
717
|
_size: isCollapsed ? 0 : size,
|
|
244
718
|
_isCollapsed: isCollapsed,
|
|
245
719
|
_onResizeStart: onResizeStart,
|
|
246
720
|
_onResizeMove: onResizeMove,
|
|
247
721
|
_onResizeEnd: onResizeEnd,
|
|
248
|
-
|
|
249
|
-
|
|
722
|
+
_onResizeCancel: onResizeCancel,
|
|
723
|
+
_minSizePx: resolvedMin,
|
|
724
|
+
_maxSizePx: resolvedMax,
|
|
250
725
|
_snaps: snaps,
|
|
251
726
|
_collapsedSize: collapsedSize,
|
|
252
727
|
_collapsible: collapsible,
|
|
728
|
+
_direction: direction,
|
|
253
729
|
_isResizableProps: true
|
|
254
730
|
};
|
|
255
731
|
return {
|
|
@@ -275,7 +751,9 @@ function useSingleResizable(config) {
|
|
|
275
751
|
function useMultiResizable(config) {
|
|
276
752
|
const {
|
|
277
753
|
regions,
|
|
278
|
-
autoSaveId
|
|
754
|
+
autoSaveId,
|
|
755
|
+
containerRef,
|
|
756
|
+
direction
|
|
279
757
|
} = config;
|
|
280
758
|
|
|
281
759
|
// Stable key order — callers must not change region keys between renders.
|
|
@@ -288,6 +766,11 @@ function useMultiResizable(config) {
|
|
|
288
766
|
// eslint-disable-next-line @eslint-react/rules-of-hooks -- region count is stable (documented contract)
|
|
289
767
|
useSingleResizable({
|
|
290
768
|
...regionConfig,
|
|
769
|
+
// One container and one axis for every region (AST-010 FR11). Regions
|
|
770
|
+
// stay independently pixel-selected; sharing a basis introduces no ratio
|
|
771
|
+
// between them and no 100% total.
|
|
772
|
+
containerRef,
|
|
773
|
+
direction,
|
|
291
774
|
autoSaveId: autoSaveId ? `${autoSaveId}:${key}` : undefined
|
|
292
775
|
}));
|
|
293
776
|
const result = {};
|