@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.
@@ -8,6 +8,7 @@ type JsxFunction = {
8
8
  type UseState = <S>(initial: S | (() => S)) => [S, (next: S | ((prev: S) => S)) => void];
9
9
  type UseId = () => string;
10
10
  type UseMemo = <T>(factory: () => T, deps: unknown[]) => T;
11
+ type UseSyncExternalStore = <T>(subscribe: (onChange: () => void) => () => void, getSnapshot: () => T, getServerSnapshot?: () => T) => T;
11
12
  declare let _Fragment: ComponentType;
12
13
  /**
13
14
  * Swap the underlying JSX factory (and, optionally, the hooks) used by all anta
@@ -32,16 +33,18 @@ export declare function configure(jsx: JsxFunction, Fragment?: ComponentType, ho
32
33
  useState?: UseState;
33
34
  useId?: UseId;
34
35
  useMemo?: UseMemo;
36
+ useSyncExternalStore?: UseSyncExternalStore;
35
37
  }): void;
36
38
  /** Hooks indirection so wrappers depend on the configured renderer, not a hard
37
39
  * `import … from 'react'`. Call these exactly like the React hooks. */
38
40
  export declare function useState<S>(initial: S | (() => S)): [S, (next: S | ((prev: S) => S)) => void];
39
41
  export declare function useId(): string;
40
42
  export declare function useMemo<T>(factory: () => T, deps: unknown[]): T;
43
+ export declare function useSyncExternalStore<T>(subscribe: (onChange: () => void) => () => void, getSnapshot: () => T, getServerSnapshot?: () => T): T;
41
44
  export declare function jsx(type: ComponentType, props: Record<string, unknown> | null, key?: string | number): unknown;
42
45
  export declare function jsxs(type: ComponentType, props: Record<string, unknown> | null, key?: string | number): unknown;
43
46
  export { _Fragment as Fragment };
44
- import type { AProgressAttributes, ATextAttributes, ATitleAttributes, ATagAttributes, AExpanderAttributes, AIconAttributes, AButtonAttributes, ACopyAttributes, ACheckboxAttributes, ATooltipAttributes, AInputAttributes, AInputTimeAttributes, ACalendarAttributes, ARadioAttributes, ARadioGroupAttributes, AMenuAttributes, AMenuItemAttributes, AMenuGroupAttributes, ATabsAttributes, ATabAttributes, ATabpanelAttributes, ADialogAttributes, ACardAttributes, BaseAttributes } from './general_types';
47
+ import type { AProgressAttributes, ATextAttributes, ATitleAttributes, ATagAttributes, AExpanderAttributes, AIconAttributes, AButtonAttributes, ACopyAttributes, ACheckboxAttributes, ATooltipAttributes, AInputAttributes, AInputTimeAttributes, ACalendarAttributes, ARadioAttributes, ARadioGroupAttributes, AMenuAttributes, AMenuItemAttributes, AMenuGroupAttributes, ATabsAttributes, ATabAttributes, ATabpanelAttributes, ADialogAttributes, ACardAttributes, ABannerAttributes, AToasterAttributes, AToastAttributes, BaseAttributes } from './general_types';
45
48
  export declare namespace JSX {
46
49
  interface IntrinsicElements extends React.JSX.IntrinsicElements, AntaIntrinsicElements {
47
50
  }
@@ -94,4 +97,8 @@ export interface AntaIntrinsicElements {
94
97
  'a-tabpanel': ATabpanelAttributes;
95
98
  'a-dialog': ADialogAttributes;
96
99
  'a-card': ACardAttributes;
100
+ 'a-banner': ABannerAttributes;
101
+ 'a-banner-message': BaseAttributes;
102
+ 'a-toaster': AToasterAttributes;
103
+ 'a-toast': AToastAttributes;
97
104
  }
@@ -4,12 +4,14 @@ let _Fragment = React.Fragment;
4
4
  let _useState = React.useState;
5
5
  let _useId = React.useId;
6
6
  let _useMemo = React.useMemo;
7
+ let _useSyncExternalStore = React.useSyncExternalStore;
7
8
  function configure(jsx2, Fragment, hooks) {
8
9
  _jsx = jsx2;
9
10
  if (Fragment !== void 0) _Fragment = Fragment;
10
11
  if (hooks?.useState) _useState = hooks.useState;
11
12
  if (hooks?.useId) _useId = hooks.useId;
12
13
  if (hooks?.useMemo) _useMemo = hooks.useMemo;
14
+ if (hooks?.useSyncExternalStore) _useSyncExternalStore = hooks.useSyncExternalStore;
13
15
  }
14
16
  function useState(initial) {
15
17
  return _useState(initial);
@@ -20,6 +22,9 @@ function useId() {
20
22
  function useMemo(factory, deps) {
21
23
  return _useMemo(factory, deps);
22
24
  }
25
+ function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
26
+ return _useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
27
+ }
23
28
  function jsx(type, props, key) {
24
29
  const { children, ...rest } = props ?? {};
25
30
  const p = key !== void 0 ? { ...rest, key } : rest;
@@ -43,5 +48,6 @@ export {
43
48
  jsxs,
44
49
  useId,
45
50
  useMemo,
46
- useState
51
+ useState,
52
+ useSyncExternalStore
47
53
  };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Toaster store — the data behind `<Toaster>` / `Toaster.manager`.
3
+ *
4
+ * A pure, renderer-agnostic store: it holds a list of toast entries and notifies
5
+ * subscribers when it changes. It never touches the DOM. A mounted `<Toaster>`
6
+ * subscribes to it (via `useSyncExternalStore`) and renders each entry through
7
+ * the reconciler, so React/Preact owns every toast node — no imperative append,
8
+ * no coordinator, no node refs.
9
+ *
10
+ * A toast's content is a **render function** `(id) => …`. Return a string, a JSX
11
+ * node, or a real DOM `Node`; the `<Toaster>` renders a string / JSX through the
12
+ * reconciler and hands a DOM node to the `<a-toast>` element to slot. The `id`
13
+ * lets the content wire its own dismiss button (`() => manager.dismiss(id)`).
14
+ *
15
+ * Dismissal is two-phase so the exit animation plays: `dismiss(id)` marks the
16
+ * entry `leaving` (it stays rendered), the `<a-toast>` element animates out and
17
+ * fires `dismiss`, and only then is the entry removed. `remove` is the second
18
+ * phase, called by the wrapper on that event.
19
+ *
20
+ * SSR-safe: no `HTMLElement` reference and no top-level DOM access; `getServerSnapshot`
21
+ * returns an empty list, so nothing renders server-side.
22
+ */
23
+ /** The placement zones, in the order their `<slot name>` appears in the toaster
24
+ * shadow. Single source of truth: `a-toaster` builds its zones from this list, so
25
+ * a placement can't drift between the type here and the element's slots. */
26
+ export declare const TOAST_PLACEMENTS: readonly ["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"];
27
+ /** Where a toast is anchored in the viewport. */
28
+ export type ToastPlacement = (typeof TOAST_PLACEMENTS)[number];
29
+ /** What a toast's render function may return: a string / JSX (rendered through
30
+ * the reconciler) or a live DOM node (slotted by the element). */
31
+ export type ToastContent = React.ReactNode | Node;
32
+ /** A toast's content, as a function of its id (so content can dismiss itself). */
33
+ export type ToastRender = (id: string) => ToastContent;
34
+ /** Per-toast options for {@link Toaster.add}. */
35
+ export interface ToastOptions {
36
+ /** Stable id. Reuse it to update a live toast in place (upsert) or to target
37
+ * it with `dismiss` / `update`. Auto-generated when omitted. */
38
+ id?: string;
39
+ /** Which corner / edge to show it in.
40
+ * @defaultValue 'bottom-right' */
41
+ placement?: ToastPlacement;
42
+ /** Auto-dismiss delay in ms. Empty or non-positive values fall back to the
43
+ * default; pass `Infinity` to keep the toast until it's dismissed (sticky).
44
+ * @defaultValue 5000 */
45
+ duration?: number;
46
+ /** Announce this toast to assistive tech via `aria-live` on the toast. Opt-in
47
+ * per toast — omit for no announcement (the content may carry its own live
48
+ * semantics, e.g. a `Banner`'s `role="status"`). `'assertive'` interrupts. */
49
+ politeness?: 'polite' | 'assertive';
50
+ }
51
+ /** One live toast, as the mounted `<Toaster>` reads it. */
52
+ export interface ToastEntry {
53
+ id: string;
54
+ render: ToastRender;
55
+ placement: ToastPlacement;
56
+ duration?: number;
57
+ /** `aria-live` politeness for this toast, or undefined for no announcement. */
58
+ politeness?: 'polite' | 'assertive';
59
+ /** True once dismissal has been requested — the entry stays rendered so the
60
+ * element can animate out, then `remove` drops it. */
61
+ leaving: boolean;
62
+ /** Bumped on every add/update for an existing id, so the element restarts its
63
+ * timer (and the wrapper recomputes the content) on an in-place change. */
64
+ rev: number;
65
+ }
66
+ /** The toast controller returned by {@link createToaster}. */
67
+ export interface Toaster {
68
+ /** Show `render()` as a toast; returns its id. Reusing an existing `id` replaces
69
+ * that toast's content in place and restarts its timer; options omitted on the
70
+ * upsert are kept from the live toast. */
71
+ add(render: ToastRender, opts?: ToastOptions): string;
72
+ /** Request dismissal: the toast animates out, then leaves. */
73
+ dismiss(id: string): void;
74
+ /** Replace a live toast's content in place. */
75
+ update(id: string, render: ToastRender): void;
76
+ /** Dismiss every toast. */
77
+ clear(): void;
78
+ /** Drop an entry now (no animation) — the wrapper calls this once the element
79
+ * reports it finished animating out. Rarely needed directly. */
80
+ remove(id: string): void;
81
+ /** Subscribe to changes; returns an unsubscribe. For `useSyncExternalStore`. */
82
+ subscribe(onChange: () => void): () => void;
83
+ /** Current entries (a stable reference until the next change). */
84
+ getSnapshot(): readonly ToastEntry[];
85
+ /** Server snapshot — always empty (toasts don't render server-side). */
86
+ getServerSnapshot(): readonly ToastEntry[];
87
+ }
88
+ /**
89
+ * Create a toast store. Export one as your app singleton and drive it from
90
+ * anywhere; bind it to a mounted region with `<Toaster toaster={…} />`. Most apps
91
+ * use the built-in default, `Toaster.manager`, and a bare `<Toaster />`.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * export const toaster = createToaster()
96
+ * toaster.add(() => <Card>Saved</Card>, { placement: 'bottom-right' })
97
+ * ```
98
+ */
99
+ export declare function createToaster(): Toaster;
@@ -0,0 +1,100 @@
1
+ const TOAST_PLACEMENTS = [
2
+ "top-left",
3
+ "top-center",
4
+ "top-right",
5
+ "bottom-left",
6
+ "bottom-center",
7
+ "bottom-right"
8
+ ];
9
+ const DEFAULT_PLACEMENT = "bottom-right";
10
+ const EMPTY = Object.freeze([]);
11
+ let uid = 0;
12
+ function createToaster() {
13
+ let snapshot = EMPTY;
14
+ const listeners = /* @__PURE__ */ new Set();
15
+ const emit = () => listeners.forEach((fn) => fn());
16
+ function add(render, opts = {}) {
17
+ const id = opts.id ?? `t${++uid}`;
18
+ const i = snapshot.findIndex((e) => e.id === id);
19
+ const prev = i === -1 ? void 0 : snapshot[i];
20
+ const next = {
21
+ id,
22
+ render,
23
+ // On an upsert (existing id), keep the live toast's placement / duration /
24
+ // politeness when the caller omits them — so replacing the content doesn't
25
+ // silently reset a sticky toast to auto-dismiss or move it to another
26
+ // corner. Pass the option explicitly (incl. `undefined`) to change it.
27
+ placement: opts.placement ?? prev?.placement ?? DEFAULT_PLACEMENT,
28
+ duration: "duration" in opts ? opts.duration : prev?.duration,
29
+ politeness: opts.politeness ?? prev?.politeness,
30
+ leaving: false,
31
+ rev: prev ? prev.rev + 1 : 0
32
+ };
33
+ snapshot = prev ? snapshot.map((e, j) => j === i ? next : e) : [...snapshot, next];
34
+ emit();
35
+ return id;
36
+ }
37
+ function dismiss(id) {
38
+ let changed = false;
39
+ snapshot = snapshot.map((e) => {
40
+ if (e.id === id && !e.leaving) {
41
+ changed = true;
42
+ return { ...e, leaving: true };
43
+ }
44
+ return e;
45
+ });
46
+ if (changed) emit();
47
+ }
48
+ function update(id, render) {
49
+ let changed = false;
50
+ snapshot = snapshot.map((e) => {
51
+ if (e.id === id && !e.leaving) {
52
+ changed = true;
53
+ return { ...e, render, rev: e.rev + 1 };
54
+ }
55
+ return e;
56
+ });
57
+ if (changed) emit();
58
+ }
59
+ function remove(id) {
60
+ const next = snapshot.filter((e) => e.id !== id);
61
+ if (next.length !== snapshot.length) {
62
+ snapshot = next;
63
+ emit();
64
+ }
65
+ }
66
+ function clear() {
67
+ let changed = false;
68
+ snapshot = snapshot.map((e) => {
69
+ if (!e.leaving) {
70
+ changed = true;
71
+ return { ...e, leaving: true };
72
+ }
73
+ return e;
74
+ });
75
+ if (changed) emit();
76
+ }
77
+ return {
78
+ add,
79
+ dismiss,
80
+ update,
81
+ remove,
82
+ clear,
83
+ subscribe(onChange) {
84
+ listeners.add(onChange);
85
+ return () => {
86
+ listeners.delete(onChange);
87
+ };
88
+ },
89
+ getSnapshot() {
90
+ return snapshot;
91
+ },
92
+ getServerSnapshot() {
93
+ return EMPTY;
94
+ }
95
+ };
96
+ }
97
+ export {
98
+ TOAST_PLACEMENTS,
99
+ createToaster
100
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antadesign/anta",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",