@vmz/ui 0.3.6 → 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 +53 -51
  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 +127 -94
  6. package/src/components/Alert.vmz +125 -28
  7. package/src/components/AppShell.vmz +125 -57
  8. package/src/components/Autocomplete.vmz +261 -154
  9. package/src/components/Avatar.vmz +20 -12
  10. package/src/components/Badge.vmz +62 -70
  11. package/src/components/Breadcrumb.vmz +86 -72
  12. package/src/components/BulkActions.vmz +56 -51
  13. package/src/components/Button.vmz +237 -211
  14. package/src/components/Callout.vmz +178 -77
  15. package/src/components/Card.vmz +141 -120
  16. package/src/components/Checkbox.vmz +131 -55
  17. package/src/components/CodeBlock.vmz +77 -30
  18. package/src/components/Collapse.vmz +64 -11
  19. package/src/components/ConsoleShell.vmz +405 -251
  20. package/src/components/Content.vmz +15 -1
  21. package/src/components/DataTable.vmz +294 -194
  22. package/src/components/DatePicker.vmz +766 -674
  23. package/src/components/Descriptions.vmz +90 -0
  24. package/src/components/Dialog.vmz +137 -38
  25. package/src/components/Divider.vmz +8 -1
  26. package/src/components/Drawer.vmz +518 -427
  27. package/src/components/Dropdown.vmz +195 -51
  28. package/src/components/Empty.vmz +96 -54
  29. package/src/components/Field.vmz +170 -115
  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 +78 -79
  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 +25 -10
  39. package/src/components/List.vmz +124 -102
  40. package/src/components/Menu.vmz +416 -243
  41. package/src/components/Message.vmz +167 -0
  42. package/src/components/Notification.vmz +132 -37
  43. package/src/components/Pagination.vmz +237 -69
  44. package/src/components/Popconfirm.vmz +287 -0
  45. package/src/components/Popover.vmz +341 -223
  46. package/src/components/Progress.vmz +16 -5
  47. package/src/components/Prose.vmz +180 -81
  48. package/src/components/QueryForm.vmz +31 -9
  49. package/src/components/RadioGroup.vmz +204 -124
  50. package/src/components/Result.vmz +60 -27
  51. package/src/components/Segmented.vmz +86 -51
  52. package/src/components/Select.vmz +620 -533
  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 +118 -103
  58. package/src/components/Switch.vmz +131 -98
  59. package/src/components/Table.vmz +270 -165
  60. package/src/components/Tabs.vmz +125 -110
  61. package/src/components/Tag.vmz +60 -43
  62. package/src/components/TextArea.vmz +125 -118
  63. package/src/components/Timeline.vmz +111 -76
  64. package/src/components/Toc.vmz +90 -74
  65. package/src/components/Tooltip.vmz +107 -58
  66. package/src/components/Tree.vmz +214 -191
  67. package/src/components/Typography.vmz +23 -8
  68. package/src/components/Upload.vmz +1394 -1289
