@ecoma-io/loom 0.4.0 → 0.5.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 +11 -9
- package/dist/a11y.js +2 -2
- package/dist/index.js +36 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/CalendarDate.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/CalendarDate.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/conversion.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/conversion.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/manipulation.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/manipulation.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/queries.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/queries.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/string.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/string.js.map +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/utils.js +1 -1
- package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/utils.js.map +1 -1
- package/dist/packages/blocks/empty-state/src/EmptyState.js.map +1 -1
- package/dist/packages/blocks/empty-state/src/EmptyState.vue.d.ts.map +1 -1
- package/dist/packages/blocks/empty-state/src/EmptyState.vue_vue_type_script_setup_true_lang.js +5 -6
- package/dist/packages/blocks/empty-state/src/EmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/blocks/error-summary/src/ErrorSummary.js +7 -0
- package/dist/packages/blocks/error-summary/src/ErrorSummary.js.map +1 -0
- package/dist/packages/blocks/error-summary/src/ErrorSummary.vue.d.ts +31 -0
- package/dist/packages/blocks/error-summary/src/ErrorSummary.vue_vue_type_script_setup_true_lang.js +90 -0
- package/dist/packages/blocks/error-summary/src/ErrorSummary.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/blocks/error-summary/src/index.d.ts +4 -0
- package/dist/packages/blocks/error-summary/src/index.js +3 -0
- package/dist/packages/composition/scroll-reel/src/ScrollReel.js.map +1 -1
- package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/composition/sidebar/src/Sidebar.js.map +1 -1
- package/dist/packages/composition/sidebar/src/Sidebar.vue.d.ts +4 -0
- package/dist/packages/composition/sidebar/src/Sidebar.vue.d.ts.map +1 -1
- package/dist/packages/composition/sidebar/src/Sidebar.vue_vue_type_script_setup_true_lang.js +13 -4
- package/dist/packages/composition/sidebar/src/Sidebar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/composition/split/src/Split.js.map +1 -1
- package/dist/packages/composition/split/src/Split.vue.d.ts +11 -12
- package/dist/packages/composition/split/src/Split.vue.d.ts.map +1 -1
- package/dist/packages/composition/split/src/Split.vue_vue_type_script_setup_true_lang.js +15 -5
- package/dist/packages/composition/split/src/Split.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/core/src/a11y-scope.d.ts +71 -5
- package/dist/packages/core/src/a11y-scope.d.ts.map +1 -1
- package/dist/packages/core/src/a11y-scope.js +126 -6
- package/dist/packages/core/src/a11y-scope.js.map +1 -1
- package/dist/packages/core/src/index.d.ts +1 -1
- package/dist/packages/core/src/index.d.ts.map +1 -1
- package/dist/packages/core/src/index.js +2 -2
- package/dist/packages/labels/src/component-shapes.d.ts +163 -1
- package/dist/packages/labels/src/component-shapes.d.ts.map +1 -1
- package/dist/packages/labels/src/date-labels.d.ts +82 -5
- package/dist/packages/labels/src/date-labels.d.ts.map +1 -1
- package/dist/packages/labels/src/date-labels.js +54 -7
- package/dist/packages/labels/src/date-labels.js.map +1 -1
- package/dist/packages/labels/src/field-context.d.ts +2 -2
- package/dist/packages/labels/src/field-context.js.map +1 -1
- package/dist/packages/labels/src/index.d.ts +3 -3
- package/dist/packages/labels/src/index.d.ts.map +1 -1
- package/dist/packages/labels/src/index.js +2 -2
- package/dist/packages/labels/src/label-registry.d.ts +9 -1
- package/dist/packages/labels/src/label-registry.d.ts.map +1 -1
- package/dist/packages/layouts/dashboard/src/Dashboard.js.map +1 -1
- package/dist/packages/layouts/dashboard/src/Dashboard.vue.d.ts +4 -4
- package/dist/packages/layouts/dashboard/src/Dashboard.vue.d.ts.map +1 -1
- package/dist/packages/layouts/dashboard/src/Dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/layouts/settings/src/Settings.js.map +1 -1
- package/dist/packages/layouts/settings/src/Settings.vue.d.ts +3 -3
- package/dist/packages/layouts/settings/src/Settings.vue.d.ts.map +1 -1
- package/dist/packages/layouts/settings/src/Settings.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/layouts/split-layout/src/SplitLayout.js.map +1 -1
- package/dist/packages/layouts/split-layout/src/SplitLayout.vue.d.ts +4 -6
- package/dist/packages/layouts/split-layout/src/SplitLayout.vue.d.ts.map +1 -1
- package/dist/packages/layouts/split-layout/src/SplitLayout.vue_vue_type_script_setup_true_lang.js +20 -5
- package/dist/packages/layouts/split-layout/src/SplitLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/layouts/split-layout/src/index.d.ts +1 -1
- package/dist/packages/layouts/split-layout/src/index.d.ts.map +1 -1
- package/dist/packages/loom/src/a11y.d.ts +1 -1
- package/dist/packages/loom/src/index.d.ts +27 -1
- package/dist/packages/primitives/alert/src/Alert.js.map +1 -1
- package/dist/packages/primitives/alert/src/Alert.vue.d.ts +18 -0
- package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js +10 -7
- package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/avatar/src/Avatar.js.map +1 -1
- package/dist/packages/primitives/avatar/src/Avatar.vue.d.ts +2 -2
- package/dist/packages/primitives/avatar/src/Avatar.vue.d.ts.map +1 -1
- package/dist/packages/primitives/avatar/src/Avatar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/packages/primitives/avatar/src/Avatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/calendar/src/Calendar.js +7 -0
- package/dist/packages/primitives/calendar/src/Calendar.js.map +1 -0
- package/dist/packages/primitives/calendar/src/Calendar.vue.d.ts +95 -0
- package/dist/packages/primitives/calendar/src/Calendar.vue_vue_type_script_setup_true_lang.js +204 -0
- package/dist/packages/primitives/calendar/src/Calendar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/calendar/src/index.d.ts +3 -0
- package/dist/packages/primitives/calendar/src/index.js +3 -0
- package/dist/packages/primitives/combobox/src/Combobox.vue.d.ts +1 -1
- package/dist/packages/primitives/command/src/Command.js +7 -0
- package/dist/packages/primitives/command/src/Command.js.map +1 -0
- package/dist/packages/primitives/command/src/Command.vue.d.ts +77 -0
- package/dist/packages/primitives/command/src/Command.vue_vue_type_script_setup_true_lang.js +333 -0
- package/dist/packages/primitives/command/src/Command.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/command/src/index.d.ts +4 -0
- package/dist/packages/primitives/command/src/index.js +3 -0
- package/dist/packages/primitives/copy-button/src/CopyButton.js +7 -0
- package/dist/packages/primitives/copy-button/src/CopyButton.js.map +1 -0
- package/dist/packages/primitives/copy-button/src/CopyButton.vue.d.ts +64 -0
- package/dist/packages/primitives/copy-button/src/CopyButton.vue_vue_type_script_setup_true_lang.js +113 -0
- package/dist/packages/primitives/copy-button/src/CopyButton.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/copy-button/src/index.d.ts +4 -0
- package/dist/packages/primitives/copy-button/src/index.js +3 -0
- package/dist/packages/primitives/data-grid/src/DataGrid.js +7 -0
- package/dist/packages/primitives/data-grid/src/DataGrid.js.map +1 -0
- package/dist/packages/primitives/data-grid/src/DataGrid.vue.d.ts +128 -0
- package/dist/packages/primitives/data-grid/src/DataGrid.vue_vue_type_script_setup_true_lang.js +347 -0
- package/dist/packages/primitives/data-grid/src/DataGrid.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/data-grid/src/index.d.ts +4 -0
- package/dist/packages/primitives/data-grid/src/index.js +3 -0
- package/dist/packages/primitives/date-picker/src/DatePicker.js.map +1 -1
- package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js +13 -13
- package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/date-range-picker/src/DateRangePicker.js.map +1 -1
- package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js +21 -18
- package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/date-time-picker/src/DateTimePicker.js.map +1 -1
- package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js +17 -16
- package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.js.map +1 -1
- package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js +17 -16
- package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/dialog/src/Dialog.js.map +1 -1
- package/dist/packages/primitives/dialog/src/Dialog.vue.d.ts +15 -7
- package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js +5 -3
- package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/drawer/src/Drawer.js.map +1 -1
- package/dist/packages/primitives/drawer/src/Drawer.vue.d.ts +10 -2
- package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js +11 -3
- package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/hover-card/src/index.d.ts.map +1 -1
- package/dist/packages/primitives/live-region/src/LiveRegion.js +7 -0
- package/dist/packages/primitives/live-region/src/LiveRegion.js.map +1 -0
- package/dist/packages/primitives/live-region/src/LiveRegion.vue.d.ts +36 -0
- package/dist/packages/primitives/live-region/src/LiveRegion.vue.d.ts.map +1 -0
- package/dist/packages/primitives/live-region/src/LiveRegion.vue_vue_type_script_setup_true_lang.js +59 -0
- package/dist/packages/primitives/live-region/src/LiveRegion.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/live-region/src/context.d.ts +40 -0
- package/dist/packages/primitives/live-region/src/context.d.ts.map +1 -0
- package/dist/packages/primitives/live-region/src/context.js +85 -0
- package/dist/packages/primitives/live-region/src/context.js.map +1 -0
- package/dist/packages/primitives/live-region/src/index.d.ts +4 -0
- package/dist/packages/primitives/live-region/src/index.d.ts.map +1 -0
- package/dist/packages/primitives/live-region/src/index.js +3 -0
- package/dist/packages/primitives/live-region/src/useAnnounce.d.ts +17 -0
- package/dist/packages/primitives/live-region/src/useAnnounce.d.ts.map +1 -0
- package/dist/packages/primitives/live-region/src/useAnnounce.js +25 -0
- package/dist/packages/primitives/live-region/src/useAnnounce.js.map +1 -0
- package/dist/packages/primitives/menubar/src/Menubar.js.map +1 -1
- package/dist/packages/primitives/menubar/src/Menubar.vue.d.ts.map +1 -1
- package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js +33 -0
- package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/meter/src/Meter.js +7 -0
- package/dist/packages/primitives/meter/src/Meter.js.map +1 -0
- package/dist/packages/primitives/meter/src/Meter.vue.d.ts +87 -0
- package/dist/packages/primitives/meter/src/Meter.vue_vue_type_script_setup_true_lang.js +236 -0
- package/dist/packages/primitives/meter/src/Meter.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/meter/src/index.d.ts +4 -0
- package/dist/packages/primitives/meter/src/index.js +3 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.js +7 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.js.map +1 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue.d.ts +69 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue.d.ts.map +1 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue_vue_type_script_setup_true_lang.js +118 -0
- package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/navigation-menu/src/index.d.ts +3 -0
- package/dist/packages/primitives/navigation-menu/src/index.d.ts.map +1 -0
- package/dist/packages/primitives/navigation-menu/src/index.js +2 -0
- package/dist/packages/primitives/segmented-control/src/SegmentedControl.js.map +1 -1
- package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue.d.ts.map +1 -1
- package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/skip-link/src/SkipLink.js +7 -0
- package/dist/packages/primitives/skip-link/src/SkipLink.js.map +1 -0
- package/dist/packages/primitives/skip-link/src/SkipLink.vue.d.ts +62 -0
- package/dist/packages/primitives/skip-link/src/SkipLink.vue_vue_type_script_setup_true_lang.js +64 -0
- package/dist/packages/primitives/skip-link/src/SkipLink.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/skip-link/src/index.d.ts +3 -0
- package/dist/packages/primitives/skip-link/src/index.js +3 -0
- package/dist/packages/primitives/stepper/src/Stepper.js.map +1 -1
- package/dist/packages/primitives/stepper/src/Stepper.vue_vue_type_script_setup_true_lang.js +7 -5
- package/dist/packages/primitives/stepper/src/Stepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/switch/src/Switch.js.map +1 -1
- package/dist/packages/primitives/switch/src/Switch.vue.d.ts.map +1 -1
- package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/table/src/index.d.ts +1 -1
- package/dist/packages/primitives/table/src/index.js +2 -2
- package/dist/packages/primitives/time-picker/src/TimePicker.js.map +1 -1
- package/dist/packages/primitives/time-picker/src/TimePicker.vue_vue_type_script_setup_true_lang.js +10 -8
- package/dist/packages/primitives/time-picker/src/TimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/toast/src/ToastItem.js.map +1 -1
- package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.js +7 -0
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.js.map +1 -0
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue.d.ts +53 -0
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue.d.ts.map +1 -0
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/toggle-group/src/index.d.ts +3 -0
- package/dist/packages/primitives/toggle-group/src/index.d.ts.map +1 -0
- package/dist/packages/primitives/toggle-group/src/index.js +2 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.js +7 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.js.map +1 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.vue.d.ts +31 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.vue.d.ts.map +1 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.vue_vue_type_script_setup_true_lang.js +149 -0
- package/dist/packages/primitives/toolbar/src/Toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.js +7 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.js.map +1 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue.d.ts +24 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue.d.ts.map +1 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue_vue_type_script_setup_true_lang.js +40 -0
- package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/toolbar/src/index.d.ts +4 -0
- package/dist/packages/primitives/toolbar/src/index.d.ts.map +1 -0
- package/dist/packages/primitives/toolbar/src/index.js +3 -0
- package/dist/packages/primitives/tree-view/src/TreeView.js +7 -0
- package/dist/packages/primitives/tree-view/src/TreeView.js.map +1 -0
- package/dist/packages/primitives/tree-view/src/TreeView.vue.d.ts +99 -0
- package/dist/packages/primitives/tree-view/src/TreeView.vue_vue_type_script_setup_true_lang.js +310 -0
- package/dist/packages/primitives/tree-view/src/TreeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.js +7 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.js.map +1 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.vue.d.ts +15 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.vue.d.ts.map +1 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.vue_vue_type_script_setup_true_lang.js +103 -0
- package/dist/packages/primitives/tree-view/src/TreeViewNode.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/tree-view/src/context.d.ts +39 -0
- package/dist/packages/primitives/tree-view/src/context.d.ts.map +1 -0
- package/dist/packages/primitives/tree-view/src/context.js +6 -0
- package/dist/packages/primitives/tree-view/src/context.js.map +1 -0
- package/dist/packages/primitives/tree-view/src/index.d.ts +4 -0
- package/dist/packages/primitives/tree-view/src/index.js +3 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.js +7 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.js.map +1 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue.d.ts +21 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue.d.ts.map +1 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue_vue_type_script_setup_true_lang.js +27 -0
- package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/packages/primitives/visually-hidden/src/index.d.ts +3 -0
- package/dist/packages/primitives/visually-hidden/src/index.d.ts.map +1 -0
- package/dist/packages/primitives/visually-hidden/src/index.js +2 -0
- package/package.json +29 -28
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { cn } from "../../../core/src/cn.js";
|
|
2
|
+
import { useSplitAttrs } from "../../../core/src/attrs.js";
|
|
3
|
+
import "../../../core/src/index.js";
|
|
4
|
+
import { useLabels } from "../../../labels/src/labels.js";
|
|
5
|
+
import "../../../labels/src/index.js";
|
|
6
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, resolveDynamicComponent, toDisplayString, unref, useId } from "vue";
|
|
7
|
+
import { Check, CircleAlert, X } from "@lucide/vue";
|
|
8
|
+
import { cva } from "class-variance-authority";
|
|
9
|
+
//#region packages/primitives/meter/src/Meter.vue?vue&type=script&setup=true&lang.ts
|
|
10
|
+
var _hoisted_1 = ["id"];
|
|
11
|
+
var _hoisted_2 = [
|
|
12
|
+
"aria-valuenow",
|
|
13
|
+
"aria-valuemin",
|
|
14
|
+
"aria-valuemax",
|
|
15
|
+
"aria-valuetext",
|
|
16
|
+
"aria-label",
|
|
17
|
+
"aria-labelledby"
|
|
18
|
+
];
|
|
19
|
+
var _hoisted_3 = [
|
|
20
|
+
"aria-valuenow",
|
|
21
|
+
"aria-valuemin",
|
|
22
|
+
"aria-valuemax",
|
|
23
|
+
"aria-valuetext",
|
|
24
|
+
"aria-label",
|
|
25
|
+
"aria-labelledby"
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Loom's English, co-located with the component so it tree-shakes with it, and
|
|
29
|
+
* exported so a host can build a partial vocabulary against the real thing.
|
|
30
|
+
*/
|
|
31
|
+
var METER_LABELS = {
|
|
32
|
+
name: "Meter",
|
|
33
|
+
valueText: ({ value, max }) => `${value} of ${max}`,
|
|
34
|
+
optimal: "Optimal",
|
|
35
|
+
cautionary: "Caution",
|
|
36
|
+
critical: "Critical"
|
|
37
|
+
};
|
|
38
|
+
var meterVariants = cva("relative w-full overflow-hidden rounded-full bg-muted", {
|
|
39
|
+
variants: { size: {
|
|
40
|
+
sm: "h-1",
|
|
41
|
+
md: "h-2",
|
|
42
|
+
lg: "h-3"
|
|
43
|
+
} },
|
|
44
|
+
defaultVariants: { size: "md" }
|
|
45
|
+
});
|
|
46
|
+
var Meter_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
47
|
+
inheritAttrs: false,
|
|
48
|
+
__name: "Meter",
|
|
49
|
+
props: {
|
|
50
|
+
value: {},
|
|
51
|
+
min: { default: 0 },
|
|
52
|
+
max: { default: 100 },
|
|
53
|
+
low: {},
|
|
54
|
+
high: {},
|
|
55
|
+
optimum: {},
|
|
56
|
+
label: {},
|
|
57
|
+
ariaLabel: {},
|
|
58
|
+
ariaLabelledby: {},
|
|
59
|
+
size: { default: "md" },
|
|
60
|
+
threshold: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: false
|
|
63
|
+
},
|
|
64
|
+
showValue: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false
|
|
67
|
+
},
|
|
68
|
+
labels: {}
|
|
69
|
+
},
|
|
70
|
+
setup(__props) {
|
|
71
|
+
const props = __props;
|
|
72
|
+
const text = useLabels("meter", METER_LABELS, () => props.labels);
|
|
73
|
+
/** Track label text sizing, matching Progress's readout scale exactly. */
|
|
74
|
+
const VALUE_TEXT = {
|
|
75
|
+
sm: "text-micro",
|
|
76
|
+
md: "text-xs",
|
|
77
|
+
lg: "text-sm"
|
|
78
|
+
};
|
|
79
|
+
const { attrs, rest } = useSplitAttrs();
|
|
80
|
+
const labelElId = useId();
|
|
81
|
+
/** Clamped value, for the fill AND the aria attributes — never out of range. */
|
|
82
|
+
const clamped = computed(() => {
|
|
83
|
+
const v = props.value;
|
|
84
|
+
if (Number.isNaN(v)) return props.min;
|
|
85
|
+
return Math.min(Math.max(v, props.min), props.max);
|
|
86
|
+
});
|
|
87
|
+
/** Percentage 0–100, from the clamped value. */
|
|
88
|
+
const pct = computed(() => {
|
|
89
|
+
const range = props.max - props.min;
|
|
90
|
+
if (range <= 0) return 100;
|
|
91
|
+
return (clamped.value - props.min) / range * 100;
|
|
92
|
+
});
|
|
93
|
+
const band = computed(() => {
|
|
94
|
+
if (!props.threshold) return void 0;
|
|
95
|
+
let lo = props.low ?? props.min;
|
|
96
|
+
let hi = props.high ?? props.max;
|
|
97
|
+
lo = Math.min(Math.max(lo, props.min), props.max);
|
|
98
|
+
hi = Math.min(Math.max(hi, props.min), props.max);
|
|
99
|
+
if (lo > hi) {
|
|
100
|
+
lo = props.min;
|
|
101
|
+
hi = props.max;
|
|
102
|
+
}
|
|
103
|
+
const o = Math.min(Math.max(props.optimum ?? (props.min + props.max) / 2, props.min), props.max);
|
|
104
|
+
const v = clamped.value;
|
|
105
|
+
const optRegion = o < lo ? 0 : o <= hi ? 1 : 2;
|
|
106
|
+
const regionMap = optRegion === 0 ? {
|
|
107
|
+
optimal: 0,
|
|
108
|
+
cautionary: [1],
|
|
109
|
+
critical: 2
|
|
110
|
+
} : optRegion === 1 ? {
|
|
111
|
+
optimal: 1,
|
|
112
|
+
cautionary: [0, 2],
|
|
113
|
+
critical: null
|
|
114
|
+
} : {
|
|
115
|
+
optimal: 2,
|
|
116
|
+
cautionary: [1],
|
|
117
|
+
critical: 0
|
|
118
|
+
};
|
|
119
|
+
const valueRegion = v < lo ? 0 : v <= hi ? 1 : 2;
|
|
120
|
+
if (valueRegion === regionMap.optimal) return "optimal";
|
|
121
|
+
if (regionMap.cautionary.includes(valueRegion)) return "cautionary";
|
|
122
|
+
if (regionMap.critical === valueRegion) return "critical";
|
|
123
|
+
return "optimal";
|
|
124
|
+
});
|
|
125
|
+
/** The fill colour when threshold is on; stays primary otherwise. */
|
|
126
|
+
const fillClass = computed(() => {
|
|
127
|
+
if (!props.threshold) return "bg-primary";
|
|
128
|
+
const b = band.value;
|
|
129
|
+
if (b === "optimal") return "bg-success";
|
|
130
|
+
if (b === "cautionary") return "bg-warning";
|
|
131
|
+
return "bg-destructive";
|
|
132
|
+
});
|
|
133
|
+
/** The band cue word from labels. */
|
|
134
|
+
const cueWord = computed(() => {
|
|
135
|
+
if (!band.value) return "";
|
|
136
|
+
return text.value[band.value];
|
|
137
|
+
});
|
|
138
|
+
/** The band cue icon component. */
|
|
139
|
+
const cueIcon = computed(() => {
|
|
140
|
+
if (band.value === "optimal") return Check;
|
|
141
|
+
if (band.value === "cautionary") return CircleAlert;
|
|
142
|
+
return X;
|
|
143
|
+
});
|
|
144
|
+
/** The cue colour class — the `-text` token of the band, never the fill token:
|
|
145
|
+
* `--color-success` in dark is a fill lightness and fails text contrast. */
|
|
146
|
+
const cueColorClass = computed(() => {
|
|
147
|
+
if (band.value === "optimal") return "text-success-text";
|
|
148
|
+
if (band.value === "cautionary") return "text-warning-text";
|
|
149
|
+
return "text-destructive-text";
|
|
150
|
+
});
|
|
151
|
+
/** The fill indicator style — translateX slides the fill from right to left. */
|
|
152
|
+
const indicatorStyle = computed(() => ({
|
|
153
|
+
transition: "transform var(--duration-slow) var(--ease-out), background-color var(--duration-slow) var(--ease-out)",
|
|
154
|
+
transform: `translateX(${pct.value - 100}%)`
|
|
155
|
+
}));
|
|
156
|
+
/** aria-valuetext — localisable; never absent because the role is meaningless without it. */
|
|
157
|
+
const valuetext = computed(() => text.value.valueText({
|
|
158
|
+
value: clamped.value,
|
|
159
|
+
min: props.min,
|
|
160
|
+
max: props.max
|
|
161
|
+
}));
|
|
162
|
+
/**
|
|
163
|
+
* `label` is a content prop and always renders; `ariaLabel`/`ariaLabelledby`
|
|
164
|
+
* choose only where the NAME comes from. A caller passing both wants visible
|
|
165
|
+
* text under one name and a different announced name — silencing the visible
|
|
166
|
+
* text would make the pair unrepresentable. And because assistive tech reads
|
|
167
|
+
* `aria-labelledby` ahead of `aria-label`, whichever source loses must not
|
|
168
|
+
* emit its attribute at all: an `ariaLabel` beside a label-pointing
|
|
169
|
+
* `aria-labelledby` would be read past, not preferred.
|
|
170
|
+
*/
|
|
171
|
+
const ariaLabelAttr = computed(() => {
|
|
172
|
+
if (props.ariaLabelledby) return void 0;
|
|
173
|
+
if (props.ariaLabel) return props.ariaLabel;
|
|
174
|
+
if (props.label) return void 0;
|
|
175
|
+
return text.value.name;
|
|
176
|
+
});
|
|
177
|
+
const ariaLabelledbyAttr = computed(() => {
|
|
178
|
+
if (props.ariaLabelledby) return props.ariaLabelledby;
|
|
179
|
+
if (props.ariaLabel) return void 0;
|
|
180
|
+
if (props.label) return labelElId;
|
|
181
|
+
});
|
|
182
|
+
/** Whether the visible `label` text renders. */
|
|
183
|
+
const hasVisibleLabel = computed(() => !!props.label);
|
|
184
|
+
/** Whether the gauge row needs extra content beside the track. */
|
|
185
|
+
const hasRow = computed(() => props.showValue || props.threshold && band.value);
|
|
186
|
+
return (_ctx, _cache) => {
|
|
187
|
+
return openBlock(), createElementBlock(Fragment, null, [hasVisibleLabel.value ? (openBlock(), createElementBlock("span", {
|
|
188
|
+
key: 0,
|
|
189
|
+
id: unref(labelElId),
|
|
190
|
+
class: normalizeClass(unref(cn)("block text-sm font-medium text-foreground mb-1", VALUE_TEXT[__props.size]))
|
|
191
|
+
}, toDisplayString(__props.label), 11, _hoisted_1)) : createCommentVNode("", true), !hasRow.value ? (openBlock(), createElementBlock("div", mergeProps({
|
|
192
|
+
key: 1,
|
|
193
|
+
role: "meter",
|
|
194
|
+
"aria-valuenow": clamped.value,
|
|
195
|
+
"aria-valuemin": __props.min,
|
|
196
|
+
"aria-valuemax": __props.max,
|
|
197
|
+
"aria-valuetext": valuetext.value,
|
|
198
|
+
"aria-label": ariaLabelAttr.value,
|
|
199
|
+
"aria-labelledby": ariaLabelledbyAttr.value
|
|
200
|
+
}, unref(rest), { class: unref(cn)(unref(meterVariants)({ size: __props.size }), unref(attrs).class) }), [createElementVNode("div", {
|
|
201
|
+
class: normalizeClass(unref(cn)("h-full rounded-full", fillClass.value)),
|
|
202
|
+
style: normalizeStyle(indicatorStyle.value)
|
|
203
|
+
}, null, 6)], 16, _hoisted_2)) : (openBlock(), createElementBlock("div", {
|
|
204
|
+
key: 2,
|
|
205
|
+
class: normalizeClass(unref(cn)("flex w-full items-center gap-3", unref(attrs).class))
|
|
206
|
+
}, [createElementVNode("div", mergeProps({
|
|
207
|
+
role: "meter",
|
|
208
|
+
"aria-valuenow": clamped.value,
|
|
209
|
+
"aria-valuemin": __props.min,
|
|
210
|
+
"aria-valuemax": __props.max,
|
|
211
|
+
"aria-valuetext": valuetext.value,
|
|
212
|
+
"aria-label": ariaLabelAttr.value,
|
|
213
|
+
"aria-labelledby": ariaLabelledbyAttr.value
|
|
214
|
+
}, unref(rest), { class: unref(cn)(unref(meterVariants)({ size: __props.size }), "w-auto min-w-0 flex-1") }), [createElementVNode("div", {
|
|
215
|
+
class: normalizeClass(unref(cn)("h-full rounded-full", fillClass.value)),
|
|
216
|
+
style: normalizeStyle(indicatorStyle.value)
|
|
217
|
+
}, null, 6)], 16, _hoisted_3), createElementVNode("div", {
|
|
218
|
+
"aria-hidden": "true",
|
|
219
|
+
class: normalizeClass(unref(cn)("flex items-center gap-2 shrink-0"))
|
|
220
|
+
}, [__props.showValue ? (openBlock(), createElementBlock("span", {
|
|
221
|
+
key: 0,
|
|
222
|
+
class: normalizeClass(unref(cn)("tabular w-10 text-right text-muted-foreground", VALUE_TEXT[__props.size]))
|
|
223
|
+
}, toDisplayString(valuetext.value), 3)) : createCommentVNode("", true), __props.threshold && band.value ? (openBlock(), createElementBlock("span", {
|
|
224
|
+
key: 1,
|
|
225
|
+
class: normalizeClass(unref(cn)("flex items-center gap-1 whitespace-nowrap", cueColorClass.value, VALUE_TEXT[__props.size]))
|
|
226
|
+
}, [(openBlock(), createBlock(resolveDynamicComponent(cueIcon.value), {
|
|
227
|
+
class: "h-3.5 w-3.5",
|
|
228
|
+
"aria-hidden": "true"
|
|
229
|
+
})), createElementVNode("span", null, toDisplayString(cueWord.value), 1)], 2)) : createCommentVNode("", true)], 2)], 2))], 64);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
//#endregion
|
|
234
|
+
export { METER_LABELS, Meter_vue_vue_type_script_setup_true_lang_default as default, meterVariants };
|
|
235
|
+
|
|
236
|
+
//# sourceMappingURL=Meter.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Meter.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/meter/src/Meter.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { MeterLabels } from \"@ecoma-io/loom-labels\";\nimport { cva } from \"class-variance-authority\";\n\n/**\n * Loom's English, co-located with the component so it tree-shakes with it, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const METER_LABELS: MeterLabels = {\n name: \"Meter\",\n valueText: ({ value, max }) => `${value} of ${max}`,\n optimal: \"Optimal\",\n cautionary: \"Caution\",\n critical: \"Critical\",\n};\n\n/**\n * The track's thickness, the same scale as Progress — a meter is a gauge, not a\n * form control, so it stays a stroke rather than aligning with input height.\n */\nexport type MeterSize = \"sm\" | \"md\" | \"lg\";\n\nexport const meterVariants = cva(\"relative w-full overflow-hidden rounded-full bg-muted\", {\n variants: {\n size: {\n sm: \"h-1\",\n md: \"h-2\",\n lg: \"h-3\",\n } satisfies Record<MeterSize, string>,\n },\n defaultVariants: { size: \"md\" },\n});\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\nimport { Check, CircleAlert, X } from \"@lucide/vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\n\n/**\n * Meter — a scalar capacity gauge on the WAI-ARIA meter role, visually a sibling\n * of Progress (same stroke scale, track/fill anatomy). Renders a measured\n * quantity against a known capacity — disk usage, budget consumption, seat\n * allocation — things that are not tasks and should never say \"loading.\"\n *\n * Built directly on `<div role=\"meter\">` rather than a headless component: the\n * ARIA meter role carries the same contract as the HTML meter element (min, max,\n * value, low, high, optimum) and a headless abstraction would add nothing but\n * an extra dependency — Progress uses Reka UI because progressbar's indeterminate\n * animation requires real DOM transitions Meter does not need.\n *\n * The accessible name is mandatory: a meter without a name is a number with no\n * referent. Four sources, resolved in order:\n * 1. `ariaLabelledby` (external element)\n * 2. `ariaLabel` (explicit string, no visible text)\n * 3. `label` (visible text, same string as the name)\n * 4. `text.name` (the labels-seam fallback — guaranteed non-empty)\n *\n * A `value` above `max` paints the bar full but `aria-valuenow` reports the\n * clamped value, because a spoken number outside the declared range is a lie\n * — the Progress lesson, carried forward.\n */\nconst props = withDefaults(\n defineProps<{\n /** The measured amount, clamped into [min, max] for both the fill and the announced value. */\n value: number;\n /** The value at which the gauge reads 0% full. */\n min?: number;\n /** The value at which the gauge reads 100% full. */\n max?: number;\n /** Low threshold — the boundary between the low and medium regions (HTML meter semantics). */\n low?: number;\n /** High threshold — the boundary between the medium and high regions (HTML meter semantics). */\n high?: number;\n /** The optimal value; decides which region is the good one. Defaults to the midpoint. */\n optimum?: number;\n /** Visible text that also supplies the accessible name when no ariaLabel/ariaLabelledby is set. */\n label?: string;\n /** Accessible name for the gauge, in place of a visible label. */\n ariaLabel?: string;\n /** Accessible name sourced from another element's id, in place of `ariaLabel`/`label`. */\n ariaLabelledby?: string;\n /** Track thickness. Its own scale, not the control-height scale — a gauge is a stroke. */\n size?: MeterSize;\n /** Opt-in band recolouring (success/warning/destructive) with a redundant non-colour cue per band. */\n threshold?: boolean;\n /** Print the localised value text beside the track. Hidden from AT — the role already announces it. */\n showValue?: boolean;\n /**\n * How the gauge speaks, as any subset of `MeterLabels` — the rest stay as\n * the host's `provideLoomLabels` vocabulary left them, and then as Loom's\n * English.\n */\n labels?: LabelOverrides<MeterLabels>;\n }>(),\n {\n min: 0,\n max: 100,\n size: \"md\",\n threshold: false,\n showValue: false,\n },\n);\n\nconst text = useLabels(\"meter\", METER_LABELS, () => props.labels);\n\n/** Track label text sizing, matching Progress's readout scale exactly. */\nconst VALUE_TEXT = {\n sm: \"text-micro\",\n md: \"text-xs\",\n lg: \"text-sm\",\n} satisfies Record<MeterSize, string>;\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest } = useSplitAttrs();\nconst labelElId = useId();\n\n// ---------------------------------------------------------------------------\n// Value clamping — the Progress lesson: a spoken value outside [min, max] is a lie.\n// ---------------------------------------------------------------------------\n\n/** Clamped value, for the fill AND the aria attributes — never out of range. */\nconst clamped = computed<number>(() => {\n const v = props.value;\n if (Number.isNaN(v)) return props.min;\n return Math.min(Math.max(v, props.min), props.max);\n});\n\n/** Percentage 0–100, from the clamped value. */\nconst pct = computed<number>(() => {\n const range = props.max - props.min;\n if (range <= 0) return 100;\n return ((clamped.value - props.min) / range) * 100;\n});\n\n// ---------------------------------------------------------------------------\n// Band logic — HTML meter element's three-quality model, exactly.\n//\n// Three regions: low [min, low), medium [low, high], high (high, max].\n// The optimum point selects which region is preferred, and how the others rank.\n//\n// Per HTML spec §4.10.16:\n// optimum in low → low=preferred, medium=suboptimal, high=even-less-good\n// optimum in medium → medium=preferred, low=suboptimal, high=suboptimal\n// optimum in high → high=preferred, medium=suboptimal, low=even-less-good\n//\n// When a region is \"even less good\" it maps to critical; \"suboptimal\" maps\n// to cautionary. No critical band exists when optimum is in the medium region\n// (both extremes are equally suboptimal).\n// ---------------------------------------------------------------------------\n\ntype Band = \"optimal\" | \"cautionary\" | \"critical\";\n\nconst band = computed<Band | undefined>(() => {\n if (!props.threshold) return undefined;\n\n let lo = props.low ?? props.min;\n let hi = props.high ?? props.max;\n\n // Clamp both boundaries into [min, max], then reject an inverted pair the\n // way the HTML meter element does: treat both as unset rather than build a\n // region model out of crossed boundaries.\n lo = Math.min(Math.max(lo, props.min), props.max);\n hi = Math.min(Math.max(hi, props.min), props.max);\n if (lo > hi) {\n lo = props.min;\n hi = props.max;\n }\n\n const o = Math.min(Math.max(props.optimum ?? (props.min + props.max) / 2, props.min), props.max);\n const v = clamped.value;\n\n // Determine which region contains the optimum.\n // Boundaries: low < value < high belongs to medium; low ≤ optimum ≤ high is medium.\n // Region 0: [min, lo) Region 1: [lo, hi] Region 2: (hi, max]\n const optRegion = o < lo ? 0 : o <= hi ? 1 : 2;\n\n // Map regions to qualities per the table.\n const regionMap: { optimal: number; cautionary: number[]; critical: number | null } =\n optRegion === 0\n ? { optimal: 0, cautionary: [1], critical: 2 }\n : optRegion === 1\n ? { optimal: 1, cautionary: [0, 2], critical: null }\n : { optimal: 2, cautionary: [1], critical: 0 };\n\n // Determine which region the current value falls into.\n const valueRegion = v < lo ? 0 : v <= hi ? 1 : 2;\n\n if (valueRegion === regionMap.optimal) return \"optimal\";\n if (regionMap.cautionary.includes(valueRegion)) return \"cautionary\";\n if (regionMap.critical === valueRegion) return \"critical\";\n return \"optimal\"; // fallback — should not be reached\n});\n\n/** The fill colour when threshold is on; stays primary otherwise. */\nconst fillClass = computed(() => {\n if (!props.threshold) return \"bg-primary\";\n const b = band.value;\n if (b === \"optimal\") return \"bg-success\";\n if (b === \"cautionary\") return \"bg-warning\";\n return \"bg-destructive\";\n});\n\n/** The band cue word from labels. */\nconst cueWord = computed(() => {\n if (!band.value) return \"\";\n return text.value[band.value];\n});\n\n/** The band cue icon component. */\nconst cueIcon = computed(() => {\n if (band.value === \"optimal\") return Check;\n if (band.value === \"cautionary\") return CircleAlert;\n return X;\n});\n\n/** The cue colour class — the `-text` token of the band, never the fill token:\n * `--color-success` in dark is a fill lightness and fails text contrast. */\nconst cueColorClass = computed(() => {\n if (band.value === \"optimal\") return \"text-success-text\";\n if (band.value === \"cautionary\") return \"text-warning-text\";\n return \"text-destructive-text\";\n});\n\n/** The fill indicator style — translateX slides the fill from right to left. */\nconst indicatorStyle = computed(() => ({\n transition:\n \"transform var(--duration-slow) var(--ease-out), background-color var(--duration-slow) var(--ease-out)\",\n transform: `translateX(${pct.value - 100}%)`,\n}));\n\n/** aria-valuetext — localisable; never absent because the role is meaningless without it. */\nconst valuetext = computed(() =>\n text.value.valueText({ value: clamped.value, min: props.min, max: props.max }),\n);\n\n// ---------------------------------------------------------------------------\n// Accessible name resolution — four sources, in precedence order.\n// ---------------------------------------------------------------------------\n\n/**\n * `label` is a content prop and always renders; `ariaLabel`/`ariaLabelledby`\n * choose only where the NAME comes from. A caller passing both wants visible\n * text under one name and a different announced name — silencing the visible\n * text would make the pair unrepresentable. And because assistive tech reads\n * `aria-labelledby` ahead of `aria-label`, whichever source loses must not\n * emit its attribute at all: an `ariaLabel` beside a label-pointing\n * `aria-labelledby` would be read past, not preferred.\n */\nconst ariaLabelAttr = computed(() => {\n if (props.ariaLabelledby) return undefined;\n if (props.ariaLabel) return props.ariaLabel;\n if (props.label) return undefined; // named through the span's id instead\n return text.value.name; // the seam fallback — a name is never absent\n});\n\nconst ariaLabelledbyAttr = computed(() => {\n if (props.ariaLabelledby) return props.ariaLabelledby;\n if (props.ariaLabel) return undefined; // ariaLabel wins the name\n if (props.label) return labelElId;\n return undefined;\n});\n\n/** Whether the visible `label` text renders. */\nconst hasVisibleLabel = computed(() => !!props.label);\n\n/** Whether the gauge row needs extra content beside the track. */\nconst hasRow = computed(() => props.showValue || (props.threshold && band.value));\n</script>\n\n<template>\n <!-- Visible label — only when `label` is set and no ariaLabel/ariaLabelledby overrides it.\n Rendered as a <span> rather than <label> because a div[role=\"meter\"] is not a\n labelable element; the association is through aria-labelledby pointing at the span's id. -->\n <span\n v-if=\"hasVisibleLabel\"\n :id=\"labelElId\"\n :class=\"cn('block text-sm font-medium text-foreground mb-1', VALUE_TEXT[size])\"\n >\n {{ label }}\n </span>\n\n <!-- Branch 1: bare track — nothing beside it, no wrapper. Mirrors Progress's\n default branch: the component's box is the track itself, and it keeps\n the exact sizing it had before `showValue`/`threshold` existed. -->\n <div\n v-if=\"!hasRow\"\n role=\"meter\"\n :aria-valuenow=\"clamped\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"valuetext\"\n :aria-label=\"ariaLabelAttr\"\n :aria-labelledby=\"ariaLabelledbyAttr\"\n v-bind=\"rest\"\n :class=\"cn(meterVariants({ size }), attrs.class as string)\"\n >\n <div :class=\"cn('h-full rounded-full', fillClass)\" :style=\"indicatorStyle\" />\n </div>\n\n <!-- Branch 2: gauge row with cue/readout — a flex row so the track and the\n side content share a single box. `class` from the caller lands on this\n row (the outermost box the caller sees), and everything else lands on the\n meter element through `rest`. -->\n <div v-else :class=\"cn('flex w-full items-center gap-3', attrs.class as string)\">\n <div\n role=\"meter\"\n :aria-valuenow=\"clamped\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"valuetext\"\n :aria-label=\"ariaLabelAttr\"\n :aria-labelledby=\"ariaLabelledbyAttr\"\n v-bind=\"rest\"\n :class=\"cn(meterVariants({ size }), 'w-auto min-w-0 flex-1')\"\n >\n <div :class=\"cn('h-full rounded-full', fillClass)\" :style=\"indicatorStyle\" />\n </div>\n\n <!-- Value readout + band cue, aria-hidden (the meter role already announces the\n value via aria-valuetext, and the band is derivable from it). Fixed width so\n the row does not resize as the digits change. -->\n <div aria-hidden=\"true\" :class=\"cn('flex items-center gap-2 shrink-0')\">\n <span\n v-if=\"showValue\"\n :class=\"cn('tabular w-10 text-right text-muted-foreground', VALUE_TEXT[size])\"\n >\n {{ valuetext }}\n </span>\n <span\n v-if=\"threshold && band\"\n :class=\"cn('flex items-center gap-1 whitespace-nowrap', cueColorClass, VALUE_TEXT[size])\"\n >\n <component :is=\"cueIcon\" class=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n <span>{{ cueWord }}</span>\n </span>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import NavigationMenu_vue_vue_type_script_setup_true_lang_default from "./NavigationMenu.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
//#region packages/primitives/navigation-menu/src/NavigationMenu.vue
|
|
3
|
+
var NavigationMenu_default = NavigationMenu_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { NavigationMenu_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=NavigationMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationMenu.js","names":[],"sources":["../../../../../packages/primitives/navigation-menu/src/NavigationMenu.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A navigation link shown inside a dropdown panel. The list is data rather than\n * markup because the primitive stays free of routing logic — the host provides\n * `href` values and decides what navigation means.\n */\nexport interface NavigationMenuLink {\n /** Visible label. */\n label: string;\n /** Link target. */\n href: string;\n /** Whether this link is the current page. */\n active?: boolean;\n /** Optional description shown below the label. */\n description?: string;\n /** Disabled state. */\n disabled?: boolean;\n}\n\n/**\n * One top-level navigation entry. An item with `children` opens a dropdown\n * panel; an item without them is a plain link.\n */\nexport interface NavigationMenuItem {\n /** Visible label for the trigger button. */\n label: string;\n /** Unique key identifying this item. */\n value: string;\n /** The link target. When set, this item is a simple link rather than a dropdown trigger. */\n href?: string;\n /** Whether this item is currently active (shows aria-current). */\n active?: boolean;\n /** Nested navigation items shown in a dropdown panel. */\n children?: NavigationMenuLink[];\n /** Disabled state. */\n disabled?: boolean;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n NavigationMenuRoot,\n NavigationMenuList,\n NavigationMenuItem as RekaNavigationMenuItem,\n NavigationMenuTrigger,\n NavigationMenuContent,\n NavigationMenuLink as RekaNavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n} from \"reka-ui\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NavigationMenu — the WAI-ARIA navigation menu pattern: a landmark containing\n * triggers that open dropdown panels of navigation links.\n *\n * Unlike `DropdownMenu` (button → command list) or `Menubar` (horizontal strip\n * of command menus), this component is a site-navigation landmark. Its panels\n * contain links, not actions, and the whole structure is wrapped in a `<nav>`\n * landmark.\n *\n * Items are data rather than markup. A top-level item with `children` opens a\n * dropdown panel; one without renders as a direct link. The keyboard model\n * follows the APG Navigation Menu pattern: roving tabindex across triggers,\n * Left/Right arrows move between triggers, ArrowDown opens, Escape closes.\n */\nwithDefaults(\n defineProps<{\n /** The top-level navigation items, in order. */\n items: NavigationMenuItem[];\n /** Layout direction of the trigger bar. */\n orientation?: \"horizontal\" | \"vertical\";\n /** Reading direction. Mirrors arrow keys when right-to-left. */\n dir?: \"ltr\" | \"rtl\";\n /** Drive the open trigger from the host; omit it and the menu owns its own state. */\n modelValue?: string | undefined;\n /** Accessible label for the nav landmark. */\n ariaLabel?: string;\n }>(),\n // `modelValue: undefined` is load-bearing — it keeps the uncontrolled third\n // state reachable past Vue's absent-Boolean casting. `optional()` in the\n // template is the other half.\n { modelValue: undefined, orientation: \"horizontal\", ariaLabel: \"Navigation\" },\n);\n\ndefineEmits<{ \"update:modelValue\": [value: string] }>();\n</script>\n\n<template>\n <nav :aria-label=\"ariaLabel\" :dir=\"dir\">\n <NavigationMenuRoot\n :orientation=\"orientation\"\n v-bind=\"optional({ modelValue, dir })\"\n class=\"relative\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <NavigationMenuList\n :class=\"\n cn('flex items-center gap-0.5', orientation === 'vertical' && 'flex-col items-stretch')\n \"\n >\n <RekaNavigationMenuItem v-for=\"item in items\" :key=\"item.value\" :value=\"item.value\">\n <!-- Items with children get a trigger button that opens a panel. -->\n <NavigationMenuTrigger\n v-if=\"item.children && item.children.length > 0\"\n :disabled=\"item.disabled ?? false\"\n aria-haspopup=\"menu\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',\n 'data-[state=open]:bg-subtle data-[state=open]:text-foreground',\n )\n \"\n >\n {{ item.label }}\n </NavigationMenuTrigger>\n\n <!-- Simple link items wrapped in NavigationMenuLink for roving focus. -->\n <RekaNavigationMenuLink\n v-else\n v-bind=\"optional({ asChild: true, active: item.active })\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n item.active && 'bg-primary-muted text-primary-text',\n item.disabled && 'pointer-events-none text-muted-foreground',\n )\n \"\n >\n <a\n :href=\"item.href\"\n :aria-current=\"item.active ? 'page' : undefined\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : undefined\"\n >\n {{ item.label }}\n </a>\n </RekaNavigationMenuLink>\n\n <NavigationMenuContent\n v-if=\"item.children && item.children.length > 0\"\n :class=\"\n cn(\n 'z-overlay min-w-[16rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none',\n 'data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out',\n )\n \"\n style=\"transform-origin: var(--reka-popper-transform-origin)\"\n >\n <div class=\"flex flex-col gap-1 p-1\">\n <a\n v-for=\"(link, i) in item.children\"\n :key=\"link.href\"\n :href=\"link.href\"\n :aria-current=\"link.active ? 'page' : undefined\"\n :aria-disabled=\"link.disabled || undefined\"\n :tabindex=\"link.disabled ? -1 : undefined\"\n :style=\"{ animationDelay: listStaggerDelay(i) }\"\n :class=\"\n cn(\n 'block rounded-sm px-2 py-1.5 text-sm text-foreground no-underline outline-none',\n 'transition-colors duration-fast ease-out',\n 'hover:bg-subtle',\n 'focus-visible:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n link.active && 'bg-primary-muted text-primary-text',\n link.disabled && 'pointer-events-none text-muted-foreground',\n 'animate-fade-rise',\n )\n \"\n >\n <div class=\"font-medium\">{{ link.label }}</div>\n <div v-if=\"link.description\" class=\"text-xs text-muted-foreground\">\n {{ link.description }}\n </div>\n </a>\n </div>\n </NavigationMenuContent>\n </RekaNavigationMenuItem>\n </NavigationMenuList>\n\n <!-- Animated indicator bar (horizontal mode only). -->\n <NavigationMenuIndicator\n v-if=\"orientation === 'horizontal'\"\n :class=\"\n cn(\n 'top-full z-10 h-0.5 bg-primary',\n 'data-[state=hidden]:animate-fade-fall data-[state=visible]:animate-fade-rise',\n )\n \"\n />\n\n <NavigationMenuViewport\n :class=\"cn('data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out')\"\n style=\"transform-origin: var(--reka-navigation-menu-viewport-transform-origin)\"\n />\n </NavigationMenuRoot>\n </nav>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A navigation link shown inside a dropdown panel. The list is data rather than
|
|
3
|
+
* markup because the primitive stays free of routing logic — the host provides
|
|
4
|
+
* `href` values and decides what navigation means.
|
|
5
|
+
*/
|
|
6
|
+
export interface NavigationMenuLink {
|
|
7
|
+
/** Visible label. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Link target. */
|
|
10
|
+
href: string;
|
|
11
|
+
/** Whether this link is the current page. */
|
|
12
|
+
active?: boolean;
|
|
13
|
+
/** Optional description shown below the label. */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Disabled state. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One top-level navigation entry. An item with `children` opens a dropdown
|
|
20
|
+
* panel; an item without them is a plain link.
|
|
21
|
+
*/
|
|
22
|
+
export interface NavigationMenuItem {
|
|
23
|
+
/** Visible label for the trigger button. */
|
|
24
|
+
label: string;
|
|
25
|
+
/** Unique key identifying this item. */
|
|
26
|
+
value: string;
|
|
27
|
+
/** The link target. When set, this item is a simple link rather than a dropdown trigger. */
|
|
28
|
+
href?: string;
|
|
29
|
+
/** Whether this item is currently active (shows aria-current). */
|
|
30
|
+
active?: boolean;
|
|
31
|
+
/** Nested navigation items shown in a dropdown panel. */
|
|
32
|
+
children?: NavigationMenuLink[];
|
|
33
|
+
/** Disabled state. */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
39
|
+
/** The top-level navigation items, in order. */
|
|
40
|
+
items: NavigationMenuItem[];
|
|
41
|
+
/** Layout direction of the trigger bar. */
|
|
42
|
+
orientation?: "horizontal" | "vertical";
|
|
43
|
+
/** Reading direction. Mirrors arrow keys when right-to-left. */
|
|
44
|
+
dir?: "ltr" | "rtl";
|
|
45
|
+
/** Drive the open trigger from the host; omit it and the menu owns its own state. */
|
|
46
|
+
modelValue?: string | undefined;
|
|
47
|
+
/** Accessible label for the nav landmark. */
|
|
48
|
+
ariaLabel?: string;
|
|
49
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
50
|
+
"update:modelValue": (value: string) => any;
|
|
51
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
52
|
+
/** The top-level navigation items, in order. */
|
|
53
|
+
items: NavigationMenuItem[];
|
|
54
|
+
/** Layout direction of the trigger bar. */
|
|
55
|
+
orientation?: "horizontal" | "vertical";
|
|
56
|
+
/** Reading direction. Mirrors arrow keys when right-to-left. */
|
|
57
|
+
dir?: "ltr" | "rtl";
|
|
58
|
+
/** Drive the open trigger from the host; omit it and the menu owns its own state. */
|
|
59
|
+
modelValue?: string | undefined;
|
|
60
|
+
/** Accessible label for the nav landmark. */
|
|
61
|
+
ariaLabel?: string;
|
|
62
|
+
}> & Readonly<{
|
|
63
|
+
"onUpdate:modelValue"?: (value: string) => any;
|
|
64
|
+
}>, {
|
|
65
|
+
modelValue: string | undefined;
|
|
66
|
+
ariaLabel: string;
|
|
67
|
+
orientation: "horizontal" | "vertical";
|
|
68
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
69
|
+
//# sourceMappingURL=NavigationMenu.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationMenu.vue.d.ts","sourceRoot":"","sources":["../../../../../packages/primitives/navigation-menu/src/NavigationMenu.vue"],"names":[],"mappings":"AA+NA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;wBACoB,OAAO,YAAY;AAAxC,wBAAyC;AACzC,QAAA,MAAM,YAAY;IAgBd,gDAAgD;WACzC,kBAAkB,EAAE;IAC3B,2CAA2C;kBAC7B,YAAY,GAAG,UAAU;IACvC,gEAAgE;UAC1D,KAAK,GAAG,KAAK;IACnB,qFAAqF;iBACxE,MAAM,GAAG,SAAS;IAC/B,6CAA6C;gBACjC,MAAM;;;;IATlB,gDAAgD;WACzC,kBAAkB,EAAE;IAC3B,2CAA2C;kBAC7B,YAAY,GAAG,UAAU;IACvC,gEAAgE;UAC1D,KAAK,GAAG,KAAK;IACnB,qFAAqF;iBACxE,MAAM,GAAG,SAAS;IAC/B,6CAA6C;gBACjC,MAAM;;;;gBAFL,MAAM,GAAG,SAAS;eAEnB,MAAM;iBANJ,YAAY,GAAG,UAAU;6EA0TvC,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { cn } from "../../../core/src/cn.js";
|
|
2
|
+
import { optional } from "../../../core/src/props.js";
|
|
3
|
+
import { listStaggerDelay } from "../../../core/src/motion.js";
|
|
4
|
+
import "../../../core/src/index.js";
|
|
5
|
+
import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
|
|
6
|
+
import { NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuRoot, NavigationMenuTrigger, NavigationMenuViewport } from "reka-ui";
|
|
7
|
+
//#region packages/primitives/navigation-menu/src/NavigationMenu.vue?vue&type=script&setup=true&lang.ts
|
|
8
|
+
var _hoisted_1 = ["aria-label", "dir"];
|
|
9
|
+
var _hoisted_2 = [
|
|
10
|
+
"href",
|
|
11
|
+
"aria-current",
|
|
12
|
+
"aria-disabled",
|
|
13
|
+
"tabindex"
|
|
14
|
+
];
|
|
15
|
+
var _hoisted_3 = { class: "flex flex-col gap-1 p-1" };
|
|
16
|
+
var _hoisted_4 = [
|
|
17
|
+
"href",
|
|
18
|
+
"aria-current",
|
|
19
|
+
"aria-disabled",
|
|
20
|
+
"tabindex"
|
|
21
|
+
];
|
|
22
|
+
var _hoisted_5 = { class: "font-medium" };
|
|
23
|
+
var _hoisted_6 = {
|
|
24
|
+
key: 0,
|
|
25
|
+
class: "text-xs text-muted-foreground"
|
|
26
|
+
};
|
|
27
|
+
var NavigationMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
28
|
+
__name: "NavigationMenu",
|
|
29
|
+
props: {
|
|
30
|
+
items: {},
|
|
31
|
+
orientation: { default: "horizontal" },
|
|
32
|
+
dir: {},
|
|
33
|
+
modelValue: { default: void 0 },
|
|
34
|
+
ariaLabel: { default: "Navigation" }
|
|
35
|
+
},
|
|
36
|
+
emits: ["update:modelValue"],
|
|
37
|
+
setup(__props) {
|
|
38
|
+
return (_ctx, _cache) => {
|
|
39
|
+
return openBlock(), createElementBlock("nav", {
|
|
40
|
+
"aria-label": __props.ariaLabel,
|
|
41
|
+
dir: __props.dir
|
|
42
|
+
}, [createVNode(unref(NavigationMenuRoot), mergeProps({ orientation: __props.orientation }, unref(optional)({
|
|
43
|
+
modelValue: __props.modelValue,
|
|
44
|
+
dir: __props.dir
|
|
45
|
+
}), {
|
|
46
|
+
class: "relative",
|
|
47
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event))
|
|
48
|
+
}), {
|
|
49
|
+
default: withCtx(() => [
|
|
50
|
+
createVNode(unref(NavigationMenuList), { class: normalizeClass(unref(cn)("flex items-center gap-0.5", __props.orientation === "vertical" && "flex-col items-stretch")) }, {
|
|
51
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
|
|
52
|
+
return openBlock(), createBlock(unref(NavigationMenuItem), {
|
|
53
|
+
key: item.value,
|
|
54
|
+
value: item.value
|
|
55
|
+
}, {
|
|
56
|
+
default: withCtx(() => [item.children && item.children.length > 0 ? (openBlock(), createBlock(unref(NavigationMenuTrigger), {
|
|
57
|
+
key: 0,
|
|
58
|
+
disabled: item.disabled ?? false,
|
|
59
|
+
"aria-haspopup": "menu",
|
|
60
|
+
class: normalizeClass(unref(cn)("min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out", "hover:bg-subtle hover:text-foreground", "focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", "data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground", "data-[state=open]:bg-subtle data-[state=open]:text-foreground"))
|
|
61
|
+
}, {
|
|
62
|
+
default: withCtx(() => [createTextVNode(toDisplayString(item.label), 1)]),
|
|
63
|
+
_: 2
|
|
64
|
+
}, 1032, ["disabled", "class"])) : (openBlock(), createBlock(unref(NavigationMenuLink), mergeProps({
|
|
65
|
+
key: 1,
|
|
66
|
+
ref_for: true
|
|
67
|
+
}, unref(optional)({
|
|
68
|
+
asChild: true,
|
|
69
|
+
active: item.active
|
|
70
|
+
}), { class: unref(cn)("min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out", "hover:bg-subtle hover:text-foreground", "focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", item.active && "bg-primary-muted text-primary-text", item.disabled && "pointer-events-none text-muted-foreground") }), {
|
|
71
|
+
default: withCtx(() => [createElementVNode("a", {
|
|
72
|
+
href: item.href,
|
|
73
|
+
"aria-current": item.active ? "page" : void 0,
|
|
74
|
+
"aria-disabled": item.disabled || void 0,
|
|
75
|
+
tabindex: item.disabled ? -1 : void 0
|
|
76
|
+
}, toDisplayString(item.label), 9, _hoisted_2)]),
|
|
77
|
+
_: 2
|
|
78
|
+
}, 1040, ["class"])), item.children && item.children.length > 0 ? (openBlock(), createBlock(unref(NavigationMenuContent), {
|
|
79
|
+
key: 2,
|
|
80
|
+
class: normalizeClass(unref(cn)("z-overlay min-w-[16rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none", "data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out")),
|
|
81
|
+
style: { "transform-origin": "var(--reka-popper-transform-origin)" }
|
|
82
|
+
}, {
|
|
83
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(item.children, (link, i) => {
|
|
84
|
+
return openBlock(), createElementBlock("a", {
|
|
85
|
+
key: link.href,
|
|
86
|
+
href: link.href,
|
|
87
|
+
"aria-current": link.active ? "page" : void 0,
|
|
88
|
+
"aria-disabled": link.disabled || void 0,
|
|
89
|
+
tabindex: link.disabled ? -1 : void 0,
|
|
90
|
+
style: normalizeStyle({ animationDelay: unref(listStaggerDelay)(i) }),
|
|
91
|
+
class: normalizeClass(unref(cn)("block rounded-sm px-2 py-1.5 text-sm text-foreground no-underline outline-none", "transition-colors duration-fast ease-out", "hover:bg-subtle", "focus-visible:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", link.active && "bg-primary-muted text-primary-text", link.disabled && "pointer-events-none text-muted-foreground", "animate-fade-rise"))
|
|
92
|
+
}, [createElementVNode("div", _hoisted_5, toDisplayString(link.label), 1), link.description ? (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(link.description), 1)) : createCommentVNode("", true)], 14, _hoisted_4);
|
|
93
|
+
}), 128))])]),
|
|
94
|
+
_: 2
|
|
95
|
+
}, 1032, ["class"])) : createCommentVNode("", true)]),
|
|
96
|
+
_: 2
|
|
97
|
+
}, 1032, ["value"]);
|
|
98
|
+
}), 128))]),
|
|
99
|
+
_: 1
|
|
100
|
+
}, 8, ["class"]),
|
|
101
|
+
__props.orientation === "horizontal" ? (openBlock(), createBlock(unref(NavigationMenuIndicator), {
|
|
102
|
+
key: 0,
|
|
103
|
+
class: normalizeClass(unref(cn)("top-full z-10 h-0.5 bg-primary", "data-[state=hidden]:animate-fade-fall data-[state=visible]:animate-fade-rise"))
|
|
104
|
+
}, null, 8, ["class"])) : createCommentVNode("", true),
|
|
105
|
+
createVNode(unref(NavigationMenuViewport), {
|
|
106
|
+
class: normalizeClass(unref(cn)("data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out")),
|
|
107
|
+
style: { "transform-origin": "var(--reka-navigation-menu-viewport-transform-origin)" }
|
|
108
|
+
}, null, 8, ["class"])
|
|
109
|
+
]),
|
|
110
|
+
_: 1
|
|
111
|
+
}, 16, ["orientation"])], 8, _hoisted_1);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
//#endregion
|
|
116
|
+
export { NavigationMenu_vue_vue_type_script_setup_true_lang_default as default };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=NavigationMenu.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationMenu.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../../../packages/primitives/navigation-menu/src/NavigationMenu.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A navigation link shown inside a dropdown panel. The list is data rather than\n * markup because the primitive stays free of routing logic — the host provides\n * `href` values and decides what navigation means.\n */\nexport interface NavigationMenuLink {\n /** Visible label. */\n label: string;\n /** Link target. */\n href: string;\n /** Whether this link is the current page. */\n active?: boolean;\n /** Optional description shown below the label. */\n description?: string;\n /** Disabled state. */\n disabled?: boolean;\n}\n\n/**\n * One top-level navigation entry. An item with `children` opens a dropdown\n * panel; an item without them is a plain link.\n */\nexport interface NavigationMenuItem {\n /** Visible label for the trigger button. */\n label: string;\n /** Unique key identifying this item. */\n value: string;\n /** The link target. When set, this item is a simple link rather than a dropdown trigger. */\n href?: string;\n /** Whether this item is currently active (shows aria-current). */\n active?: boolean;\n /** Nested navigation items shown in a dropdown panel. */\n children?: NavigationMenuLink[];\n /** Disabled state. */\n disabled?: boolean;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n NavigationMenuRoot,\n NavigationMenuList,\n NavigationMenuItem as RekaNavigationMenuItem,\n NavigationMenuTrigger,\n NavigationMenuContent,\n NavigationMenuLink as RekaNavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n} from \"reka-ui\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NavigationMenu — the WAI-ARIA navigation menu pattern: a landmark containing\n * triggers that open dropdown panels of navigation links.\n *\n * Unlike `DropdownMenu` (button → command list) or `Menubar` (horizontal strip\n * of command menus), this component is a site-navigation landmark. Its panels\n * contain links, not actions, and the whole structure is wrapped in a `<nav>`\n * landmark.\n *\n * Items are data rather than markup. A top-level item with `children` opens a\n * dropdown panel; one without renders as a direct link. The keyboard model\n * follows the APG Navigation Menu pattern: roving tabindex across triggers,\n * Left/Right arrows move between triggers, ArrowDown opens, Escape closes.\n */\nwithDefaults(\n defineProps<{\n /** The top-level navigation items, in order. */\n items: NavigationMenuItem[];\n /** Layout direction of the trigger bar. */\n orientation?: \"horizontal\" | \"vertical\";\n /** Reading direction. Mirrors arrow keys when right-to-left. */\n dir?: \"ltr\" | \"rtl\";\n /** Drive the open trigger from the host; omit it and the menu owns its own state. */\n modelValue?: string | undefined;\n /** Accessible label for the nav landmark. */\n ariaLabel?: string;\n }>(),\n // `modelValue: undefined` is load-bearing — it keeps the uncontrolled third\n // state reachable past Vue's absent-Boolean casting. `optional()` in the\n // template is the other half.\n { modelValue: undefined, orientation: \"horizontal\", ariaLabel: \"Navigation\" },\n);\n\ndefineEmits<{ \"update:modelValue\": [value: string] }>();\n</script>\n\n<template>\n <nav :aria-label=\"ariaLabel\" :dir=\"dir\">\n <NavigationMenuRoot\n :orientation=\"orientation\"\n v-bind=\"optional({ modelValue, dir })\"\n class=\"relative\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <NavigationMenuList\n :class=\"\n cn('flex items-center gap-0.5', orientation === 'vertical' && 'flex-col items-stretch')\n \"\n >\n <RekaNavigationMenuItem v-for=\"item in items\" :key=\"item.value\" :value=\"item.value\">\n <!-- Items with children get a trigger button that opens a panel. -->\n <NavigationMenuTrigger\n v-if=\"item.children && item.children.length > 0\"\n :disabled=\"item.disabled ?? false\"\n aria-haspopup=\"menu\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',\n 'data-[state=open]:bg-subtle data-[state=open]:text-foreground',\n )\n \"\n >\n {{ item.label }}\n </NavigationMenuTrigger>\n\n <!-- Simple link items wrapped in NavigationMenuLink for roving focus. -->\n <RekaNavigationMenuLink\n v-else\n v-bind=\"optional({ asChild: true, active: item.active })\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n item.active && 'bg-primary-muted text-primary-text',\n item.disabled && 'pointer-events-none text-muted-foreground',\n )\n \"\n >\n <a\n :href=\"item.href\"\n :aria-current=\"item.active ? 'page' : undefined\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : undefined\"\n >\n {{ item.label }}\n </a>\n </RekaNavigationMenuLink>\n\n <NavigationMenuContent\n v-if=\"item.children && item.children.length > 0\"\n :class=\"\n cn(\n 'z-overlay min-w-[16rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none',\n 'data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out',\n )\n \"\n style=\"transform-origin: var(--reka-popper-transform-origin)\"\n >\n <div class=\"flex flex-col gap-1 p-1\">\n <a\n v-for=\"(link, i) in item.children\"\n :key=\"link.href\"\n :href=\"link.href\"\n :aria-current=\"link.active ? 'page' : undefined\"\n :aria-disabled=\"link.disabled || undefined\"\n :tabindex=\"link.disabled ? -1 : undefined\"\n :style=\"{ animationDelay: listStaggerDelay(i) }\"\n :class=\"\n cn(\n 'block rounded-sm px-2 py-1.5 text-sm text-foreground no-underline outline-none',\n 'transition-colors duration-fast ease-out',\n 'hover:bg-subtle',\n 'focus-visible:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n link.active && 'bg-primary-muted text-primary-text',\n link.disabled && 'pointer-events-none text-muted-foreground',\n 'animate-fade-rise',\n )\n \"\n >\n <div class=\"font-medium\">{{ link.label }}</div>\n <div v-if=\"link.description\" class=\"text-xs text-muted-foreground\">\n {{ link.description }}\n </div>\n </a>\n </div>\n </NavigationMenuContent>\n </RekaNavigationMenuItem>\n </NavigationMenuList>\n\n <!-- Animated indicator bar (horizontal mode only). -->\n <NavigationMenuIndicator\n v-if=\"orientation === 'horizontal'\"\n :class=\"\n cn(\n 'top-full z-10 h-0.5 bg-primary',\n 'data-[state=hidden]:animate-fade-fall data-[state=visible]:animate-fade-rise',\n )\n \"\n />\n\n <NavigationMenuViewport\n :class=\"cn('data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out')\"\n style=\"transform-origin: var(--reka-navigation-menu-viewport-transform-origin)\"\n />\n </NavigationMenuRoot>\n </nav>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FE,OAAA,UAAA,GAAA,mBAgHM,OAAA;IAhHA,cAAY,QAAA;IAAY,KAAK,QAAA;GACjC,GAAA,CAAA,YA8GqB,MAAA,kBAAA,GA9GrB,WA8GqB,EA7GlB,aAAa,QAAA,YAAW,GACjB,MAAA,QAAA,CAAQ,CAAA;IAAA,YAAG,QAAA;IAAU,KAAE,QAAA;GAAG,CAAA,GAAA;IAClC,OAAM;IACL,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAEA,KAAAA,MAAK,qBAAsB,MAAM;;IAEtD,SAAA,cAuFqB;KAvFrB,YAuFqB,MAAA,kBAAA,GAAA,EAtFlB,OAAK,eAAa,MAAA,EAAA,CAAE,CAAA,6BAA8B,QAAA,gBAAW,cAAA,wBAAA,CAAA,EAAA,GAAA;MAItC,SAAA,cAAqB,EAA7C,UAAA,IAAA,GAAA,mBAiFyB,UAAA,MAAA,WAjFc,QAAA,QAAR,SAAI;OAAnC,OAAA,UAAA,GAAA,YAiFyB,MAAA,kBAAA,GAAA;QAjFsB,KAAK,KAAK;QAAQ,OAAO,KAAK;;QAE3E,SAAA,cAewB,CAdhB,KAAK,YAAY,KAAK,SAAS,SAAM,KAD7C,UAAA,GAAA,YAewB,MAAA,qBAAA,GAAA;;SAbrB,UAAU,KAAK,YAAQ;SACxB,iBAAc;SACb,OAAK,eAAiB,MAAA,EAAA,CAAE,CAAA,uGAAA,yCAAA,qKAAA,6EAAA,+DAAA,CAAA;;SAUzB,SAAA,cAAgB,CAAb,gBAAA,gBAAA,KAAK,KAAK,GAAA,CAAA,CAAA,CAAA;;QAIf,GAAA,MAAA,CAAA,YAAA,OAAA,CAAA,MAAA,UAAA,GAAA,YAqByB,MAAA,kBAAA,GArBzB,WAqByB;;;QAnBf,GAAA,MAAA,QAAA,CAAQ,CAAA;SAAA,SAAA;SAAA,QAA0B,KAAK;QAAM,CAAA,GAAA,EACpD,OAAsB,MAAA,EAAA,CAAE,CAAA,uGAAA,yCAAA,qKAAuX,KAAK,UAAM,sCAA0D,KAAK,YAAQ,2CAAA,EAAA,CAAA,GAAA;SAUle,SAAA,cAOI,CAPJ,mBAOI,KAAA;UAND,MAAM,KAAK;UACX,gBAAc,KAAK,SAAM,SAAY,KAAA;UACrC,iBAAe,KAAK,YAAY,KAAA;UAChC,UAAU,KAAK,WAAQ,KAAQ,KAAA;SAE7B,GAAA,gBAAA,KAAK,KAAK,GAAA,GAAA,UAAA,CAAA,CAAA;;QAKT,GAAA,MAAA,CAAA,OAAA,CAAA,IAAA,KAAK,YAAY,KAAK,SAAS,SAAM,KAD7C,UAAA,GAAA,YAqCwB,MAAA,qBAAA,GAAA;;SAnCrB,OAAK,eAAiB,MAAA,EAAA,CAAE,CAAA,yHAAA,0EAAA,CAAA;SAMzB,OAAA,EAAA,oBAAA,sCAAA;;SAEA,SAAA,cA0BM,CA1BN,mBA0BM,OA1BN,YA0BM,EAzBJ,UAAA,IAAA,GAAA,mBAwBI,UAAA,MAAA,WAvBkB,KAAK,WAAjB,MAAM,MAAC;UADjB,OAAA,UAAA,GAAA,mBAwBI,KAAA;WAtBD,KAAK,KAAK;WACV,MAAM,KAAK;WACX,gBAAc,KAAK,SAAM,SAAY,KAAA;WACrC,iBAAe,KAAK,YAAY,KAAA;WAChC,UAAU,KAAK,WAAQ,KAAQ,KAAA;WAC/B,OAAK,eAAA,EAAA,gBAAoB,MAAA,gBAAA,CAAgB,CAAC,CAAC,EAAA,CAAA;WAC3C,OAAK,eAAqB,MAAA,EAAA,CAAE,CAAA,kFAAA,4CAAA,mBAAA,uIAA8X,KAAK,UAAM,sCAA8D,KAAK,YAAQ,6CAAA,mBAAA,CAAA;UAYjf,GAAA,CAAA,mBAA+C,OAA/C,YAA+C,gBAAnB,KAAK,KAAK,GAAA,CAAA,GAC3B,KAAK,eAAhB,UAAA,GAAA,mBAEM,OAFN,YAEM,gBADD,KAAK,WAAW,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,IAAA,UAAA;;;;;;;;;KAUvB,QAAA,gBAAW,gBADnB,UAAA,GAAA,YAQE,MAAA,uBAAA,GAAA;;MANC,OAAK,eAAa,MAAA,EAAA,CAAE,CAAA,kCAAA,8EAAA,CAAA;;KAQvB,YAGE,MAAA,sBAAA,GAAA;MAFC,OAAK,eAAE,MAAA,EAAA,CAAE,CAAA,0EAAA,CAAA;MACV,OAAA,EAAA,oBAAA,wDAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/primitives/navigation-menu/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControl.js","names":[],"sources":["../../../../../packages/primitives/segmented-control/src/SegmentedControl.vue"],"sourcesContent":["<script lang=\"ts\">\n/** One segment in a {@link SegmentedControl}. */\nexport interface SegmentedControlOption {\n /** Matched against `modelValue`. */\n value: string;\n /** Visible label, rendered inside the segment. */\n label: string;\n /** Disables this segment alone, leaving its neighbours pickable. */\n disabled?: boolean;\n /** Forwarded to the segment's DOM node — lets hosts keep stable test hooks. */\n testId?: string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { nextTick, onMounted, onUnmounted, reactive, ref, useTemplateRef, watch } from \"vue\";\nimport { RadioGroupRoot, RadioGroupItem } from \"reka-ui\";\nimport { cn, optional } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\n\n/**\n * SegmentedControl — pick one of 2-5 mutually exclusive options, all visible\n * at once; one is always active. Built on Reka UI's RadioGroup (single\n * choice, roving tabindex — the group is one Tab stop, not N — with\n * arrow-key navigation). Styled with Loom tokens.\n *\n * Inside a [Field](../Field/Field.vue) the row's description, `name`,\n * `required` and `invalid` reach the group through `useFieldControl()`.\n *\n * **A row's label does not name this group.** `<label for>` names a labelable\n * element and this renders a `div[role=\"radiogroup\"]`, so the row's label\n * resolves to the element and names nobody. Name it with a\n * [Fieldset](../Fieldset/Fieldset.vue)'s real `<legend>`, or with an\n * `aria-label`/`aria-labelledby` of its own — which is what every example\n * already does, because the control carries no label text.\n *\n * There is no `readonly`, and a row's is ignored rather than approximated: each\n * segment is a `<button role=\"radio\">` with no native read-only state to put it\n * in, and one segment is always active, so a read-only control here would be a\n * disabled one showing an answer.\n */\nconst props = withDefaults(\n defineProps<{\n /** The selected option's `value`. */\n modelValue?: string;\n /** The segments to render, in order. */\n options: SegmentedControlOption[];\n /** Disables every segment at once, distinct from a single option's own `disabled`. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** `\"sm\"` is the compressed form for dense chrome — e.g. a status bar density level. */\n size?: \"default\" | \"sm\";\n }>(),\n {\n // Not `false`: absent has to stay tellable from `false` for a Field above\n // to disable the control and for `:disabled=\"false\"` to overrule one that\n // does. Vue casts an absent Boolean prop with no declared default to\n // `false`, which would leave the context wired and inert.\n disabled: undefined,\n size: \"default\",\n },\n);\n\ndefineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: groupAttrs } = useSplitAttrs();\n\n/**\n * The segments are real `<button>`s, so a `<fieldset disabled>` above this\n * control already makes each of them inert — but Reka's roving focus is what\n * decides where Tab lands, and it is driven by the group's own `disabled`,\n * which the fieldset never reaches. Measured on this branch: every segment went\n * `:disabled` while the `role=\"radiogroup\"` container kept `tabindex=\"0\"`, so\n * Tab still stopped on an unavailable control and left the reader nowhere. Its\n * own `disabled` prop sets that to `-1`.\n *\n * Reading the group state here rather than painting it with a CSS variant is\n * also what keeps the appearance honest, and this is the control where the\n * distinction bites: one option may carry `disabled` of its own, so \"a\n * `:disabled` button is inside this track\" and \"this track is unavailable\" are\n * different facts. Only the fieldset says the second.\n *\n * Read off the DOM rather than taken from the Field context, which publishes no\n * `disabled` on purpose — see `../../lib/ancestor-disabled.ts`.\n */\nconst root = useTemplateRef<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// One rendered node, so the split is only about `class` — a caller's class\n// needs a Tailwind-aware merge rather than Vue's concatenating fallthrough.\n//\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values; `field.attrs` then spreads\n// **after** `groupAttrs`, and that position is the contract (see TextField.vue).\n//\n// `name`, `required` and `invalid` are omitted because this component has no\n// prop for any of them — the row's answer stands unopposed, and its `name`\n// reaches RadioGroupRoot's own prop, which mints the hidden input a `<form>`\n// submits. `readonly` is omitted for the reason the docblock gives.\n//\n// The resolved `required` then reaches the group through Reka's own prop rather\n// than the `aria-required` in `field.attrs`: RadioGroupRoot writes that\n// attribute itself, so leaving it to the bag is a race with a `false` — see\n// Checkbox.vue, where the merge order makes the same race a certainty.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // segments themselves.\n disabled: groupDisabled.value ? true : props.disabled,\n}));\n\n// Shared sliding indicator behind the checked segment. Measured off the live\n// DOM (offsetLeft/offsetWidth of the [data-state=checked] item), not derived\n// from index/width math, so \"sm\"/\"default\" sizing and variable-width labels\n// stay correct without per-size indicator code.\nconst indicatorEl = ref<HTMLElement | null>(null);\nconst indicatorStyle = reactive({ left: \"0px\", width: \"0px\", opacity: \"0\" });\n\nlet resizeObserver: ResizeObserver | undefined;\n\n// Re-targets onto the checked item itself (not the container) so a late\n// width settle on exactly that element — e.g. the `font-medium` weight swap\n// on data-[state=checked] reflowing after this function's own read — is\n// caught by the observer and re-measured, instead of going unnoticed because\n// the container's total box happened not to change.\nfunction updateIndicator() {\n const container = indicatorEl.value?.parentElement;\n const active = container?.querySelector<HTMLElement>('[role=\"radio\"][data-state=\"checked\"]');\n if (!active) {\n indicatorStyle.opacity = \"0\";\n resizeObserver?.disconnect();\n return;\n }\n indicatorStyle.left = `${active.offsetLeft}px`;\n indicatorStyle.width = `${active.offsetWidth}px`;\n indicatorStyle.opacity = \"1\";\n resizeObserver?.disconnect();\n resizeObserver?.observe(active);\n}\n\nonMounted(() => {\n resizeObserver = new ResizeObserver(updateIndicator);\n updateIndicator();\n});\nonUnmounted(() => resizeObserver?.disconnect());\n\n// One watcher for both re-measure triggers, and deliberately **not** deep on\n// `options`. The two separate watches each scheduled the same\n// `nextTick(updateIndicator)`, so one host tick that changed both inputs paid\n// the measurement twice; merging them costs nothing and removes the double.\n// Dropping `deep:` is not a regression: an in-place mutation of an option\n// object only matters to the indicator if it changes a *width* — a label edit,\n// say — and the ResizeObserver above is already watching exactly the checked\n// element for that, so the deep traversal bought nothing the observer does not\n// deliver sooner. A new array identity or a new selection still fires this.\nwatch([() => props.modelValue, () => props.options], () => nextTick(updateIndicator));\n</script>\n\n<template>\n <RadioGroupRoot\n ref=\"root\"\n v-bind=\"{ ...optional({ modelValue }), ...groupAttrs, ...field.attrs }\"\n :disabled=\"field.disabled\"\n :required=\"field.required\"\n orientation=\"horizontal\"\n :class=\"\n cn(\n 'relative inline-flex items-center gap-0.5 rounded-md border border-input bg-muted p-0.5',\n // The one thing the *track* has left to say. Its fill is already\n // `bg-muted` — the well every other unavailable control drains to — so\n // the fill channel is spent before the state arrives, and the segments\n // inside carry the rest. The rim slackening from `input` to the lighter\n // `border` is what makes a wholly unavailable control tellable from an\n // available one at the group's own edge rather than only cell by cell.\n field.disabled && 'cursor-not-allowed border-border',\n attrs.class as string,\n )\n \"\n @update:model-value=\"$emit('update:modelValue', String($event))\"\n >\n <span\n ref=\"indicatorEl\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute inset-y-0.5 rounded-sm bg-card shadow-sm\"\n style=\"\n transition:\n left var(--duration-fast) var(--ease-spring),\n width var(--duration-fast) var(--ease-spring);\n \"\n :style=\"{\n left: indicatorStyle.left,\n width: indicatorStyle.width,\n opacity: indicatorStyle.opacity,\n }\"\n />\n <RadioGroupItem\n v-for=\"opt in options\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled ?? false\"\n v-bind=\"optional({ 'data-testid': opt.testId })\"\n style=\"\n transition:\n transform var(--duration-fast) var(--ease-spring),\n color var(--duration-fast) var(--ease-out);\n \"\n :class=\"\n cn(\n // Named, not the bare `group`: a segmented control is dropped into\n // application chrome, and a bare `group-*` would also answer to any\n // ancestor a host happened to mark up as one.\n // `min-h-6` is the WCAG 2.5.8 target floor made explicit: the sm\n // segment's box was previously a product of whatever line-height the\n // surrounding text happened to hand down, and swapping to the named\n // `text-micro` (line-height 1.4) dropped it to 23px. A floor that\n // holds regardless of type metrics is the honest encoding.\n 'group/segment relative z-10 inline-flex min-h-6 items-center justify-center rounded-sm text-muted-foreground',\n size === 'sm' ? 'px-1.5 py-px text-micro' : 'px-3 py-1 text-sm',\n 'data-[state=checked]:font-medium data-[state=checked]:text-foreground',\n 'data-[state=unchecked]:hover:bg-subtle',\n 'active:scale-press',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n // No `opacity` here. A segment *is* its label — there is no glyph and\n // no fill of its own to dim — so `disabled:opacity-50` had nothing to\n // act on but the text, taking an unchecked segment to 1.96:1 and a\n // chosen one to 3.13:1 on its thumb.\n //\n // Unavailability is a fill instead, and it is the one step this\n // system already treats as meaningful: the cell drains up a rung of\n // the elevation rhythm, from the track's `bg-muted` to the page's own\n // `bg-background`, so it reads as a hole in the track rather than as\n // part of it. On a *chosen* segment that same fill covers the raised\n // white thumb, which is the whole signal — the value is still shown\n // and it is visibly no longer settable.\n 'disabled:pointer-events-none data-[disabled]:bg-background',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the segment, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:` in\n Tailwind's variant order, so a disabled segment that is also the\n chosen one would take `text-foreground` from the button — and a\n colour set on the button is only inherited here. Declared on the span\n it wins whatever the segment resolved to: 5.25:1 over the drained\n fill, against the 1.96:1 the dim used to leave. -->\n <span class=\"group-data-[disabled]/segment:text-muted-foreground\">{{ opt.label }}</span>\n </RadioGroupItem>\n </RadioGroupRoot>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"SegmentedControl.js","names":[],"sources":["../../../../../packages/primitives/segmented-control/src/SegmentedControl.vue"],"sourcesContent":["<script lang=\"ts\">\n/** One segment in a {@link SegmentedControl}. */\nexport interface SegmentedControlOption {\n /** Matched against `modelValue`. */\n value: string;\n /** Visible label, rendered inside the segment. */\n label: string;\n /** Disables this segment alone, leaving its neighbours pickable. */\n disabled?: boolean;\n /** Forwarded to the segment's DOM node — lets hosts keep stable test hooks. */\n testId?: string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { nextTick, onMounted, onUnmounted, reactive, ref, useTemplateRef, watch } from \"vue\";\nimport { RadioGroupRoot, RadioGroupItem } from \"reka-ui\";\nimport { cn, optional } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\n\n/**\n * SegmentedControl — pick one of 2-5 mutually exclusive options, all visible\n * at once; one is always active. Built on Reka UI's RadioGroup (single\n * choice, roving tabindex — the group is one Tab stop, not N — with\n * arrow-key navigation). Styled with Loom tokens.\n *\n * Inside a [Field](../Field/Field.vue) the row's description, `name`,\n * `required` and `invalid` reach the group through `useFieldControl()`.\n *\n * **A row's label does not name this group.** `<label for>` names a labelable\n * element and this renders a `div[role=\"radiogroup\"]`, so the row's label\n * resolves to the element and names nobody. Name it with a\n * [Fieldset](../Fieldset/Fieldset.vue)'s real `<legend>`, or with an\n * `aria-label`/`aria-labelledby` of its own — which is what every example\n * already does, because the control carries no label text.\n *\n * There is no `readonly`, and a row's is ignored rather than approximated: each\n * segment is a `<button role=\"radio\">` with no native read-only state to put it\n * in, and one segment is always active, so a read-only control here would be a\n * disabled one showing an answer.\n */\nconst props = withDefaults(\n defineProps<{\n /** The selected option's `value`. */\n modelValue?: string;\n /** The segments to render, in order. */\n options: SegmentedControlOption[];\n /** Disables every segment at once, distinct from a single option's own `disabled`. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** `\"sm\"` is the compressed form for dense chrome — e.g. a status bar density level. */\n size?: \"default\" | \"sm\";\n }>(),\n {\n // Not `false`: absent has to stay tellable from `false` for a Field above\n // to disable the control and for `:disabled=\"false\"` to overrule one that\n // does. Vue casts an absent Boolean prop with no declared default to\n // `false`, which would leave the context wired and inert.\n disabled: undefined,\n size: \"default\",\n },\n);\n\ndefineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: groupAttrs } = useSplitAttrs();\n\n/**\n * The segments are real `<button>`s, so a `<fieldset disabled>` above this\n * control already makes each of them inert — but Reka's roving focus is what\n * decides where Tab lands, and it is driven by the group's own `disabled`,\n * which the fieldset never reaches. Measured on this branch: every segment went\n * `:disabled` while the `role=\"radiogroup\"` container kept `tabindex=\"0\"`, so\n * Tab still stopped on an unavailable control and left the reader nowhere. Its\n * own `disabled` prop sets that to `-1`.\n *\n * Reading the group state here rather than painting it with a CSS variant is\n * also what keeps the appearance honest, and this is the control where the\n * distinction bites: one option may carry `disabled` of its own, so \"a\n * `:disabled` button is inside this track\" and \"this track is unavailable\" are\n * different facts. Only the fieldset says the second.\n *\n * Read off the DOM rather than taken from the Field context, which publishes no\n * `disabled` on purpose — see `../../lib/ancestor-disabled.ts`.\n */\nconst root = useTemplateRef<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// One rendered node, so the split is only about `class` — a caller's class\n// needs a Tailwind-aware merge rather than Vue's concatenating fallthrough.\n//\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values; `field.attrs` then spreads\n// **after** `groupAttrs`, and that position is the contract (see TextField.vue).\n//\n// `name`, `required` and `invalid` are omitted because this component has no\n// prop for any of them — the row's answer stands unopposed, and its `name`\n// reaches RadioGroupRoot's own prop, which mints the hidden input a `<form>`\n// submits. `readonly` is omitted for the reason the docblock gives.\n//\n// The resolved `required` then reaches the group through Reka's own prop rather\n// than the `aria-required` in `field.attrs`: RadioGroupRoot writes that\n// attribute itself, so leaving it to the bag is a race with a `false` — see\n// Checkbox.vue, where the merge order makes the same race a certainty.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // segments themselves.\n disabled: groupDisabled.value ? true : props.disabled,\n}));\n\n// Shared sliding indicator behind the checked segment. Measured off the live\n// DOM (offsetLeft/offsetWidth of the [data-state=checked] item), not derived\n// from index/width math, so \"sm\"/\"default\" sizing and variable-width labels\n// stay correct without per-size indicator code.\nconst indicatorEl = ref<HTMLElement | null>(null);\nconst indicatorStyle = reactive({ left: \"0px\", width: \"0px\", opacity: \"0\" });\n\nlet resizeObserver: ResizeObserver | undefined;\n\n// Re-targets onto the checked item itself (not the container) so a late\n// width settle on exactly that element — e.g. the `font-medium` weight swap\n// on data-[state=checked] reflowing after this function's own read — is\n// caught by the observer and re-measured, instead of going unnoticed because\n// the container's total box happened not to change.\nfunction updateIndicator() {\n const container = indicatorEl.value?.parentElement;\n const active = container?.querySelector<HTMLElement>('[role=\"radio\"][data-state=\"checked\"]');\n if (!active) {\n indicatorStyle.opacity = \"0\";\n resizeObserver?.disconnect();\n return;\n }\n indicatorStyle.left = `${active.offsetLeft}px`;\n indicatorStyle.width = `${active.offsetWidth}px`;\n indicatorStyle.opacity = \"1\";\n resizeObserver?.disconnect();\n resizeObserver?.observe(active);\n}\n\nonMounted(() => {\n resizeObserver = new ResizeObserver(updateIndicator);\n updateIndicator();\n});\nonUnmounted(() => resizeObserver?.disconnect());\n\n// One watcher for both re-measure triggers, and deliberately **not** deep on\n// `options`. The two separate watches each scheduled the same\n// `nextTick(updateIndicator)`, so one host tick that changed both inputs paid\n// the measurement twice; merging them costs nothing and removes the double.\n// Dropping `deep:` is not a regression: an in-place mutation of an option\n// object only matters to the indicator if it changes a *width* — a label edit,\n// say — and the ResizeObserver above is already watching exactly the checked\n// element for that, so the deep traversal bought nothing the observer does not\n// deliver sooner. A new array identity or a new selection still fires this.\nwatch([() => props.modelValue, () => props.options], () => nextTick(updateIndicator));\n</script>\n\n<template>\n <RadioGroupRoot\n ref=\"root\"\n v-bind=\"{ ...optional({ modelValue }), ...groupAttrs, ...field.attrs }\"\n :disabled=\"field.disabled\"\n :required=\"field.required\"\n orientation=\"horizontal\"\n :class=\"\n cn(\n 'relative inline-flex items-center gap-0.5 rounded-md border border-input bg-muted p-0.5',\n // The one thing the *track* has left to say. Its fill is already\n // `bg-muted` — the well every other unavailable control drains to — so\n // the fill channel is spent before the state arrives, and the segments\n // inside carry the rest. The rim slackening from `input` to the lighter\n // `border` is what makes a wholly unavailable control tellable from an\n // available one at the group's own edge rather than only cell by cell.\n field.disabled && 'cursor-not-allowed border-border',\n attrs.class as string,\n )\n \"\n @update:model-value=\"$emit('update:modelValue', String($event))\"\n >\n <span\n ref=\"indicatorEl\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute inset-y-0.5 rounded-sm bg-card shadow-sm\"\n style=\"\n transition:\n left var(--duration-fast) var(--ease-spring),\n width var(--duration-fast) var(--ease-spring);\n \"\n :style=\"{\n left: indicatorStyle.left,\n width: indicatorStyle.width,\n opacity: indicatorStyle.opacity,\n }\"\n />\n <RadioGroupItem\n v-for=\"opt in options\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled ?? false\"\n v-bind=\"optional({ 'data-testid': opt.testId })\"\n style=\"\n transition:\n transform var(--duration-fast) var(--ease-spring),\n color var(--duration-fast) var(--ease-out);\n \"\n :class=\"\n cn(\n // Named, not the bare `group`: a segmented control is dropped into\n // application chrome, and a bare `group-*` would also answer to any\n // ancestor a host happened to mark up as one.\n // `min-h-6`/`min-w-6` are the WCAG 2.5.8 target floors made\n // explicit: the sm segment's box was previously a product of whatever\n // line-height the surrounding text happened to hand down — swapping\n // to the named `text-micro` (line-height 1.4) dropped it to 23px,\n // and a micro label (a one-character density toggle) collapses the\n // width under 24 too. Floors that hold regardless of type metrics\n // are the honest encoding.\n 'group/segment relative z-10 inline-flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground',\n size === 'sm' ? 'px-1.5 py-px text-micro' : 'px-3 py-1 text-sm',\n 'data-[state=checked]:font-medium data-[state=checked]:text-foreground',\n 'data-[state=unchecked]:hover:bg-subtle',\n 'active:scale-press',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n // No `opacity` here. A segment *is* its label — there is no glyph and\n // no fill of its own to dim — so `disabled:opacity-50` had nothing to\n // act on but the text, taking an unchecked segment to 1.96:1 and a\n // chosen one to 3.13:1 on its thumb.\n //\n // Unavailability is a fill instead, and it is the one step this\n // system already treats as meaningful: the cell drains up a rung of\n // the elevation rhythm, from the track's `bg-muted` to the page's own\n // `bg-background`, so it reads as a hole in the track rather than as\n // part of it. On a *chosen* segment that same fill covers the raised\n // white thumb, which is the whole signal — the value is still shown\n // and it is visibly no longer settable.\n 'disabled:pointer-events-none data-[disabled]:bg-background',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the segment, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:` in\n Tailwind's variant order, so a disabled segment that is also the\n chosen one would take `text-foreground` from the button — and a\n colour set on the button is only inherited here. Declared on the span\n it wins whatever the segment resolved to: 5.25:1 over the drained\n fill, against the 1.96:1 the dim used to leave. -->\n <span class=\"group-data-[disabled]/segment:text-muted-foreground\">{{ opt.label }}</span>\n </RadioGroupItem>\n </RadioGroupRoot>\n</template>\n"],"mappings":""}
|