@vmz/ui 0.3.4 → 0.3.6
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 +51 -51
- package/package.json +2 -2
- package/presets/web-surface.v0.json +19 -7
- package/src/components/Accordion.vmz +94 -94
- package/src/components/AppShell.vmz +77 -33
- package/src/components/Autocomplete.vmz +154 -154
- package/src/components/Badge.vmz +70 -70
- package/src/components/Breadcrumb.vmz +72 -72
- package/src/components/BulkActions.vmz +51 -51
- package/src/components/Button.vmz +211 -192
- package/src/components/Callout.vmz +77 -77
- package/src/components/Card.vmz +120 -106
- package/src/components/Checkbox.vmz +55 -55
- package/src/components/CodeBlock.vmz +77 -18
- package/src/components/ConsoleShell.vmz +251 -251
- package/src/components/DataTable.vmz +194 -194
- package/src/components/DatePicker.vmz +674 -674
- package/src/components/Dialog.vmz +10 -7
- package/src/components/Drawer.vmz +427 -427
- package/src/components/Dropdown.vmz +135 -16
- package/src/components/Empty.vmz +54 -54
- package/src/components/Field.vmz +115 -115
- package/src/components/FormItem.vmz +79 -79
- package/src/components/Link.vmz +34 -4
- package/src/components/List.vmz +102 -102
- package/src/components/Menu.vmz +243 -243
- package/src/components/Pagination.vmz +69 -69
- package/src/components/Popover.vmz +223 -223
- package/src/components/Prose.vmz +81 -81
- package/src/components/RadioGroup.vmz +124 -124
- package/src/components/Segmented.vmz +51 -51
- package/src/components/Select.vmz +533 -533
- package/src/components/Steps.vmz +103 -103
- package/src/components/Switch.vmz +98 -98
- package/src/components/Table.vmz +165 -133
- package/src/components/Tabs.vmz +110 -110
- package/src/components/Tag.vmz +43 -40
- package/src/components/TextArea.vmz +118 -118
- package/src/components/Timeline.vmz +76 -76
- package/src/components/Toc.vmz +74 -74
- package/src/components/Tooltip.vmz +58 -58
- package/src/components/Tree.vmz +191 -191
- package/src/components/Upload.vmz +1289 -1289
package/src/components/Menu.vmz
CHANGED
|
@@ -1,243 +1,243 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div data-vmz-ui="menu" :data-vmz-overlay-open="open" class="vmz-ui-menu">
|
|
3
|
-
<button type="button" data-vmz-menu="trigger" aria-haspopup="menu" :aria-expanded="open ? 'true' : 'false'" :aria-controls="menuId" @click="toggle" :onKeyDown="onTriggerKeyDown" class="vmz-ui-menu__trigger">
|
|
4
|
-
{{ label }}
|
|
5
|
-
</button>
|
|
6
|
-
<div v-if="open" :id="menuId" role="menu" data-vmz-overlay="menu" data-vmz-overlay-owner="menu" data-vmz-focus="enter" tabindex="-1" :onKeyDown="onMenuKeyDown" class="vmz-ui-menu__panel">
|
|
7
|
-
<button v-for="item in items" :key="item.id" type="button" role="menuitem" :data-vmz-menu-item="item.id" @click="onItemClick" class="vmz-ui-menu__item">
|
|
8
|
-
{{ item.title }}
|
|
9
|
-
</button>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
</template>
|
|
13
|
-
<style>
|
|
14
|
-
.vmz-ui-menu {
|
|
15
|
-
position: relative;
|
|
16
|
-
display: inline-block;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.vmz-ui-menu__trigger {
|
|
20
|
-
font: inherit;
|
|
21
|
-
font-size: 0.875rem;
|
|
22
|
-
font-weight: 400;
|
|
23
|
-
min-height: var(--vmz-control-height, 2rem);
|
|
24
|
-
padding: 0.25rem 0.75rem;
|
|
25
|
-
border: 1px solid var(--vmz-line-default);
|
|
26
|
-
border-radius: var(--vmz-radius-md, 6px);
|
|
27
|
-
cursor: pointer;
|
|
28
|
-
background: var(--vmz-surface-paper, #fff);
|
|
29
|
-
color: var(--vmz-text-ink);
|
|
30
|
-
outline: none;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.vmz-ui-menu__trigger:hover {
|
|
34
|
-
color: var(--vmz-action-primary-background);
|
|
35
|
-
border-color: var(--vmz-action-primary-background);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.vmz-ui-menu__trigger:focus-visible {
|
|
39
|
-
box-shadow: 0 0 0
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.vmz-ui-menu__panel {
|
|
43
|
-
position: absolute;
|
|
44
|
-
z-index: 30;
|
|
45
|
-
top: calc(100% + 0.25rem);
|
|
46
|
-
left: 0;
|
|
47
|
-
min-width: 10rem;
|
|
48
|
-
padding: 0.25rem;
|
|
49
|
-
border: 1px solid var(--vmz-line-default);
|
|
50
|
-
border-radius: var(--vmz-radius-md, 6px);
|
|
51
|
-
background: var(--vmz-surface-paper, #fff);
|
|
52
|
-
color: var(--vmz-text-ink);
|
|
53
|
-
box-shadow: var(--vmz-shadow-popup);
|
|
54
|
-
outline: none;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.vmz-ui-menu__panel:focus-visible {
|
|
58
|
-
box-shadow: 0 0 0
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.vmz-ui-menu__item {
|
|
62
|
-
display: block;
|
|
63
|
-
width: 100%;
|
|
64
|
-
text-align: left;
|
|
65
|
-
font: inherit;
|
|
66
|
-
padding: 0.
|
|
67
|
-
border: 0;
|
|
68
|
-
border-radius: var(--vmz-radius-md, 6px);
|
|
69
|
-
background: transparent;
|
|
70
|
-
color: inherit;
|
|
71
|
-
cursor: pointer;
|
|
72
|
-
outline: none;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.vmz-ui-menu__item:hover,
|
|
76
|
-
.vmz-ui-menu__item:focus-visible {
|
|
77
|
-
background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.vmz-ui-menu__item:focus-visible {
|
|
81
|
-
box-shadow: 0 0 0
|
|
82
|
-
}
|
|
83
|
-
</style>
|
|
84
|
-
<script client>
|
|
85
|
-
/**
|
|
86
|
-
* VMZ UI — Menu (UI2).
|
|
87
|
-
* Parent owns `open`; Escape / choose closes via onClose; focus restore to trigger.
|
|
88
|
-
*/
|
|
89
|
-
export default class Menu {
|
|
90
|
-
public label: string = "Menu";
|
|
91
|
-
public open: boolean = false;
|
|
92
|
-
public menuId: string = "vmz-menu";
|
|
93
|
-
/** @type {{ id: string, title: string }[]} */
|
|
94
|
-
public items: {
|
|
95
|
-
id: string;
|
|
96
|
-
title: string;
|
|
97
|
-
}[] = [];
|
|
98
|
-
public onOpenChange: ((open: boolean) => void) | null = null;
|
|
99
|
-
public onSelect: ((id: string) => void) | null = null;
|
|
100
|
-
_restoreFocus = null;
|
|
101
|
-
_panel = null;
|
|
102
|
-
_docKeyHandler = null;
|
|
103
|
-
_docPointerHandler = null;
|
|
104
|
-
_observer = null;
|
|
105
|
-
async onMount() {
|
|
106
|
-
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
|
|
107
|
-
const root = this.__vmzDomRoot;
|
|
108
|
-
if (!root || typeof root.querySelector !== "function") return;
|
|
109
|
-
this._observer = new MutationObserver(() => this._syncFromDom());
|
|
110
|
-
this._observer.observe(root, {
|
|
111
|
-
childList: true,
|
|
112
|
-
subtree: true,
|
|
113
|
-
});
|
|
114
|
-
this._syncFromDom();
|
|
115
|
-
}
|
|
116
|
-
onDestroy() {
|
|
117
|
-
if (this._observer) {
|
|
118
|
-
this._observer.disconnect();
|
|
119
|
-
this._observer = null;
|
|
120
|
-
}
|
|
121
|
-
this._teardownDoc();
|
|
122
|
-
this._panel = null;
|
|
123
|
-
this._restoreFocus = null;
|
|
124
|
-
}
|
|
125
|
-
toggle() {
|
|
126
|
-
if (typeof this.onOpenChange === "function") this.onOpenChange(!this.open);
|
|
127
|
-
}
|
|
128
|
-
close() {
|
|
129
|
-
if (typeof this.onOpenChange === "function") this.onOpenChange(false);
|
|
130
|
-
}
|
|
131
|
-
choose(id) {
|
|
132
|
-
if (typeof this.onSelect === "function") this.onSelect(id);
|
|
133
|
-
this.close();
|
|
134
|
-
}
|
|
135
|
-
onItemClick(ev) {
|
|
136
|
-
const el = ev?.currentTarget;
|
|
137
|
-
const id =
|
|
138
|
-
el && typeof el.getAttribute === "function"
|
|
139
|
-
? el.getAttribute("data-vmz-menu-item")
|
|
140
|
-
: null;
|
|
141
|
-
if (id) this.choose(id);
|
|
142
|
-
}
|
|
143
|
-
onTriggerKeyDown(ev) {
|
|
144
|
-
if (!ev) return;
|
|
145
|
-
if ((ev.key === "ArrowDown" || ev.key === "Enter" || ev.key === " ") && !this.open) {
|
|
146
|
-
ev.preventDefault();
|
|
147
|
-
if (typeof this.onOpenChange === "function") this.onOpenChange(true);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
onMenuKeyDown(ev) {
|
|
151
|
-
if (!ev || !this._panel) return;
|
|
152
|
-
if (ev.key === "Escape") {
|
|
153
|
-
ev.preventDefault();
|
|
154
|
-
this.close();
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const nodes = [...this._panel.querySelectorAll('[role="menuitem"]')].filter(
|
|
158
|
-
(n) => n instanceof HTMLElement && n.offsetParent !== null,
|
|
159
|
-
);
|
|
160
|
-
if (!nodes.length) return;
|
|
161
|
-
const idx = nodes.indexOf(document.activeElement);
|
|
162
|
-
if (ev.key === "ArrowDown") {
|
|
163
|
-
ev.preventDefault();
|
|
164
|
-
nodes[(idx + 1 + nodes.length) % nodes.length].focus();
|
|
165
|
-
} else if (ev.key === "ArrowUp") {
|
|
166
|
-
ev.preventDefault();
|
|
167
|
-
nodes[(idx - 1 + nodes.length) % nodes.length].focus();
|
|
168
|
-
} else if (ev.key === "Home") {
|
|
169
|
-
ev.preventDefault();
|
|
170
|
-
nodes[0].focus();
|
|
171
|
-
} else if (ev.key === "End") {
|
|
172
|
-
ev.preventDefault();
|
|
173
|
-
nodes[nodes.length - 1].focus();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
_syncFromDom() {
|
|
177
|
-
const root = this.__vmzDomRoot;
|
|
178
|
-
const panel =
|
|
179
|
-
root && typeof root.querySelector === "function"
|
|
180
|
-
? root.querySelector('[data-vmz-overlay="menu"][data-vmz-focus="enter"]')
|
|
181
|
-
: null;
|
|
182
|
-
if (panel && !this._panel) this._enterFocus(panel);
|
|
183
|
-
else if (!panel && this._panel) this._exitFocus();
|
|
184
|
-
}
|
|
185
|
-
_enterFocus(panel) {
|
|
186
|
-
if (typeof document === "undefined") return;
|
|
187
|
-
const root = this.__vmzDomRoot;
|
|
188
|
-
const trigger =
|
|
189
|
-
root && typeof root.querySelector === "function"
|
|
190
|
-
? root.querySelector('[data-vmz-menu="trigger"]')
|
|
191
|
-
: null;
|
|
192
|
-
const active = document.activeElement;
|
|
193
|
-
if (!this._restoreFocus) {
|
|
194
|
-
if (trigger instanceof HTMLElement) this._restoreFocus = trigger;
|
|
195
|
-
else if (active instanceof HTMLElement) this._restoreFocus = active;
|
|
196
|
-
}
|
|
197
|
-
this._panel = panel instanceof HTMLElement ? panel : null;
|
|
198
|
-
const first =
|
|
199
|
-
this._panel && typeof this._panel.querySelector === "function"
|
|
200
|
-
? this._panel.querySelector('[role="menuitem"]')
|
|
201
|
-
: null;
|
|
202
|
-
if (first && typeof first.focus === "function") first.focus();
|
|
203
|
-
else if (this._panel && typeof this._panel.focus === "function") this._panel.focus();
|
|
204
|
-
if (!this._docKeyHandler) {
|
|
205
|
-
this._docKeyHandler = (ev) => {
|
|
206
|
-
if (ev.key === "Escape") {
|
|
207
|
-
ev.preventDefault();
|
|
208
|
-
this.close();
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
document.addEventListener("keydown", this._docKeyHandler);
|
|
212
|
-
}
|
|
213
|
-
if (!this._docPointerHandler) {
|
|
214
|
-
this._docPointerHandler = (ev) => {
|
|
215
|
-
const t = ev.target;
|
|
216
|
-
if (!(t instanceof Node)) return;
|
|
217
|
-
if (root && typeof root.contains === "function" && root.contains(t)) return;
|
|
218
|
-
this.close();
|
|
219
|
-
};
|
|
220
|
-
document.addEventListener("pointerdown", this._docPointerHandler, true);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
_exitFocus() {
|
|
224
|
-
this._teardownDoc();
|
|
225
|
-
this._panel = null;
|
|
226
|
-
const restore = this._restoreFocus;
|
|
227
|
-
this._restoreFocus = null;
|
|
228
|
-
if (restore && typeof restore.focus === "function" && document.contains(restore)) {
|
|
229
|
-
restore.focus();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
_teardownDoc() {
|
|
233
|
-
if (typeof document !== "undefined") {
|
|
234
|
-
if (this._docKeyHandler) document.removeEventListener("keydown", this._docKeyHandler);
|
|
235
|
-
if (this._docPointerHandler) {
|
|
236
|
-
document.removeEventListener("pointerdown", this._docPointerHandler, true);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
this._docKeyHandler = null;
|
|
240
|
-
this._docPointerHandler = null;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div data-vmz-ui="menu" :data-vmz-overlay-open="open" class="vmz-ui-menu">
|
|
3
|
+
<button type="button" data-vmz-menu="trigger" aria-haspopup="menu" :aria-expanded="open ? 'true' : 'false'" :aria-controls="menuId" @click="toggle" :onKeyDown="onTriggerKeyDown" class="vmz-ui-menu__trigger">
|
|
4
|
+
{{ label }}
|
|
5
|
+
</button>
|
|
6
|
+
<div v-if="open" :id="menuId" role="menu" data-vmz-overlay="menu" data-vmz-overlay-owner="menu" data-vmz-focus="enter" tabindex="-1" :onKeyDown="onMenuKeyDown" class="vmz-ui-menu__panel">
|
|
7
|
+
<button v-for="item in items" :key="item.id" type="button" role="menuitem" :data-vmz-menu-item="item.id" @click="onItemClick" class="vmz-ui-menu__item">
|
|
8
|
+
{{ item.title }}
|
|
9
|
+
</button>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
<style>
|
|
14
|
+
.vmz-ui-menu {
|
|
15
|
+
position: relative;
|
|
16
|
+
display: inline-block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.vmz-ui-menu__trigger {
|
|
20
|
+
font: inherit;
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
font-weight: 400;
|
|
23
|
+
min-height: var(--vmz-control-height, 2rem);
|
|
24
|
+
padding: 0.25rem 0.75rem;
|
|
25
|
+
border: 1px solid var(--vmz-line-default);
|
|
26
|
+
border-radius: var(--vmz-radius-md, 6px);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
background: var(--vmz-surface-paper, #fff);
|
|
29
|
+
color: var(--vmz-text-ink);
|
|
30
|
+
outline: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.vmz-ui-menu__trigger:hover {
|
|
34
|
+
color: var(--vmz-action-primary-background);
|
|
35
|
+
border-color: var(--vmz-action-primary-background);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vmz-ui-menu__trigger:focus-visible {
|
|
39
|
+
box-shadow: 0 0 0 3px var(--vmz-focus-ring);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.vmz-ui-menu__panel {
|
|
43
|
+
position: absolute;
|
|
44
|
+
z-index: 30;
|
|
45
|
+
top: calc(100% + 0.25rem);
|
|
46
|
+
left: 0;
|
|
47
|
+
min-width: 10rem;
|
|
48
|
+
padding: 0.25rem;
|
|
49
|
+
border: 1px solid var(--vmz-line-default);
|
|
50
|
+
border-radius: var(--vmz-radius-md, 6px);
|
|
51
|
+
background: var(--vmz-surface-paper, #fff);
|
|
52
|
+
color: var(--vmz-text-ink);
|
|
53
|
+
box-shadow: var(--vmz-shadow-popup);
|
|
54
|
+
outline: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.vmz-ui-menu__panel:focus-visible {
|
|
58
|
+
box-shadow: 0 0 0 3px var(--vmz-focus-ring);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.vmz-ui-menu__item {
|
|
62
|
+
display: block;
|
|
63
|
+
width: 100%;
|
|
64
|
+
text-align: left;
|
|
65
|
+
font: inherit;
|
|
66
|
+
padding: 0.375rem 0.625rem;
|
|
67
|
+
border: 0;
|
|
68
|
+
border-radius: var(--vmz-radius-md, 6px);
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: inherit;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
outline: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.vmz-ui-menu__item:hover,
|
|
76
|
+
.vmz-ui-menu__item:focus-visible {
|
|
77
|
+
background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.vmz-ui-menu__item:focus-visible {
|
|
81
|
+
box-shadow: 0 0 0 3px var(--vmz-focus-ring);
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
<script client>
|
|
85
|
+
/**
|
|
86
|
+
* VMZ UI — Menu (UI2).
|
|
87
|
+
* Parent owns `open`; Escape / choose closes via onClose; focus restore to trigger.
|
|
88
|
+
*/
|
|
89
|
+
export default class Menu {
|
|
90
|
+
public label: string = "Menu";
|
|
91
|
+
public open: boolean = false;
|
|
92
|
+
public menuId: string = "vmz-menu";
|
|
93
|
+
/** @type {{ id: string, title: string }[]} */
|
|
94
|
+
public items: {
|
|
95
|
+
id: string;
|
|
96
|
+
title: string;
|
|
97
|
+
}[] = [];
|
|
98
|
+
public onOpenChange: ((open: boolean) => void) | null = null;
|
|
99
|
+
public onSelect: ((id: string) => void) | null = null;
|
|
100
|
+
_restoreFocus = null;
|
|
101
|
+
_panel = null;
|
|
102
|
+
_docKeyHandler = null;
|
|
103
|
+
_docPointerHandler = null;
|
|
104
|
+
_observer = null;
|
|
105
|
+
async onMount() {
|
|
106
|
+
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
|
|
107
|
+
const root = this.__vmzDomRoot;
|
|
108
|
+
if (!root || typeof root.querySelector !== "function") return;
|
|
109
|
+
this._observer = new MutationObserver(() => this._syncFromDom());
|
|
110
|
+
this._observer.observe(root, {
|
|
111
|
+
childList: true,
|
|
112
|
+
subtree: true,
|
|
113
|
+
});
|
|
114
|
+
this._syncFromDom();
|
|
115
|
+
}
|
|
116
|
+
onDestroy() {
|
|
117
|
+
if (this._observer) {
|
|
118
|
+
this._observer.disconnect();
|
|
119
|
+
this._observer = null;
|
|
120
|
+
}
|
|
121
|
+
this._teardownDoc();
|
|
122
|
+
this._panel = null;
|
|
123
|
+
this._restoreFocus = null;
|
|
124
|
+
}
|
|
125
|
+
toggle() {
|
|
126
|
+
if (typeof this.onOpenChange === "function") this.onOpenChange(!this.open);
|
|
127
|
+
}
|
|
128
|
+
close() {
|
|
129
|
+
if (typeof this.onOpenChange === "function") this.onOpenChange(false);
|
|
130
|
+
}
|
|
131
|
+
choose(id) {
|
|
132
|
+
if (typeof this.onSelect === "function") this.onSelect(id);
|
|
133
|
+
this.close();
|
|
134
|
+
}
|
|
135
|
+
onItemClick(ev) {
|
|
136
|
+
const el = ev?.currentTarget;
|
|
137
|
+
const id =
|
|
138
|
+
el && typeof el.getAttribute === "function"
|
|
139
|
+
? el.getAttribute("data-vmz-menu-item")
|
|
140
|
+
: null;
|
|
141
|
+
if (id) this.choose(id);
|
|
142
|
+
}
|
|
143
|
+
onTriggerKeyDown(ev) {
|
|
144
|
+
if (!ev) return;
|
|
145
|
+
if ((ev.key === "ArrowDown" || ev.key === "Enter" || ev.key === " ") && !this.open) {
|
|
146
|
+
ev.preventDefault();
|
|
147
|
+
if (typeof this.onOpenChange === "function") this.onOpenChange(true);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
onMenuKeyDown(ev) {
|
|
151
|
+
if (!ev || !this._panel) return;
|
|
152
|
+
if (ev.key === "Escape") {
|
|
153
|
+
ev.preventDefault();
|
|
154
|
+
this.close();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const nodes = [...this._panel.querySelectorAll('[role="menuitem"]')].filter(
|
|
158
|
+
(n) => n instanceof HTMLElement && n.offsetParent !== null,
|
|
159
|
+
);
|
|
160
|
+
if (!nodes.length) return;
|
|
161
|
+
const idx = nodes.indexOf(document.activeElement);
|
|
162
|
+
if (ev.key === "ArrowDown") {
|
|
163
|
+
ev.preventDefault();
|
|
164
|
+
nodes[(idx + 1 + nodes.length) % nodes.length].focus();
|
|
165
|
+
} else if (ev.key === "ArrowUp") {
|
|
166
|
+
ev.preventDefault();
|
|
167
|
+
nodes[(idx - 1 + nodes.length) % nodes.length].focus();
|
|
168
|
+
} else if (ev.key === "Home") {
|
|
169
|
+
ev.preventDefault();
|
|
170
|
+
nodes[0].focus();
|
|
171
|
+
} else if (ev.key === "End") {
|
|
172
|
+
ev.preventDefault();
|
|
173
|
+
nodes[nodes.length - 1].focus();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
_syncFromDom() {
|
|
177
|
+
const root = this.__vmzDomRoot;
|
|
178
|
+
const panel =
|
|
179
|
+
root && typeof root.querySelector === "function"
|
|
180
|
+
? root.querySelector('[data-vmz-overlay="menu"][data-vmz-focus="enter"]')
|
|
181
|
+
: null;
|
|
182
|
+
if (panel && !this._panel) this._enterFocus(panel);
|
|
183
|
+
else if (!panel && this._panel) this._exitFocus();
|
|
184
|
+
}
|
|
185
|
+
_enterFocus(panel) {
|
|
186
|
+
if (typeof document === "undefined") return;
|
|
187
|
+
const root = this.__vmzDomRoot;
|
|
188
|
+
const trigger =
|
|
189
|
+
root && typeof root.querySelector === "function"
|
|
190
|
+
? root.querySelector('[data-vmz-menu="trigger"]')
|
|
191
|
+
: null;
|
|
192
|
+
const active = document.activeElement;
|
|
193
|
+
if (!this._restoreFocus) {
|
|
194
|
+
if (trigger instanceof HTMLElement) this._restoreFocus = trigger;
|
|
195
|
+
else if (active instanceof HTMLElement) this._restoreFocus = active;
|
|
196
|
+
}
|
|
197
|
+
this._panel = panel instanceof HTMLElement ? panel : null;
|
|
198
|
+
const first =
|
|
199
|
+
this._panel && typeof this._panel.querySelector === "function"
|
|
200
|
+
? this._panel.querySelector('[role="menuitem"]')
|
|
201
|
+
: null;
|
|
202
|
+
if (first && typeof first.focus === "function") first.focus();
|
|
203
|
+
else if (this._panel && typeof this._panel.focus === "function") this._panel.focus();
|
|
204
|
+
if (!this._docKeyHandler) {
|
|
205
|
+
this._docKeyHandler = (ev) => {
|
|
206
|
+
if (ev.key === "Escape") {
|
|
207
|
+
ev.preventDefault();
|
|
208
|
+
this.close();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
document.addEventListener("keydown", this._docKeyHandler);
|
|
212
|
+
}
|
|
213
|
+
if (!this._docPointerHandler) {
|
|
214
|
+
this._docPointerHandler = (ev) => {
|
|
215
|
+
const t = ev.target;
|
|
216
|
+
if (!(t instanceof Node)) return;
|
|
217
|
+
if (root && typeof root.contains === "function" && root.contains(t)) return;
|
|
218
|
+
this.close();
|
|
219
|
+
};
|
|
220
|
+
document.addEventListener("pointerdown", this._docPointerHandler, true);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
_exitFocus() {
|
|
224
|
+
this._teardownDoc();
|
|
225
|
+
this._panel = null;
|
|
226
|
+
const restore = this._restoreFocus;
|
|
227
|
+
this._restoreFocus = null;
|
|
228
|
+
if (restore && typeof restore.focus === "function" && document.contains(restore)) {
|
|
229
|
+
restore.focus();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
_teardownDoc() {
|
|
233
|
+
if (typeof document !== "undefined") {
|
|
234
|
+
if (this._docKeyHandler) document.removeEventListener("keydown", this._docKeyHandler);
|
|
235
|
+
if (this._docPointerHandler) {
|
|
236
|
+
document.removeEventListener("pointerdown", this._docPointerHandler, true);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
this._docKeyHandler = null;
|
|
240
|
+
this._docPointerHandler = null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
</script>
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<nav data-vmz-ui="pagination" :aria-label="label" class="vmz-ui-pagination">
|
|
3
|
-
<button type="button" :disabled="prevDisabled" @click="onPrevClick" class="vmz-ui-pagination__btn">
|
|
4
|
-
Previous
|
|
5
|
-
</button>
|
|
6
|
-
<p data-vmz-fixture="page-status" class="vmz-ui-pagination__status">
|
|
7
|
-
{{ status }}
|
|
8
|
-
</p>
|
|
9
|
-
<button type="button" :disabled="nextDisabled" @click="onNextClick" class="vmz-ui-pagination__btn">
|
|
10
|
-
Next
|
|
11
|
-
</button>
|
|
12
|
-
</nav>
|
|
13
|
-
</template>
|
|
14
|
-
<style>
|
|
15
|
-
.vmz-ui-pagination {
|
|
16
|
-
display: flex;
|
|
17
|
-
flex-wrap: wrap;
|
|
18
|
-
align-items: center;
|
|
19
|
-
gap: 0.
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.vmz-ui-pagination__btn {
|
|
23
|
-
font: inherit;
|
|
24
|
-
font-weight: 600;
|
|
25
|
-
padding: 0.
|
|
26
|
-
border: 1px solid var(--vmz-line-default);
|
|
27
|
-
border-radius: var(--vmz-radius-md, 6px);
|
|
28
|
-
background: transparent;
|
|
29
|
-
color: var(--vmz-action-primary-active);
|
|
30
|
-
cursor: pointer;
|
|
31
|
-
outline: none;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.vmz-ui-pagination__btn:focus-visible {
|
|
35
|
-
box-shadow: 0 0 0
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.vmz-ui-pagination__btn:disabled {
|
|
39
|
-
opacity: 0.55;
|
|
40
|
-
cursor: not-allowed;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.vmz-ui-pagination__status {
|
|
44
|
-
margin: 0;
|
|
45
|
-
font-weight: 600;
|
|
46
|
-
}
|
|
47
|
-
</style>
|
|
48
|
-
<script client>
|
|
49
|
-
/**
|
|
50
|
-
* VMZ UI — Pagination (Console composition skeleton).
|
|
51
|
-
* Parent owns page / pageCount; buttons call onPrev / onNext.
|
|
52
|
-
*/
|
|
53
|
-
export default class Pagination {
|
|
54
|
-
public label: string = "Pagination";
|
|
55
|
-
public status: string = "Page 1 / 1";
|
|
56
|
-
public prevDisabled: boolean = true;
|
|
57
|
-
public nextDisabled: boolean = true;
|
|
58
|
-
public onPrev: (() => void) | null = null;
|
|
59
|
-
public onNext: (() => void) | null = null;
|
|
60
|
-
onPrevClick() {
|
|
61
|
-
if (this.prevDisabled) return;
|
|
62
|
-
if (typeof this.onPrev === "function") this.onPrev();
|
|
63
|
-
}
|
|
64
|
-
onNextClick() {
|
|
65
|
-
if (this.nextDisabled) return;
|
|
66
|
-
if (typeof this.onNext === "function") this.onNext();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<nav data-vmz-ui="pagination" :aria-label="label" class="vmz-ui-pagination">
|
|
3
|
+
<button type="button" :disabled="prevDisabled" @click="onPrevClick" class="vmz-ui-pagination__btn">
|
|
4
|
+
Previous
|
|
5
|
+
</button>
|
|
6
|
+
<p data-vmz-fixture="page-status" class="vmz-ui-pagination__status">
|
|
7
|
+
{{ status }}
|
|
8
|
+
</p>
|
|
9
|
+
<button type="button" :disabled="nextDisabled" @click="onNextClick" class="vmz-ui-pagination__btn">
|
|
10
|
+
Next
|
|
11
|
+
</button>
|
|
12
|
+
</nav>
|
|
13
|
+
</template>
|
|
14
|
+
<style>
|
|
15
|
+
.vmz-ui-pagination {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 0.625rem 1rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.vmz-ui-pagination__btn {
|
|
23
|
+
font: inherit;
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
padding: 0.375rem 0.7rem;
|
|
26
|
+
border: 1px solid var(--vmz-line-default);
|
|
27
|
+
border-radius: var(--vmz-radius-md, 6px);
|
|
28
|
+
background: transparent;
|
|
29
|
+
color: var(--vmz-action-primary-active);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
outline: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.vmz-ui-pagination__btn:focus-visible {
|
|
35
|
+
box-shadow: 0 0 0 3px var(--vmz-focus-ring);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vmz-ui-pagination__btn:disabled {
|
|
39
|
+
opacity: 0.55;
|
|
40
|
+
cursor: not-allowed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.vmz-ui-pagination__status {
|
|
44
|
+
margin: 0;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
<script client>
|
|
49
|
+
/**
|
|
50
|
+
* VMZ UI — Pagination (Console composition skeleton).
|
|
51
|
+
* Parent owns page / pageCount; buttons call onPrev / onNext.
|
|
52
|
+
*/
|
|
53
|
+
export default class Pagination {
|
|
54
|
+
public label: string = "Pagination";
|
|
55
|
+
public status: string = "Page 1 / 1";
|
|
56
|
+
public prevDisabled: boolean = true;
|
|
57
|
+
public nextDisabled: boolean = true;
|
|
58
|
+
public onPrev: (() => void) | null = null;
|
|
59
|
+
public onNext: (() => void) | null = null;
|
|
60
|
+
onPrevClick() {
|
|
61
|
+
if (this.prevDisabled) return;
|
|
62
|
+
if (typeof this.onPrev === "function") this.onPrev();
|
|
63
|
+
}
|
|
64
|
+
onNextClick() {
|
|
65
|
+
if (this.nextDisabled) return;
|
|
66
|
+
if (typeof this.onNext === "function") this.onNext();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|