@antadesign/anta 0.3.13 → 0.3.15

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.
@@ -0,0 +1,70 @@
1
+ import { HTMLElementBase } from '../anta_helpers';
2
+ import './a-toast.css';
3
+ /**
4
+ * `<a-toast>` — one item inside an `<a-toaster>`. A style-neutral container around
5
+ * slotted content (a `Banner`, `Card`, `Sticker`, a string, or a raw DOM node):
6
+ * it owns the enter / exit animation and the auto-dismiss timer (paused while
7
+ * hovered or focused), nothing else. No chrome of its own — no ✕, no surface; the
8
+ * toasted content brings its own look and its own dismiss affordance. Its `slot`
9
+ * attribute (`slot="bottom-right"`, …) routes it to a placement zone.
10
+ *
11
+ * Content arrives one of two ways: as slotted light-DOM children (a string / JSX
12
+ * the `<Toaster>` renders through the reconciler), or as a live DOM node set on
13
+ * the `content` property (the wrapper's DOM-node branch), which the element slots
14
+ * into its own light DOM. Either way it projects through the shadow `<slot>`.
15
+ *
16
+ * ## Time left — `--toast-remaining`
17
+ *
18
+ * While the auto-dismiss timer runs, the element animates `--toast-remaining` from
19
+ * `1` to `0` on the slot (paused in lockstep with the timer on hover / focus).
20
+ * It's a registered `@property` (see `a-toast.css`) with `inherits: true`, so
21
+ * slotted content inherits the live value and can draw a countdown with one CSS
22
+ * rule — `transform: scaleX(var(--toast-remaining))` — with no timer of its own
23
+ * and no re-render. Display only: the real dismiss is the JS timer, so it degrades
24
+ * to a full bar where custom-property animation is unsupported. Sticky
25
+ * (`duration="0"`) runs no countdown; the var stays `1`.
26
+ *
27
+ * ## Dismiss from an action — `data-toast-dismiss`
28
+ *
29
+ * Any element inside the toast carrying `data-toast-dismiss` dismisses it on click
30
+ * — the same convention as `<a-banner>`'s `data-banner-dismiss` and `<a-dialog>`'s
31
+ * `data-dialog-close`. It's a click listener (a light-DOM READ, declarative-safe),
32
+ * so it catches any activated control — native `<button>`, `<a>`, `<a-button>`,
33
+ * `<a-menu-item>` — and keyboard activation for free. A toasted `<Banner>` instead
34
+ * dismisses through its own `onDismiss` (wire it to `manager.dismiss(id)`); its
35
+ * built-in ✕ and any `data-banner-dismiss` action ride that channel.
36
+ *
37
+ * It **never removes itself**. Dismissal — from the timer, a `data-toast-dismiss`
38
+ * control, or the `leaving` attribute the wrapper sets for a programmatic
39
+ * `dismiss` — fades the host out (an off-DOM `:state(leaving)`), then emits a
40
+ * bubbling **`dismiss`**; the owner removes the node on that event. So the element
41
+ * writes only its own shadow + internals, reads its own attributes (and, read-only,
42
+ * the light DOM for the dismiss trigger), appends only its own (wrapper-supplied)
43
+ * content node, and dispatches events.
44
+ *
45
+ * Shadow structure — the host IS the box (no wrappers):
46
+ *
47
+ * :host ← the fade/slide layer
48
+ * <slot> ← the content
49
+ */
50
+ export declare class AToastElement extends HTMLElementBase {
51
+ #private;
52
+ static observedAttributes: string[];
53
+ constructor();
54
+ connectedCallback(): void;
55
+ disconnectedCallback(): void;
56
+ attributeChangedCallback(name: string): void;
57
+ /** A live DOM node to show as the content (set by the wrapper's DOM-node
58
+ * branch). Paired getter/setter so React 19's property assignment works. */
59
+ get content(): Node | null;
60
+ set content(node: Node | null);
61
+ /** Restart the auto-dismiss timer from the current `duration` (an in-place
62
+ * update). If the toast is currently paused, the fresh time is banked and the
63
+ * countdown stays paused until hover / focus releases. */
64
+ restart(): void;
65
+ /** Play the exit (shadow only), then emit a bubbling `dismiss`. The owner
66
+ * removes the node on that event — the element never removes itself.
67
+ * Idempotent. */
68
+ dismiss(): void;
69
+ }
70
+ export declare function register_a_toast(): void;
@@ -0,0 +1,283 @@
1
+ import { HTMLElementBase, installDismissTrigger } from "../anta_helpers";
2
+ import "./a-toast.css";
3
+ const EXIT_MS = 220;
4
+ const DEFAULT_DURATION = 5e3;
5
+ const DISMISS_ATTR = "data-toast-dismiss";
6
+ class AToastElement extends HTMLElementBase {
7
+ // `leaving` triggers the exit; `rev` (bumped on an in-place update) restarts
8
+ // the timer. `duration` is read on demand, not observed.
9
+ static observedAttributes = ["leaving", "rev"];
10
+ /** Off-DOM state channel — drives `:state(leaving)` for the exit fade. */
11
+ #internals;
12
+ /** The content slot. Also the element the `--toast-remaining` countdown
13
+ * animates on, so slotted content can inherit the value (see SHADOW_STYLE). */
14
+ #slot;
15
+ /** A DOM node handed in via the `content` property (the wrapper's DOM-node
16
+ * branch). String / JSX content arrives as slotted children instead. */
17
+ #content;
18
+ /** The content node we last slotted, so an in-place update can swap or clear it
19
+ * without touching the reconciler's own children. */
20
+ #slotted;
21
+ #timer;
22
+ #exitTimer;
23
+ /** Time left on the auto-dismiss timer (ms), tracked across pause/resume. */
24
+ #remaining = 0;
25
+ /** When the current run started, for computing remaining on pause. */
26
+ #startedAt = 0;
27
+ #leaving = false;
28
+ // Independent pause holds — the countdown runs only when BOTH are false.
29
+ #hovered = false;
30
+ #focused = false;
31
+ constructor() {
32
+ super();
33
+ try {
34
+ this.#internals = this.attachInternals?.();
35
+ } catch (err) {
36
+ console.warn("a-toast: ElementInternals unavailable \u2014 exit fade disabled.", err);
37
+ }
38
+ const shadow = this.attachShadow({ mode: "open" });
39
+ const style = document.createElement("style");
40
+ style.textContent = SHADOW_STYLE;
41
+ this.#slot = document.createElement("slot");
42
+ shadow.append(style, this.#slot);
43
+ installDismissTrigger(this, DISMISS_ATTR, () => this.dismiss());
44
+ }
45
+ connectedCallback() {
46
+ this.#applyContent();
47
+ this.addEventListener("pointerenter", this.#onPointerEnter);
48
+ this.addEventListener("pointerleave", this.#onPointerLeave);
49
+ this.addEventListener("focusin", this.#onFocusIn);
50
+ this.addEventListener("focusout", this.#onFocusOut);
51
+ if (this.hasAttribute("leaving")) {
52
+ this.dismiss();
53
+ return;
54
+ }
55
+ this.#startTimer();
56
+ }
57
+ disconnectedCallback() {
58
+ this.#clearTimer();
59
+ if (this.#exitTimer != null) {
60
+ this.view.clearTimeout(this.#exitTimer);
61
+ this.#exitTimer = void 0;
62
+ }
63
+ this.removeEventListener("pointerenter", this.#onPointerEnter);
64
+ this.removeEventListener("pointerleave", this.#onPointerLeave);
65
+ this.removeEventListener("focusin", this.#onFocusIn);
66
+ this.removeEventListener("focusout", this.#onFocusOut);
67
+ }
68
+ attributeChangedCallback(name) {
69
+ if (name === "leaving") {
70
+ if (this.hasAttribute("leaving")) {
71
+ if (this.isConnected) this.dismiss();
72
+ } else if (this.#leaving) {
73
+ this.#revive();
74
+ }
75
+ } else if (name === "rev") {
76
+ if (this.isConnected) this.restart();
77
+ }
78
+ }
79
+ /** Cancel a pending exit and bring the toast back (an upsert landed during the
80
+ * exit window). */
81
+ #revive() {
82
+ if (this.#exitTimer != null) {
83
+ this.view.clearTimeout(this.#exitTimer);
84
+ this.#exitTimer = void 0;
85
+ }
86
+ this.#leaving = false;
87
+ this.#internals?.states.delete("leaving");
88
+ this.#startTimer();
89
+ }
90
+ /** A live DOM node to show as the content (set by the wrapper's DOM-node
91
+ * branch). Paired getter/setter so React 19's property assignment works. */
92
+ get content() {
93
+ return this.#content ?? null;
94
+ }
95
+ set content(node) {
96
+ this.#content = node ?? void 0;
97
+ this.#applyContent();
98
+ }
99
+ /** Slot the content node into our own light DOM. Only ever touches the node WE
100
+ * slotted (tracked in `#slotted`), never the reconciler's children: it removes
101
+ * the previous node and appends the new one, or removes it when `content` goes
102
+ * null (an in-place update swapping DOM-node content for a string / JSX — else
103
+ * the stale node would stack under the new content). String / JSX content
104
+ * arrives as reconciler-owned children, so `#content` is undefined and this
105
+ * only clears any node we had slotted. */
106
+ #applyContent() {
107
+ if (this.#content === this.#slotted) return;
108
+ if (this.#slotted?.parentNode === this) this.removeChild(this.#slotted);
109
+ this.#slotted = this.#content;
110
+ if (this.#content) this.append(this.#content);
111
+ }
112
+ /** Auto-dismiss delay in ms. Empty / non-positive falls back to the 5000ms
113
+ * default; `Infinity` is sticky (never auto-dismisses). */
114
+ get #duration() {
115
+ const a = this.getAttribute("duration");
116
+ if (a == null) return DEFAULT_DURATION;
117
+ const n = Number(a);
118
+ if (n === Infinity) return Infinity;
119
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_DURATION;
120
+ }
121
+ /** Paused while either hold is active. */
122
+ get #paused() {
123
+ return this.#hovered || this.#focused;
124
+ }
125
+ #startTimer() {
126
+ this.#remaining = this.#duration;
127
+ this.#startCountdown();
128
+ this.#armIfActive();
129
+ }
130
+ /** Set up (or restart) the display countdown that drives `--toast-remaining`
131
+ * 1 → 0 on the slot over `duration`; slotted content inherits it. It's display
132
+ * only — the auto-dismiss above is the real JS timer — so it degrades to a
133
+ * static full bar where custom-property animation is unsupported. Sticky
134
+ * (`Infinity` duration) runs no countdown; the var stays at its initial 1. */
135
+ #startCountdown() {
136
+ if (Number.isFinite(this.#duration)) {
137
+ this.#slot.style.animation = `toast-remaining ${this.#duration}ms linear forwards`;
138
+ this.#eachCountdown((a) => {
139
+ a.currentTime = 0;
140
+ });
141
+ if (this.#paused || this.#leaving) this.#pauseCountdown();
142
+ } else {
143
+ this.#slot.style.animation = "";
144
+ }
145
+ }
146
+ /** Run `fn` for the `--toast-remaining` animation on the slot, if present.
147
+ * Guarded — `getAnimations` / animation control may be absent in a partial
148
+ * runtime, and the countdown is non-essential (never let it break the toast). */
149
+ #eachCountdown(fn) {
150
+ try {
151
+ for (const a of this.#slot.getAnimations?.() ?? []) {
152
+ if (a.animationName === "toast-remaining") fn(a);
153
+ }
154
+ } catch {
155
+ }
156
+ }
157
+ #pauseCountdown() {
158
+ this.#eachCountdown((a) => a.pause());
159
+ }
160
+ #playCountdown() {
161
+ this.#eachCountdown((a) => a.play());
162
+ }
163
+ /** Start (or resume) the dismiss timer only when it should be running: a finite
164
+ * duration (a non-finite one is sticky), not leaving, not paused, not already
165
+ * running. */
166
+ #armIfActive() {
167
+ if (this.#timer != null || this.#leaving || this.#paused || !Number.isFinite(this.#duration)) return;
168
+ if (this.#remaining <= 0) {
169
+ this.dismiss();
170
+ return;
171
+ }
172
+ this.#startedAt = Date.now();
173
+ this.#timer = this.view.setTimeout(() => {
174
+ this.#timer = void 0;
175
+ this.dismiss();
176
+ }, this.#remaining);
177
+ this.#playCountdown();
178
+ }
179
+ #clearTimer() {
180
+ if (this.#timer != null) {
181
+ this.view.clearTimeout(this.#timer);
182
+ this.#timer = void 0;
183
+ }
184
+ }
185
+ /** Freeze the timer, banking the time left, and freeze the display countdown
186
+ * with it. Idempotent. */
187
+ #pause() {
188
+ if (this.#timer == null) return;
189
+ this.#remaining -= Date.now() - this.#startedAt;
190
+ this.#clearTimer();
191
+ this.#pauseCountdown();
192
+ }
193
+ #onPointerEnter = () => {
194
+ this.#hovered = true;
195
+ this.#pause();
196
+ };
197
+ #onPointerLeave = () => {
198
+ this.#hovered = false;
199
+ this.#armIfActive();
200
+ };
201
+ #onFocusIn = () => {
202
+ this.#focused = true;
203
+ this.#pause();
204
+ };
205
+ #onFocusOut = () => {
206
+ this.#focused = false;
207
+ this.#armIfActive();
208
+ };
209
+ #reducedMotion() {
210
+ return this.view.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
211
+ }
212
+ /** How long to wait after starting the exit before emitting `dismiss` (node
213
+ * removal). Read from the computed `--toast-dur` token so it tracks a consumer
214
+ * override and the removal fires exactly when the fade ends — instead of a
215
+ * fixed 220ms that desyncs from a changed `--toast-dur`. 0 under reduced motion
216
+ * (the shadow transition is gated off there); `EXIT_MS` if it can't be read. */
217
+ #exitDuration() {
218
+ if (this.#reducedMotion()) return 0;
219
+ const raw = this.view.getComputedStyle?.(this).getPropertyValue("--toast-dur").trim();
220
+ if (raw) {
221
+ const n = parseFloat(raw);
222
+ if (Number.isFinite(n)) return raw.endsWith("ms") ? n : n * 1e3;
223
+ }
224
+ return EXIT_MS;
225
+ }
226
+ /** Restart the auto-dismiss timer from the current `duration` (an in-place
227
+ * update). If the toast is currently paused, the fresh time is banked and the
228
+ * countdown stays paused until hover / focus releases. */
229
+ restart() {
230
+ if (this.#leaving) return;
231
+ this.#clearTimer();
232
+ this.#startTimer();
233
+ }
234
+ /** Play the exit (shadow only), then emit a bubbling `dismiss`. The owner
235
+ * removes the node on that event — the element never removes itself.
236
+ * Idempotent. */
237
+ dismiss() {
238
+ if (this.#leaving) return;
239
+ this.#leaving = true;
240
+ this.#clearTimer();
241
+ this.#pauseCountdown();
242
+ this.#internals?.states.add("leaving");
243
+ const wait = this.#exitDuration();
244
+ this.#exitTimer = this.view.setTimeout(() => {
245
+ this.#exitTimer = void 0;
246
+ this.dispatchEvent(new CustomEvent("dismiss", { bubbles: true, composed: true }));
247
+ }, wait);
248
+ }
249
+ }
250
+ const SHADOW_STYLE = `
251
+ :host { display: block; opacity: 1; translate: 0 0; }
252
+ :host(:state(leaving)) { opacity: 0; }
253
+
254
+ @starting-style {
255
+ :host { opacity: 0; translate: 0 12px; }
256
+ }
257
+
258
+ @media (prefers-reduced-motion: no-preference) {
259
+ :host {
260
+ transition:
261
+ opacity var(--toast-dur, 220ms) ease,
262
+ translate var(--toast-dur, 220ms) ease;
263
+ }
264
+ }
265
+
266
+ slot { display: block; }
267
+
268
+ @keyframes toast-remaining {
269
+ from { --toast-remaining: 1; }
270
+ to { --toast-remaining: 0; }
271
+ }
272
+ `;
273
+ function register_a_toast() {
274
+ if (typeof customElements === "undefined") return;
275
+ if (!customElements.get("a-toast")) {
276
+ customElements.define("a-toast", AToastElement);
277
+ }
278
+ }
279
+ register_a_toast();
280
+ export {
281
+ AToastElement,
282
+ register_a_toast
283
+ };
@@ -0,0 +1 @@
1
+ @layer anta{a-toaster{--toaster-inset: 16px;--toaster-gap: 12px;--toaster-width: 380px}a-toaster:not(:defined){display:none}}
@@ -0,0 +1,30 @@
1
+ import { HTMLElementBase } from '../anta_helpers';
2
+ import './a-toaster.css';
3
+ /**
4
+ * `<a-toaster>` — a viewport-anchored notification region. One instance hosts all
5
+ * six placement zones; each `<a-toast>` routes to a corner/edge by its `slot`
6
+ * attribute (`slot="bottom-right"`, …). The `<Toaster>` wrapper keeps it mounted
7
+ * and renders its `<a-toast>` children through the reconciler; this element only
8
+ * lays them out — it touches its own shadow and reads its children, never a host
9
+ * or light-DOM write.
10
+ *
11
+ * Shadow structure:
12
+ *
13
+ * <div part="region" popover="manual"> ← top-layer, viewport-covering, click-through
14
+ * <slot name="top-left"> … ← one flex column per zone, pinned to its edge
15
+ * <slot name="bottom-right"> …
16
+ *
17
+ * The region is a `popover` so it renders in the top layer and escapes any
18
+ * transformed / clipping ancestor of wherever `<Toaster>` is mounted. It's shown
19
+ * only while it holds at least one toast (`showPopover`) and hidden when empty
20
+ * (`hidePopover`), so it never sits in the top layer for nothing. Natural
21
+ * top-layer ordering stands: a dialog or menu opened afterward can cover it (by
22
+ * design — toasts don't force themselves above modals).
23
+ */
24
+ export declare class AToasterElement extends HTMLElementBase {
25
+ #private;
26
+ constructor();
27
+ connectedCallback(): void;
28
+ disconnectedCallback(): void;
29
+ }
30
+ export declare function register_a_toaster(): void;
@@ -0,0 +1,98 @@
1
+ import { HTMLElementBase } from "../anta_helpers";
2
+ import { TOAST_PLACEMENTS } from "../toaster";
3
+ import "./a-toaster.css";
4
+ const ZONES = TOAST_PLACEMENTS;
5
+ class AToasterElement extends HTMLElementBase {
6
+ #region;
7
+ #shown = false;
8
+ constructor() {
9
+ super();
10
+ const shadow = this.attachShadow({ mode: "open" });
11
+ const style = document.createElement("style");
12
+ style.textContent = SHADOW_STYLE;
13
+ this.#region = document.createElement("div");
14
+ this.#region.className = "region";
15
+ this.#region.setAttribute("part", "region");
16
+ this.#region.setAttribute("popover", "manual");
17
+ for (const zone of ZONES) {
18
+ const slot = document.createElement("slot");
19
+ slot.name = zone;
20
+ this.#region.append(slot);
21
+ }
22
+ this.#region.addEventListener("slotchange", this.#syncVisibility);
23
+ shadow.append(style, this.#region);
24
+ }
25
+ connectedCallback() {
26
+ this.#syncVisibility();
27
+ }
28
+ disconnectedCallback() {
29
+ this.#shown = false;
30
+ }
31
+ /** Show the region while it holds a toast, hide it when empty. A child count
32
+ * read + a shadow popover toggle — no host or light-DOM mutation. */
33
+ #syncVisibility = () => {
34
+ const region = this.#region;
35
+ if (typeof region.showPopover !== "function") return;
36
+ const has = this.childElementCount > 0;
37
+ if (has && !this.#shown) {
38
+ if (!this.isConnected) return;
39
+ try {
40
+ region.showPopover();
41
+ this.#shown = true;
42
+ } catch {
43
+ }
44
+ } else if (!has && this.#shown) {
45
+ try {
46
+ if (region.matches(":popover-open")) region.hidePopover();
47
+ } catch {
48
+ }
49
+ this.#shown = false;
50
+ }
51
+ };
52
+ }
53
+ const SHADOW_STYLE = `
54
+ :host { display: contents; }
55
+
56
+ .region {
57
+ position: fixed;
58
+ inset: 0;
59
+ margin: 0;
60
+ padding: 0;
61
+ border: 0;
62
+ background: transparent;
63
+ overflow: visible;
64
+ pointer-events: none;
65
+ width: auto;
66
+ height: auto;
67
+ }
68
+ .region:popover-open { display: block; }
69
+
70
+ slot {
71
+ position: absolute;
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: var(--toaster-gap, 12px);
75
+ width: min(var(--toaster-width, 380px), calc(100vw - 2 * var(--toaster-inset, 16px)));
76
+ box-sizing: border-box;
77
+ pointer-events: none;
78
+ }
79
+
80
+ ::slotted(*) { pointer-events: auto; }
81
+
82
+ slot[name^="top-"] { top: var(--toaster-inset, 16px); flex-direction: column-reverse; }
83
+ slot[name^="bottom-"] { bottom: var(--toaster-inset, 16px); flex-direction: column; }
84
+ slot[name$="-left"] { left: var(--toaster-inset, 16px); }
85
+ slot[name$="-right"] { right: var(--toaster-inset, 16px); }
86
+ slot[name$="-center"] { left: 50%; transform: translateX(-50%); }
87
+ `;
88
+ function register_a_toaster() {
89
+ if (typeof customElements === "undefined") return;
90
+ if (!customElements.get("a-toaster")) {
91
+ customElements.define("a-toaster", AToasterElement);
92
+ }
93
+ }
94
+ register_a_toaster();
95
+ export {
96
+ AToasterElement,
97
+ register_a_toaster
98
+ };
@@ -35,5 +35,8 @@ export { ATabsElement, register_a_tabs } from './a-tabs';
35
35
  export { ATabPanelElement, register_a_tabpanel } from './a-tabpanel';
