@antadesign/anta 0.3.34 → 0.3.35

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 (184) hide show
  1. package/LICENSE.md +14 -1
  2. package/README.md +9 -0
  3. package/dist/anta_helpers.d.ts +46 -30
  4. package/dist/anta_helpers.js +66 -25
  5. package/dist/bundle.css +1 -1
  6. package/dist/bundle.js +2 -1333
  7. package/dist/components/Banner.d.ts +25 -40
  8. package/dist/components/Banner.js +15 -17
  9. package/dist/components/Button.js +1 -0
  10. package/dist/components/ButtonCopy.js +3 -2
  11. package/dist/components/Calendar.js +13 -13
  12. package/dist/components/Card.d.ts +9 -17
  13. package/dist/components/Card.js +50 -46
  14. package/dist/components/Dialog.d.ts +1 -0
  15. package/dist/components/Dialog.js +16 -10
  16. package/dist/components/Expander.d.ts +6 -21
  17. package/dist/components/Expander.js +24 -23
  18. package/dist/components/Icon.d.ts +8 -14
  19. package/dist/components/Input.d.ts +77 -96
  20. package/dist/components/Input.js +23 -27
  21. package/dist/components/InputAutocomplete.d.ts +29 -10
  22. package/dist/components/InputAutocomplete.js +25 -8
  23. package/dist/components/InputDate.js +2 -2
  24. package/dist/components/InputTime.d.ts +10 -4
  25. package/dist/components/InputTime.js +20 -16
  26. package/dist/components/Loader.d.ts +2 -2
  27. package/dist/components/Menu.d.ts +5 -4
  28. package/dist/components/Menu.js +13 -8
  29. package/dist/components/MenuItem.js +1 -14
  30. package/dist/components/MenuItemCopy.js +2 -2
  31. package/dist/components/Panel.d.ts +3 -1
  32. package/dist/components/Panel.js +17 -11
  33. package/dist/components/Progress.d.ts +21 -23
  34. package/dist/components/Progress.js +13 -9
  35. package/dist/components/RadioGroup.js +6 -8
  36. package/dist/components/{select-parts.css → Select.css} +1 -1
  37. package/dist/components/Select.d.ts +53 -35
  38. package/dist/components/Select.js +53 -97
  39. package/dist/components/SelectFaceted.d.ts +7 -3
  40. package/dist/components/SelectFaceted.js +21 -9
  41. package/dist/components/Slider.d.ts +28 -11
  42. package/dist/components/Slider.js +20 -20
  43. package/dist/components/Steps.css +1 -1
  44. package/dist/components/Steps.js +1 -11
  45. package/dist/components/Tabs.d.ts +17 -20
  46. package/dist/components/Tabs.js +5 -8
  47. package/dist/components/Text.d.ts +55 -36
  48. package/dist/components/Text.js +35 -22
  49. package/dist/components/Title.d.ts +16 -26
  50. package/dist/components/Title.js +2 -1
  51. package/dist/components/Toaster.js +9 -2
  52. package/dist/components/Tooltip.d.ts +10 -15
  53. package/dist/components/copy-props.d.ts +14 -3
  54. package/dist/components/copy-props.js +14 -3
  55. package/dist/components/select-tree.d.ts +24 -0
  56. package/dist/components/select-tree.js +35 -0
  57. package/dist/element-base.d.ts +10 -0
  58. package/dist/element-base.js +15 -0
  59. package/dist/elements/a-banner.css +1 -1
  60. package/dist/elements/a-banner.d.ts +4 -1
  61. package/dist/elements/a-banner.js +22 -112
  62. package/dist/elements/a-button.d.ts +2 -1
  63. package/dist/elements/a-button.js +18 -1
  64. package/dist/elements/a-calendar.d.ts +14 -13
  65. package/dist/elements/a-calendar.js +29 -24
  66. package/dist/elements/a-card.css +1 -1
  67. package/dist/elements/a-card.d.ts +0 -9
  68. package/dist/elements/a-card.js +0 -184
  69. package/dist/elements/a-checkbox.css +1 -1
  70. package/dist/elements/a-copy.css +1 -1
  71. package/dist/elements/a-copy.d.ts +1 -5
  72. package/dist/elements/a-copy.js +129 -108
  73. package/dist/elements/a-dialog.css +1 -1
  74. package/dist/elements/a-dialog.d.ts +1 -1
  75. package/dist/elements/a-dialog.js +66 -194
  76. package/dist/elements/a-expander.css +1 -1
  77. package/dist/elements/a-expander.d.ts +4 -30
  78. package/dist/elements/a-expander.js +85 -344
  79. package/dist/elements/a-icon.d.ts +5 -23
  80. package/dist/elements/a-icon.js +1 -2
  81. package/dist/elements/a-input-time.css +1 -1
  82. package/dist/elements/a-input-time.d.ts +2 -1
  83. package/dist/elements/a-input-time.js +101 -168
  84. package/dist/elements/a-input.css +1 -1
  85. package/dist/elements/a-input.d.ts +10 -8
  86. package/dist/elements/a-input.js +155 -230
  87. package/dist/elements/a-loader.d.ts +5 -8
  88. package/dist/elements/a-loader.js +1 -2
  89. package/dist/elements/a-menu-item.css +1 -1
  90. package/dist/elements/a-menu-item.d.ts +5 -3
  91. package/dist/elements/a-menu-item.js +5 -1
  92. package/dist/elements/a-menu.css +1 -1
  93. package/dist/elements/a-menu.d.ts +5 -7
  94. package/dist/elements/a-menu.js +383 -299
  95. package/dist/elements/a-panel.css +1 -1
  96. package/dist/elements/a-panel.js +19 -37
  97. package/dist/elements/a-progress.css +1 -1
  98. package/dist/elements/a-progress.d.ts +0 -22
  99. package/dist/elements/a-progress.js +0 -100
  100. package/dist/elements/a-radio-group.d.ts +1 -1
  101. package/dist/elements/a-radio-group.js +6 -4
  102. package/dist/elements/a-select-faceted.d.ts +1 -1
  103. package/dist/elements/a-select-faceted.js +1 -1
  104. package/dist/elements/a-select.d.ts +1 -1
  105. package/dist/elements/a-select.js +1 -1
  106. package/dist/elements/a-slider.css +1 -1
  107. package/dist/elements/a-slider.d.ts +11 -3
  108. package/dist/elements/a-slider.js +244 -242
  109. package/dist/elements/a-switch.css +1 -1
  110. package/dist/elements/a-tab.css +1 -1
  111. package/dist/elements/a-tabs.css +1 -1
  112. package/dist/elements/a-tabs.d.ts +1 -1
  113. package/dist/elements/a-tabs.js +6 -4
  114. package/dist/elements/a-tag.css +1 -1
  115. package/dist/elements/a-text.css +1 -1
  116. package/dist/elements/a-text.d.ts +6 -43
  117. package/dist/elements/a-text.js +110 -193
  118. package/dist/elements/a-title.css +1 -1
  119. package/dist/elements/a-toast.css +1 -1
  120. package/dist/elements/a-toast.d.ts +10 -45
  121. package/dist/elements/a-toast.js +80 -66
  122. package/dist/elements/a-toaster.css +1 -1
  123. package/dist/elements/a-toaster.js +9 -44
  124. package/dist/elements/a-tooltip.css +1 -1
  125. package/dist/elements/a-tooltip.d.ts +1 -1
  126. package/dist/elements/a-tooltip.js +142 -136
  127. package/dist/elements/copy-behavior.d.ts +1 -1
  128. package/dist/elements/copy-behavior.js +21 -21
  129. package/dist/elements/index.d.ts +3 -3
  130. package/dist/elements/index.js +3 -7
  131. package/dist/elements/input-focus-modality.d.ts +15 -0
  132. package/dist/elements/input-focus-modality.js +44 -0
  133. package/dist/elements/input-trailing-actions.d.ts +5 -0
  134. package/dist/elements/input-trailing-actions.js +38 -0
  135. package/dist/elements/shadow-aria.d.ts +2 -0
  136. package/dist/elements/shadow-aria.js +2 -1
  137. package/dist/elements/value-input.d.ts +3 -0
  138. package/dist/elements/value-input.js +12 -0
  139. package/dist/general_types.d.ts +103 -96
  140. package/dist/index.d.ts +1 -1
  141. package/dist/input-events.d.ts +4 -0
  142. package/dist/input-events.js +15 -0
  143. package/dist/jsx-runtime.d.ts +70 -23
  144. package/dist/jsx-runtime.js +26 -19
  145. package/dist/native-event-props.d.ts +2 -0
  146. package/dist/native-event-props.js +10 -0
  147. package/dist/reset.css +1 -1
  148. package/dist/theme-antithesis.css +10 -1
  149. package/dist/theme-antune.css +1 -1
  150. package/docs/accessibility.md +17 -3
  151. package/docs/changelog.md +348 -2
  152. package/docs/comparison.md +2 -2
  153. package/docs/components/avatar.md +5 -0
  154. package/docs/components/banner.md +117 -187
  155. package/docs/components/button.md +22 -0
  156. package/docs/components/card.md +74 -28
  157. package/docs/components/checkbox.md +9 -10
  158. package/docs/components/dialog.md +83 -32
  159. package/docs/components/expander.md +198 -435
  160. package/docs/components/icon.md +11 -17
  161. package/docs/components/input-autocomplete.md +152 -52
  162. package/docs/components/input-date.md +175 -148
  163. package/docs/components/input-time.md +81 -38
  164. package/docs/components/input.md +343 -349
  165. package/docs/components/loader.md +2 -1
  166. package/docs/components/menu.md +35 -6
  167. package/docs/components/panel.md +18 -0
  168. package/docs/components/progress.md +83 -49
  169. package/docs/components/radio.md +39 -43
  170. package/docs/components/select-faceted.md +54 -7
  171. package/docs/components/select.md +390 -386
  172. package/docs/components/slider.md +136 -19
  173. package/docs/components/steps.md +2 -1
  174. package/docs/components/switch.md +8 -9
  175. package/docs/components/tabs.md +110 -104
  176. package/docs/components/text.md +110 -86
  177. package/docs/components/title.md +47 -33
  178. package/docs/components/toaster.md +242 -84
  179. package/docs/components/tooltip.md +204 -113
  180. package/docs/install-config.md +183 -26
  181. package/docs/packages/plot.md +170 -148
  182. package/docs/theming.md +7 -0
  183. package/package.json +41 -3
  184. package/theme-antithesis-license.md +66 -0
