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.
- package/LICENSE +15 -0
- package/README.md +126 -0
- package/dist/accordion.js +130 -0
- package/dist/avatar.js +54 -0
- package/dist/calendar.js +2 -0
- package/dist/carousel.js +239 -0
- package/dist/chart.js +790 -0
- package/dist/checkbox-group.js +70 -0
- package/dist/checkbox.js +77 -0
- package/dist/chunks/bar-CVafh6C1.js +99 -0
- package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
- package/dist/chunks/collection-DtRB63U4.js +111 -0
- package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
- package/dist/chunks/menu-B45IyHHC.js +704 -0
- package/dist/chunks/native-BJdhd9XJ.js +20 -0
- package/dist/chunks/popup-C8Bb3l_g.js +459 -0
- package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
- package/dist/chunks/position-D6_i_SRn.js +434 -0
- package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
- package/dist/collapsible.js +106 -0
- package/dist/command.js +263 -0
- package/dist/context-menu.js +112 -0
- package/dist/data-table.js +5 -0
- package/dist/dialog.js +207 -0
- package/dist/direction.js +22 -0
- package/dist/drawer.js +139 -0
- package/dist/field.js +26 -0
- package/dist/index.js +38 -0
- package/dist/input-date.js +994 -0
- package/dist/input-group.js +52 -0
- package/dist/input-otp.js +179 -0
- package/dist/menu.js +2 -0
- package/dist/menubar.js +236 -0
- package/dist/message-scroller.js +446 -0
- package/dist/navigation-menu.js +330 -0
- package/dist/popover.js +307 -0
- package/dist/progress.js +39 -0
- package/dist/radio-group.js +107 -0
- package/dist/resizable.js +172 -0
- package/dist/select.js +961 -0
- package/dist/sidebar.js +343 -0
- package/dist/slider.js +259 -0
- package/dist/switch.js +53 -0
- package/dist/tabs.js +182 -0
- package/dist/toast.js +492 -0
- package/dist/toggle-group.js +111 -0
- package/dist/toggle.js +52 -0
- package/dist/toolbar.js +127 -0
- package/dist/tooltip.js +196 -0
- package/dist/utils.js +104 -0
- package/dist/virtual-list.js +2 -0
- package/package.json +250 -0
- package/src/accordion.tsx +261 -0
- package/src/availability.ts +261 -0
- package/src/avatar.tsx +99 -0
- package/src/bar.ts +156 -0
- package/src/calendar.tsx +1441 -0
- package/src/carousel.tsx +424 -0
- package/src/chart.tsx +1194 -0
- package/src/checkbox-group.tsx +132 -0
- package/src/checkbox.tsx +130 -0
- package/src/collapsible.tsx +188 -0
- package/src/collection.ts +154 -0
- package/src/command.tsx +511 -0
- package/src/context-menu.tsx +233 -0
- package/src/data-table-contract.ts +143 -0
- package/src/data-table-model.ts +760 -0
- package/src/data-table.tsx +475 -0
- package/src/dialog.tsx +393 -0
- package/src/direction.tsx +45 -0
- package/src/drawer.tsx +251 -0
- package/src/field.tsx +61 -0
- package/src/index.ts +37 -0
- package/src/input-date.tsx +1539 -0
- package/src/input-group.tsx +142 -0
- package/src/input-otp.tsx +324 -0
- package/src/menu-cluster.ts +124 -0
- package/src/menu.tsx +1095 -0
- package/src/menubar.tsx +459 -0
- package/src/message-scroller.tsx +732 -0
- package/src/native.ts +26 -0
- package/src/navigation-menu.tsx +578 -0
- package/src/popover.tsx +519 -0
- package/src/popup-surface.tsx +31 -0
- package/src/popup.ts +569 -0
- package/src/position.ts +523 -0
- package/src/progress.tsx +70 -0
- package/src/radio-group.tsx +186 -0
- package/src/resizable.tsx +310 -0
- package/src/segments.ts +922 -0
- package/src/select.tsx +1501 -0
- package/src/sidebar.tsx +683 -0
- package/src/slider.tsx +424 -0
- package/src/switch.tsx +104 -0
- package/src/tabs.tsx +314 -0
- package/src/toast.tsx +923 -0
- package/src/toggle-group.tsx +249 -0
- package/src/toggle.tsx +91 -0
- package/src/toolbar.tsx +212 -0
- package/src/tooltip.tsx +359 -0
- package/src/utils.ts +204 -0
- package/src/virtual-list.tsx +205 -0
- 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 };
|