@@ -1,110 +1,125 @@
1
- <template>
2
- <div data-vmz-ui="tabs" class="vmz-ui-tabs">
3
- <div role="tablist" :aria-label="label" :onKeyDown="onListKeyDown" @click="onListClick" class="vmz-ui-tabs__list">
4
- <button v-for="item in items" :key="item.id" type="button" role="tab" :id="item.id" :data-vmz-tab="item.id" :aria-selected="selected === item.id ? 'true' : 'false'" :aria-controls="item.controls" :tabindex="selected === item.id ? 0 : -1" class="vmz-ui-tabs__tab">
5
- {{ item.title }}
6
- </button>
7
- </div>
8
- <div role="tabpanel" :id="panelId" :data-vmz-tab-panel="selected" :aria-labelledby="selected" tabindex="0" class="vmz-ui-tabs__panel">
9
- <slot />
10
- </div>
11
- </div>
12
- </template>
13
- <style>
14
- .vmz-ui-tabs {
15
- display: flex;
16
- flex-direction: column;
17
- gap: 0.625rem;
18
- max-width: 36rem;
19
- }
20
-
21
- .vmz-ui-tabs__list {
22
- display: flex;
23
- flex-wrap: wrap;
24
- gap: 0.25rem;
25
- border-bottom: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
26
- }
27
-
28
- .vmz-ui-tabs__tab {
29
- font: inherit;
30
- font-size: 0.875rem;
31
- font-weight: 400;
32
- padding: 0.5rem 0;
33
- margin: 0 1rem 0 0;
34
- border: 0;
35
- border-bottom: 2px solid transparent;
36
- border-radius: 0;
37
- background: transparent;
38
- color: var(--vmz-text-ink);
39
- cursor: pointer;
40
- outline: none;
41
- }
42
-
43
- .vmz-ui-tabs__tab[aria-selected="true"] {
44
- background: transparent;
45
- color: var(--vmz-action-primary-background);
46
- font-weight: 500;
47
- border-bottom-color: var(--vmz-action-primary-background);
48
- }
49
-
50
- .vmz-ui-tabs__tab:focus-visible {
51
- box-shadow: 0 0 0 3px var(--vmz-focus-ring);
52
- }
53
-
54
- .vmz-ui-tabs__panel {
55
- min-height: 2.5rem;
56
- outline: none;
57
- }
58
-
59
- .vmz-ui-tabs__panel:focus-visible {
60
- box-shadow: 0 0 0 3px var(--vmz-focus-ring);
61
- }
62
- </style>
63
- <script client>
64
- /**
65
- * VMZ UI — Tabs (UI2).
66
- * Parent owns `selected` id; keyboard Left/Right/Home/End on the tablist.
67
- * Panel content is the default slot (page chooses which body to show).
68
- */
69
- export default class Tabs {
70
- public label: string = "Tabs";
71
- public selected: string = "";
72
- public panelId: string = "";
73
- /** @type {{ id: string, title: string, controls?: string }[]} */
74
- public items: {
75
- id: string;
76
- title: string;
77
- controls?: string;
78
- }[] = [];
79
- public onSelect: ((id: string) => void) | null = null;
80
- select(id) {
81
- if (typeof this.onSelect === "function") this.onSelect(id);
82
- }
83
- onListClick(ev) {
84
- const t = ev && ev.target;
85
- const el = t && typeof t.closest === "function" ? t.closest("[data-vmz-tab]") : null;
86
- const id =
87
- el && typeof el.getAttribute === "function" ? el.getAttribute("data-vmz-tab") : null;
88
- if (id) this.select(id);
89
- }
90
- onListKeyDown(ev) {
91
- if (!ev || !this.items?.length) return;
92
- const ids = this.items.map((it) => it.id);
93
- const idx = Math.max(0, ids.indexOf(this.selected));
94
- let next = idx;
95
- if (ev.key === "ArrowRight") next = (idx + 1) % ids.length;
96
- else if (ev.key === "ArrowLeft") next = (idx - 1 + ids.length) % ids.length;
97
- else if (ev.key === "Home") next = 0;
98
- else if (ev.key === "End") next = ids.length - 1;
99
- else return;
100
- ev.preventDefault();
101
- this.select(ids[next]);
102
- const root = this.__vmzDomRoot;
103
- const btn =
104
- root && typeof root.querySelector === "function"
105
- ? root.querySelector(`[data-vmz-tab="${ids[next]}"]`)
106
- : null;
107
- if (btn && typeof btn.focus === "function") btn.focus();
108
- }
109
- }
110
- </script>
1
+ <template>
2
+ <div data-vmz-ui="tabs" class="vmz-ui-tabs">
3
+ <div role="tablist" :aria-label="label" :onKeyDown="onListKeyDown" @click="onListClick" class="vmz-ui-tabs__list">
4
+ <button v-for="item in items" :key="item.id" type="button" role="tab" :id="item.id" :data-vmz-tab="item.id" :aria-selected="selected === item.id ? 'true' : 'false'" :aria-controls="item.controls" :tabindex="selected === item.id ? 0 : -1" class="vmz-ui-tabs__tab">
5
+ {{ item.title }}
6
+ </button>
7
+ </div>
8
+ <div role="tabpanel" :id="panelId" :data-vmz-tab-panel="selected" :aria-labelledby="selected" tabindex="0" class="vmz-ui-tabs__panel">
9
+ <slot />
10
+ </div>
11
+ </div>
12
+ </template>
13
+ <style>
14
+ .vmz-ui-tabs {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 1rem;
18
+ width: 100%;
19
+ min-width: 0;
20
+ }
21
+
22
+ .vmz-ui-tabs__list {
23
+ display: flex;
24
+ flex-wrap: nowrap;
25
+ gap: 1.5rem;
26
+ overflow-x: auto;
27
+ border-bottom: 1px solid var(--vmz-line-subtle);
28
+ scrollbar-width: thin;
29
+ }
30
+
31
+ .vmz-ui-tabs__tab {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ height: 2.5rem;
35
+ padding: 0;
36
+ margin: 0;
37
+ border: 0;
38
+ border-bottom: 2px solid transparent;
39
+ border-radius: 0;
40
+ background: transparent;
41
+ color: var(--vmz-text-ink);
42
+ font: inherit;
43
+ font-size: 0.875rem;
44
+ font-weight: 400;
45
+ white-space: nowrap;
46
+ cursor: pointer;
47
+ outline: none;
48
+ transition: color var(--vmz-motion-control-duration, 150ms)
49
+ var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
50
+ }
51
+
52
+ .vmz-ui-tabs__tab:hover {
53
+ color: var(--vmz-action-primary-background);
54
+ }
55
+
56
+ .vmz-ui-tabs__tab[aria-selected="true"] {
57
+ background: transparent;
58
+ color: var(--vmz-action-primary-background);
59
+ font-weight: 500;
60
+ border-bottom-color: var(--vmz-action-primary-background);
61
+ }
62
+
63
+ .vmz-ui-tabs__tab:focus-visible {
64
+ outline: 2px solid var(--vmz-action-primary-background);
65
+ outline-offset: -2px;
66
+ border-radius: var(--vmz-radius-sm, 4px);
67
+ }
68
+
69
+ .vmz-ui-tabs__panel {
70
+ min-height: 2.5rem;
71
+ outline: none;
72
+ }
73
+
74
+ .vmz-ui-tabs__panel:focus-visible {
75
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
76
+ }
77
+ </style>
78
+ <script client>
79
+ /**
80
+ * VMZ UI — Tabs.
81
+ * Parent owns `selected` id; keyboard Left/Right/Home/End on the tablist.
82
+ * Panel content is the default slot (page chooses which body to show).
83
+ */
84
+ export default class Tabs {
85
+ public label: string = "";
86
+ public selected: string = "";
87
+ public panelId: string = "";
88
+ /** @type {{ id: string, title: string, controls?: string }[]} */
89
+ public items: {
90
+ id: string;
91
+ title: string;
92
+ controls?: string;
93
+ }[] = [];
94
+ public onSelect: ((id: string) => void) | null = null;
95
+ select(id) {
96
+ if (typeof this.onSelect === "function") this.onSelect(id);
97
+ }
98
+ onListClick(ev) {
99
+ const t = ev && ev.target;
100
+ const el = t && typeof t.closest === "function" ? t.closest("[data-vmz-tab]") : null;
101
+ const id =
102
+ el && typeof el.getAttribute === "function" ? el.getAttribute("data-vmz-tab") : null;
103
+ if (id) this.select(id);
104
+ }
105
+ onListKeyDown(ev) {
106
+ if (!ev || !this.items?.length) return;
107
+ const ids = this.items.map((it) => it.id);
108
+ const idx = Math.max(0, ids.indexOf(this.selected));
109
+ let next = idx;
110
+ if (ev.key === "ArrowRight") next = (idx + 1) % ids.length;
111
+ else if (ev.key === "ArrowLeft") next = (idx - 1 + ids.length) % ids.length;
112
+ else if (ev.key === "Home") next = 0;
113
+ else if (ev.key === "End") next = ids.length - 1;
114
+ else return;
115
+ ev.preventDefault();
116
+ this.select(ids[next]);
117
+ const root = this.__vmzDomRoot;
118
+ const btn =
119
+ root && typeof root.querySelector === "function"
120
+ ? root.querySelector(`[data-vmz-tab="${ids[next]}"]`)
121
+ : null;
122
+ if (btn && typeof btn.focus === "function") btn.focus();
123
+ }
124
+ }
125
+ </script>
@@ -1,43 +1,60 @@
1
- <template>
2
- <span data-vmz-ui="tag" :data-status="status || undefined" :data-vmz-tag-status="status || undefined" class="vmz-ui-tag">
3
- <slot />
4
- </span>
5
- </template>
6
- <style>
7
- .vmz-ui-tag {
8
- display: inline-flex;
9
- align-items: center;
10
- gap: 0.25rem;
11
- min-height: 1.5rem;
12
- padding: 0 0.5rem;
13
- border: 1px solid var(--vmz-line-default);
14
- border-radius: var(--vmz-radius-sm, 4px);
15
- background: var(--vmz-surface-mist);
16
- color: var(--vmz-text-steel);
17
- font-size: 0.75rem;
18
- font-weight: 500;
19
- line-height: 1;
20
- white-space: nowrap;
21
- }
22
- .vmz-ui-tag[data-status="success"] {
23
- color: var(--vmz-status-success-foreground);
24
- background: color-mix(in srgb, var(--vmz-status-success-accent) 12%, var(--vmz-surface-paper));
25
- border-color: color-mix(in srgb, var(--vmz-status-success-accent) 45%, var(--vmz-line-default));
26
- }
27
- .vmz-ui-tag[data-status="warning"] {
28
- color: var(--vmz-status-warning-foreground);
29
- background: color-mix(in srgb, var(--vmz-status-warning-accent) 12%, var(--vmz-surface-paper));
30
- border-color: color-mix(in srgb, var(--vmz-status-warning-accent) 45%, var(--vmz-line-default));
31
- }
32
- .vmz-ui-tag[data-status="error"] {
33
- color: var(--vmz-status-danger-foreground);
34
- background: color-mix(in srgb, var(--vmz-status-danger-accent) 12%, var(--vmz-surface-paper));
35
- border-color: color-mix(in srgb, var(--vmz-status-danger-accent) 45%, var(--vmz-line-default));
36
- }
37
- </style>
38
- <script client>
39
- export default class Tag {
40
- static __vmzHostBox = "contents";
41
- public status: string = "";
42
- }
43
- </script>
1
+ <template>
2
+ <span data-vmz-ui="tag" :data-status="status || undefined" :data-vmz-tag-status="status || undefined" class="vmz-ui-tag">
3
+ <slot />
4
+ </span>
5
+ </template>
6
+ <style>
7
+ .vmz-ui-tag {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: var(--vmz-space-1);
11
+ min-height: var(--vmz-space-5);
12
+ max-width: 100%;
13
+ padding: 0 var(--vmz-space-2);
14
+ border: 1px solid var(--vmz-line-default);
15
+ border-radius: var(--vmz-radius-sm);
16
+ background: var(--vmz-surface-mist);
17
+ color: var(--vmz-text-steel);
18
+ font-size: 0.75rem;
19
+ font-weight: 500;
20
+ line-height: 1.6667;
21
+ white-space: nowrap;
22
+ }
23
+
24
+ .vmz-ui-tag[data-status="success"] {
25
+ color: var(--vmz-status-success-foreground);
26
+ background: var(--vmz-status-success-background);
27
+ border-color: var(--vmz-status-success-border);
28
+ }
29
+
30
+ .vmz-ui-tag[data-status="warning"] {
31
+ color: var(--vmz-status-warning-foreground);
32
+ background: var(--vmz-status-warning-background);
33
+ border-color: var(--vmz-status-warning-border);
34
+ }
35
+
36
+ .vmz-ui-tag[data-status="error"],
37
+ .vmz-ui-tag[data-status="danger"] {
38
+ color: var(--vmz-status-danger-foreground);
39
+ background: var(--vmz-status-danger-background);
40
+ border-color: var(--vmz-status-danger-border);
41
+ }
42
+
43
+ .vmz-ui-tag[data-status="info"] {
44
+ color: var(--vmz-status-info-foreground);
45
+ background: var(--vmz-status-info-background);
46
+ border-color: var(--vmz-status-info-border);
47
+ }
48
+
49
+ .vmz-ui-tag[data-status="brand"] {
50
+ color: var(--vmz-action-primary-active);
51
+ background: var(--vmz-action-primary-soft);
52
+ border-color: var(--vmz-action-primary-hover);
53
+ }
54
+ </style>
55
+ <script client>
56
+ export default class Tag {
57
+ static __vmzHostBox = "contents";
58
+ public status: string = "";
59
+ }
60
+ </script>
@@ -1,118 +1,125 @@
1
- <template>
2
- <div data-vmz-ui="textarea" :data-invalid="invalidAttr" class="vmz-ui-textarea">
3
- <label v-if="label" :for="controlId" class="vmz-ui-textarea__label">
4
- {{ label }}
5
- </label>
6
- <textarea :id="controlId" :rows="rows" :value="value" :disabled="disabled" :readonly="readonly" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" @input="onDomInput" class="vmz-ui-textarea__control" />
7
- <p v-if="description" :id="descriptionId" class="vmz-ui-textarea__description">
8
- {{ description }}
9
- </p>
10
- <p v-if="error" :id="errorId" role="alert" class="vmz-ui-textarea__error">
11
- {{ error }}
12
- </p>
13
- </div>
14
- </template>
15
- <style>
16
- .vmz-ui-textarea {
17
- display: flex;
18
- flex-direction: column;
19
- gap: 0.5rem;
20
- max-width: none;
21
- width: 100%;
22
- }
23
-
24
- .vmz-ui-textarea__label {
25
- font: inherit;
26
- font-weight: 500;
27
- font-size: 0.875rem;
28
- }
29
-
30
- .vmz-ui-textarea__control {
31
- box-sizing: border-box;
32
- width: 100%;
33
- font-family: var(--vmz-font-sans, inherit);
34
- font-size: 0.875rem;
35
- line-height: 1.5715;
36
- min-height: 5.5rem;
37
- padding: var(--vmz-density-control-padding-y, 0.25rem)
38
- var(--vmz-density-control-padding-x, 0.75rem);
39
- border: 1px solid var(--vmz-line-default, #d9d9d9);
40
- border-radius: var(--vmz-radius-md, 6px);
41
- background: var(--vmz-surface-paper, #fff);
42
- color: var(--vmz-text-ink, inherit);
43
- outline: none;
44
- resize: vertical;
45
- transition:
46
- border-color var(--vmz-motion-control-duration, 120ms)
47
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
48
- box-shadow var(--vmz-motion-control-duration, 120ms)
49
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
50
- }
51
-
52
- .vmz-ui-textarea__control::placeholder {
53
- color: var(--vmz-text-steel, #8c8c8c);
54
- opacity: 1;
55
- }
56
-
57
- .vmz-ui-textarea[data-invalid="true"] .vmz-ui-textarea__control {
58
- border-color: var(--vmz-status-danger-accent);
59
- }
60
-
61
- .vmz-ui-textarea__control:hover:not(:disabled):not(:focus-visible) {
62
- border-color: var(--vmz-action-primary-background);
63
- }
64
-
65
- .vmz-ui-textarea__control:focus-visible {
66
- border-color: var(--vmz-action-primary-background);
67
- box-shadow: 0 0 0 3px var(--vmz-focus-ring);
68
- }
69
-
70
- .vmz-ui-textarea__control:disabled,
71
- .vmz-ui-textarea__control:read-only {
72
- cursor: default;
73
- background: var(--vmz-surface-mist, #fafafa);
74
- }
75
-
76
- .vmz-ui-textarea__control:disabled {
77
- cursor: not-allowed;
78
- opacity: 0.55;
79
- }
80
-
81
- .vmz-ui-textarea__description {
82
- margin: 0;
83
- color: var(--vmz-text-steel, #8c8c8c);
84
- font-size: 0.875rem;
85
- line-height: 1.4;
86
- }
87
-
88
- .vmz-ui-textarea__error {
89
- margin: 0;
90
- color: var(--vmz-status-danger-foreground);
91
- font-size: 0.875rem;
92
- }
93
- </style>
94
- <script client>
95
- /**
96
- * VMZ UI — TextArea.
97
- * Same label/description/error ownership contract as Field; parent owns value.
98
- */
99
- export default class TextArea {
100
- public label: string = "";
101
- public description: string = "";
102
- public error: string = "";
103
- public value: string = "";
104
- public rows: number = 4;
105
- public disabled: boolean = false;
106
- public readonly: boolean = false;
107
- public invalidAttr: string = "false";
108
- public controlId: string = "vmz-textarea";
109
- public descriptionId: string = "vmz-textarea-desc";
110
- public errorId: string = "vmz-textarea-err";
111
- public describedBy: string = "";
112
- public onInput: ((e: Event) => void) | null = null;
113
- onDomInput(e: Event) {
114
- this.emit("input", e);
115
- if (typeof this.onInput === "function") this.onInput(e);
116
- }
117
- }
118
- </script>
1
+ <template>
2
+ <div data-vmz-ui="textarea" :data-invalid="invalidAttr" class="vmz-ui-textarea">
3
+ <label v-if="label" :for="controlId" class="vmz-ui-textarea__label">
4
+ {{ label }}
5
+ </label>
6
+ <textarea :id="controlId" :rows="rows" :value="value" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" @input="onDomInput" class="vmz-ui-textarea__control" />
7
+ <p v-if="description" :id="descriptionId" class="vmz-ui-textarea__description">
8
+ {{ description }}
9
+ </p>
10
+ <p v-if="error" :id="errorId" role="alert" class="vmz-ui-textarea__error">
11
+ {{ error }}
12
+ </p>
13
+ </div>
14
+ </template>
15
+ <style>
16
+ .vmz-ui-textarea {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--vmz-space-2);
20
+ max-width: none;
21
+ width: 100%;
22
+ }
23
+
24
+ .vmz-ui-textarea__label {
25
+ font: inherit;
26
+ font-weight: 500;
27
+ font-size: 0.875rem;
28
+ line-height: 1.5715;
29
+ color: var(--vmz-text-ink);
30
+ }
31
+
32
+ .vmz-ui-textarea__control {
33
+ box-sizing: border-box;
34
+ width: 100%;
35
+ font-family: inherit;
36
+ font-size: 0.875rem;
37
+ line-height: 1.5715;
38
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
39
+ border: 1px solid var(--vmz-line-default);
40
+ border-radius: var(--vmz-radius-lg);
41
+ background: var(--vmz-surface-paper);
42
+ color: var(--vmz-text-ink);
43
+ outline: none;
44
+ resize: vertical;
45
+ transition:
46
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
47
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
48
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
49
+ }
50
+
51
+ .vmz-ui-textarea__control::placeholder {
52
+ color: var(--vmz-text-tertiary);
53
+ opacity: 1;
54
+ }
55
+
56
+ .vmz-ui-textarea[data-invalid="true"] .vmz-ui-textarea__control {
57
+ border-color: var(--vmz-status-danger-accent);
58
+ }
59
+
60
+ .vmz-ui-textarea[data-invalid="true"] .vmz-ui-textarea__control:hover:not(:disabled),
61
+ .vmz-ui-textarea[data-invalid="true"] .vmz-ui-textarea__control:focus-visible {
62
+ border-color: var(--vmz-status-danger-foreground);
63
+ }
64
+
65
+ .vmz-ui-textarea__control:hover:not(:disabled):not(:focus-visible) {
66
+ border-color: var(--vmz-action-primary-hover);
67
+ }
68
+
69
+ .vmz-ui-textarea__control:focus-visible {
70
+ border-color: var(--vmz-action-primary-background);
71
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
72
+ }
73
+
74
+ .vmz-ui-textarea__control:disabled {
75
+ opacity: 0.6;
76
+ color: var(--vmz-text-muted);
77
+ cursor: not-allowed;
78
+ background: var(--vmz-surface-mist);
79
+ }
80
+
81
+ .vmz-ui-textarea__control:read-only {
82
+ cursor: default;
83
+ background: var(--vmz-surface-mist);
84
+ }
85
+
86
+ .vmz-ui-textarea__description {
87
+ margin: 0;
88
+ color: var(--vmz-text-steel);
89
+ font-size: 0.875rem;
90
+ line-height: 1.5715;
91
+ }
92
+
93
+ .vmz-ui-textarea__error {
94
+ margin: 0;
95
+ color: var(--vmz-status-danger-foreground);
96
+ font-size: 0.875rem;
97
+ line-height: 1.5715;
98
+ }
99
+ </style>
100
+ <script client>
101
+ /**
102
+ * VMZ UI — TextArea.
103
+ * Same label/description/error ownership contract as Field; parent owns value.
104
+ */
105
+ export default class TextArea {
106
+ public label: string = "";
107
+ public description: string = "";
108
+ public error: string = "";
109
+ public value: string = "";
110
+ public placeholder: string = "";
111
+ public rows: number = 4;
112
+ public disabled: boolean = false;
113
+ public readonly: boolean = false;
114
+ public invalidAttr: string = "false";
115
+ public controlId: string = "vmz-textarea";
116
+ public descriptionId: string = "vmz-textarea-desc";
117
+ public errorId: string = "vmz-textarea-err";
118
+ public describedBy: string = "";
119
+ public onInput: ((e: Event) => void) | null = null;
120
+ onDomInput(e: Event) {
121
+ this.emit("input", e);
122
+ if (typeof this.onInput === "function") this.onInput(e);
123
+ }
124
+ }
125
+ </script>