@@ -1,10 +1,8 @@
1
1
  import type { BaseProps } from "../general_types";
2
2
  export interface TooltipProps extends BaseProps {
3
- /** Tooltip content. Renders anything — text, markup, an icon + text.
4
- * Lives in the light DOM, so it's styleable with your own plain CSS. */
3
+ /** Supplementary text or markup. Content stays in light DOM for application CSS. */
5
4
  children?: React.ReactNode;
6
- /** Show delay in milliseconds after hover / focus. Never use `0` — use
7
- * ~`50` for a near-instant tooltip (0 has caused issues in practice).
5
+ /** Wait in milliseconds after hover or keyboard focus. Use `50` for a short delay.
8
6
  * @defaultValue 300 */
9
7
  delay?: number;
10
8
  /** Which side of the anchor the bubble prefers. Auto-flips to the other
@@ -18,10 +16,9 @@ export interface TooltipProps extends BaseProps {
18
16
  * pinned (anchored beneath the target) by default; pass `follow` for the
19
17
  * cursor-tracking behavior, which fades by distance as the cursor leaves. */
20
18
  follow?: boolean;
21
- /** Make the bubble hoverable and clickable — enables pointer events and
22
- * keeps it open while the cursor is over it, so its content (links,
23
- * buttons) can be interacted with. Always pinned (an interactive bubble
24
- * can't follow the cursor, even with `follow`). */
19
+ /** Keep content available while the pointer or keyboard focus is inside it.
20
+ * Enables pointer events and pins the bubble even with `follow`. Essential
21
+ * actions belong in a Menu or Dialog with an explicit trigger. */
25
22
  interactive?: boolean;
26
23
  /** Only show when the target is actually truncated (its text overflows and is
27
24
  * ellipsized); a label that fits gets no tooltip. The check is a UI-thread
@@ -49,13 +46,12 @@ export interface TooltipProps extends BaseProps {
49
46
  *
50
47
  * @example Basic usage
51
48
  * ```tsx
52
- * import { Tooltip } from '@antadesign/anta'
49
+ * import { Tooltip, Button } from '@antadesign/anta'
53
50
  * import '@antadesign/anta/elements'
54
51
  *
55
- * <button>
56
- * Save
52
+ * <Button label="Save">
57
53
  * <Tooltip>Saves your work</Tooltip>
58
- * </button>
54
+ * </Button>
59
55
  * ```
60
56
  *
61
57
  * @example Above the anchor, rich content
@@ -70,10 +66,9 @@ export interface TooltipProps extends BaseProps {
70
66
  *
71
67
  * @example Follow the cursor
72
68
  * ```tsx
73
- * <button>
74
- * Hover me
69
+ * <Button label="Hover me">
75
70
  * <Tooltip follow>Tracks the pointer</Tooltip>
76
- * </button>
71
+ * </Button>
77
72
  * ```
78
73
  */
79
74
  export declare const Tooltip: ({ delay, placement, round, follow, interactive, truncatedOnly, truncatedSelector, className, style, children, ...rest }: TooltipProps) => any;
@@ -2,7 +2,7 @@
2
2
  * The copy target for a copy preset (`ButtonCopy` / `MenuItemCopy`) — exactly
3
3
  * one of a literal string, a DOM region, or the page URL. The write itself is
4
4
  * performed by the slotted `<a-copy>` element; these props map to its attributes
5
- * via `copyElementProps`. `copyWithUrl` / `onCopyRequest` are only meaningful for
5
+ * via `useCopyElementProps`. `copyWithUrl` / `onCopyRequest` are only meaningful for
6
6
  * the string target, so the union pins them `never` elsewhere.
7
7
  */
8
8
  export type CopyTarget = {
@@ -40,7 +40,7 @@ export type CopyTarget = {
40
40
  copyWithUrl?: never;
41
41
  onCopyRequest?: never;
42
42
  };
43
- /** Everything `copyElementProps` reads: the target plus the resolved `onCopied`
43
+ /** Everything `useCopyElementProps` reads: the target plus the resolved `onCopied`
44
44
  * (the preset passes its feedback-wrapped handler) and the `toast` opt-in. */
45
45
  type CopyElementInput = {
46
46
  copy?: string;
@@ -63,7 +63,18 @@ export declare function copyElementProps(p: CopyElementInput): {
63
63
  readonly toast: "" | undefined;
64
64
  readonly 'copied-label': string | undefined;
65
65
  readonly oncopydone: ((e: any) => void) | undefined;
66
- readonly oncopyrequest: (() => void) | undefined;
66
+ readonly oncopyrequest: ((e: any) => void) | undefined;
67
+ };
68
+ /** @internal Apply the shared browser hydration option to copy event props. */
69
+ export declare function useCopyElementProps(p: CopyElementInput): {
70
+ copy: string | undefined;
71
+ 'copy-node': string | undefined;
72
+ 'copy-url': "" | undefined;
73
+ 'copy-with-url': "" | undefined;
74
+ toast: "" | undefined;
75
+ 'copied-label': string | undefined;
76
+ oncopydone?: ((e: any) => void) | undefined;
77
+ oncopyrequest?: ((e: any) => void) | undefined;
67
78
  };
68
79
  /** True when these props copy a DOM node — the host control must then be marked
69
80
  * `data-copy-node-button` so the serializer strips it from the copied region. */
@@ -1,4 +1,5 @@
1
1
  import { nativeStateChange } from "../anta_helpers";
2
+ import { useNativeEventProps } from "../native-event-props";
2
3
  function copyElementProps(p) {
3
4
  const { copy, copyNode, copyUrl, copyWithUrl, onCopyRequest, onCopied, toast, copiedLabel } = p;
4
5
  return {
@@ -8,14 +9,24 @@ function copyElementProps(p) {
8
9
  "copy-with-url": copyWithUrl ? "" : void 0,
9
10
  toast: toast ? "" : void 0,
10
11
  "copied-label": copiedLabel,
11
- oncopydone: onCopied ? (e) => onCopied(nativeStateChange(e).detail?.ok ?? false) : void 0,
12
- oncopyrequest: onCopyRequest ? () => onCopyRequest() : void 0
12
+ oncopydone: onCopied ? (e) => {
13
+ const result = nativeStateChange(e);
14
+ if (result.isOwn) onCopied(result.detail?.ok ?? false);
15
+ } : void 0,
16
+ oncopyrequest: onCopyRequest ? (e) => {
17
+ if (nativeStateChange(e).isOwn) onCopyRequest();
18
+ } : void 0
13
19
  };
14
20
  }
21
+ function useCopyElementProps(p) {
22
+ const { oncopydone, oncopyrequest, ...attributes } = copyElementProps(p);
23
+ return { ...attributes, ...useNativeEventProps({ oncopydone, oncopyrequest }) };
24
+ }
15
25
  function isNodeCopy(p) {
16
26
  return p.copyNode != null && p.copyNode !== false;
17
27
  }
18
28
  export {
19
29
  copyElementProps,
20
- isNodeCopy
30
+ isNodeCopy,
31
+ useCopyElementProps
21
32
  };
@@ -0,0 +1,24 @@
1
+ import type { OptionValue, SelectGroup, SelectItem, SelectOption, SelectSubmenu } from './Select';
2
+ interface Leaf<V extends OptionValue> {
3
+ kind: 'option';
4
+ opt: SelectOption<V>;
5
+ disabled: boolean;
6
+ group?: string;
7
+ }
8
+ export type SelectNode<V extends OptionValue> = Leaf<V> | ({
9
+ key: string;
10
+ disabled: boolean;
11
+ children: SelectNode<V>[];
12
+ } & ({
13
+ kind: 'group';
14
+ source: SelectGroup<V>;
15
+ } | {
16
+ kind: 'submenu';
17
+ source: SelectSubmenu<V>;
18
+ }));
19
+ export declare function prepareSelectOptions<V extends OptionValue>(items: SelectItem<V>[], matches?: (option: SelectOption<V>) => boolean): {
20
+ tree: SelectNode<V>[];
21
+ allLeaves: Leaf<V>[];
22
+ visibleLeaves: Leaf<V>[];
23
+ };
24
+ export {};
@@ -0,0 +1,35 @@
1
+ import { normalizeOpt } from "./select-options";
2
+ function prepareSelectOptions(items, matches) {
3
+ const allLeaves = [];
4
+ const visibleLeaves = [];
5
+ const walk = (items2, group, disabled = false) => {
6
+ const nodes = [];
7
+ items2.forEach((raw, index) => {
8
+ if (typeof raw !== "string" && Array.isArray(raw.submenu)) {
9
+ const source = raw;
10
+ const effectiveDisabled = disabled || !!source.disabled;
11
+ const children = walk(source.submenu, source.label, effectiveDisabled);
12
+ if (children.length) nodes.push({ kind: "submenu", source, disabled: effectiveDisabled, children, key: `sub-${index}-${source.label}` });
13
+ } else if (typeof raw !== "string" && Array.isArray(raw.options)) {
14
+ const source = raw;
15
+ const effectiveDisabled = disabled || !!source.disabled;
16
+ const children = walk(source.options, source.label, effectiveDisabled);
17
+ if (children.length) nodes.push({ kind: "group", source, disabled: effectiveDisabled, children, key: `grp-${index}-${source.label}` });
18
+ } else {
19
+ const opt = normalizeOpt(raw);
20
+ const leaf = { kind: "option", opt, disabled: disabled || !!opt.disabled, group };
21
+ allLeaves.push(leaf);
22
+ if (!matches || matches(opt)) {
23
+ visibleLeaves.push(leaf);
24
+ nodes.push(leaf);
25
+ }
26
+ }
27
+ });
28
+ return nodes;
29
+ };
30
+ const tree = walk(items);
31
+ return { tree, allLeaves, visibleLeaves };
32
+ }
33
+ export {
34
+ prepareSelectOptions
35
+ };
@@ -0,0 +1,10 @@
1
+ /** Use a harmless base when importing element classes without a browser DOM. */
2
+ declare const NativeHTMLElement: typeof HTMLElement;
3
+ /** Base for custom elements that can live in a different document or iframe. */
4
+ export declare class HTMLElementBase extends NativeHTMLElement {
5
+ /** This element's own window, including when rendered in an iframe. */
6
+ protected get view(): Window & typeof globalThis;
7
+ /** This element's own document. */
8
+ protected get doc(): Document;
9
+ }
10
+ export {};
@@ -0,0 +1,15 @@
1
+ const NativeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
2
+ };
3
+ class HTMLElementBase extends NativeHTMLElement {
4
+ /** This element's own window, including when rendered in an iframe. */
5
+ get view() {
6
+ return this.ownerDocument?.defaultView ?? window;
7
+ }
8
+ /** This element's own document. */
9
+ get doc() {
10
+ return this.ownerDocument ?? document;
11
+ }
12
+ }
13
+ export {
14
+ HTMLElementBase
15
+ };
@@ -1 +1 @@
1
- @layer base,anta,components,utilities;@layer anta.reset,anta.components,anta.theme;@layer anta.components{a-banner{--banner-bg: var(--bg-4);--banner-border: var(--border-4);--banner-text: var(--text-2);display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:4px 1ch;position:relative;box-sizing:border-box;width:100%;min-height:40px;padding-block:6px;padding-inline:16px 48px;color:var(--banner-text);background:var(--banner-bg);border:0 solid var(--banner-border);border-radius:0;overflow:clip;overflow-clip-margin:3px;interpolate-size:allow-keywords;transition:opacity .16s ease,block-size .2s ease,padding-block .2s ease,border-block-width .2s ease,display .2s allow-discrete}a-banner[align=center]{justify-content:center;padding-inline:48px}a-banner[round]{border-radius:var(--banner-round, 999px);@supports (border-radius: attr(probe type(<length>),1px)){--banner-round: attr(round type(<length>))}}a-banner [slot=close] a-button,a-banner a-button[slot=close]{height:100%;width:40px}a-banner-message{font-size:14px;line-height:20px;font-weight:475;text-align:start}a-banner[align=center] a-banner-message{text-align:center}a-banner[tone=brand]{--banner-bg: var(--bg-4-brand);--banner-border: var(--border-4-brand);--banner-text: var(--text-2-brand)}a-banner[tone=info]{--banner-bg: var(--bg-4-info);--banner-border: var(--border-4-info);--banner-text: var(--text-2-info)}a-banner[tone=success]{--banner-bg: var(--bg-4-success);--banner-border: var(--border-4-success);--banner-text: var(--text-2-success)}a-banner[tone=warning]{--banner-bg: var(--bg-4-warning);--banner-border: var(--border-4-warning);--banner-text: var(--text-2-warning)}a-banner[tone=critical]{--banner-bg: var(--bg-4-critical);--banner-border: var(--border-4-critical);--banner-text: var(--text-2-critical)}a-banner[tone]:not([tone=""],[tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--banner-tone-source: currentColor;--banner-text: light-dark( oklch(from var(--banner-tone-source) .5 .125 h), oklch(from var(--banner-tone-source) .77 .11 h) );--banner-bg: light-dark( oklch(from var(--banner-tone-source) .955 .02 h), oklch(from var(--banner-tone-source) .185 .05 h) );--banner-border: light-dark( oklch(from var(--banner-tone-source) .9 .04 h), oklch(from var(--banner-tone-source) .26 .06 h) );@supports (color: attr(probe type(<color>),red)){--banner-tone-source: attr(tone type(<color>), currentColor)}}a-banner:not(:defined){opacity:0}a-banner[state=closed],a-banner[default-state=closed],a-banner:state(closed){display:none;block-size:0;min-block-size:0;padding-block:0;border-block-width:0;opacity:0}@media(forced-colors:active){a-banner{border-width:1px;border-color:CanvasText}}@media(prefers-reduced-motion:reduce){a-banner{transition:none}}}
1
+ @layer base,anta,components,utilities;@layer anta.reset,anta.components,anta.theme;@layer anta.components{a-banner{--banner-bg: var(--bg-4);--banner-border: var(--border-4);--banner-text: var(--text-2);--banner-round: initial;--banner-tone-source: initial;display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:4px 1ch;position:relative;box-sizing:border-box;width:100%;min-height:40px;padding-block:6px;padding-inline:16px 48px;color:var(--banner-text);background:var(--banner-bg);border:0 solid var(--banner-border);border-radius:0;overflow:clip;overflow-clip-margin:3px;interpolate-size:allow-keywords;transition:opacity .16s ease,block-size .2s ease,padding-block .2s ease,border-block-width .2s ease,display .2s allow-discrete}a-banner[align=center]{justify-content:center;padding-inline:48px}a-banner[round]{border-radius:var(--banner-round, 999px);@supports (border-radius: attr(probe type(<length>),1px)){--banner-round: attr(round type(<length>))}}a-banner>a-banner-content{display:contents}a-banner>a-banner-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}a-banner>a-banner-actions:empty{display:none}a-banner>a-banner-message:empty,a-banner>:is(a-banner-message,a-banner-content,a-banner-actions,a-banner-close)[hidden]{display:none}a-banner>a-banner-close{position:absolute;inset-inline-end:0;inset-block:0;display:grid;place-items:center;opacity:.65;transition:opacity .15s ease}a-banner>a-banner-close:hover,a-banner>a-banner-close:focus-within{opacity:1}a-banner>a-banner-close>a-button{height:100%;width:40px}a-banner-message{font-size:14px;line-height:20px;font-weight:475;text-align:start}a-banner[align=center]>a-banner-message{text-align:center}a-banner[tone=brand]{--banner-bg: var(--bg-4-brand);--banner-border: var(--border-4-brand);--banner-text: var(--text-2-brand)}a-banner[tone=info]{--banner-bg: var(--bg-4-info);--banner-border: var(--border-4-info);--banner-text: var(--text-2-info)}a-banner[tone=success]{--banner-bg: var(--bg-4-success);--banner-border: var(--border-4-success);--banner-text: var(--text-2-success)}a-banner[tone=warning]{--banner-bg: var(--bg-4-warning);--banner-border: var(--border-4-warning);--banner-text: var(--text-2-warning)}a-banner[tone=critical]{--banner-bg: var(--bg-4-critical);--banner-border: var(--border-4-critical);--banner-text: var(--text-2-critical)}a-banner[tone]:not([tone=""],[tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--banner-tone-source: currentColor;--banner-text: light-dark( oklch(from var(--banner-tone-source) .5 .125 h), oklch(from var(--banner-tone-source) .77 .11 h) );--banner-bg: light-dark( oklch(from var(--banner-tone-source) .955 .02 h), oklch(from var(--banner-tone-source) .185 .05 h) );--banner-border: light-dark( oklch(from var(--banner-tone-source) .9 .04 h), oklch(from var(--banner-tone-source) .26 .06 h) );@supports (color: attr(probe type(<color>),red)){--banner-tone-source: attr(tone type(<color>), currentColor)}}a-banner[state]:not([state=open]),a-banner:not([state])[default-state=closed]:not(:state(open)),a-banner:not([state]):state(closed){display:none;block-size:0;min-block-size:0;padding-block:0;border-block-width:0;opacity:0}@media(forced-colors:active){a-banner{border-width:1px;border-color:CanvasText}}@media(prefers-reduced-motion:reduce){a-banner,a-banner>a-banner-close{transition:none}}}
@@ -1,10 +1,13 @@
1
1
  import { HTMLElementBase } from '../anta_helpers';
2
2
  import './a-banner.css';
3
+ /** Light-DOM notice with declarative regions and external styling. The element
4
+ * owns dismissal through ElementInternals, without changing
5
+ * application-owned markup, attributes, styles, or ARIA. */
3
6
  export declare class ABannerElement extends HTMLElementBase {
4
7
  #private;
5
8
  static observedAttributes: string[];
6
9
  constructor();
7
10
  connectedCallback(): void;
8
- attributeChangedCallback(name: string): void;
11
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
9
12
  }
10
13
  export declare function register_a_banner(): void;
@@ -1,145 +1,55 @@
1
1
  import { HTMLElementBase, installDismissTrigger, parseOpenState } from "../anta_helpers";
2
2
  import "./a-banner.css";
3
- const DISMISS_TRIGGER = "dismissrequest";
4
- const DISMISS_ATTR = "data-banner-dismiss";
5
- const SHADOW_STYLE = `
6
- slot[name="message"], slot:not([name]) { display: contents; }
7
-
8
- slot[name="actions"] {
9
- display: flex;
10
- align-items: center;
11
- gap: 4px;
12
- flex-shrink: 0;
13
- }
14
- slot[name="actions"][hidden] { display: none; }
15
-
16
- slot[name="close"] {
17
- position: absolute;
18
- inset-inline-end: 0;
19
- inset-block: 0;
20
- display: grid;
21
- place-items: center;
22
- opacity: 0.65;
23
- transition: opacity 150ms ease;
24
- }
25
- slot[name="close"]:hover,
26
- slot[name="close"]:focus-within {
27
- opacity: 1;
28
- }
29
- `;
30
- const parseState = parseOpenState;
31
3
  class ABannerElement extends HTMLElementBase {
32
4
  static observedAttributes = ["state"];
33
- // Visibility is held here as a custom state (`:state(open)` / `:state(closed)`),
34
- // NOT a host attribute — declarative-DOM safe (no host mutation, no reconcile
35
- // churn). This is the ONLY declarative-safe self-hide channel, so a runtime
36
- // (uncontrolled) ✕ dismiss needs CustomStateSet + the CSS `:state()` selector
37
- // (Chrome 90+, Safari 17.4+, Firefox 126+); below that the ✕ can't self-hide (an
38
- // attribute fallback would violate the no-host-mutation rule). Controlled mode
39
- // degrades everywhere — the `state="closed"` attribute the wrapper sets has the
40
- // `[state="closed"]` CSS fallback, which needs no custom-state support.
41
5
  #internals;
42
- // `default-state` seeds the uncontrolled intent ONCE, on the first connect; a
43
- // re-parent (disconnect → reconnect) then keeps the applied state (which lives
44
- // off-DOM in #internals and survives the move) instead of re-seeding `open` over a
45
- // banner the user already dismissed. Mirrors a-dialog's #seeded.
46
6
  #seeded = false;
47
7
  constructor() {
48
8
  super();
49
- this.#internals = typeof this.attachInternals === "function" ? this.attachInternals() : void 0;
50
- const shadow = this.attachShadow({ mode: "open" });
51
- const style = document.createElement("style");
52
- style.textContent = SHADOW_STYLE;
53
- const messageSlot = document.createElement("slot");
54
- messageSlot.name = "message";
55
- messageSlot.setAttribute("part", "message");
56
- const contentSlot = document.createElement("slot");
57
- contentSlot.setAttribute("part", "content");
58
- const actionsSlot = document.createElement("slot");
59
- actionsSlot.name = "actions";
60
- actionsSlot.setAttribute("part", "actions");
61
- const syncActionsSlot = () => {
62
- actionsSlot.hidden = !actionsSlot.assignedNodes({ flatten: true }).some((node) => {
63
- if (node.nodeType === Node.TEXT_NODE) return node.textContent?.trim() !== "";
64
- if (node.nodeType !== Node.ELEMENT_NODE) return false;
65
- const element = node;
66
- return !element.hasAttribute("data-anta-banner-actions-wrapper") || !element.matches(":empty");
67
- });
68
- };
69
- actionsSlot.hidden = true;
70
- actionsSlot.addEventListener("slotchange", syncActionsSlot);
71
- const closeSlot = document.createElement("slot");
72
- closeSlot.name = "close";
73
- closeSlot.setAttribute("part", "close");
74
- shadow.append(style, messageSlot, contentSlot, actionsSlot, closeSlot);
75
- syncActionsSlot();
76
- this.addEventListener(DISMISS_TRIGGER, (e) => {
77
- e.stopPropagation();
9
+ this.#internals = this.attachInternals();
10
+ this.addEventListener("dismissrequest", (event) => {
11
+ const banner = event.composedPath().find((node) => node.nodeType === 1 && node.localName === "a-banner");
12
+ if (banner !== this) return;
13
+ event.stopPropagation();
78
14
  this.#requestDismiss();
79
15
  });
80
- installDismissTrigger(this, DISMISS_ATTR, () => this.#requestDismiss());
16
+ installDismissTrigger(this, "data-banner-dismiss", () => this.#requestDismiss());
81
17
  }
82
- /** Controlled mode: the `state` attribute is present and owns visibility. */
83
18
  get #controlled() {
84
19
  return this.hasAttribute("state");
85
20
  }
86
- /** The currently *applied* state (read from the custom state — the source of
87
- * truth for what's painted). Reads open until the element has applied a state.
88
- * Both `?.` guard engines with ElementInternals but no CustomStateSet (`.states`
89
- * undefined), where reading it would otherwise throw inside the ✕ click. */
90
21
  get #current() {
91
- return this.#internals?.states?.has("closed") ? "closed" : "open";
22
+ return this.#internals.states.has("closed") ? "closed" : "open";
92
23
  }
93
24
  connectedCallback() {
94
25
  if (this.#controlled) {
95
- this.#applyState(parseState(this.getAttribute("state")));
26
+ this.#applyState(parseOpenState(this.getAttribute("state")));
96
27
  } else if (!this.#seeded) {
97
- this.#applyState(this.#seedState());
98
- this.#seeded = true;
28
+ this.#applyState(this.getAttribute("default-state") === "closed" ? "closed" : "open");
99
29
  }
30
+ this.#seeded = true;
100
31
  }
101
- /** The uncontrolled initial state. A banner is shown by DEFAULT, so `open` unless
102
- * `default-state="closed"` is explicit. (Unlike a-dialog / a-expander, whose
103
- * absent default-state means closed — a banner is the inverse, so it can't share
104
- * `parseState` here. A hand-authored `<a-banner>` with no attributes shows.) */
105
- #seedState() {
106
- return this.getAttribute("default-state") === "closed" ? "closed" : "open";
32
+ attributeChangedCallback(name, oldValue, newValue) {
33
+ if (oldValue === newValue) return;
34
+ if (name === "state" && this.#controlled) this.#applyState(parseOpenState(newValue));
107
35
  }
108
- attributeChangedCallback(name) {
109
- if (name === "state" && this.#controlled) this.#applyState(parseState(this.getAttribute("state")));
110
- }
111
- /** Mirror the state to `:state(open)` / `:state(closed)` (idempotent). The
112
- * external sheet collapses the host on `:state(closed)`. Off-DOM only. */
113
36
  #applyState(state) {
114
- if (!this.#internals) return;
115
- try {
116
- if (state === "closed") {
117
- this.#internals?.states?.add("closed");
118
- this.#internals?.states?.delete("open");
119
- } else {
120
- this.#internals?.states?.add("open");
121
- this.#internals?.states?.delete("closed");
122
- }
123
- } catch {
124
- }
37
+ this.#internals.states.delete(state === "closed" ? "open" : "closed");
38
+ this.#internals.states.add(state);
125
39
  }
126
- /** Announce a requested dismiss (cancelable, before applying), then — uncontrolled
127
- * and un-vetoed — apply it. Controlled: never self-apply; the consumer answers
128
- * via `state`. See STATEFUL-COMPONENTS.md. */
129
40
  #requestDismiss() {
130
41
  if (this.#current === "closed") return;
131
- const ok = this.dispatchEvent(
132
- new CustomEvent("statechange", { cancelable: true, detail: { next: "closed", prev: "open" } })
133
- );
134
- if (this.#controlled) return;
135
- if (ok) this.#applyState("closed");
42
+ const controlled = this.#controlled;
43
+ const accepted = this.dispatchEvent(new this.view.CustomEvent("statechange", {
44
+ cancelable: true,
45
+ detail: { next: "closed", prev: "open" }
46
+ }));
47
+ if (!controlled && !this.#controlled && accepted) this.#applyState("closed");
136
48
  }
137
49
  }
138
50
  function register_a_banner() {
139
51
  if (typeof customElements === "undefined") return;
140
- if (!customElements.get("a-banner")) {
141
- customElements.define("a-banner", ABannerElement);
142
- }
52
+ if (!customElements.get("a-banner")) customElements.define("a-banner", ABannerElement);
143
53
  }
144
54
  register_a_banner();
145
55
  export {
@@ -1,4 +1,4 @@
1
- import { HTMLElementBase, SYNC_POPUP_ARIA, type PopupAriaRelations } from "../anta_helpers";
1
+ import { HTMLElementBase, SYNC_POPUP_ARIA, type PopupAriaRelations, SYNC_TEXT_TOGGLE, type TextToggleAria } from "../anta_helpers";
2
2
  import "./a-button.css";
3
3
  declare global {
4
4
  interface Document {
@@ -73,6 +73,7 @@ export declare class AButtonElement extends HTMLElementBase {
73
73
  connectedCallback(): void;
74
74
  disconnectedCallback(): void;
75
75
  attributeChangedCallback(): void;
76
+ [SYNC_TEXT_TOGGLE](state: TextToggleAria): void;
76
77
  [SYNC_POPUP_ARIA](relations: PopupAriaRelations): void;
77
78
  }
78
79
  export declare function register_a_button(): void;
@@ -1,4 +1,4 @@
1
- import { HTMLElementBase, SYNC_POPUP_ARIA } from "../anta_helpers";
1
+ import { HTMLElementBase, SYNC_POPUP_ARIA, SYNC_TEXT_TOGGLE } from "../anta_helpers";
2
2
  import { installKeyActivation } from "./key-activation";
3
3
  import "./a-button.css";
4
4
  function installDocumentHandlers(doc) {
@@ -18,6 +18,7 @@ class AButtonElement extends HTMLElementBase {
18
18
  popupAriaSource;
19
19
  #fieldObserver;
20
20
  #field;
21
+ #textToggleSource;
21
22
  constructor() {
22
23
  super();
23
24
  try {
@@ -27,6 +28,8 @@ class AButtonElement extends HTMLElementBase {
27
28
  }
28
29
  connectedCallback() {
29
30
  installDocumentHandlers(this.doc);
31
+ if (this.parentElement?.localName === "a-text")
32
+ this.dispatchEvent(new Event("anta-text-control-ready", { bubbles: true }));
30
33
  if (this.parentElement?.localName === "a-select-field") {
31
34
  this.#field = this.parentElement;
32
35
  this.#fieldObserver = new MutationObserver(() => this.#syncField());
@@ -36,6 +39,7 @@ class AButtonElement extends HTMLElementBase {
36
39
  }
37
40
  }
38
41
  disconnectedCallback() {
42
+ if (this.#textToggleSource) this[SYNC_TEXT_TOGGLE]({ source: this.#textToggleSource, clear: true });
39
43
  if (!this.#field) return;
40
44
  this.#fieldObserver?.disconnect();
41
45
  this.#field?.removeEventListener("click", this.#onFieldClick);
@@ -61,6 +65,19 @@ class AButtonElement extends HTMLElementBase {
61
65
  if (this.hasAttribute("disabled") || this.hasAttribute("loading")) return;
62
66
  if (event.composedPath().some((node) => node instanceof Element && node.localName === "a-select-label")) this.focus();
63
67
  };
68
+ [SYNC_TEXT_TOGGLE](state) {
69
+ if (!this.internals) return;
70
+ if (state.clear) {
71
+ if (this.#textToggleSource !== state.source) return;
72
+ this.#textToggleSource = void 0;
73
+ this.internals.ariaExpanded = null;
74
+ this.internals.ariaLabel = null;
75
+ return;
76
+ }
77
+ this.#textToggleSource = state.source;
78
+ this.internals.ariaExpanded = state.expanded ? "true" : "false";
79
+ this.internals.ariaLabel = state.expanded ? "Show less" : "Show more";
80
+ }
64
81
  [SYNC_POPUP_ARIA](relations) {
65
82
  if (!this.internals || !("ariaControlsElements" in this.internals)) return;
66
83
  if (relations.clear) {
@@ -21,18 +21,18 @@ import './a-calendar.css';
21
21
  * `{ next, prev, reason }`: ISO-string dates (or `null`), `reason` ∈
22
22
  * `'user' | 'reset' | 'restore'`. `preventDefault()` vetoes a `'user'` pick in
23
23
  * uncontrolled mode.
24
- * - `change` — fired AFTER a selection applies (uncontrolled), and on
25
- * reset/restore.
24
+ * - `change` — fired AFTER a selection applies, including controlled attribute
25
+ * updates and accepted reset/restore.
26
26
  * - `navigate` — `detail: { date }` (ISO). Fired on keyboard navigation (and
27
27
  * when the target month isn't rendered) so the renderer can move the roving tab
28
28
  * stop and flip the displayed month.
29
29
  *
30
30
  * ## Controlled vs uncontrolled (like `<a-radio-group>`)
31
31
  *
32
- * Presence of the **`value`** attribute = controlled: a pick only *requests* a
33
- * change (fires `statechange`); the consumer answers by updating `value`. With
34
- * **`defaultvalue`** instead, the element owns selection — a pick self-applies
35
- * (form value + `change`) unless vetoed.
32
+ * Presence of **`value`** = controlled: a pick requests a change through
33
+ * `statechange`; the renderer answers by updating `value`. With **`defaultvalue`**
34
+ * instead, the element owns selection and applies picks unless vetoed. Consumer
35
+ * property assignments reflect these inputs; picks never rewrite them.
36
36
  *
37
37
  * ## Selection & keyboard
38
38
  *
@@ -47,8 +47,9 @@ export declare class ACalendarElement extends HTMLElementBase {
47
47
  static formAssociated: boolean;
48
48
  static observedAttributes: string[];
49
49
  private internals?;
50
- /** Selection in uncontrolled mode (controlled reads the `value` attribute). */
50
+ /** Latest applied selection, retained when controlled state is removed. */
51
51
  private selectedIso;
52
+ private seeded;
52
53
  constructor();
53
54
  connectedCallback(): void;
54
55
  attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
@@ -64,12 +65,12 @@ export declare class ACalendarElement extends HTMLElementBase {
64
65
  * microtask so a just-rendered new month has committed. Signal: `"<iso>#<nonce>"`. */
65
66
  private focusCell;
66
67
  private emitState;
67
- /** The selected date as an ISO string (`''` when empty). */
68
- get value(): string;
69
- set value(v: string);
70
- /** Form field name — mirrors the `name` attribute, like native `<input>.name`. */
71
- get name(): string;
72
- set name(v: string);
68
+ /** Applied ISO date, or `null` when empty. String assignments supply controlled input. */
69
+ get value(): string | null;
70
+ set value(value: string | null | undefined);
71
+ /** Authored initial/reset date, or `null` when no default is supplied. */
72
+ get defaultValue(): string | null;
73
+ set defaultValue(value: string | null | undefined);
73
74
  formResetCallback(): void;
74
75
  formStateRestoreCallback(state: string): void;
75
76
  }
@@ -1,12 +1,14 @@
1
1
  import { HTMLElementBase, isModifiedNavigationKey } from "../anta_helpers";
2
2
  import { Temporal } from "temporal-polyfill";
3
3
  import "./a-calendar.css";
4
+ import { upgradeValueProperties } from "./value-input";
4
5
  class ACalendarElement extends HTMLElementBase {
5
6
  static formAssociated = true;
6
7
  static observedAttributes = ["value", "data-focus"];
7
8
  internals;
8
- /** Selection in uncontrolled mode (controlled reads the `value` attribute). */
9
+ /** Latest applied selection, retained when controlled state is removed. */
9
10
  selectedIso = null;
11
+ seeded = false;
10
12
  constructor() {
11
13
  super();
12
14
  try {
@@ -16,27 +18,28 @@ class ACalendarElement extends HTMLElementBase {
16
18
  }
17
19
  this.addEventListener("click", this.onClick);
18
20
  this.addEventListener("keydown", this.onKeydown);
21
+ upgradeValueProperties(this);
19
22
  }
20
23
  connectedCallback() {
21
- if (Object.prototype.hasOwnProperty.call(this, "value")) {
22
- const v = this.value;
23
- delete this.value;
24
- this.value = v;
24
+ if (!this.seeded) {
25
+ this.selectedIso = this.#controlled ? this.currentIso() : this.getAttribute("defaultvalue") || null;
26
+ this.seeded = true;
25
27
  }
26
- this.selectedIso = this.getAttribute("value") || this.getAttribute("defaultvalue") || null;
27
28
  this.applyFormValue(this.currentIso());
28
29
  }
29
30
  attributeChangedCallback(name, _old, value) {
30
31
  if (name === "value") {
31
- this.applyFormValue(value);
32
+ const prev = this.selectedIso;
33
+ this.selectedIso = this.currentIso();
34
+ this.applyFormValue(this.selectedIso);
35
+ if (this.seeded && this.selectedIso !== prev) this.dispatchEvent(new Event("change", { bubbles: true }));
32
36
  return;
33
37
  }
34
38
  if (name === "data-focus") {
35
39
  this.focusCell(value);
36
40
  }
37
41
  }
38
- /** Controlled when the `value` attribute is present (the renderer sets it only
39
- * in controlled mode); otherwise the element owns selection. */
42
+ /** A present value input keeps selection controlled, including an empty date. */
40
43
  get #controlled() {
41
44
  return this.hasAttribute("value");
42
45
  }
@@ -145,37 +148,39 @@ class ACalendarElement extends HTMLElementBase {
145
148
  }
146
149
  // --- Public value API + form callbacks ----------------------------------
147
150
  emitState(next, prev, reason) {
151
+ const controlled = this.#controlled;
152
+ if (!controlled) {
153
+ this.selectedIso = next;
154
+ this.applyFormValue(next);
155
+ }
148
156
  this.dispatchEvent(
149
157
  new CustomEvent("statechange", { bubbles: false, detail: { next, prev, reason } })
150
158
  );
151
- this.dispatchEvent(new Event("change", { bubbles: true }));
159
+ if (!controlled) this.dispatchEvent(new Event("change", { bubbles: true }));
152
160
  }
153
- /** The selected date as an ISO string (`''` when empty). */
161
+ /** Applied ISO date, or `null` when empty. String assignments supply controlled input. */
154
162
  get value() {
155
- return this.currentIso() ?? "";
163
+ return this.currentIso();
156
164
  }
157
- set value(v) {
158
- this.selectedIso = v || null;
159
- this.applyFormValue(v || null);
165
+ set value(value) {
166
+ if (value == null) this.removeAttribute("value");
167
+ else this.setAttribute("value", value);
160
168
  }
161
- /** Form field name — mirrors the `name` attribute, like native `<input>.name`. */
162
- get name() {
163
- return this.getAttribute("name") ?? "";
169
+ /** Authored initial/reset date, or `null` when no default is supplied. */
170
+ get defaultValue() {
171
+ return this.getAttribute("defaultvalue");
164
172
  }
165
- set name(v) {
166
- this.setAttribute("name", v);
173
+ set defaultValue(value) {
174
+ if (value == null) this.removeAttribute("defaultvalue");
175
+ else this.setAttribute("defaultvalue", value);
167
176
  }
168
177
  formResetCallback() {
169
178
  const def = this.getAttribute("defaultvalue") || null;
170
179
  const prev = this.currentIso();
171
- this.selectedIso = def;
172
- this.applyFormValue(def);
173
180
  this.emitState(def, prev, "reset");
174
181
  }
175
182
  formStateRestoreCallback(state) {
176
183
  const prev = this.currentIso();
177
- this.selectedIso = state || null;
178
- this.applyFormValue(state || null);
179
184
  this.emitState(state || null, prev, "restore");
180
185
  }
181
186
  }