@vmz/ui 0.0.2 → 0.0.4

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 (53) hide show
  1. package/README.md +25 -3
  2. package/conformance/fixtures/button-control-motion.json +22 -0
  3. package/conformance/fixtures/dialog-overlay-motion.json +27 -0
  4. package/conformance/fixtures/drawer-overlay-motion.json +27 -0
  5. package/conformance/pack.v0.json +7 -0
  6. package/contracts/token-requirements.v0.json +604 -2
  7. package/package.json +50 -2
  8. package/presets/web-surface.v0.json +34 -0
  9. package/src/components/Accordion.vmz +112 -0
  10. package/src/components/Alert.vmz +84 -0
  11. package/src/components/AppShell.vmz +97 -0
  12. package/src/components/Autocomplete.vmz +176 -0
  13. package/src/components/Badge.vmz +63 -0
  14. package/src/components/Breadcrumb.vmz +66 -0
  15. package/src/components/BulkActions.vmz +52 -0
  16. package/src/components/Button.vmz +71 -4
  17. package/src/components/Callout.vmz +80 -0
  18. package/src/components/Card.vmz +81 -0
  19. package/src/components/Checkbox.vmz +69 -0
  20. package/src/components/CodeBlock.vmz +67 -0
  21. package/src/components/ConsoleShell.vmz +127 -0
  22. package/src/components/DataTable.vmz +220 -0
  23. package/src/components/DatePicker.vmz +93 -0
  24. package/src/components/Dialog.vmz +442 -0
  25. package/src/components/Drawer.vmz +435 -0
  26. package/src/components/Empty.vmz +52 -0
  27. package/src/components/Field.vmz +88 -0
  28. package/src/components/FilterBar.vmz +27 -0
  29. package/src/components/Form.vmz +72 -0
  30. package/src/components/FormItem.vmz +78 -0
  31. package/src/components/Link.vmz +48 -0
  32. package/src/components/List.vmz +116 -0
  33. package/src/components/Menu.vmz +270 -0
  34. package/src/components/Notification.vmz +83 -0
  35. package/src/components/Pagination.vmz +81 -0
  36. package/src/components/Popover.vmz +253 -0
  37. package/src/components/Prose.vmz +83 -0
  38. package/src/components/QueryForm.vmz +39 -0
  39. package/src/components/RadioGroup.vmz +135 -0
  40. package/src/components/Result.vmz +84 -0
  41. package/src/components/Select.vmz +351 -0
  42. package/src/components/Skeleton.vmz +80 -0
  43. package/src/components/Spinner.vmz +45 -0
  44. package/src/components/Steps.vmz +107 -0
  45. package/src/components/Switch.vmz +109 -0
  46. package/src/components/Table.vmz +134 -0
  47. package/src/components/Tabs.vmz +132 -0
  48. package/src/components/TextArea.vmz +91 -0
  49. package/src/components/Timeline.vmz +67 -0
  50. package/src/components/Toc.vmz +77 -0
  51. package/src/components/Tooltip.vmz +68 -0
  52. package/src/components/Tree.vmz +215 -0
  53. package/src/components/Upload.vmz +102 -0
