@antadesign/anta 0.3.1 → 0.3.3

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 (98) hide show
  1. package/dist/anta_helpers.d.ts +111 -0
  2. package/dist/anta_helpers.js +90 -1
  3. package/dist/calendar-core.d.ts +126 -0
  4. package/dist/calendar-core.js +289 -0
  5. package/dist/components/Button.d.ts +9 -1
  6. package/dist/components/Button.js +12 -18
  7. package/dist/components/Calendar.d.ts +80 -0
  8. package/dist/components/Calendar.js +220 -0
  9. package/dist/components/Checkbox.d.ts +21 -15
  10. package/dist/components/Checkbox.js +17 -7
  11. package/dist/components/Expander.d.ts +7 -1
  12. package/dist/components/Expander.js +4 -2
  13. package/dist/components/Input.d.ts +16 -8
  14. package/dist/components/Input.js +19 -11
  15. package/dist/components/InputDate.d.ts +87 -0
  16. package/dist/components/InputDate.js +278 -0
  17. package/dist/components/InputDate.module.css +1 -0
  18. package/dist/components/Menu.d.ts +5 -1
  19. package/dist/components/Menu.js +6 -2
  20. package/dist/components/MenuItem.d.ts +48 -7
  21. package/dist/components/MenuItem.js +71 -7
  22. package/dist/components/MenuSeparator.d.ts +13 -2
  23. package/dist/components/MenuSeparator.js +12 -2
  24. package/dist/components/Progress.d.ts +4 -1
  25. package/dist/components/Progress.js +4 -3
  26. package/dist/components/RadioGroup.d.ts +17 -15
  27. package/dist/components/RadioGroup.js +8 -8
  28. package/dist/components/Select.d.ts +255 -0
  29. package/dist/components/Select.js +290 -0
  30. package/dist/components/Select.module.css +1 -0
  31. package/dist/components/Tab.d.ts +37 -0
  32. package/dist/components/Tab.js +5 -0
  33. package/dist/components/TabPanel.d.ts +22 -0
  34. package/dist/components/TabPanel.js +5 -0
  35. package/dist/components/Tabs.d.ts +129 -0
  36. package/dist/components/Tabs.js +176 -0
  37. package/dist/components/Tabs.module.css +1 -0
  38. package/dist/components/Tag.d.ts +10 -9
  39. package/dist/components/Tag.js +2 -2
  40. package/dist/components/Text.d.ts +6 -5
  41. package/dist/components/Title.d.ts +4 -2
  42. package/dist/components/Tooltip.d.ts +13 -1
  43. package/dist/components/Tooltip.js +9 -0
  44. package/dist/elements/a-button.css +1 -1
  45. package/dist/elements/a-calendar.css +1 -0
  46. package/dist/elements/a-calendar.d.ts +76 -0
  47. package/dist/elements/a-calendar.js +190 -0
  48. package/dist/elements/a-checkbox.css +1 -1
  49. package/dist/elements/a-checkbox.d.ts +1 -2
  50. package/dist/elements/a-checkbox.js +5 -5
  51. package/dist/elements/a-expander.css +1 -1
  52. package/dist/elements/a-expander.d.ts +16 -5
  53. package/dist/elements/a-expander.js +65 -9
  54. package/dist/elements/a-icon.shapes.css +1 -1
  55. package/dist/elements/a-icon.shapes.d.ts +6 -1
  56. package/dist/elements/a-icon.shapes.js +9 -0
  57. package/dist/elements/a-input.css +1 -1
  58. package/dist/elements/a-input.d.ts +6 -0
  59. package/dist/elements/a-input.js +28 -9
  60. package/dist/elements/a-menu-group.css +1 -1
  61. package/dist/elements/a-menu-item.css +1 -1
  62. package/dist/elements/a-menu-item.d.ts +14 -4
  63. package/dist/elements/a-menu-item.js +17 -0
  64. package/dist/elements/a-menu-separator.css +1 -1
  65. package/dist/elements/a-menu.css +1 -1
  66. package/dist/elements/a-menu.d.ts +51 -8
  67. package/dist/elements/a-menu.js +285 -42
  68. package/dist/elements/a-progress.css +1 -1
  69. package/dist/elements/a-radio-group.d.ts +1 -3
  70. package/dist/elements/a-radio-group.js +13 -13
  71. package/dist/elements/a-radio.css +1 -1
  72. package/dist/elements/a-radio.d.ts +3 -11
  73. package/dist/elements/a-radio.js +3 -34
  74. package/dist/elements/a-tab.css +1 -0
  75. package/dist/elements/a-tab.d.ts +6 -0
  76. package/dist/elements/a-tab.js +15 -0
  77. package/dist/elements/a-tabpanel.css +1 -0
  78. package/dist/elements/a-tabs.css +1 -0
  79. package/dist/elements/a-tabs.d.ts +28 -0
  80. package/dist/elements/a-tabs.js +157 -0
  81. package/dist/elements/a-tag.css +1 -1
  82. package/dist/elements/a-text.css +1 -1
  83. package/dist/elements/a-text.d.ts +14 -13
  84. package/dist/elements/a-text.js +53 -23
  85. package/dist/elements/a-title.css +1 -1
  86. package/dist/elements/a-tooltip.css +1 -1
  87. package/dist/elements/a-tooltip.d.ts +16 -8
  88. package/dist/elements/a-tooltip.js +62 -16
  89. package/dist/elements/index.d.ts +4 -0
  90. package/dist/elements/index.js +10 -0
  91. package/dist/general_types.d.ts +252 -30
  92. package/dist/index.d.ts +15 -1
  93. package/dist/index.js +43 -1
  94. package/dist/jsx-runtime.d.ts +20 -7
  95. package/dist/jsx-runtime.js +6 -0
  96. package/dist/reset.css +1 -1
  97. package/dist/tokens.css +1 -1
  98. package/package.json +4 -2
