@vmz/ui 0.3.7 → 0.3.8

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 (68) hide show
  1. package/README.md +2 -0
  2. package/contracts/token-requirements.v0.json +58 -1
  3. package/package.json +5 -2
  4. package/presets/web-surface.v0.json +14 -12
  5. package/src/components/Accordion.vmz +49 -16
  6. package/src/components/Alert.vmz +125 -28
  7. package/src/components/AppShell.vmz +121 -56
  8. package/src/components/Autocomplete.vmz +136 -29
  9. package/src/components/Avatar.vmz +20 -12
  10. package/src/components/Badge.vmz +18 -26
  11. package/src/components/Breadcrumb.vmz +28 -14
  12. package/src/components/BulkActions.vmz +13 -8
  13. package/src/components/Button.vmz +68 -44
  14. package/src/components/Callout.vmz +128 -27
  15. package/src/components/Card.vmz +67 -46
  16. package/src/components/Checkbox.vmz +81 -5
  17. package/src/components/CodeBlock.vmz +59 -27
  18. package/src/components/Collapse.vmz +64 -11
  19. package/src/components/ConsoleShell.vmz +217 -63
  20. package/src/components/Content.vmz +15 -1
  21. package/src/components/DataTable.vmz +123 -23
  22. package/src/components/DatePicker.vmz +171 -79
  23. package/src/components/Descriptions.vmz +90 -0
  24. package/src/components/Dialog.vmz +133 -50
  25. package/src/components/Divider.vmz +8 -1
  26. package/src/components/Drawer.vmz +131 -40
  27. package/src/components/Dropdown.vmz +147 -49
  28. package/src/components/Empty.vmz +56 -14
  29. package/src/components/Field.vmz +78 -23
  30. package/src/components/FilterBar.vmz +34 -4
  31. package/src/components/Flex.vmz +3 -1
  32. package/src/components/Footer.vmz +6 -3
  33. package/src/components/Form.vmz +41 -0
  34. package/src/components/FormItem.vmz +2 -3
  35. package/src/components/Grid.vmz +6 -3
  36. package/src/components/Header.vmz +193 -6
  37. package/src/components/Layout.vmz +3 -1
  38. package/src/components/Link.vmz +20 -9
  39. package/src/components/List.vmz +35 -13
  40. package/src/components/Menu.vmz +198 -25
  41. package/src/components/Message.vmz +167 -0
  42. package/src/components/Notification.vmz +132 -37
  43. package/src/components/Pagination.vmz +186 -20
  44. package/src/components/Popconfirm.vmz +287 -0
  45. package/src/components/Popover.vmz +152 -34
  46. package/src/components/Progress.vmz +16 -5
  47. package/src/components/Prose.vmz +131 -32
  48. package/src/components/QueryForm.vmz +31 -9
  49. package/src/components/RadioGroup.vmz +91 -11
  50. package/src/components/Result.vmz +60 -27
  51. package/src/components/Segmented.vmz +46 -11
  52. package/src/components/Select.vmz +174 -87
  53. package/src/components/Sider.vmz +120 -7
  54. package/src/components/Skeleton.vmz +9 -7
  55. package/src/components/Space.vmz +9 -3
  56. package/src/components/Spinner.vmz +5 -4
  57. package/src/components/Steps.vmz +40 -25
  58. package/src/components/Switch.vmz +48 -15
  59. package/src/components/Table.vmz +130 -25
  60. package/src/components/Tabs.vmz +28 -13
  61. package/src/components/Tag.vmz +29 -12
  62. package/src/components/TextArea.vmz +32 -25
  63. package/src/components/Timeline.vmz +65 -30
  64. package/src/components/Toc.vmz +45 -29
  65. package/src/components/Tooltip.vmz +70 -21
  66. package/src/components/Tree.vmz +48 -25
  67. package/src/components/Typography.vmz +23 -8
  68. package/src/components/Upload.vmz +191 -96
@@ -1,13 +1,35 @@
1
1
  <template>
2
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
- {{ prevLabel }}
3
+ <button type="button" :disabled="prevDisabled" :aria-label="prevLabel ? prevLabel : null" @click="onPrevClick" class="vmz-ui-pagination__nav">
4
+ <Icon name="action.chevron-left" size="sm" decorative />
5
+ <span v-if="prevLabel" class="vmz-ui-pagination__nav-label">
6
+ {{ prevLabel }}
7
+ </span>
5
8
  </button>
