@antadesign/anta 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/anta_helpers.d.ts +20 -0
  2. package/dist/anta_helpers.js +5 -1
  3. package/dist/components/Button.js +2 -2
  4. package/dist/components/Card.d.ts +100 -0
  5. package/dist/components/Card.js +104 -0
  6. package/dist/components/Card.module.css +35 -0
  7. package/dist/components/Checkbox.js +2 -2
  8. package/dist/components/Dialog.js +4 -4
  9. package/dist/components/Expander.js +4 -4
  10. package/dist/components/Input.js +2 -2
  11. package/dist/components/InputDate.js +1 -2
  12. package/dist/components/InputTime.js +2 -2
  13. package/dist/components/Menu.js +4 -4
  14. package/dist/components/Progress.js +2 -2
  15. package/dist/components/Select.js +0 -6
  16. package/dist/components/Tabs.js +3 -3
  17. package/dist/components/Tag.js +3 -2
  18. package/dist/components/Text.d.ts +5 -3
  19. package/dist/components/Text.js +8 -2
  20. package/dist/components/Title.d.ts +5 -3
  21. package/dist/components/Title.js +4 -2
  22. package/dist/components/Tooltip.js +2 -2
  23. package/dist/elements/a-card.css +1 -0
  24. package/dist/elements/a-card.d.ts +10 -0
  25. package/dist/elements/a-card.js +185 -0
  26. package/dist/elements/a-checkbox.css +1 -1
  27. package/dist/elements/a-checkbox.js +0 -2
  28. package/dist/elements/a-input.d.ts +2 -0
  29. package/dist/elements/a-input.js +34 -1
  30. package/dist/elements/a-menu.js +21 -9
  31. package/dist/elements/a-radio-group.d.ts +3 -1
  32. package/dist/elements/a-radio-group.js +3 -3
  33. package/dist/elements/a-radio.css +1 -1
  34. package/dist/elements/a-tabs.d.ts +3 -1
  35. package/dist/elements/a-tabs.js +3 -3
  36. package/dist/elements/a-text.css +1 -1
  37. package/dist/elements/a-title.css +1 -1
  38. package/dist/elements/index.d.ts +1 -0
  39. package/dist/elements/index.js +3 -0
  40. package/dist/general_types.d.ts +55 -6
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/jsx-runtime.d.ts +2 -1
  44. package/package.json +1 -1
@@ -15,12 +15,25 @@ export declare function wrapLabel(kids: React.ReactNode, tag: string): React.Rea
15
15
  * the native event directly. Returns the native event and its `detail`. Shared by
16
16
  * every stateful wrapper (`Menu`, `Expander`, `Checkbox`, `RadioGroup`) — don't
17
17
  * re-implement it per component.
18
+ *
19
+ * `isOwn` is `true` when the event was dispatched on the element the listener is
20
+ * bound to (`target === currentTarget`), `false` when it bubbled up from a
21
+ * descendant. `statechange` is a shared event name with a per-component `detail`
22
+ * vocabulary, so a container wrapper (`Dialog`, `Menu`, `Expander`) that projects
23
+ * arbitrary children must gate on `isOwn` before acting: a foreign bubbling
24
+ * `statechange` from a nested control would otherwise be read in the wrong
25
+ * vocabulary (a checkbox toggle looking like a dialog close). Anta's own controls
26
+ * no longer bubble `statechange`, so this is defense-in-depth against consumer /
27
+ * third-party elements that do. Read synchronously during dispatch, so
28
+ * `currentTarget` is still live. (Read as `true` for the falsy-event guard so a
29
+ * missing event never spuriously reads as bubbled.)
18
30
  */
19
31
  export declare function nativeStateChange<D>(e: CustomEvent<D> | {
20
32
  nativeEvent: CustomEvent<D>;
21
33
  }): {
22
34
  event: CustomEvent<D>;
23
35
  detail?: D;
36
+ isOwn: boolean;
24
37
  };
25
38
  export declare const IS_MAC: boolean;
