@antadesign/anta 0.2.2 → 0.3.1

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 (82) hide show
  1. package/README.md +14 -0
  2. package/dist/anta_helpers.d.ts +39 -1
  3. package/dist/anta_helpers.js +30 -2
  4. package/dist/components/Button.d.ts +7 -4
  5. package/dist/components/Button.js +6 -11
  6. package/dist/components/Checkbox.d.ts +97 -0
  7. package/dist/components/Checkbox.js +77 -0
  8. package/dist/components/Expander.d.ts +74 -0
  9. package/dist/components/Expander.js +53 -0
  10. package/dist/components/Input.d.ts +159 -0
  11. package/dist/components/Input.js +150 -0
  12. package/dist/components/Menu.d.ts +73 -0
  13. package/dist/components/Menu.js +42 -0
  14. package/dist/components/MenuGroup.d.ts +24 -0
  15. package/dist/components/MenuGroup.js +19 -0
  16. package/dist/components/MenuItem.d.ts +61 -0
  17. package/dist/components/MenuItem.js +50 -0
  18. package/dist/components/MenuSeparator.d.ts +14 -0
  19. package/dist/components/MenuSeparator.js +7 -0
  20. package/dist/components/Progress.d.ts +12 -6
  21. package/dist/components/Progress.js +7 -4
  22. package/dist/components/Radio.d.ts +37 -0
  23. package/dist/components/Radio.js +33 -0
  24. package/dist/components/RadioGroup.d.ts +119 -0
  25. package/dist/components/RadioGroup.js +108 -0
  26. package/dist/components/Tag.d.ts +38 -5
  27. package/dist/components/Tag.js +9 -5
  28. package/dist/components/Text.d.ts +27 -12
  29. package/dist/components/Text.js +6 -3
  30. package/dist/components/Title.d.ts +10 -1
  31. package/dist/elements/a-button.css +1 -1
  32. package/dist/elements/a-button.d.ts +56 -0
  33. package/dist/elements/a-button.js +13 -11
  34. package/dist/elements/a-checkbox.css +1 -0
  35. package/dist/elements/a-checkbox.d.ts +52 -0
  36. package/dist/elements/a-checkbox.js +130 -0
  37. package/dist/elements/a-expander.css +1 -0
  38. package/dist/elements/a-expander.d.ts +28 -0
  39. package/dist/elements/a-expander.js +237 -0
  40. package/dist/elements/a-icon.d.ts +14 -0
  41. package/dist/elements/a-icon.shapes.css +1 -1
  42. package/dist/elements/a-icon.shapes.d.ts +10 -1
  43. package/dist/elements/a-icon.shapes.js +11 -1
  44. package/dist/elements/a-input.css +1 -0
  45. package/dist/elements/a-input.d.ts +68 -0
  46. package/dist/elements/a-input.js +511 -0
  47. package/dist/elements/a-menu-group.css +1 -0
  48. package/dist/elements/a-menu-group.d.ts +13 -0
  49. package/dist/elements/a-menu-group.js +15 -0
  50. package/dist/elements/a-menu-item.css +1 -0
  51. package/dist/elements/a-menu-item.d.ts +47 -0
  52. package/dist/elements/a-menu-item.js +30 -0
  53. package/dist/elements/a-menu-separator.css +1 -0
  54. package/dist/elements/a-menu-separator.d.ts +13 -0
  55. package/dist/elements/a-menu-separator.js +15 -0
  56. package/dist/elements/a-menu.css +1 -0
  57. package/dist/elements/a-menu.d.ts +183 -0
  58. package/dist/elements/a-menu.js +763 -0
  59. package/dist/elements/a-progress.css +1 -1
  60. package/dist/elements/a-progress.d.ts +12 -0
  61. package/dist/elements/a-progress.js +1 -0
  62. package/dist/elements/a-radio-group.css +1 -0
  63. package/dist/elements/a-radio-group.d.ts +33 -0
  64. package/dist/elements/a-radio-group.js +160 -0
  65. package/dist/elements/a-radio.css +1 -0
  66. package/dist/elements/a-radio.d.ts +14 -0
  67. package/dist/elements/a-radio.js +46 -0
  68. package/dist/elements/a-tag.css +1 -1
  69. package/dist/elements/a-text.css +1 -1
  70. package/dist/elements/a-text.d.ts +42 -3
  71. package/dist/elements/a-text.js +73 -33
  72. package/dist/elements/a-tooltip.d.ts +43 -11
  73. package/dist/elements/a-tooltip.js +46 -51
  74. package/dist/elements/index.d.ts +9 -0
  75. package/dist/elements/index.js +27 -0
  76. package/dist/general_types.d.ts +468 -15
  77. package/dist/index.d.ts +16 -0
  78. package/dist/index.js +16 -0
  79. package/dist/jsx-runtime.d.ts +42 -7
  80. package/dist/jsx-runtime.js +14 -2
  81. package/dist/tokens.css +1 -1
  82. package/package.json +1 -1