36
36
  export { ADialogElement, register_a_dialog } from './a-dialog';
37
37
  export { ACardElement, register_a_card } from './a-card';
38
+ export { ABannerElement, register_a_banner } from './a-banner';
39
+ export { AToasterElement, register_a_toaster } from './a-toaster';
40
+ export { AToastElement, register_a_toast } from './a-toast';
38
41
  import './a-title.css';
39
42
  import './a-tag.css';
@@ -20,9 +20,13 @@ import { ATabsElement, register_a_tabs } from "./a-tabs";
20
20
  import { ATabPanelElement, register_a_tabpanel } from "./a-tabpanel";
21
21
  import { ADialogElement, register_a_dialog } from "./a-dialog";
22
22
  import { ACardElement, register_a_card } from "./a-card";
23
+ import { ABannerElement, register_a_banner } from "./a-banner";
24
+ import { AToasterElement, register_a_toaster } from "./a-toaster";
25
+ import { AToastElement, register_a_toast } from "./a-toast";
23
26
  import "./a-title.css";
24
27
  import "./a-tag.css";
25
28
  export {
29
+ ABannerElement,
26
30
  AButtonElement,
27
31
  ACalendarElement,
28
32
  ACardElement,
@@ -44,7 +48,10 @@ export {
44
48
  ATabPanelElement,
45
49
  ATabsElement,
46
50
  ATextElement,
51
+ AToastElement,
52
+ AToasterElement,
47
53
  ATooltipElement,
54
+ register_a_banner,
48
55
  register_a_button,
49
56
  register_a_calendar,
50
57
  register_a_card,
@@ -66,5 +73,7 @@ export {
66
73
  register_a_tabpanel,
67
74
  register_a_tabs,
68
75
  register_a_text,
76
+ register_a_toast,
77
+ register_a_toaster,
69
78
  register_a_tooltip
70
79
  };
@@ -1081,3 +1081,82 @@ export interface ACardAttributes extends BaseAttributes {
1081
1081
  * chain the element derives in link mode. */
1082
1082
  'aria-label'?: string;
1083
1083
  }
1084
+ /**
1085
+ * Attributes for the `<a-banner>` custom element — a full-width, dismissible
1086
+ * message strip (borderless by default; opt into a rule via `border-bottom-width` /
1087
+ * `border-width`, the Progress pattern). Slots (light-DOM children): `message`
1088
+ * (leading), the default slot (middle content), `actions` (trailing), and `close`
1089
+ * (the ✕ button). The element exposes `::part(message | content | actions | close)`,
1090
+ * and `:state(open)` / `:state(closed)` as visibility hooks (the external
1091
+ * sheet collapses the host on `:state(closed)`). Low-level attributes; for the
1092
+ * typed JSX wrapper use `Banner` from `@antadesign/anta`.
1093
+ */
1094
+ export interface ABannerAttributes extends BaseAttributes {
1095
+ /** Controlled visibility (`'open'` / `'closed'`). Present → controlled: the
1096
+ * attribute is the source of truth, the ✕ dismiss only dispatches the cancelable
1097
+ * `statechange` event, and the consumer answers by updating it. Absent →
1098
+ * uncontrolled (use `default-state`). The `<Banner>` wrapper presents this as
1099
+ * `dismissed` (closed = dismissed). See STATEFUL-COMPONENTS.md. */
1100
+ state?: 'open' | 'closed';
1101
+ /** Initial visibility for the uncontrolled mode (`'open'` / `'closed'`), read
1102
+ * once at connect. A banner is shown by default, so omit / `'open'` shows it;
1103
+ * `'closed'` starts it dismissed. */
1104
+ 'default-state'?: 'open' | 'closed';
1105
+ /** Semantic tone, or any literal CSS color for a one-off custom tone. Named tones
1106
+ * re-point the surface + text + border colour (used if you opt into a border); a
1107
+ * custom color keeps its hue with lightness/chroma pinned. `'neutral'` is the
1108
+ * default (same as omitting it). */
1109
+ tone?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical' | (string & {});
1110
+ /** Rounded corners for a standalone banner — `border-radius: 999px` (clamps to a
1111
+ * stadium), or a custom radius via a length value (`round="12px"`). Borderless
1112
+ * like the default. Presence-based for the boolean form. */
1113
+ round?: boolean | number | string;
1114
+ /** Fires before visibility changes — the element dispatches a `cancelable`
1115
+ * `statechange` `CustomEvent` whose `detail` is `{ next, prev }` in the
1116
+ * `'open'|'closed'` vocabulary (a dismiss is always `next: 'closed'`).
1117
+ * Uncontrolled, `preventDefault()` vetoes the transition. All-lowercase so both
1118
+ * renderers bind it (React 19 keeps the case after `on`; Preact lowercases). */
1119
+ onstatechange?: (e: CustomEvent<{
1120
+ next: 'open' | 'closed';
1121
+ prev: 'open' | 'closed';
1122
+ }>) => void;
1123
+ }
1124
+ /**
1125
+ * Attributes for the `<a-toaster>` custom element — a viewport-anchored
1126
+ * notification region. One instance hosts all six placement zones; each
1127
+ * `<a-toast>` routes to a corner/edge by its `slot` attribute. Keep one mounted
1128
+ * (the `<Toaster>` wrapper, or by hand). Exposes `::part(region)`. Low-level
1129
+ * attributes; for the typed JSX wrapper use `Toaster` from `@antadesign/anta`.
1130
+ */
1131
+ export interface AToasterAttributes extends BaseAttributes {
1132
+ /** Accessible name for the region landmark. */
1133
+ 'aria-label'?: string;
1134
+ }
1135
+ /**
1136
+ * Attributes for the `<a-toast>` custom element — one item inside an
1137
+ * `<a-toaster>`. A style-neutral container around slotted content: it owns the
1138
+ * enter/exit animation and the auto-dismiss timer (paused on hover / focus), with
1139
+ * no chrome of its own (no ✕, no surface — the content brings its own look and its
1140
+ * own dismiss affordance). Route it to a placement zone with `slot`
1141
+ * (`slot="bottom-right"`, …). It never removes itself — on dismiss it animates out
1142
+ * then emits a bubbling `dismiss`, and the owner removes the node. For an
1143
+ * announcement, give it `role="status"` (polite) or `role="alert"` (assertive).
1144
+ */
1145
+ export interface AToastAttributes extends BaseAttributes {
1146
+ /** Auto-dismiss delay in ms (default 5000). Empty / non-positive uses the
1147
+ * default; `Infinity` keeps it until dismissed (sticky). */
1148
+ duration?: number | string;
1149
+ /** Set by the wrapper to request dismissal (a programmatic `dismiss`): the
1150
+ * element plays its exit and emits `dismiss`. Presence-based (`''` on, omit off). */
1151
+ leaving?: boolean | '';
1152
+ /** Bumped on an in-place update so the element restarts its auto-dismiss timer. */
1153
+ rev?: number | string;
1154
+ /** A live DOM node to show as the content (the wrapper's DOM-node branch);
1155
+ * set as a property, not a string attribute. String / JSX content is slotted
1156
+ * as children instead. */
1157
+ content?: Node;
1158
+ /** Fires after the exit animation, when the toast has dismissed — a bubbling
1159
+ * `CustomEvent`. The owner removes the node in response. All-lowercase so both
1160
+ * renderers bind it (React 19 keeps the case after `on`; Preact lowercases). */
1161
+ ondismiss?: (e: CustomEvent) => void;
1162
+ }
package/dist/index.d.ts CHANGED
@@ -74,6 +74,10 @@ export { Dialog } from './components/Dialog';
74
74
  export type { DialogProps } from './components/Dialog';
75
75
  export { Card } from './components/Card';
76
76
  export type { CardProps } from './components/Card';
77
+ export { Banner } from './components/Banner';
78
+ export type { BannerProps } from './components/Banner';
79
+ export { Toaster, createToaster } from './components/Toaster';
80
+ export type { ToasterProps, ToasterManager, ToastOptions, ToastPlacement, ToastContent, ToastRender, } from './components/Toaster';
77
81
  export type { BaseProps, BaseAttributes } from './general_types';
78
82
  export { configure } from './jsx-runtime';
79
83
  export type { AntaIntrinsicElements } from './jsx-runtime';
package/dist/index.js CHANGED
@@ -42,8 +42,11 @@ import { Tabs } from "./components/Tabs";
42
42
  import { TabPanel } from "./components/TabPanel";
43
43
  import { Dialog } from "./components/Dialog";
44
44
  import { Card } from "./components/Card";
45
+ import { Banner } from "./components/Banner";
46
+ import { Toaster, createToaster } from "./components/Toaster";
45
47
  import { configure } from "./jsx-runtime";
46
48
  export {
49
+ Banner,
47
50
  Button,
48
51
  ButtonCopy,
49
52
  Calendar,
@@ -72,10 +75,12 @@ export {
72
75
  Tag,
73
76
  Text,
74
77
  Title,
78
+ Toaster,
75
79
  Tooltip,
76
80
  buildMonth,
77
81
  clampDate,
78
82
  configure,
83
+ createToaster,
79
84
  dateFormatPattern,
80
85
  dateTimeFormatPattern,
81
86
  firstDayOfWeek,