@antadesign/anta 0.3.9 → 0.3.10

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.
@@ -1,3 +1,4 @@
1
+ import type { IconShape } from "./elements/a-icon.shapes";
1
2
  export declare function hasChildren(children: React.ReactNode): boolean;
2
3
  /**
3
4
  * Normalize a wrapper's label content the way `Button` / `Tabs` do: a bare string
@@ -74,6 +75,23 @@ export declare function roundStyle(round: boolean | number | string | undefined,
74
75
  * `false` / `undefined` / `''`). Mirrors what `roundStyle` emits a variable for.
75
76
  */
76
77
  export declare function roundAttr(round: boolean | number | string | undefined): '' | undefined;
78
+ /**
79
+ * Copy-feedback state for `ButtonCopy` / `MenuItemCopy` (and any future copy
80
+ * preset). The `<a-button>` / `<a-menu-item>` element performs the clipboard
81
+ * write and fires a `copydone` event; this hook holds the transient result and
82
+ * maps it to the icon + tone the wrapper renders: a `check` / `success` on
83
+ * success, an `x` / `critical` on failure, otherwise the caller's own values.
84
+ * Returns the `handleCopied` listener the wrapper wires to `onCopied`.
85
+ *
86
+ * Kept here (not duplicated per preset) so the timing, glyph choice, and timer
87
+ * cleanup live in one place. Hooks come through the `jsx-runtime` indirection, so
88
+ * a custom non-React runtime supplies its own via `configure()`.
89
+ */
90
+ export declare function useCopyFeedback(icon: IconShape | undefined, tone: string | undefined, onCopied?: (ok: boolean) => void): {
91
+ shownIcon: IconShape | undefined;
92
+ shownTone: string | undefined;
93
+ handleCopied: (ok: boolean) => void;
94
+ };
77
95
  /**
78
96
  * The rect a floating-element anchor advertises for positioning. Any element may
79
97
  * implement `getAnchorRect(): DOMRect` to point positioners (`a-menu`,
@@ -1,4 +1,4 @@
1
- import { jsx } from "./jsx-runtime";
1
+ import { jsx, useState, useMemo } from "./jsx-runtime";
2
2
  function hasChildren(children) {
3
3
  return Array.isArray(children) ? children.length > 0 : children != null;
4
4
  }
@@ -40,6 +40,20 @@ function roundStyle(round, varName, base) {
40
40
  function roundAttr(round) {
41
41
  return round || round === 0 ? "" : void 0;
42
42
  }
43
+ const COPY_FEEDBACK_MS = 2e3;
44
+ function useCopyFeedback(icon, tone, onCopied) {
45
+ const [status, setStatus] = useState("idle");
46
+ const box = useMemo(() => ({ timer: void 0 }), []);
47
+ const handleCopied = (ok) => {
48
+ clearTimeout(box.timer);
49
+ setStatus(ok ? "ok" : "fail");
50
+ box.timer = setTimeout(() => setStatus("idle"), COPY_FEEDBACK_MS);
51
+ onCopied?.(ok);
52
+ };
53
+ const shownIcon = status === "ok" ? "check" : status === "fail" ? "x" : icon;
54
+ const shownTone = status === "ok" ? "success" : status === "fail" ? "critical" : tone;
55
+ return { shownIcon, shownTone, handleCopied };
56
+ }
43
57
  function anchorRect(el) {
44
58
  const fn = el.getAnchorRect;
45
59
  return typeof fn === "function" ? fn.call(el) : el.getBoundingClientRect();
@@ -127,5 +141,6 @@ export {
127
141
  roundStyle,
128
142
  setMenuPresence,
129
143
  toneStyle,
144
+ useCopyFeedback,
130
145
  wrapLabel
131
146
  };
@@ -100,7 +100,50 @@ export type PriorityMode = {
100
100
  /** Drops outer padding to zero. */
101
101
  paddingless?: boolean;
102
102
  };
103
- export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitMode & BaseProps;
103
+ /** Copy axis — turns the button into a copy control that writes to the clipboard
104
+ * on click and flashes a success / failure state (see `<a-button>`'s copy
105
+ * behavior). **Exactly one** of `copy`, `copyNode`, or `copyLazy` may be set;
106
+ * the union makes the others `never` in each mode. For the batteries-included
107
+ * preset, use `ButtonCopy`. */
108
+ export type CopyMode = {
109
+ copy?: never;
110
+ copyNode?: never;
111
+ copyLazy?: never;
112
+ onCopied?: never;
113
+ onCopyRequest?: never;
114
+ } | {
115
+ /** Text copied to the clipboard on click. */
116
+ copy: string;
117
+ copyNode?: never;
118
+ copyLazy?: never;
119
+ onCopyRequest?: never;
120
+ /** Fires after the copy attempt with whether it succeeded. */
121
+ onCopied?: (ok: boolean) => void;
122
+ } | {
123
+ /** Copy a DOM node as rich text instead of a string. `true` copies the
124
+ * nearest ancestor marked `data-copy-source`; a string is a CSS selector
125
+ * for an ancestor region (`closest`). The copy control is stripped from
126
+ * the copied output. */
127
+ copyNode: boolean | string;
128
+ copy?: never;
129
+ copyLazy?: never;
130
+ onCopyRequest?: never;
131
+ /** Fires after the copy attempt with whether it succeeded. */
132
+ onCopied?: (ok: boolean) => void;
133
+ } | {
134
+ /** Lazy copy: the content isn't computed or held in the DOM until the
135
+ * click. The click fires `onCopyRequest(provide)`; call `provide(text)`
136
+ * with the value — synchronously, or after an `await` (the browser's
137
+ * transient-activation window still covers the write). */
138
+ copyLazy: true;
139
+ copy?: never;
140
+ copyNode?: never;
141
+ /** Supplies the lazily-computed content on click: call `provide(text)`. */
142
+ onCopyRequest: (provide: (text: string) => void) => void;
143
+ /** Fires after the copy attempt with whether it succeeded. */
144
+ onCopied?: (ok: boolean) => void;
145
+ };
146
+ export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitMode & CopyMode & BaseProps;
104
147
  /**
105
148
  * Action button.
106
149
  *
@@ -121,4 +164,4 @@ export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitM
121
164
  * <Button href="/docs" target="_blank" label="Read the docs" />
122
165
  * ```
123
166
  */