@@ -16,6 +16,11 @@ export declare class AInputElement extends HTMLElementBase {
16
16
  private ready;
17
17
  private formDisabled;
18
18
  constructor();
19
+ /** Floating-element anchor protocol (see `anchorRect` in anta_helpers): point
20
+ * a menu / tooltip anchored to this input at the `.field` box, not the host —
21
+ * the host's box also spans the label and hint, which would push a dropdown
22
+ * below the hint or misalign a tooltip. */
23
+ getAnchorRect(): DOMRect;
19
24
  connectedCallback(): void;
20
25
  attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
21
26
  /** (Re)build the shadow control from the current attributes. */
@@ -61,6 +66,7 @@ export declare class AInputElement extends HTMLElementBase {
61
66
  /** Form field name — mirrors the `name` attribute, like native `<input>.name`,
62
67
  * so `el.name` works (e.g. keying validation messages by field in a form loop). */
63
68
  get name(): string;
69
+ set name(v: string);
64
70
  formResetCallback(): void;
65
71
  formDisabledCallback(disabled: boolean): void;
66
72
  formStateRestoreCallback(state: string): void;
@@ -14,7 +14,12 @@ const FORWARDED = [
14
14
  "required",
15
15
  "min",
16
16
  "max",
17
- "step"
17
+ "step",
18
+ // `aria-label` on the host names the internal control (the actual textbox);
19
+ // without forwarding it, a bare `<a-input aria-label>` leaves the control
20
+ // unnamed. `applyLabelAria` already defers to a host `aria-label` over the
21
+ // label-slot text, so the two don't fight.
22
+ "aria-label"
18
23
  ];
19
24
  const BOOL_FORWARDED = /* @__PURE__ */ new Set(["readonly", "required"]);
20
25
  const CLEAR_TRIGGER = "clearrequest";
@@ -45,6 +50,7 @@ const SHADOW_STYLE = `
45
50
  .field {
46
51
  --_bc: var(--input-border);
47
52
  --_bw: 0.5px;
53
+ --_pad-block: 4px;
48
54
 
49
55
  display: flex;
50
56
  align-items: center;
@@ -58,9 +64,10 @@ const SHADOW_STYLE = `
58
64
  :host([multiline]) .field { align-items: stretch; }
59
65
  :host([status]) .field { --_bw: 1px; }
60
66
  :host([size="small"]) { --_fs: 13px; --_lh: 16px; }
61
- :host([size="large"]) { --_fs: 17px; --_lh: 24px; }
67
+ :host([size="large"]) { --_fs: 17px; --_lh: 22px; }
62
68
  :host([size="small"]) .field { min-height: 24px; }
63
69
  :host([size="large"]) .field { min-height: 32px; }
70
+ :host([round]) .field { border-radius: var(--input-round, 999px); }
64
71
 
65
72
  @media (hover: hover) and (pointer: fine) {
66
73
  :host(:not(:disabled)) .field:hover { --_bc: var(--input-border-hover); }
@@ -84,7 +91,7 @@ const SHADOW_STYLE = `
84
91
  outline: none;
85
92
  color: var(--input-text);
86
93
  font-family: var(--sans-serif);
87
- font-feature-settings: 'ss02', 'ss05';
94
+ font-feature-settings: 'ss02', 'ss05', 'tnum';
88
95
  font-size: var(--_fs);
89
96
  line-height: var(--_lh);
90
97
  font-weight: 400;
@@ -92,8 +99,6 @@ const SHADOW_STYLE = `
92
99
  appearance: none;
93
100
  }
94
101
  textarea {
95
- --_pad-block: 4px;
96
-
97
102
  resize: none;
98
103
  padding-block: var(--_pad-block);
99
104
  overflow-y: auto;
@@ -103,6 +108,10 @@ const SHADOW_STYLE = `
103
108
  }
104
109
  input::placeholder, textarea::placeholder { color: var(--input-placeholder); opacity: 1; }
105
110
  input:disabled, textarea:disabled { cursor: not-allowed; }
111
+ :host([readonly]:not([disabled])) .field,
112
+ :host([readonly]:not([disabled])) input,
113
+ :host([readonly]:not([disabled])) textarea { cursor: pointer; }
114
+ :host([readonly]:not([disabled])) input { text-overflow: ellipsis; }
106
115
  input::-webkit-search-cancel-button,
107
116
  input::-webkit-search-decoration,
108
117
  input::-webkit-search-results-button,
@@ -142,15 +151,15 @@ const SHADOW_STYLE = `
142
151
  .field.is-filled slot[name="clear"] { display: flex; align-items: center; }
143
152
  :host(:disabled) slot[name="clear"],
144
153
  :host([readonly]) slot[name="clear"] { display: none; }
145
- :host([multiline]) slot[name="clear"] { align-self: flex-start; }
146
154
 
147
155
  .field.has-leading slot[name="leading"] { margin-inline-start: 7px; }
148
156
  .field.has-leading input,
149
157
  .field.has-leading textarea { padding-inline-start: 5px; }
150
158
  :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;
159
+ :host([multiline]) .field.has-trailing slot[name="trailing"],
160
+ :host([multiline]) .field.is-filled slot[name="clear"] {
161
+ align-self: flex-start;
162
+ height: calc(var(--_lh) + var(--_pad-block) * 2);
154
163
  }
155
164
 
156
165
  .hint {
@@ -245,6 +254,13 @@ class AInputElement extends HTMLElementBase {
245
254
  const extrasSlot = document.createElement("slot");
246
255
  shadow.append(style, this.labelBox, this.field, extrasSlot, this.hintBox);
247
256
  }
257
+ /** Floating-element anchor protocol (see `anchorRect` in anta_helpers): point
258
+ * a menu / tooltip anchored to this input at the `.field` box, not the host —
259
+ * the host's box also spans the label and hint, which would push a dropdown
260
+ * below the hint or misalign a tooltip. */
261
+ getAnchorRect() {
262
+ return this.field.getBoundingClientRect();
263
+ }
248
264
  connectedCallback() {
249
265
  if (Object.prototype.hasOwnProperty.call(this, "value")) {
250
266
  const v = this.value;
@@ -484,6 +500,9 @@ class AInputElement extends HTMLElementBase {
484
500
  get name() {
485
501
  return this.getAttribute("name") ?? "";
486
502
  }
503
+ set name(v) {
504
+ this.setAttribute("name", v);
505
+ }
487
506
  // --- Form-associated callbacks ---
488
507
  formResetCallback() {
489
508
  this.value = this.getAttribute("defaultvalue") ?? "";
@@ -1 +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}}
1
+ @layer anta{a-menu-group:not(:defined){display:none}a-menu-group{display:flex;flex-direction:column;gap:1px}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}}
@@ -1 +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%}}
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-hint-color: var(--text-3);--menu-item-hover: 5%;--menu-item-active: 8%;--menu-item-selected: 8%;--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[round] a-menu-item{border-radius:999px}a-menu-item:hover,a-menu-item:focus-visible,a-menu-item:state(active){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[selected]{background:color-mix(in oklch,currentColor var(--menu-item-selected),transparent)}a-menu-item:focus-visible{outline:1px solid var(--focus-ring);outline-offset:1px}a-menu-item>a-menu-item-label,a-menu-item>a-menu-item-text{flex:1 1 auto;min-width:0}a-menu-item>a-menu-item-text{display:flex;flex-direction:column}a-menu-item:has(>a-menu-item-text){align-items:flex-start}a-menu-item-label{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis}a-menu-item-hint{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-block-start:1px;color:var(--menu-item-hint-color);font-size:12px;line-height:15px}a-menu-item>a-icon{flex:none;--icon-size: 16px;color:var(--menu-item-icon-color)}a-menu-item>a-checkbox,a-menu-item>a-radio{flex:none;--checkbox-gap: 0;--radio-gap: 0;pointer-events:none}a-menu-item:has(>:is(a-icon,a-checkbox,a-radio,[data-indicator]):first-child:not(:only-child)){padding-inline-start:var(--menu-item-padding-y)}a-menu-item:not([submenu]):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);--menu-item-hint-color: var(--text-3-brand)}a-menu-item[tone=info]{--menu-item-color: var(--text-2-info);--menu-item-hint-color: var(--text-3-info)}a-menu-item[tone=success]{--menu-item-color: var(--text-2-success);--menu-item-hint-color: var(--text-3-success)}a-menu-item[tone=warning]{--menu-item-color: var(--text-2-warning);--menu-item-hint-color: var(--text-3-warning)}a-menu-item[tone=critical]{--menu-item-color: var(--text-2-critical);--menu-item-hint-color: var(--text-3-critical)}a-menu-item[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]){--menu-item-tone-source: attr(tone type(<color>), var(--text-2));--menu-item-color: oklch(from var(--menu-item-tone-source) .45 c h);--menu-item-icon-color: var(--menu-item-color);--menu-item-hint-color: oklch(from var(--menu-item-tone-source) .55 calc(c*.85) h);.dark &{--menu-item-color: oklch(from var(--menu-item-tone-source) .78 c h);--menu-item-hint-color: oklch(from var(--menu-item-tone-source) .68 calc(c*.85) h)}}a-menu-item[disabled]{--menu-item-color: var(--text-5);--menu-item-icon-color: var(--text-5);--menu-item-hint-color: var(--text-5);cursor:default;pointer-events:none}.dark a-menu-item{--menu-item-hover: 10%;--menu-item-active: 14%;--menu-item-selected: 14%}}
@@ -36,12 +36,22 @@ declare global {
36
36
  * surface's 4px padding; pairs with the hover background so keyboard focus
37
37
  * reads as tint + ring.
38
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).
39
+ * is trimmed ~2px on that side. Submenu items keep symmetric padding — the
40
+ * end-trim is scoped `:not([submenu])` and the chevron / a trailing icon is
41
+ * instead nudged toward the edge with relative positioning (visual only, no
42
+ * reflow). The trim's `:not([submenu])` guard is why children (which include a
43
+ * submenu's nested `<a-menu>`) can render before the chevron without the
44
+ * chevron-as-last-child re-triggering the trim.
43
45
  */
