@antadesign/anta 0.3.3 → 0.3.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.
@@ -134,6 +134,10 @@ class AMenuElement extends HTMLElementBase {
134
134
  * but stays visible until the consumer flips `state`. The flag lets the
135
135
  * `closeAll` backstop skip a duplicate emit. Cleared on every show. */
136
136
  _dismissNotified = false;
137
+ // Custom-state carrier — exposes the menu's own open state as `:state(open)`
138
+ // (off-DOM, like `a-menu-item`'s `:state(active)`). Never used to mutate light
139
+ // DOM; see `reflectOpen`.
140
+ internals;
137
141
  // Submenu hover-intent timers.
138
142
  openTimer;
139
143
  closeTimer;
@@ -142,7 +146,8 @@ class AMenuElement extends HTMLElementBase {
142
146
  typeTimer;
143
147
  // Combobox (filter) state — engaged when a `[data-menu-search]` field is slotted
144
148
  // in (e.g. `Select` with `filter`). Focus stays in that field; ArrowUp/Down move
145
- // `activeItem` (a cursor, not DOM focus) and reflect `aria-activedescendant`.
149
+ // `activeItem` (a cursor, not DOM focus) and REPORT it via the `activedescendant`
150
+ // event, which the reactive layer reflects onto the field's `aria-activedescendant`.
146
151
  activeItem = null;
147
152
  comboObserver;
148
153
  // The vertical side chosen at open (true = flipped above the anchor). A re-anchor
@@ -151,6 +156,10 @@ class AMenuElement extends HTMLElementBase {
151
156
  _flippedTop = null;
152
157
  constructor() {
153
158
  super();
159
+ try {
160
+ this.internals = this.attachInternals?.();
161
+ } catch {
162
+ }
154
163
  const shadow = this.attachShadow({ mode: "open" });
155
164
  const style = document.createElement("style");
156
165
  style.textContent = `
@@ -449,9 +458,9 @@ class AMenuElement extends HTMLElementBase {
449
458
  return el && el.closest("a-menu") === this ? el : null;
450
459
  }
451
460
  /** Move the combobox cursor. Sets the item's `active` **property** (off-DOM
452
- * `:state(active)`, no attribute churn) and reflects `aria-activedescendant`
453
- * onto the search field the same ARIA-state-reflection latitude as
454
- * `aria-expanded` on the anchor. `null` clears the cursor. */
461
+ * `:state(active)`, no attribute churn) for the highlight, and REPORTS the
462
+ * active id via the `activedescendant` event so the reactive layer can set
463
+ * `aria-activedescendant` on the light-DOM field. `null` clears the cursor. */
455
464
  setActive(item) {
456
465
  if (this.activeItem && this.activeItem !== item) this.activeItem.active = false;
457
466
  this.activeItem = item;
@@ -459,10 +468,9 @@ class AMenuElement extends HTMLElementBase {
459
468
  item.active = true;
460
469
  item.scrollIntoView?.({ block: "nearest" });
461
470
  }
462
- const s = this.#searchField;
463
- if (!s) return;
464
- if (item?.id) s.setAttribute("aria-activedescendant", item.id);
465
- else s.removeAttribute("aria-activedescendant");
471
+ this.dispatchEvent(
472
+ new CustomEvent("activedescendant", { detail: { id: item?.id ?? null } })
473
+ );
466
474
  }
467
475
  /** Re-seat the cursor on the first option — but only once the filter has input.
468
476
  * An empty filter (e.g. right after opening) shows NO active row, so the first
@@ -661,7 +669,7 @@ class AMenuElement extends HTMLElementBase {
661
669
  if (this.surface.isConnected && !this._shown) this.surface.showPopover();
662
670
  this._shown = true;
663
671
  this._dismissNotified = false;
664
- this.reflectExpanded(true);
672
+ this.reflectOpen(true);
665
673
  this.hideAnchorTooltip();
666
674
  this.position(coord, instant);
667
675
  }
@@ -677,24 +685,24 @@ class AMenuElement extends HTMLElementBase {
677
685
  _doHide() {
678
686
  if (this.surface.isConnected && this._shown) this.surface.hidePopover();
679
687
  this._shown = false;
680
- this.reflectExpanded(false);
688
+ this.reflectOpen(false);
681
689
  this.cancelOpenTimer();
682
690
  this.cancelCloseTimer();
683
691
  }
684
- /** Mirror the open state onto a SUBMENU parent's `aria-expanded`. This is the
685
- * one sanctioned light-DOM ARIA mutation (like `el.focus()`): the anchor is
686
- * an `<a-menu-item>` the `MenuItem` wrapper renders WITH a resting
687
- * `aria-expanded="false"` baseline, so a reactive re-render resets it to a
688
- * valid value and the next open/close re-syncs.
689
- *
690
- * A ROOT menu's trigger is a consumer-owned sibling we don't render and have
691
- * no baseline forwriting to it would mutate foreign DOM (and couldn't
692
- * self-heal), so we leave its `aria-expanded` to the consumer. The menu is
693
- * still announced and Esc-dismissable; consumers add `aria-haspopup="menu"`
694
- * to their trigger themselves. (`context` menus aren't triggers either.) */
695
- reflectExpanded(open) {
696
- if (!this.isSubmenu) return;
697
- this.triggerAnchor?.setAttribute("aria-expanded", open ? "true" : "false");
692
+ /** Expose the menu's OWN open state as an off-DOM custom state (`:state(open)`),
693
+ * never a light-DOM attribute. A web component must not mutate light DOM — it
694
+ * desyncs the worker-thread reactive model, which owns the light tree. A
695
+ * submenu parent lights its open branch purely in CSS via
696
+ * `a-menu-item:has(> a-menu:state(open))`; the state is element-owned (like
697
+ * `a-menu-item`'s `:state(active)`) and, being off-DOM, survives a reactive
698
+ * re-render without the element ever writing an attribute. Set on every menu
699
+ * (harmless on rootsno `a-menu-item` parent matches the selector). */
700
+ reflectOpen(open) {
701
+ try {
702
+ if (open) this.internals?.states.add("open");
703
+ else this.internals?.states.delete("open");
704
+ } catch {
705
+ }
698
706
  }
699
707
  /* ============================ positioning ============================ */
700
708
  position(coord, sync = false, reanchor = false) {
@@ -779,6 +787,23 @@ class AMenuElement extends HTMLElementBase {
779
787
  * - nothing → keep open (plain custom content doesn't dismiss).
780
788
  */
781
789
  onSurfaceClick = (e) => {
790
+ for (const node of e.composedPath()) {
791
+ if (node === this.surface) break;
792
+ if (node instanceof AMenuItemElement) {
793
+ if (!node.hasAttribute("disabled") && !node.querySelector("a-menu") && node.closest("a-menu") === this) {
794
+ node.dispatchEvent(
795
+ new MouseEvent("menuselect", {
796
+ bubbles: false,
797
+ altKey: e.altKey,
798
+ ctrlKey: e.ctrlKey,
799
+ metaKey: e.metaKey,
800
+ shiftKey: e.shiftKey
801
+ })
802
+ );
803
+ }
804
+ break;
805
+ }
806
+ }
782
807
  for (const node of e.composedPath()) {
783
808
  if (node === this.surface) break;
784
809
  if (!(node instanceof Element)) continue;
@@ -974,12 +999,23 @@ class AMenuElement extends HTMLElementBase {
974
999
  scheduleClose() {
975
1000
  this.cancelOpenTimer();
976
1001
  if (!this._shown) return;
1002
+ if (this.#hasKeyboardFocusInside) return;
977
1003
  this.cancelCloseTimer();
978
1004
  this.closeTimer = setTimeout(() => {
979
1005
  this.closeTimer = void 0;
980
1006
  this.requestClose();
981
1007
  }, SUBMENU_CLOSE_DELAY);
982
1008
  }
1009
+ /** True when the document's focused element is inside this menu AND is
1010
+ * keyboard-focused (`:focus-visible`). Distinguishes "arrowed into the flyout"
1011
+ * (keep it open on hover-out) from a mouse-click focus (close as usual).
1012
+ * `activeElement` retargets to the shadow host at the document level, so a
1013
+ * focused menu item (or its delegated inner control) reads as a light-DOM
1014
+ * descendant here. */
1015
+ get #hasKeyboardFocusInside() {
1016
+ const active = this.doc.activeElement;
1017
+ return !!active && this.contains(active) && active.matches(":focus-visible");
1018
+ }
983
1019
  cancelOpenTimer() {
984
1020
  if (this.openTimer !== void 0) {
985
1021
  clearTimeout(this.openTimer);
@@ -1 +1 @@
1
- @layer anta{a-radio{--radio-control-size: 16px;--radio-border-width: 1.5px;--radio-dot-size: 6px;--_radio-dot-adjust: 0px;--radio-gap: 8px;--radio-bg: var(--bg-1);--radio-fill: #635b65;--radio-fill-hover: #534c57;--radio-fill-active: #49424c;--radio-border-neutral: #d4ced4;--radio-border: var(--radio-border-neutral);--radio-border-hover: #c1b9c1;--radio-border-active: #9f99a1;--radio-dot: #ffffff;--radio-dot-disabled: #c1b9c1;--radio-bg-disabled: color-mix(in oklch, #44374b 10%, transparent);--radio-border-disabled: color-mix(in oklch, #44374b 15%, transparent);--radio-label-color: var(--text-2);--radio-label-color-disabled: var(--text-4);--radio-hint-color: var(--text-3);--radio-label-fs: 15px;--radio-label-lh: 20px;--radio-hint-fs: 14px;--radio-hint-lh: 17px;display:inline-grid;grid-template-columns:auto 1fr;align-items:center;column-gap:var(--radio-gap);cursor:pointer;user-select:none;-webkit-user-drag:none;vertical-align:middle;outline:none;color:var(--radio-label-color);font-family:var(--sans-serif);font-size:var(--radio-label-fs);line-height:var(--radio-label-lh);font-feature-settings:"ss02","ss05","tnum";&:before{content:"";grid-column:1;grid-row:1 / -1;align-self:center;box-sizing:border-box;inline-size:var(--radio-control-size);block-size:var(--radio-control-size);border-radius:50%;background:var(--radio-bg);border:var(--radio-border-width) solid var(--radio-border);transition:background-color .15s ease-out,border-color .15s ease-out}&:after{content:"";grid-column:1;grid-row:1 / -1;align-self:center;justify-self:center;inline-size:calc(var(--radio-dot-size) - var(--_radio-dot-adjust));block-size:calc(var(--radio-dot-size) - var(--_radio-dot-adjust));border-radius:50%;background:var(--radio-dot);transform:scale(0);transition:transform .12s ease-out;pointer-events:none}@media(hover:hover)and (pointer:fine){&:not([disabled]):hover:before{border-color:var(--radio-border-hover);transition:background-color 75ms ease-in,border-color 75ms ease-in}&:not([disabled]):hover:state(selected):before{background:var(--radio-fill-hover);border-color:var(--radio-fill-hover)}}&:not([disabled]):active:before{border-color:var(--radio-border-active);transition:background-color 50ms linear,border-color 50ms linear}&:not([disabled]):active:state(selected):before{background:var(--radio-fill-active);border-color:var(--radio-fill-active)}&:state(selected){&:before{background:var(--radio-fill);border-color:var(--radio-fill)}&:after{transform:scale(1)}}&:focus-visible:before{outline:1px solid var(--focus-ring);outline-offset:1px}&[disabled]{cursor:not-allowed;color:var(--radio-label-color-disabled);&:before{background:var(--radio-bg-disabled);border-color:var(--radio-border-disabled);transition:none}&:state(selected):after{background:var(--radio-dot-disabled)}a-radio-hint{color:var(--radio-label-color-disabled)}}&[size=small]{--radio-control-size: 14px;--radio-dot-size: 5px;--radio-gap: 6px;--radio-label-fs: 13px;--radio-label-lh: 16px;--radio-hint-fs: 12px;--radio-hint-lh: 14px}&[size=large]{--radio-control-size: 18px;--radio-dot-size: 7px;--radio-label-fs: 17px;--radio-label-lh: 24px;--radio-hint-fs: 16px;--radio-hint-lh: 21px}}a-radio-label,a-radio-hint{display:block;grid-column:2;min-width:0}a-radio-hint{color:var(--radio-hint-color);font-size:var(--radio-hint-fs);line-height:var(--radio-hint-lh);margin-block-start:2px}a-radio-group:focus-visible a-radio:state(selected):before{outline:1px solid var(--focus-ring);outline-offset:1px}a-radio[tone=brand],a-radio[tone-selected=brand],a-radio-group[tone=brand] a-radio:not([tone]),a-radio-group[tone-selected=brand] a-radio:not([tone-selected]){--radio-fill: #5f4bc3;--radio-fill-hover: #503cb4;--radio-fill-active: #483493}a-radio[tone=neutral],a-radio[tone-selected=neutral],a-radio-group[tone=neutral] a-radio:not([tone]),a-radio-group[tone-selected=neutral] a-radio:not([tone-selected]){--radio-fill: #635b65;--radio-fill-hover: #534c57;--radio-fill-active: #49424c}a-radio[tone=info],a-radio[tone-selected=info],a-radio-group[tone=info] a-radio:not([tone]),a-radio-group[tone-selected=info] a-radio:not([tone-selected]){--radio-fill: #1f6eb2;--radio-fill-hover: #1a5b93;--radio-fill-active: #175082}a-radio[tone=success],a-radio[tone-selected=success],a-radio-group[tone=success] a-radio:not([tone]),a-radio-group[tone-selected=success] a-radio:not([tone-selected]){--radio-fill: #2a7e43;--radio-fill-hover: #226737;--radio-fill-active: #1f5c31}a-radio[tone=warning],a-radio[tone-selected=warning],a-radio-group[tone=warning] a-radio:not([tone]),a-radio-group[tone-selected=warning] a-radio:not([tone-selected]){--radio-fill: #c37416;--radio-fill-hover: #ae6613;--radio-fill-active: #995200}a-radio[tone=critical],a-radio[tone-selected=critical],a-radio-group[tone=critical] a-radio:not([tone]),a-radio-group[tone-selected=critical] a-radio:not([tone-selected]){--radio-fill: #c9302c;--radio-fill-hover: #b02120;--radio-fill-active: #a01c1c}a-radio[tone]:not([tone=""],[tone=neutral]),a-radio-group[tone]:not([tone=""],[tone=neutral]) a-radio:not([tone]){--_radio-border-base: color-mix(in oklch, var(--radio-fill) 70%, var(--radio-border-neutral));--radio-border: oklch(from var(--_radio-border-base) calc(l + .17) c h);--radio-border-hover: oklch(from var(--_radio-border-base) calc(l + .1) c h);--radio-border-active: var(--_radio-border-base);.dark &{--radio-border: var(--_radio-border-base);--radio-border-hover: oklch(from var(--_radio-border-base) calc(l + .1) c h);--radio-border-active: oklch(from var(--_radio-border-base) calc(l + .17) c h)}}a-radio[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]){--radio-tone-source: attr(tone type(<color>), transparent)}a-radio[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]){--radio-tone-source: attr(tone-selected type(<color>), transparent)}a-radio[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]),a-radio[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]),a-radio-group[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]) a-radio:not([tone]),a-radio-group[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]) a-radio:not([tone-selected]){--_tone-l-rest: .5;--_tone-l-hover: .45;--_tone-l-active: .4;--radio-fill: oklch(from var(--radio-tone-source) var(--_tone-l-rest) c h);--radio-fill-hover: oklch(from var(--radio-tone-source) var(--_tone-l-hover) c h);--radio-fill-active: oklch(from var(--radio-tone-source) var(--_tone-l-active) c h);.dark &{--_tone-l-rest: .45;--_tone-l-hover: .5;--_tone-l-active: .57}}.dark a-radio{--_radio-dot-adjust: 1px;&:after{box-shadow:0 0 1px 1px #000}--radio-border-neutral: #49424c;--radio-border-hover: #635b65;--radio-border-active: #776e77;--radio-fill: #534c57;--radio-fill-hover: #635b65;--radio-fill-active: #938d96;--radio-dot-disabled: #635b65;--radio-bg-disabled: color-mix(in oklch, #e4d1ef 10%, transparent);--radio-border-disabled: color-mix(in oklch, #e4d1ef 15%, transparent)}.dark a-radio[tone=brand],.dark a-radio[tone-selected=brand],.dark a-radio-group[tone=brand] a-radio:not([tone]),.dark a-radio-group[tone-selected=brand] a-radio:not([tone-selected]){--radio-fill: #503cb4;--radio-fill-hover: #5f4bc3;--radio-fill-active: #7460d7}.dark a-radio[tone=neutral],.dark a-radio[tone-selected=neutral],.dark a-radio-group[tone=neutral] a-radio:not([tone]),.dark a-radio-group[tone-selected=neutral] a-radio:not([tone-selected]){--radio-fill: #534c57;--radio-fill-hover: #635b65;--radio-fill-active: #938d96}.dark a-radio[tone=info],.dark a-radio[tone-selected=info],.dark a-radio-group[tone=info] a-radio:not([tone]),.dark a-radio-group[tone-selected=info] a-radio:not([tone-selected]){--radio-fill: #1a5b93;--radio-fill-hover: #1f6eb2;--radio-fill-active: #2686d9}.dark a-radio[tone=success],.dark a-radio[tone-selected=success],.dark a-radio-group[tone=success] a-radio:not([tone]),.dark a-radio-group[tone-selected=success] a-radio:not([tone-selected]){--radio-fill: #226737;--radio-fill-hover: #2a7e43;--radio-fill-active: #329550}.dark a-radio[tone=warning],.dark a-radio[tone-selected=warning],.dark a-radio-group[tone=warning] a-radio:not([tone]),.dark a-radio-group[tone-selected=warning] a-radio:not([tone-selected]){--radio-fill: #7f410b;--radio-fill-hover: #995200;--radio-fill-active: #ae6613}.dark a-radio[tone=critical],.dark a-radio[tone-selected=critical],.dark a-radio-group[tone=critical] a-radio:not([tone]),.dark a-radio-group[tone-selected=critical] a-radio:not([tone-selected]){--radio-fill: #b02120;--radio-fill-hover: #c9302c;--radio-fill-active: #de4545}}
1
+ @layer anta{a-radio{--radio-control-size: 16px;--radio-border-width: 1.5px;--radio-dot-size: 6px;--_radio-dot-adjust: 0px;--radio-gap: 8px;--radio-bg: var(--bg-1);--radio-fill: #878089;--radio-fill-hover: #776e77;--radio-fill-active: #635b65;--radio-border-neutral: #d4ced4;--radio-border: var(--radio-border-neutral);--radio-border-hover: #c1b9c1;--radio-border-active: #9f99a1;--radio-dot: #f1eff1;--radio-dot-disabled: #c1b9c1;--radio-bg-disabled: color-mix(in oklch, #44374b 10%, transparent);--radio-border-disabled: color-mix(in oklch, #44374b 10%, transparent);--radio-label-color: var(--text-2);--radio-label-color-disabled: var(--text-4);--radio-hint-color: var(--text-3);--radio-label-fs: 15px;--radio-label-lh: 20px;--radio-hint-fs: 14px;--radio-hint-lh: 17px;display:inline-grid;grid-template-columns:auto 1fr;align-items:start;column-gap:var(--radio-gap);cursor:pointer;user-select:none;-webkit-user-drag:none;vertical-align:middle;outline:none;color:var(--radio-label-color);font-family:var(--sans-serif);font-size:var(--radio-label-fs);line-height:var(--radio-label-lh);font-feature-settings:"ss02","ss05","tnum";&:before{content:"";grid-column:1;grid-row:1 / -1;align-self:start;margin-block-start:calc((var(--radio-label-lh) - var(--radio-control-size)) / 2);box-sizing:border-box;inline-size:var(--radio-control-size);block-size:var(--radio-control-size);border-radius:50%;background:var(--radio-bg);border:var(--radio-border-width) solid var(--radio-border);transition:background-color .15s ease-out,border-color .15s ease-out}&:after{content:"";grid-column:1;grid-row:1 / -1;align-self:start;justify-self:center;margin-block-start:calc((var(--radio-label-lh) - (var(--radio-dot-size) - var(--_radio-dot-adjust))) / 2);inline-size:calc(var(--radio-dot-size) - var(--_radio-dot-adjust));block-size:calc(var(--radio-dot-size) - var(--_radio-dot-adjust));border-radius:50%;background:var(--radio-dot);transform:scale(0);transition:transform .12s ease-out;pointer-events:none}@media(hover:hover)and (pointer:fine){&:not([disabled]):hover:before{border-color:var(--radio-border-hover);transition:background-color 75ms ease-in,border-color 75ms ease-in}&:not([disabled]):hover:state(selected):before{background:var(--radio-fill-hover);border-color:var(--radio-fill-hover)}}&:not([disabled]):active:before{border-color:var(--radio-border-active);transition:background-color 50ms linear,border-color 50ms linear}&:not([disabled]):active:state(selected):before{background:var(--radio-fill-active);border-color:var(--radio-fill-active)}&:state(selected){&:before{background:var(--radio-fill);border-color:var(--radio-fill)}&:after{transform:scale(1)}}&:focus-visible:before{outline:1px solid var(--focus-ring);outline-offset:1px}&[disabled]{cursor:not-allowed;color:var(--radio-label-color-disabled);&:before{background:var(--radio-bg-disabled);border-color:var(--radio-border-disabled);transition:none}&:state(selected):after{background:var(--radio-dot-disabled)}a-radio-hint{color:var(--radio-label-color-disabled)}}&[size=small]{--radio-control-size: 14px;--radio-dot-size: 5px;--radio-gap: 6px;--radio-label-fs: 13px;--radio-label-lh: 16px;--radio-hint-fs: 12px;--radio-hint-lh: 14px}&[size=large]{--radio-control-size: 18px;--radio-dot-size: 7px;--radio-label-fs: 17px;--radio-label-lh: 22px;--radio-hint-fs: 16px;--radio-hint-lh: 21px}}a-radio-label,a-radio-hint{display:block;grid-column:2;min-width:0}a-radio-hint{color:var(--radio-hint-color);font-size:var(--radio-hint-fs);line-height:var(--radio-hint-lh);margin-block-start:2px}a-radio-group:focus-visible a-radio:state(selected):before{outline:1px solid var(--focus-ring);outline-offset:1px}a-radio[tone=brand],a-radio[tone-selected=brand],a-radio-group[tone=brand] a-radio:not([tone]),a-radio-group[tone-selected=brand] a-radio:not([tone-selected]){--radio-fill: #5f4bc3;--radio-fill-hover: #503cb4;--radio-fill-active: #483493}a-radio[tone=neutral],a-radio[tone-selected=neutral],a-radio-group[tone=neutral] a-radio:not([tone]),a-radio-group[tone-selected=neutral] a-radio:not([tone-selected]){--radio-fill: #878089;--radio-fill-hover: #776e77;--radio-fill-active: #635b65}a-radio[tone=info],a-radio[tone-selected=info],a-radio-group[tone=info] a-radio:not([tone]),a-radio-group[tone-selected=info] a-radio:not([tone-selected]){--radio-fill: #1f6eb2;--radio-fill-hover: #1a5b93;--radio-fill-active: #175082}a-radio[tone=success],a-radio[tone-selected=success],a-radio-group[tone=success] a-radio:not([tone]),a-radio-group[tone-selected=success] a-radio:not([tone-selected]){--radio-fill: #2a7e43;--radio-fill-hover: #226737;--radio-fill-active: #1f5c31}a-radio[tone=warning],a-radio[tone-selected=warning],a-radio-group[tone=warning] a-radio:not([tone]),a-radio-group[tone-selected=warning] a-radio:not([tone-selected]){--radio-fill: #c37416;--radio-fill-hover: #ae6613;--radio-fill-active: #995200}a-radio[tone=critical],a-radio[tone-selected=critical],a-radio-group[tone=critical] a-radio:not([tone]),a-radio-group[tone-selected=critical] a-radio:not([tone-selected]){--radio-fill: #c9302c;--radio-fill-hover: #b02120;--radio-fill-active: #a01c1c}a-radio[tone=brand],a-radio-group[tone=brand] a-radio:not([tone]){--radio-bg: #fcfcfe;--radio-border: #bcb1f1;--radio-border-hover: #9e8eeb;--radio-border-active: #9081df}a-radio[tone=info],a-radio-group[tone=info] a-radio:not([tone]){--radio-bg: #fbfcfe;--radio-border: #93c5ec;--radio-border-hover: #69ace5;--radio-border-active: #56a1e1}a-radio[tone=success],a-radio-group[tone=success] a-radio:not([tone]){--radio-bg: #f7fcf9;--radio-border: #88d7a0;--radio-border-hover: #5bc87b;--radio-border-active: #44c169}a-radio[tone=warning],a-radio-group[tone=warning] a-radio:not([tone]){--radio-bg: #fefbf6;--radio-border: #edb25a;--radio-border-hover: #e09127;--radio-border-active: #d88118}a-radio[tone=critical],a-radio-group[tone=critical] a-radio:not([tone]){--radio-bg: #fefbfb;--radio-border: #efa4a4;--radio-border-hover: #e87f7f;--radio-border-active: #e56c6c}a-radio[tone]:not([tone=""],[tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]),a-radio-group[tone]:not([tone=""],[tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]) a-radio:not([tone]){--radio-bg: oklch(from var(--radio-tone-source) .99 calc(c*.03) h);--_radio-border-base: color-mix(in oklch, var(--radio-fill) 70%, var(--radio-border-neutral));--radio-border: oklch(from var(--_radio-border-base) calc(l + .17) c h);--radio-border-hover: oklch(from var(--_radio-border-base) calc(l + .1) c h);--radio-border-active: var(--_radio-border-base);.dark &{--radio-bg: oklch(from var(--radio-tone-source) .15 calc(c*.18) h);--radio-border: var(--_radio-border-base);--radio-border-hover: oklch(from var(--_radio-border-base) calc(l + .1) c h);--radio-border-active: oklch(from var(--_radio-border-base) calc(l + .17) c h)}}a-radio[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]){--radio-tone-source: attr(tone type(<color>), transparent)}a-radio[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]){--radio-tone-source: attr(tone-selected type(<color>), transparent)}a-radio[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]),a-radio[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]),a-radio-group[tone]:not([tone=""],[tone=brand],[tone=neutral],[tone=info],[tone=success],[tone=warning],[tone=critical]) a-radio:not([tone]),a-radio-group[tone-selected]:not([tone-selected=""],[tone-selected=brand],[tone-selected=neutral],[tone-selected=info],[tone-selected=success],[tone-selected=warning],[tone-selected=critical]) a-radio:not([tone-selected]){--_tone-l-rest: .5;--_tone-l-hover: .45;--_tone-l-active: .4;--radio-fill: oklch(from var(--radio-tone-source) var(--_tone-l-rest) c h);--radio-fill-hover: oklch(from var(--radio-tone-source) var(--_tone-l-hover) c h);--radio-fill-active: oklch(from var(--radio-tone-source) var(--_tone-l-active) c h);.dark &{--_tone-l-rest: .45;--_tone-l-hover: .5;--_tone-l-active: .57}}.dark a-radio{--_radio-dot-adjust: 1px;&:after{box-shadow:0 0 1px 1px color-mix(in oklch,black 40%,transparent)}--radio-border-neutral: #49424c;--radio-border-hover: #635b65;--radio-border-active: #776e77;--radio-fill: #49424c;--radio-fill-hover: #534c57;--radio-fill-active: #635b65;--radio-dot: #d4ced4;--radio-dot-disabled: #534c57;--radio-bg-disabled: color-mix(in oklch, #e4d1ef 10%, transparent);--radio-border-disabled: color-mix(in oklch, #e4d1ef 10%, transparent)}.dark a-radio[tone=brand],.dark a-radio[tone-selected=brand],.dark a-radio-group[tone=brand] a-radio:not([tone]),.dark a-radio-group[tone-selected=brand] a-radio:not([tone-selected]){--radio-fill: #503cb4;--radio-fill-hover: #5f4bc3;--radio-fill-active: #7460d7}.dark a-radio[tone=neutral],.dark a-radio[tone-selected=neutral],.dark a-radio-group[tone=neutral] a-radio:not([tone]),.dark a-radio-group[tone-selected=neutral] a-radio:not([tone-selected]){--radio-fill: #49424c;--radio-fill-hover: #534c57;--radio-fill-active: #635b65}.dark a-radio[tone=info],.dark a-radio[tone-selected=info],.dark a-radio-group[tone=info] a-radio:not([tone]),.dark a-radio-group[tone-selected=info] a-radio:not([tone-selected]){--radio-fill: #1a5b93;--radio-fill-hover: #1f6eb2;--radio-fill-active: #2686d9}.dark a-radio[tone=success],.dark a-radio[tone-selected=success],.dark a-radio-group[tone=success] a-radio:not([tone]),.dark a-radio-group[tone-selected=success] a-radio:not([tone-selected]){--radio-fill: #226737;--radio-fill-hover: #2a7e43;--radio-fill-active: #329550}.dark a-radio[tone=warning],.dark a-radio[tone-selected=warning],.dark a-radio-group[tone=warning] a-radio:not([tone]),.dark a-radio-group[tone-selected=warning] a-radio:not([tone-selected]){--radio-fill: #7f410b;--radio-fill-hover: #995200;--radio-fill-active: #ae6613}.dark a-radio[tone=critical],.dark a-radio[tone-selected=critical],.dark a-radio-group[tone=critical] a-radio:not([tone]),.dark a-radio-group[tone-selected=critical] a-radio:not([tone-selected]){--radio-fill: #b02120;--radio-fill-hover: #c9302c;--radio-fill-active: #de4545}.dark a-radio[tone=brand],.dark a-radio-group[tone=brand] a-radio:not([tone]){--radio-bg: #0b0916;--radio-border: #483493;--radio-border-hover: #5f4bc3;--radio-border-active: #7460d7}.dark a-radio[tone=info],.dark a-radio-group[tone=info] a-radio:not([tone]){--radio-bg: #040d18;--radio-border: #175082;--radio-border-hover: #1f6eb2;--radio-border-active: #2686d9}.dark a-radio[tone=success],.dark a-radio-group[tone=success] a-radio:not([tone]){--radio-bg: #041008;--radio-border: #1f5c31;--radio-border-hover: #2a7e43;--radio-border-active: #329550}.dark a-radio[tone=warning],.dark a-radio-group[tone=warning] a-radio:not([tone]){--radio-bg: #120a03;--radio-border: #6a3b0c;--radio-border-hover: #995200;--radio-border-active: #ae6613}.dark a-radio[tone=critical],.dark a-radio-group[tone=critical] a-radio:not([tone]){--radio-bg: #190405;--radio-border: #a01c1c;--radio-border-hover: #c9302c;--radio-border-active: #de4545}}
@@ -1 +1 @@
1
- @layer anta{a-tabpanel{display:block}a-tabpanel[hidden]{display:none}a-tabpanel[data-hide=visibility]{visibility:hidden}}
1
+ @layer anta{a-tabpanel{display:block}a-tabpanel:not(:state(active)):not([hide-mode=visibility]){display:none}a-tabpanel[hide-mode=visibility]:not(:state(active)){visibility:hidden}}
@@ -0,0 +1,17 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-tabpanel.css";
3
+ export declare class ATabPanelElement extends HTMLElementBase {
4
+ static observedAttributes: string[];
5
+ private internals?;
6
+ private tabs;
7
+ private onTabsChange;
8
+ constructor();
9
+ connectedCallback(): void;
10
+ disconnectedCallback(): void;
11
+ attributeChangedCallback(): void;
12
+ /** Locate the sibling <a-tabs> (the strip and panels are flat siblings under one
13
+ * parent — `Tabs` renders no wrapper) and subscribe to its `change`. */
14
+ private bindTabs;
15
+ private sync;
16
+ }
17
+ export declare function register_a_tabpanel(): void;
@@ -0,0 +1,66 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import "./a-tabpanel.css";
3
+ class ATabPanelElement extends HTMLElementBase {
4
+ static observedAttributes = ["value"];
5
+ internals;
6
+ // The tablist this panel belongs to (its flat sibling under the same parent).
7
+ tabs = null;
8
+ onTabsChange = () => this.sync();
9
+ constructor() {
10
+ super();
11
+ try {
12
+ this.internals = this.attachInternals?.();
13
+ } catch {
14
+ }
15
+ }
16
+ connectedCallback() {
17
+ this.bindTabs();
18
+ this.sync();
19
+ requestAnimationFrame(() => {
20
+ if (!this.tabs) this.bindTabs();
21
+ this.sync();
22
+ });
23
+ }
24
+ disconnectedCallback() {
25
+ this.tabs?.removeEventListener("change", this.onTabsChange);
26
+ this.tabs = null;
27
+ }
28
+ attributeChangedCallback() {
29
+ this.sync();
30
+ }
31
+ /** Locate the sibling <a-tabs> (the strip and panels are flat siblings under one
32
+ * parent — `Tabs` renders no wrapper) and subscribe to its `change`. */
33
+ bindTabs() {
34
+ const tabs = this.parentElement?.querySelector(":scope > a-tabs") ?? null;
35
+ if (tabs === this.tabs) return;
36
+ this.tabs?.removeEventListener("change", this.onTabsChange);
37
+ this.tabs = tabs;
38
+ this.tabs?.addEventListener("change", this.onTabsChange);
39
+ }
40
+ sync() {
41
+ const value = this.getAttribute("value");
42
+ const active = !!this.tabs && value === (this.tabs.value ?? null);
43
+ try {
44
+ if (active) this.internals?.states.add("active");
45
+ else this.internals?.states.delete("active");
46
+ } catch {
47
+ }
48
+ if (this.internals && "ariaLabelledByElements" in this.internals) {
49
+ const tab = value != null ? this.tabs?.querySelector(`a-tab[value="${cssEscape(value)}"]`) : null;
50
+ this.internals.ariaLabelledByElements = tab ? [tab] : [];
51
+ }
52
+ }
53
+ }
54
+ function cssEscape(v) {
55
+ return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(v) : v;
56
+ }
57
+ function register_a_tabpanel() {
58
+ if (typeof customElements === "undefined") return;
59
+ if (!customElements.get("a-tabpanel"))
60
+ customElements.define("a-tabpanel", ATabPanelElement);
61
+ }
62
+ register_a_tabpanel();
63
+ export {
64
+ ATabPanelElement,
65
+ register_a_tabpanel
66
+ };
@@ -30,6 +30,6 @@ export { AMenuSeparatorElement, register_a_menu_separator } from './a-menu-separ
30
30
  export { AMenuGroupElement, register_a_menu_group } from './a-menu-group';
31
31
  export { ATabElement, register_a_tab } from './a-tab';
32
32
  export { ATabsElement, register_a_tabs } from './a-tabs';
33
+ export { ATabPanelElement, register_a_tabpanel } from './a-tabpanel';
33
34
  import './a-title.css';
34
35
  import './a-tag.css';
35
- import './a-tabpanel.css';
@@ -15,9 +15,9 @@ import { AMenuSeparatorElement, register_a_menu_separator } from "./a-menu-separ
15
15
  import { AMenuGroupElement, register_a_menu_group } from "./a-menu-group";
16
16
  import { ATabElement, register_a_tab } from "./a-tab";
17
17
  import { ATabsElement, register_a_tabs } from "./a-tabs";
18
+ import { ATabPanelElement, register_a_tabpanel } from "./a-tabpanel";
18
19
  import "./a-title.css";
19
20
  import "./a-tag.css";
20
- import "./a-tabpanel.css";
21
21
  export {
22
22
  AButtonElement,
23
23
  ACalendarElement,
@@ -33,6 +33,7 @@ export {
33
33
  ARadioElement,
34
34
  ARadioGroupElement,
35
35
  ATabElement,
36
+ ATabPanelElement,
36
37
  ATabsElement,
37
38
  ATextElement,
38
39
  ATooltipElement,
@@ -50,6 +51,7 @@ export {
50
51
  register_a_radio,
51
52
  register_a_radio_group,
52
53
  register_a_tab,
54
+ register_a_tabpanel,
53
55
  register_a_tabs,
54
56
  register_a_text,
55
57
  register_a_tooltip
@@ -572,6 +572,15 @@ export interface AMenuAttributes extends BaseAttributes {
572
572
  next: 'open' | 'closed';
573
573
  prev: 'open' | 'closed';
574
574
  }>) => void;
575
+ /** Combobox-mode cursor report — fired when the active option changes (arrow
576
+ * keys, or the list re-filtering), with `detail.id` the active option's `id`
577
+ * (`null` when none). The element owns the keyboard cursor but must NOT write
578
+ * `aria-activedescendant` on the (light-DOM) filter field itself; the reactive
579
+ * layer that owns the field (e.g. `Select`) listens here and reflects it.
580
+ * All-lowercase so React/Preact bind it to the CustomEvent. */
581
+ onactivedescendant?: (e: CustomEvent<{
582
+ id: string | null;
583
+ }>) => void;
575
584
  /** ARIA role — the JSX wrapper sets this to `'menu'`. */
576
585
  role?: string;
577
586
  'aria-orientation'?: 'vertical' | 'horizontal';
@@ -602,10 +611,20 @@ export interface AMenuItemAttributes extends BaseAttributes {
602
611
  /** ARIA role — `'menuitem'`. */
603
612
  role?: string;
604
613
  'aria-haspopup'?: 'menu' | 'true' | 'false' | boolean;
605
- /** Submenu-parent expanded state. Render `'false'` as the resting baseline;
606
- * the nested `<a-menu>` element reflects the live open state. */
614
+ /** Available for hand-authored markup, but the `MenuItem` wrapper does NOT set
615
+ * it: keeping it in sync would need a light-DOM mutation (desyncs the
616
+ * worker-thread reactive model) or reactive state for one attribute, and a
617
+ * static value would lie once the submenu opens. `aria-haspopup` announces the
618
+ * submenu; the open branch's visual rides the nested `<a-menu>`'s `:state(open)`. */
607
619
  'aria-expanded'?: 'true' | 'false' | boolean;
608
620
  'aria-disabled'?: 'true' | 'false' | boolean;
621
+ /** Fired when this row is genuinely activated (click / Enter / Space). The
622
+ * parent `<a-menu>` owns click delegation and dispatches this — already
623
+ * filtered so it never fires on a submenu parent or from a bubbled child
624
+ * click — so the `MenuItem` wrapper's `onSelect` needs no DOM traversal. It's
625
+ * a `MouseEvent`, carrying the modifier keys (e.g. `altKey`). All-lowercase so
626
+ * React/Preact bind it to the CustomEvent. */
627
+ onmenuselect?: (e: MouseEvent) => void;
609
628
  }
610
629
  /**
611
630
  * Attributes for the `<a-menu-group>` styled element. For the typed JSX
@@ -867,14 +886,15 @@ export interface ATabsAttributes extends BaseAttributes {
867
886
  * from `@antadesign/anta`.
868
887
  */
869
888
  export interface ATabpanelAttributes extends BaseAttributes {
870
- /** Hidden via `display:none`. Presence-based (`''` on, omit off). */
871
- hidden?: boolean | '';
872
- /** Hidden via `visibility:hidden` (keeps the layout box). The `Tabs` wrapper sets
873
- * this for `mounting="visibility"`. */
874
- 'data-hide'?: 'visibility';
875
- /** Removes the hidden panel from focus + the a11y tree. Presence-based. */
876
- inert?: boolean | '';
877
- /** ARIA — set by the `Tabs` wrapper. */
889
+ /** Pairs the panel with the tab of the same value. The element reads its
890
+ * `<a-tabs>` and shows itself when this is the active value. */
891
+ value?: string;
892
+ /** How the panel hides while inactive: omit for `display:none` (removed from
893
+ * layout + the a11y tree), or `'visibility'` to keep its layout box. The
894
+ * element toggles its own `:state(active)`; this only picks the hide style. */
895
+ 'hide-mode'?: 'display' | 'visibility';
896
+ /** ARIA — set statically by the `TabPanel` wrapper. `aria-labelledby` is NOT an
897
+ * attribute here: the element points at its tab off-DOM via
898
+ * `internals.ariaLabelledByElements`. */
878
899
  role?: 'tabpanel';
879
- 'aria-labelledby'?: string;
880
900
  }
package/dist/index.d.ts CHANGED
@@ -53,12 +53,10 @@ export { buildMonth, getWeekdays, firstDayOfWeek, clampDate, isOutOfRange, parse
53
53
  export type { CalendarDay, CalendarWeekday, CalendarMonth, BuildMonthOptions, ParseDateOptions, } from './calendar-core';
54
54
  export { RadioGroup } from './components/RadioGroup';
55
55
  export type { RadioGroupProps, RadioOption } from './components/RadioGroup';
56
- export { Select } from './components/Select';
57
- export type { SelectProps, SelectCommonProps, SelectOption, SelectGroup, SelectSubmenu, SelectItem, SelectChangeAttrs, OptionState, TriggerState, EmptyState, } from './components/Select';
56
+ export { Select, optionsWithSelection } from './components/Select';
57
+ export type { SelectProps, SelectCommonProps, SelectOption, SelectGroup, SelectSubmenu, SelectItem, SelectChangeAttrs, OptionState, TriggerState, EmptyState, SelectionState, SelectedOption, SelectedGroup, SelectedSubmenu, SelectedItem, } from './components/Select';
58
58
  export { Tabs } from './components/Tabs';
59
- export type { TabsProps, TabsMounting, TabsChangeAttrs } from './components/Tabs';
60
- export { Tab } from './components/Tab';
61
- export type { TabProps } from './components/Tab';
59
+ export type { TabsProps, TabOption, TabsChangeAttrs } from './components/Tabs';
62
60
  export { TabPanel } from './components/TabPanel';
63
61
  export type { TabPanelProps } from './components/TabPanel';
64
62
  export type { BaseProps, BaseAttributes } from './general_types';
package/dist/index.js CHANGED
@@ -32,9 +32,8 @@ import {
32
32
  usesHour12
33
33
  } from "./calendar-core";
34
34
  import { RadioGroup } from "./components/RadioGroup";
35
- import { Select } from "./components/Select";
35
+ import { Select, optionsWithSelection } from "./components/Select";
36
36
  import { Tabs } from "./components/Tabs";
37
- import { Tab } from "./components/Tab";
38
37
  import { TabPanel } from "./components/TabPanel";
39
38
  import { configure } from "./jsx-runtime";
40
39
  export {
@@ -54,7 +53,6 @@ export {
54
53
  Progress,
55
54
  RadioGroup,
56
55
  Select,
57
- Tab,
58
56
  TabPanel,
59
57
  Tabs,
60
58
  Tag,
@@ -71,6 +69,7 @@ export {
71
69
  formatDateTimeInput,
72
70
  getWeekdays,
73
71
  isOutOfRange,
72
+ optionsWithSelection,
74
73
  parseDateInput,
75
74
  parseDateTimeInput,
76
75
  parseISODate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antadesign/anta",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",