9
+ <ul v-if="pageItems.length" class="vmz-ui-pagination__pages">
10
+ <li v-for="item in pageItems" :key="item.key" class="vmz-ui-pagination__page-item">
11
+ <span v-if="item.gap" aria-hidden="true" class="vmz-ui-pagination__ellipsis">
12
+ <Icon name="action.more" size="sm" decorative />
13
+ </span>
14
+ <button v-else type="button" :data-vmz-page="item.page" :aria-current="item.page === currentPage ? 'page' : null" :aria-label="pageAria(item.page)" @click="onPageClick(item.page)" class="vmz-ui-pagination__page">
15
+ {{ item.page }}
16
+ </button>
17
+ </li>
18
+ </ul>
6
19
  <p data-vmz-fixture="page-status" class="vmz-ui-pagination__status">
7
20
  {{ status }}
8
21
  </p>
9
- <button type="button" :disabled="nextDisabled" @click="onNextClick" class="vmz-ui-pagination__btn">
10
- {{ nextLabel }}
22
+ <span v-if="pageSizeOptions.length" class="vmz-ui-pagination__size">
23
+ <span v-if="pageSizeLabel" class="vmz-ui-pagination__size-label">
24
+ {{ pageSizeLabel }}
25
+ </span>
26
+ <Select :control-id="pageSizeControlId" :value="pageSize" :options="pageSizeOptions" :on-change="onPageSizeSelect" />
27
+ </span>
28
+ <button type="button" :disabled="nextDisabled" :aria-label="nextLabel ? nextLabel : null" @click="onNextClick" class="vmz-ui-pagination__nav">
29
+ <span v-if="nextLabel" class="vmz-ui-pagination__nav-label">
30
+ {{ nextLabel }}
31
+ </span>
32
+ <Icon name="action.chevron-right" size="sm" decorative />
11
33
  </button>
12
34
  </nav>
13
35
  </template>
@@ -16,49 +38,193 @@
16
38
  display: flex;
17
39
  flex-wrap: wrap;
18
40
  align-items: center;
19
- gap: 0.625rem 1rem;
41
+ gap: 0.5rem;
42
+ font-size: 0.875rem;
43
+ color: var(--vmz-text-ink);
20
44
  }
21
45
 