26
39
  /** Default row hint teaching the Alt/⌥-click "select only this" accelerator on
@@ -54,6 +67,13 @@ export declare function toneStyle(tone: string | undefined, varName: string, bas
54
67
  * element's default full-round). Returns `base` unchanged when there's no value.
55
68
  */
56
69
  export declare function roundStyle(round: boolean | number | string | undefined, varName: string, base?: React.CSSProperties): React.CSSProperties | undefined;
70
+ /**
71
+ * Presence flag for a `round` attribute: `''` when a radius applies, else
72
+ * `undefined`. A bare `round ? '' : undefined` wrongly drops `round={0}` — the
73
+ * caller asking for square corners — since `0` is falsy; this keeps it (and drops
74
+ * `false` / `undefined` / `''`). Mirrors what `roundStyle` emits a variable for.
75
+ */
76
+ export declare function roundAttr(round: boolean | number | string | undefined): '' | undefined;
57
77
  /**
58
78
  * The rect a floating-element anchor advertises for positioning. Any element may
59
79
  * implement `getAnchorRect(): DOMRect` to point positioners (`a-menu`,
@@ -14,7 +14,7 @@ function wrapLabel(kids, tag) {
14
14
  }
15
15
  function nativeStateChange(e) {
16
16
  const event = "nativeEvent" in e ? e.nativeEvent : e;
17
- return { event, detail: event?.detail };
17
+ return { event, detail: event?.detail, isOwn: !event || event.target === event.currentTarget };
18
18
  }
19
19
  const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/i.test(navigator.userAgent || "");
20
20
  const ISOLATE_HINT = IS_MAC ? "\u2325+Click to select only this" : "Alt+Click to select only this";
@@ -37,6 +37,9 @@ function roundStyle(round, varName, base) {
37
37
  if (typeof round === "string" && round !== "") return { ...base, [varName]: round };
38
38
  return base;
39
39
  }
40
+ function roundAttr(round) {
41
+ return round || round === 0 ? "" : void 0;
42
+ }
40
43
  function anchorRect(el) {
41
44
  const fn = el.getAnchorRect;
42
45
  return typeof fn === "function" ? fn.call(el) : el.getBoundingClientRect();
@@ -120,6 +123,7 @@ export {
120
123
  isMenuOpen,
121
124
  nativeStateChange,
122
125
  parseOpenState,
126
+ roundAttr,
123
127
  roundStyle,
124
128
  setMenuPresence,
125
129
  toneStyle,
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { toneStyle, roundStyle, wrapLabel } from "../anta_helpers";
2
+ import { toneStyle, roundStyle, roundAttr, wrapLabel } from "../anta_helpers";
3
3
  const Button = ({
4
4
  priority,
5
5
  tone,
@@ -42,7 +42,7 @@ const Button = ({
42
42
  // presence (`[disabled]`, not `[disabled="true"]`), so any present form
43
43
  // works. (ARIA attributes below stay string-valued — ARIA needs "true".)
44
44
  paddingless: paddingless ? "" : void 0,
45
- round: round ? "" : void 0,
45
+ round: roundAttr(round),
46
46
  loading: loading ? "" : void 0,
47
47
  disabled: disabled ? "" : void 0,
48
48
  selected: selected ? "" : void 0,
@@ -0,0 +1,100 @@
1
+ import type { BaseProps } from "../general_types";
2
+ import type { IconShape } from "../elements/a-icon.shapes";
3
+ export interface CardProps extends BaseProps {
4
+ /** Header content, in the top zone. A string is wrapped in a `<Title>` — a real
5
+ * heading in the document outline, tinted to match `tone`, at a level that
6
+ * tracks `size` (small → 5, medium → 4, large → 3). Pass your own node
7
+ * (`<Title level={2}>`, an `<h2>`) for a different level or styling. Omit for
8
+ * no header. */
9
+ header?: React.ReactNode;
10
+ /** A secondary line under the header title, rendered as smaller text
11
+ * (`<Text size="small">`) in the heading section. Tinted to match `tone`, like
12
+ * the title. Omit for no subtitle. */
13
+ subtitle?: React.ReactNode;
14
+ /** Leading visual for the header, laid out to the left of the `header` /
15
+ * `subtitle` as one aligned row (so it doesn't sit inline in the title, where it
16
+ * mis-aligns). A **string** is an icon shape, rendered as an `<Icon>` inside a
17
+ * circular chip (sized per the card via `--card-icon-size`); pass an `<img>`,
18
+ * initials (`<Text>`), or any node to use it as-is. Omit for none. */
19
+ icon?: IconShape | (string & {}) | React.ReactNode;
20
+ /** Footer content, usually action buttons. Rendered as a **left-aligned** row
21
+ * (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. */
22
+ footer?: React.ReactNode;
23
+ /** Media (image, illustration) rendered full-bleed to the card edge, clipped to
24
+ * its corners. Position it with `mediaPosition`. */
25
+ media?: React.ReactNode;
26
+ /** Which edge the `media` bleeds to.
27
+ * @defaultValue 'top' */
28
+ mediaPosition?: "top" | "bottom" | "left" | "right";
29
+ /** The card body. A string is wrapped in a `<Text>` at secondary priority,
30
+ * following the card's `tone` and `size`; pass your own nodes to take over. */
31
+ children?: React.ReactNode;
32
+ /** Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for
33
+ * a one-off custom tone. Named tones re-point the surface + text; a custom
34
+ * color keeps its hue while lightness/chroma are pinned. `'neutral'` (the
35
+ * default) is the same as omitting it.
36
+ * @defaultValue 'neutral' */
37
+ tone?: "neutral" | "brand" | "info" | "success" | "warning" | "critical" | (string & {});
38
+ /** Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a
39
+ * subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop
40
+ * blur.
41
+ * @defaultValue 'primary' */
42
+ priority?: "primary" | "secondary" | "tertiary";
43
+ /** Size variant — scales the padding.
44
+ * @defaultValue 'medium' */
45
+ size?: "small" | "medium" | "large";
46
+ /** Selected / chosen state — draws an inset ring in the tone color, for
47
+ * choice-card / plan-picker patterns. */
48
+ selected?: boolean;
49
+ /** Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in
50
+ * link mode) blocks navigation. */
51
+ loading?: boolean;
52
+ /** Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
53
+ * `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for
54
+ * the default `--card-radius`. */
55
+ round?: boolean | number | string;
56
+ /** Turn the whole card into a link. The card renders a focusable anchor and its
57
+ * accessible name comes from `header` → body → this URL (override with
58
+ * `aria-label`). A link card is display content — don't nest interactive
59
+ * controls inside it. */
60
+ href?: string;
61
+ /** Anchor target (only with `href`). */
62
+ target?: string;
63
+ /** Anchor rel (only with `href`). */
64
+ rel?: string;
65
+ /** Space-separated URLs the browser pings on navigation (only with `href`). */
66
+ ping?: string;
67
+ }
68
+ /**
69
+ * A surface container: a bordered, toned box laying out an optional `media`
70
+ * region plus a `header` / body / `footer` stack. Pass `href` to turn the whole
71
+ * card into a link.
72
+ *
73
+ * Renders an `<a-card>` web component. A stateless projection — the element owns
74
+ * layout, the link, and the accessible name, and stylizes only its own surface
75
+ * (never its children's typography). The wrapper maps props to attributes,
76
+ * projects content into the `media` / `icon` / `header` / body / `footer`
77
+ * sections, and supplies typography for the two text zones by wrapping a string
78
+ * `header` in `<Title>` and a string body in `<Text>`. A leading `icon` (a shape
79
+ * string, or any node) is laid out beside the title / subtitle by the element.
80
+ * There is no actions slot — lay out any header controls (buttons, tags) inside
81
+ * the `header` yourself.
82
+ *
83
+ * Requires `@antadesign/anta/elements` to be imported (client-side only) to
84
+ * register the underlying custom element.
85
+ *
86
+ * @example Basic usage
87
+ * ```tsx
88
+ * <Card header="Deployment ready" footer={<Button priority="primary" label="Deploy" />}>
89
+ * Your build passed all checks and is ready to ship.
90
+ * </Card>
91
+ * ```
92
+ *
93
+ * @example Link card
94
+ * ```tsx
95
+ * <Card href="/docs/ci" header="CI integration guide">
96
+ * Wire Antithesis into your pipeline in a few minutes.
97
+ * </Card>
98
+ * ```
99
+ */
100
+ export declare const Card: ({ header, subtitle, icon, footer, media, mediaPosition, tone, priority, size, selected, loading, round, href, target, rel, ping, className, style, children, ...rest }: CardProps) => any;
@@ -0,0 +1,104 @@
1
+ import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
+ import { toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
+ import { Title } from "./Title";
4
+ import { Text } from "./Text";
5
+ import { Icon } from "./Icon";
6
+ const Card = ({
7
+ header,
8
+ subtitle,
9
+ icon,
10
+ footer,
11
+ media,
12
+ mediaPosition,
13
+ tone,
14
+ priority,
15
+ size,
16
+ selected,
17
+ loading,
18
+ round,
19
+ href,
20
+ target,
21
+ rel,
22
+ ping,
23
+ className,
24
+ style,
25
+ children,
26
+ ...rest
27
+ }) => {
28
+ const toneAttr = tone || void 0;
29
+ const computedStyle = roundStyle(
30
+ round,
31
+ "--card-round",
32
+ toneStyle(toneAttr, "--card-tone-source", style)
33
+ );
34
+ const titleLevel = size === "small" ? 5 : size === "large" ? 3 : 4;
35
+ return /* @__PURE__ */ jsxs(
36
+ "a-card",
37
+ {
38
+ tone: toneAttr,
39
+ priority: priority && priority !== "primary" ? priority : void 0,
40
+ size: size && size !== "medium" ? size : void 0,
41
+ "media-position": mediaPosition && mediaPosition !== "top" ? mediaPosition : void 0,
42
+ selected: selected ? "" : void 0,
43
+ loading: loading ? "" : void 0,
44
+ round: roundAttr(round),
45
+ href,
46
+ target: href ? target : void 0,
47
+ rel: href ? rel : void 0,
48
+ ping: href ? ping : void 0,
49
+ class: className,
50
+ style: computedStyle,
51
+ ...rest,
52
+ children: [
53
+ media != null && /* @__PURE__ */ jsx("div", { slot: "media", children: media }),
54
+ icon != null && // Leading visual for the header — carried in a slot div (like `media` /
55
+ // `header`); the element lays it out beside the title / subtitle (see the
56
+ // `icon` slot in a-card.ts).
57
+ (typeof icon === "string" ? (
58
+ // A shape string → the icon centered in a circular chip, sized per the
59
+ // card via --card-icon-size (32 / 40 / 44 for small / medium / large).
60
+ /* @__PURE__ */ jsx(
61
+ "div",
62
+ {
63
+ slot: "icon",
64
+ style: {
65
+ width: "var(--card-icon-size, 40px)",
66
+ height: "var(--card-icon-size, 40px)",
67
+ borderRadius: "50%",
68
+ background: "var(--card-icon-bg)",
69
+ display: "grid",
70
+ placeItems: "center"
71
+ },
72
+ children: /* @__PURE__ */ jsx(Icon, { shape: icon })
73
+ }
74
+ )
75
+ ) : (
76
+ // Any node → used as-is (no chip).
77
+ /* @__PURE__ */ jsx("div", { slot: "icon", children: icon })
78
+ )),
79
+ header != null && (typeof header === "string" || typeof header === "number" ? (
80
+ // A string header becomes a real <Title> at the size-mapped level (see
81
+ // titleLevel). The header section's padding is the header→body gap; the
82
+ // element zeroes the slotted title's own margins so it doesn't double it.
83
+ // Pass a node (a `<Title level={n}>`, an `<h2>`) for a different level.
84
+ /* @__PURE__ */ jsx(Title, { slot: "header", level: titleLevel, tone: toneAttr, children: header })
85
+ ) : /* @__PURE__ */ jsx("div", { slot: "header", children: header })),
86
+ subtitle != null && // A subtitle sits in the header section too, right under the title, as
87
+ // smaller text. Last header child, so its (absent) bottom margin leaves
88
+ // the header→body gap to the section padding, like the title above it.
89
+ /* @__PURE__ */ jsx(Text, { slot: "header", size: "small", priority: "tertiary", tone: toneAttr, children: subtitle }),
90
+ typeof children === "string" || typeof children === "number" ? (
91
+ // A string / number body becomes a <Text> at secondary priority (text-2,
92
+ // the body colour) — stated explicitly rather than leaning on the default —
93
+ // following the card's tone and size. Anything else is a node the caller
94
+ // owns, passed through untouched.
95
+ /* @__PURE__ */ jsx(Text, { priority: "secondary", tone: toneAttr, size, children })
96
+ ) : children,
97
+ footer != null && /* @__PURE__ */ jsx("span", { slot: "footer", style: { display: "contents" }, children: footer })
98
+ ]
99
+ }
100
+ );
101
+ };
102
+ export {
103
+ Card
104
+ };
@@ -0,0 +1,35 @@
1
+ /* Header with a leading avatar. When an `avatar` is passed, the wrapper unites it
2
+ with the title + subtitle as one flex row, so a leading icon / image / initials
3
+ aligns as a unit with the heading instead of sitting inline inside the title
4
+ (where it's baseline-misaligned and has no clean vertical-centering escape).
5
+ The element stays neutral — this layout lives in the light-DOM header node the
6
+ wrapper slots in. */
7
+ .header {
8
+ display: flex;
9
+ align-items: flex-start;
10
+ gap: 12px;
11
+ }
12
+
13
+ .avatar {
14
+ flex: none;
15
+ display: flex;
16
+ align-items: center;
17
+ }
18
+
19
+ /* Title + subtitle column. min-width:0 lets a long title truncate rather than
20
+ push the avatar. */
21
+ .text {
22
+ min-width: 0;
23
+ }
24
+
25
+ /* The title / subtitle are nested here (not direct <a-card> slot children), so the
26
+ element's header-margin rules don't reach them — neutralize the title's own
27
+ document margins and give the subtitle the same 2px gap it gets elsewhere. The
28
+ [level] / tag specificity clears a-title's per-level margins. */
29
+ .text > :is(h1, h2, h3, h4, h5, h6),
30
+ .text > a-title[level] {
31
+ margin-block: 0;
32
+ }
33
+ .text > a-text {
34
+ margin-block-start: 2px;
35
+ }
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, toneStyle, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  const valueToState = (v) => v === "indeterminate" ? "indeterminate" : v ? "checked" : "unchecked";
4
4
  const stateToValue = (s) => s === "indeterminate" ? "indeterminate" : s === "checked";
5
5
  const checkboxAttrsOf = (el) => ({
@@ -66,7 +66,7 @@ const Checkbox = ({
66
66
  tone: tone && tone !== "neutral" ? tone : void 0,
67
67
  "tone-selected": toneSelected && toneSelected !== "neutral" ? toneSelected : void 0,
68
68
  size: size && size !== "medium" ? size : void 0,
69
- round: round ? "" : void 0,
69
+ round: roundAttr(round),
70
70
  tabIndex: disabled ? -1 : tabIndex ?? 0,
71
71
  onstatechange,
72
72
  onchange,
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, roundStyle, roundAttr } from "../anta_helpers";
3
3
  import { Button } from "./Button";
4
4
  const ROUND_SIDES = /* @__PURE__ */ new Set(["top", "right", "bottom", "left"]);
5
5
  const Dialog = ({
@@ -27,11 +27,11 @@ const Dialog = ({
27
27
  "default-state": !controlled && defaultOpen ? "open" : void 0,
28
28
  position: position && position !== "center" ? position : void 0,
29
29
  persistent: persistent ? "" : void 0,
30
- round: roundSide ?? (round ? "" : void 0),
30
+ round: roundSide ?? roundAttr(round),
31
31
  name: !controlled ? name : void 0,
32
32
  onstatechange: onStateChange ? (e) => {
33
- const { event, detail } = nativeStateChange(e);
34
- if (detail)
33
+ const { event, detail, isOwn } = nativeStateChange(e);
34
+ if (isOwn && detail)
35
35
  onStateChange(event, {
36
36
  next: detail.next === "open",
37
37
  prev: detail.prev === "open"
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, toneStyle, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  const Expander = ({
4
4
  title,
5
5
  level,
@@ -29,11 +29,11 @@ const Expander = ({
29
29
  tone: tone && tone !== "neutral" ? tone : void 0,
30
30
  priority: priority && priority !== "secondary" ? priority : void 0,
31
31
  outdent: outdent ? "" : void 0,
32
- round: round ? "" : void 0,
32
+ round: roundAttr(round),
33
33
  disabled: disabled ? "" : void 0,
34
34
  onstatechange: onStateChange ? (e) => {
35
- const { event, detail } = nativeStateChange(e);
36
- if (detail)
35
+ const { event, detail, isOwn } = nativeStateChange(e);
36
+ if (isOwn && detail)
37
37
  onStateChange(event, {
38
38
  next: detail.next === "open",
39
39
  prev: detail.prev === "open"
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, toneStyle, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  import { Button } from "./Button";
4
4
  import { Icon } from "./Icon";
5
5
  const presence = (on) => on ? "" : void 0;
@@ -82,7 +82,7 @@ const Input = ({
82
82
  "a-input",
83
83
  {
84
84
  size: size && size !== "medium" ? size : void 0,
85
- round: round ? "" : void 0,
85
+ round: roundAttr(round),
86
86
  value,
87
87
  defaultvalue: defaultValue,
88
88
  multiline: presence(multiline || rows != null),
@@ -136,9 +136,8 @@ const InputDate = ({
136
136
  },
137
137
  onChange: (e) => resolve(e.currentTarget.value),
138
138
  onKeyDown: (e) => {
139
- if (e.key === "ArrowDown" && !disabled) {
139
+ if ((e.key === "ArrowDown" || e.key === "ArrowUp") && !disabled) {
140
140
  e.preventDefault();
141
- if (!open) e.currentTarget.click();
142
141
  setFocusNonce((n) => n + 1);
143
142
  }
144
143
  if (e.key === "Enter" && !disabled) {
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, toneStyle, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  import { Button } from "./Button";
4
4
  import { Icon } from "./Icon";
5
5
  const presence = (on) => on ? "" : void 0;
@@ -56,7 +56,7 @@ const InputTime = ({
56
56
  "a-input-time",
57
57
  {
58
58
  size: size && size !== "medium" ? size : void 0,
59
- round: round ? "" : void 0,
59
+ round: roundAttr(round),
60
60
  value,
61
61
  defaultvalue: defaultValue,
62
62
  locale,
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "@antadesign/anta/jsx-runtime";
2
- import { nativeStateChange, roundStyle } from "../anta_helpers";
2
+ import { nativeStateChange, roundStyle, roundAttr } from "../anta_helpers";
3
3
  const Menu = ({
4
4
  placement,
5
5
  context,
@@ -27,14 +27,14 @@ const Menu = ({
27
27
  autowidth: autoWidth ? "" : void 0,
28
28
  state: open === void 0 ? void 0 : open ? "open" : "closed",
29
29
  onstatechange: onStateChange ? (e) => {
30
- const { event, detail } = nativeStateChange(e);
31
- if (detail && (detail.next === "open" || detail.next === "closed"))
30
+ const { event, detail, isOwn } = nativeStateChange(e);
31
+ if (isOwn && detail)
32
32
  onStateChange(event, {
33
33
  next: detail.next === "open",
34
34
  prev: detail.prev === "open"
35
35
  });
36
36
  } : void 0,
37
- round: round ? "" : void 0,
37
+ round: roundAttr(round),
38
38
  role,
39
39
  "aria-orientation": "vertical",
40
40
  class: className,
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { hasChildren, toneStyle, roundStyle } from "../anta_helpers";
2
+ import { hasChildren, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  const Progress = ({ value, max = 100, tone, round, label, hint, className, style, children, ...rest }) => {
4
4
  const percent = max > 0 ? Math.round(Math.min(100, Math.max(0, value / max * 100))) : 0;
5
5
  const clampedValue = max > 0 ? Math.min(max, Math.max(0, value)) : 0;
@@ -10,7 +10,7 @@ const Progress = ({ value, max = 100, tone, round, label, hint, className, style
10
10
  value,
11
11
  max,
12
12
  tone: tone && tone !== "neutral" ? tone : void 0,
13
- round: round ? "" : void 0,
13
+ round: roundAttr(round),
14
14
  role: "progressbar",
15
15
  "aria-valuenow": clampedValue,
16
16
  "aria-valuemin": 0,
@@ -254,12 +254,6 @@ const Select = (props) => {
254
254
  round,
255
255
  "aria-haspopup": "menu",
256
256
  "aria-expanded": open ? "true" : "false",
257
- onKeyDown: (e) => {
258
- if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown")) {
259
- e.preventDefault();
260
- e.currentTarget.click();
261
- }
262
- },
263
257
  trailing: (
264
258
  // Chevron rotates down → up when open (spacing + rotation in the CSS
265
259
  // module, matching the Input "Select dropdown" example).
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
2
  import { useState } from "../jsx-runtime";
3
- import { nativeStateChange, toneStyle, roundStyle, wrapLabel } from "../anta_helpers";
3
+ import { nativeStateChange, toneStyle, roundStyle, roundAttr, wrapLabel } from "../anta_helpers";
4
4
  import { Tooltip } from "./Tooltip";
5
5
  const Tabs = ({
6
6
  children,
@@ -62,7 +62,7 @@ const Tabs = ({
62
62
  size: size && size !== "medium" ? size : void 0,
63
63
  orientation: vertical ? "vertical" : void 0,
64
64
  noslide: noslide ? "" : void 0,
65
- round: round ? "" : void 0,
65
+ round: roundAttr(round),
66
66
  disabled: disabled ? "" : void 0,
67
67
  onstatechange,
68
68
  onchange,
@@ -85,7 +85,7 @@ const Tabs = ({
85
85
  "aria-disabled": tabDisabled ? "true" : void 0,
86
86
  tabIndex: tabDisabled && !isSelected ? -1 : 0,
87
87
  disabled: tabDisabled ? "" : void 0,
88
- round: round || p.round ? "" : void 0,
88
+ round: roundAttr(round) ?? roundAttr(p.round),
89
89
  children: [
90
90
  p.icon && /* @__PURE__ */ jsx("a-icon", { shape: p.icon, "aria-hidden": "true" }),
91
91
  wrapLabel(p.label != null ? p.label : p.children, "a-tab-label"),
@@ -15,13 +15,14 @@ const Tag = ({
15
15
  children,
16
16
  ...rest
17
17
  }) => {
18
- const computedStyle = toneStyle(tone, "--tag-tone-source", style);
18
+ const toneAttr = tone || void 0;
19
+ const computedStyle = toneStyle(toneAttr, "--tag-tone-source", style);
19
20
  const hasValue = value != null;
20
21
  const isIconOnly = icon != null && label == null && value == null && children == null && iconTrailing == null;
21
22
  return /* @__PURE__ */ jsxs(
22
23
  "a-tag",
23
24
  {
24
- tone,
25
+ tone: toneAttr,
25
26
  "aria-label": isIconOnly ? `${icon} tag` : void 0,
26
27
  priority: priority && priority !== "secondary" ? priority : void 0,
27
28
  size: size && size !== "medium" ? size : void 0,
@@ -23,10 +23,12 @@ export type TextProps = BaseProps & {
23
23
  * step softer than the strongest foreground; pass `primary` for emphasis.
24
24
  * @defaultValue secondary */
25
25
  priority?: 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'quinary';
26
- /** Color tint. `neutral` (the default) is the untinted `--text-{N}` scale;
27
- * the others apply the matching `--text-{N}-{tone}` palette.
26
+ /** Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
27
+ * named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
28
+ * color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
29
+ * kept while lightness/chroma are pinned per priority in oklch.
28
30
  * @defaultValue neutral */
29
- tone?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
31
+ tone?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical' | (string & {});
30
32
  /** Type scale. `small` = 13/16, `medium` = 15/20, `large` = 17/24.
31
33
  * @defaultValue medium */
32
34
  size?: 'small' | 'medium' | 'large';
@@ -1,14 +1,20 @@
1
1
  import { jsx } from "@antadesign/anta/jsx-runtime";
2
+ import { toneStyle } from "../anta_helpers";
2
3
  const Text = ({ priority, tone, size, inline, truncate, expandable, collapsible, className, style, children, ...rest }) => {
3
4
  const n = typeof truncate === "number" ? truncate : truncate ? 1 : null;
4
5
  const lineCount = n != null && n >= 1 ? n : null;
5
- const computedStyle = lineCount != null ? { ...style, ["--line-clamp"]: lineCount } : style;
6
+ const toneAttr = tone || void 0;
7
+ const computedStyle = toneStyle(
8
+ toneAttr,
9
+ "--text-tone-source",
10
+ lineCount != null ? { ...style, ["--line-clamp"]: lineCount } : style
11
+ );
6
12
  const canExpand = expandable && lineCount != null;
7
13
  return /* @__PURE__ */ jsx(
8
14
  "a-text",
9
15
  {
10
16
  priority,
11
- tone,
17
+ tone: toneAttr,
12
18
  size,
13
19
  inline: inline ? "" : void 0,
14
20
  truncate: lineCount != null ? String(lineCount) : void 0,
@@ -8,10 +8,12 @@ export interface TitleProps extends BaseProps {
8
8
  /** Visual priority. Maps to text-1..text-5 (`primary` = text-1).
9
9
  * @defaultValue primary */
10
10
  priority?: 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'quinary';
11
- /** Color tint. `neutral` (the default) is the untinted `--text-{N}` scale;
12
- * the others apply the matching `--text-{N}-{tone}` palette.
11
+ /** Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
12
+ * named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
13
+ * color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
14
+ * kept while lightness/chroma are pinned per priority in oklch.
13
15
  * @defaultValue neutral */
14
- tone?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
16
+ tone?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical' | (string & {});
15
17
  }
16
18
  /**
17
19
  * Block-level heading with level 1-6, priority, and tone.
@@ -1,15 +1,17 @@
1
1
  import { jsx } from "@antadesign/anta/jsx-runtime";
2
+ import { toneStyle } from "../anta_helpers";
2
3
  const Title = ({ level = 2, priority, tone, className, style, children, ...rest }) => {
4
+ const toneAttr = tone || void 0;
3
5
  return /* @__PURE__ */ jsx(
4
6
  "a-title",
5
7
  {
6
8
  level: String(level),
7
9
  priority,
8
- tone,
10
+ tone: toneAttr,
9
11
  role: "heading",
10
12
  "aria-level": level,
11
13
  class: className,
12
- style,
14
+ style: toneStyle(toneAttr, "--title-tone-source", style),
13
15
  ...rest,
14
16
  children
15
17
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "@antadesign/anta/jsx-runtime";
2
- import { roundStyle } from "../anta_helpers";
2
+ import { roundStyle, roundAttr } from "../anta_helpers";
3
3
  const Tooltip = ({
4
4
  delay,
5
5
  placement,
@@ -18,7 +18,7 @@ const Tooltip = ({
18
18
  {
19
19
  delay: delay != null ? String(delay) : void 0,
20
20
  placement: placement === "top" ? "top" : void 0,
21
- round: round ? "" : void 0,
21
+ round: roundAttr(round),
22
22
  follow: follow ? "" : void 0,
23
23
  interactive: interactive ? "" : void 0,
24
24
  "truncated-only": truncatedOnly ? "" : void 0,