@vizejs/ui 0.443.0 → 0.457.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 +31 -0
- package/dist/accordion.d.mts +1 -1
- package/dist/action-sheet.d.mts +1 -1
- package/dist/alert-dialog.d.mts +1 -1
- package/dist/{announcer-D0bFq-HV.d.mts → announcer-DunfmMGR.d.mts} +3352 -2953
- package/dist/announcer.d.mts +1 -1
- package/dist/app-bar-kKBZ3G2y.mjs +172 -0
- package/dist/app-bar-yxtiYseG.d.mts +103 -0
- package/dist/app-bar.d.mts +2 -0
- package/dist/app-bar.mjs +2 -0
- package/dist/autocomplete.d.mts +1 -1
- package/dist/avatar.d.mts +1 -1
- package/dist/badge.d.mts +1 -1
- package/dist/banner.d.mts +1 -1
- package/dist/block-ui.d.mts +1 -1
- package/dist/blockquote.d.mts +1 -1
- package/dist/{breadcrumb-DJIZ3zDG.d.mts → breadcrumb-Cdyh20z-.d.mts} +1 -1
- package/dist/breadcrumb.d.mts +1 -1
- package/dist/{calendar-C_ihbcsR.mjs → calendar-BaK_yoKC.mjs} +3 -3
- package/dist/{calendar-root-Dy3WPuNT.mjs → calendar-root-Bl1sv8qy.mjs} +2 -2
- package/dist/calendar.d.mts +1 -1
- package/dist/calendar.mjs +2 -2
- package/dist/callout.d.mts +1 -1
- package/dist/card.d.mts +1 -1
- package/dist/catalog.d.mts +69 -1
- package/dist/catalog.mjs +275 -119
- package/dist/chart.d.mts +1 -1
- package/dist/checkbox.d.mts +1 -1
- package/dist/code.d.mts +1 -1
- package/dist/collapsible.d.mts +1 -1
- package/dist/color-picker.d.mts +1 -1
- package/dist/combobox.d.mts +1 -1
- package/dist/command-palette.d.mts +1 -1
- package/dist/confirm.d.mts +1 -1
- package/dist/context-menu.d.mts +1 -1
- package/dist/copy-button.d.mts +1 -1
- package/dist/{date-picker-Bwt03r8S.mjs → date-picker-BsYXMErN.mjs} +1 -1
- package/dist/date-picker.mjs +1 -1
- package/dist/{date-range-picker-BBHrOLAU.mjs → date-range-picker-H2q9rFOA.mjs} +1 -1
- package/dist/date-range-picker.d.mts +1 -1
- package/dist/date-range-picker.mjs +1 -1
- package/dist/dialog.d.mts +1 -1
- package/dist/direction.d.mts +1 -1
- package/dist/drawer.d.mts +1 -1
- package/dist/dropdown-menu.d.mts +1 -1
- package/dist/{duration-field-BmykCKTC.mjs → duration-field-DowlJQvz.mjs} +3 -3
- package/dist/duration-field.d.mts +1 -1
- package/dist/duration-field.mjs +1 -1
- package/dist/empty-state.d.mts +1 -1
- package/dist/fieldset.d.mts +1 -1
- package/dist/file-upload.d.mts +1 -1
- package/dist/floating-action-button.d.mts +1 -1
- package/dist/focus-visible.d.mts +1 -1
- package/dist/{form-wizard-mErOrBg3.d.mts → form-wizard-D_B-Cydo.d.mts} +2 -2
- package/dist/form-wizard.d.mts +1 -1
- package/dist/fullscreen-button.d.mts +1 -1
- package/dist/heading.d.mts +1 -1
- package/dist/hover-card.d.mts +1 -1
- package/dist/icon-button.d.mts +1 -1
- package/dist/icon.d.mts +1 -1
- package/dist/{image-D9CEf8uD.d.mts → image-DCQeTHqi.d.mts} +2 -2
- package/dist/{image-cropper-BJsgLDjH.d.mts → image-cropper-BPAHa995.d.mts} +2 -2
- package/dist/image-cropper.d.mts +1 -1
- package/dist/image.d.mts +1 -1
- package/dist/index.d.mts +99 -98
- package/dist/index.mjs +15 -12
- package/dist/{input-C0fgeRnS.d.mts → input-D4Zvmf67.d.mts} +2 -2
- package/dist/input.d.mts +1 -1
- package/dist/kbd.d.mts +1 -1
- package/dist/landmark.d.mts +1 -1
- package/dist/list.d.mts +1 -1
- package/dist/listbox.d.mts +1 -1
- package/dist/locale.d.mts +1 -1
- package/dist/media-preferences.d.mts +1 -1
- package/dist/menu.d.mts +1 -1
- package/dist/menubar.d.mts +1 -1
- package/dist/{meter-ZZIKQTLX.mjs → meter-CxHgTYvs.mjs} +3 -2
- package/dist/{meter-B3m17_8g.d.mts → meter-LxMMvjjK.d.mts} +8 -1
- package/dist/meter.d.mts +1 -1
- package/dist/meter.mjs +1 -1
- package/dist/month-picker.d.mts +1 -1
- package/dist/native-select.d.mts +1 -1
- package/dist/navigation-rail-BBg5bin7.mjs +246 -0
- package/dist/navigation-rail.d.mts +2 -0
- package/dist/navigation-rail.mjs +2 -0
- package/dist/notification-center.d.mts +1 -1
- package/dist/pager.d.mts +1 -1
- package/dist/pagination.d.mts +1 -1
- package/dist/popconfirm.d.mts +1 -1
- package/dist/popover.d.mts +1 -1
- package/dist/print-button.d.mts +1 -1
- package/dist/{progress-bar-lXHO-U8x.mjs → progress-bar-DsH4Jgn2.mjs} +3 -1
- package/dist/progress-bar.d.mts +1 -1
- package/dist/progress-bar.mjs +1 -1
- package/dist/radio-group.d.mts +1 -1
- package/dist/{range-calendar-root-CEhaPJAd.mjs → range-calendar-root-D9ZoZkWc.mjs} +2 -2
- package/dist/range-calendar.mjs +1 -1
- package/dist/resolver.d.mts +25 -1
- package/dist/resolver.mjs +25 -1
- package/dist/scroll-area.d.mts +1 -1
- package/dist/{search-field-zyMCYvZ2.d.mts → search-field-DzW31UHM.d.mts} +4 -4
- package/dist/search-field.d.mts +1 -1
- package/dist/select.d.mts +1 -1
- package/dist/share-button.d.mts +1 -1
- package/dist/sidebar.d.mts +1 -1
- package/dist/split-button-Cff_lsza.mjs +339 -0
- package/dist/split-button.d.mts +2 -0
- package/dist/split-button.mjs +3 -0
- package/dist/splitter.d.mts +1 -1
- package/dist/status-light.d.mts +1 -1
- package/dist/stepper.d.mts +1 -1
- package/dist/surface.d.mts +1 -1
- package/dist/switch.d.mts +1 -1
- package/dist/tabs.d.mts +1 -1
- package/dist/tags-input.d.mts +1 -1
- package/dist/text.d.mts +1 -1
- package/dist/{textarea-4eIDNcvv.d.mts → textarea-D7V5lLPX.d.mts} +2 -2
- package/dist/textarea.d.mts +1 -1
- package/dist/toggle.d.mts +1 -1
- package/dist/tooltip.d.mts +1 -1
- package/dist/tour.d.mts +1 -1
- package/dist/tree.d.mts +1 -1
- package/dist/{week-picker-BUduCITx.mjs → week-picker-Ce2qvuBO.mjs} +2 -2
- package/dist/week-picker.d.mts +1 -1
- package/dist/week-picker.mjs +1 -1
- package/package.json +21 -5
- package/registry/files/families/actions/split-button/examples/split-button-basic.vue +35 -0
- package/registry/files/families/actions/split-button/split-button-action.vue +102 -0
- package/registry/files/families/actions/split-button/split-button-context.ts +17 -0
- package/registry/files/families/actions/split-button/split-button-root.vue +191 -0
- package/registry/files/families/actions/split-button/split-button-trigger.vue +140 -0
- package/registry/files/families/actions/split-button/split-button-types.ts +40 -0
- package/registry/files/families/actions/split-button/split-button.ts +40 -0
- package/registry/files/families/date-time/calendar/calendar-multiple-root.vue +2 -2
- package/registry/files/families/date-time/calendar/calendar-root.vue +2 -2
- package/registry/files/families/date-time/duration-field/duration-field.vue +3 -3
- package/registry/files/families/date-time/range-calendar/range-calendar-root.vue +2 -2
- package/registry/files/families/date-time/week-picker/week-picker-root.vue +2 -2
- package/registry/files/families/feedback/meter/meter-state.ts +10 -2
- package/registry/files/families/feedback/meter/meter-types.ts +4 -1
- package/registry/files/families/feedback/meter/meter.vue +2 -0
- package/registry/files/families/feedback/progress-bar/progress-bar-state.ts +11 -1
- package/registry/files/families/feedback/progress-bar/progress-bar-types.ts +6 -2
- package/registry/files/families/feedback/progress-bar/progress-bar.vue +6 -1
- package/registry/files/families/menus/menu/menu-types.ts +1 -1
- package/registry/files/families/navigation/app-bar/app-bar-types.ts +32 -0
- package/registry/files/families/navigation/app-bar/app-bar.ts +3 -0
- package/registry/files/families/navigation/app-bar/app-bar.vue +215 -0
- package/registry/files/families/navigation/app-bar/examples/app-bar-basic.vue +21 -0
- package/registry/files/families/navigation/navigation-rail/examples/navigation-rail-basic.vue +21 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail-context.ts +17 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail-item.vue +89 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail-toggle.vue +50 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail-types.ts +23 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail.ts +11 -0
- package/registry/files/families/navigation/navigation-rail/navigation-rail.vue +156 -0
- package/registry/registry.json +450 -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
|
|
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
|
-
/**
|
|
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,
|
|
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
|
-
/**
|
|
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,32 @@
|
|
|
1
|
+
/** Scroll container an AppBar follows: an element, a selector, or `null` for the window. */
|
|
2
|
+
export type AppBarTarget = HTMLElement | string | null;
|
|
3
|
+
|
|
4
|
+
/** Stable visibility state exposed for consumer-owned styling and tests. */
|
|
5
|
+
export type AppBarState = "visible" | "hidden";
|
|
6
|
+
|
|
7
|
+
/** State exposed to every AppBar slot. */
|
|
8
|
+
export interface AppBarSlotState {
|
|
9
|
+
/** Whether the followed container has scrolled past `elevateThreshold`. */
|
|
10
|
+
readonly elevated: boolean;
|
|
11
|
+
|
|
12
|
+
/** Whether `hideOnScroll` currently asks the consumer to move the bar out of view. */
|
|
13
|
+
readonly hidden: boolean;
|
|
14
|
+
|
|
15
|
+
/** Stable visibility token for styling and tests. */
|
|
16
|
+
readonly state: AppBarState;
|
|
17
|
+
|
|
18
|
+
/** Current scroll offset of the followed container in CSS pixels. */
|
|
19
|
+
readonly scrollTop: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Public instance exposed by AppBar. */
|
|
23
|
+
export interface AppBarExpose extends AppBarSlotState {
|
|
24
|
+
/** Rendered root element. */
|
|
25
|
+
readonly element: HTMLElement | null;
|
|
26
|
+
|
|
27
|
+
/** Bring a hidden bar back into view. */
|
|
28
|
+
readonly show: () => void;
|
|
29
|
+
|
|
30
|
+
/** Re-read the scroll offset, for containers that scroll without a scroll event. */
|
|
31
|
+
readonly refresh: () => number;
|
|
32
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, onScopeDispose, shallowRef, useTemplateRef, watch } from "vue";
|
|
3
|
+
import type { ComputedRef, ShallowRef } from "vue";
|
|
4
|
+
|
|
5
|
+
import type { AppBarExpose, AppBarSlotState, AppBarState, AppBarTarget } from "./app-bar-types.ts";
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
as = "header",
|
|
9
|
+
target = null,
|
|
10
|
+
elevateThreshold = 0,
|
|
11
|
+
hideOnScroll = false,
|
|
12
|
+
hideThreshold = 64,
|
|
13
|
+
ariaLabel = undefined,
|
|
14
|
+
ariaLabelledby = undefined,
|
|
15
|
+
} = defineProps<{
|
|
16
|
+
/**
|
|
17
|
+
* Host element. A top-level `header` is the page's banner landmark; use `div`
|
|
18
|
+
* when the bar sits inside another landmark that already has a header.
|
|
19
|
+
*
|
|
20
|
+
* @default "header"
|
|
21
|
+
*/
|
|
22
|
+
readonly as?: "header" | "div";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scroll container to follow: an element, a selector, or `null` for the window.
|
|
26
|
+
*
|
|
27
|
+
* @default null
|
|
28
|
+
*/
|
|
29
|
+
readonly target?: AppBarTarget;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Scroll offset in CSS pixels past which the bar reports `elevated`.
|
|
33
|
+
*
|
|
34
|
+
* @default 0
|
|
35
|
+
*/
|
|
36
|
+
readonly elevateThreshold?: number;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Report `hidden` while the container scrolls down, and visible again as soon as it scrolls up.
|
|
40
|
+
*
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
readonly hideOnScroll?: boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Scroll offset in CSS pixels before `hideOnScroll` may hide the bar.
|
|
47
|
+
*
|
|
48
|
+
* @default 64
|
|
49
|
+
*/
|
|
50
|
+
readonly hideThreshold?: number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Accessible name of the landmark when no visible title labels it.
|
|
54
|
+
*
|
|
55
|
+
* @default undefined
|
|
56
|
+
*/
|
|
57
|
+
readonly ariaLabel?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ids that label the landmark (overrides `ariaLabel`).
|
|
61
|
+
*
|
|
62
|
+
* @default undefined
|
|
63
|
+
*/
|
|
64
|
+
readonly ariaLabelledby?: string;
|
|
65
|
+
}>();
|
|
66
|
+
|
|
67
|
+
const emit = defineEmits<{
|
|
68
|
+
/** Fired when `hideOnScroll` hides or reveals the bar. */
|
|
69
|
+
"visibility-change": [hidden: boolean];
|
|
70
|
+
}>();
|
|
71
|
+
|
|
72
|
+
defineSlots<{
|
|
73
|
+
/** Leading content such as a navigation or back button. */
|
|
74
|
+
start?(props: AppBarSlotState): unknown;
|
|
75
|
+
/** Title and main content. */
|
|
76
|
+
default?(props: AppBarSlotState): unknown;
|
|
77
|
+
/** Trailing actions. */
|
|
78
|
+
end?(props: AppBarSlotState): unknown;
|
|
79
|
+
}>();
|
|
80
|
+
|
|
81
|
+
const element = useTemplateRef<HTMLElement>("element");
|
|
82
|
+
const scrollTop = shallowRef(0);
|
|
83
|
+
const hidden = shallowRef(false);
|
|
84
|
+
const focusWithin = shallowRef(false);
|
|
85
|
+
const finite = (value: number, fallback: number) =>
|
|
86
|
+
Number.isFinite(value) ? Math.max(0, value) : fallback;
|
|
87
|
+
const elevated = computed(() => scrollTop.value > finite(elevateThreshold, 0));
|
|
88
|
+
const state = computed<AppBarState>(() => (hidden.value ? "hidden" : "visible"));
|
|
89
|
+
const slotState = computed<AppBarSlotState>(() => ({
|
|
90
|
+
elevated: elevated.value,
|
|
91
|
+
hidden: hidden.value,
|
|
92
|
+
scrollTop: scrollTop.value,
|
|
93
|
+
state: state.value,
|
|
94
|
+
}));
|
|
95
|
+
let container: HTMLElement | Window | null = null;
|
|
96
|
+
|
|
97
|
+
function setHidden(next: boolean): void {
|
|
98
|
+
if (hidden.value === next) return;
|
|
99
|
+
hidden.value = next;
|
|
100
|
+
emit("visibility-change", next);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveContainer(): HTMLElement | Window | null {
|
|
104
|
+
const ownerDocument = element.value?.ownerDocument;
|
|
105
|
+
if (!ownerDocument) return null;
|
|
106
|
+
if (target === null) return ownerDocument.defaultView;
|
|
107
|
+
if (typeof target !== "string") return target;
|
|
108
|
+
const found = ownerDocument.querySelector(target);
|
|
109
|
+
return found instanceof HTMLElement ? found : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readOffset(source: HTMLElement | Window): number {
|
|
113
|
+
const offset = "scrollY" in source ? source.scrollY : source.scrollTop;
|
|
114
|
+
return Number.isFinite(offset) ? Math.max(0, offset) : 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function refresh(): number {
|
|
118
|
+
const previous = scrollTop.value;
|
|
119
|
+
const next = container === null ? 0 : readOffset(container);
|
|
120
|
+
scrollTop.value = next;
|
|
121
|
+
if (!hideOnScroll || focusWithin.value) setHidden(false);
|
|
122
|
+
else if (next < previous || next <= finite(hideThreshold, 64)) setHidden(false);
|
|
123
|
+
else if (next > previous) setHidden(true);
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function onScroll(): void {
|
|
128
|
+
refresh();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function detach(): void {
|
|
132
|
+
container?.removeEventListener("scroll", onScroll);
|
|
133
|
+
container = null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function attach(): void {
|
|
137
|
+
detach();
|
|
138
|
+
container = resolveContainer();
|
|
139
|
+
container?.addEventListener("scroll", onScroll, { passive: true });
|
|
140
|
+
scrollTop.value = container === null ? 0 : readOffset(container);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function onFocusin(): void {
|
|
144
|
+
focusWithin.value = true;
|
|
145
|
+
setHidden(false);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function onFocusout(event: FocusEvent): void {
|
|
149
|
+
const next = event.relatedTarget;
|
|
150
|
+
focusWithin.value = next instanceof Node && element.value?.contains(next) === true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function show(): void {
|
|
154
|
+
setHidden(false);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
onMounted(attach);
|
|
158
|
+
watch(
|
|
159
|
+
() => target,
|
|
160
|
+
() => attach(),
|
|
161
|
+
);
|
|
162
|
+
watch(
|
|
163
|
+
() => hideOnScroll,
|
|
164
|
+
(enabled) => {
|
|
165
|
+
if (!enabled) setHidden(false);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
onScopeDispose(detach);
|
|
169
|
+
|
|
170
|
+
defineExpose({
|
|
171
|
+
element,
|
|
172
|
+
elevated,
|
|
173
|
+
hidden,
|
|
174
|
+
refresh,
|
|
175
|
+
scrollTop,
|
|
176
|
+
show,
|
|
177
|
+
state,
|
|
178
|
+
} satisfies Omit<AppBarExpose, "element" | "elevated" | "hidden" | "scrollTop" | "state"> & {
|
|
179
|
+
readonly element: typeof element;
|
|
180
|
+
readonly elevated: ComputedRef<boolean>;
|
|
181
|
+
readonly hidden: Readonly<ShallowRef<boolean>>;
|
|
182
|
+
readonly scrollTop: Readonly<ShallowRef<number>>;
|
|
183
|
+
readonly state: ComputedRef<AppBarState>;
|
|
184
|
+
});
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<component
|
|
189
|
+
:is="as"
|
|
190
|
+
ref="element"
|
|
191
|
+
:aria-label="ariaLabelledby === undefined ? ariaLabel : undefined"
|
|
192
|
+
:aria-labelledby
|
|
193
|
+
part="root"
|
|
194
|
+
data-vize-ui="app-bar"
|
|
195
|
+
:data-state="state"
|
|
196
|
+
:data-elevated="elevated ? 'true' : undefined"
|
|
197
|
+
:data-hidden="hidden ? 'true' : undefined"
|
|
198
|
+
@focusin="onFocusin"
|
|
199
|
+
@focusout="onFocusout"
|
|
200
|
+
>
|
|
201
|
+
<div v-if="$slots.start" part="start" data-vize-ui="app-bar-start">
|
|
202
|
+
<slot name="start" v-bind="slotState" />
|
|
203
|
+
</div>
|
|
204
|
+
<div part="content" data-vize-ui="app-bar-content">
|
|
205
|
+
<slot v-bind="slotState" />
|
|
206
|
+
</div>
|
|
207
|
+
<div v-if="$slots.end" part="end" data-vize-ui="app-bar-end">
|
|
208
|
+
<slot name="end" v-bind="slotState" />
|
|
209
|
+
</div>
|
|
210
|
+
</component>
|
|
211
|
+
</template>
|
|
212
|
+
|
|
213
|
+
<style scoped>
|
|
214
|
+
/* Headless by design. Position, elevation, and the hide transition are consumer-owned. */
|
|
215
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!-- Top app bar with a menu button, a title, and a search action that hides while scrolling down. -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { useId } from "vue";
|
|
4
|
+
import { AppBar } from "../app-bar.ts";
|
|
5
|
+
|
|
6
|
+
const titleId = useId();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<AppBar hide-on-scroll :aria-labelledby="titleId">
|
|
11
|
+
<template #start>
|
|
12
|
+
<button type="button" aria-label="Open navigation">≡</button>
|
|
13
|
+
</template>
|
|
14
|
+
<template #default="{ elevated }">
|
|
15
|
+
<span :id="titleId" :data-elevated="elevated ? 'true' : undefined">Inbox</span>
|
|
16
|
+
</template>
|
|
17
|
+
<template #end>
|
|
18
|
+
<button type="button">Search</button>
|
|
19
|
+
</template>
|
|
20
|
+
</AppBar>
|
|
21
|
+
</template>
|
|
@@ -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";
|