@vizejs/ui 0.443.0 → 0.454.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +31 -0
  2. package/dist/accordion.d.mts +1 -1
  3. package/dist/action-sheet.d.mts +1 -1
  4. package/dist/alert-dialog.d.mts +1 -1
  5. package/dist/{announcer-D0bFq-HV.d.mts → announcer-DcI680jo.d.mts} +3351 -2952
  6. package/dist/announcer.d.mts +1 -1
  7. package/dist/autocomplete.d.mts +1 -1
  8. package/dist/avatar.d.mts +1 -1
  9. package/dist/badge.d.mts +1 -1
  10. package/dist/banner.d.mts +1 -1
  11. package/dist/block-ui.d.mts +1 -1
  12. package/dist/blockquote.d.mts +1 -1
  13. package/dist/{breadcrumb-DJIZ3zDG.d.mts → breadcrumb-Cdyh20z-.d.mts} +1 -1
  14. package/dist/breadcrumb.d.mts +1 -1
  15. package/dist/{calendar-C_ihbcsR.mjs → calendar-BaK_yoKC.mjs} +3 -3
  16. package/dist/{calendar-root-Dy3WPuNT.mjs → calendar-root-Bl1sv8qy.mjs} +2 -2
  17. package/dist/calendar.d.mts +1 -1
  18. package/dist/calendar.mjs +2 -2
  19. package/dist/callout.d.mts +1 -1
  20. package/dist/card.d.mts +1 -1
  21. package/dist/catalog.d.mts +47 -1
  22. package/dist/catalog.mjs +234 -119
  23. package/dist/chart.d.mts +1 -1
  24. package/dist/checkbox.d.mts +1 -1
  25. package/dist/code.d.mts +1 -1
  26. package/dist/collapsible.d.mts +1 -1
  27. package/dist/color-picker.d.mts +1 -1
  28. package/dist/combobox.d.mts +1 -1
  29. package/dist/command-palette.d.mts +1 -1
  30. package/dist/confirm.d.mts +1 -1
  31. package/dist/context-menu.d.mts +1 -1
  32. package/dist/copy-button.d.mts +1 -1
  33. package/dist/{date-picker-Bwt03r8S.mjs → date-picker-BsYXMErN.mjs} +1 -1
  34. package/dist/date-picker.mjs +1 -1
  35. package/dist/{date-range-picker-BBHrOLAU.mjs → date-range-picker-H2q9rFOA.mjs} +1 -1
  36. package/dist/date-range-picker.d.mts +1 -1
  37. package/dist/date-range-picker.mjs +1 -1
  38. package/dist/dialog.d.mts +1 -1
  39. package/dist/direction.d.mts +1 -1
  40. package/dist/drawer.d.mts +1 -1
  41. package/dist/dropdown-menu.d.mts +1 -1
  42. package/dist/{duration-field-BmykCKTC.mjs → duration-field-DowlJQvz.mjs} +3 -3
  43. package/dist/duration-field.d.mts +1 -1
  44. package/dist/duration-field.mjs +1 -1
  45. package/dist/empty-state.d.mts +1 -1
  46. package/dist/fieldset.d.mts +1 -1
  47. package/dist/file-upload.d.mts +1 -1
  48. package/dist/floating-action-button.d.mts +1 -1
  49. package/dist/focus-visible.d.mts +1 -1
  50. package/dist/fullscreen-button.d.mts +1 -1
  51. package/dist/heading.d.mts +1 -1
  52. package/dist/hover-card.d.mts +1 -1
  53. package/dist/icon-button.d.mts +1 -1
  54. package/dist/icon.d.mts +1 -1
  55. package/dist/{image-D9CEf8uD.d.mts → image-DCQeTHqi.d.mts} +2 -2
  56. package/dist/{image-cropper-BJsgLDjH.d.mts → image-cropper-BPAHa995.d.mts} +2 -2
  57. package/dist/image-cropper.d.mts +1 -1
  58. package/dist/image.d.mts +1 -1
  59. package/dist/index.d.mts +55 -55
  60. package/dist/index.mjs +14 -12
  61. package/dist/{input-C0fgeRnS.d.mts → input-D4Zvmf67.d.mts} +2 -2
  62. package/dist/input.d.mts +1 -1
  63. package/dist/kbd.d.mts +1 -1
  64. package/dist/landmark.d.mts +1 -1
  65. package/dist/list.d.mts +1 -1
  66. package/dist/listbox.d.mts +1 -1
  67. package/dist/locale.d.mts +1 -1
  68. package/dist/media-preferences.d.mts +1 -1
  69. package/dist/menu.d.mts +1 -1
  70. package/dist/menubar.d.mts +1 -1
  71. package/dist/{meter-ZZIKQTLX.mjs → meter-CxHgTYvs.mjs} +3 -2
  72. package/dist/{meter-B3m17_8g.d.mts → meter-LxMMvjjK.d.mts} +8 -1
  73. package/dist/meter.d.mts +1 -1
  74. package/dist/meter.mjs +1 -1
  75. package/dist/month-picker.d.mts +1 -1
  76. package/dist/native-select.d.mts +1 -1
  77. package/dist/navigation-rail-BBg5bin7.mjs +246 -0
  78. package/dist/navigation-rail.d.mts +2 -0
  79. package/dist/navigation-rail.mjs +2 -0
  80. package/dist/notification-center.d.mts +1 -1
  81. package/dist/pager.d.mts +1 -1
  82. package/dist/pagination.d.mts +1 -1
  83. package/dist/popconfirm.d.mts +1 -1
  84. package/dist/popover.d.mts +1 -1
  85. package/dist/print-button.d.mts +1 -1
  86. package/dist/{progress-bar-lXHO-U8x.mjs → progress-bar-DsH4Jgn2.mjs} +3 -1
  87. package/dist/progress-bar.d.mts +1 -1
  88. package/dist/progress-bar.mjs +1 -1
  89. package/dist/radio-group.d.mts +1 -1
  90. package/dist/{range-calendar-DL_eKx8L.d.mts → range-calendar-DInfduyf.d.mts} +2 -2
  91. package/dist/{range-calendar-root-CEhaPJAd.mjs → range-calendar-root-D9ZoZkWc.mjs} +2 -2
  92. package/dist/range-calendar.d.mts +1 -1
  93. package/dist/range-calendar.mjs +1 -1
  94. package/dist/resolver.d.mts +23 -1
  95. package/dist/resolver.mjs +23 -1
  96. package/dist/scroll-area.d.mts +1 -1
  97. package/dist/{search-field-zyMCYvZ2.d.mts → search-field-DzW31UHM.d.mts} +4 -4
  98. package/dist/search-field.d.mts +1 -1
  99. package/dist/select.d.mts +1 -1
  100. package/dist/share-button.d.mts +1 -1
  101. package/dist/sidebar.d.mts +1 -1
  102. package/dist/split-button-Cff_lsza.mjs +339 -0
  103. package/dist/split-button.d.mts +2 -0
  104. package/dist/split-button.mjs +3 -0
  105. package/dist/splitter.d.mts +1 -1
  106. package/dist/status-light.d.mts +1 -1
  107. package/dist/stepper.d.mts +1 -1
  108. package/dist/surface.d.mts +1 -1
  109. package/dist/switch.d.mts +1 -1
  110. package/dist/tabs.d.mts +1 -1
  111. package/dist/tags-input.d.mts +1 -1
  112. package/dist/text.d.mts +1 -1
  113. package/dist/{textarea-4eIDNcvv.d.mts → textarea-D7V5lLPX.d.mts} +2 -2
  114. package/dist/textarea.d.mts +1 -1
  115. package/dist/toggle.d.mts +1 -1
  116. package/dist/tooltip.d.mts +1 -1
  117. package/dist/tour.d.mts +1 -1
  118. package/dist/tree.d.mts +1 -1
  119. package/dist/{week-picker-BUduCITx.mjs → week-picker-Ce2qvuBO.mjs} +2 -2
  120. package/dist/week-picker.d.mts +1 -1
  121. package/dist/week-picker.mjs +1 -1
  122. package/package.json +16 -5
  123. package/registry/files/families/actions/split-button/examples/split-button-basic.vue +35 -0
  124. package/registry/files/families/actions/split-button/split-button-action.vue +102 -0
  125. package/registry/files/families/actions/split-button/split-button-context.ts +17 -0
  126. package/registry/files/families/actions/split-button/split-button-root.vue +191 -0
  127. package/registry/files/families/actions/split-button/split-button-trigger.vue +140 -0
  128. package/registry/files/families/actions/split-button/split-button-types.ts +40 -0
  129. package/registry/files/families/actions/split-button/split-button.ts +40 -0
  130. package/registry/files/families/date-time/calendar/calendar-multiple-root.vue +2 -2
  131. package/registry/files/families/date-time/calendar/calendar-root.vue +2 -2
  132. package/registry/files/families/date-time/duration-field/duration-field.vue +3 -3
  133. package/registry/files/families/date-time/range-calendar/range-calendar-root.vue +2 -2
  134. package/registry/files/families/date-time/week-picker/week-picker-root.vue +2 -2
  135. package/registry/files/families/feedback/meter/meter-state.ts +10 -2
  136. package/registry/files/families/feedback/meter/meter-types.ts +4 -1
  137. package/registry/files/families/feedback/meter/meter.vue +2 -0
  138. package/registry/files/families/feedback/progress-bar/progress-bar-state.ts +11 -1
  139. package/registry/files/families/feedback/progress-bar/progress-bar-types.ts +6 -2
  140. package/registry/files/families/feedback/progress-bar/progress-bar.vue +6 -1
  141. package/registry/files/families/menus/menu/menu-types.ts +1 -1
  142. package/registry/files/families/navigation/navigation-rail/examples/navigation-rail-basic.vue +21 -0
  143. package/registry/files/families/navigation/navigation-rail/navigation-rail-context.ts +17 -0
  144. package/registry/files/families/navigation/navigation-rail/navigation-rail-item.vue +89 -0
  145. package/registry/files/families/navigation/navigation-rail/navigation-rail-toggle.vue +50 -0
  146. package/registry/files/families/navigation/navigation-rail/navigation-rail-types.ts +23 -0
  147. package/registry/files/families/navigation/navigation-rail/navigation-rail.ts +11 -0
  148. package/registry/files/families/navigation/navigation-rail/navigation-rail.vue +156 -0
  149. package/registry/registry.json +398 -238