22
- .vmz-ui-pagination__btn {
46
+ .vmz-ui-pagination__nav,
47
+ .vmz-ui-pagination__page {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ gap: 0.25rem;
52
+ min-width: 2rem;
53
+ height: 2rem;
54
+ padding: 0 0.5rem;
55
+ box-sizing: border-box;
23
56
  font: inherit;
24
- font-weight: 600;
25
- padding: 0.375rem 0.7rem;
57
+ font-size: 0.875rem;
58
+ font-weight: 500;
26
59
  border: 1px solid var(--vmz-line-default);
27
60
  border-radius: var(--vmz-radius-md, 6px);
28
- background: transparent;
29
- color: var(--vmz-action-primary-active);
61
+ background: var(--vmz-surface-paper);
62
+ color: var(--vmz-text-ink);
30
63
  cursor: pointer;
31
64
  outline: none;
65
+ transition:
66
+ color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
67
+ border-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
68
+ background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
69
+ }
70
+
71
+ .vmz-ui-pagination__nav:hover:not(:disabled),
72
+ .vmz-ui-pagination__page:hover:not([aria-current="page"]) {
73
+ background: var(--vmz-surface-mist);
74
+ border-color: var(--vmz-line-default);
75
+ color: var(--vmz-action-primary-background);
32
76
  }
33
77
 
34
- .vmz-ui-pagination__btn:focus-visible {
78
+ .vmz-ui-pagination__nav:focus-visible,
79
+ .vmz-ui-pagination__page:focus-visible {
35
80
  box-shadow: 0 0 0 3px var(--vmz-focus-ring);
36
81
  }
37
82
 
38
- .vmz-ui-pagination__btn:disabled {
83
+ .vmz-ui-pagination__nav:disabled {
39
84
  opacity: 0.55;
40
85
  cursor: not-allowed;
41
86
  }
42
87
 
43
- .vmz-ui-pagination__status {
88
+ .vmz-ui-pagination__pages {
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ align-items: center;
92
+ gap: 0.25rem;
44
93
  margin: 0;
45
- font-weight: 600;
94
+ padding: 0;
95
+ list-style: none;
96
+ }
97
+
98
+ .vmz-ui-pagination__page {
99
+ min-width: 2rem;
100
+ padding: 0;
101
+ font-variant-numeric: tabular-nums;
102
+ }
103
+
104
+ .vmz-ui-pagination__page[aria-current="page"] {
105
+ border-color: var(--vmz-action-primary-background);
106
+ background: var(--vmz-action-primary-background);
107
+ color: var(--vmz-action-primary-foreground);
108
+ cursor: default;
109
+ }
110
+
111
+ .vmz-ui-pagination__ellipsis {
112
+ display: inline-flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ width: 2rem;
116
+ height: 2rem;
117
+ color: var(--vmz-text-tertiary);
118
+ }
119
+
120
+ .vmz-ui-pagination__status {
121
+ margin: 0 0 0 auto;
122
+ font-size: 0.875rem;
123
+ font-weight: 400;
124
+ color: var(--vmz-text-steel);
125
+ }
126
+
127
+ .vmz-ui-pagination__size {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 0.5rem;
131
+ }
132
+
133
+ .vmz-ui-pagination__size-label {
134
+ font-size: 0.875rem;
135
+ font-weight: 500;
136
+ color: var(--vmz-text-steel);
137
+ white-space: nowrap;
138
+ }
139
+
140
+ .vmz-ui-pagination__size .vmz-ui-select {
141
+ max-width: 6rem;
142
+ }
143
+
144
+ .vmz-ui-pagination__size .vmz-ui-select__control {
145
+ min-height: 2rem;
146
+ padding-top: 0;
147
+ padding-bottom: 0;
46
148
  }
47
149
  </style>
48
150
  <script client>
49
151
  /**
50
- * VMZ UI — Pagination (Console composition skeleton).
51
- * Parent owns page / pageCount; buttons call onPrev / onNext.
152
+ * VMZ UI — Pagination.
153
+ * Parent owns page / pageCount; buttons call onPrev / onNext / onPage.
154
+ * Optional page-size Select renders when `pageSizeOptions` is non-empty.
52
155
  */
53
156
  export default class Pagination {
54
- public label: string = "Pagination";
55
- public prevLabel: string = "Previous";
56
- public nextLabel: string = "Next";
57
- public status: string = "Page 1 / 1";
157
+ public label: string = "";
158
+ public prevLabel: string = "";
159
+ public nextLabel: string = "";
160
+ public status: string = "";
58
161
  public prevDisabled: boolean = true;
59
162
  public nextDisabled: boolean = true;
163
+ public page: number = 1;
164
+ public pageCount: number = 0;
165
+ /** Normalised numeric page mirror read by the template. */
166
+ public currentPage: number = 1;
167
+ public pageLabel: string = "";
168
+ public pageSize: string = "";
169
+ public pageSizeLabel: string = "";
170
+ public pageSizeControlId: string = "vmz-pagination-size";
171
+ /** @type {{ value: string, label: string }[]} */
172
+ public pageSizeOptions: {
173
+ value: string;
174
+ label: string;
175
+ }[] = [];
176
+ /** @type {{ key: string, page: number, gap: boolean }[]} */
177
+ pageItems: {
178
+ key: string;
179
+ page: number;
180
+ gap: boolean;
181
+ }[] = [];
60
182
  public onPrev: (() => void) | null = null;
61
183
  public onNext: (() => void) | null = null;
184
+ public onPage: ((page: number) => void) | null = null;
185
+ public onPageSizeChange: ((value: string) => void) | null = null;
186
+ onMount() {
187
+ this._rebuildPages();
188
+ }
189
+ /** Parent-owned prop writes — keep the page window in sync. */
190
+ __vmzOnParentProp(name: string) {
191
+ if (name === "page" || name === "pageCount") this._rebuildPages();
192
+ }
193
+ _rebuildPages() {
194
+ const total = Math.max(0, Math.floor(Number(this.pageCount) || 0));
195
+ const current = Math.min(Math.max(1, Math.floor(Number(this.page) || 1)), Math.max(1, total));
196
+ this.currentPage = current;
197
+ if (total <= 0) {
198
+ this.pageItems = [];
199
+ return;
200
+ }
201
+ const items = [];
202
+ const pageItem = (p) => ({ key: `p-${p}`, page: p, gap: false });
203
+ const gapItem = (k) => ({ key: k, page: 0, gap: true });
204
+ if (total <= 7) {
205
+ for (let p = 1; p <= total; p += 1) items.push(pageItem(p));
206
+ } else {
207
+ items.push(pageItem(1));
208
+ const start = Math.max(2, current - 1);
209
+ const end = Math.min(total - 1, current + 1);
210
+ if (start > 2) items.push(gapItem("gap-start"));
211
+ for (let p = start; p <= end; p += 1) items.push(pageItem(p));
212
+ if (end < total - 1) items.push(gapItem("gap-end"));
213
+ items.push(pageItem(total));
214
+ }
215
+ this.pageItems = items;
216
+ }
217
+ pageAria(p) {
218
+ return this.pageLabel ? `${this.pageLabel} ${p}` : String(p);
219
+ }
220
+ onPageClick(p) {
221
+ if (p === this.currentPage) return;
222
+ if (typeof this.onPage === "function") this.onPage(p);
223
+ }
224
+ onPageSizeSelect(payload) {
225
+ const next = payload && payload.target ? payload.target.value : "";
226
+ if (typeof this.onPageSizeChange === "function") this.onPageSizeChange(next);
227
+ }
62
228
  onPrevClick() {
63
229
  if (this.prevDisabled) return;
64
230
  if (typeof this.onPrev === "function") this.onPrev();
@@ -0,0 +1,287 @@
1
+ <template>
2
+ <span data-vmz-ui="popconfirm" :data-tone="tone" class="vmz-ui-popconfirm-root">
3
+ <span class="vmz-ui-popconfirm__trigger">
4
+ <slot />
5
+ </span>
6
+ <span v-if="open" data-vmz-overlay="popconfirm" data-vmz-overlay-owner="popconfirm" :data-vmz-overlay-stack="stackLevel" class="vmz-ui-popconfirm">
7
+ <button type="button" data-vmz-overlay-layer="backdrop" :aria-label="cancelLabel || title" @click="cancel" class="vmz-ui-popconfirm__backdrop" />
8
+ <span role="dialog" aria-modal="false" :aria-labelledby="title ? titleId : null" tabindex="-1" data-vmz-overlay-layer="panel" data-vmz-focus="enter" :onKeyDown="onPanelKeyDown" class="vmz-ui-popconfirm__panel">
9
+ <span aria-hidden="true" class="vmz-ui-popconfirm__icon">
10
+ <Icon :name="toneIcon()" size="md" decorative />
11
+ </span>
12
+ <span class="vmz-ui-popconfirm__content">
13
+ <span v-if="title" :id="titleId" class="vmz-ui-popconfirm__title">
14
+ {{ title }}
15
+ </span>
16
+ <span v-if="description" class="vmz-ui-popconfirm__description">
17
+ {{ description }}
18
+ </span>
19
+ <span class="vmz-ui-popconfirm__actions">
20
+ <Button type="button" variant="secondary" size="small" @click="cancel">
21
+ {{ cancelLabel }}
22
+ </Button>
23
+ <Button type="button" :variant="tone === 'danger' ? 'danger' : 'primary'" size="small" @click="confirm">
24
+ {{ confirmLabel }}
25
+ </Button>
26
+ </span>
27
+ </span>
28
+ </span>
29
+ </span>
30
+ </span>
31
+ </template>
32
+ <style>
33
+ .vmz-ui-popconfirm-root {
34
+ display: inline-flex;
35
+ }
36
+
37
+ .vmz-ui-popconfirm {
38
+ position: fixed;
39
+ inset: 0;
40
+ z-index: 35;
41
+ pointer-events: none;
42
+ }
43
+
44
+ .vmz-ui-popconfirm[data-vmz-overlay-stack="1"] {
45
+ z-index: 50;
46
+ }
47
+
48
+ .vmz-ui-popconfirm[data-vmz-overlay-stack="2"] {
49
+ z-index: 60;
50
+ }
51
+
52
+ .vmz-ui-popconfirm[data-vmz-overlay-stack="3"] {
53
+ z-index: 70;
54
+ }
55
+
56
+ .vmz-ui-popconfirm__backdrop {
57
+ position: absolute;
58
+ inset: 0;
59
+ border: 0;
60
+ padding: 0;
61
+ margin: 0;
62
+ cursor: default;
63
+ background: transparent;
64
+ pointer-events: auto;
65
+ }
66
+
67
+ .vmz-ui-popconfirm__panel {
68
+ position: absolute;
69
+ z-index: 1;
70
+ top: 30%;
71
+ left: 50%;
72
+ transform: translateX(-50%);
73
+ box-sizing: border-box;
74
+ display: flex;
75
+ align-items: flex-start;
76
+ gap: 0.5rem;
77
+ width: min(340px, calc(100vw - 32px));
78
+ padding: 12px;
79
+ border: 1px solid var(--vmz-line-subtle);
80
+ border-radius: var(--vmz-radius-lg);
81
+ background: var(--vmz-surface-paper);
82
+ color: var(--vmz-text-ink);
83
+ box-shadow: var(--vmz-shadow-popup);
84
+ outline: none;
85
+ pointer-events: auto;
86
+ animation: vmz-ui-popconfirm-in var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
87
+ }
88
+
89
+ .vmz-ui-popconfirm__panel:focus-visible {
90
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring), var(--vmz-shadow-popup);
91
+ }
92
+
93
+ .vmz-ui-popconfirm__icon {
94
+ flex: 0 0 auto;
95
+ display: inline-flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ height: 1.5rem;
99
+ color: var(--vmz-status-warning-accent);
100
+ }
101
+
102
+ .vmz-ui-popconfirm[data-tone="danger"] .vmz-ui-popconfirm__icon {
103
+ color: var(--vmz-status-danger-accent);
104
+ }
105
+
106
+ .vmz-ui-popconfirm[data-tone="info"] .vmz-ui-popconfirm__icon {
107
+ color: var(--vmz-status-info-accent);
108
+ }
109
+
110
+ .vmz-ui-popconfirm__content {
111
+ flex: 1 1 auto;
112
+ display: block;
113
+ min-width: 0;
114
+ }
115
+
116
+ .vmz-ui-popconfirm__title {
117
+ display: block;
118
+ font-size: 0.875rem;
119
+ font-weight: 600;
120
+ line-height: 1.5;
121
+ color: var(--vmz-text-ink);
122
+ }
123
+
124
+ .vmz-ui-popconfirm__description {
125
+ display: block;
126
+ margin-top: 2px;
127
+ font-size: 0.875rem;
128
+ line-height: 1.5;
129
+ color: var(--vmz-text-steel);
130
+ }
131
+
132
+ .vmz-ui-popconfirm__actions {
133
+ display: flex;
134
+ flex-wrap: wrap;
135
+ align-items: center;
136
+ justify-content: flex-end;
137
+ gap: 8px;
138
+ margin-top: 10px;
139
+ }
140
+
141
+ @keyframes vmz-ui-popconfirm-in {
142
+ from {
143
+ opacity: 0;
144
+ transform: translateX(-50%) translateY(-6px) scale(0.98);
145
+ }
146
+ to {
147
+ opacity: 1;
148
+ transform: translateX(-50%) translateY(0) scale(1);
149
+ }
150
+ }
151
+
152
+ @media (prefers-reduced-motion: reduce) {
153
+ .vmz-ui-popconfirm__panel {
154
+ animation: none;
155
+ }
156
+ }
157
+ </style>
158
+ <script client>
159
+ /**
160
+ * VMZ UI — Popconfirm.
161
+ * Destructive-action confirmation. Same overlay contract as Popover: Escape,
162
+ * outside dismiss, focus enter/restore, stack-aware z-index.
163
+ * Parent owns `open`; confirm/cancel are emitted, the caller performs the action.
164
+ */
165
+ export default class Popconfirm {
166
+ public open: boolean = false;
167
+ public title: string = "";
168
+ public description: string = "";
169
+ public tone: string = "danger";
170
+ public confirmLabel: string = "";
171
+ public cancelLabel: string = "";
172
+ public titleId: string = "vmz-popconfirm-title";
173
+ public stackLevel: string = "0";
174
+ public onConfirm: (() => void) | null = null;
175
+ public onCancel: (() => void) | null = null;
176
+ _restoreFocus = null;
177
+ _panel = null;
178
+ _docKeyHandler = null;
179
+ _observer = null;
180
+ onMount() {
181
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
182
+ const root = this.__vmzDomRoot;
183
+ if (!root || typeof root.querySelector !== "function") return;
184
+ this._observer = new MutationObserver(() => this._syncFromDom());
185
+ this._observer.observe(root, {
186
+ childList: true,
187
+ subtree: true,
188
+ });
189
+ this._syncFromDom();
190
+ }
191
+ onDestroy() {
192
+ if (this._observer) {
193
+ this._observer.disconnect();
194
+ this._observer = null;
195
+ }
196
+ this._teardownDocKey();
197
+ this._panel = null;
198
+ this._restoreFocus = null;
199
+ }
200
+ confirm() {
201
+ this.emit("confirm");
202
+ if (typeof this.onConfirm === "function") this.onConfirm();
203
+ }
204
+ cancel() {
205
+ this.emit("cancel");
206
+ if (typeof this.onCancel === "function") this.onCancel();
207
+ }
208
+ toneIcon() {
209
+ const tone = String(this.tone || "").toLowerCase();
210
+ if (tone === "danger" || tone === "info" || tone === "success") return `status.${tone}`;
211
+ return "status.warning";
212
+ }
213
+ _isTopmostStack() {
214
+ if (typeof document === "undefined") return true;
215
+ const root = this.__vmzDomRoot;
216
+ const mine =
217
+ root && typeof root.querySelector === "function"
218
+ ? root.querySelector("[data-vmz-overlay][data-vmz-overlay-stack]")
219
+ : null;
220
+ const myStack = Number(mine?.getAttribute("data-vmz-overlay-stack") || this.stackLevel || "0");
221
+ const open = document.querySelectorAll("[data-vmz-overlay][data-vmz-overlay-stack]");
222
+ let max = -Infinity;
223
+ for (const el of open) {
224
+ const n = Number(el.getAttribute("data-vmz-overlay-stack") || "0");
225
+ if (Number.isFinite(n) && n > max) max = n;
226
+ }
227
+ if (!Number.isFinite(max)) return true;
228
+ return (Number.isFinite(myStack) ? myStack : 0) >= max;
229
+ }
230
+ onPanelKeyDown(ev) {
231
+ if (!ev || ev.key !== "Escape") return;
232
+ if (!this._isTopmostStack()) return;
233
+ ev.preventDefault();
234
+ ev.stopPropagation();
235
+ this.cancel();
236
+ }
237
+ _syncFromDom() {
238
+ const root = this.__vmzDomRoot;
239
+ const panel =
240
+ root && typeof root.querySelector === "function"
241
+ ? root.querySelector('[data-vmz-overlay="popconfirm"] [data-vmz-focus="enter"]')
242
+ : null;
243
+ if (panel && !this._panel) this._enterFocus(panel);
244
+ else if (!panel && this._panel) this._exitFocus();
245
+ }
246
+ _enterFocus(panel) {
247
+ if (typeof document === "undefined") return;
248
+ const active = document.activeElement;
249
+ if (!this._restoreFocus) {
250
+ const root = this.__vmzDomRoot;
251
+ const outside =
252
+ active instanceof HTMLElement &&
253
+ active !== document.body &&
254
+ !(root && typeof root.contains === "function" && root.contains(active));
255
+ if (outside) this._restoreFocus = active;
256
+ }
257
+ this._panel = panel instanceof HTMLElement ? panel : null;
258
+ if (this._panel && typeof this._panel.focus === "function") this._panel.focus();
259
+ if (!this._docKeyHandler) {
260
+ this._docKeyHandler = (ev) => {
261
+ if (ev.key !== "Escape") return;
262
+ if (ev.defaultPrevented) return;
263
+ if (!this._isTopmostStack()) return;
264
+ ev.preventDefault();
265
+ ev.stopPropagation();
266
+ this.cancel();
267
+ };
268
+ document.addEventListener("keydown", this._docKeyHandler);
269
+ }
270
+ }
271
+ _exitFocus() {
272
+ this._teardownDocKey();
273
+ this._panel = null;
274
+ const restore = this._restoreFocus;
275
+ this._restoreFocus = null;
276
+ if (restore && typeof restore.focus === "function" && document.contains(restore)) {
277
+ restore.focus();
278
+ }
279
+ }
280
+ _teardownDocKey() {
281
+ if (typeof document !== "undefined" && this._docKeyHandler) {
282
+ document.removeEventListener("keydown", this._docKeyHandler);
283
+ }
284
+ this._docKeyHandler = null;
285
+ }
286
+ }
287
+ </script>