44
46
  export declare class AMenuItemElement extends HTMLElementBase {
47
+ private internals?;
48
+ constructor();
49
+ /** The active (combobox) cursor. `a-menu` sets this **property** (never an
50
+ * attribute — no DOM mutation, same rule as `a-radio.selected`) as ArrowUp/Down
51
+ * move the cursor while focus stays in the filter input; the item mirrors it to
52
+ * `:state(active)` for CSS. Off-DOM, so a reactive re-render never churns it. */
53
+ set active(on: boolean);
54
+ get active(): boolean;
45
55
  connectedCallback(): void;
46
56
  }
47
57
  export declare function register_a_menu_item(): void;
@@ -1,6 +1,23 @@
1
1
  import { HTMLElementBase } from "../anta_helpers";
2
2
  import "./a-menu-item.css";
3
3
  class AMenuItemElement extends HTMLElementBase {
4
+ internals;
5
+ constructor() {
6
+ super();
7
+ this.internals = this.attachInternals?.();
8
+ }
9
+ /** The active (combobox) cursor. `a-menu` sets this **property** (never an
10
+ * attribute — no DOM mutation, same rule as `a-radio.selected`) as ArrowUp/Down
11
+ * move the cursor while focus stays in the filter input; the item mirrors it to
12
+ * `:state(active)` for CSS. Off-DOM, so a reactive re-render never churns it. */
13
+ set active(on) {
14
+ if (!this.internals) return;
15
+ if (on) this.internals.states.add("active");
16
+ else this.internals.states.delete("active");
17
+ }
18
+ get active() {
19
+ return this.internals?.states.has("active") ?? false;
20
+ }
4
21
  connectedCallback() {
5
22
  const doc = this.doc;
6
23
  if (!doc.hasKeyListenerForAMenuItem) {
@@ -1 +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)}}
1
+ @layer anta{a-menu-separator:not(:defined){display:none}a-menu-separator{--menu-separator-label-size: 13px;display:block;flex-shrink:0;height:1px;margin:2px 4px;background:var(--border-5)}a-menu-separator:not(:empty){height:auto;margin:0;padding:6px 8px;background:none;color:var(--text-3);font-size:var(--menu-separator-label-size);line-height:1.3;user-select:none}}
@@ -1 +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)}}
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)}a-menu[round]{--menu-radius: attr(round type(<length>), 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)}}
@@ -22,9 +22,12 @@ type MenuState = 'open' | 'closed';
22
22
  * slotted light DOM (see `a-menu-item.css`), directly styleable.