@@ -24,6 +24,8 @@ export interface ProgressBarStateOptions {
24
24
 
25
25
  /**
26
26
  * Raw maximum. Values less than or equal to `min` are repaired to `min + 100`.
27
+ * If that addition rounds back to `min`, the collapsed finite range reports
28
+ * zero ratio/percent and invalid state without changing the completion policy.
27
29
  *
28
30
  * @default PROGRESS_BAR_DEFAULT_MAX
29
31
  */
@@ -66,12 +68,20 @@ export function getProgressBarState(
66
68
  invalid = invalid || options.max != null;
67
69
  max = min + PROGRESS_BAR_DEFAULT_MAX;
68
70
  }
71
+ invalid = invalid || max === min;
69
72
 
70
73
  const value = finite(options.value) ? clamp(options.value, min, max) : null;
71
74
  invalid =
72
75
  invalid || (options.value != null && (!finite(options.value) || options.value !== value));
73
76
  const indeterminate = value === null;
74
- const ratio = indeterminate ? null : (value - min) / (max - min);
77
+ const span = max - min;
78
+ const ratio = indeterminate
79
+ ? null
80
+ : span === 0
81
+ ? 0
82
+ : Number.isFinite(span)
83
+ ? (value - min) / span
84
+ : (value / 2 - min / 2) / (max / 2 - min / 2);
75
85
  const percent = ratio === null ? null : ratio * 100;
76
86
  const complete = value !== null && value >= max;
77
87
 
@@ -38,7 +38,11 @@ export interface ProgressBarProps {
38
38
  /** Lower bound. Non-finite values fall back to 0. */
39
39
  readonly min?: number | null;
40
40
 
41
- /** Upper bound. Values less than or equal to `min` are repaired to `min + 100`. */
41
+ /**
42
+ * Upper bound. Values less than or equal to `min` are repaired to `min + 100`.
43
+ * If that addition rounds back to `min`, the collapsed finite range reports
44
+ * zero ratio/percent and invalid state without changing the completion policy.
45
+ */
42
46
  readonly max?: number | null;
43
47
 
44
48
  /** Reading direction for inline-start fill and indeterminate motion. */
@@ -71,7 +75,7 @@ export interface ProgressBarSlotState {
71
75
  /** Finite lower bound. */
72
76
  readonly min: number;
73
77
 
74
- /** Finite upper bound, always greater than `min`. */
78
+ /** Finite upper bound, equal to `min` only when the default repair cannot advance it. */
75
79
  readonly max: number;
76
80
 
77
81
  /** Current completion percentage from 0 to 100, or `null` when indeterminate. */
@@ -46,7 +46,12 @@ const {
46
46
  /** Lower bound for the progress range. @default 0 */
47
47
  readonly min?: number | null;
48
48
 
49
- /** Upper bound for the progress range. @default 100 */
49
+ /**
50
+ * Upper bound. Values less than or equal to `min` are repaired to `min + 100`.
51
+ * If that addition rounds back to `min`, the collapsed finite range reports
52
+ * zero ratio/percent and invalid state without changing the completion policy.
53
+ * @default 100
54
+ */
50
55
  readonly max?: number | null;
51
56
 
52
57
  /** Reading direction for inline-start fill and indeterminate motion. @default "ltr" */
@@ -24,7 +24,7 @@ export type MenuDirection = "ltr" | "rtl";
24
24
  * Which surface owns a menu tree. The kind only changes defaults and data hooks;
25
25
  * every kind shares the same keyboard, focus, and dismissal core.
26
26
  */
27
- export type MenuKind = "context-menu" | "dropdown-menu" | "menu" | "menubar";
27
+ export type MenuKind = "context-menu" | "dropdown-menu" | "menu" | "menubar" | "split-button";
28
28
 
29
29
  /**
30
30
  * Where focus lands when a menu opens.
@@ -0,0 +1,21 @@
1
+ <!-- Collapsible side rail with three destinations and an unread badge. -->
2
+ <script setup lang="ts">
3
+ import { ref } from "vue";
4
+
5
+ import { NavigationRail, NavigationRailItem, NavigationRailToggle } from "../navigation-rail.ts";
6
+
7
+ const destinations = ["home", "search", "inbox"] as const;
8
+ const current = ref<(typeof destinations)[number]>("home");
9
+ const expanded = ref(false);
10
+ </script>
11
+
12
+ <template>
13
+ <NavigationRail v-model="current" v-model:expanded="expanded" :destinations>
14
+ <NavigationRailToggle aria-label="Toggle navigation labels">
15
+ {{ expanded ? "«" : "»" }}
16
+ </NavigationRailToggle>
17
+ <NavigationRailItem value="home">Home</NavigationRailItem>
18
+ <NavigationRailItem value="search">Search</NavigationRailItem>
19
+ <NavigationRailItem value="inbox" :badge="3">Inbox</NavigationRailItem>
20
+ </NavigationRail>
21
+ </template>
@@ -0,0 +1,17 @@
1
+ import type { ComputedRef } from "vue";
2
+
3
+ import { createContext } from "../../foundations/context/context.ts";
4
+
5
+ /** Type-erased NavigationRail state shared with items and the toggle. */
6
+ export interface NavigationRailContextValue {
7
+ /** Id of the navigation landmark, referenced by the toggle's `aria-controls`. */
8
+ readonly id: ComputedRef<string>;
9
+ readonly active: ComputedRef<string | undefined>;
10
+ /** Whether destinations show their labels beside the icons. */
11
+ readonly expanded: ComputedRef<boolean>;
12
+ readonly select: (value: string, event: MouseEvent) => void;
13
+ readonly setExpanded: (value: boolean) => void;
14
+ }
15
+
16
+ /** Typed context shared by NavigationRail, its items, and its toggle. */
17
+ export const navigationRailContext = createContext<NavigationRailContextValue>("NavigationRail");
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ import { navigationRailContext } from "./navigation-rail-context.ts";
5
+
6
+ const {
7
+ value,
8
+ href = undefined,
9
+ disabled = false,
10
+ badge = undefined,
11
+ } = defineProps<{
12
+ /**
13
+ * Destination value; must be one of the root `destinations`.
14
+ *
15
+ * @default required
16
+ */
17
+ readonly value: string;
18
+
19
+ /**
20
+ * Link target. With `href` the item renders an `<a>`, otherwise a `<button>`.
21
+ *
22
+ * @default undefined
23
+ */
24
+ readonly href?: string;
25
+
26
+ /**
27
+ * Disable the destination.
28
+ *
29
+ * @default false
30
+ */
31
+ readonly disabled?: boolean;
32
+
33
+ /**
34
+ * Badge text rendered with the label (for example an unread count).
35
+ *
36
+ * @default undefined
37
+ */
38
+ readonly badge?: string | number;
39
+ }>();
40
+
41
+ defineSlots<{
42
+ /** Icon and label contents with the active and expanded state. */
43
+ default(props: { readonly active: boolean; readonly expanded: boolean }): unknown;
44
+ /** Visual badge contents; defaults to the `badge` text. */
45
+ badge?(props: { readonly badge: string | number }): unknown;
46
+ }>();
47
+
48
+ const context = navigationRailContext.use();
49
+ const active = computed(() => context.active.value === value);
50
+ // Script-capable URLs never render; only safe hrefs are bound (spread keeps the attribute absent).
51
+ const unsafeHref = /^\s*(?:javascript|vbscript|data):/i;
52
+ const linkAttributes = computed(() =>
53
+ href === undefined || disabled || unsafeHref.test(href) ? {} : { href },
54
+ );
55
+
56
+ function onClick(event: MouseEvent): void {
57
+ if (disabled) {
58
+ event.preventDefault();
59
+ return;
60
+ }
61
+ context.select(value, event);
62
+ }
63
+ </script>
64
+
65
+ <template>
66
+ <component
67
+ :is="href === undefined ? 'button' : 'a'"
68
+ :type="href === undefined ? 'button' : undefined"
69
+ v-bind="linkAttributes"
70
+ :disabled="href === undefined ? disabled : undefined"
71
+ :aria-disabled="href !== undefined && disabled ? 'true' : undefined"
72
+ :aria-current="active ? 'page' : undefined"
73
+ part="item"
74
+ data-vize-ui="navigation-rail-item"
75
+ :data-value="value"
76
+ :data-state="active ? 'active' : 'inactive'"
77
+ :data-expanded="context.expanded.value ? 'true' : 'false'"
78
+ @click="onClick"
79
+ >
80
+ <slot :active :expanded="context.expanded.value" />
81
+ <span v-if="badge !== undefined" part="badge" data-vize-ui="navigation-rail-badge">
82
+ <slot name="badge" :badge>{{ badge }}</slot>
83
+ </span>
84
+ </component>
85
+ </template>
86
+
87
+ <style scoped>
88
+ /* Headless by design. Native CSS remains entirely consumer-owned. */
89
+ </style>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import { navigationRailContext } from "./navigation-rail-context.ts";
3
+
4
+ const { disabled = false, ariaLabel = undefined } = defineProps<{
5
+ /**
6
+ * Disable expanding and collapsing.
7
+ *
8
+ * @default false
9
+ */
10
+ readonly disabled?: boolean;
11
+
12
+ /**
13
+ * Accessible name when the toggle renders only an icon.
14
+ *
15
+ * @default undefined
16
+ */
17
+ readonly ariaLabel?: string;
18
+ }>();
19
+
20
+ defineSlots<{
21
+ /** Toggle contents with the expanded state. */
22
+ default(props: { readonly expanded: boolean }): unknown;
23
+ }>();
24
+
25
+ const context = navigationRailContext.use();
26
+
27
+ function onClick(): void {
28
+ if (!disabled) context.setExpanded(!context.expanded.value);
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <button
34
+ type="button"
35
+ :disabled
36
+ :aria-label
37
+ :aria-expanded="context.expanded.value ? 'true' : 'false'"
38
+ :aria-controls="context.id.value"
39
+ part="toggle"
40
+ data-vize-ui="navigation-rail-toggle"
41
+ :data-state="context.expanded.value ? 'expanded' : 'collapsed'"
42
+ @click="onClick"
43
+ >
44
+ <slot :expanded="context.expanded.value" />
45
+ </button>
46
+ </template>
47
+
48
+ <style scoped>
49
+ /* Headless by design. Native CSS remains entirely consumer-owned. */
50
+ </style>
@@ -0,0 +1,23 @@
1
+ /** State exposed to the NavigationRail default slot. */
2
+ export interface NavigationRailSlotState<Value extends string = string> {
3
+ /** Current destination, or `undefined` before one is chosen. */
4
+ readonly active: Value | undefined;
5
+
6
+ /** Whether destinations show their labels beside the icons. */
7
+ readonly expanded: boolean;
8
+ }
9
+
10
+ /** Imperative surface exposed by NavigationRail. */
11
+ export interface NavigationRailExpose {
12
+ /** Id of the navigation landmark. */
13
+ readonly id: string;
14
+
15
+ /** Whether destinations show their labels beside the icons. */
16
+ readonly expanded: boolean;
17
+
18
+ /** Request an expanded value. */
19
+ readonly setExpanded: (value: boolean) => void;
20
+
21
+ /** Flip the expanded value. */
22
+ readonly toggle: () => void;
23
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Side navigation rail: a labelled navigation landmark of top-level
3
+ * destinations whose current one carries `aria-current="page"`, with an
4
+ * optional expanded state that shows labels beside the icons.
5
+ */
6
+ export { default as NavigationRail } from "./navigation-rail.vue";
7
+ /** Destination link or button inside NavigationRail. */
8
+ export { default as NavigationRailItem } from "./navigation-rail-item.vue";
9
+ /** Button that expands or collapses the rail. */
10
+ export { default as NavigationRailToggle } from "./navigation-rail-toggle.vue";
11
+ export type { NavigationRailExpose, NavigationRailSlotState } from "./navigation-rail-types.ts";
@@ -0,0 +1,156 @@
1
+ <script setup lang="ts" generic="Value extends string">
2
+ import { computed } from "vue";
3
+ import type { ComputedRef } from "vue";
4
+
5
+ import { useControllableState } from "../../foundations/controllable-state/controllable-state.ts";
6
+ import { useDeterministicId } from "../../foundations/id/deterministic-id.ts";
7
+ import { navigationRailContext } from "./navigation-rail-context.ts";
8
+ import type { NavigationRailExpose, NavigationRailSlotState } from "./navigation-rail-types.ts";
9
+
10
+ const {
11
+ destinations,
12
+ id = undefined,
13
+ modelValue = undefined,
14
+ defaultValue = undefined,
15
+ expanded = undefined,
16
+ defaultExpanded = false,
17
+ ariaLabel = "Primary",
18
+ ariaLabelledby = undefined,
19
+ } = defineProps<{
20
+ /**
21
+ * Destination values in order; their literal union types `v-model` and `select`.
22
+ *
23
+ * @default required
24
+ */
25
+ readonly destinations: readonly Value[];
26
+
27
+ /**
28
+ * Consumer-owned id. `null` and `undefined` select a deterministic fallback.
29
+ *
30
+ * @default undefined
31
+ */
32
+ readonly id?: string | null;
33
+
34
+ /**
35
+ * Controlled active destination. `undefined` selects uncontrolled behavior.
36
+ *
37
+ * @default undefined
38
+ */
39
+ readonly modelValue?: Value;
40
+
41
+ /**
42
+ * Initial uncontrolled destination.
43
+ *
44
+ * @default undefined
45
+ */
46
+ readonly defaultValue?: Value;
47
+
48
+ /**
49
+ * Controlled expanded state. `undefined` selects uncontrolled behavior.
50
+ *
51
+ * @default undefined
52
+ */
53
+ readonly expanded?: boolean;
54
+
55
+ /**
56
+ * Initial expanded state for uncontrolled use.
57
+ *
58
+ * @default false
59
+ */
60
+ readonly defaultExpanded?: boolean;
61
+
62
+ /**
63
+ * Accessible name of the navigation landmark.
64
+ *
65
+ * @default "Primary"
66
+ */
67
+ readonly ariaLabel?: string;
68
+
69
+ /**
70
+ * Ids that label the navigation landmark (overrides `ariaLabel`).
71
+ *
72
+ * @default undefined
73
+ */
74
+ readonly ariaLabelledby?: string;
75
+ }>();
76
+
77
+ const emit = defineEmits<{
78
+ /** Fired when an item requests to become the active destination. */
79
+ "update:modelValue": [value: Value];
80
+
81
+ /** Fired after an item is activated, with its value and the native click. */
82
+ select: [value: Value, nativeEvent: MouseEvent];
83
+
84
+ /** Fired with the requested expanded value (supports `v-model:expanded`). */
85
+ "update:expanded": [value: boolean];
86
+ }>();
87
+
88
+ defineSlots<{
89
+ /** Toggle and NavigationRailItem destinations with the active value and expanded state. */
90
+ default(props: NavigationRailSlotState<Value>): unknown;
91
+ }>();
92
+
93
+ const railId = useDeterministicId({ id: () => id, hint: "navigation-rail" });
94
+ const state = useControllableState<Value | undefined>({
95
+ value: () => modelValue,
96
+ defaultValue: () => defaultValue,
97
+ onChange: (value) => {
98
+ if (value !== undefined) emit("update:modelValue", value);
99
+ },
100
+ });
101
+ const expandedState = useControllableState<boolean>({
102
+ value: () => expanded,
103
+ defaultValue: () => defaultExpanded,
104
+ onChange: (value) => emit("update:expanded", value),
105
+ });
106
+ const isExpanded = computed(() => expandedState.value.value);
107
+
108
+ function setExpanded(value: boolean): void {
109
+ expandedState.set(value);
110
+ }
111
+
112
+ function toggle(): void {
113
+ expandedState.set(!expandedState.value.value);
114
+ }
115
+
116
+ navigationRailContext.provide({
117
+ id: railId,
118
+ active: computed(() => state.value.value),
119
+ expanded: isExpanded,
120
+ select: (value: string, event: MouseEvent) => {
121
+ const typed = destinations.find((destination) => destination === value);
122
+ if (typed === undefined) return;
123
+ state.set(typed);
124
+ emit("select", typed, event);
125
+ },
126
+ setExpanded,
127
+ });
128
+
129
+ defineExpose({
130
+ expanded: isExpanded,
131
+ id: railId,
132
+ setExpanded,
133
+ toggle,
134
+ } satisfies Omit<NavigationRailExpose, "expanded" | "id"> & {
135
+ readonly expanded: ComputedRef<boolean>;
136
+ readonly id: ComputedRef<string>;
137
+ });
138
+ </script>
139
+
140
+ <template>
141
+ <nav
142
+ :id="railId"
143
+ :aria-label="ariaLabelledby === undefined ? ariaLabel : undefined"
144
+ :aria-labelledby
145
+ part="root"
146
+ data-vize-ui="navigation-rail"
147
+ :data-count="destinations.length"
148
+ :data-expanded="isExpanded ? 'true' : 'false'"
149
+ >
150
+ <slot :active="state.value.value" :expanded="isExpanded" />
151
+ </nav>
152
+ </template>
153
+
154
+ <style scoped>
155
+ /* Headless by design. Pin with position: sticky or fixed and pad with SafeArea. */
156
+ </style>