@@ -0,0 +1,435 @@
1
+ <template>
2
+ <div
3
+ class="vmz-ui-drawer-root"
4
+ data-vmz-ui="drawer"
5
+ data-vmz-overlay-open={open}
6
+ >
7
+ <div
8
+ if={open}
9
+ class="vmz-ui-drawer"
10
+ data-vmz-overlay="drawer"
11
+ data-vmz-overlay-owner="drawer"
12
+ data-vmz-overlay-stack={stackLevel}
13
+ >
14
+ <button
15
+ type="button"
16
+ class="vmz-ui-drawer__backdrop"
17
+ data-vmz-overlay-layer="backdrop"
18
+ aria-label="Dismiss drawer"
19
+ onClick={dismiss}
20
+ ></button>
21
+ <div
22
+ class="vmz-ui-drawer__panel"
23
+ role="dialog"
24
+ aria-modal="true"
25
+ aria-labelledby={titleId}
26
+ tabindex="-1"
27
+ data-vmz-overlay-layer="panel"
28
+ data-vmz-focus="enter"
29
+ data-vmz-motion="overlay-enter"
30
+ data-vmz-motion-token="motion.overlay"
31
+ onKeyDown={onPanelKeyDown}
32
+ >
33
+ <h2 id={titleId} class="vmz-ui-drawer__title">{title}</h2>
34
+ <div class="vmz-ui-drawer__body">
35
+ <slot />
36
+ </div>
37
+ <div class="vmz-ui-drawer__actions">
38
+ <button type="button" class="vmz-ui-drawer__close" data-vmz-focus="close" onClick={dismiss}>
39
+ Close
40
+ </button>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </template>
46
+
47
+ <style>
48
+ .vmz-ui-drawer-root {
49
+ display: block;
50
+ }
51
+
52
+ .vmz-ui-drawer {
53
+ position: fixed;
54
+ inset: 0;
55
+ z-index: 40;
56
+ }
57
+
58
+ .vmz-ui-drawer[data-vmz-overlay-stack='1'] {
59
+ z-index: 50;
60
+ }
61
+
62
+ .vmz-ui-drawer[data-vmz-overlay-stack='2'] {
63
+ z-index: 60;
64
+ }
65
+
66
+ .vmz-ui-drawer[data-vmz-overlay-stack='3'] {
67
+ z-index: 70;
68
+ }
69
+
70
+ .vmz-ui-drawer__backdrop {
71
+ position: absolute;
72
+ inset: 0;
73
+ border: 0;
74
+ padding: 0;
75
+ margin: 0;
76
+ cursor: pointer;
77
+ background: color-mix(in srgb, var(--vmz-action-primary-active) 35%, transparent);
78
+ animation: vmz-ui-drawer-fade-in var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
79
+ }
80
+
81
+ .vmz-ui-drawer__panel {
82
+ position: absolute;
83
+ top: 0;
84
+ right: 0;
85
+ z-index: 1;
86
+ box-sizing: border-box;
87
+ width: min(22rem, 100vw);
88
+ height: 100%;
89
+ padding: 1rem 1.1rem 1.1rem;
90
+ border-left: 1px solid var(--vmz-action-primary-background);
91
+ background: var(--vmz-action-primary-foreground);
92
+ color: var(--vmz-action-primary-active);
93
+ outline: none;
94
+ display: flex;
95
+ flex-direction: column;
96
+ animation: vmz-ui-drawer-panel-in var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
97
+ }
98
+
99
+ .vmz-ui-drawer[data-vmz-motion-adopt='true'] .vmz-ui-drawer__backdrop,
100
+ .vmz-ui-drawer[data-vmz-motion-adopt='true'] .vmz-ui-drawer__panel {
101
+ animation: none;
102
+ }
103
+
104
+ .vmz-ui-drawer[data-vmz-motion-phase='exit'] .vmz-ui-drawer__backdrop {
105
+ animation: vmz-ui-drawer-fade-out var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
106
+ }
107
+
108
+ .vmz-ui-drawer[data-vmz-motion-phase='exit'] .vmz-ui-drawer__panel {
109
+ animation: vmz-ui-drawer-panel-out var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
110
+ }
111
+
112
+ .vmz-ui-drawer__panel:focus-visible {
113
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
114
+ }
115
+
116
+ .vmz-ui-drawer__title {
117
+ margin: 0 0 0.65rem;
118
+ font-size: 1.1rem;
119
+ }
120
+
121
+ .vmz-ui-drawer__body {
122
+ flex: 1;
123
+ margin-bottom: 0.9rem;
124
+ overflow: auto;
125
+ }
126
+
127
+ .vmz-ui-drawer__close {
128
+ font: inherit;
129
+ font-weight: 600;
130
+ padding: 0.45rem 0.8rem;
131
+ border: 1px solid transparent;
132
+ border-radius: 2px;
133
+ cursor: pointer;
134
+ background: var(--vmz-action-primary-background);
135
+ color: var(--vmz-action-primary-foreground);
136
+ }
137
+
138
+ .vmz-ui-drawer__close:focus-visible {
139
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
140
+ }
141
+
142
+ @keyframes vmz-ui-drawer-fade-in {
143
+ from {
144
+ opacity: 0;
145
+ }
146
+ to {
147
+ opacity: 1;
148
+ }
149
+ }
150
+
151
+ @keyframes vmz-ui-drawer-panel-in {
152
+ from {
153
+ opacity: 0;
154
+ transform: translateX(0.75rem);
155
+ }
156
+ to {
157
+ opacity: 1;
158
+ transform: none;
159
+ }
160
+ }
161
+
162
+ @keyframes vmz-ui-drawer-fade-out {
163
+ from {
164
+ opacity: 1;
165
+ }
166
+ to {
167
+ opacity: 0;
168
+ }
169
+ }
170
+
171
+ @keyframes vmz-ui-drawer-panel-out {
172
+ from {
173
+ opacity: 1;
174
+ transform: none;
175
+ }
176
+ to {
177
+ opacity: 0;
178
+ transform: translateX(0.75rem);
179
+ }
180
+ }
181
+
182
+ @media (prefers-reduced-motion: reduce) {
183
+ .vmz-ui-drawer__backdrop,
184
+ .vmz-ui-drawer__panel,
185
+ .vmz-ui-drawer[data-vmz-motion-phase='exit'] .vmz-ui-drawer__backdrop,
186
+ .vmz-ui-drawer[data-vmz-motion-phase='exit'] .vmz-ui-drawer__panel {
187
+ animation: none;
188
+ }
189
+ }
190
+ </style>
191
+
192
+ <script client>
193
+ /**
194
+ * VMZ UI — Drawer (UI2).
195
+ * Same overlay ownership / focus / Escape / stack as Dialog.
196
+ * SSR/resume adopt skips enter replay; dismiss plays overlay-exit then calls onClose.
197
+ */
198
+ export default class Drawer {
199
+ public open: boolean = false;
200
+ public title: string = 'Drawer';
201
+ public titleId: string = 'vmz-drawer-title';
202
+ public stackLevel: string = '0';
203
+ public onClose: (() => void) | null = null;
204
+
205
+ _restoreFocus = null;
206
+ _panel = null;
207
+ _docKeyHandler = null;
208
+ _observer = null;
209
+ _adoptNextEnter = false;
210
+ _exiting = false;
211
+ _exitTimer = null;
212
+ /** Motion generation — exit timer and cancel edges key off this owner counter. */
213
+ _motionGen = 0;
214
+
215
+ async onMount() {
216
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return;
217
+ const root = this.__vmzDomRoot;
218
+ if (!root || typeof root.querySelector !== 'function') return;
219
+ if (root.querySelector('[data-vmz-overlay="drawer"]')) this._adoptNextEnter = true;
220
+ this._observer = new MutationObserver(() => this._syncFromDom());
221
+ this._observer.observe(root, { childList: true, subtree: true });
222
+ this._syncFromDom();
223
+ }
224
+
225
+ onDestroy() {
226
+ if (this._exitTimer) {
227
+ clearTimeout(this._exitTimer);
228
+ this._exitTimer = null;
229
+ }
230
+ this._exiting = false;
231
+ if (this._observer) {
232
+ this._observer.disconnect();
233
+ this._observer = null;
234
+ }
235
+ this._teardownDocKey();
236
+ this._panel = null;
237
+ this._restoreFocus = null;
238
+ }
239
+
240
+ dismiss() {
241
+ if (!this._isTopmostStack()) return;
242
+ if (this._exiting) {
243
+ this._cancelExit('reverse');
244
+ return;
245
+ }
246
+ const panel = this._panel;
247
+ const overlay =
248
+ panel && typeof panel.closest === 'function' ? panel.closest('[data-vmz-overlay="drawer"]') : null;
249
+ const reduce =
250
+ typeof window !== 'undefined' &&
251
+ typeof window.matchMedia === 'function' &&
252
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
253
+ if (panel && overlay && !reduce) {
254
+ this._exiting = true;
255
+ const exitGen = this._motionGen;
256
+ overlay.setAttribute('data-vmz-motion-phase', 'exit');
257
+ overlay.removeAttribute('data-vmz-motion-cancelled');
258
+ panel.setAttribute('data-vmz-motion', 'overlay-exit');
259
+ const ms = this._overlayDurationMs(panel);
260
+ this._exitTimer = setTimeout(() => {
261
+ this._exitTimer = null;
262
+ if (!this._exiting || exitGen !== this._motionGen) return;
263
+ this._exiting = false;
264
+ if (typeof this.onClose === 'function') this.onClose();
265
+ }, ms);
266
+ return;
267
+ }
268
+ if (typeof this.onClose === 'function') this.onClose();
269
+ }
270
+
271
+ _cancelExit(reason) {
272
+ if (this._exitTimer) {
273
+ clearTimeout(this._exitTimer);
274
+ this._exitTimer = null;
275
+ }
276
+ this._exiting = false;
277
+ this._motionGen += 1;
278
+ const panel = this._panel;
279
+ const overlay =
280
+ panel && typeof panel.closest === 'function' ? panel.closest('[data-vmz-overlay="drawer"]') : null;
281
+ if (overlay) {
282
+ overlay.removeAttribute('data-vmz-motion-phase');
283
+ overlay.setAttribute('data-vmz-motion-cancelled', reason || 'true');
284
+ overlay.setAttribute('data-vmz-motion-gen', String(this._motionGen));
285
+ }
286
+ if (panel) {
287
+ panel.setAttribute('data-vmz-motion', 'overlay-enter');
288
+ }
289
+ }
290
+
291
+ _overlayDurationMs(panel) {
292
+ try {
293
+ const raw = (getComputedStyle(panel).getPropertyValue('--vmz-motion-overlay-duration') || '').trim() || '180ms';
294
+ if (raw.endsWith('ms')) return Math.max(0, Number.parseFloat(raw) || 180);
295
+ if (raw.endsWith('s')) return Math.max(0, (Number.parseFloat(raw) || 0.18) * 1000);
296
+ return 180;
297
+ } catch {
298
+ return 180;
299
+ }
300
+ }
301
+
302
+ _isTopmostStack() {
303
+ if (typeof document === 'undefined') return true;
304
+ const root = this.__vmzDomRoot;
305
+ const mine =
306
+ root && typeof root.querySelector === 'function'
307
+ ? root.querySelector('[data-vmz-overlay][data-vmz-overlay-stack]')
308
+ : null;
309
+ if (!mine) return true;
310
+ const myStack = Number(mine.getAttribute('data-vmz-overlay-stack') || this.stackLevel || '0');
311
+ const open = [...document.querySelectorAll('[data-vmz-overlay][data-vmz-overlay-stack]')];
312
+ let max = -Infinity;
313
+ for (const el of open) {
314
+ const n = Number(el.getAttribute('data-vmz-overlay-stack') || '0');
315
+ if (Number.isFinite(n) && n > max) max = n;
316
+ }
317
+ if (!Number.isFinite(max)) return true;
318
+ if ((Number.isFinite(myStack) ? myStack : 0) < max) return false;
319
+ if ((Number.isFinite(myStack) ? myStack : 0) > max) return true;
320
+ const same = open.filter((el) => Number(el.getAttribute('data-vmz-overlay-stack') || '0') === max);
321
+ return same[same.length - 1] === mine;
322
+ }
323
+
324
+ onPanelKeyDown(ev) {
325
+ if (!ev || ev.key !== 'Tab') return;
326
+ const panel = this._panel;
327
+ if (!panel) return;
328
+ const nodes = panel.querySelectorAll(
329
+ 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])',
330
+ );
331
+ const list = [];
332
+ for (const n of nodes) {
333
+ if (n instanceof HTMLElement && n.offsetParent !== null) list.push(n);
334
+ }
335
+ if (panel.tabIndex >= 0) list.unshift(panel);
336
+ if (!list.length) {
337
+ ev.preventDefault();
338
+ panel.focus();
339
+ return;
340
+ }
341
+ const first = list[0];
342
+ const last = list[list.length - 1];
343
+ const active = document.activeElement;
344
+ if (ev.shiftKey) {
345
+ if (active === first || active === panel) {
346
+ ev.preventDefault();
347
+ last.focus();
348
+ }
349
+ } else if (active === last) {
350
+ ev.preventDefault();
351
+ first.focus();
352
+ }
353
+ }
354
+
355
+ _syncFromDom() {
356
+ const root = this.__vmzDomRoot;
357
+ const panel =
358
+ root && typeof root.querySelector === 'function'
359
+ ? root.querySelector('[data-vmz-overlay="drawer"] [data-vmz-focus="enter"]')
360
+ : null;
361
+ if (panel && !this._panel) this._enterFocus(panel);
362
+ else if (!panel && this._panel) this._exitFocus();
363
+ }
364
+
365
+ _enterFocus(panel) {
366
+ if (typeof document === 'undefined') return;
367
+ const active = document.activeElement;
368
+ if (!this._restoreFocus) {
369
+ const root = this.__vmzDomRoot;
370
+ const outside =
371
+ active instanceof HTMLElement &&
372
+ active !== document.body &&
373
+ !(root && typeof root.contains === 'function' && root.contains(active));
374
+ if (outside) this._restoreFocus = active;
375
+ }
376
+ this._panel = panel instanceof HTMLElement ? panel : null;
377
+ const overlay =
378
+ this._panel && typeof this._panel.closest === 'function'
379
+ ? this._panel.closest('[data-vmz-overlay="drawer"]')
380
+ : null;
381
+ if (this._exiting) {
382
+ this._cancelExit('reenter');
383
+ } else {
384
+ this._motionGen += 1;
385
+ }
386
+ if (overlay) {
387
+ overlay.setAttribute('data-vmz-motion-gen', String(this._motionGen));
388
+ }
389
+ if (this._adoptNextEnter && overlay) {
390
+ overlay.setAttribute('data-vmz-motion-adopt', 'true');
391
+ this._panel.setAttribute('data-vmz-motion', 'overlay-stable');
392
+ this._adoptNextEnter = false;
393
+ } else if (overlay) {
394
+ overlay.removeAttribute('data-vmz-motion-adopt');
395
+ overlay.removeAttribute('data-vmz-motion-phase');
396
+ overlay.removeAttribute('data-vmz-motion-cancelled');
397
+ this._panel.setAttribute('data-vmz-motion', 'overlay-enter');
398
+ }
399
+ if (this._panel && typeof this._panel.focus === 'function') this._panel.focus();
400
+ if (!this._docKeyHandler) {
401
+ this._docKeyHandler = (ev) => {
402
+ if (ev.key !== 'Escape') return;
403
+ if (ev.defaultPrevented) return;
404
+ if (!this._isTopmostStack()) return;
405
+ ev.preventDefault();
406
+ ev.stopPropagation();
407
+ this.dismiss();
408
+ };
409
+ document.addEventListener('keydown', this._docKeyHandler);
410
+ }
411
+ }
412
+
413
+ _exitFocus() {
414
+ if (this._exitTimer) {
415
+ clearTimeout(this._exitTimer);
416
+ this._exitTimer = null;
417
+ }
418
+ this._exiting = false;
419
+ this._teardownDocKey();
420
+ this._panel = null;
421
+ const restore = this._restoreFocus;
422
+ this._restoreFocus = null;
423
+ if (restore && typeof restore.focus === 'function' && document.contains(restore)) {
424
+ restore.focus();
425
+ }
426
+ }
427
+
428
+ _teardownDocKey() {
429
+ if (typeof document !== 'undefined' && this._docKeyHandler) {
430
+ document.removeEventListener('keydown', this._docKeyHandler);
431
+ }
432
+ this._docKeyHandler = null;
433
+ }
434
+ }
435
+ </script>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div class="vmz-ui-empty" data-vmz-ui="empty">
3
+ <h2 class="vmz-ui-empty__title">{title}</h2>
4
+ <p if={description} class="vmz-ui-empty__description">{description}</p>
5
+ <div class="vmz-ui-empty__actions">
6
+ <slot />
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <style>
12
+ .vmz-ui-empty {
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: flex-start;
16
+ gap: 0.65rem;
17
+ padding: 1.5rem 1.15rem;
18
+ border: 1px dashed var(--vmz-line-default);
19
+ border-radius: 2px;
20
+ background: color-mix(in srgb, var(--vmz-surface-mist) 70%, var(--vmz-surface-paper));
21
+ color: var(--vmz-text-ink);
22
+ }
23
+
24
+ .vmz-ui-empty__title {
25
+ margin: 0;
26
+ font-family: var(--vmz-font-display);
27
+ font-size: 1.1rem;
28
+ font-weight: 700;
29
+ }
30
+
31
+ .vmz-ui-empty__description {
32
+ margin: 0;
33
+ color: var(--vmz-text-steel);
34
+ max-width: 36rem;
35
+ line-height: 1.45;
36
+ }
37
+
38
+ .vmz-ui-empty__actions:empty {
39
+ display: none;
40
+ }
41
+ </style>
42
+
43
+ <script client>
44
+ /**
45
+ * VMZ UI — Empty (Commercial composition).
46
+ * Empty / zero-state surface with optional action slot.
47
+ */
48
+ export default class Empty {
49
+ public title: string = 'Nothing here yet';
50
+ public description: string = '';
51
+ }
52
+ </script>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <div class="vmz-ui-field" data-vmz-ui="field" data-invalid={invalidAttr}>
3
+ <label if={label} class="vmz-ui-field__label" for={controlId}>{label}</label>
4
+ <input
5
+ id={controlId}
6
+ class="vmz-ui-field__control"
7
+ type={type}
8
+ value={value}
9
+ disabled={disabled}
10
+ aria-invalid={error ? 'true' : 'false'}
11
+ aria-describedby={describedBy}
12
+ onInput={onInput}
13
+ />
14
+ <p if={description} id={descriptionId} class="vmz-ui-field__description">{description}</p>
15
+ <p if={error} id={errorId} class="vmz-ui-field__error" role="alert">{error}</p>
16
+ </div>
17
+ </template>
18
+
19
+ <style>
20
+ .vmz-ui-field {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 0.35rem;
24
+ max-width: 28rem;
25
+ }
26
+
27
+ .vmz-ui-field__label {
28
+ font: inherit;
29
+ font-weight: 600;
30
+ }
31
+
32
+ .vmz-ui-field__control {
33
+ font: inherit;
34
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
35
+ border: 1px solid var(--vmz-action-primary-background);
36
+ border-radius: 2px;
37
+ background: transparent;
38
+ color: inherit;
39
+ outline: none;
40
+ }
41
+
42
+ .vmz-ui-field[data-invalid='true'] .vmz-ui-field__control {
43
+ border-color: var(--vmz-status-danger-accent);
44
+ }
45
+
46
+ .vmz-ui-field__control:focus-visible {
47
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
48
+ }
49
+
50
+ .vmz-ui-field__control:disabled {
51
+ opacity: 0.55;
52
+ cursor: not-allowed;
53
+ }
54
+
55
+ .vmz-ui-field__description {
56
+ margin: 0;
57
+ opacity: 0.75;
58
+ font-size: 0.9em;
59
+ }
60
+
61
+ .vmz-ui-field__error {
62
+ margin: 0;
63
+ color: var(--vmz-status-danger-foreground);
64
+ font-size: 0.9em;
65
+ }
66
+ </style>
67
+
68
+ <script client>
69
+ /**
70
+ * VMZ UI — Field (UI1).
71
+ * Label / description / error association via for + aria-describedby.
72
+ * Colors only from Style Theme semantic tokens.
73
+ */
74
+ export default class Field {
75
+ public label: string = '';
76
+ public description: string = '';
77
+ public error: string = '';
78
+ public value: string = '';
79
+ public type: string = 'text';
80
+ public disabled: boolean = false;
81
+ public invalidAttr: string = 'false';
82
+ public controlId: string = 'vmz-field';
83
+ public descriptionId: string = 'vmz-field-desc';
84
+ public errorId: string = 'vmz-field-err';
85
+ public describedBy: string = '';
86
+ public onInput: ((e: Event) => void) | null = null;
87
+ }
88
+ </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <section class="vmz-ui-filter-bar" data-vmz-ui="filter-bar" aria-label={label}>
3
+ <slot />
4
+ </section>
5
+ </template>
6
+
7
+ <style>
8
+ .vmz-ui-filter-bar {
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ align-items: flex-end;
12
+ gap: 0.75rem 1rem;
13
+ padding: 0.75rem 0.85rem;
14
+ border: 1px solid var(--vmz-action-primary-background);
15
+ border-radius: 2px;
16
+ }
17
+ </style>
18
+
19
+ <script client>
20
+ /**
21
+ * VMZ UI — FilterBar (Console composition).
22
+ * Dense filter/query landmark; parent projects Field + action controls.
23
+ */
24
+ export default class FilterBar {
25
+ public label: string = 'Filters';
26
+ }
27
+ </script>
@@ -0,0 +1,72 @@
1
+ <template>
2
+ <form
3
+ class="vmz-ui-form"
4
+ data-vmz-ui="form"
5
+ data-disabled={disabledAttr}
6
+ data-novalidate="true"
7
+ aria-label={label}
8
+ aria-describedby={describedBy}
9
+ onSubmit={onSubmitGuard}
10
+ onReset={onResetGuard}
11
+ >
12
+ <div if={errorSummary} id={summaryId} class="vmz-ui-form__summary" role="alert" data-vmz-form="summary">
13
+ {errorSummary}
14
+ </div>
15
+ <slot />
16
+ </form>
17
+ </template>
18
+
19
+ <style>
20
+ .vmz-ui-form {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: var(--vmz-density-control-gap);
24
+ max-width: 36rem;
25
+ }
26
+
27
+ .vmz-ui-form[data-disabled='true'] {
28
+ opacity: 0.72;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .vmz-ui-form__summary {
33
+ margin: 0;
34
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
35
+ border: 1px solid var(--vmz-status-danger-accent);
36
+ border-left-width: 4px;
37
+ border-radius: 2px;
38
+ background: color-mix(in srgb, var(--vmz-status-danger-accent) 12%, transparent);
39
+ color: var(--vmz-status-danger-foreground);
40
+ font-weight: 600;
41
+ }
42
+ </style>
43
+
44
+ <script client>
45
+ /**
46
+ * VMZ UI — Form (hand-written commercial form shell).
47
+ * Parent owns field values + validation presentation; no parallel form store.
48
+ * Native submit/reset guarded; browser HTML5 validation left off (data-novalidate + preventDefault).
49
+ */
50
+ export default class Form {
51
+ public label: string = 'Form';
52
+ public errorSummary: string = '';
53
+ public summaryId: string = 'vmz-form-summary';
54
+ public describedBy: string = '';
55
+ public disabled: boolean = false;
56
+ public disabledAttr: string = 'false';
57
+ public onSubmit: (() => void) | null = null;
58
+ public onReset: (() => void) | null = null;
59
+
60
+ onSubmitGuard(ev) {
61
+ if (ev && typeof ev.preventDefault === 'function') ev.preventDefault();
62
+ if (this.disabled) return;
63
+ if (typeof this.onSubmit === 'function') this.onSubmit();
64
+ }
65
+
66
+ onResetGuard(ev) {
67
+ if (ev && typeof ev.preventDefault === 'function') ev.preventDefault();
68
+ if (this.disabled) return;
69
+ if (typeof this.onReset === 'function') this.onReset();
70
+ }
71
+ }
72
+ </script>