124
- export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, type, form, className, style, children, ...rest }: ButtonProps) => any;
167
+ export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, type, form, copy, copyNode, copyLazy, onCopied, onCopyRequest, className, style, children, ...rest }: ButtonProps) => any;
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { toneStyle, roundStyle, roundAttr, wrapLabel } from "../anta_helpers";
2
+ import { toneStyle, roundStyle, roundAttr, wrapLabel, nativeStateChange } from "../anta_helpers";
3
3
  const Button = ({
4
4
  priority,
5
5
  tone,
@@ -16,14 +16,21 @@ const Button = ({
16
16
  href,
17
17
  type,
18
18
  form,
19
+ copy,
20
+ copyNode,
21
+ copyLazy,
22
+ onCopied,
23
+ onCopyRequest,
19
24
  className,
20
25
  style,
21
26
  children,
22
27
  ...rest
23
28
  }) => {
29
+ const isCopy = copy != null || copyNode != null || copyLazy === true;
24
30
  const toneAttr = tone || void 0;
25
31
  const computedStyle = roundStyle(round, "--button-round", toneStyle(toneAttr, "--button-tone-source", style));
26
- const isIconOnly = icon != null && label == null && children == null && iconTrailing == null;
32
+ const leadingIcon = icon ?? (isCopy ? "copy" : void 0);
33
+ const isIconOnly = leadingIcon != null && label == null && children == null && iconTrailing == null;
27
34
  const sharedAttrs = {
28
35
  // `<a-button>` is a custom element with no implicit ARIA role, so AT would
29
36
  // announce it as a generic clickable — and `aria-pressed` below is only
@@ -53,14 +60,26 @@ const Button = ({
53
60
  "aria-disabled": disabled || loading ? "true" : void 0,
54
61
  "aria-busy": loading ? "true" : void 0,
55
62
  "aria-pressed": selected ? "true" : void 0,
56
- // Icon-only buttons get an accessible name from the icon shape;
57
- // consumer's own `aria-label` (via ...rest) wins by spread order.
58
- "aria-label": isIconOnly ? icon : void 0,
63
+ // Icon-only buttons get an accessible name: "Copy" for a copy control (its
64
+ // glyph carries no text), else the icon shape. Consumer's own `aria-label`
65
+ // (via ...rest) wins by spread order.
66
+ "aria-label": isIconOnly ? isCopy ? "Copy" : icon : void 0,
67
+ // Copy behavior — the element reads these and performs the write itself.
68
+ copy: copy != null ? copy : void 0,
69
+ "copy-node": copyNode === true ? "" : typeof copyNode === "string" ? copyNode : void 0,
70
+ "copy-lazy": copyLazy ? "" : void 0,
71
+ // Marks the control so `copy-node` serialization strips it from the copied
72
+ // node (a copy button inside the copied region shouldn't paste itself).
73
+ "data-copy-node-button": copyNode != null && copyNode !== false ? "" : void 0,
74
+ oncopydone: onCopied ? (e) => onCopied(nativeStateChange(e).detail?.ok ?? false) : void 0,
75
+ // The element hands `provide` on the request event; forward it so the
76
+ // consumer supplies the lazily-computed content.
77
+ oncopyrequest: onCopyRequest ? (e) => onCopyRequest(nativeStateChange(e).detail.provide) : void 0,
59
78
  class: className,
60
79
  style: computedStyle
61
80
  };
62
81
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
63
- icon && /* @__PURE__ */ jsx("a-icon", { shape: icon, "aria-hidden": "true" }),
82
+ leadingIcon && /* @__PURE__ */ jsx("a-icon", { shape: leadingIcon, "aria-hidden": "true" }),
64
83
  label != null && /* @__PURE__ */ jsx("a-button-label", { children: label }),
65
84
  wrapLabel(children, "a-button-label"),
66
85
  iconTrailing && /* @__PURE__ */ jsx("a-icon", { shape: iconTrailing, "aria-hidden": "true" })
@@ -0,0 +1,32 @@
1
+ import { type ButtonProps } from './Button';
2
+ /** `ButtonProps` narrowed so a copy target is required — a copy button must copy
3
+ * something. Intersecting with the target union drops the "no copy prop" member
4
+ * of `CopyMode` (its `copy?: never` makes each combination `never`). */
5
+ export type ButtonCopyProps = ButtonProps & ({
6
+ copy: string;
7
+ } | {
8
+ copyNode: boolean | string;
9
+ } | {
10
+ copyLazy: true;
11
+ });
12
+ /**
13
+ * Copy button — a `Button` preset for copy-to-clipboard. Set `copy` for a literal
14
+ * string, `copyNode` to copy a DOM region, or `copyLazy` + `onCopyRequest` to
15
+ * compute the content on click. The `<a-button>` element performs the write and
16
+ * reports the result; this wrapper flips the leading icon to a check (success) or
17
+ * ✕ (failure) and retones to `success` / `critical` for ~2s — a JSX re-render, no
18
+ * element state (see `useCopyFeedback`).
19
+ *
20
+ * With a `label` it's a labeled button; without one it's an icon-only copy button.
21
+ * Everything else is a normal `Button` prop — `tone`, `priority`, `size`, `icon`
22
+ * (the resting glyph), `onCopied`.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <ButtonCopy copy="npm i @antadesign/anta" label="Copy install" />
27
+ * <ButtonCopy copy="https://anta.design" /> // icon-only
28
+ * <ButtonCopy copyNode=".snippet" label="Copy block" priority="tertiary" />
29
+ * <ButtonCopy copyLazy label="Copy report" onCopyRequest={(p) => p(buildReport())} />
30
+ * ```
31
+ */
32
+ export declare const ButtonCopy: ({ icon, tone, onCopied, ...rest }: ButtonCopyProps) => any;
@@ -0,0 +1,10 @@
1
+ import { jsx } from "@antadesign/anta/jsx-runtime";
2
+ import { Button } from "./Button";
3
+ import { useCopyFeedback } from "../anta_helpers";
4
+ const ButtonCopy = ({ icon, tone, onCopied, ...rest }) => {
5
+ const { shownIcon, shownTone, handleCopied } = useCopyFeedback(icon, tone, onCopied);
6
+ return /* @__PURE__ */ jsx(Button, { icon: shownIcon, tone: shownTone, onCopied: handleCopied, ...rest });
7
+ };
8
+ export {
9
+ ButtonCopy
10
+ };
@@ -1,5 +1,6 @@
1
1
  import type { BaseProps } from '../general_types';
2
2
  import type { IconShape } from '../elements/a-icon.shapes';
3
+ import type { CopyMode } from './Button';
3
4
  /** Props shared by every menu item, link or not. */
4
5
  export interface MenuItemCommonProps extends BaseProps {
5
6
  /** Leading icon shape. */
@@ -83,6 +84,11 @@ export type MenuItemLinkMode = {
83
84
  selectionIndicator?: never;
84
85
  indeterminate?: never;
85
86
  indicator?: never;
87
+ copy?: never;
88
+ copyNode?: never;
89
+ copyLazy?: never;
90
+ onCopied?: never;
91
+ onCopyRequest?: never;
86
92
  };
87
93
  export type MenuItemActionMode = {
88
94
  href?: never;
@@ -114,7 +120,7 @@ export type MenuItemActionMode = {
114
120
  * passive (aria-hidden, no pointer events) so the row owns the click. */
115
121
  indicator?: React.ReactNode;
116
122
  };
117
- export type MenuItemProps = MenuItemCommonProps & (MenuItemLinkMode | MenuItemActionMode);
123
+ export type MenuItemProps = MenuItemCommonProps & (MenuItemLinkMode | (MenuItemActionMode & CopyMode));
118
124
  /**
119
125
  * MenuItem — a single selectable row inside a `Menu`. Composes a leading
120
126
  * `icon`, a `label` (or `children`), an optional trailing `kbd` hint, and an
@@ -135,4 +141,4 @@ export type MenuItemProps = MenuItemCommonProps & (MenuItemLinkMode | MenuItemAc
135
141
  * </MenuItem>
136
142
  * ```
137
143
  */
138
- export declare const MenuItem: ({ icon, label, hint, kbd, iconTrailing, disabled, selected, selectionIndicator, indeterminate, indicator, tone, toneSelected, submenu, value, onSelect, href, target, rel, download, ping, role: roleOverride, className, style, children, ...rest }: MenuItemProps) => any;
144
+ export declare const MenuItem: ({ icon, label, hint, kbd, iconTrailing, disabled, selected, selectionIndicator, indeterminate, indicator, tone, toneSelected, submenu, value, onSelect, href, target, rel, download, ping, copy, copyNode, copyLazy, onCopied, onCopyRequest, role: roleOverride, className, style, children, ...rest }: MenuItemProps) => any;
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { toneStyle } from "../anta_helpers";
2
+ import { toneStyle, nativeStateChange } from "../anta_helpers";
3
3
  const MenuItem = ({
4
4
  icon,
5
5
  label,
@@ -21,12 +21,19 @@ const MenuItem = ({
21
21
  rel,
22
22
  download,
23
23
  ping,
24
+ copy,
25
+ copyNode,
26
+ copyLazy,
27
+ onCopied,
28
+ onCopyRequest,
24
29
  role: roleOverride,
25
30
  className,
26
31
  style,
27
32
  children,
28
33
  ...rest
29
34
  }) => {
35
+ const isCopy = copy != null || copyNode != null || copyLazy === true;
36
+ const leadingIcon = icon ?? (isCopy ? "copy" : void 0);
30
37
  const checkable = selectionIndicator === "checkbox" || selectionIndicator === "radio" || selectionIndicator === "check";
31
38
  const role = selectionIndicator === "checkbox" ? "menuitemcheckbox" : selectionIndicator === "radio" || selectionIndicator === "check" ? "menuitemradio" : "menuitem";
32
39
  const ariaChecked = !checkable ? void 0 : selectionIndicator === "checkbox" && indeterminate ? "mixed" : selected ? "true" : "false";
@@ -76,6 +83,12 @@ const MenuItem = ({
76
83
  "aria-haspopup": submenu ? "menu" : void 0,
77
84
  "aria-disabled": disabled ? "true" : void 0,
78
85
  onmenuselect: onSelect ? (e) => onSelect(e, { value, label }) : void 0,
86
+ copy: copy != null ? copy : void 0,
87
+ "copy-node": copyNode === true ? "" : typeof copyNode === "string" ? copyNode : void 0,
88
+ "copy-lazy": copyLazy ? "" : void 0,
89
+ "data-copy-node-button": copyNode != null && copyNode !== false ? "" : void 0,
90
+ oncopydone: onCopied ? (e) => onCopied(nativeStateChange(e).detail?.ok ?? false) : void 0,
91
+ oncopyrequest: onCopyRequest ? (e) => onCopyRequest(nativeStateChange(e).detail.provide) : void 0,
79
92
  class: className,
80
93
  ...rest,
81
94
  children: [
@@ -116,7 +129,7 @@ const MenuItem = ({
116
129
  }
117
130
  )
118
131
  ) : null,
119
- icon && /* @__PURE__ */ jsx("a-icon", { shape: icon, "aria-hidden": "true" }),
132
+ leadingIcon && /* @__PURE__ */ jsx("a-icon", { shape: leadingIcon, "aria-hidden": "true" }),
120
133
  labelNode,
121
134
  children,
122
135
  kbd && /* @__PURE__ */ jsx("kbd", { children: kbd }),
@@ -0,0 +1,33 @@
1
+ import { type MenuItemCommonProps, type MenuItemActionMode } from './MenuItem';
2
+ import type { CopyMode } from './Button';
3
+ /** The action-mode menu-item props plus a required copy target (a copying row
4
+ * must copy something). The target intersection drops `CopyMode`'s "no copy
5
+ * prop" member. */
6
+ export type MenuItemCopyProps = MenuItemCommonProps & MenuItemActionMode & CopyMode & ({
7
+ copy: string;
8
+ } | {
9
+ copyNode: boolean | string;
10
+ } | {
11
+ copyLazy: true;
12
+ });
13
+ /**
14
+ * Copying menu item — a `MenuItem` preset for copy-to-clipboard rows inside a
15
+ * `Menu`. Set `copy` for a literal string, `copyNode` to copy a DOM region, or
16
+ * `copyLazy` + `onCopyRequest` to compute the content on select. The
17
+ * `<a-menu-item>` element performs the write when the row is chosen; this wrapper
18
+ * flips the leading icon to a check / ✕ and retones for ~2s (see `useCopyFeedback`).
19
+ *
20
+ * The menu closes on select by default, so the feedback shows only when the row
21
+ * is kept open (`data-menu-open`). Defaults `label` to "Copy" and the leading
22
+ * `icon` to `copy`; override either. Other props pass through — `tone`, `kbd`,
23
+ * `onCopied`.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <Menu>
28
+ * <MenuItemCopy copy={shareUrl} label="Copy link" kbd="⌘C" />
29
+ * <MenuItemCopy copyNode=".invoice" label="Copy invoice" data-menu-open />
30
+ * </Menu>
31
+ * ```
32
+ */
33
+ export declare const MenuItemCopy: ({ label, icon, tone, onCopied, ...rest }: MenuItemCopyProps) => any;
@@ -0,0 +1,10 @@
1
+ import { jsx } from "@antadesign/anta/jsx-runtime";
2
+ import { MenuItem } from "./MenuItem";
3
+ import { useCopyFeedback } from "../anta_helpers";
4
+ const MenuItemCopy = ({ label = "Copy", icon = "copy", tone, onCopied, ...rest }) => {
5
+ const { shownIcon, shownTone, handleCopied } = useCopyFeedback(icon, tone, onCopied);
6
+ return /* @__PURE__ */ jsx(MenuItem, { label, icon: shownIcon, tone: shownTone, onCopied: handleCopied, ...rest });
7
+ };
8
+ export {
9
+ MenuItemCopy
10
+ };
@@ -24,18 +24,22 @@ export interface TitleProps extends BaseProps {
24
24
  * — so there are no `icon` / `iconTrailing` props; just compose
25
25
  * inside.
26
26
  *
27
- * Raw `<h1>`-`<h6>` get the same visual styling via `src/reset.css`,
28
- * so use a real heading tag if SEO matters and you don't need the
29
- * `tone` / `priority` props.
27
+ * `<Title>` carries no vertical margins: it's a spacing-neutral atom
28
+ * that drops into any container, and the parent owns spacing (a `gap`
29
+ * or padding). Raw `<h1>`-`<h6>` do keep per-level top/bottom margins
30
+ * (via `src/reset.css`) for document prose rhythm — so use a real
31
+ * heading tag if SEO matters or you want that built-in rhythm and don't
32
+ * need the `tone` / `priority` props.
30
33
  *
31
34
  * Styling notes (`a-title.css` ships comment-free): `<a-title>` is
32
35
  * intentionally CSS-only — no JS, no shadow DOM, not even
33
36
  * `customElements.define`; the browser treats it as a generic unknown
34
37
  * element and the CSS gives it block layout, the demi-bold variable-font
35
- * weight, and the per-level type scale + vertical rhythm. The matching
36
- * `h1`–`h6` rules in `src/reset.css` are kept in lockstep so raw markup
37
- * looks the same as `<Title level={n}>`. The tone × priority color matrix
38
- * has the same shape as `a-text`'s.
38
+ * weight, and the per-level type scale. The matching `h1`–`h6` rules in
39
+ * `src/reset.css` share that type scale and weight, so raw markup reads
40
+ * the same as `<Title level={n}>` — they diverge only on margins: raw
41
+ * headings carry the document vertical rhythm, `<a-title>` is margin-free.
42
+ * The tone × priority color matrix has the same shape as `a-text`'s.
39
43
  *
40
44
  * Requires `@antadesign/anta/elements` to be imported (client-side only)
41
45
  * so the CSS ships with the page.
@@ -63,5 +63,9 @@ declare global {
63
63
  */
64
64
  export declare class AButtonElement extends HTMLElementBase {
65
65
  connectedCallback(): void;
66
+ /** Perform a copy if this button is a copy control (`copy` / `copy-node` /
67
+ * `copy-lazy`). Called from the delegated click handler on activation. The
68
+ * outcome is announced via a `copydone` event the wrapper reflects. */
69
+ performCopy(): boolean;
66
70
  }
67
71
  export declare function register_a_button(): void;
@@ -1,4 +1,5 @@
1
1
  import { HTMLElementBase } from "../anta_helpers";
2
+ import { runCopy } from "./copy-behavior";
2
3
  import "./a-button.css";
3
4
  function installDocumentHandlers(doc) {
4
5
  if (!doc || doc.hasKeyListenerForAButton) return;
@@ -10,6 +11,12 @@ class AButtonElement extends HTMLElementBase {
10
11
  connectedCallback() {
11
12
  installDocumentHandlers(this.doc);
12
13
  }
14
+ /** Perform a copy if this button is a copy control (`copy` / `copy-node` /
15
+ * `copy-lazy`). Called from the delegated click handler on activation. The
16
+ * outcome is announced via a `copydone` event the wrapper reflects. */
17
+ performCopy() {
18
+ return runCopy(this);
19
+ }
13
20
  }
14
21
  function findForm(el) {
15
22
  const formId = el.getAttribute("form");
@@ -41,6 +48,7 @@ function handleClick(e) {
41
48
  new CustomEvent(customEvent, { bubbles: true, cancelable: true })
42
49
  );
43
50
  }
51
+ el.performCopy();
44
52
  const type = el.getAttribute("type") || "button";
45
53
  const form = findForm(el);
46
54
  if (!form) return;
@@ -1,10 +1,12 @@
1
1
  import { HTMLElementBase } from "../anta_helpers";
2
+ import { runCopy } from "./copy-behavior";
2
3
  import "./a-menu-item.css";
3
4
  class AMenuItemElement extends HTMLElementBase {
4
5
  internals;
5
6
  constructor() {
6
7
  super();
7
8
  this.internals = this.attachInternals?.();
9
+ this.addEventListener("menuselect", () => runCopy(this));
8
10
  }
9
11
  /** The active (combobox) cursor. `a-menu` sets this **property** (never an
10
12
  * attribute — no DOM mutation, same rule as `a-radio.selected`) as ArrowUp/Down
@@ -1 +1 @@
1
- @layer anta{a-text{--text-color: var(--text-2);--text-link-color: var(--link-color);--text-link-hover: var(--link-color-hover);display:block;color:var(--text-color);font-size:15px;line-height:20px;text-wrap:pretty;font-feature-settings:"ss02","ss05","tnum"}a-text[size=small]{font-size:13px;line-height:16px}a-text[size=medium]{font-size:15px;line-height:20px}a-text[size=large]{font-size:17px;line-height:24px}a-text[inline]{display:inline-block}a-text[truncate]{min-width:0}a-text[priority=primary]{--text-color: var(--text-1)}a-text[priority=tertiary]{--text-color: var(--text-3);--text-link-color: currentColor;--text-link-hover: var(--text-2)}a-text[priority=quaternary]{--text-color: var(--text-4);--text-link-color: currentColor;--text-link-hover: var(--text-3)}a-text[priority=quinary]{--text-color: var(--text-5);--text-link-color: currentColor;--text-link-hover: var(--text-4)}a-text[tone=brand]{--text-color: var(--text-2-brand);--text-link-color: currentColor;--text-link-hover: var(--text-1-brand)}a-text[tone=brand][priority=primary]{--text-color: var(--text-1-brand)}a-text[tone=brand][priority=tertiary]{--text-color: var(--text-3-brand);--text-link-hover: var(--text-2-brand)}a-text[tone=brand][priority=quaternary]{--text-color: var(--text-4-brand);--text-link-hover: var(--text-3-brand)}a-text[tone=brand][priority=quinary]{--text-color: var(--text-5-brand);--text-link-hover: var(--text-4-brand)}a-text[tone=success]{--text-color: var(--text-2-success);--text-link-color: currentColor;--text-link-hover: var(--text-1-success)}a-text[tone=success][priority=primary]{--text-color: var(--text-1-success)}a-text[tone=success][priority=tertiary]{--text-color: var(--text-3-success);--text-link-hover: var(--text-2-success)}a-text[tone=success][priority=quaternary]{--text-color: var(--text-4-success);--text-link-hover: var(--text-3-success)}a-text[tone=success][priority=quinary]{--text-color: var(--text-5-success);--text-link-hover: var(--text-4-success)}a-text[tone=critical]{--text-color: var(--text-2-critical);--text-link-color: currentColor;--text-link-hover: var(--text-1-critical)}a-text[tone=critical][priority=primary]{--text-color: var(--text-1-critical)}a-text[tone=critical][priority=tertiary]{--text-color: var(--text-3-critical);--text-link-hover: var(--text-2-critical)}a-text[tone=critical][priority=quaternary]{--text-color: var(--text-4-critical);--text-link-hover: var(--text-3-critical)}a-text[tone=critical][priority=quinary]{--text-color: var(--text-5-critical);--text-link-hover: var(--text-4-critical)}a-text[tone=warning]{--text-color: var(--text-2-warning);--text-link-color: currentColor;--text-link-hover: var(--text-1-warning)}a-text[tone=warning][priority=primary]{--text-color: var(--text-1-warning)}a-text[tone=warning][priority=tertiary]{--text-color: var(--text-3-warning);--text-link-hover: var(--text-2-warning)}a-text[tone=warning][priority=quaternary]{--text-color: var(--text-4-warning);--text-link-hover: var(--text-3-warning)}a-text[tone=warning][priority=quinary]{--text-color: var(--text-5-warning);--text-link-hover: var(--text-4-warning)}a-text[tone=info]{--text-color: var(--text-2-info);--text-link-color: currentColor;--text-link-hover: var(--text-1-info)}a-text[tone=info][priority=primary]{--text-color: var(--text-1-info)}a-text[tone=info][priority=tertiary]{--text-color: var(--text-3-info);--text-link-hover: var(--text-2-info)}a-text[tone=info][priority=quaternary]{--text-color: var(--text-4-info);--text-link-hover: var(--text-3-info)}a-text[tone=info][priority=quinary]{--text-color: var(--text-5-info);--text-link-hover: var(--text-4-info)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--text-tone-source: attr(tone type(<color>), currentColor);--_text-tone: oklch(from var(--text-tone-source) .41 .15 h);--text-color: var(--_text-tone);--text-link-color: currentColor;--text-link-hover: oklch(from var(--text-tone-source) .33 .15 h)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=primary]{--text-color: oklch(from var(--text-tone-source) .33 .15 h)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=tertiary]{--text-color: color-mix(in oklch, var(--_text-tone) 80%, transparent);--text-link-hover: var(--_text-tone)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quaternary]{--text-color: color-mix(in oklch, var(--_text-tone) 60%, transparent)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quinary]{--text-color: color-mix(in oklch, var(--_text-tone) 40%, transparent)}.dark a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--_text-tone: oklch(from var(--text-tone-source) .75 .11 h);--text-link-hover: oklch(from var(--text-tone-source) .82 .1 h)}.dark a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=primary]{--text-color: oklch(from var(--text-tone-source) .82 .1 h)}a-text a,a-text a:link,a-text a:visited{color:var(--text-link-color)}@media(hover:hover)and (pointer:fine){a-text a:hover{color:var(--text-link-hover);text-decoration-color:var(--text-link-hover)}}}
1
+ @layer anta{a-text{--text-color: var(--text-2);--text-link-color: var(--link-color);--text-link-hover: var(--link-color-hover);display:block;font-family:var(--sans-serif);color:var(--text-color);font-size:15px;line-height:20px;text-wrap:pretty;font-feature-settings:"ss02","ss05","tnum"}a-text[size=small]{font-size:13px;line-height:16px}a-text[size=medium]{font-size:15px;line-height:20px}a-text[size=large]{font-size:17px;line-height:24px}a-text[inline]{display:inline-block}a-text[truncate]{min-width:0}a-text[priority=primary]{--text-color: var(--text-1)}a-text[priority=tertiary]{--text-color: var(--text-3);--text-link-color: currentColor;--text-link-hover: var(--text-2)}a-text[priority=quaternary]{--text-color: var(--text-4);--text-link-color: currentColor;--text-link-hover: var(--text-3)}a-text[priority=quinary]{--text-color: var(--text-5);--text-link-color: currentColor;--text-link-hover: var(--text-4)}a-text[tone=brand]{--text-color: var(--text-2-brand);--text-link-color: currentColor;--text-link-hover: var(--text-1-brand)}a-text[tone=brand][priority=primary]{--text-color: var(--text-1-brand)}a-text[tone=brand][priority=tertiary]{--text-color: var(--text-3-brand);--text-link-hover: var(--text-2-brand)}a-text[tone=brand][priority=quaternary]{--text-color: var(--text-4-brand);--text-link-hover: var(--text-3-brand)}a-text[tone=brand][priority=quinary]{--text-color: var(--text-5-brand);--text-link-hover: var(--text-4-brand)}a-text[tone=success]{--text-color: var(--text-2-success);--text-link-color: currentColor;--text-link-hover: var(--text-1-success)}a-text[tone=success][priority=primary]{--text-color: var(--text-1-success)}a-text[tone=success][priority=tertiary]{--text-color: var(--text-3-success);--text-link-hover: var(--text-2-success)}a-text[tone=success][priority=quaternary]{--text-color: var(--text-4-success);--text-link-hover: var(--text-3-success)}a-text[tone=success][priority=quinary]{--text-color: var(--text-5-success);--text-link-hover: var(--text-4-success)}a-text[tone=critical]{--text-color: var(--text-2-critical);--text-link-color: currentColor;--text-link-hover: var(--text-1-critical)}a-text[tone=critical][priority=primary]{--text-color: var(--text-1-critical)}a-text[tone=critical][priority=tertiary]{--text-color: var(--text-3-critical);--text-link-hover: var(--text-2-critical)}a-text[tone=critical][priority=quaternary]{--text-color: var(--text-4-critical);--text-link-hover: var(--text-3-critical)}a-text[tone=critical][priority=quinary]{--text-color: var(--text-5-critical);--text-link-hover: var(--text-4-critical)}a-text[tone=warning]{--text-color: var(--text-2-warning);--text-link-color: currentColor;--text-link-hover: var(--text-1-warning)}a-text[tone=warning][priority=primary]{--text-color: var(--text-1-warning)}a-text[tone=warning][priority=tertiary]{--text-color: var(--text-3-warning);--text-link-hover: var(--text-2-warning)}a-text[tone=warning][priority=quaternary]{--text-color: var(--text-4-warning);--text-link-hover: var(--text-3-warning)}a-text[tone=warning][priority=quinary]{--text-color: var(--text-5-warning);--text-link-hover: var(--text-4-warning)}a-text[tone=info]{--text-color: var(--text-2-info);--text-link-color: currentColor;--text-link-hover: var(--text-1-info)}a-text[tone=info][priority=primary]{--text-color: var(--text-1-info)}a-text[tone=info][priority=tertiary]{--text-color: var(--text-3-info);--text-link-hover: var(--text-2-info)}a-text[tone=info][priority=quaternary]{--text-color: var(--text-4-info);--text-link-hover: var(--text-3-info)}a-text[tone=info][priority=quinary]{--text-color: var(--text-5-info);--text-link-hover: var(--text-4-info)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--text-tone-source: attr(tone type(<color>), currentColor);--_text-tone: oklch(from var(--text-tone-source) .41 .15 h);--text-color: var(--_text-tone);--text-link-color: currentColor;--text-link-hover: oklch(from var(--text-tone-source) .33 .15 h)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=primary]{--text-color: oklch(from var(--text-tone-source) .33 .15 h)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=tertiary]{--text-color: color-mix(in oklch, var(--_text-tone) 80%, transparent);--text-link-hover: var(--_text-tone)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quaternary]{--text-color: color-mix(in oklch, var(--_text-tone) 60%, transparent)}a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quinary]{--text-color: color-mix(in oklch, var(--_text-tone) 40%, transparent)}.dark a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--_text-tone: oklch(from var(--text-tone-source) .75 .11 h);--text-link-hover: oklch(from var(--text-tone-source) .82 .1 h)}.dark a-text[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=primary]{--text-color: oklch(from var(--text-tone-source) .82 .1 h)}a-text a,a-text a:link,a-text a:visited{color:var(--text-link-color)}@media(hover:hover)and (pointer:fine){a-text a:hover{color:var(--text-link-hover);text-decoration-color:var(--text-link-hover)}}}
@@ -1 +1 @@
1
- @layer anta{a-title{display:block;color:var(--text-1);font-weight:584.62;letter-spacing:0;text-wrap:balance;font-feature-settings:"ss02","ss05","tnum"}a-title a-icon{transform:translateY(-.05em)}a-title[level="1"]{font-size:28px;line-height:32px;margin-block-start:0;margin-block-end:16px}a-title[level="2"]{font-size:24px;line-height:28px;margin-block-start:32px;margin-block-end:12px}a-title[level="3"]{font-size:20px;line-height:24px;margin-block-start:24px;margin-block-end:12px}a-title[level="4"]{font-size:17px;line-height:20px;margin-block-start:20px;margin-block-end:8px}a-title[level="5"]{font-size:15px;line-height:20px;margin-block-start:16px;margin-block-end:8px}a-title[level="6"]{font-size:13px;line-height:16px;margin-block-start:16px;margin-block-end:4px}a-title[priority=secondary]{color:var(--text-2)}a-title[priority=tertiary]{color:var(--text-3)}a-title[priority=quaternary]{color:var(--text-4)}a-title[priority=quinary]{color:var(--text-5)}a-title[tone=brand]{color:var(--text-1-brand)}a-title[tone=brand][priority=secondary]{color:var(--text-2-brand)}a-title[tone=brand][priority=tertiary]{color:var(--text-3-brand)}a-title[tone=brand][priority=quaternary]{color:var(--text-4-brand)}a-title[tone=brand][priority=quinary]{color:var(--text-5-brand)}a-title[tone=success]{color:var(--text-1-success)}a-title[tone=success][priority=secondary]{color:var(--text-2-success)}a-title[tone=success][priority=tertiary]{color:var(--text-3-success)}a-title[tone=success][priority=quaternary]{color:var(--text-4-success)}a-title[tone=success][priority=quinary]{color:var(--text-5-success)}a-title[tone=critical]{color:var(--text-1-critical)}a-title[tone=critical][priority=secondary]{color:var(--text-2-critical)}a-title[tone=critical][priority=tertiary]{color:var(--text-3-critical)}a-title[tone=critical][priority=quaternary]{color:var(--text-4-critical)}a-title[tone=critical][priority=quinary]{color:var(--text-5-critical)}a-title[tone=warning]{color:var(--text-1-warning)}a-title[tone=warning][priority=secondary]{color:var(--text-2-warning)}a-title[tone=warning][priority=tertiary]{color:var(--text-3-warning)}a-title[tone=warning][priority=quaternary]{color:var(--text-4-warning)}a-title[tone=warning][priority=quinary]{color:var(--text-5-warning)}a-title[tone=info]{color:var(--text-1-info)}a-title[tone=info][priority=secondary]{color:var(--text-2-info)}a-title[tone=info][priority=tertiary]{color:var(--text-3-info)}a-title[tone=info][priority=quaternary]{color:var(--text-4-info)}a-title[tone=info][priority=quinary]{color:var(--text-5-info)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--title-tone-source: attr(tone type(<color>), currentColor);--_title-tone: oklch(from var(--title-tone-source) .41 .15 h);color:oklch(from var(--title-tone-source) .33 .15 h)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=secondary]{color:var(--_title-tone)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=tertiary]{color:color-mix(in oklch,var(--_title-tone) 80%,transparent)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quaternary]{color:color-mix(in oklch,var(--_title-tone) 60%,transparent)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quinary]{color:color-mix(in oklch,var(--_title-tone) 40%,transparent)}.dark a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--_title-tone: oklch(from var(--title-tone-source) .75 .11 h);color:oklch(from var(--title-tone-source) .82 .1 h)}}
1
+ @layer anta{a-title{display:block;font-family:var(--sans-serif);color:var(--text-1);font-weight:584.62;letter-spacing:0;text-wrap:balance;font-feature-settings:"ss02","ss05","tnum"}a-title a-icon{transform:translateY(-.05em)}a-title[level="1"]{font-size:28px;line-height:32px}a-title[level="2"]{font-size:24px;line-height:28px}a-title[level="3"]{font-size:20px;line-height:24px}a-title[level="4"]{font-size:17px;line-height:20px}a-title[level="5"]{font-size:15px;line-height:20px}a-title[level="6"]{font-size:13px;line-height:16px}a-title[priority=secondary]{color:var(--text-2)}a-title[priority=tertiary]{color:var(--text-3)}a-title[priority=quaternary]{color:var(--text-4)}a-title[priority=quinary]{color:var(--text-5)}a-title[tone=brand]{color:var(--text-1-brand)}a-title[tone=brand][priority=secondary]{color:var(--text-2-brand)}a-title[tone=brand][priority=tertiary]{color:var(--text-3-brand)}a-title[tone=brand][priority=quaternary]{color:var(--text-4-brand)}a-title[tone=brand][priority=quinary]{color:var(--text-5-brand)}a-title[tone=success]{color:var(--text-1-success)}a-title[tone=success][priority=secondary]{color:var(--text-2-success)}a-title[tone=success][priority=tertiary]{color:var(--text-3-success)}a-title[tone=success][priority=quaternary]{color:var(--text-4-success)}a-title[tone=success][priority=quinary]{color:var(--text-5-success)}a-title[tone=critical]{color:var(--text-1-critical)}a-title[tone=critical][priority=secondary]{color:var(--text-2-critical)}a-title[tone=critical][priority=tertiary]{color:var(--text-3-critical)}a-title[tone=critical][priority=quaternary]{color:var(--text-4-critical)}a-title[tone=critical][priority=quinary]{color:var(--text-5-critical)}a-title[tone=warning]{color:var(--text-1-warning)}a-title[tone=warning][priority=secondary]{color:var(--text-2-warning)}a-title[tone=warning][priority=tertiary]{color:var(--text-3-warning)}a-title[tone=warning][priority=quaternary]{color:var(--text-4-warning)}a-title[tone=warning][priority=quinary]{color:var(--text-5-warning)}a-title[tone=info]{color:var(--text-1-info)}a-title[tone=info][priority=secondary]{color:var(--text-2-info)}a-title[tone=info][priority=tertiary]{color:var(--text-3-info)}a-title[tone=info][priority=quaternary]{color:var(--text-4-info)}a-title[tone=info][priority=quinary]{color:var(--text-5-info)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--title-tone-source: attr(tone type(<color>), currentColor);--_title-tone: oklch(from var(--title-tone-source) .41 .15 h);color:oklch(from var(--title-tone-source) .33 .15 h)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=secondary]{color:var(--_title-tone)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=tertiary]{color:color-mix(in oklch,var(--_title-tone) 80%,transparent)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quaternary]{color:color-mix(in oklch,var(--_title-tone) 60%,transparent)}a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical])[priority=quinary]{color:color-mix(in oklch,var(--_title-tone) 40%,transparent)}.dark a-title[tone]:not([tone=neutral],[tone=brand],[tone=info],[tone=success],[tone=warning],[tone=critical]){--_title-tone: oklch(from var(--title-tone-source) .75 .11 h);color:oklch(from var(--title-tone-source) .82 .1 h)}}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared copy-to-clipboard behavior for `<a-button>` and `<a-menu-item>` — the
3
+ * write half of the productized "copy button" / "copying menu item". The element
4
+ * performs the clipboard write itself (rather than a UI-thread callback) and
5
+ * reports the outcome with a `copydone` event; the JSX wrapper listens and
6
+ * orchestrates the visual feedback (icon + tone swap). Nothing here touches the
7
+ * DOM — no light-DOM mutation, no host attributes, no ElementInternals.
8
+ *
9
+ * Events are dispatched non-bubbling (`bubbles: false`): the wrapper binds on the
10
+ * host itself, so it catches them in the target phase, and not bubbling keeps a
11
+ * nested copy row's `copydone` from flipping an ancestor copy row's feedback
12
+ * (the same point-to-point rule Anta's `statechange` follows).
13
+ *
14
+ * Three modes, chosen by attribute:
15
+ * - `copy="<text>"` — copy the literal string on activation.
16
+ * - `copy-node` / `copy-node="<selector>"` — copy a DOM node as rich text
17
+ * (`text/html`) + plain text. Bare `copy-node` copies the nearest ancestor
18
+ * marked `[data-copy-source]`; a value is a selector resolved with `closest()`
19
+ * (an ancestor region). The copy control is stripped from the serialized
20
+ * output (`[data-copy-node-button]`).
21
+ * - `copy-lazy` — the content isn't known until the click. Activation dispatches
22
+ * `copyrequest` whose `detail.provide(text)` the consumer calls with the value
23
+ * (synchronously, or after an `await` — the browser's transient-activation
24
+ * window still covers the write). Nothing lands in the DOM, and there's no
25
+ * attribute round-trip to reset. Takes precedence over any `copy` value.
26
+ *
27
+ * Precedence when several are set (the discriminated-union prop types make this
28
+ * unreachable from the wrappers, but hand-authored markup can): node → lazy →
29
+ * literal `copy`.
30
+ */
31
+ /**
32
+ * Perform a copy on activation (click / Enter / Space / menu select). Returns
33
+ * `true` if the element is a copy control (so the caller knows the copy path
34
+ * ran); `false` for a plain, non-copy element.
35
+ */
36
+ export declare function runCopy(el: HTMLElement): boolean;
@@ -0,0 +1,68 @@
1
+ async function writeText(text) {
2
+ try {
3
+ await navigator.clipboard.writeText(text);
4
+ return true;
5
+ } catch {
6
+ return false;
7
+ }
8
+ }
9
+ async function writeNode(node) {
10
+ const clone = node.cloneNode(true);
11
+ clone.querySelectorAll("[data-copy-node-button]").forEach((n) => n.remove());
12
+ const html = clone.outerHTML;
13
+ const text = clone.textContent ?? "";
14
+ try {
15
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
16
+ await navigator.clipboard.write([
17
+ new ClipboardItem({
18
+ "text/html": new Blob([html], { type: "text/html" }),
19
+ "text/plain": new Blob([text], { type: "text/plain" })
20
+ })
21
+ ]);
22
+ return true;
23
+ }
24
+ await navigator.clipboard.writeText(text);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ function resolveNodeTarget(el) {
31
+ const sel = el.getAttribute("copy-node");
32
+ if (sel == null) return null;
33
+ return el.closest(sel === "" ? "[data-copy-source]" : sel);
34
+ }
35
+ function run(el, work) {
36
+ void work.then((ok) => {
37
+ el.dispatchEvent(new CustomEvent("copydone", { bubbles: false, detail: { ok } }));
38
+ });
39
+ }
40
+ function runCopy(el) {
41
+ if (el.hasAttribute("copy-node")) {
42
+ const target = resolveNodeTarget(el);
43
+ run(el, target ? writeNode(target) : Promise.resolve(false));
44
+ return true;
45
+ }
46
+ if (el.hasAttribute("copy-lazy")) {
47
+ let done = false;
48
+ el.dispatchEvent(
49
+ new CustomEvent("copyrequest", {
50
+ bubbles: false,
51
+ detail: {
52
+ provide(text) {
53
+ if (done) return;
54
+ done = true;
55
+ run(el, writeText(String(text ?? "")));
56
+ }
57
+ }
58
+ })
59
+ );
60
+ return true;
61
+ }
62
+ if (!el.hasAttribute("copy")) return false;
63
+ run(el, writeText(el.getAttribute("copy") ?? ""));
64
+ return true;
65
+ }
66
+ export {
67
+ runCopy
68
+ };
@@ -727,6 +727,26 @@ export interface AMenuItemAttributes extends BaseAttributes {
727
727
  * a `MouseEvent`, carrying the modifier keys (e.g. `altKey`). All-lowercase so
728
728
  * React/Preact bind it to the CustomEvent. */
729
729
  onmenuselect?: (e: MouseEvent) => void;
730
+ /** Text copied to the clipboard when the row is chosen (a "copying menu item").
731
+ * The element performs the write itself on `menuselect`. */
732
+ copy?: string;
733
+ /** Copy a DOM node instead of a string (rich `text/html` + plain text). Bare
734
+ * `copy-node` copies the nearest ancestor marked `[data-copy-source]`; a value
735
+ * is a CSS selector resolved with `closest()`. Presence-based for the boolean
736
+ * form. */
737
+ 'copy-node'?: boolean | string;
738
+ /** Lazy copy: with an empty `copy`, activation dispatches `copyrequest` and the
739
+ * write completes once `copy` is set back (within the activation window).
740
+ * Presence-based. */
741
+ 'copy-lazy'?: boolean | '';
742
+ /** Fired after a copy attempt (bubbling `copydone`, `detail: { ok }`).
743
+ * All-lowercase so it binds in React *and* Preact. */
744
+ oncopydone?: (e: CustomEvent<{
745
+ ok: boolean;
746
+ }>) => void;
747
+ /** Fired on a lazy copy activation to request the content (bubbling
748
+ * `copyrequest`). Answer by setting the `copy` attribute. All-lowercase. */
749
+ oncopyrequest?: (e: CustomEvent) => void;
730
750
  }
731
751
  /**
732
752
  * Attributes for the `<a-menu-group>` styled element. For the typed JSX
@@ -770,6 +790,30 @@ export interface AButtonAttributes extends BaseAttributes {
770
790
  form?: string;
771
791
  /** Custom event name dispatched (bubbling) on click. */
772
792
  'data-custom-event'?: string;
793
+ /** Text copied to the clipboard on activation (the "copy button"). The element
794
+ * performs the write itself and flashes a success / failure state. */
795
+ copy?: string;
796
+ /** Copy a DOM node instead of a string (rich `text/html` + plain text). Bare
797
+ * `copy-node` copies the nearest ancestor marked `[data-copy-source]`; a value
798
+ * is a CSS selector resolved with `closest()` (an ancestor region). Presence-
799
+ * based for the boolean form. */
800
+ 'copy-node'?: boolean | string;
801
+ /** Lazy copy: activation dispatches `copyrequest`; the consumer calls
802
+ * `detail.provide(text)` with the content, which stays out of the DOM until
803
+ * then. Takes precedence over `copy`. Presence-based. */
804
+ 'copy-lazy'?: boolean | '';
805
+ /** Fired after a copy attempt (`copydone`, `detail: { ok }`, non-bubbling so a
806
+ * nested copy row can't flip an ancestor's feedback). All-lowercase so it
807
+ * binds in React *and* Preact. */
808
+ oncopydone?: (e: CustomEvent<{
809
+ ok: boolean;
810
+ }>) => void;
811
+ /** Fired on a lazy copy activation (`copyrequest`, non-bubbling). Supply the
812
+ * content by calling `detail.provide(text)` — synchronously or within the
813
+ * click's activation window. All-lowercase. */
814
+ oncopyrequest?: (e: CustomEvent<{
815
+ provide: (text: string) => void;
816
+ }>) => void;
773
817
  'aria-disabled'?: 'true' | 'false' | boolean;
774
818
  'aria-busy'?: 'true' | 'false' | boolean;
775
819
  }
package/dist/index.d.ts CHANGED
@@ -27,7 +27,9 @@ export type { TagProps } from './components/Tag';
27
27
  export { Icon } from './components/Icon';
28
28
  export type { IconProps } from './components/Icon';
29
29
  export { Button } from './components/Button';
30
- export type { ButtonProps, BaseButtonProps, ContentMode, SubmitMode, PriorityMode, } from './components/Button';
30
+ export type { ButtonProps, BaseButtonProps, ContentMode, SubmitMode, PriorityMode, CopyMode, } from './components/Button';
31
+ export { ButtonCopy } from './components/ButtonCopy';
32
+ export type { ButtonCopyProps } from './components/ButtonCopy';
31
33
  export { ICON_SHAPES, ICON_SYNONYMS } from './elements/a-icon.shapes';
32
34
  export { Tooltip } from './components/Tooltip';
33
35
  export type { TooltipProps } from './components/Tooltip';
@@ -37,6 +39,8 @@ export { Menu } from './components/Menu';
37
39
  export type { MenuProps } from './components/Menu';
38
40
  export { MenuItem } from './components/MenuItem';
39
41
  export type { MenuItemProps } from './components/MenuItem';
42
+ export { MenuItemCopy } from './components/MenuItemCopy';
43
+ export type { MenuItemCopyProps } from './components/MenuItemCopy';
40
44
  export { MenuSeparator } from './components/MenuSeparator';
41
45
  export type { MenuSeparatorProps } from './components/MenuSeparator';
42
46
  export { MenuGroup } from './components/MenuGroup';
package/dist/index.js CHANGED
@@ -4,11 +4,13 @@ import { Title } from "./components/Title";
4
4
  import { Tag } from "./components/Tag";
5
5
  import { Icon } from "./components/Icon";
6
6
  import { Button } from "./components/Button";
7
+ import { ButtonCopy } from "./components/ButtonCopy";
7
8
  import { ICON_SHAPES, ICON_SYNONYMS } from "./elements/a-icon.shapes";
8
9
  import { Tooltip } from "./components/Tooltip";
9
10
  import { Checkbox } from "./components/Checkbox";
10
11
  import { Menu } from "./components/Menu";
11
12
  import { MenuItem } from "./components/MenuItem";
13
+ import { MenuItemCopy } from "./components/MenuItemCopy";
12
14
  import { MenuSeparator } from "./components/MenuSeparator";
13
15
  import { MenuGroup } from "./components/MenuGroup";
14
16
  import { Expander } from "./components/Expander";
@@ -43,6 +45,7 @@ import { Card } from "./components/Card";
43
45
  import { configure } from "./jsx-runtime";
44
46
  export {
45
47
  Button,
48
+ ButtonCopy,
46
49
  Calendar,
47
50
  Card,
48
51
  Checkbox,
@@ -58,6 +61,7 @@ export {
58
61
  Menu,
59
62
  MenuGroup,
60
63
  MenuItem,
64
+ MenuItemCopy,
61
65
  MenuSeparator,
62
66
  Progress,
63
67
  RadioGroup,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antadesign/anta",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",