@antadesign/anta 0.3.5 → 0.3.7
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.
- package/dist/anta_helpers.d.ts +10 -0
- package/dist/anta_helpers.js +8 -0
- package/dist/components/Card.d.ts +86 -0
- package/dist/components/Card.js +72 -0
- package/dist/components/Dialog.d.ts +92 -0
- package/dist/components/Dialog.js +71 -0
- package/dist/components/Input.d.ts +3 -0
- package/dist/components/InputAutocomplete.d.ts +69 -0
- package/dist/components/InputAutocomplete.js +95 -0
- package/dist/components/InputDate.js +1 -2
- package/dist/components/Menu.d.ts +4 -1
- package/dist/components/Menu.js +2 -1
- package/dist/components/MenuItem.d.ts +9 -1
- package/dist/components/MenuItem.js +2 -1
- package/dist/components/Select.d.ts +5 -2
- package/dist/components/Select.js +11 -34
- package/dist/components/SelectFaceted.d.ts +7 -1
- package/dist/components/SelectFaceted.js +12 -11
- package/dist/components/select-options.d.ts +14 -0
- package/dist/components/select-options.js +31 -0
- package/dist/elements/a-card.css +211 -0
- package/dist/elements/a-card.d.ts +10 -0
- package/dist/elements/a-card.js +213 -0
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-dialog.css +1 -0
- package/dist/elements/a-dialog.d.ts +26 -0
- package/dist/elements/a-dialog.js +386 -0
- package/dist/elements/a-expander.js +2 -2
- package/dist/elements/a-input-time.js +2 -1
- package/dist/elements/a-input.d.ts +2 -0
- package/dist/elements/a-input.js +34 -1
- package/dist/elements/a-menu.d.ts +5 -2
- package/dist/elements/a-menu.js +52 -15
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-tooltip.d.ts +17 -0
- package/dist/elements/a-tooltip.js +28 -0
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +3 -0
- package/dist/general_types.d.ts +45 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/jsx-runtime.d.ts +2 -1
- package/package.json +1 -1
package/dist/anta_helpers.d.ts
CHANGED
|
@@ -22,6 +22,16 @@ export declare function nativeStateChange<D>(e: CustomEvent<D> | {
|
|
|
22
22
|
event: CustomEvent<D>;
|
|
23
23
|
detail?: D;
|
|
24
24
|
};
|
|
25
|
+
export declare const IS_MAC: boolean;
|
|
26
|
+
/** Default row hint teaching the Alt/⌥-click "select only this" accelerator on
|
|
27
|
+
* bulk-select rows (`Select` multiple, `SelectFaceted` multiple facets). Shared
|
|
28
|
+
* so the wording stays identical across both. */
|
|
29
|
+
export declare const ISOLATE_HINT: string;
|
|
30
|
+
/** Parse an open/closed `state` / `default-state` attribute — the shared
|
|
31
|
+
* open-state vocabulary (`a-dialog`, `a-expander`, …). Anything but the literal
|
|
32
|
+
* `'open'` is `'closed'`, so an absent / malformed attribute reads closed. Kept
|
|
33
|
+
* here so the two elements can't drift on how the token is parsed. */
|
|
34
|
+
export declare function parseOpenState(v: string | null): 'open' | 'closed';
|
|
25
35
|
/** The six named tones every toned component shares. Anything else is a literal
|
|
26
36
|
* CSS colour the element resolves through its `--{component}-tone-source` var. */
|
|
27
37
|
export declare const NAMED_TONES: Set<string>;
|
package/dist/anta_helpers.js
CHANGED
|
@@ -16,6 +16,11 @@ function nativeStateChange(e) {
|
|
|
16
16
|
const event = "nativeEvent" in e ? e.nativeEvent : e;
|
|
17
17
|
return { event, detail: event?.detail };
|
|
18
18
|
}
|
|
19
|
+
const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/i.test(navigator.userAgent || "");
|
|
20
|
+
const ISOLATE_HINT = IS_MAC ? "\u2325+Click to select only this" : "Alt+Click to select only this";
|
|
21
|
+
function parseOpenState(v) {
|
|
22
|
+
return v === "open" ? "open" : "closed";
|
|
23
|
+
}
|
|
19
24
|
const NAMED_TONES = /* @__PURE__ */ new Set([
|
|
20
25
|
"brand",
|
|
21
26
|
"neutral",
|
|
@@ -105,6 +110,8 @@ class SelectableChildElement extends HTMLElementBase {
|
|
|
105
110
|
}
|
|
106
111
|
export {
|
|
107
112
|
HTMLElementBase,
|
|
113
|
+
ISOLATE_HINT,
|
|
114
|
+
IS_MAC,
|
|
108
115
|
NAMED_TONES,
|
|
109
116
|
SelectableChildElement,
|
|
110
117
|
anchorRect,
|
|
@@ -112,6 +119,7 @@ export {
|
|
|
112
119
|
isInsideOpenMenu,
|
|
113
120
|
isMenuOpen,
|
|
114
121
|
nativeStateChange,
|
|
122
|
+
parseOpenState,
|
|
115
123
|
roundStyle,
|
|
116
124
|
setMenuPresence,
|
|
117
125
|
toneStyle,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { BaseProps } from "../general_types";
|
|
2
|
+
export interface CardProps extends BaseProps {
|
|
3
|
+
/** Header content, rendered in the top zone with the default title
|
|
4
|
+
* typography. A string is respected as-is; pass a `<Title>` (or `<h2>`–`<h6>`)
|
|
5
|
+
* for real heading semantics in the document outline. Omit for no header. */
|
|
6
|
+
header?: React.ReactNode;
|
|
7
|
+
/** Footer content, usually action buttons. Rendered as a **left-aligned** row
|
|
8
|
+
* (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. */
|
|
9
|
+
footer?: React.ReactNode;
|
|
10
|
+
/** Controls rendered at the top-right of the header row, OUTSIDE the header
|
|
11
|
+
* (e.g. a "more" button, a tag) — the same slot as `Expander`'s `actions`. */
|
|
12
|
+
actions?: React.ReactNode;
|
|
13
|
+
/** Media (image, illustration) rendered full-bleed to the card edge, clipped to
|
|
14
|
+
* its corners. Position it with `mediaPosition`. */
|
|
15
|
+
media?: React.ReactNode;
|
|
16
|
+
/** Which edge the `media` bleeds to.
|
|
17
|
+
* @defaultValue 'top' */
|
|
18
|
+
mediaPosition?: "top" | "bottom" | "left" | "right";
|
|
19
|
+
/** The card body. A string renders with the default `<Text>`-like typography;
|
|
20
|
+
* scopes the color hierarchy for any nested content. */
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
/** Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for
|
|
23
|
+
* a one-off custom tone. Named tones re-point the surface + text; a custom
|
|
24
|
+
* color keeps its hue while lightness/chroma are pinned. `'neutral'` (the
|
|
25
|
+
* default) is the same as omitting it.
|
|
26
|
+
* @defaultValue 'neutral' */
|
|
27
|
+
tone?: "neutral" | "brand" | "info" | "success" | "warning" | "critical" | (string & {});
|
|
28
|
+
/** Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a
|
|
29
|
+
* subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop
|
|
30
|
+
* blur.
|
|
31
|
+
* @defaultValue 'primary' */
|
|
32
|
+
priority?: "primary" | "secondary" | "tertiary";
|
|
33
|
+
/** Size variant — scales the padding.
|
|
34
|
+
* @defaultValue 'medium' */
|
|
35
|
+
size?: "small" | "medium" | "large";
|
|
36
|
+
/** Selected / chosen state — draws an inset ring in the tone color, for
|
|
37
|
+
* choice-card / plan-picker patterns. */
|
|
38
|
+
selected?: boolean;
|
|
39
|
+
/** Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in
|
|
40
|
+
* link mode) blocks navigation. */
|
|
41
|
+
loading?: boolean;
|
|
42
|
+
/** Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
|
|
43
|
+
* `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for
|
|
44
|
+
* the default `--card-radius`. */
|
|
45
|
+
round?: boolean | number | string;
|
|
46
|
+
/** Turn the whole card into a link. The card renders a focusable anchor and its
|
|
47
|
+
* accessible name comes from `header` → body → this URL (override with
|
|
48
|
+
* `aria-label`). A link card is display content — don't nest interactive
|
|
49
|
+
* controls inside it. */
|
|
50
|
+
href?: string;
|
|
51
|
+
/** Anchor target (only with `href`). */
|
|
52
|
+
target?: string;
|
|
53
|
+
/** Anchor rel (only with `href`). */
|
|
54
|
+
rel?: string;
|
|
55
|
+
/** Space-separated URLs the browser pings on navigation (only with `href`). */
|
|
56
|
+
ping?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A surface container: a bordered, toned box laying out an optional `media`
|
|
60
|
+
* region plus a `header` / body / `footer` stack. Pass `href` to turn the whole
|
|
61
|
+
* card into a link.
|
|
62
|
+
*
|
|
63
|
+
* Renders an `<a-card>` web component. A pure, stateless pass-through — the
|
|
64
|
+
* element owns layout, the link, and the accessible name — so the wrapper only
|
|
65
|
+
* maps props to attributes and projects content into the `media` / `header` /
|
|
66
|
+
* `actions` / body / `footer` slots. Header / body / footer are respected as
|
|
67
|
+
* passed; the element supplies their default typography.
|
|
68
|
+
*
|
|
69
|
+
* Requires `@antadesign/anta/elements` to be imported (client-side only) to
|
|
70
|
+
* register the underlying custom element.
|
|
71
|
+
*
|
|
72
|
+
* @example Basic usage
|
|
73
|
+
* ```tsx
|
|
74
|
+
* <Card header="Deployment ready" footer={<Button priority="primary" label="Deploy" />}>
|
|
75
|
+
* Your build passed all checks and is ready to ship.
|
|
76
|
+
* </Card>
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example Link card
|
|
80
|
+
* ```tsx
|
|
81
|
+
* <Card href="/docs/ci" header="CI integration guide">
|
|
82
|
+
* Wire Antithesis into your pipeline in a few minutes.
|
|
83
|
+
* </Card>
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare const Card: ({ header, footer, actions, media, mediaPosition, tone, priority, size, selected, loading, round, href, target, rel, ping, className, style, children, ...rest }: CardProps) => any;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
|
|
2
|
+
import { toneStyle, roundStyle } from "../anta_helpers";
|
|
3
|
+
const Card = ({
|
|
4
|
+
header,
|
|
5
|
+
footer,
|
|
6
|
+
actions,
|
|
7
|
+
media,
|
|
8
|
+
mediaPosition,
|
|
9
|
+
tone,
|
|
10
|
+
priority,
|
|
11
|
+
size,
|
|
12
|
+
selected,
|
|
13
|
+
loading,
|
|
14
|
+
round,
|
|
15
|
+
href,
|
|
16
|
+
target,
|
|
17
|
+
rel,
|
|
18
|
+
ping,
|
|
19
|
+
className,
|
|
20
|
+
style,
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
23
|
+
}) => {
|
|
24
|
+
const toneAttr = tone || void 0;
|
|
25
|
+
const computedStyle = roundStyle(
|
|
26
|
+
round,
|
|
27
|
+
"--card-round",
|
|
28
|
+
toneStyle(toneAttr, "--card-tone-source", style)
|
|
29
|
+
);
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
"a-card",
|
|
32
|
+
{
|
|
33
|
+
tone: toneAttr,
|
|
34
|
+
priority: priority && priority !== "primary" ? priority : void 0,
|
|
35
|
+
size: size && size !== "medium" ? size : void 0,
|
|
36
|
+
"media-position": mediaPosition && mediaPosition !== "top" ? mediaPosition : void 0,
|
|
37
|
+
selected: selected ? "" : void 0,
|
|
38
|
+
loading: loading ? "" : void 0,
|
|
39
|
+
round: round ? "" : void 0,
|
|
40
|
+
href,
|
|
41
|
+
target: href ? target : void 0,
|
|
42
|
+
rel: href ? rel : void 0,
|
|
43
|
+
ping: href ? ping : void 0,
|
|
44
|
+
class: className,
|
|
45
|
+
style: computedStyle,
|
|
46
|
+
...rest,
|
|
47
|
+
children: [
|
|
48
|
+
media != null && /* @__PURE__ */ jsx("div", { slot: "media", children: media }),
|
|
49
|
+
header != null && // A plain-string (or number) header imitates a `<Title>`: the element's
|
|
50
|
+
// header slot supplies the level-4 type scale, and this div adds the
|
|
51
|
+
// matching 8px bottom padding (inside the box — no margin-collapse). A
|
|
52
|
+
// *node* header brings its own margin (a slotted `<Title>` keeps its
|
|
53
|
+
// bottom margin; the element zeroes only the top), so it gets no wrapper
|
|
54
|
+
// padding — otherwise the gap would double.
|
|
55
|
+
/* @__PURE__ */ jsx(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
slot: "header",
|
|
59
|
+
style: typeof header === "string" || typeof header === "number" ? { paddingBlockEnd: "8px" } : void 0,
|
|
60
|
+
children: header
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
actions != null && /* @__PURE__ */ jsx("span", { slot: "actions", style: { display: "contents" }, children: actions }),
|
|
64
|
+
children,
|
|
65
|
+
footer != null && /* @__PURE__ */ jsx("span", { slot: "footer", style: { display: "contents" }, children: footer })
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
Card
|
|
72
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { BaseProps } from "../general_types";
|
|
2
|
+
/** Public props for the `<Dialog>` modal / drawer. `header` and `footer` are the
|
|
3
|
+
* chrome zones; `children` is the (scrollable) body. */
|
|
4
|
+
export interface DialogProps extends Omit<BaseProps, "title"> {
|
|
5
|
+
/** Header content: a title, or any node. Rendered in the top zone. Omit for a
|
|
6
|
+
* chromeless dialog with only a body. */
|
|
7
|
+
header?: React.ReactNode;
|
|
8
|
+
/** Footer content, usually the action buttons. Rendered in the bottom zone as
|
|
9
|
+
* a right-aligned row (wraps under pressure). Omit for none. */
|
|
10
|
+
footer?: React.ReactNode;
|
|
11
|
+
/** The dialog body. Scrolls when it overflows the available height. */
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
/** Placement. `center` (the default) is a centered modal; `left` / `right` /
|
|
14
|
+
* `top` / `bottom` turn it into an edge drawer (full height for left/right,
|
|
15
|
+
* full width for top/bottom); `fullscreen` fills the whole viewport with no
|
|
16
|
+
* edge gap or corner radius.
|
|
17
|
+
* @defaultValue 'center' */
|
|
18
|
+
position?: "center" | "left" | "right" | "top" | "bottom" | "fullscreen";
|
|
19
|
+
/** Whether the top-right ✕ button is present. It's one way to close the dialog,
|
|
20
|
+
* alongside Esc, the backdrop, a `data-dialog-close` / footer action, and your
|
|
21
|
+
* own code; `false` just removes the ✕, it doesn't make the dialog un-closable.
|
|
22
|
+
* @defaultValue true */
|
|
23
|
+
closable?: boolean;
|
|
24
|
+
/** Turn off light-dismiss: a backdrop click and Esc no longer close the dialog.
|
|
25
|
+
* It stays closable through explicit controls (the ✕ when `closable`, a footer
|
|
26
|
+
* action, your own code); `persistent` isn't "un-closable", it stops an
|
|
27
|
+
* accidental click or stray Esc from dismissing. For an alert / confirm that
|
|
28
|
+
* should be answered deliberately. Omit for the default dismissible behavior. */
|
|
29
|
+
persistent?: boolean;
|
|
30
|
+
/** Corner radius. A side keyword — `'top'` / `'right'` / `'bottom'` / `'left'` —
|
|
31
|
+
* rounds only that edge's two corners (at `--dialog-radius`), which pairs with
|
|
32
|
+
* a drawer's exposed edge (a bottom sheet → `'top'`, a right drawer → `'left'`).
|
|
33
|
+
* A `number` (px) or CSS string rounds all corners at that value; the string
|
|
34
|
+
* may be a full `border-radius` shorthand (`'12px 12px 0 0'`). `true` rounds all
|
|
35
|
+
* corners at `--dialog-radius`. Omit for the position default: `center` is
|
|
36
|
+
* rounded, drawers and `fullscreen` are square. */
|
|
37
|
+
round?: boolean | number | "top" | "right" | "bottom" | "left" | (string & {});
|
|
38
|
+
/** Controlled open state. When provided, the consumer owns open/close: the
|
|
39
|
+
* dialog only follows this prop, and every user dismiss (Esc, backdrop, close
|
|
40
|
+
* button) *requests* a change via `onStateChange` (reject by not updating).
|
|
41
|
+
* Leave undefined for uncontrolled. */
|
|
42
|
+
open?: boolean;
|
|
43
|
+
/** Initial open state for the uncontrolled case (read once on mount). */
|
|
44
|
+
defaultOpen?: boolean;
|
|
45
|
+
/** Uncontrolled trigger name. Any element with `data-dialog-open="{name}"`
|
|
46
|
+
* opens this dialog, `data-dialog-close="{name}"` closes it. A convenience for
|
|
47
|
+
* triggers rendered elsewhere; ignored in controlled mode. */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Fired before the open state changes, on every user open or dismiss.
|
|
50
|
+
* `event` is the cancelable `statechange`: call `event.preventDefault()` to
|
|
51
|
+
* veto an *uncontrolled* transition. `detail.next` is the requested open
|
|
52
|
+
* state, `detail.prev` the current one (booleans). In controlled mode, apply
|
|
53
|
+
* `detail.next` to `open` to accept, or do nothing to reject. */
|
|
54
|
+
onStateChange?: (event: CustomEvent, detail: {
|
|
55
|
+
next: boolean;
|
|
56
|
+
prev: boolean;
|
|
57
|
+
}) => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `<Dialog>` — a modal dialog (or edge drawer, via `position`), built on a
|
|
61
|
+
* native `<dialog>` inside `<a-dialog>` — so the top layer, focus trap, focus
|
|
62
|
+
* return, backdrop, and Esc all come from the platform.
|
|
63
|
+
*
|
|
64
|
+
* A pure, stateless pass-through: the element owns all interaction (open/close,
|
|
65
|
+
* dismiss, focus), so the wrapper holds no state and grabs no ref — it only maps
|
|
66
|
+
* props to attributes and drops content into the `header` / body / `footer` /
|
|
67
|
+
* `close` slots.
|
|
68
|
+
*
|
|
69
|
+
* Uncontrolled (`defaultOpen`, or the `name` trigger convenience): the element
|
|
70
|
+
* owns open/close. Controlled (`open` + `onStateChange`): the wrapper emits
|
|
71
|
+
* `state="open"|"closed"`; the element treats the attribute as the source of
|
|
72
|
+
* truth, and every user dismiss only *requests* a change (a consumer can reject
|
|
73
|
+
* it). See STATEFUL-COMPONENTS.md.
|
|
74
|
+
*
|
|
75
|
+
* Requires `@antadesign/anta/elements` to be imported (client-side only) to
|
|
76
|
+
* register the underlying custom element.
|
|
77
|
+
*
|
|
78
|
+
* @example Controlled
|
|
79
|
+
* ```tsx
|
|
80
|
+
* const [open, setOpen] = useState(false)
|
|
81
|
+
* <Button onClick={() => setOpen(true)}>Open</Button>
|
|
82
|
+
* <Dialog
|
|
83
|
+
* open={open}
|
|
84
|
+
* onStateChange={(_e, { next }) => setOpen(next)}
|
|
85
|
+
* header="Delete project?"
|
|
86
|
+
* footer={<><Button onClick={() => setOpen(false)}>Cancel</Button><Button priority="primary" tone="critical">Delete</Button></>}
|
|
87
|
+
* >
|
|
88
|
+
* This action cannot be undone.
|
|
89
|
+
* </Dialog>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare const Dialog: ({ header, footer, position, closable, persistent, round, open, defaultOpen, name, onStateChange, className, style, children, ...rest }: DialogProps) => any;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
|
|
2
|
+
import { nativeStateChange, roundStyle } from "../anta_helpers";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
const ROUND_SIDES = /* @__PURE__ */ new Set(["top", "right", "bottom", "left"]);
|
|
5
|
+
const Dialog = ({
|
|
6
|
+
header,
|
|
7
|
+
footer,
|
|
8
|
+
position,
|
|
9
|
+
closable,
|
|
10
|
+
persistent,
|
|
11
|
+
round,
|
|
12
|
+
open,
|
|
13
|
+
defaultOpen,
|
|
14
|
+
name,
|
|
15
|
+
onStateChange,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
children,
|
|
19
|
+
...rest
|
|
20
|
+
}) => {
|
|
21
|
+
const controlled = open !== void 0;
|
|
22
|
+
const roundSide = typeof round === "string" && ROUND_SIDES.has(round) ? round : void 0;
|
|
23
|
+
return /* @__PURE__ */ jsxs(
|
|
24
|
+
"a-dialog",
|
|
25
|
+
{
|
|
26
|
+
state: controlled ? open ? "open" : "closed" : void 0,
|
|
27
|
+
"default-state": !controlled && defaultOpen ? "open" : void 0,
|
|
28
|
+
position: position && position !== "center" ? position : void 0,
|
|
29
|
+
persistent: persistent ? "" : void 0,
|
|
30
|
+
round: roundSide ?? (round ? "" : void 0),
|
|
31
|
+
name: !controlled ? name : void 0,
|
|
32
|
+
onstatechange: onStateChange ? (e) => {
|
|
33
|
+
const { event, detail } = nativeStateChange(e);
|
|
34
|
+
if (detail)
|
|
35
|
+
onStateChange(event, {
|
|
36
|
+
next: detail.next === "open",
|
|
37
|
+
prev: detail.prev === "open"
|
|
38
|
+
});
|
|
39
|
+
} : void 0,
|
|
40
|
+
class: className,
|
|
41
|
+
style: roundStyle(roundSide ? void 0 : round, "--dialog-round", style),
|
|
42
|
+
...rest,
|
|
43
|
+
children: [
|
|
44
|
+
closable !== false && // A real <a-button> (light DOM → fully styled, keyboard-focusable) in the
|
|
45
|
+
// element's `close` slot. It fires the bubbling `closerequest` event via
|
|
46
|
+
// a-button's global listener, so closing works even without framework
|
|
47
|
+
// hydration; the element turns that into a close request.
|
|
48
|
+
// CONTRACT: `data-custom-event` below MUST match `CLOSE_TRIGGER` in the
|
|
49
|
+
// element (src/elements/a-dialog.ts). Duplicated, not shared — importing
|
|
50
|
+
// the element module here would self-register it and break the
|
|
51
|
+
// wrapper/element decoupling. Rename in both places.
|
|
52
|
+
/* @__PURE__ */ jsx("span", { slot: "close", style: { display: "contents" }, children: /* @__PURE__ */ jsx(
|
|
53
|
+
Button,
|
|
54
|
+
{
|
|
55
|
+
priority: "tertiary",
|
|
56
|
+
size: "large",
|
|
57
|
+
icon: "x",
|
|
58
|
+
"aria-label": "Close",
|
|
59
|
+
"data-custom-event": "closerequest"
|
|
60
|
+
}
|
|
61
|
+
) }),
|
|
62
|
+
header != null && /* @__PURE__ */ jsx("div", { slot: "header", children: header }),
|
|
63
|
+
children,
|
|
64
|
+
footer != null && /* @__PURE__ */ jsx("span", { slot: "footer", style: { display: "contents" }, children: footer })
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
export {
|
|
70
|
+
Dialog
|
|
71
|
+
};
|
|
@@ -146,6 +146,9 @@ export interface InputProps extends BaseProps, DOMEventHandlers {
|
|
|
146
146
|
onFocus?: (e: any) => void;
|
|
147
147
|
/** Fires when the field loses focus. */
|
|
148
148
|
onBlur?: (e: any) => void;
|
|
149
|
+
/** ARIA `role` for the field — e.g. `combobox` when the input drives a
|
|
150
|
+
* suggestion `listbox` (see `InputAutocomplete`). Left unset by default. */
|
|
151
|
+
role?: string;
|
|
149
152
|
}
|
|
150
153
|
/**
|
|
151
154
|
* `<Input>` — a text field. Renders an `<a-input>` web component whose real
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BaseProps } from '../general_types';
|
|
2
|
+
import type { IconShape } from '../elements/a-icon.shapes';
|
|
3
|
+
import type { SelectOption } from './Select';
|
|
4
|
+
export interface InputAutocompleteProps extends Omit<BaseProps, 'children'> {
|
|
5
|
+
/** The suggestions: bare strings or `SelectOption`s (`value` / `label` / `hint` /
|
|
6
|
+
* `icon` / `tone` / `disabled`). A flat list; picking one fills the field. */
|
|
7
|
+
suggestions: (string | SelectOption)[];
|
|
8
|
+
/** Controlled value: the field's text, a **free** string not constrained to a
|
|
9
|
+
* suggestion. Leave undefined for uncontrolled. */
|
|
10
|
+
value?: string;
|
|
11
|
+
/** Initial value for the uncontrolled case (the wrapper then owns it). */
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/** Fires as the text changes (typing, picking a suggestion, or clearing) with
|
|
14
|
+
* the new field text. */
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
/** Fires only when a suggestion is chosen, with that option. */
|
|
17
|
+
onSelect?: (option: SelectOption) => void;
|
|
18
|
+
/** How suggestions match the text. `true` uses the built-in case-insensitive
|
|
19
|
+
* substring match on `value` / `label` / `hint`; a function `(option, query) =>
|
|
20
|
+
* boolean` does custom matching; `false` shows `suggestions` verbatim so you
|
|
21
|
+
* can filter them yourself (async / remote).
|
|
22
|
+
* @defaultValue true */
|
|
23
|
+
filter?: boolean | ((option: SelectOption, query: string) => boolean);
|
|
24
|
+
/** Placeholder text. */
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
/** Field size.
|
|
27
|
+
* @defaultValue medium */
|
|
28
|
+
size?: 'small' | 'medium' | 'large';
|
|
29
|
+
/** Validation status. */
|
|
30
|
+
status?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
|
|
31
|
+
/** Status icon override, or `false` to drop it. */
|
|
32
|
+
statusIcon?: IconShape | (string & {}) | false;
|
|
33
|
+
/** Custom accent colour — a named tone or any CSS colour. */
|
|
34
|
+
tone?: string;
|
|
35
|
+
/** Show a clear button while the field has text. */
|
|
36
|
+
clearable?: boolean;
|
|
37
|
+
/** Form field name. */
|
|
38
|
+
name?: string;
|
|
39
|
+
/** Disable the field. */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
/** Field label. */
|
|
42
|
+
label?: React.ReactNode;
|
|
43
|
+
/** Hint under the field. */
|
|
44
|
+
hint?: React.ReactNode;
|
|
45
|
+
/** Leading adornment inside the field. */
|
|
46
|
+
leading?: React.ReactNode;
|
|
47
|
+
/** Trailing adornment inside the field. */
|
|
48
|
+
trailing?: React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `<InputAutocomplete>` — a text field whose value is a free string, with a
|
|
52
|
+
* suggestion dropdown that assists without constraining. Typing something not in
|
|
53
|
+
* the list is valid and stays the value; picking a suggestion just fills the
|
|
54
|
+
* field. For a value that MUST be one of the options, use `Select` with `filter`.
|
|
55
|
+
*
|
|
56
|
+
* Controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`).
|
|
57
|
+
* Requires `@antadesign/anta/elements` (client-side only).
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* <InputAutocomplete
|
|
62
|
+
* label="City"
|
|
63
|
+
* suggestions={['Berlin', 'Bogotá', 'Boston']}
|
|
64
|
+
* value={city}
|
|
65
|
+
* onValueChange={setCity}
|
|
66
|
+
* />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare const InputAutocomplete: (props: InputAutocompleteProps) => any;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
|
|
2
|
+
import { useState, useMemo, useId } from "../jsx-runtime";
|
|
3
|
+
import { nativeStateChange } from "../anta_helpers";
|
|
4
|
+
import { normalizeOpt, matchQueryRegex, matchesQuery, highlight } from "./select-options";
|
|
5
|
+
import { Input } from "./Input";
|
|
6
|
+
import { Menu } from "./Menu";
|
|
7
|
+
import { MenuItem } from "./MenuItem";
|
|
8
|
+
const InputAutocomplete = (props) => {
|
|
9
|
+
const {
|
|
10
|
+
suggestions,
|
|
11
|
+
value,
|
|
12
|
+
defaultValue,
|
|
13
|
+
onValueChange,
|
|
14
|
+
onSelect,
|
|
15
|
+
filter = true,
|
|
16
|
+
...rest
|
|
17
|
+
} = props;
|
|
18
|
+
const rid = useId();
|
|
19
|
+
const controlled = value !== void 0;
|
|
20
|
+
const [internal, setInternal] = useState(defaultValue ?? "");
|
|
21
|
+
const text = controlled ? value : internal;
|
|
22
|
+
const [open, setOpen] = useState(false);
|
|
23
|
+
const [activeId, setActiveId] = useState(null);
|
|
24
|
+
const options = useMemo(() => suggestions.map(normalizeOpt), [suggestions]);
|
|
25
|
+
const custom = typeof filter === "function";
|
|
26
|
+
const queryRe = filter !== false && !custom ? matchQueryRegex(text) : null;
|
|
27
|
+
const visible = custom ? options.filter((o) => filter(o, text)) : options.filter((o) => matchesQuery(o, queryRe));
|
|
28
|
+
const menuOpen = open && visible.length > 0;
|
|
29
|
+
const setText = (next) => {
|
|
30
|
+
if (!controlled) setInternal(next);
|
|
31
|
+
onValueChange?.(next);
|
|
32
|
+
};
|
|
33
|
+
const pick = (opt) => {
|
|
34
|
+
setText(opt.value);
|
|
35
|
+
onSelect?.(opt);
|
|
36
|
+
setOpen(false);
|
|
37
|
+
};
|
|
38
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx(
|
|
40
|
+
Input,
|
|
41
|
+
{
|
|
42
|
+
...rest,
|
|
43
|
+
value: text,
|
|
44
|
+
"data-menu-search": "",
|
|
45
|
+
role: "combobox",
|
|
46
|
+
"aria-haspopup": "listbox",
|
|
47
|
+
"aria-controls": `${rid}-list`,
|
|
48
|
+
"aria-expanded": menuOpen ? "true" : "false",
|
|
49
|
+
"aria-autocomplete": "list",
|
|
50
|
+
"aria-activedescendant": menuOpen && activeId ? activeId : void 0,
|
|
51
|
+
onFocus: () => setOpen(true),
|
|
52
|
+
onInput: (e) => {
|
|
53
|
+
setText(e.currentTarget.value);
|
|
54
|
+
setOpen(true);
|
|
55
|
+
},
|
|
56
|
+
onKeyDown: (e) => {
|
|
57
|
+
if (e.key === "Enter") setOpen(false);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
Menu,
|
|
63
|
+
{
|
|
64
|
+
id: `${rid}-list`,
|
|
65
|
+
role: "listbox",
|
|
66
|
+
open: menuOpen,
|
|
67
|
+
onStateChange: (_e, { next }) => {
|
|
68
|
+
setOpen(next);
|
|
69
|
+
if (!next) setActiveId(null);
|
|
70
|
+
},
|
|
71
|
+
onactivedescendant: (e) => setActiveId(nativeStateChange(e).detail?.id ?? null),
|
|
72
|
+
children: visible.map((opt, i) => /* @__PURE__ */ jsx(
|
|
73
|
+
MenuItem,
|
|
74
|
+
{
|
|
75
|
+
id: `${rid}-o${i}`,
|
|
76
|
+
role: "option",
|
|
77
|
+
"aria-selected": activeId === `${rid}-o${i}` ? "true" : "false",
|
|
78
|
+
icon: opt.icon,
|
|
79
|
+
label: highlight(opt.label ?? opt.value, queryRe),
|
|
80
|
+
hint: opt.hint ? highlight(opt.hint, queryRe) : opt.hint,
|
|
81
|
+
tone: opt.tone,
|
|
82
|
+
disabled: opt.disabled,
|
|
83
|
+
value: opt.value,
|
|
84
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
85
|
+
onSelect: () => pick(opt)
|
|
86
|
+
},
|
|
87
|
+
`${rid}-${opt.value}-${i}`
|
|
88
|
+
))
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] });
|
|
92
|
+
};
|
|
93
|
+
export {
|
|
94
|
+
InputAutocomplete
|
|
95
|
+
};
|
|
@@ -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) {
|
|
@@ -58,6 +58,9 @@ export interface MenuProps extends BaseProps {
|
|
|
58
58
|
onactivedescendant?: (e: CustomEvent<{
|
|
59
59
|
id: string | null;
|
|
60
60
|
}>) => void;
|
|
61
|
+
/** ARIA role of the menu surface.
|
|
62
|
+
* @defaultValue menu */
|
|
63
|
+
role?: string;
|
|
61
64
|
/** The menu's contents: `MenuItem`, `MenuSeparator`, `MenuGroup`, or any
|
|
62
65
|
* custom element. */
|
|
63
66
|
children?: React.ReactNode;
|
|
@@ -88,4 +91,4 @@ export interface MenuProps extends BaseProps {
|
|
|
88
91
|
* </Menu>
|
|
89
92
|
* ```
|
|
90
93
|
*/
|
|
91
|
-
export declare const Menu: ({ placement, context, coord, nohover, offset, autoWidth, open, onStateChange, round, className, style, children, ...rest }: MenuProps) => any;
|
|
94
|
+
export declare const Menu: ({ placement, context, coord, nohover, offset, autoWidth, open, onStateChange, round, role, className, style, children, ...rest }: MenuProps) => any;
|
package/dist/components/Menu.js
CHANGED
|
@@ -10,6 +10,7 @@ const Menu = ({
|
|
|
10
10
|
open,
|
|
11
11
|
onStateChange,
|
|
12
12
|
round,
|
|
13
|
+
role = "menu",
|
|
13
14
|
className,
|
|
14
15
|
style,
|
|
15
16
|
children,
|
|
@@ -34,7 +35,7 @@ const Menu = ({
|
|
|
34
35
|
});
|
|
35
36
|
} : void 0,
|
|
36
37
|
round: round ? "" : void 0,
|
|
37
|
-
role
|
|
38
|
+
role,
|
|
38
39
|
"aria-orientation": "vertical",
|
|
39
40
|
class: className,
|
|
40
41
|
style: roundStyle(round, "--menu-round", style),
|
|
@@ -75,6 +75,14 @@ export interface MenuItemProps extends BaseProps {
|
|
|
75
75
|
value?: string | number;
|
|
76
76
|
label?: React.ReactNode;
|
|
77
77
|
}) => void;
|
|
78
|
+
/** Raw `mousedown` on the row. Mainly to `preventDefault()` so the row doesn't
|
|
79
|
+
* take focus on a mouse press — e.g. a combobox option keeping focus in its
|
|
80
|
+
* input field while the click still selects. */
|
|
81
|
+
onMouseDown?: (event: any) => void;
|
|
82
|
+
/** ARIA role override. Defaults to the role implied by `selectionIndicator`
|
|
83
|
+
* (`menuitem` / `menuitemcheckbox` / `menuitemradio`); set it to reparent the
|
|
84
|
+
* row under a different container role — e.g. `option` inside a `listbox`. */
|
|
85
|
+
role?: string;
|
|
78
86
|
/** Item content. With `label` set, children are extra content — most
|
|
79
87
|
* notably the nested `<Menu>` for a submenu parent. */
|
|
80
88
|
children?: React.ReactNode;
|
|
@@ -99,4 +107,4 @@ export interface MenuItemProps extends BaseProps {
|
|
|
99
107
|
* </MenuItem>
|
|
100
108
|
* ```
|
|
101
109
|
*/
|
|
102
|
-
export declare const MenuItem: ({ icon, label, hint, kbd, iconTrailing, disabled, selected, selectionIndicator, indeterminate, indicator, tone, toneSelected, submenu, value, onSelect, className, style, children, ...rest }: MenuItemProps) => any;
|
|
110
|
+
export declare const MenuItem: ({ icon, label, hint, kbd, iconTrailing, disabled, selected, selectionIndicator, indeterminate, indicator, tone, toneSelected, submenu, value, onSelect, role: roleOverride, className, style, children, ...rest }: MenuItemProps) => any;
|
|
@@ -16,6 +16,7 @@ const MenuItem = ({
|
|
|
16
16
|
submenu,
|
|
17
17
|
value,
|
|
18
18
|
onSelect,
|
|
19
|
+
role: roleOverride,
|
|
19
20
|
className,
|
|
20
21
|
style,
|
|
21
22
|
children,
|
|
@@ -30,7 +31,7 @@ const MenuItem = ({
|
|
|
30
31
|
return /* @__PURE__ */ jsxs(
|
|
31
32
|
"a-menu-item",
|
|
32
33
|
{
|
|
33
|
-
role,
|
|
34
|
+
role: roleOverride ?? role,
|
|
34
35
|
tabIndex: 0,
|
|
35
36
|
disabled: disabled ? "" : void 0,
|
|
36
37
|
selected: keepTint ? "" : void 0,
|
|
@@ -176,9 +176,12 @@ export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
|
176
176
|
* option's **value / label / hint**. Pass a **function** `(option, query) =>
|
|
177
177
|
* boolean` for custom matching (called per option; return `true` to keep it). */
|
|
178
178
|
filter?: boolean | ((option: SelectOption, query: string) => boolean);
|
|
179
|
-
/** `multiple` only:
|
|
179
|
+
/** `multiple` only: a "Select all" row at the top that toggles every enabled
|
|
180
180
|
* option (the currently-visible ones when a `filter` query is active); its box
|
|
181
|
-
* shows the mixed state when only some are selected.
|
|
181
|
+
* shows the mixed state when only some are selected. On by default in
|
|
182
|
+
* `multiple` mode — set `false` to drop the row (and with it the Alt/⌥-click
|
|
183
|
+
* isolate accelerator it gates).
|
|
184
|
+
* @defaultValue true */
|
|
182
185
|
selectAll?: boolean;
|
|
183
186
|
/** Label for the `selectAll` row.
|
|
184
187
|
* @defaultValue Select all */
|