23
23
  */
24
24
  export declare class AMenuElement extends HTMLElementBase {
25
+ #private;
25
26
  static observedAttributes: string[];
26
27
  /** Shadow-internal popover surface — the only thing we ever mutate. */
27
28
  surface: HTMLDivElement;
29
+ /** The scrolling body inside the surface (holds the items). */
30
+ private scrollEl;
28
31
  listening: boolean;
29
32
  private _shown;
30
33
  private teardown?;
@@ -36,6 +39,9 @@ export declare class AMenuElement extends HTMLElementBase {
36
39
  private closeTimer?;
37
40
  private typeBuffer;
38
41
  private typeTimer?;
42
+ private activeItem;
43
+ private comboObserver?;
44
+ private _flippedTop;
39
45
  constructor();
40
46
  connectedCallback(): void;
41
47
  disconnectedCallback(): void;
@@ -48,16 +54,13 @@ export declare class AMenuElement extends HTMLElementBase {
48
54
  /** A submenu is an `<a-menu>` nested inside an `<a-menu-item>` — derived from
49
55
  * structure, no `submenu` attribute needed (the parent item is the anchor). */
50
56
  get isSubmenu(): boolean;
51
- private get isContext();
52
- private get isCoord();
53
- private get isHover();
54
- private get offset();
55
- private get placement();
56
57
  /** Root menu: the previous element sibling is the trigger. Submenu: the
57
58
  * enclosing menu item. One deterministic rule per case — no ambiguity. */
58
59
  get triggerAnchor(): HTMLElement | null;
59
- /** For a submenu: the menu that contains its anchor item. */
60
- private get ownerMenu();
60
+ /** Return focus to the trigger. Focuses the first focusable in the anchor; if
61
+ * there is none (a mis-authored trigger — e.g. `renderTrigger` returning a
62
+ * fragment or a non-focusable node), warns instead of silently focusing air. */
63
+ private focusTrigger;
61
64
  get isOpen(): boolean;
62
65
  /** Skip elements that can't actually take focus — `display:none` (incl. a
63
66
  * closed submenu's contents), `visibility:hidden`, `content-visibility`
@@ -74,7 +77,41 @@ export declare class AMenuElement extends HTMLElementBase {
74
77
  * used to trap Tab within the open menu. Submenu contents are excluded
75
78
  * (their nearest `a-menu` is the submenu). */
76
79
  private focusables;
77
- private focusFirstItem;
80
+ /** On open, seat initial focus like a native `<select>` / macOS menu: a menu
81
+ * carrying a current value opens *at* that value, not at the top. Brings the
82
+ * first selected row into view and, when opened via keyboard, focuses it;
83
+ * with no selected row it focuses the first item (keyboard) and does nothing
84
+ * otherwise. "Selected" = a `selected` row (tint) or `aria-checked` /
85
+ * `aria-selected="true"` (checkable rows) — the first such visible item of
86
+ * THIS menu, so a leaf buried in a closed submenu (not visible) is skipped and
87
+ * a multi-select lands on its topmost checked row. */
88
+ private seatInitialFocus;
89
+ /** Scroll THIS menu's body so `item` sits inside the visible scroll viewport,
90
+ * touching only the internal `.scroll` container — never the document, whose
91
+ * scroll would trip the anchor-scrolled-out dismiss. No-op for a menu short
92
+ * enough not to scroll. */
93
+ private scrollItemIntoView;
94
+ /** Fade the scrolling body's content into the top / bottom edges — but only on the
95
+ * side that actually has more to scroll, so a short (non-scrolling) menu and the
96
+ * true top / bottom stay crisp. Drives the `--fade-*` / `--gap-top` vars the
97
+ * `.scroll` mask reads; runs on scroll and after every (re)position.
98
+ * Shadow-internal only. */
99
+ private updateScrollFade;
100
+ /** Move the combobox cursor. Sets the item's `active` **property** (off-DOM
101
+ * `:state(active)`, no attribute churn) and reflects `aria-activedescendant`
102
+ * onto the search field — the same ARIA-state-reflection latitude as
103
+ * `aria-expanded` on the anchor. `null` clears the cursor. */
104
+ private setActive;
105
+ /** Re-seat the cursor on the first option — but only once the filter has input.
106
+ * An empty filter (e.g. right after opening) shows NO active row, so the first
107
+ * ArrowDown is what steps onto the list; typing then keeps the top match active.
108
+ * Rows marked `data-menu-skip-active` (e.g. a Select-all action) are skipped as
109
+ * the seat target — the cursor lands on the first real option — but they stay
110
+ * arrow-reachable. */
111
+ private resetActive;
112
+ /** Combobox arrow / Home / End / Enter handling; returns true if it consumed the
113
+ * key (so `handleKey` stops). Any other key falls through to the input (typing). */
114
+ private handleComboKey;
78
115
  /** Public imperative API. Routes through the same intent path as the
79
116
  * triggers, so it emits `statechange` and respects controlled mode. */
80
117
  open(opts?: {
@@ -109,6 +146,12 @@ export declare class AMenuElement extends HTMLElementBase {
109
146
  /** Apply OPEN to the DOM (no event) — used by uncontrolled intent and by the
110
147
  * controlled `state` sync. */
111
148
  private show;
149
+ /** While a filter field is open, the visible option list changes as the user
150
+ * types (the consumer re-renders the matches); re-seat the cursor on the first
151
+ * match. `childList` only — an item toggling its own `selected` (multi-select)
152
+ * mutates deep in its subtree, not this menu's direct children, so it won't
153
+ * spuriously reset the cursor. */
154
+ private startComboObserver;
112
155
  /** Watch the root trigger and dismiss the system once it scrolls out of the
113
156
  * spot it held at open (see trackPosition). Deferred a frame so the trigger's
114
157
  * post-open layout has settled before the rect is snapshotted; guarded in case