ajo-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +126 -0
  3. package/dist/accordion.js +130 -0
  4. package/dist/avatar.js +54 -0
  5. package/dist/calendar.js +2 -0
  6. package/dist/carousel.js +239 -0
  7. package/dist/chart.js +790 -0
  8. package/dist/checkbox-group.js +70 -0
  9. package/dist/checkbox.js +77 -0
  10. package/dist/chunks/bar-CVafh6C1.js +99 -0
  11. package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
  12. package/dist/chunks/collection-DtRB63U4.js +111 -0
  13. package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
  14. package/dist/chunks/menu-B45IyHHC.js +704 -0
  15. package/dist/chunks/native-BJdhd9XJ.js +20 -0
  16. package/dist/chunks/popup-C8Bb3l_g.js +459 -0
  17. package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
  18. package/dist/chunks/position-D6_i_SRn.js +434 -0
  19. package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
  20. package/dist/collapsible.js +106 -0
  21. package/dist/command.js +263 -0
  22. package/dist/context-menu.js +112 -0
  23. package/dist/data-table.js +5 -0
  24. package/dist/dialog.js +207 -0
  25. package/dist/direction.js +22 -0
  26. package/dist/drawer.js +139 -0
  27. package/dist/field.js +26 -0
  28. package/dist/index.js +38 -0
  29. package/dist/input-date.js +994 -0
  30. package/dist/input-group.js +52 -0
  31. package/dist/input-otp.js +179 -0
  32. package/dist/menu.js +2 -0
  33. package/dist/menubar.js +236 -0
  34. package/dist/message-scroller.js +446 -0
  35. package/dist/navigation-menu.js +330 -0
  36. package/dist/popover.js +307 -0
  37. package/dist/progress.js +39 -0
  38. package/dist/radio-group.js +107 -0
  39. package/dist/resizable.js +172 -0
  40. package/dist/select.js +961 -0
  41. package/dist/sidebar.js +343 -0
  42. package/dist/slider.js +259 -0
  43. package/dist/switch.js +53 -0
  44. package/dist/tabs.js +182 -0
  45. package/dist/toast.js +492 -0
  46. package/dist/toggle-group.js +111 -0
  47. package/dist/toggle.js +52 -0
  48. package/dist/toolbar.js +127 -0
  49. package/dist/tooltip.js +196 -0
  50. package/dist/utils.js +104 -0
  51. package/dist/virtual-list.js +2 -0
  52. package/package.json +250 -0
  53. package/src/accordion.tsx +261 -0
  54. package/src/availability.ts +261 -0
  55. package/src/avatar.tsx +99 -0
  56. package/src/bar.ts +156 -0
  57. package/src/calendar.tsx +1441 -0
  58. package/src/carousel.tsx +424 -0
  59. package/src/chart.tsx +1194 -0
  60. package/src/checkbox-group.tsx +132 -0
  61. package/src/checkbox.tsx +130 -0
  62. package/src/collapsible.tsx +188 -0
  63. package/src/collection.ts +154 -0
  64. package/src/command.tsx +511 -0
  65. package/src/context-menu.tsx +233 -0
  66. package/src/data-table-contract.ts +143 -0
  67. package/src/data-table-model.ts +760 -0
  68. package/src/data-table.tsx +475 -0
  69. package/src/dialog.tsx +393 -0
  70. package/src/direction.tsx +45 -0
  71. package/src/drawer.tsx +251 -0
  72. package/src/field.tsx +61 -0
  73. package/src/index.ts +37 -0
  74. package/src/input-date.tsx +1539 -0
  75. package/src/input-group.tsx +142 -0
  76. package/src/input-otp.tsx +324 -0
  77. package/src/menu-cluster.ts +124 -0
  78. package/src/menu.tsx +1095 -0
  79. package/src/menubar.tsx +459 -0
  80. package/src/message-scroller.tsx +732 -0
  81. package/src/native.ts +26 -0
  82. package/src/navigation-menu.tsx +578 -0
  83. package/src/popover.tsx +519 -0
  84. package/src/popup-surface.tsx +31 -0
  85. package/src/popup.ts +569 -0
  86. package/src/position.ts +523 -0
  87. package/src/progress.tsx +70 -0
  88. package/src/radio-group.tsx +186 -0
  89. package/src/resizable.tsx +310 -0
  90. package/src/segments.ts +922 -0
  91. package/src/select.tsx +1501 -0
  92. package/src/sidebar.tsx +683 -0
  93. package/src/slider.tsx +424 -0
  94. package/src/switch.tsx +104 -0
  95. package/src/tabs.tsx +314 -0
  96. package/src/toast.tsx +923 -0
  97. package/src/toggle-group.tsx +249 -0
  98. package/src/toggle.tsx +91 -0
  99. package/src/toolbar.tsx +212 -0
  100. package/src/tooltip.tsx +359 -0
  101. package/src/utils.ts +204 -0
  102. package/src/virtual-list.tsx +205 -0
  103. package/src/virtual.ts +385 -0
