@multiplatform.one/components 7.10.0 → 7.15.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/README.md +100 -2
- package/dist/cjs/Accordion/index.cjs +2 -6
- package/dist/cjs/Accordion/index.native.js +2 -6
- package/dist/cjs/Accordion/index.native.js.map +1 -1
- package/dist/cjs/AnimateHeight/index.cjs +1 -3
- package/dist/cjs/AnimateHeight/index.native.js +1 -3
- package/dist/cjs/AnimateHeight/index.native.js.map +1 -1
- package/dist/cjs/Chip/index.cjs +8 -1
- package/dist/cjs/Chip/index.native.js +8 -1
- package/dist/cjs/Chip/index.native.js.map +1 -1
- package/dist/cjs/SheetModal/index.cjs +22 -3
- package/dist/cjs/SheetModal/index.native.js +115 -27
- package/dist/cjs/SheetModal/index.native.js.map +1 -1
- package/dist/cjs/Tabs/index.cjs +35 -18
- package/dist/cjs/Tabs/index.native.js +39 -18
- package/dist/cjs/Tabs/index.native.js.map +1 -1
- package/dist/cjs/Text.cjs +2 -0
- package/dist/cjs/Text.native.js +2 -0
- package/dist/cjs/Text.native.js.map +1 -1
- package/dist/cjs/TreeSelect/index.cjs +55 -52
- package/dist/cjs/TreeSelect/index.native.js +55 -52
- package/dist/cjs/TreeSelect/index.native.js.map +1 -1
- package/dist/cjs/Video/PlayerChrome.cjs +3 -9
- package/dist/cjs/Video/PlayerChrome.native.js +3 -9
- package/dist/cjs/Video/PlayerChrome.native.js.map +1 -1
- package/dist/cjs/code/Code/index.cjs +6 -0
- package/dist/cjs/code/Code/index.native.js +7 -4
- package/dist/cjs/code/Code/index.native.js.map +1 -1
- package/dist/cjs/code/CodeBlock/index.cjs +4 -5
- package/dist/cjs/code/CodeBlock/index.native.js +4 -5
- package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
- package/dist/cjs/icons/themed.cjs +2 -1
- package/dist/cjs/icons/themed.native.js +2 -1
- package/dist/cjs/icons/themed.native.js.map +1 -1
- package/dist/cjs/knobImmuneExports.cjs +3 -3
- package/dist/cjs/knobImmuneExports.native.js +3 -3
- package/dist/cjs/layouts/PaneScaffold.cjs +70 -42
- package/dist/cjs/layouts/PaneScaffold.native.js +75 -48
- package/dist/cjs/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/cjs/layouts/page.cjs +2 -1
- package/dist/cjs/layouts/page.native.js +2 -1
- package/dist/cjs/layouts/page.native.js.map +1 -1
- package/dist/cjs/layouts/sidebar.cjs +1 -3
- package/dist/cjs/layouts/sidebar.native.js +1 -3
- package/dist/cjs/layouts/sidebar.native.js.map +1 -1
- package/dist/cjs/layouts/tags.native.js.map +1 -1
- package/dist/cjs/surfaces.cjs +0 -32
- package/dist/cjs/surfaces.native.js +0 -32
- package/dist/cjs/surfaces.native.js.map +1 -1
- package/dist/cjs/views/List.cjs +1 -3
- package/dist/cjs/views/List.native.js +1 -3
- package/dist/cjs/views/List.native.js.map +1 -1
- package/dist/cjs/views/Matrix.cjs +568 -0
- package/dist/cjs/views/Matrix.native.js +612 -0
- package/dist/cjs/views/Matrix.native.js.map +1 -0
- package/dist/cjs/views/TreeView.cjs +175 -43
- package/dist/cjs/views/TreeView.native.js +181 -46
- package/dist/cjs/views/TreeView.native.js.map +1 -1
- package/dist/cjs/views/ganttMath.cjs +2 -0
- package/dist/cjs/views/ganttMath.native.js +2 -0
- package/dist/cjs/views/ganttMath.native.js.map +1 -1
- package/dist/cjs/views/index.cjs +16 -0
- package/dist/cjs/views/index.native.js +16 -0
- package/dist/cjs/views/index.native.js.map +1 -1
- package/dist/cjs/views/matrixMath.cjs +157 -0
- package/dist/cjs/views/matrixMath.native.js +229 -0
- package/dist/cjs/views/matrixMath.native.js.map +1 -0
- package/dist/esm/Accordion/index.mjs +3 -7
- package/dist/esm/Accordion/index.mjs.map +1 -1
- package/dist/esm/Accordion/index.native.js +3 -7
- package/dist/esm/Accordion/index.native.js.map +1 -1
- package/dist/esm/AnimateHeight/index.mjs +2 -4
- package/dist/esm/AnimateHeight/index.mjs.map +1 -1
- package/dist/esm/AnimateHeight/index.native.js +2 -4
- package/dist/esm/AnimateHeight/index.native.js.map +1 -1
- package/dist/esm/Avatar/getInitials.native.js.map +1 -1
- package/dist/esm/Avatar/index.native.js.map +1 -1
- package/dist/esm/Badge/index.native.js.map +1 -1
- package/dist/esm/Button.native.js.map +1 -1
- package/dist/esm/Carousel/index.native.js.map +1 -1
- package/dist/esm/Chip/index.mjs +9 -2
- package/dist/esm/Chip/index.mjs.map +1 -1
- package/dist/esm/Chip/index.native.js +9 -2
- package/dist/esm/Chip/index.native.js.map +1 -1
- package/dist/esm/Collapsible.native.js.map +1 -1
- package/dist/esm/ContextMenu/index.native.js.map +1 -1
- package/dist/esm/CopyField/index.native.js.map +1 -1
- package/dist/esm/DotIndicator/index.native.js.map +1 -1
- package/dist/esm/DropdownMenu/index.native.js.map +1 -1
- package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
- package/dist/esm/Heading.native.js.map +1 -1
- package/dist/esm/Meter/index.native.js.map +1 -1
- package/dist/esm/Meter/meter.native.js.map +1 -1
- package/dist/esm/Pagination.native.js.map +1 -1
- package/dist/esm/ProgressSteps/index.native.js.map +1 -1
- package/dist/esm/Quote/index.native.js.map +1 -1
- package/dist/esm/Separator/index.native.js.map +1 -1
- package/dist/esm/SheetModal/detents.native.js.map +1 -1
- package/dist/esm/SheetModal/index.mjs +22 -3
- package/dist/esm/SheetModal/index.mjs.map +1 -1
- package/dist/esm/SheetModal/index.native.js +117 -29
- package/dist/esm/SheetModal/index.native.js.map +1 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/SkipLink/index.native.js.map +1 -1
- package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
- package/dist/esm/SortableList/index.native.js.map +1 -1
- package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
- package/dist/esm/Tabs/index.mjs +36 -19
- package/dist/esm/Tabs/index.mjs.map +1 -1
- package/dist/esm/Tabs/index.native.js +40 -19
- package/dist/esm/Tabs/index.native.js.map +1 -1
- package/dist/esm/Text.mjs +3 -1
- package/dist/esm/Text.mjs.map +1 -1
- package/dist/esm/Text.native.js +3 -1
- package/dist/esm/Text.native.js.map +1 -1
- package/dist/esm/Toast/index.native.js.map +1 -1
- package/dist/esm/Toast/store.native.js.map +1 -1
- package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
- package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
- package/dist/esm/TreeSelect/index.mjs +56 -53
- package/dist/esm/TreeSelect/index.mjs.map +1 -1
- package/dist/esm/TreeSelect/index.native.js +56 -53
- package/dist/esm/TreeSelect/index.native.js.map +1 -1
- package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
- package/dist/esm/Video/PlayerChrome.mjs +4 -10
- package/dist/esm/Video/PlayerChrome.mjs.map +1 -1
- package/dist/esm/Video/PlayerChrome.native.js +4 -10
- package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
- package/dist/esm/Video/VideoFrame.native.js.map +1 -1
- package/dist/esm/Video/types.native.js.map +1 -1
- package/dist/esm/Wheel/index.native.js.map +1 -1
- package/dist/esm/Wizard/index.native.js.map +1 -1
- package/dist/esm/actions/ActionBar.native.js.map +1 -1
- package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
- package/dist/esm/actions/devWarn.native.js.map +1 -1
- package/dist/esm/actions/index.native.js.map +1 -1
- package/dist/esm/actions/severity.native.js.map +1 -1
- package/dist/esm/charts/Axis.native.js.map +1 -1
- package/dist/esm/charts/BarChart.native.js.map +1 -1
- package/dist/esm/charts/ChartDataView.native.js.map +1 -1
- package/dist/esm/charts/ChartSurface.native.js.map +1 -1
- package/dist/esm/charts/LineChart.native.js.map +1 -1
- package/dist/esm/charts/PieChart.native.js.map +1 -1
- package/dist/esm/charts/Sparkline.native.js.map +1 -1
- package/dist/esm/charts/accessibility.native.js.map +1 -1
- package/dist/esm/charts/composeChart.native.js.map +1 -1
- package/dist/esm/charts/index.native.js.map +1 -1
- package/dist/esm/charts/math.native.js.map +1 -1
- package/dist/esm/charts/series.native.js.map +1 -1
- package/dist/esm/charts/svg.native.js.map +1 -1
- package/dist/esm/charts/types.native.js.map +1 -1
- package/dist/esm/charts/useChartTheme.native.js.map +1 -1
- package/dist/esm/code/Code/index.mjs +6 -0
- package/dist/esm/code/Code/index.mjs.map +1 -1
- package/dist/esm/code/Code/index.native.js +10 -7
- package/dist/esm/code/Code/index.native.js.map +1 -1
- package/dist/esm/code/CodeBlock/index.mjs +5 -6
- package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
- package/dist/esm/code/CodeBlock/index.native.js +5 -6
- package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
- package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
- package/dist/esm/code/Highlighter/index.native.js.map +1 -1
- package/dist/esm/code/Pre/index.native.js.map +1 -1
- package/dist/esm/code/index.native.js.map +1 -1
- package/dist/esm/componentColors.native.js.map +1 -1
- package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
- package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
- package/dist/esm/feedback/copy.native.js.map +1 -1
- package/dist/esm/feedback/index.native.js.map +1 -1
- package/dist/esm/feedback/notify.native.js.map +1 -1
- package/dist/esm/feedback/route.native.js.map +1 -1
- package/dist/esm/feedback/store.native.js.map +1 -1
- package/dist/esm/hooks/index.native.js.map +1 -1
- package/dist/esm/hooks/useClipboard.native.js.map +1 -1
- package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
- package/dist/esm/icons/CheckRegular.native.js.map +1 -1
- package/dist/esm/icons/MinusRegular.native.js.map +1 -1
- package/dist/esm/icons/index.native.js.map +1 -1
- package/dist/esm/icons/lucide.native.js.map +1 -1
- package/dist/esm/icons/themed.mjs +3 -2
- package/dist/esm/icons/themed.mjs.map +1 -1
- package/dist/esm/icons/themed.native.js +3 -2
- package/dist/esm/icons/themed.native.js.map +1 -1
- package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
- package/dist/esm/images/Image/shared.native.js.map +1 -1
- package/dist/esm/images/Image/types.native.js.map +1 -1
- package/dist/esm/images/index.native.js.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/knobImmuneExports.mjs +3 -3
- package/dist/esm/knobImmuneExports.native.js +3 -3
- package/dist/esm/knobImmuneExports.native.js.map +1 -1
- package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
- package/dist/esm/layouts/Debug/index.native.js.map +1 -1
- package/dist/esm/layouts/EmptyState.native.js.map +1 -1
- package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
- package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
- package/dist/esm/layouts/PaneScaffold.mjs +70 -42
- package/dist/esm/layouts/PaneScaffold.mjs.map +1 -1
- package/dist/esm/layouts/PaneScaffold.native.js +75 -48
- package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
- package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
- package/dist/esm/layouts/attachments.native.js.map +1 -1
- package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
- package/dist/esm/layouts/index.native.js.map +1 -1
- package/dist/esm/layouts/page.mjs +3 -2
- package/dist/esm/layouts/page.mjs.map +1 -1
- package/dist/esm/layouts/page.native.js +3 -2
- package/dist/esm/layouts/page.native.js.map +1 -1
- package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
- package/dist/esm/layouts/sidebar.mjs +2 -4
- package/dist/esm/layouts/sidebar.mjs.map +1 -1
- package/dist/esm/layouts/sidebar.native.js +2 -4
- package/dist/esm/layouts/sidebar.native.js.map +1 -1
- package/dist/esm/layouts/tags.mjs.map +1 -1
- package/dist/esm/layouts/tags.native.js.map +1 -1
- package/dist/esm/layouts/timeline.native.js.map +1 -1
- package/dist/esm/layouts/toolbar.native.js.map +1 -1
- package/dist/esm/shared/actionScan.native.js.map +1 -1
- package/dist/esm/shared/i18n.native.js.map +1 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/esm/shared/textRidesText.native.js.map +1 -1
- package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/esm/surfaces.mjs +1 -33
- package/dist/esm/surfaces.mjs.map +1 -1
- package/dist/esm/surfaces.native.js +1 -33
- package/dist/esm/surfaces.native.js.map +1 -1
- package/dist/esm/tamagui.native.js.map +1 -1
- package/dist/esm/testSetup.native.js.map +1 -1
- package/dist/esm/utils/setTinted.native.js.map +1 -1
- package/dist/esm/utils/unwrapText.native.js.map +1 -1
- package/dist/esm/views/Calendar.native.js.map +1 -1
- package/dist/esm/views/Dashboard.native.js.map +1 -1
- package/dist/esm/views/Gantt.native.js.map +1 -1
- package/dist/esm/views/ImageGrid.native.js.map +1 -1
- package/dist/esm/views/List.mjs +2 -4
- package/dist/esm/views/List.mjs.map +1 -1
- package/dist/esm/views/List.native.js +2 -4
- package/dist/esm/views/List.native.js.map +1 -1
- package/dist/esm/views/Matrix.mjs +543 -0
- package/dist/esm/views/Matrix.mjs.map +1 -0
- package/dist/esm/views/Matrix.native.js +573 -0
- package/dist/esm/views/Matrix.native.js.map +1 -0
- package/dist/esm/views/ReportBuilder.native.js.map +1 -1
- package/dist/esm/views/TreeView.mjs +175 -43
- package/dist/esm/views/TreeView.mjs.map +1 -1
- package/dist/esm/views/TreeView.native.js +181 -46
- package/dist/esm/views/TreeView.native.js.map +1 -1
- package/dist/esm/views/calendarMath.native.js.map +1 -1
- package/dist/esm/views/ganttMath.mjs +1 -1
- package/dist/esm/views/ganttMath.mjs.map +1 -1
- package/dist/esm/views/ganttMath.native.js +1 -1
- package/dist/esm/views/ganttMath.native.js.map +1 -1
- package/dist/esm/views/index.mjs +4 -2
- package/dist/esm/views/index.mjs.map +1 -1
- package/dist/esm/views/index.native.js +4 -2
- package/dist/esm/views/index.native.js.map +1 -1
- package/dist/esm/views/kanbanDnd.native.js.map +1 -1
- package/dist/esm/views/matrixMath.mjs +122 -0
- package/dist/esm/views/matrixMath.mjs.map +1 -0
- package/dist/esm/views/matrixMath.native.js +191 -0
- package/dist/esm/views/matrixMath.native.js.map +1 -0
- package/dist/esm/views/treeViewDnd.native.js.map +1 -1
- package/dist/esm/views/treeViewLayout.native.js.map +1 -1
- package/dist/esm/views/treeViewMotion.native.js.map +1 -1
- package/dist/esm/views/treeViewReveal.native.js.map +1 -1
- package/dist/jsx/Accordion/index.mjs +3 -7
- package/dist/jsx/Accordion/index.mjs.map +1 -1
- package/dist/jsx/Accordion/index.native.js +2 -6
- package/dist/jsx/Accordion/index.native.js.map +1 -1
- package/dist/jsx/AnimateHeight/index.mjs +2 -4
- package/dist/jsx/AnimateHeight/index.mjs.map +1 -1
- package/dist/jsx/AnimateHeight/index.native.js +1 -3
- package/dist/jsx/AnimateHeight/index.native.js.map +1 -1
- package/dist/jsx/Chip/index.mjs +9 -2
- package/dist/jsx/Chip/index.mjs.map +1 -1
- package/dist/jsx/Chip/index.native.js +8 -1
- package/dist/jsx/Chip/index.native.js.map +1 -1
- package/dist/jsx/SheetModal/index.mjs +22 -3
- package/dist/jsx/SheetModal/index.mjs.map +1 -1
- package/dist/jsx/SheetModal/index.native.js +115 -27
- package/dist/jsx/SheetModal/index.native.js.map +1 -1
- package/dist/jsx/Tabs/index.mjs +36 -19
- package/dist/jsx/Tabs/index.mjs.map +1 -1
- package/dist/jsx/Tabs/index.native.js +39 -18
- package/dist/jsx/Tabs/index.native.js.map +1 -1
- package/dist/jsx/Text.mjs +3 -1
- package/dist/jsx/Text.mjs.map +1 -1
- package/dist/jsx/Text.native.js +2 -0
- package/dist/jsx/Text.native.js.map +1 -1
- package/dist/jsx/TreeSelect/index.mjs +56 -53
- package/dist/jsx/TreeSelect/index.mjs.map +1 -1
- package/dist/jsx/TreeSelect/index.native.js +55 -52
- package/dist/jsx/TreeSelect/index.native.js.map +1 -1
- package/dist/jsx/Video/PlayerChrome.mjs +4 -10
- package/dist/jsx/Video/PlayerChrome.mjs.map +1 -1
- package/dist/jsx/Video/PlayerChrome.native.js +3 -9
- package/dist/jsx/Video/PlayerChrome.native.js.map +1 -1
- package/dist/jsx/code/Code/index.mjs +6 -0
- package/dist/jsx/code/Code/index.mjs.map +1 -1
- package/dist/jsx/code/Code/index.native.js +7 -4
- package/dist/jsx/code/Code/index.native.js.map +1 -1
- package/dist/jsx/code/CodeBlock/index.mjs +5 -6
- package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
- package/dist/jsx/code/CodeBlock/index.native.js +4 -5
- package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
- package/dist/jsx/icons/themed.mjs +3 -2
- package/dist/jsx/icons/themed.mjs.map +1 -1
- package/dist/jsx/icons/themed.native.js +2 -1
- package/dist/jsx/icons/themed.native.js.map +1 -1
- package/dist/jsx/knobImmuneExports.mjs +3 -3
- package/dist/jsx/knobImmuneExports.native.js +3 -3
- package/dist/jsx/layouts/PaneScaffold.mjs +70 -42
- package/dist/jsx/layouts/PaneScaffold.mjs.map +1 -1
- package/dist/jsx/layouts/PaneScaffold.native.js +75 -48
- package/dist/jsx/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/jsx/layouts/page.mjs +3 -2
- package/dist/jsx/layouts/page.mjs.map +1 -1
- package/dist/jsx/layouts/page.native.js +2 -1
- package/dist/jsx/layouts/page.native.js.map +1 -1
- package/dist/jsx/layouts/sidebar.mjs +2 -4
- package/dist/jsx/layouts/sidebar.mjs.map +1 -1
- package/dist/jsx/layouts/sidebar.native.js +1 -3
- package/dist/jsx/layouts/sidebar.native.js.map +1 -1
- package/dist/jsx/layouts/tags.mjs.map +1 -1
- package/dist/jsx/layouts/tags.native.js.map +1 -1
- package/dist/jsx/surfaces.mjs +1 -33
- package/dist/jsx/surfaces.mjs.map +1 -1
- package/dist/jsx/surfaces.native.js +0 -32
- package/dist/jsx/surfaces.native.js.map +1 -1
- package/dist/jsx/views/List.mjs +2 -4
- package/dist/jsx/views/List.mjs.map +1 -1
- package/dist/jsx/views/List.native.js +1 -3
- package/dist/jsx/views/List.native.js.map +1 -1
- package/dist/jsx/views/Matrix.mjs +543 -0
- package/dist/jsx/views/Matrix.mjs.map +1 -0
- package/dist/jsx/views/Matrix.native.js +612 -0
- package/dist/jsx/views/Matrix.native.js.map +1 -0
- package/dist/jsx/views/TreeView.mjs +175 -43
- package/dist/jsx/views/TreeView.mjs.map +1 -1
- package/dist/jsx/views/TreeView.native.js +181 -46
- package/dist/jsx/views/TreeView.native.js.map +1 -1
- package/dist/jsx/views/ganttMath.mjs +1 -1
- package/dist/jsx/views/ganttMath.mjs.map +1 -1
- package/dist/jsx/views/ganttMath.native.js +2 -0
- package/dist/jsx/views/ganttMath.native.js.map +1 -1
- package/dist/jsx/views/index.mjs +4 -2
- package/dist/jsx/views/index.mjs.map +1 -1
- package/dist/jsx/views/index.native.js +16 -0
- package/dist/jsx/views/index.native.js.map +1 -1
- package/dist/jsx/views/matrixMath.mjs +122 -0
- package/dist/jsx/views/matrixMath.mjs.map +1 -0
- package/dist/jsx/views/matrixMath.native.js +229 -0
- package/dist/jsx/views/matrixMath.native.js.map +1 -0
- package/package.json +12 -12
- package/src/Accordion/Accordion.spec.tsx +20 -0
- package/src/Accordion/index.tsx +8 -4
- package/src/AnimateHeight/index.tsx +143 -142
- package/src/Chip/Chip.borderLadder.spec.tsx +54 -0
- package/src/Chip/Chip.pressSlop.spec.tsx +57 -0
- package/src/Chip/index.tsx +15 -1
- package/src/Heading.native.spec.tsx +1 -1
- package/src/Separator/index.tsx +3 -3
- package/src/SheetModal/SheetModal.detentLayout.spec.tsx +173 -0
- package/src/SheetModal/SheetModal.fill.spec.tsx +147 -0
- package/src/SheetModal/SheetModal.ownedBody.spec.tsx +200 -0
- package/src/SheetModal/SheetModal.stories.spec.tsx +23 -2
- package/src/SheetModal/SheetModal.stories.tsx +275 -2
- package/src/SheetModal/SheetModal.touch.spec.tsx +63 -0
- package/src/SheetModal/index.native.tsx +143 -21
- package/src/SheetModal/index.tsx +40 -3
- package/src/Tabs/Tabs.hydration.spec.tsx +123 -0
- package/src/Tabs/Tabs.lifecycle.spec.tsx +123 -0
- package/src/Tabs/Tabs.measurement.spec.tsx +186 -0
- package/src/Tabs/Tabs.stories.tsx +43 -0
- package/src/Tabs/index.tsx +45 -15
- package/src/Text.spec.tsx +32 -2
- package/src/Text.tsx +7 -6
- package/src/Toast/Toast.spec.tsx +50 -0
- package/src/Tooltip/Tooltip.spec.tsx +55 -2
- package/src/TreeSelect/TreeSelect.spec.tsx +15 -15
- package/src/TreeSelect/index.tsx +53 -50
- package/src/Video/PlayerChrome.tsx +4 -3
- package/src/Wheel/Wheel.spec.tsx +4 -4
- package/src/adjacency.l1.spec.tsx +175 -0
- package/src/code/Code/code.spec.tsx +2 -19
- package/src/code/Code/index.tsx +0 -6
- package/src/code/CodeBlock/index.tsx +5 -3
- package/src/code/highlightedCode.spec.tsx +2 -2
- package/src/code/hostProps.spec.tsx +55 -0
- package/src/icons/themed.hydration.spec.tsx +60 -0
- package/src/icons/themed.tsx +3 -6
- package/src/knobImmuneExports.ts +3 -3
- package/src/layouts/PaneScaffold.spec.tsx +148 -2
- package/src/layouts/PaneScaffold.stories.tsx +33 -1
- package/src/layouts/PaneScaffold.tsx +101 -48
- package/src/layouts/page.designLaw.spec.tsx +20 -0
- package/src/layouts/page.tsx +2 -0
- package/src/layouts/sidebar.tsx +2 -3
- package/src/layouts/tags.tsx +2 -1
- package/src/overlayElevation.spec.tsx +39 -51
- package/src/surfaces.spec.tsx +13 -10
- package/src/surfaces.tsx +4 -33
- package/src/tamagui.spec.ts +4 -3
- package/src/textAccentSecondary.spec.tsx +100 -2
- package/src/views/List.tsx +2 -1
- package/src/views/Matrix.spec.tsx +339 -0
- package/src/views/Matrix.stories.spec.tsx +68 -0
- package/src/views/Matrix.stories.tsx +153 -0
- package/src/views/Matrix.tsx +738 -0
- package/src/views/TreeView.reveal.spec.tsx +7 -0
- package/src/views/TreeView.reveal.stories.tsx +134 -0
- package/src/views/TreeView.scroll-index.spec.tsx +246 -0
- package/src/views/TreeView.scroll.spec.tsx +711 -50
- package/src/views/TreeView.tsx +230 -49
- package/src/views/ganttMath.ts +4 -2
- package/src/views/index.ts +30 -0
- package/src/views/matrixMath.spec.ts +237 -0
- package/src/views/matrixMath.ts +251 -0
- package/src/views/views.spec.tsx +3 -1
- package/types/Accordion/index.d.ts.map +1 -1
- package/types/AnimateHeight/index.d.ts.map +1 -1
- package/types/Chip/index.d.ts.map +1 -1
- package/types/Separator/index.d.ts +3 -3
- package/types/SheetModal/index.d.ts +8 -1
- package/types/SheetModal/index.d.ts.map +1 -1
- package/types/SheetModal/index.native.d.ts +1 -1
- package/types/SheetModal/index.native.d.ts.map +1 -1
- package/types/Tabs/index.d.ts.map +1 -1
- package/types/Text.d.ts.map +1 -1
- package/types/TreeSelect/index.d.ts.map +1 -1
- package/types/Video/PlayerChrome.d.ts.map +1 -1
- package/types/code/Code/index.d.ts.map +1 -1
- package/types/code/CodeBlock/index.d.ts.map +1 -1
- package/types/icons/themed.d.ts.map +1 -1
- package/types/layouts/PaneScaffold.d.ts +14 -1
- package/types/layouts/PaneScaffold.d.ts.map +1 -1
- package/types/layouts/page.d.ts.map +1 -1
- package/types/layouts/sidebar.d.ts.map +1 -1
- package/types/layouts/tags.d.ts.map +1 -1
- package/types/surfaces.d.ts.map +1 -1
- package/types/views/List.d.ts.map +1 -1
- package/types/views/Matrix.d.ts +102 -0
- package/types/views/Matrix.d.ts.map +1 -0
- package/types/views/TreeView.d.ts +10 -1
- package/types/views/TreeView.d.ts.map +1 -1
- package/types/views/ganttMath.d.ts +4 -0
- package/types/views/ganttMath.d.ts.map +1 -1
- package/types/views/index.d.ts +3 -1
- package/types/views/index.d.ts.map +1 -1
- package/types/views/matrixMath.d.ts +114 -0
- package/types/views/matrixMath.d.ts.map +1 -0
package/src/SheetModal/index.tsx
CHANGED
|
@@ -25,6 +25,13 @@ export interface SheetModalProps {
|
|
|
25
25
|
snapPoint?: number;
|
|
26
26
|
/** Wrap children in the sheet's scroll container. */
|
|
27
27
|
scrollable?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Opt the body into the sheet's remaining height instead of sizing it to
|
|
30
|
+
* its content. A consumer-owned viewport (a virtualized list, a measured
|
|
31
|
+
* workspace) cannot bootstrap from an intrinsic host: it reports zero and
|
|
32
|
+
* then mounts nothing. Off by default, so existing sheets are unchanged.
|
|
33
|
+
*/
|
|
34
|
+
fill?: boolean;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
const FOCUSABLE =
|
|
@@ -32,6 +39,14 @@ const FOCUSABLE =
|
|
|
32
39
|
|
|
33
40
|
const IN_DIALOG_REGION = { inDialogRegion: true } as const;
|
|
34
41
|
|
|
42
|
+
/*
|
|
43
|
+
* MPO-299 fill: the web frame already has a definite height from the Sheet's
|
|
44
|
+
* percent snap point, so fill only opens the body chain inside it. A flex
|
|
45
|
+
* basis on the frame itself would replace that height, not extend it.
|
|
46
|
+
*/
|
|
47
|
+
const fillSlot = { flex: 1, minHeight: 0 } as const;
|
|
48
|
+
const nonShrinking = { flexShrink: 0 } as const;
|
|
49
|
+
|
|
35
50
|
/** LC-71: the sheet frame is not a control. Auto-focus must not paint a ring. */
|
|
36
51
|
const frameRingReset = {
|
|
37
52
|
outlineWidth: 0,
|
|
@@ -140,6 +155,7 @@ export function SheetModal({
|
|
|
140
155
|
header,
|
|
141
156
|
snapPoint = 60,
|
|
142
157
|
scrollable,
|
|
158
|
+
fill = false,
|
|
143
159
|
}: SheetModalProps) {
|
|
144
160
|
const { knobProps } = useResolvedKnobs();
|
|
145
161
|
const transition = knobProps.transition;
|
|
@@ -190,9 +206,30 @@ export function SheetModal({
|
|
|
190
206
|
{...frameRingReset}
|
|
191
207
|
>
|
|
192
208
|
<DialogRegionContext.Provider value={IN_DIALOG_REGION}>
|
|
193
|
-
{header ?
|
|
194
|
-
|
|
195
|
-
|
|
209
|
+
{header ? (
|
|
210
|
+
<View id={titleId} {...(fill ? nonShrinking : null)}>
|
|
211
|
+
{header}
|
|
212
|
+
</View>
|
|
213
|
+
) : null}
|
|
214
|
+
{fill ? (
|
|
215
|
+
<View testID="sheet-modal-body" {...fillSlot}>
|
|
216
|
+
{scrollable ? (
|
|
217
|
+
<Sheet.ScrollView testID="sheet-modal-scroll" {...fillSlot}>
|
|
218
|
+
{children}
|
|
219
|
+
</Sheet.ScrollView>
|
|
220
|
+
) : (
|
|
221
|
+
children
|
|
222
|
+
)}
|
|
223
|
+
</View>
|
|
224
|
+
) : scrollable ? (
|
|
225
|
+
<Sheet.ScrollView testID="sheet-modal-scroll">{children}</Sheet.ScrollView>
|
|
226
|
+
) : (
|
|
227
|
+
children
|
|
228
|
+
)}
|
|
229
|
+
<View
|
|
230
|
+
style={{ height: "env(safe-area-inset-bottom)" }}
|
|
231
|
+
{...(fill ? nonShrinking : null)}
|
|
232
|
+
/>
|
|
196
233
|
</DialogRegionContext.Provider>
|
|
197
234
|
</SheetFrame>
|
|
198
235
|
</Sheet>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* MPO-279: on touch mobile web the server cannot know the device, so it renders
|
|
4
|
+
* pointer-sized tabs. Hydration has to reproduce that markup, then adopt the
|
|
5
|
+
* touch floor; a first client render at touch size is an attribute mismatch
|
|
6
|
+
* React never patches.
|
|
7
|
+
*/
|
|
8
|
+
import { TestProviders } from "@multiplatform.one/test-utils";
|
|
9
|
+
import { act } from "react";
|
|
10
|
+
import { hydrateRoot, type Root } from "react-dom/client";
|
|
11
|
+
import { renderToString } from "react-dom/server";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { Tabs } from "./index";
|
|
14
|
+
|
|
15
|
+
const host = vi.hoisted(() => {
|
|
16
|
+
vi.stubGlobal("matchMedia", () => ({
|
|
17
|
+
matches: false,
|
|
18
|
+
addEventListener() {},
|
|
19
|
+
removeEventListener() {},
|
|
20
|
+
addListener() {},
|
|
21
|
+
removeListener() {},
|
|
22
|
+
}));
|
|
23
|
+
return { web: true, touch: false };
|
|
24
|
+
});
|
|
25
|
+
vi.mock("@multiplatform.one/platform", async (importOriginal) => ({
|
|
26
|
+
...(await importOriginal<typeof import("@multiplatform.one/platform")>()),
|
|
27
|
+
get isWeb() {
|
|
28
|
+
return host.web;
|
|
29
|
+
},
|
|
30
|
+
get isTouchable() {
|
|
31
|
+
return !host.web && host.touch;
|
|
32
|
+
},
|
|
33
|
+
get isWebTouchable() {
|
|
34
|
+
return host.web && host.touch;
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const items = [
|
|
39
|
+
{ value: "address", label: "Address & Contact", content: <>address panel</> },
|
|
40
|
+
{ value: "settings", label: "Settings", content: <>settings panel</> },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function Fixture() {
|
|
44
|
+
return (
|
|
45
|
+
<TestProviders>
|
|
46
|
+
<Tabs items={items} defaultValue="address" />
|
|
47
|
+
</TestProviders>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every `_h-` height atom painted inside each tab, in document order. */
|
|
52
|
+
function tabHeights(root: ParentNode): string[][] {
|
|
53
|
+
return [...root.querySelectorAll('[role="tab"]')].map((tab) =>
|
|
54
|
+
[tab, ...tab.querySelectorAll("*")].flatMap((node) =>
|
|
55
|
+
[...node.classList].filter((name) => /^_h-\d/.test(name)),
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let container: HTMLDivElement;
|
|
61
|
+
let root: Root | undefined;
|
|
62
|
+
let errors: unknown[];
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
host.web = true;
|
|
66
|
+
host.touch = false;
|
|
67
|
+
errors = [];
|
|
68
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
69
|
+
vi.spyOn(console, "error").mockImplementation((...args) => errors.push(args));
|
|
70
|
+
container = document.createElement("div");
|
|
71
|
+
document.body.append(container);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(async () => {
|
|
75
|
+
if (root) await act(async () => root?.unmount());
|
|
76
|
+
root = undefined;
|
|
77
|
+
container.remove();
|
|
78
|
+
vi.restoreAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
async function hydrate() {
|
|
82
|
+
await act(async () => {
|
|
83
|
+
root = hydrateRoot(container, <Fixture />, {
|
|
84
|
+
onRecoverableError: (error) => errors.push(error),
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const hydrationErrors = () =>
|
|
90
|
+
errors.filter((error) => /hydration|hydrated|didn't match/i.test(String(error)));
|
|
91
|
+
|
|
92
|
+
describe("Tabs sizing through hydration (MPO-279)", () => {
|
|
93
|
+
it("keeps the server's pointer-sized tabs through hydration, then adopts the touch floor", async () => {
|
|
94
|
+
container.innerHTML = renderToString(<Fixture />);
|
|
95
|
+
const serverTabs = [...container.querySelectorAll('[role="tab"]')];
|
|
96
|
+
const serverHeights = tabHeights(container);
|
|
97
|
+
expect(serverHeights.flat()).not.toHaveLength(0);
|
|
98
|
+
|
|
99
|
+
host.touch = true;
|
|
100
|
+
await hydrate();
|
|
101
|
+
|
|
102
|
+
expect(hydrationErrors()).toEqual([]);
|
|
103
|
+
expect([...container.querySelectorAll('[role="tab"]')]).toEqual(serverTabs);
|
|
104
|
+
const clientHeights = tabHeights(container);
|
|
105
|
+
expect(clientHeights).not.toEqual(serverHeights);
|
|
106
|
+
for (const heights of clientHeights) expect(heights).toContain("_h-48px");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("leaves pointer sizing alone on a pointer device", async () => {
|
|
110
|
+
container.innerHTML = renderToString(<Fixture />);
|
|
111
|
+
const serverHeights = tabHeights(container);
|
|
112
|
+
await hydrate();
|
|
113
|
+
expect(hydrationErrors()).toEqual([]);
|
|
114
|
+
expect(tabHeights(container)).toEqual(serverHeights);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("sizes native tabs to the touch floor on the first render", () => {
|
|
118
|
+
host.web = false;
|
|
119
|
+
host.touch = true;
|
|
120
|
+
container.innerHTML = renderToString(<Fixture />);
|
|
121
|
+
for (const heights of tabHeights(container)) expect(heights).toContain("_h-48px");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { act, cleanup, screen } from "@testing-library/react";
|
|
2
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
3
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
4
|
+
import { Tabs } from "./index";
|
|
5
|
+
|
|
6
|
+
const observed = vi.hoisted(() => ({ scroll: null as any }));
|
|
7
|
+
vi.mock("tamagui", async (original) => {
|
|
8
|
+
const actual = await original<any>();
|
|
9
|
+
const React = await import("react");
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
ScrollView: React.forwardRef((props: any, ref) => {
|
|
13
|
+
observed.scroll = props;
|
|
14
|
+
return React.createElement(actual.ScrollView, { ...props, ref });
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
const observers: ControlledResizeObserver[] = [];
|
|
19
|
+
class ControlledResizeObserver {
|
|
20
|
+
elements = new Set<Element>();
|
|
21
|
+
constructor(readonly callback: ResizeObserverCallback) {
|
|
22
|
+
observers.push(this);
|
|
23
|
+
}
|
|
24
|
+
observe(element: Element) {
|
|
25
|
+
this.elements.add(element);
|
|
26
|
+
}
|
|
27
|
+
unobserve(element: Element) {
|
|
28
|
+
this.elements.delete(element);
|
|
29
|
+
}
|
|
30
|
+
disconnect() {
|
|
31
|
+
this.elements.clear();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let precedingGrowth = 0;
|
|
35
|
+
const items = Array.from({ length: 6 }, (_, index) => ({
|
|
36
|
+
value: `tab-${index}`,
|
|
37
|
+
label: `Tab ${index + 1}`,
|
|
38
|
+
}));
|
|
39
|
+
function indexFor(element: HTMLElement) {
|
|
40
|
+
if (element.getAttribute("role") !== "tab") return -1;
|
|
41
|
+
return Number(element.textContent?.match(/Tab (\d)/)?.[1] ?? 0) - 1;
|
|
42
|
+
}
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
precedingGrowth = 0;
|
|
45
|
+
observers.length = 0;
|
|
46
|
+
vi.stubGlobal("ResizeObserver", ControlledResizeObserver);
|
|
47
|
+
vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
|
|
48
|
+
function (this: HTMLElement) {
|
|
49
|
+
const index = indexFor(this);
|
|
50
|
+
return index < 0 ? 0 : 100 + (index === 0 ? precedingGrowth : 0);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(
|
|
54
|
+
function (this: HTMLElement) {
|
|
55
|
+
return indexFor(this) < 0 ? 0 : 40;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
vi.spyOn(HTMLElement.prototype, "offsetLeft", "get").mockImplementation(
|
|
59
|
+
function (this: HTMLElement) {
|
|
60
|
+
const index = indexFor(this);
|
|
61
|
+
return index < 0 ? 0 : index * 100 + (index > 0 ? precedingGrowth : 0);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
cleanup();
|
|
67
|
+
vi.restoreAllMocks();
|
|
68
|
+
vi.unstubAllGlobals();
|
|
69
|
+
});
|
|
70
|
+
function measured() {
|
|
71
|
+
const tree = renderWithProviders(<Tabs items={items} value="tab-5" />);
|
|
72
|
+
const host = tree.container.querySelector('[data-tabs-scroll="true"]') as any;
|
|
73
|
+
const scroll = vi.spyOn(host, "scrollTo").mockImplementation(() => {});
|
|
74
|
+
act(() => observed.scroll.onLayout({ nativeEvent: { layout: { width: 390 } } }));
|
|
75
|
+
act(() => observed.scroll.onContentSizeChange(600, 40));
|
|
76
|
+
expect(scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
|
|
77
|
+
act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x: 210 } } }));
|
|
78
|
+
scroll.mockClear();
|
|
79
|
+
return { ...tree, scroll };
|
|
80
|
+
}
|
|
81
|
+
it("uses the primitive's real initial measurement and selection effect", () => {
|
|
82
|
+
measured();
|
|
83
|
+
});
|
|
84
|
+
it("refreshes selected position after a preceding label grows without resizing the selected trigger", () => {
|
|
85
|
+
const tree = measured();
|
|
86
|
+
precedingGrowth = 100;
|
|
87
|
+
tree.rerender(
|
|
88
|
+
<Tabs
|
|
89
|
+
value="tab-5"
|
|
90
|
+
items={items.map((item, index) =>
|
|
91
|
+
index === 0 ? { ...item, label: "Tab 1 with loaded details" } : item,
|
|
92
|
+
)}
|
|
93
|
+
/>,
|
|
94
|
+
);
|
|
95
|
+
const first = screen.getByRole("tab", { name: "Tab 1 with loaded details" });
|
|
96
|
+
act(() => {
|
|
97
|
+
for (const observer of observers) {
|
|
98
|
+
if (observer.elements.has(first))
|
|
99
|
+
observer.callback(
|
|
100
|
+
[
|
|
101
|
+
{
|
|
102
|
+
target: first,
|
|
103
|
+
contentRect: first.getBoundingClientRect(),
|
|
104
|
+
borderBoxSize: [],
|
|
105
|
+
contentBoxSize: [],
|
|
106
|
+
devicePixelContentBoxSize: [],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
observer as unknown as ResizeObserver,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
observed.scroll.onContentSizeChange(700, 40);
|
|
113
|
+
});
|
|
114
|
+
const selected = screen.getByRole("tab", { name: "Tab 6" });
|
|
115
|
+
expect(selected.offsetLeft).toBe(600);
|
|
116
|
+
const lastRequest = tree.scroll.mock.calls.at(-1)?.[0] as { x: number } | undefined;
|
|
117
|
+
expect(lastRequest?.x).toBeGreaterThanOrEqual(selected.offsetLeft + selected.offsetWidth - 390);
|
|
118
|
+
expect(lastRequest!.x).toBeLessThanOrEqual(selected.offsetLeft);
|
|
119
|
+
tree.scroll.mockClear();
|
|
120
|
+
act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x: 0 } } }));
|
|
121
|
+
act(() => observed.scroll.onContentSizeChange(700, 40));
|
|
122
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { act, cleanup, screen } from "@testing-library/react";
|
|
3
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
4
|
+
import { Tabs, tabsScrollIntoViewX } from "./index";
|
|
5
|
+
|
|
6
|
+
const observed = vi.hoisted(() => ({ scroll: null as any, interactions: new Map<string, any>() }));
|
|
7
|
+
|
|
8
|
+
// The actual component and primitives render. Only public measurement events
|
|
9
|
+
// and the imperative scroll boundary are controlled; this is not browser proof.
|
|
10
|
+
vi.mock("tamagui", async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal<any>();
|
|
12
|
+
const React = await import("react");
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
ScrollView: React.forwardRef((props: any, ref) => {
|
|
16
|
+
observed.scroll = props;
|
|
17
|
+
return React.createElement(actual.ScrollView, { ...props, ref });
|
|
18
|
+
}),
|
|
19
|
+
styled: (...args: any[]) => {
|
|
20
|
+
const Component = actual.styled(...args);
|
|
21
|
+
if (args[0] !== actual.Tabs.Tab) return Component;
|
|
22
|
+
return React.forwardRef((props: any, ref) => {
|
|
23
|
+
observed.interactions.set(props.value, props.onInteraction);
|
|
24
|
+
return React.createElement(Component, { ...props, ref });
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const items = Array.from({ length: 6 }, (_, index) => ({
|
|
31
|
+
value: `tab-${index}`,
|
|
32
|
+
label: `Tab ${index + 1}`,
|
|
33
|
+
}));
|
|
34
|
+
const last = { x: 500, y: 0, width: 100, height: 40 };
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
cleanup();
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
observed.scroll = null;
|
|
40
|
+
observed.interactions.clear();
|
|
41
|
+
document.documentElement.removeAttribute("dir");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function fixture(mode: "value" | "defaultValue" = "value") {
|
|
45
|
+
const rendered = renderWithProviders(<Tabs items={items} {...{ [mode]: "tab-5" }} />);
|
|
46
|
+
expect(screen.getByRole("tab", { name: "Tab 6" })).toHaveAttribute("aria-selected", "true");
|
|
47
|
+
const host = rendered.container.querySelector('[data-tabs-scroll="true"]') as any;
|
|
48
|
+
expect(typeof host.scrollTo).toBe("function");
|
|
49
|
+
const scroll = vi.spyOn(host, "scrollTo").mockImplementation(() => {});
|
|
50
|
+
const viewport = (width: number) =>
|
|
51
|
+
act(() => observed.scroll.onLayout({ nativeEvent: { layout: { width } } }));
|
|
52
|
+
const content = (width: number) => act(() => observed.scroll.onContentSizeChange(width, 40));
|
|
53
|
+
const active = (layout = last) => act(() => observed.interactions.get("tab-5")("select", layout));
|
|
54
|
+
const manualScroll = (x: number) =>
|
|
55
|
+
act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x } } }));
|
|
56
|
+
return { ...rendered, scroll, viewport, content, active, manualScroll };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe.each(["value", "defaultValue"] as const)(
|
|
60
|
+
"Tabs public measurement ordering (%s)",
|
|
61
|
+
(mode) => {
|
|
62
|
+
it("reveals initial last tab when metrics arrive before its layout (control)", () => {
|
|
63
|
+
const tree = fixture(mode);
|
|
64
|
+
tree.viewport(390);
|
|
65
|
+
tree.content(600);
|
|
66
|
+
tree.active();
|
|
67
|
+
expect(tree.scroll).toHaveBeenCalledWith({ x: 210, animated: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("reveals initial last tab when content arrives after its layout", () => {
|
|
71
|
+
const tree = fixture(mode);
|
|
72
|
+
tree.viewport(390);
|
|
73
|
+
tree.active();
|
|
74
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
75
|
+
tree.content(600);
|
|
76
|
+
expect(tree.scroll).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("corrects after a viewport shrink with unchanged active-tab bounds", () => {
|
|
80
|
+
const tree = fixture(mode);
|
|
81
|
+
tree.viewport(800);
|
|
82
|
+
tree.content(600);
|
|
83
|
+
tree.active();
|
|
84
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
85
|
+
tree.viewport(390);
|
|
86
|
+
expect(tree.scroll).toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("does not force a manual scroll back after the active tab was revealed", () => {
|
|
90
|
+
const tree = fixture(mode);
|
|
91
|
+
tree.viewport(390);
|
|
92
|
+
tree.content(600);
|
|
93
|
+
tree.active();
|
|
94
|
+
expect(tree.scroll).toHaveBeenCalledWith({ x: 210, animated: true });
|
|
95
|
+
tree.scroll.mockClear();
|
|
96
|
+
tree.manualScroll(0);
|
|
97
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
it.each(["value", "defaultValue"] as const)(
|
|
103
|
+
"reveals after late viewport measurement (%s)",
|
|
104
|
+
(mode) => {
|
|
105
|
+
const tree = fixture(mode);
|
|
106
|
+
tree.content(600);
|
|
107
|
+
tree.active();
|
|
108
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
109
|
+
tree.viewport(390);
|
|
110
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
it("retries after a hidden viewport becomes usable and ignores identical measurements", () => {
|
|
115
|
+
const tree = fixture();
|
|
116
|
+
tree.viewport(0);
|
|
117
|
+
tree.content(600);
|
|
118
|
+
tree.active();
|
|
119
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
120
|
+
tree.viewport(390);
|
|
121
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
|
|
122
|
+
tree.manualScroll(210);
|
|
123
|
+
tree.scroll.mockClear();
|
|
124
|
+
tree.viewport(390);
|
|
125
|
+
tree.content(600);
|
|
126
|
+
tree.active();
|
|
127
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
128
|
+
tree.viewport(800);
|
|
129
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("rechecks changed content width without fighting subsequent manual scroll", () => {
|
|
133
|
+
const tree = fixture();
|
|
134
|
+
tree.viewport(390);
|
|
135
|
+
tree.content(390);
|
|
136
|
+
tree.active();
|
|
137
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
138
|
+
tree.content(600);
|
|
139
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
|
|
140
|
+
tree.manualScroll(210);
|
|
141
|
+
tree.scroll.mockClear();
|
|
142
|
+
tree.content(620);
|
|
143
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 230, animated: true });
|
|
144
|
+
tree.manualScroll(230);
|
|
145
|
+
tree.scroll.mockClear();
|
|
146
|
+
tree.manualScroll(0);
|
|
147
|
+
tree.active();
|
|
148
|
+
tree.viewport(390);
|
|
149
|
+
tree.content(620);
|
|
150
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
151
|
+
tree.content(650);
|
|
152
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 242, animated: true });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("waits for the latest selected tab's layout when dimensions were pending", () => {
|
|
156
|
+
const tree = fixture();
|
|
157
|
+
tree.viewport(390);
|
|
158
|
+
tree.active();
|
|
159
|
+
tree.rerender(<Tabs items={items} value="tab-0" />);
|
|
160
|
+
tree.content(600);
|
|
161
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
162
|
+
act(() => observed.interactions.get("tab-0")("select", { ...last, x: 0 }));
|
|
163
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
164
|
+
expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveAttribute("aria-selected", "true");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("uses the negative RTL offset range after delayed dimensions", () => {
|
|
168
|
+
document.documentElement.setAttribute("dir", "rtl");
|
|
169
|
+
const tree = fixture();
|
|
170
|
+
tree.viewport(390);
|
|
171
|
+
tree.active({ ...last, x: 0 });
|
|
172
|
+
tree.content(600);
|
|
173
|
+
expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: -210, animated: true });
|
|
174
|
+
tree.manualScroll(-210);
|
|
175
|
+
tree.scroll.mockClear();
|
|
176
|
+
tree.manualScroll(0);
|
|
177
|
+
expect(tree.scroll).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY])(
|
|
181
|
+
"does not scroll with unusable width %s",
|
|
182
|
+
(width) => {
|
|
183
|
+
expect(tabsScrollIntoViewX(last, 0, width, 600, "ltr")).toBeUndefined();
|
|
184
|
+
expect(tabsScrollIntoViewX({ ...last, width }, 0, 390, 600, "ltr")).toBeUndefined();
|
|
185
|
+
},
|
|
186
|
+
);
|
|
@@ -362,3 +362,46 @@ export const EqualSmall: Story = {
|
|
|
362
362
|
</YStack>
|
|
363
363
|
),
|
|
364
364
|
};
|
|
365
|
+
|
|
366
|
+
export const InitialSixthActive: Story = {
|
|
367
|
+
args: { defaultValue: "section-6", ariaLabel: "Example sections" },
|
|
368
|
+
render: (args) => {
|
|
369
|
+
const [wide, setWide] = React.useState(false);
|
|
370
|
+
const [fullLabels, setFullLabels] = React.useState(true);
|
|
371
|
+
const [hidden, setHidden] = React.useState(false);
|
|
372
|
+
const [mount, setMount] = React.useState(0);
|
|
373
|
+
const items: TabsItem[] = Array.from({ length: 6 }, (_, index) => ({
|
|
374
|
+
value: `section-${index + 1}`,
|
|
375
|
+
label: fullLabels ? `Section ${index + 1} overview` : `${index + 1}`,
|
|
376
|
+
content: (
|
|
377
|
+
<Panel
|
|
378
|
+
title={`Section ${index + 1}`}
|
|
379
|
+
body="The selected section stays visible when the strip is measured or resized."
|
|
380
|
+
/>
|
|
381
|
+
),
|
|
382
|
+
}));
|
|
383
|
+
return (
|
|
384
|
+
<YStack gap="$3">
|
|
385
|
+
<XStack gap="$2" flexWrap="wrap">
|
|
386
|
+
<Button onPress={() => setWide((value) => !value)}>
|
|
387
|
+
{wide ? "Set width to 390px" : "Set width to 800px"}
|
|
388
|
+
</Button>
|
|
389
|
+
<Button onPress={() => setFullLabels((value) => !value)}>
|
|
390
|
+
{fullLabels ? "Use short labels" : "Load full labels"}
|
|
391
|
+
</Button>
|
|
392
|
+
<Button onPress={() => setHidden((value) => !value)}>
|
|
393
|
+
{hidden ? "Show strip" : "Hide strip"}
|
|
394
|
+
</Button>
|
|
395
|
+
<Button onPress={() => setMount((value) => value + 1)}>Reset to sixth tab</Button>
|
|
396
|
+
</XStack>
|
|
397
|
+
<Text color="$color11">
|
|
398
|
+
Use short labels, then load full labels to change content width. Hide and show the strip
|
|
399
|
+
to defer usable measurements. Scroll manually after reveal to check that it stays put.
|
|
400
|
+
</Text>
|
|
401
|
+
<YStack width={wide ? 800 : 390} maxWidth="100%" display={hidden ? "none" : "flex"}>
|
|
402
|
+
<Tabs key={mount} {...args} items={items} />
|
|
403
|
+
</YStack>
|
|
404
|
+
</YStack>
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
};
|
package/src/Tabs/index.tsx
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
useResolvedKnobs,
|
|
38
38
|
wasKeyboardFocus,
|
|
39
39
|
type GroupPosition,
|
|
40
|
+
transitionProps,
|
|
40
41
|
} from "@multiplatform.one/theme";
|
|
41
42
|
import type { ReactNode } from "react";
|
|
42
43
|
import React from "react";
|
|
@@ -174,6 +175,14 @@ export function tabsScrollIntoViewX(
|
|
|
174
175
|
direction: TabsDirection,
|
|
175
176
|
pad = 32,
|
|
176
177
|
): number | undefined {
|
|
178
|
+
if (
|
|
179
|
+
![layout.x, layout.width, layout.height, offset, viewport, content].every(Number.isFinite) ||
|
|
180
|
+
layout.width <= 0 ||
|
|
181
|
+
layout.height <= 0 ||
|
|
182
|
+
viewport <= 0 ||
|
|
183
|
+
content <= 0
|
|
184
|
+
)
|
|
185
|
+
return undefined;
|
|
177
186
|
const range = Math.max(content - viewport, 0);
|
|
178
187
|
if (range <= 0) return undefined;
|
|
179
188
|
const minScrollLeft = direction === "rtl" ? -range : 0;
|
|
@@ -366,23 +375,28 @@ function TabsRoot({
|
|
|
366
375
|
};
|
|
367
376
|
|
|
368
377
|
// Active tab layout (relative to the list) for the sliding indicator
|
|
369
|
-
const [activeLayout, setActiveLayout] = React.useState<TabLayout | null>(
|
|
370
|
-
|
|
371
|
-
|
|
378
|
+
const [activeLayout, setActiveLayout] = React.useState<(TabLayout & { value: string }) | null>(
|
|
379
|
+
null,
|
|
380
|
+
);
|
|
381
|
+
const handleInteraction = (tabValue: string, type: string, layout: TabLayout | null) => {
|
|
382
|
+
if (type !== "select" || !layout || tabValue !== currentValue) return;
|
|
372
383
|
setActiveLayout((prev) =>
|
|
373
384
|
prev &&
|
|
385
|
+
prev.value === tabValue &&
|
|
374
386
|
prev.x === layout.x &&
|
|
375
387
|
prev.y === layout.y &&
|
|
376
388
|
prev.width === layout.width &&
|
|
377
389
|
prev.height === layout.height
|
|
378
390
|
? prev
|
|
379
|
-
: layout,
|
|
391
|
+
: { ...layout, value: tabValue },
|
|
380
392
|
);
|
|
381
393
|
};
|
|
382
394
|
|
|
383
395
|
// Overflow tracking for edge fades + keep-active-visible scrolling
|
|
384
396
|
const scrollRef = React.useRef<ScrollView>(null);
|
|
397
|
+
const tabRefs = React.useRef(new Map<string, HTMLElement>());
|
|
385
398
|
const scrollMetrics = React.useRef({ offset: 0, viewport: 0, content: 0 });
|
|
399
|
+
const [sizeRevision, setSizeRevision] = React.useState(0);
|
|
386
400
|
const [fades, setFades] = React.useState({ left: false, right: false });
|
|
387
401
|
const updateFades = () => {
|
|
388
402
|
const { offset, viewport, content } = scrollMetrics.current;
|
|
@@ -390,20 +404,38 @@ function TabsRoot({
|
|
|
390
404
|
setFades((prev) => (prev.left === next.left && prev.right === next.right ? prev : next));
|
|
391
405
|
};
|
|
392
406
|
|
|
407
|
+
const updateScrollSize = (dimension: "viewport" | "content", size: number) => {
|
|
408
|
+
if (!Object.is(scrollMetrics.current[dimension], size)) {
|
|
409
|
+
scrollMetrics.current[dimension] = size;
|
|
410
|
+
if (isWeb) {
|
|
411
|
+
const selected = tabRefs.current.get(currentValue);
|
|
412
|
+
if (selected && selected.offsetWidth > 0 && selected.offsetHeight > 0) {
|
|
413
|
+
handleInteraction(currentValue, "select", {
|
|
414
|
+
x: selected.offsetLeft,
|
|
415
|
+
y: selected.offsetTop,
|
|
416
|
+
width: selected.offsetWidth,
|
|
417
|
+
height: selected.offsetHeight,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
setSizeRevision((current) => current + 1);
|
|
422
|
+
}
|
|
423
|
+
updateFades();
|
|
424
|
+
};
|
|
425
|
+
|
|
393
426
|
// Scroll the newly active tab into view when it is clipped
|
|
394
427
|
React.useEffect(() => {
|
|
395
|
-
if (!activeLayout) return;
|
|
428
|
+
if (!activeLayout || activeLayout.value !== currentValue) return;
|
|
396
429
|
const { offset, viewport, content } = scrollMetrics.current;
|
|
397
430
|
const target = tabsScrollIntoViewX(activeLayout, offset, viewport, content, direction);
|
|
398
|
-
if (target !== undefined) {
|
|
431
|
+
if (target !== undefined && Math.abs(target - offset) >= 1) {
|
|
399
432
|
scrollRef.current?.scrollTo({ x: target, animated: true });
|
|
400
433
|
}
|
|
401
|
-
}, [activeLayout, direction]);
|
|
434
|
+
}, [activeLayout, currentValue, direction, sizeRevision]);
|
|
402
435
|
|
|
403
436
|
const isUnderline = variant === "underline";
|
|
404
437
|
const isBand = variant === "band";
|
|
405
438
|
const equalDistribution = isBand || distribution === "equal";
|
|
406
|
-
const tabRefs = React.useRef(new Map<string, HTMLElement>());
|
|
407
439
|
const onTabKeyDown = (event: React.KeyboardEvent, value: string) => {
|
|
408
440
|
if (!["Home", "End", "ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
409
441
|
const enabled = items.filter((item) => !disabled && !item.disabled);
|
|
@@ -461,7 +493,7 @@ function TabsRoot({
|
|
|
461
493
|
x={activeLayout.x}
|
|
462
494
|
width={activeLayout.width}
|
|
463
495
|
data-tabs-indicator="underline"
|
|
464
|
-
{...(transition
|
|
496
|
+
{...transitionProps(transition)}
|
|
465
497
|
/>
|
|
466
498
|
</Theme>
|
|
467
499
|
) : (
|
|
@@ -477,7 +509,7 @@ function TabsRoot({
|
|
|
477
509
|
{...(knobProps.elevation !== undefined
|
|
478
510
|
? { elevation: knobProps.elevation }
|
|
479
511
|
: undefined)}
|
|
480
|
-
{...(transition
|
|
512
|
+
{...transitionProps(transition)}
|
|
481
513
|
/>
|
|
482
514
|
</Theme>
|
|
483
515
|
)
|
|
@@ -576,7 +608,7 @@ function TabsRoot({
|
|
|
576
608
|
accessibilityState: { selected: isActive, disabled: Boolean(isDisabled) },
|
|
577
609
|
})}
|
|
578
610
|
hitSlop={tabHitSlop}
|
|
579
|
-
onInteraction={handleInteraction}
|
|
611
|
+
onInteraction={(type, layout) => handleInteraction(item.value, type, layout)}
|
|
580
612
|
cursor={isDisabled ? "not-allowed" : "pointer"}
|
|
581
613
|
backgroundColor={
|
|
582
614
|
isBand
|
|
@@ -716,12 +748,10 @@ function TabsRoot({
|
|
|
716
748
|
updateFades();
|
|
717
749
|
}}
|
|
718
750
|
onLayout={(e) => {
|
|
719
|
-
|
|
720
|
-
updateFades();
|
|
751
|
+
updateScrollSize("viewport", e.nativeEvent.layout.width);
|
|
721
752
|
}}
|
|
722
753
|
onContentSizeChange={(w) => {
|
|
723
|
-
|
|
724
|
-
updateFades();
|
|
754
|
+
updateScrollSize("content", w);
|
|
725
755
|
}}
|
|
726
756
|
>
|
|
727
757
|
{tabList}
|