@@ -0,0 +1,511 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-input.css";
3
+ const FORWARDED = [
4
+ "placeholder",
5
+ "type",
6
+ "name",
7
+ "autocomplete",
8
+ "inputmode",
9
+ "maxlength",
10
+ "minlength",
11
+ "pattern",
12
+ "spellcheck",
13
+ "readonly",
14
+ "required",
15
+ "min",
16
+ "max",
17
+ "step"
18
+ ];
19
+ const BOOL_FORWARDED = /* @__PURE__ */ new Set(["readonly", "required"]);
20
+ const CLEAR_TRIGGER = "clearrequest";
21
+ const CLEAR_CLICK_EVENT = "clearclick";
22
+ const CLEAR_INPUT_EVENT = "clearinput";
23
+ const SUPPORTS_FIELD_SIZING = typeof CSS !== "undefined" && !!CSS.supports?.("field-sizing", "content");
24
+ const SHADOW_STYLE = `
25
+ :host {
26
+ --_fs: 15px;
27
+ --_lh: 20px;
28
+
29
+ display: grid;
30
+ grid-template-columns: minmax(0, 1fr);
31
+ row-gap: 4px;
32
+ outline: none;
33
+ }
34
+
35
+ .label {
36
+ display: none;
37
+ color: var(--input-label);
38
+ font-family: var(--sans-serif);
39
+ font-size: var(--_fs);
40
+ line-height: var(--_lh);
41
+ font-weight: 500;
42
+ }
43
+ .label.has-label { display: block; }
44
+
45
+ .field {
46
+ --_bc: var(--input-border);
47
+ --_bw: 0.5px;
48
+
49
+ display: flex;
50
+ align-items: center;
51
+ box-sizing: border-box;
52
+ min-height: 28px;
53
+ background: var(--input-bg);
54
+ border-radius: 4px;
55
+ box-shadow: inset 0 0 0 var(--_bw) var(--_bc);
56
+ transition: box-shadow 120ms ease;
57
+ }
58
+ :host([multiline]) .field { align-items: stretch; }
59
+ :host([status]) .field { --_bw: 1px; }
60
+ :host([size="small"]) { --_fs: 13px; --_lh: 16px; }
61
+ :host([size="large"]) { --_fs: 17px; --_lh: 24px; }
62
+ :host([size="small"]) .field { min-height: 24px; }
63
+ :host([size="large"]) .field { min-height: 32px; }
64
+
65
+ @media (hover: hover) and (pointer: fine) {
66
+ :host(:not(:disabled)) .field:hover { --_bc: var(--input-border-hover); }
67
+ }
68
+ .field:has(input:focus, textarea:focus) {
69
+ --_bc: var(--input-border-hover);
70
+ outline: 1px solid var(--focus-ring);
71
+ outline-offset: 1px;
72
+ }
73
+
74
+ input, textarea {
75
+ flex: 1 1 auto;
76
+ min-width: 0;
77
+ width: 100%;
78
+ box-sizing: border-box;
79
+ border: 0;
80
+ margin: 0;
81
+ padding-block: 0;
82
+ padding-inline: 7px;
83
+ background: transparent;
84
+ outline: none;
85
+ color: var(--input-text);
86
+ font-family: var(--sans-serif);
87
+ font-feature-settings: 'ss02', 'ss05';
88
+ font-size: var(--_fs);
89
+ line-height: var(--_lh);
90
+ font-weight: 400;
91
+ -webkit-appearance: none;
92
+ appearance: none;
93
+ }
94
+ textarea {
95
+ --_pad-block: 4px;
96
+
97
+ resize: none;
98
+ padding-block: var(--_pad-block);
99
+ overflow-y: auto;
100
+ }
101
+ :host([maxrows]:not([rows])) textarea {
102
+ max-height: calc(var(--_lh) * var(--_maxrows) + var(--_pad-block) * 2);
103
+ }
104
+ input::placeholder, textarea::placeholder { color: var(--input-placeholder); opacity: 1; }
105
+ input:disabled, textarea:disabled { cursor: not-allowed; }
106
+ input::-webkit-search-cancel-button,
107
+ input::-webkit-search-decoration,
108
+ input::-webkit-search-results-button,
109
+ input::-webkit-search-results-decoration,
110
+ input::-webkit-inner-spin-button,
111
+ input::-webkit-outer-spin-button { -webkit-appearance: none; appearance: none; display: none; }
112
+ input::-ms-clear, input::-ms-reveal { display: none; }
113
+
114
+ slot[name="leading"], slot[name="trailing"] { display: none; color: var(--input-adornment); }
115
+ .field.has-leading slot[name="leading"],
116
+ .field.has-trailing slot[name="trailing"] {
117
+ display: flex;
118
+ align-items: center;
119
+ flex-shrink: 0;
120
+ }
121
+ .field.has-trailing slot[name="trailing"] { gap: 2px; }
122
+
123
+ :host([dim-actions]) slot[name="leading"],
124
+ :host([dim-actions]) slot[name="trailing"],
125
+ :host([dim-actions]) slot[name="clear"] {
126
+ opacity: 0.6;
127
+ transition: opacity 120ms ease;
128
+ }
129
+ :host([dim-actions]:not(:disabled)) .field:hover slot[name="leading"],
130
+ :host([dim-actions]:not(:disabled)) .field:hover slot[name="trailing"],
131
+ :host([dim-actions]:not(:disabled)) .field:hover slot[name="clear"],
132
+ :host([dim-actions]:not(:disabled)) .field:focus-within slot[name="leading"],
133
+ :host([dim-actions]:not(:disabled)) .field:focus-within slot[name="trailing"],
134
+ :host([dim-actions]:not(:disabled)) .field:focus-within slot[name="clear"] {
135
+ opacity: 1;
136
+ }
137
+
138
+ :host(:disabled) slot[name="leading"],
139
+ :host(:disabled) slot[name="trailing"] { opacity: 0.5; pointer-events: none; }
140
+
141
+ slot[name="clear"] { display: none; flex-shrink: 0; }
142
+ .field.is-filled slot[name="clear"] { display: flex; align-items: center; }
143
+ :host(:disabled) slot[name="clear"],
144
+ :host([readonly]) slot[name="clear"] { display: none; }
145
+ :host([multiline]) slot[name="clear"] { align-self: flex-start; }
146
+
147
+ .field.has-leading slot[name="leading"] { margin-inline-start: 7px; }
148
+ .field.has-leading input,
149
+ .field.has-leading textarea { padding-inline-start: 5px; }
150
+ :host([multiline]) .field.has-leading slot[name="leading"],
151
+ :host([multiline]) .field.has-trailing slot[name="trailing"] {
152
+ align-items: flex-start;
153
+ padding-top: 2px;
154
+ }
155
+
156
+ .hint {
157
+ display: none;
158
+ gap: 4px;
159
+ align-items: flex-start;
160
+ padding-inline-start: 1px;
161
+ color: var(--input-hint);
162
+ font-family: var(--sans-serif);
163
+ font-size: calc(var(--_fs) - 1px);
164
+ line-height: calc(var(--_lh) - 2px);
165
+ }
166
+ .hint.has-hint { display: flex; }
167
+ `;
168
+ class AInputElement extends HTMLElementBase {
169
+ static formAssociated = true;
170
+ static observedAttributes = [
171
+ ...FORWARDED,
172
+ "value",
173
+ "defaultvalue",
174
+ "multiline",
175
+ "rows",
176
+ "maxrows",
177
+ "status",
178
+ "disabled"
179
+ ];
180
+ internals;
181
+ field;
182
+ labelBox;
183
+ hintBox;
184
+ labelSlot;
185
+ hintSlot;
186
+ leadingSlot;
187
+ trailingSlot;
188
+ control;
189
+ // The intended value, captured even before the control exists (a framework
190
+ // may set the `value` property before the element connects), so the initial
191
+ // value isn't lost when the control is built.
192
+ pendingValue;
193
+ // True between connect and the first control build, so attribute changes for
194
+ // forwarded props don't try to touch a control that doesn't exist yet.
195
+ ready = false;
196
+ // Set by formDisabledCallback when an ancestor <fieldset disabled> / disabled
197
+ // form turns the field off (the host can't carry a [disabled] attribute itself).
198
+ formDisabled = false;
199
+ constructor() {
200
+ super();
201
+ try {
202
+ this.internals = this.attachInternals?.();
203
+ } catch (err) {
204
+ console.warn("a-input: ElementInternals unavailable \u2014 form association disabled.", err);
205
+ }
206
+ const shadow = this.attachShadow({ mode: "open", delegatesFocus: true });
207
+ const style = document.createElement("style");
208
+ style.textContent = SHADOW_STYLE;
209
+ this.labelBox = document.createElement("div");
210
+ this.labelBox.className = "label";
211
+ this.labelBox.setAttribute("part", "label");
212
+ this.labelSlot = document.createElement("slot");
213
+ this.labelSlot.name = "label";
214
+ this.labelBox.append(this.labelSlot);
215
+ this.labelSlot.addEventListener("click", () => this.control?.focus());
216
+ this.labelSlot.addEventListener("slotchange", this.onLabelSlotChange);
217
+ this.field = document.createElement("div");
218
+ this.field.className = "field";
219
+ this.field.setAttribute("part", "field");
220
+ this.leadingSlot = document.createElement("slot");
221
+ this.leadingSlot.name = "leading";
222
+ this.leadingSlot.setAttribute("part", "leading");
223
+ this.trailingSlot = document.createElement("slot");
224
+ this.trailingSlot.name = "trailing";
225
+ this.trailingSlot.setAttribute("part", "trailing");
226
+ this.leadingSlot.addEventListener("slotchange", () => this.field.classList.toggle("has-leading", this.leadingSlot.assignedNodes().length > 0));
227
+ this.trailingSlot.addEventListener("slotchange", () => this.field.classList.toggle("has-trailing", this.trailingSlot.assignedNodes().length > 0));
228
+ const clearSlot = document.createElement("slot");
229
+ clearSlot.name = "clear";
230
+ clearSlot.setAttribute("part", "clear");
231
+ this.field.append(this.leadingSlot, clearSlot, this.trailingSlot);
232
+ this.addEventListener(CLEAR_TRIGGER, () => {
233
+ const proceed = this.dispatchEvent(
234
+ new CustomEvent(CLEAR_CLICK_EVENT, { bubbles: true, cancelable: true })
235
+ );
236
+ if (proceed) this.clear();
237
+ });
238
+ this.hintBox = document.createElement("div");
239
+ this.hintBox.className = "hint";
240
+ this.hintBox.setAttribute("part", "hint");
241
+ this.hintSlot = document.createElement("slot");
242
+ this.hintSlot.name = "hint";
243
+ this.hintSlot.addEventListener("slotchange", this.onHintSlotChange);
244
+ this.hintBox.append(this.hintSlot);
245
+ const extrasSlot = document.createElement("slot");
246
+ shadow.append(style, this.labelBox, this.field, extrasSlot, this.hintBox);
247
+ }
248
+ connectedCallback() {
249
+ if (Object.prototype.hasOwnProperty.call(this, "value")) {
250
+ const v = this.value;
251
+ delete this.value;
252
+ this.value = v;
253
+ }
254
+ if (!this.control) this.buildControl();
255
+ this.ready = true;
256
+ }
257
+ attributeChangedCallback(name, _old, value) {
258
+ if (!this.ready && name !== "value" && name !== "defaultvalue") return;
259
+ if (name === "multiline" || name === "rows" || name === "maxrows") {
260
+ if (!this.control) return;
261
+ const needTextarea = this.hasAttribute("multiline") || this.hasAttribute("rows");
262
+ const isTextarea = this.control instanceof HTMLTextAreaElement;
263
+ if (needTextarea !== isTextarea) this.buildControl(this.value);
264
+ else if (this.control instanceof HTMLTextAreaElement) this.configureTextarea(this.control);
265
+ return;
266
+ }
267
+ if (name === "status") {
268
+ this.syncStatus();
269
+ this.updateValidity();
270
+ return;
271
+ }
272
+ if (name === "disabled") {
273
+ this.syncDisabled();
274
+ return;
275
+ }
276
+ if (name === "value") {
277
+ if (value !== null) this.value = value;
278
+ return;
279
+ }
280
+ if (name === "defaultvalue") return;
281
+ if (this.control) {
282
+ this.forward(name, value);
283
+ this.updateValidity();
284
+ }
285
+ }
286
+ /** (Re)build the shadow control from the current attributes. */
287
+ buildControl(initial) {
288
+ const prev = this.control;
289
+ const refocus = prev != null && this.shadowRoot?.activeElement === prev;
290
+ let selStart = null;
291
+ let selEnd = null;
292
+ if (prev) try {
293
+ selStart = prev.selectionStart;
294
+ selEnd = prev.selectionEnd;
295
+ } catch {
296
+ }
297
+ const multiline = this.hasAttribute("multiline") || this.hasAttribute("rows");
298
+ const next = document.createElement(multiline ? "textarea" : "input");
299
+ next.setAttribute("part", "input");
300
+ if (this.control) this.control.replaceWith(next);
301
+ else this.leadingSlot.after(next);
302
+ this.control = next;
303
+ for (const attr of FORWARDED) {
304
+ if (next instanceof HTMLTextAreaElement && attr === "type") continue;
305
+ this.forward(attr, this.getAttribute(attr));
306
+ }
307
+ this.syncDisabled();
308
+ this.syncStatus();
309
+ this.applyLabelAria();
310
+ this.applyDescriptionAria();
311
+ if (multiline) this.configureTextarea(next);
312
+ const value = initial ?? this.pendingValue ?? this.getAttribute("value") ?? this.getAttribute("defaultvalue") ?? "";
313
+ next.value = value;
314
+ next.addEventListener("input", this.onInput);
315
+ next.addEventListener("change", this.onChange);
316
+ if (refocus) {
317
+ next.focus();
318
+ if (selStart != null) try {
319
+ next.setSelectionRange(selStart, selEnd ?? selStart);
320
+ } catch {
321
+ }
322
+ }
323
+ this.internals?.setFormValue(value);
324
+ this.updateValidity();
325
+ this.updateFilled();
326
+ this.syncAutoHeight();
327
+ }
328
+ /** Autogrow (no `rows`) via `field-sizing: content` — or the JS fallback where
329
+ * that's unsupported — capped by `maxrows`; a fixed `rows` count switches it
330
+ * off for a constant-height box. */
331
+ configureTextarea(ta) {
332
+ const rows = this.getAttribute("rows");
333
+ const maxrows = this.getAttribute("maxrows");
334
+ if (rows != null) {
335
+ ta.rows = Math.max(1, parseInt(rows, 10) || 1);
336
+ ta.style.setProperty("field-sizing", "fixed");
337
+ ta.style.removeProperty("--_maxrows");
338
+ ta.style.removeProperty("height");
339
+ } else {
340
+ ta.rows = 1;
341
+ if (SUPPORTS_FIELD_SIZING) ta.style.setProperty("field-sizing", "content");
342
+ else ta.style.removeProperty("field-sizing");
343
+ if (maxrows != null) ta.style.setProperty("--_maxrows", String(parseInt(maxrows, 10) || 1));
344
+ else ta.style.removeProperty("--_maxrows");
345
+ this.syncAutoHeight();
346
+ }
347
+ }
348
+ /** Autogrow fallback for browsers without `field-sizing: content` (Firefox,
349
+ * Safari < 26.2). Grows the shadow textarea to fit its content; the CSS
350
+ * `max-height` cap (when `maxrows` is set) still clamps it and scrolls past.
351
+ * A no-op where `field-sizing` is supported, or when the field isn't
352
+ * autogrowing (fixed `rows`, or not a textarea) — those size via CSS. */
353
+ syncAutoHeight = () => {
354
+ if (SUPPORTS_FIELD_SIZING) return;
355
+ const ta = this.control;
356
+ if (!(ta instanceof HTMLTextAreaElement) || this.getAttribute("rows") != null) return;
357
+ ta.style.height = "auto";
358
+ ta.style.height = `${ta.scrollHeight}px`;
359
+ };
360
+ forward(name, value) {
361
+ const c = this.control;
362
+ if (!c) return;
363
+ if (c instanceof HTMLTextAreaElement && name === "type") return;
364
+ if (BOOL_FORWARDED.has(name)) c.toggleAttribute(name, value != null);
365
+ else if (value == null) c.removeAttribute(name);
366
+ else c.setAttribute(name, value);
367
+ }
368
+ syncDisabled() {
369
+ if (this.control) this.control.disabled = this.hasAttribute("disabled") || this.formDisabled;
370
+ }
371
+ /** Reflect `status` into the control's `aria-invalid` and the `:state(invalid)`
372
+ * custom state. Called on `status` change AND from buildControl — so a field
373
+ * that mounts already `status="critical"` gets `:state(invalid)` on the FIRST
374
+ * paint (the status attributeChangedCallback fires before connect, while
375
+ * `ready` is false and the early-return skips it). */
376
+ syncStatus() {
377
+ const critical = this.getAttribute("status") === "critical";
378
+ this.control?.setAttribute("aria-invalid", critical ? "true" : "false");
379
+ try {
380
+ critical ? this.internals?.states.add("invalid") : this.internals?.states.delete("invalid");
381
+ } catch {
382
+ }
383
+ }
384
+ onInput = () => {
385
+ const v = this.control?.value ?? "";
386
+ this.internals?.setFormValue(v);
387
+ this.updateValidity();
388
+ this.updateFilled();
389
+ this.syncAutoHeight();
390
+ };
391
+ // `change` is not composed; re-emit one on the host so it escapes the shadow.
392
+ onChange = () => {
393
+ this.dispatchEvent(new Event("change", { bubbles: true }));
394
+ };
395
+ onLabelSlotChange = () => {
396
+ this.labelBox.classList.toggle("has-label", this.labelSlot.assignedNodes().length > 0);
397
+ this.applyLabelAria();
398
+ };
399
+ applyLabelAria() {
400
+ const c = this.control;
401
+ if (!c) return;
402
+ if (this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby")) return;
403
+ const text = this.labelSlot.assignedNodes().map((n) => n.textContent ?? "").join(" ").trim();
404
+ if (text) c.setAttribute("aria-label", text);
405
+ else c.removeAttribute("aria-label");
406
+ }
407
+ onHintSlotChange = () => {
408
+ this.hintBox.classList.toggle("has-hint", this.hintSlot.assignedNodes().length > 0);
409
+ this.applyDescriptionAria();
410
+ };
411
+ applyDescriptionAria() {
412
+ const c = this.control;
413
+ if (!c) return;
414
+ if (this.hasAttribute("aria-description") || this.hasAttribute("aria-describedby")) return;
415
+ const text = this.hintSlot.assignedNodes().map((n) => n.textContent ?? "").join(" ").trim();
416
+ if (text) c.setAttribute("aria-description", text);
417
+ else c.removeAttribute("aria-description");
418
+ }
419
+ updateFilled() {
420
+ const filled = !!this.value;
421
+ this.field.classList.toggle("is-filled", filled);
422
+ try {
423
+ if (filled) this.internals?.states.add("filled");
424
+ else this.internals?.states.delete("filled");
425
+ } catch {
426
+ }
427
+ }
428
+ updateValidity() {
429
+ const c = this.control;
430
+ if (!this.internals || !c) return;
431
+ if (this.getAttribute("status") === "critical" && c.validity.valid) {
432
+ this.internals.setValidity({ customError: true }, "Invalid value.", c);
433
+ } else {
434
+ this.internals.setValidity(c.validity, c.validationMessage, c);
435
+ }
436
+ }
437
+ /** The current text value. Reading prefers the live control; before it's
438
+ * built, the pending/attribute value. */
439
+ get value() {
440
+ if (this.control) return this.control.value;
441
+ return this.pendingValue ?? this.getAttribute("value") ?? this.getAttribute("defaultvalue") ?? "";
442
+ }
443
+ set value(v) {
444
+ this.pendingValue = v;
445
+ if (this.control && this.control.value !== v) this.control.value = v;
446
+ this.internals?.setFormValue(v);
447
+ this.updateValidity();
448
+ this.updateFilled();
449
+ this.syncAutoHeight();
450
+ }
451
+ /** Clear the field and refocus it — fired by the wrapper's clear button.
452
+ * Dispatches `input` + `change` on the host so controlled consumers update. */
453
+ clear() {
454
+ if (!this.control) return;
455
+ this.control.value = "";
456
+ this.internals?.setFormValue("");
457
+ this.updateValidity();
458
+ this.updateFilled();
459
+ this.syncAutoHeight();
460
+ this.dispatchEvent(new Event("input", { bubbles: true }));
461
+ this.dispatchEvent(new Event("change", { bubbles: true }));
462
+ this.dispatchEvent(new CustomEvent(CLEAR_INPUT_EVENT, { bubbles: true }));
463
+ this.control.focus();
464
+ }
465
+ // --- Constraint-validation API, proxied from ElementInternals so the host
466
+ // behaves like a native form control (`el.checkValidity()`, `el.validity`). ---
467
+ get validity() {
468
+ return this.internals?.validity;
469
+ }
470
+ get validationMessage() {
471
+ return this.internals?.validationMessage ?? "";
472
+ }
473
+ get willValidate() {
474
+ return this.internals?.willValidate ?? false;
475
+ }
476
+ checkValidity() {
477
+ return this.internals?.checkValidity() ?? true;
478
+ }
479
+ reportValidity() {
480
+ return this.internals?.reportValidity() ?? true;
481
+ }
482
+ /** Form field name — mirrors the `name` attribute, like native `<input>.name`,
483
+ * so `el.name` works (e.g. keying validation messages by field in a form loop). */
484
+ get name() {
485
+ return this.getAttribute("name") ?? "";
486
+ }
487
+ // --- Form-associated callbacks ---
488
+ formResetCallback() {
489
+ this.value = this.getAttribute("defaultvalue") ?? "";
490
+ this.dispatchEvent(new Event("input", { bubbles: true }));
491
+ this.dispatchEvent(new Event("change", { bubbles: true }));
492
+ }
493
+ formDisabledCallback(disabled) {
494
+ this.formDisabled = disabled;
495
+ this.syncDisabled();
496
+ }
497
+ formStateRestoreCallback(state) {
498
+ this.value = state ?? "";
499
+ }
500
+ }
501
+ function register_a_input() {
502
+ if (typeof customElements === "undefined") return;
503
+ if (!customElements.get("a-input")) {
504
+ customElements.define("a-input", AInputElement);
505
+ }
506
+ }
507
+ register_a_input();
508
+ export {
509
+ AInputElement,
510
+ register_a_input
511
+ };
@@ -0,0 +1 @@
1
+ @layer anta{a-menu-group:not(:defined){display:none}a-menu-group{display:block}a-menu-group-label{display:block;padding:6px 8px 2px;color:var(--text-5);font-size:.75em;font-weight:600;letter-spacing:.06em;text-transform:uppercase;user-select:none}}
@@ -0,0 +1,13 @@
1
+ import { HTMLElementBase } from '../anta_helpers';
2
+ import './a-menu-group.css';
3
+ /**
4
+ * `<a-menu-group>` — a titled section grouping related menu items.
5
+ * No JS / no shadow DOM; styled by `a-menu-group.css`. The group's heading
6
+ * is a non-focusable `<a-menu-group-label>` child rendered by the
7
+ * `MenuGroup` JSX wrapper; `role="group"` + `aria-label` are added there too.
8
+ * Keyboard navigation in `<a-menu>` flattens items across group boundaries,
9
+ * skipping the heading.
10
+ */
11
+ export declare class AMenuGroupElement extends HTMLElementBase {
12
+ }
13
+ export declare function register_a_menu_group(): void;
@@ -0,0 +1,15 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-menu-group.css";
3
+ class AMenuGroupElement extends HTMLElementBase {
4
+ }
5
+ function register_a_menu_group() {
6
+ if (typeof customElements === "undefined") return;
7
+ if (!customElements.get("a-menu-group")) {
8
+ customElements.define("a-menu-group", AMenuGroupElement);
9
+ }
10
+ }
11
+ register_a_menu_group();
12
+ export {
13
+ AMenuGroupElement,
14
+ register_a_menu_group
15
+ };
@@ -0,0 +1 @@
1
+ @layer anta{a-menu-item:not(:defined){display:none}a-menu-item{--menu-item-color: var(--text-2);--menu-item-icon-color: var(--text-3);--menu-item-hover: 5%;--menu-item-active: 9%;--menu-item-timing-in: 60ms ease-in-out;--menu-item-timing-out: .28s ease-out;--menu-item-padding-x: 8px;--menu-item-padding-y: 6px;display:flex;flex-shrink:0;align-items:center;gap:.6ch;box-sizing:border-box;width:100%;min-height:28px;padding-block:var(--menu-item-padding-y);padding-inline:var(--menu-item-padding-x);border-radius:var(--radius-sm, 4px);color:var(--menu-item-color);background:transparent;transition:background-color var(--menu-item-timing-out);font:inherit;font-size:14px;line-height:16px;text-align:left;white-space:nowrap;cursor:pointer;user-select:none;outline:none}a-menu-item:hover,a-menu-item:focus-visible{background:color-mix(in oklch,currentColor var(--menu-item-hover),transparent);transition:background-color var(--menu-item-timing-in)}a-menu-item:active{background:color-mix(in oklch,currentColor var(--menu-item-active),transparent);transition:none}a-menu-item:focus-visible{outline:1px solid var(--focus-ring);outline-offset:1px}a-menu-item>a-menu-item-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}a-menu-item>a-icon{flex:none;--icon-size: 16px;color:var(--menu-item-icon-color)}a-menu-item:has(>a-icon:first-child:not(:only-child)){padding-inline-start:max(0px,var(--menu-item-padding-x) - 2px)}a-menu-item:has(>a-icon:last-child:not(:only-child)){padding-inline-end:max(0px,var(--menu-item-padding-x) - 2px)}a-menu-item>kbd{flex:none;margin-left:auto;padding-left:12px;color:var(--text-5);font:inherit;font-size:1em;letter-spacing:.1ch;white-space:nowrap}a-menu-item[submenu]>a-icon:last-of-type{margin-left:auto}a-menu-item[submenu]>a-icon:last-of-type,a-menu-item>a-icon:last-child:not(:only-child){position:relative;inset-inline-end:-2px}a-menu-item[tone=brand],a-menu-item[tone=info],a-menu-item[tone=success],a-menu-item[tone=warning],a-menu-item[tone=critical]{--menu-item-icon-color: var(--menu-item-color)}a-menu-item[tone=brand]{--menu-item-color: var(--text-2-brand)}a-menu-item[tone=info]{--menu-item-color: var(--text-2-info)}a-menu-item[tone=success]{--menu-item-color: var(--text-2-success)}a-menu-item[tone=warning]{--menu-item-color: var(--text-2-warning)}a-menu-item[tone=critical]{--menu-item-color: var(--text-2-critical)}a-menu-item[disabled]{--menu-item-color: var(--text-5);--menu-item-icon-color: var(--text-5);cursor:default;pointer-events:none}.dark a-menu-item{--menu-item-hover: 10%;--menu-item-active: 15%}}
@@ -0,0 +1,47 @@
1
+ import { HTMLElementBase } from '../anta_helpers';
2
+ import './a-menu-item.css';
3
+ declare global {
4
+ interface Document {
5
+ hasKeyListenerForAMenuItem?: boolean;
6
+ }
7
+ }
8
+ /**
9
+ * `<a-menu-item>` — a single row inside an `<a-menu>`.
10
+ *
11
+ * No shadow DOM: its content (leading icon, label, trailing `kbd` /
12
+ * chevron) is slotted light DOM, styled entirely from `a-menu-item.css`.
13
+ * The element carries almost no logic — the parent `<a-menu>` owns
14
+ * click delegation, keyboard navigation, and the close contract. This
15
+ * class exists so the menu can identify items via `instanceof` and so
16
+ * Enter / Space on a focused item synthesizes a click (the single
17
+ * activation path that flows through the menu's click delegation).
18
+ *
19
+ * Static ARIA (`role="menuitem"`, `tabindex`, `aria-haspopup` and the
20
+ * `aria-expanded="false"` baseline on submenu parents) is added by the
21
+ * `MenuItem` JSX wrapper, never here — the element must stay re-renderable
22
+ * from any reactive engine without churning host attributes. The one dynamic
23
+ * bit, live `aria-expanded` on a submenu parent, is reflected by the nested
24
+ * `a-menu` element, which owns that state (see `reflectExpanded` there).
25
+ *
26
+ * Styling notes (`a-menu-item.css` ships comment-free):
27
+ * - `a-menu-item:not(:defined)` is hidden against the pre-upgrade flash
28
+ * (items would render inline in the page before registration).
29
+ * - The `--menu-item-*` custom properties are INTERNAL plumbing for the
30
+ * tone × dark matrix, not a public theming API: `--menu-item-color` is the
31
+ * text color and the hover/active tint mixes from `currentColor` at the
32
+ * `--menu-item-hover` / `--menu-item-active` percentages, so it tracks the
33
+ * tone for free (and toned rows don't read heavier than gray ones). Dark
34
+ * mode just raises the percentages.
35
+ * - Focus ring mirrors a-button: 1px outline at +1px offset, sitting in the
36
+ * surface's 4px padding; pairs with the hover background so keyboard focus
37
+ * reads as tint + ring.
38
+ * - Optical side padding (same idea as a-button): a non-only icon at an edge
39
+ * is trimmed ~2px on that side. Submenu items keep symmetric padding —
40
+ * the nested `<a-menu>` is the actual last child, so the trim rule doesn't
41
+ * fire — and the chevron / a trailing icon is instead nudged toward the
42
+ * edge with relative positioning (visual only, no reflow).
43
+ */
44
+ export declare class AMenuItemElement extends HTMLElementBase {
45
+ connectedCallback(): void;
46
+ }
47
+ export declare function register_a_menu_item(): void;
@@ -0,0 +1,30 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-menu-item.css";
3
+ class AMenuItemElement extends HTMLElementBase {
4
+ connectedCallback() {
5
+ const doc = this.doc;
6
+ if (!doc.hasKeyListenerForAMenuItem) {
7
+ doc.addEventListener("keydown", handleKeyDown, true);
8
+ doc.hasKeyListenerForAMenuItem = true;
9
+ }
10
+ }
11
+ }
12
+ function handleKeyDown(e) {
13
+ if (e.key !== "Enter" && e.key !== " ") return;
14
+ const el = e.target?.closest?.("a-menu-item");
15
+ if (!el) return;
16
+ e.preventDefault();
17
+ if (el.hasAttribute("disabled")) return;
18
+ el.click();
19
+ }
20
+ function register_a_menu_item() {
21
+ if (typeof customElements === "undefined") return;
22
+ if (!customElements.get("a-menu-item")) {
23
+ customElements.define("a-menu-item", AMenuItemElement);
24
+ }
25
+ }
26
+ register_a_menu_item();
27
+ export {
28
+ AMenuItemElement,
29
+ register_a_menu_item
30
+ };
@@ -0,0 +1 @@
1
+ @layer anta{a-menu-separator:not(:defined){display:none}a-menu-separator{display:block;flex-shrink:0;height:1px;margin:2px 4px;background:var(--border-5)}}
@@ -0,0 +1,13 @@
1
+ import { HTMLElementBase } from '../anta_helpers';
2
+ import './a-menu-separator.css';
3
+ /**
4
+ * `<a-menu-separator>` — a thin divider between groups of menu items.
5
+ * No JS / no shadow DOM; styled entirely by `a-menu-separator.css`. The
6
+ * trivial class exists only so importing this module registers the tag and
7
+ * pulls its CSS along the granular entry point. `role="separator"` is added
8
+ * by the `MenuSeparator` JSX wrapper. The hairline uses `--border-5` — the
9
+ * most subtle border token, already mode-adaptive, so no `.dark` override.
10
+ */
11
+ export declare class AMenuSeparatorElement extends HTMLElementBase {
12
+ }
13
+ export declare function register_a_menu_separator(): void;
@@ -0,0 +1,15 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-menu-separator.css";
3
+ class AMenuSeparatorElement extends HTMLElementBase {
4
+ }
5
+ function register_a_menu_separator() {
6
+ if (typeof customElements === "undefined") return;
7
+ if (!customElements.get("a-menu-separator")) {
8
+ customElements.define("a-menu-separator", AMenuSeparatorElement);
9
+ }
10
+ }
11
+ register_a_menu_separator();
12
+ export {
13
+ AMenuSeparatorElement,
14
+ register_a_menu_separator
15
+ };
@@ -0,0 +1 @@
1
+ @layer anta{a-menu:not(:defined){display:none}a-menu{--menu-bg: color-mix(in oklch, var(--bg-1) 90%, transparent);--menu-radius: var(--radius-md, 8px);--menu-border: 1px solid var(--border-3);--menu-shadow: 0 2px 6px color-mix(in oklch, var(--text-1) 8%, transparent), 0 8px 24px color-mix(in oklch, var(--text-1) 10%, transparent);--menu-padding: 4px;--menu-min-width: 88px;--menu-backdrop-filter: blur(20px)}.dark a-menu{--menu-bg: color-mix(in oklch, var(--bg-1) 78%, transparent);--menu-shadow: 0 2px 8px color-mix(in oklch, black 50%, transparent), 0 12px 32px color-mix(in oklch, black 40%, transparent), inset 0 0 0 1px color-mix(in oklch, white 8%, transparent)}}