package/dist/tabs.js ADDED
@@ -0,0 +1,182 @@
1
+ import { DirectionContext } from "./direction.js";
2
+ import { callHandler, callRef, controlled, dom, id, indicator, listen, overflow, roving, statefulRootAttrs } from "ajo-cloves";
3
+ import { context } from "ajo/context";
4
+ import { Fragment, jsx } from "ajo/jsx-runtime";
5
+ //#region packages/ajo-ui/src/tabs.tsx
6
+ var TabsContext = context(null);
7
+ var state = (active) => active ? "active" : "inactive";
8
+ var tabs = (root) => Array.from(root.querySelectorAll("button[data-slot=\"tabs-trigger\"]")).filter((button) => !button.disabled && button.offsetParent !== null);
9
+ var TabsRoot = function* ({ defaultValue, value }) {
10
+ const rootId = id("tabs");
11
+ let activationMode = "automatic";
12
+ let dir = "ltr";
13
+ let loop = true;
14
+ let onValueChange;
15
+ let orientation = "horizontal";
16
+ const state = controlled(this, {
17
+ fallback: String(value ?? defaultValue ?? ""),
18
+ onChange: (next, event) => onValueChange?.(next, event)
19
+ });
20
+ const triggerId = (value) => `${rootId}-trigger-${encodeURIComponent(value)}`;
21
+ const contentId = (value) => `${rootId}-content-${encodeURIComponent(value)}`;
22
+ const selected = (value) => state.value === value;
23
+ const setValue = (next, event) => {
24
+ if (!next || next === state.value) return;
25
+ state.set(next, event);
26
+ };
27
+ const ensureValue = () => {
28
+ if (state.value || state.controlled || !dom(this)) return;
29
+ const first = tabs(this)[0];
30
+ if (first?.value) state.init(first.value);
31
+ };
32
+ const nav = roving(this, {
33
+ items: () => tabs(this),
34
+ orientation: () => orientation,
35
+ dir: () => dir,
36
+ loop: () => loop,
37
+ onMove: (target, event) => {
38
+ target.focus();
39
+ target.scrollIntoView({
40
+ block: "nearest",
41
+ inline: "nearest"
42
+ });
43
+ if (activationMode === "automatic") setValue(String(target.value), event);
44
+ }
45
+ });
46
+ let list = null;
47
+ const edges = overflow(this, { target: () => list });
48
+ const mark = indicator(this, {
49
+ target: () => list,
50
+ of: (container) => container.querySelector("button[data-slot=\"tabs-trigger\"][data-state=\"active\"]")
51
+ });
52
+ const setList = (element) => {
53
+ list = element;
54
+ edges.sync();
55
+ mark.sync();
56
+ };
57
+ listen(this, "keydown", (event) => {
58
+ const target = event.target;
59
+ if (!target?.matches("button[data-slot=\"tabs-trigger\"]")) return;
60
+ if (nav.handle(event)) return;
61
+ if (activationMode === "manual" && (event.key === "Enter" || event.key === " ")) {
62
+ event.preventDefault();
63
+ setValue(String(target.value), event);
64
+ }
65
+ });
66
+ for (const args of this) {
67
+ activationMode = args.activationMode;
68
+ dir = args.dir;
69
+ loop = args.loop;
70
+ onValueChange = args.onValueChange;
71
+ orientation = args.orientation;
72
+ state.sync(args.value != null ? String(args.value) : void 0);
73
+ TabsContext({
74
+ activationMode,
75
+ contentId,
76
+ dir,
77
+ loop,
78
+ orientation,
79
+ selected,
80
+ setList,
81
+ setValue,
82
+ triggerId,
83
+ value: state.value
84
+ });
85
+ edges.sync();
86
+ mark.sync();
87
+ queueMicrotask(ensureValue);
88
+ yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
89
+ }
90
+ };
91
+ /** Unstyled root provider for tab state. */
92
+ var Tabs = ({ activationMode = "automatic", children, defaultValue, dir, loop = true, onValueChange, orientation = "horizontal", value, ...attrs }) => {
93
+ const resolvedDir = dir ?? DirectionContext();
94
+ return /* @__PURE__ */ jsx(TabsRoot, {
95
+ ...statefulRootAttrs(attrs),
96
+ activationMode,
97
+ defaultValue,
98
+ dir: resolvedDir,
99
+ loop,
100
+ onValueChange,
101
+ orientation,
102
+ value,
103
+ "attr:data-orientation": orientation,
104
+ "attr:data-slot": "tabs",
105
+ "attr:dir": resolvedDir,
106
+ children
107
+ });
108
+ };
109
+ /** Unstyled container for tab triggers. */
110
+ var TabsList = ({ children, ref, role = "tablist", ...attrs }) => {
111
+ const context = TabsContext();
112
+ return /* @__PURE__ */ jsx("div", {
113
+ ...attrs,
114
+ "aria-orientation": context?.orientation,
115
+ "data-orientation": context?.orientation,
116
+ "data-slot": "tabs-list",
117
+ ref: (element) => {
118
+ context?.setList(element);
119
+ callRef(ref, element);
120
+ },
121
+ role,
122
+ children
123
+ });
124
+ };
125
+ /** Unstyled button that activates a tab panel. */
126
+ var TabsTrigger = ({ children, disabled, id, "set:onclick": onClick, "set:onfocus": onFocus, type = "button", value, ...attrs }) => {
127
+ const context = TabsContext();
128
+ const tabValue = String(value);
129
+ const active = Boolean(context?.selected(tabValue));
130
+ const disabledFlag = Boolean(disabled);
131
+ const triggerId = id ?? context?.triggerId(tabValue);
132
+ return /* @__PURE__ */ jsx("button", {
133
+ ...attrs,
134
+ "aria-controls": context?.contentId(tabValue),
135
+ "aria-selected": active ? "true" : "false",
136
+ "data-slot": "tabs-trigger",
137
+ "data-state": state(active),
138
+ "data-value": tabValue,
139
+ disabled: disabledFlag,
140
+ id: triggerId,
141
+ role: "tab",
142
+ "set:onclick": (event) => {
143
+ callHandler(onClick, event);
144
+ if (event.defaultPrevented || disabledFlag) return;
145
+ context?.setValue(tabValue, event);
146
+ event.currentTarget.scrollIntoView({
147
+ block: "nearest",
148
+ inline: "nearest"
149
+ });
150
+ },
151
+ "set:onfocus": (event) => {
152
+ callHandler(onFocus, event);
153
+ if (event.defaultPrevented || disabledFlag || active || context?.activationMode !== "automatic") return;
154
+ context?.setValue(tabValue, event);
155
+ },
156
+ tabindex: active ? 0 : -1,
157
+ type,
158
+ value: tabValue,
159
+ children
160
+ });
161
+ };
162
+ /** Unstyled panel displayed by its matching tab trigger. */
163
+ var TabsContent = ({ children, forceMount, value, ...attrs }) => {
164
+ const context = TabsContext();
165
+ const tabValue = String(value);
166
+ const active = Boolean(context?.selected(tabValue));
167
+ if (!active && !forceMount) return null;
168
+ return /* @__PURE__ */ jsx("div", {
169
+ ...attrs,
170
+ "aria-labelledby": context?.triggerId(tabValue),
171
+ "data-slot": "tabs-content",
172
+ "data-state": state(active),
173
+ "data-value": tabValue,
174
+ hidden: active ? void 0 : true,
175
+ id: context?.contentId(tabValue),
176
+ role: "tabpanel",
177
+ tabindex: 0,
178
+ children
179
+ });
180
+ };
181
+ //#endregion
182
+ export { Tabs, TabsContent, TabsList, TabsTrigger };
package/dist/toast.js ADDED
@@ -0,0 +1,492 @@
1
+ import { clx, popupStyle, toNumber } from "./utils.js";
2
+ import { n as openPopover, t as closePopover } from "./chunks/native-BJdhd9XJ.js";
3
+ import { browser, callHandler, hotkey, id } from "ajo-cloves";
4
+ import { jsx, jsxs } from "ajo/jsx-runtime";
5
+ import { render } from "ajo";
6
+ //#region packages/ajo-ui/src/toast.tsx
7
+ var defaultDuration = 5e3;
8
+ var defaultLimit = 3;
9
+ var closeDelay = 200;
10
+ var hotkeyDefault = ["F8"];
11
+ var edgeInset = 16;
12
+ var stackGap = 8;
13
+ var records = [];
14
+ var configuredDuration = defaultDuration;
15
+ var configuredLabel;
16
+ var configuredPosition = "bottom-right";
17
+ var listeners = /* @__PURE__ */ new Set();
18
+ var emit = () => {
19
+ for (const listener of listeners) listener();
20
+ };
21
+ var clearTimer = (record) => {
22
+ if (record.timeout) clearTimeout(record.timeout);
23
+ record.timeout = null;
24
+ };
25
+ var removeToast = (id) => {
26
+ records = records.filter((record) => record.id !== id);
27
+ emit();
28
+ };
29
+ var startTimer = (record) => {
30
+ clearTimer(record);
31
+ if (!browser() || record.remaining <= 0) return;
32
+ record.startedAt = Date.now();
33
+ record.timeout = setTimeout(() => dismissToast(record.id), record.remaining);
34
+ };
35
+ var pauseRecord = (record) => {
36
+ if (!record.timeout) return;
37
+ clearTimer(record);
38
+ record.remaining = Math.max(0, record.remaining - (Date.now() - record.startedAt));
39
+ };
40
+ var resumeRecord = (record) => {
41
+ if (!record.open || record.timeout || record.remaining <= 0) return;
42
+ startTimer(record);
43
+ };
44
+ var normalize = (input, options) => {
45
+ if (input && typeof input === "object" && ("id" in input || "title" in input || "description" in input || "action" in input || "closeButton" in input || "variant" in input || "duration" in input || "position" in input)) return input;
46
+ return {
47
+ ...options,
48
+ title: input
49
+ };
50
+ };
51
+ var visibleToasts = (limit = defaultLimit, position) => {
52
+ return (position ? records.filter((record) => record.position === position) : records).slice(0, Math.max(1, limit));
53
+ };
54
+ var subscribeToasts = (listener) => {
55
+ listeners.add(listener);
56
+ return () => listeners.delete(listener);
57
+ };
58
+ var stringValue = (value) => typeof value === "string" ? value : void 0;
59
+ var booleanValue = (value, fallback) => typeof value === "boolean" ? value : fallback;
60
+ var hotkeyValue = (value) => Array.isArray(value) && value.every((item) => typeof item === "string") ? value : hotkeyDefault;
61
+ var positions = new Set([
62
+ "bottom-center",
63
+ "bottom-left",
64
+ "bottom-right",
65
+ "top-center",
66
+ "top-left",
67
+ "top-right"
68
+ ]);
69
+ var positionValue = (value) => typeof value === "string" && positions.has(value) ? value : "bottom-right";
70
+ var composeClick = (previous, next) => (event) => {
71
+ callHandler(previous, event);
72
+ next?.();
73
+ };
74
+ var resolveClass = (value, toast) => typeof value === "function" ? value(toast) : value;
75
+ /** Add a generated toast and return controls for updating or dismissing it. */
76
+ var toast = (input, options) => {
77
+ const data = normalize(input, options);
78
+ const toastId = data.id ?? id("toast");
79
+ const existing = records.find((record) => record.id === toastId);
80
+ const duration = data.duration ?? existing?.duration ?? configuredDuration;
81
+ const record = {
82
+ ...existing,
83
+ ...data,
84
+ id: toastId,
85
+ duration,
86
+ open: true,
87
+ remaining: Math.max(0, duration),
88
+ startedAt: 0,
89
+ timeout: existing?.timeout ?? null,
90
+ variant: data.variant ?? existing?.variant ?? "default",
91
+ position: data.position ?? existing?.position ?? configuredPosition
92
+ };
93
+ if (existing) {
94
+ clearTimer(existing);
95
+ records = records.map((current) => current.id === toastId ? record : current);
96
+ } else records = [record, ...records];
97
+ startTimer(record);
98
+ emit();
99
+ return {
100
+ id: toastId,
101
+ dismiss: () => dismissToast(toastId),
102
+ update: (patch) => updateToast(toastId, patch)
103
+ };
104
+ };
105
+ /** Update an existing generated toast. */
106
+ var updateToast = (id, patch) => {
107
+ const record = records.find((current) => current.id === id);
108
+ if (!record) return;
109
+ clearTimer(record);
110
+ Object.assign(record, patch, {
111
+ duration: patch.duration ?? record.duration,
112
+ remaining: Math.max(0, patch.duration ?? record.duration ?? defaultDuration)
113
+ });
114
+ startTimer(record);
115
+ emit();
116
+ };
117
+ /** Dismiss one generated toast, or every generated toast when no id is passed. */
118
+ var dismissToast = (id) => {
119
+ const targets = id ? records.filter((record) => record.id === id) : records;
120
+ for (const record of targets) {
121
+ clearTimer(record);
122
+ record.open = false;
123
+ }
124
+ emit();
125
+ if (!browser()) {
126
+ records = id ? records.filter((record) => record.id !== id) : [];
127
+ emit();
128
+ return;
129
+ }
130
+ for (const record of targets) setTimeout(() => removeToast(record.id), closeDelay);
131
+ };
132
+ /** Remove all generated toasts immediately. Useful for deterministic tests and stories. */
133
+ var clearToasts = () => {
134
+ for (const record of records) clearTimer(record);
135
+ records = [];
136
+ emit();
137
+ };
138
+ /** Provider for toast defaults. */
139
+ var ToastProvider = ({ children, duration = defaultDuration, label }) => {
140
+ configuredDuration = duration;
141
+ configuredLabel = label;
142
+ return children;
143
+ };
144
+ var viewportInsets = {
145
+ "bottom-center": "bottom:0;left:50%;transform:translateX(-50%)",
146
+ "bottom-left": "bottom:0;left:0",
147
+ "bottom-right": "bottom:0;right:0",
148
+ "top-center": "top:0;left:50%;transform:translateX(-50%)",
149
+ "top-left": "top:0;left:0",
150
+ "top-right": "top:0;right:0"
151
+ };
152
+ /** Toast viewport where generated or manually composed toasts are rendered. */
153
+ var ToastViewport = ({ children, class: classes, hotkey = hotkeyDefault, label, popover = "manual", position = "bottom-right", style, tabIndex = -1, ...attrs }) => {
154
+ const hotkeyText = hotkey.join("+");
155
+ const title = label ?? configuredLabel ?? `Notifications (${hotkeyText})`;
156
+ return /* @__PURE__ */ jsx("ol", {
157
+ ...attrs,
158
+ "aria-atomic": "false",
159
+ "aria-label": title,
160
+ "aria-live": "polite",
161
+ class: classes,
162
+ "data-hotkey": hotkeyText,
163
+ "data-position": position,
164
+ "data-slot": "toast-viewport",
165
+ popover,
166
+ role: "region",
167
+ style: popupStyle("border:none;background:transparent;color:inherit;overflow:visible", viewportInsets[position], style),
168
+ tabIndex,
169
+ children
170
+ });
171
+ };
172
+ /** Toast root for composed notifications. */
173
+ var Toast = ({ children, class: classes, open = true, role, type = "foreground", variant = "default", ...attrs }) => open ? /* @__PURE__ */ jsx("li", {
174
+ ...attrs,
175
+ class: classes,
176
+ "data-slot": "toast",
177
+ "data-state": "open",
178
+ "data-type": type,
179
+ "data-variant": variant,
180
+ role: role ?? (variant === "danger" ? "alert" : "status"),
181
+ children
182
+ }) : null;
183
+ /** Toast title slot. */
184
+ var ToastTitle = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
185
+ ...attrs,
186
+ class: classes,
187
+ "data-slot": "toast-title",
188
+ children
189
+ });
190
+ /** Toast description slot. */
191
+ var ToastDescription = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
192
+ ...attrs,
193
+ class: classes,
194
+ "data-slot": "toast-description",
195
+ children
196
+ });
197
+ /** Toast action button. */
198
+ var ToastAction = ({ altText, children, class: classes, onAction, type = "button", "set:onclick": setOnClick, ...attrs }) => /* @__PURE__ */ jsx("button", {
199
+ ...attrs,
200
+ "aria-label": altText,
201
+ class: classes,
202
+ "data-slot": "toast-action",
203
+ "set:onclick": composeClick(setOnClick, onAction),
204
+ type,
205
+ children
206
+ });
207
+ /** Toast close button. */
208
+ var ToastClose = ({ children, class: classes, onClose, type = "button", "set:onclick": setOnClick, ...attrs }) => /* @__PURE__ */ jsx("button", {
209
+ ...attrs,
210
+ "aria-label": attrs["aria-label"] ?? "Close",
211
+ class: classes,
212
+ "data-slot": "toast-close",
213
+ "set:onclick": (event) => {
214
+ event.currentTarget.closest("[data-slot=\"toast-viewport\"]")?.focus();
215
+ composeClick(setOnClick, onClose)(event);
216
+ },
217
+ type,
218
+ children
219
+ });
220
+ /** Render generated toasts from the module-level toast store. */
221
+ var Toaster = function* ({ hotkey: hotkey$1 = hotkeyDefault, limit = defaultLimit, pauseOnWindowBlur = true }) {
222
+ let rootViewport = null;
223
+ let portalViewport = null;
224
+ let portalMount = null;
225
+ let portalWatch = null;
226
+ let renderPortal = null;
227
+ const modals = [];
228
+ let toasts = visibleToasts(toNumber(limit, defaultLimit));
229
+ let currentHotkey = hotkeyValue(hotkey$1);
230
+ let currentPauseOnWindowBlur = booleanValue(pauseOnWindowBlur, true);
231
+ let hovered = false;
232
+ const heights = /* @__PURE__ */ new Map();
233
+ const viewport = () => portalMount ? portalViewport : rootViewport;
234
+ const measure = () => {
235
+ const host = viewport();
236
+ if (!host) return;
237
+ const known = new Set(records.map((record) => record.id));
238
+ let changed = false;
239
+ for (const key of Array.from(heights.keys())) if (!known.has(key)) heights.delete(key);
240
+ for (const element of Array.from(host.querySelectorAll("[data-slot=\"toast\"]"))) {
241
+ const id = element.dataset.toastId;
242
+ if (!id || !known.has(id)) continue;
243
+ const height = element.offsetHeight;
244
+ if (heights.get(id) !== height) {
245
+ heights.set(id, height);
246
+ changed = true;
247
+ }
248
+ }
249
+ if (changed) this.next();
250
+ };
251
+ let topLayerEpoch = 0;
252
+ let promotedEpoch = 0;
253
+ const syncViewport = () => {
254
+ if (!rootViewport?.isConnected) return;
255
+ if (promotedEpoch < topLayerEpoch) closePopover(rootViewport);
256
+ openPopover(rootViewport);
257
+ promotedEpoch = topLayerEpoch;
258
+ };
259
+ const syncPortal = () => {
260
+ if (portalViewport?.isConnected) openPopover(portalViewport);
261
+ };
262
+ const onViewportToggle = (event) => {
263
+ if (event.newState !== "closed") return;
264
+ const target = event.currentTarget;
265
+ queueMicrotask(() => {
266
+ if (target === portalViewport) syncPortal();
267
+ else if (target === rootViewport) syncViewport();
268
+ });
269
+ };
270
+ const pauseStack = () => {
271
+ for (const record of records) pauseRecord(record);
272
+ };
273
+ const resumeStack = () => {
274
+ for (const record of records) resumeRecord(record);
275
+ };
276
+ const portalOutlet = (dialog) => Array.from(dialog.querySelectorAll("[data-slot=\"dialog-portal\"]")).find((element) => element.closest("dialog") === dialog) ?? null;
277
+ const portalTarget = () => {
278
+ for (let index = modals.length - 1; index >= 0; index--) {
279
+ const dialog = modals[index];
280
+ if (!dialog.isConnected || !dialog.open) {
281
+ modals.splice(index, 1);
282
+ continue;
283
+ }
284
+ return portalOutlet(dialog);
285
+ }
286
+ return null;
287
+ };
288
+ const syncWatch = () => {
289
+ const wanted = Boolean(portalMount) || modals.length > 0;
290
+ if (wanted && !portalWatch) {
291
+ portalWatch = new MutationObserver(() => {
292
+ if (portalMount && !portalMount.isConnected || modals.some((dialog) => !dialog.isConnected)) rehome();
293
+ });
294
+ portalWatch.observe(document.documentElement, {
295
+ childList: true,
296
+ subtree: true
297
+ });
298
+ } else if (!wanted && portalWatch) {
299
+ portalWatch.disconnect();
300
+ portalWatch = null;
301
+ }
302
+ };
303
+ const rehome = () => {
304
+ const outlet = portalTarget();
305
+ if (outlet !== (portalMount?.parentElement ?? null)) {
306
+ if (portalMount) {
307
+ render(null, portalMount);
308
+ portalMount.remove();
309
+ portalMount = null;
310
+ portalViewport = null;
311
+ }
312
+ if (outlet) {
313
+ portalMount = document.createElement("div");
314
+ portalMount.style.display = "contents";
315
+ outlet.append(portalMount);
316
+ renderPortal?.([]);
317
+ }
318
+ if (hovered) {
319
+ hovered = false;
320
+ resumeStack();
321
+ }
322
+ this.next();
323
+ }
324
+ syncWatch();
325
+ };
326
+ const onTopLayerToggle = (event) => {
327
+ const target = event.target;
328
+ if (!(target instanceof HTMLDialogElement)) return;
329
+ const state = event.newState;
330
+ if (state === "open" && target.matches(":modal")) {
331
+ topLayerEpoch++;
332
+ if (!modals.includes(target)) modals.push(target);
333
+ queueMicrotask(() => {
334
+ syncViewport();
335
+ rehome();
336
+ });
337
+ } else if (state === "closed" && modals.includes(target)) {
338
+ modals.splice(modals.indexOf(target), 1);
339
+ queueMicrotask(rehome);
340
+ }
341
+ };
342
+ const sync = () => this.next();
343
+ const unsubscribe = subscribeToasts(sync);
344
+ this.signal.addEventListener("abort", () => {
345
+ unsubscribe();
346
+ portalWatch?.disconnect();
347
+ portalWatch = null;
348
+ if (portalMount) {
349
+ render(null, portalMount);
350
+ portalMount.remove();
351
+ portalMount = null;
352
+ portalViewport = null;
353
+ }
354
+ }, { once: true });
355
+ const focusViewport = (_event) => viewport()?.focus();
356
+ const pauseAll = () => {
357
+ if (!currentPauseOnWindowBlur) return;
358
+ for (const record of records) pauseRecord(record);
359
+ };
360
+ const resumeAll = () => {
361
+ if (!currentPauseOnWindowBlur) return;
362
+ for (const record of records) resumeRecord(record);
363
+ };
364
+ hotkey(this, {
365
+ keys: () => currentHotkey.join("+"),
366
+ active: () => toasts.length > 0,
367
+ onPress: (event) => focusViewport(event)
368
+ });
369
+ if (browser()) {
370
+ window.addEventListener("blur", pauseAll, { signal: this.signal });
371
+ window.addEventListener("focus", resumeAll, { signal: this.signal });
372
+ document.addEventListener("toggle", onTopLayerToggle, {
373
+ capture: true,
374
+ signal: this.signal
375
+ });
376
+ for (const dialog of Array.from(document.querySelectorAll("dialog"))) if (dialog.matches(":modal")) modals.push(dialog);
377
+ }
378
+ for (const { actionWrapperClass, class: classes, closeButton = true, closeChildren, closeClass, contentClass, descriptionClass, duration = defaultDuration, expand = false, hotkey = hotkeyDefault, label, limit = defaultLimit, pauseOnWindowBlur = true, position = "bottom-right", titleClass, toastClass, ...attrs } of this) {
379
+ const nextClass = stringValue(classes);
380
+ const nextCloseButton = booleanValue(closeButton, true);
381
+ const nextDuration = toNumber(duration, defaultDuration);
382
+ const nextHotkey = hotkeyValue(hotkey);
383
+ const nextLabel = stringValue(label) ?? configuredLabel;
384
+ const nextLimit = toNumber(limit, defaultLimit);
385
+ const nextPauseOnWindowBlur = booleanValue(pauseOnWindowBlur, true);
386
+ const nextPosition = positionValue(position);
387
+ configuredDuration = nextDuration;
388
+ configuredPosition = nextPosition;
389
+ currentHotkey = nextHotkey;
390
+ currentPauseOnWindowBlur = nextPauseOnWindowBlur;
391
+ toasts = visibleToasts(nextLimit, nextPosition);
392
+ if (hovered) pauseStack();
393
+ const bottom = nextPosition.startsWith("bottom");
394
+ const lift = bottom ? -1 : 1;
395
+ const expanded = booleanValue(expand, false) || hovered;
396
+ const frontHeight = heights.get(toasts[0]?.id ?? "") ?? 0;
397
+ const openToasts = toasts.filter((item) => item.open);
398
+ const stackHeight = edgeInset + (expanded ? openToasts.reduce((total, item) => total + (heights.get(item.id) ?? 0), 0) + Math.max(0, openToasts.length - 1) * stackGap : frontHeight);
399
+ const view = (items, portal) => /* @__PURE__ */ jsx(ToastViewport, {
400
+ ...attrs,
401
+ class: nextClass,
402
+ "data-portal": portal ? "true" : void 0,
403
+ hotkey: nextHotkey,
404
+ id: portal ? void 0 : attrs.id,
405
+ label: nextLabel,
406
+ position: nextPosition,
407
+ ref: (element) => {
408
+ if (portal) portalViewport = element;
409
+ else rootViewport = element;
410
+ },
411
+ style: `--front-toast-height:${frontHeight}px;--toast-gap:${stackGap}px;height:${stackHeight}px`,
412
+ "set:ontoggle": onViewportToggle,
413
+ "set:onpointerenter": () => {
414
+ if (hovered) return;
415
+ hovered = true;
416
+ pauseStack();
417
+ this.next();
418
+ },
419
+ "set:onpointerleave": () => {
420
+ if (!hovered) return;
421
+ hovered = false;
422
+ resumeStack();
423
+ this.next();
424
+ },
425
+ children: items.map((item, index) => {
426
+ const depth = Math.min(index, 2);
427
+ const offset = expanded ? items.slice(0, index).reduce((total, prior) => prior.open ? total + (heights.get(prior.id) ?? 0) + stackGap : total, 0) : depth * 14;
428
+ return /* @__PURE__ */ jsxs(Toast, {
429
+ class: clx(resolveClass(toastClass, item), item.class),
430
+ "data-closing": item.open ? "false" : "true",
431
+ "data-expanded": expanded ? "true" : "false",
432
+ "data-front": index === 0 ? "true" : "false",
433
+ "data-side": bottom ? "bottom" : "top",
434
+ "data-toast-id": item.id,
435
+ "data-toast-index": index,
436
+ role: item.role,
437
+ "set:onfocusin": () => pauseRecord(item),
438
+ "set:onfocusout": () => resumeRecord(item),
439
+ "set:onpointerenter": () => pauseRecord(item),
440
+ "set:onpointerleave": () => {
441
+ if (!hovered) resumeRecord(item);
442
+ },
443
+ style: [
444
+ `--toast-y:${lift * offset}px`,
445
+ `--toast-scale:${expanded ? 1 : 1 - depth * .04}`,
446
+ `z-index:${100 - index}`
447
+ ].join(";"),
448
+ type: item.type,
449
+ variant: item.variant,
450
+ children: [
451
+ /* @__PURE__ */ jsxs("div", {
452
+ class: contentClass,
453
+ "data-slot": "toast-content",
454
+ children: [item.title == null ? null : /* @__PURE__ */ jsx(ToastTitle, {
455
+ class: titleClass,
456
+ children: item.title
457
+ }), item.description == null ? null : /* @__PURE__ */ jsx(ToastDescription, {
458
+ class: descriptionClass,
459
+ children: item.description
460
+ })]
461
+ }),
462
+ item.action == null ? null : /* @__PURE__ */ jsx("div", {
463
+ class: actionWrapperClass,
464
+ "data-slot": "toast-action-wrapper",
465
+ children: item.action
466
+ }),
467
+ item.closeButton ?? nextCloseButton ? /* @__PURE__ */ jsx(ToastClose, {
468
+ class: closeClass,
469
+ onClose: () => dismissToast(item.id),
470
+ children: closeChildren
471
+ }) : null
472
+ ]
473
+ }, item.id);
474
+ })
475
+ });
476
+ renderPortal = (items) => {
477
+ if (!portalMount?.isConnected) return;
478
+ render(view(items, true), portalMount);
479
+ syncPortal();
480
+ };
481
+ if (browser()) queueMicrotask(() => {
482
+ if (portalMount?.isConnected) renderPortal?.(toasts);
483
+ else if (portalMount || modals.length && portalTarget()) rehome();
484
+ syncViewport();
485
+ measure();
486
+ });
487
+ yield view(portalMount ? [] : toasts, false);
488
+ }
489
+ };
490
+ Toaster.attrs = { "data-slot": "toaster" };
491
+ //#endregion
492
+ export { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, clearToasts, dismissToast, toast, updateToast };