@fluix-ui/vanilla 0.0.2
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 +21 -0
- package/dist/index.cjs +748 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.global.js +1208 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +743 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
var Fluix = (function (exports) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ../core/dist/index.js
|
|
5
|
+
function createStore(initialState) {
|
|
6
|
+
let state = initialState;
|
|
7
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
8
|
+
const store = {
|
|
9
|
+
getSnapshot() {
|
|
10
|
+
return state;
|
|
11
|
+
},
|
|
12
|
+
getServerSnapshot() {
|
|
13
|
+
return initialState;
|
|
14
|
+
},
|
|
15
|
+
subscribe(listener) {
|
|
16
|
+
listeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
update(fn) {
|
|
22
|
+
const next = fn(state);
|
|
23
|
+
if (next === state) return;
|
|
24
|
+
state = next;
|
|
25
|
+
for (const listener of listeners) {
|
|
26
|
+
listener();
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
set(next) {
|
|
30
|
+
store.update(() => next);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return store;
|
|
34
|
+
}
|
|
35
|
+
var DEFAULT_CONFIG = {
|
|
36
|
+
stiffness: 100,
|
|
37
|
+
damping: 10,
|
|
38
|
+
mass: 1
|
|
39
|
+
};
|
|
40
|
+
var SOLVER_STEP = 1 / 120;
|
|
41
|
+
var SETTLE_THRESHOLD = 1e-3;
|
|
42
|
+
var MAX_DURATION_S = 3;
|
|
43
|
+
function resolveConfig(config) {
|
|
44
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
45
|
+
}
|
|
46
|
+
function simulateSpring(config) {
|
|
47
|
+
const { stiffness, damping, mass } = resolveConfig(config);
|
|
48
|
+
const samples = [];
|
|
49
|
+
let state = { position: 0, velocity: 0 };
|
|
50
|
+
let t = 0;
|
|
51
|
+
samples.push([0, 0]);
|
|
52
|
+
while (t < MAX_DURATION_S) {
|
|
53
|
+
const dt = SOLVER_STEP;
|
|
54
|
+
const acceleration = (-stiffness * (state.position - 1) - damping * state.velocity) / mass;
|
|
55
|
+
const midVelocity = state.velocity + acceleration * (dt / 2);
|
|
56
|
+
const midPosition = state.position + state.velocity * (dt / 2);
|
|
57
|
+
const midAcceleration = (-stiffness * (midPosition - 1) - damping * midVelocity) / mass;
|
|
58
|
+
state = {
|
|
59
|
+
velocity: state.velocity + midAcceleration * dt,
|
|
60
|
+
position: state.position + midVelocity * dt
|
|
61
|
+
};
|
|
62
|
+
t += dt;
|
|
63
|
+
samples.push([t, state.position]);
|
|
64
|
+
if (Math.abs(state.position - 1) < SETTLE_THRESHOLD && Math.abs(state.velocity) < SETTLE_THRESHOLD) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
samples.push([t, 1]);
|
|
69
|
+
return samples;
|
|
70
|
+
}
|
|
71
|
+
function animateSpring(el, properties, config) {
|
|
72
|
+
const samples = simulateSpring(config);
|
|
73
|
+
const totalTime = samples[samples.length - 1][0];
|
|
74
|
+
const durationMs = Math.round(totalTime * 1e3);
|
|
75
|
+
const targetFrames = 40;
|
|
76
|
+
const step = Math.max(1, Math.floor(samples.length / targetFrames));
|
|
77
|
+
const keyframes = [];
|
|
78
|
+
for (let i = 0; i < samples.length; i += step) {
|
|
79
|
+
const [t, value] = samples[i];
|
|
80
|
+
const frame = { offset: t / totalTime };
|
|
81
|
+
for (const [prop, { from, to, unit }] of Object.entries(properties)) {
|
|
82
|
+
const v = from + value * (to - from);
|
|
83
|
+
frame[prop] = unit ? `${round(v)}${unit}` : round(v);
|
|
84
|
+
}
|
|
85
|
+
keyframes.push(frame);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return el.animate(keyframes, {
|
|
89
|
+
duration: durationMs,
|
|
90
|
+
fill: "forwards",
|
|
91
|
+
easing: "linear"
|
|
92
|
+
// Easing is baked into the keyframes
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function round(n) {
|
|
99
|
+
return Math.round(n * 1e3) / 1e3;
|
|
100
|
+
}
|
|
101
|
+
var FLUIX_SPRING = {
|
|
102
|
+
stiffness: 170,
|
|
103
|
+
damping: 18,
|
|
104
|
+
mass: 1
|
|
105
|
+
};
|
|
106
|
+
var TOAST_DEFAULTS = {
|
|
107
|
+
duration: 6e3,
|
|
108
|
+
lightFill: "#FFFFFF",
|
|
109
|
+
darkFill: "#141416",
|
|
110
|
+
roundness: 16,
|
|
111
|
+
theme: "light",
|
|
112
|
+
position: "top-right",
|
|
113
|
+
layout: "stack"
|
|
114
|
+
};
|
|
115
|
+
var EXIT_DURATION_MS = TOAST_DEFAULTS.duration * 0.1;
|
|
116
|
+
var AUTO_EXPAND_DELAY_MS = TOAST_DEFAULTS.duration * 0.025;
|
|
117
|
+
var AUTO_COLLAPSE_DELAY_MS = TOAST_DEFAULTS.duration - 450;
|
|
118
|
+
var idCounter = 0;
|
|
119
|
+
function generateId() {
|
|
120
|
+
return `${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
121
|
+
}
|
|
122
|
+
function createToastMachine() {
|
|
123
|
+
const store = createStore({
|
|
124
|
+
toasts: [],
|
|
125
|
+
config: { position: TOAST_DEFAULTS.position, layout: TOAST_DEFAULTS.layout }
|
|
126
|
+
});
|
|
127
|
+
const exitTimers = /* @__PURE__ */ new Map();
|
|
128
|
+
function getConfig() {
|
|
129
|
+
return store.getSnapshot().config;
|
|
130
|
+
}
|
|
131
|
+
function resolveAutopilot(opts, duration) {
|
|
132
|
+
if (opts.autopilot === false || !duration || duration <= 0) return {};
|
|
133
|
+
const cfg = typeof opts.autopilot === "object" ? opts.autopilot : void 0;
|
|
134
|
+
const clamp = (v) => Math.min(duration, Math.max(0, v));
|
|
135
|
+
return {
|
|
136
|
+
expandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY_MS),
|
|
137
|
+
collapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY_MS)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function mergeDefaults(opts) {
|
|
141
|
+
const defaults = getConfig().defaults;
|
|
142
|
+
if (!defaults) return opts;
|
|
143
|
+
return {
|
|
144
|
+
...defaults,
|
|
145
|
+
...opts,
|
|
146
|
+
styles: { ...defaults.styles, ...opts.styles }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildItem(opts, id, fallbackPosition) {
|
|
150
|
+
const merged = mergeDefaults(opts);
|
|
151
|
+
const theme = merged.theme ?? TOAST_DEFAULTS.theme;
|
|
152
|
+
const duration = merged.duration ?? TOAST_DEFAULTS.duration;
|
|
153
|
+
const auto = resolveAutopilot(merged, duration);
|
|
154
|
+
return {
|
|
155
|
+
...merged,
|
|
156
|
+
id,
|
|
157
|
+
instanceId: generateId(),
|
|
158
|
+
state: opts.state ?? "success",
|
|
159
|
+
theme,
|
|
160
|
+
position: merged.position ?? fallbackPosition ?? getConfig().position ?? TOAST_DEFAULTS.position,
|
|
161
|
+
duration,
|
|
162
|
+
fill: merged.fill ?? (theme === "dark" ? TOAST_DEFAULTS.darkFill : TOAST_DEFAULTS.lightFill),
|
|
163
|
+
roundness: merged.roundness ?? TOAST_DEFAULTS.roundness,
|
|
164
|
+
exiting: false,
|
|
165
|
+
autoExpandDelayMs: auto.expandDelayMs,
|
|
166
|
+
autoCollapseDelayMs: auto.collapseDelayMs
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function create(opts) {
|
|
170
|
+
const requestedId = opts.id ?? generateId();
|
|
171
|
+
let resolvedId = requestedId;
|
|
172
|
+
store.update((prev) => {
|
|
173
|
+
const live = prev.toasts.filter((t) => !t.exiting);
|
|
174
|
+
const existing = live.find((t) => t.id === requestedId);
|
|
175
|
+
const item = buildItem(opts, requestedId, existing?.position);
|
|
176
|
+
const layout = prev.config.layout ?? TOAST_DEFAULTS.layout;
|
|
177
|
+
if (existing) {
|
|
178
|
+
return {
|
|
179
|
+
...prev,
|
|
180
|
+
toasts: prev.toasts.map((t) => t.id === requestedId ? item : t)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (layout === "notch") {
|
|
184
|
+
const currentAtPosition = live.find((t) => t.position === item.position);
|
|
185
|
+
if (currentAtPosition) {
|
|
186
|
+
resolvedId = currentAtPosition.id;
|
|
187
|
+
const replaced = {
|
|
188
|
+
...item,
|
|
189
|
+
id: currentAtPosition.id,
|
|
190
|
+
instanceId: currentAtPosition.instanceId
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
...prev,
|
|
194
|
+
toasts: prev.toasts.map((t) => t.id === currentAtPosition.id ? replaced : t)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...prev,
|
|
199
|
+
toasts: [...prev.toasts.filter((t) => t.id !== requestedId), item]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
...prev,
|
|
204
|
+
toasts: [...prev.toasts.filter((t) => t.id !== requestedId), item]
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
return resolvedId;
|
|
208
|
+
}
|
|
209
|
+
function update(id, opts) {
|
|
210
|
+
store.update((prev) => {
|
|
211
|
+
const existing = prev.toasts.find((t) => t.id === id);
|
|
212
|
+
if (!existing) return prev;
|
|
213
|
+
const item = buildItem(opts, id, existing.position);
|
|
214
|
+
return {
|
|
215
|
+
...prev,
|
|
216
|
+
toasts: prev.toasts.map((t) => t.id === id ? item : t)
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function dismiss(id) {
|
|
221
|
+
const { toasts } = store.getSnapshot();
|
|
222
|
+
const item = toasts.find((t) => t.id === id);
|
|
223
|
+
if (!item || item.exiting) return;
|
|
224
|
+
store.update((prev) => ({
|
|
225
|
+
...prev,
|
|
226
|
+
toasts: prev.toasts.map((t) => t.id === id ? { ...t, exiting: true } : t)
|
|
227
|
+
}));
|
|
228
|
+
const timer = setTimeout(() => {
|
|
229
|
+
exitTimers.delete(id);
|
|
230
|
+
store.update((prev) => ({
|
|
231
|
+
...prev,
|
|
232
|
+
toasts: prev.toasts.filter((t) => t.id !== id)
|
|
233
|
+
}));
|
|
234
|
+
}, EXIT_DURATION_MS);
|
|
235
|
+
exitTimers.set(id, timer);
|
|
236
|
+
}
|
|
237
|
+
function clear(position) {
|
|
238
|
+
store.update((prev) => ({
|
|
239
|
+
...prev,
|
|
240
|
+
toasts: position ? prev.toasts.filter((t) => t.position !== position) : []
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
function configure(config) {
|
|
244
|
+
store.update((prev) => ({
|
|
245
|
+
...prev,
|
|
246
|
+
config: { ...prev.config, ...config }
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
function destroy() {
|
|
250
|
+
for (const timer of exitTimers.values()) clearTimeout(timer);
|
|
251
|
+
exitTimers.clear();
|
|
252
|
+
}
|
|
253
|
+
return { store, create, update, dismiss, clear, configure, destroy };
|
|
254
|
+
}
|
|
255
|
+
var machine = null;
|
|
256
|
+
function getToastMachine() {
|
|
257
|
+
if (!machine) {
|
|
258
|
+
machine = createToastMachine();
|
|
259
|
+
}
|
|
260
|
+
return machine;
|
|
261
|
+
}
|
|
262
|
+
function resetToastMachine() {
|
|
263
|
+
machine?.destroy();
|
|
264
|
+
machine = null;
|
|
265
|
+
}
|
|
266
|
+
var fluix = {
|
|
267
|
+
/** Show a toast with explicit options. */
|
|
268
|
+
show(opts) {
|
|
269
|
+
return getToastMachine().create(opts);
|
|
270
|
+
},
|
|
271
|
+
/** Show a success toast. */
|
|
272
|
+
success(opts) {
|
|
273
|
+
return getToastMachine().create({ ...opts, state: "success" });
|
|
274
|
+
},
|
|
275
|
+
/** Show an error toast. */
|
|
276
|
+
error(opts) {
|
|
277
|
+
return getToastMachine().create({ ...opts, state: "error" });
|
|
278
|
+
},
|
|
279
|
+
/** Show a warning toast. */
|
|
280
|
+
warning(opts) {
|
|
281
|
+
return getToastMachine().create({ ...opts, state: "warning" });
|
|
282
|
+
},
|
|
283
|
+
/** Show an info toast. */
|
|
284
|
+
info(opts) {
|
|
285
|
+
return getToastMachine().create({ ...opts, state: "info" });
|
|
286
|
+
},
|
|
287
|
+
/** Show an action toast (with button). */
|
|
288
|
+
action(opts) {
|
|
289
|
+
return getToastMachine().create({ ...opts, state: "action" });
|
|
290
|
+
},
|
|
291
|
+
/**
|
|
292
|
+
* Bind a promise to a toast.
|
|
293
|
+
* Shows loading state, then transitions to success or error.
|
|
294
|
+
*/
|
|
295
|
+
promise(promise, opts) {
|
|
296
|
+
const m = getToastMachine();
|
|
297
|
+
const id = m.create({
|
|
298
|
+
...opts.loading,
|
|
299
|
+
state: "loading",
|
|
300
|
+
duration: null,
|
|
301
|
+
// Persistent until promise resolves
|
|
302
|
+
position: opts.position
|
|
303
|
+
});
|
|
304
|
+
const p = typeof promise === "function" ? promise() : promise;
|
|
305
|
+
p.then((data) => {
|
|
306
|
+
if (opts.action) {
|
|
307
|
+
const actionOpts = typeof opts.action === "function" ? opts.action(data) : opts.action;
|
|
308
|
+
m.update(id, { ...actionOpts, state: "action", id });
|
|
309
|
+
} else {
|
|
310
|
+
const successOpts = typeof opts.success === "function" ? opts.success(data) : opts.success;
|
|
311
|
+
m.update(id, { ...successOpts, state: "success", id });
|
|
312
|
+
}
|
|
313
|
+
}).catch((err) => {
|
|
314
|
+
const errorOpts = typeof opts.error === "function" ? opts.error(err) : opts.error;
|
|
315
|
+
m.update(id, { ...errorOpts, state: "error", id });
|
|
316
|
+
});
|
|
317
|
+
return p;
|
|
318
|
+
},
|
|
319
|
+
/** Dismiss a specific toast by ID. */
|
|
320
|
+
dismiss(id) {
|
|
321
|
+
getToastMachine().dismiss(id);
|
|
322
|
+
},
|
|
323
|
+
/** Clear all toasts, or only those at a specific position. */
|
|
324
|
+
clear(position) {
|
|
325
|
+
getToastMachine().clear(position);
|
|
326
|
+
},
|
|
327
|
+
/** Update default toaster configuration. */
|
|
328
|
+
configure(config) {
|
|
329
|
+
getToastMachine().configure(config);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function getPillAlign(position) {
|
|
333
|
+
if (position.includes("right")) return "right";
|
|
334
|
+
if (position.includes("center")) return "center";
|
|
335
|
+
return "left";
|
|
336
|
+
}
|
|
337
|
+
function getExpandDirection(position) {
|
|
338
|
+
return position.startsWith("top") ? "bottom" : "top";
|
|
339
|
+
}
|
|
340
|
+
function getViewportAttrs(position, layout = "stack") {
|
|
341
|
+
return {
|
|
342
|
+
"data-fluix-viewport": "",
|
|
343
|
+
"data-position": position,
|
|
344
|
+
"data-layout": layout,
|
|
345
|
+
"aria-live": "polite",
|
|
346
|
+
role: "region"
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function getToastAttrs(item, context) {
|
|
350
|
+
const edge = getExpandDirection(item.position);
|
|
351
|
+
const pillAlign = getPillAlign(item.position);
|
|
352
|
+
return {
|
|
353
|
+
viewport: getViewportAttrs(item.position),
|
|
354
|
+
root: {
|
|
355
|
+
"data-fluix-toast": "",
|
|
356
|
+
"data-state": item.state,
|
|
357
|
+
"data-theme": item.theme,
|
|
358
|
+
"data-ready": String(context.ready),
|
|
359
|
+
"data-expanded": String(context.expanded),
|
|
360
|
+
"data-exiting": String(item.exiting),
|
|
361
|
+
"data-edge": edge,
|
|
362
|
+
"data-position": pillAlign
|
|
363
|
+
},
|
|
364
|
+
canvas: {
|
|
365
|
+
"data-fluix-canvas": "",
|
|
366
|
+
"data-edge": edge
|
|
367
|
+
},
|
|
368
|
+
header: {
|
|
369
|
+
"data-fluix-header": "",
|
|
370
|
+
"data-edge": edge
|
|
371
|
+
},
|
|
372
|
+
badge: {
|
|
373
|
+
"data-fluix-badge": "",
|
|
374
|
+
"data-state": item.state
|
|
375
|
+
},
|
|
376
|
+
title: {
|
|
377
|
+
"data-fluix-title": "",
|
|
378
|
+
"data-state": item.state
|
|
379
|
+
},
|
|
380
|
+
content: {
|
|
381
|
+
"data-fluix-content": "",
|
|
382
|
+
"data-edge": edge,
|
|
383
|
+
"data-visible": String(context.expanded)
|
|
384
|
+
},
|
|
385
|
+
description: {
|
|
386
|
+
"data-fluix-description": ""
|
|
387
|
+
},
|
|
388
|
+
button: {
|
|
389
|
+
"data-fluix-button": "",
|
|
390
|
+
"data-state": item.state
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
var SWIPE_DISMISS_THRESHOLD = 30;
|
|
395
|
+
var SWIPE_MAX_VISUAL = 20;
|
|
396
|
+
function connectToast(element, callbacks, item) {
|
|
397
|
+
const cleanups = [];
|
|
398
|
+
let pointerStartY = null;
|
|
399
|
+
const hasDescription = Boolean(item.description) || Boolean(item.button);
|
|
400
|
+
const handleMouseEnter = () => {
|
|
401
|
+
callbacks.onHoverStart();
|
|
402
|
+
if (hasDescription && item.state !== "loading") {
|
|
403
|
+
callbacks.onExpand();
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
const handleMouseLeave = () => {
|
|
407
|
+
callbacks.onHoverEnd();
|
|
408
|
+
callbacks.onCollapse();
|
|
409
|
+
};
|
|
410
|
+
element.addEventListener("mouseenter", handleMouseEnter);
|
|
411
|
+
element.addEventListener("mouseleave", handleMouseLeave);
|
|
412
|
+
cleanups.push(() => {
|
|
413
|
+
element.removeEventListener("mouseenter", handleMouseEnter);
|
|
414
|
+
element.removeEventListener("mouseleave", handleMouseLeave);
|
|
415
|
+
});
|
|
416
|
+
const handlePointerMove = (e) => {
|
|
417
|
+
if (pointerStartY === null) return;
|
|
418
|
+
const dy = e.clientY - pointerStartY;
|
|
419
|
+
const sign = dy > 0 ? 1 : -1;
|
|
420
|
+
const clamped = Math.min(Math.abs(dy), SWIPE_MAX_VISUAL) * sign;
|
|
421
|
+
element.style.transform = `translateY(${clamped}px)`;
|
|
422
|
+
};
|
|
423
|
+
const handlePointerUp = (e) => {
|
|
424
|
+
if (pointerStartY === null) return;
|
|
425
|
+
const dy = e.clientY - pointerStartY;
|
|
426
|
+
pointerStartY = null;
|
|
427
|
+
element.style.transform = "";
|
|
428
|
+
element.removeEventListener("pointermove", handlePointerMove);
|
|
429
|
+
element.removeEventListener("pointerup", handlePointerUp);
|
|
430
|
+
if (Math.abs(dy) > SWIPE_DISMISS_THRESHOLD) {
|
|
431
|
+
callbacks.onDismiss();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
const handlePointerDown = (e) => {
|
|
435
|
+
if (item.exiting) return;
|
|
436
|
+
const target = e.target;
|
|
437
|
+
if (target.closest("[data-fluix-button]")) return;
|
|
438
|
+
pointerStartY = e.clientY;
|
|
439
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
440
|
+
element.addEventListener("pointermove", handlePointerMove, { passive: true });
|
|
441
|
+
element.addEventListener("pointerup", handlePointerUp, { passive: true });
|
|
442
|
+
};
|
|
443
|
+
element.addEventListener("pointerdown", handlePointerDown);
|
|
444
|
+
cleanups.push(() => {
|
|
445
|
+
element.removeEventListener("pointerdown", handlePointerDown);
|
|
446
|
+
element.removeEventListener("pointermove", handlePointerMove);
|
|
447
|
+
element.removeEventListener("pointerup", handlePointerUp);
|
|
448
|
+
});
|
|
449
|
+
return {
|
|
450
|
+
destroy() {
|
|
451
|
+
for (const cleanup of cleanups) cleanup();
|
|
452
|
+
cleanups.length = 0;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
var Toaster = {
|
|
457
|
+
getMachine: getToastMachine,
|
|
458
|
+
reset: resetToastMachine,
|
|
459
|
+
getAttrs: getToastAttrs,
|
|
460
|
+
getViewportAttrs,
|
|
461
|
+
connect: connectToast
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// src/toast.ts
|
|
465
|
+
var WIDTH = 350;
|
|
466
|
+
var HEIGHT = 40;
|
|
467
|
+
var PILL_PADDING = 10;
|
|
468
|
+
var MIN_EXPAND_RATIO = 2.25;
|
|
469
|
+
var HEADER_EXIT_MS = 600 * 0.7;
|
|
470
|
+
var BODY_MERGE_OVERLAP = 6;
|
|
471
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
472
|
+
function createSvgIcon(state) {
|
|
473
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
474
|
+
svg.setAttribute("width", "14");
|
|
475
|
+
svg.setAttribute("height", "14");
|
|
476
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
477
|
+
svg.setAttribute("fill", "none");
|
|
478
|
+
svg.setAttribute("stroke", "currentColor");
|
|
479
|
+
svg.setAttribute("stroke-width", "2.5");
|
|
480
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
481
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
482
|
+
svg.setAttribute("aria-hidden", "true");
|
|
483
|
+
switch (state) {
|
|
484
|
+
case "success": {
|
|
485
|
+
const p = document.createElementNS(SVG_NS, "polyline");
|
|
486
|
+
p.setAttribute("points", "20 6 9 17 4 12");
|
|
487
|
+
svg.appendChild(p);
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case "error": {
|
|
491
|
+
const l1 = document.createElementNS(SVG_NS, "line");
|
|
492
|
+
l1.setAttribute("x1", "18");
|
|
493
|
+
l1.setAttribute("y1", "6");
|
|
494
|
+
l1.setAttribute("x2", "6");
|
|
495
|
+
l1.setAttribute("y2", "18");
|
|
496
|
+
const l2 = document.createElementNS(SVG_NS, "line");
|
|
497
|
+
l2.setAttribute("x1", "6");
|
|
498
|
+
l2.setAttribute("y1", "6");
|
|
499
|
+
l2.setAttribute("x2", "18");
|
|
500
|
+
l2.setAttribute("y2", "18");
|
|
501
|
+
svg.appendChild(l1);
|
|
502
|
+
svg.appendChild(l2);
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case "warning": {
|
|
506
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
507
|
+
path.setAttribute(
|
|
508
|
+
"d",
|
|
509
|
+
"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
|
510
|
+
);
|
|
511
|
+
const l1 = document.createElementNS(SVG_NS, "line");
|
|
512
|
+
l1.setAttribute("x1", "12");
|
|
513
|
+
l1.setAttribute("y1", "9");
|
|
514
|
+
l1.setAttribute("x2", "12");
|
|
515
|
+
l1.setAttribute("y2", "13");
|
|
516
|
+
const l2 = document.createElementNS(SVG_NS, "line");
|
|
517
|
+
l2.setAttribute("x1", "12");
|
|
518
|
+
l2.setAttribute("y1", "17");
|
|
519
|
+
l2.setAttribute("x2", "12.01");
|
|
520
|
+
l2.setAttribute("y2", "17");
|
|
521
|
+
svg.appendChild(path);
|
|
522
|
+
svg.appendChild(l1);
|
|
523
|
+
svg.appendChild(l2);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "info": {
|
|
527
|
+
const c = document.createElementNS(SVG_NS, "circle");
|
|
528
|
+
c.setAttribute("cx", "12");
|
|
529
|
+
c.setAttribute("cy", "12");
|
|
530
|
+
c.setAttribute("r", "10");
|
|
531
|
+
const l1 = document.createElementNS(SVG_NS, "line");
|
|
532
|
+
l1.setAttribute("x1", "12");
|
|
533
|
+
l1.setAttribute("y1", "16");
|
|
534
|
+
l1.setAttribute("x2", "12");
|
|
535
|
+
l1.setAttribute("y2", "12");
|
|
536
|
+
const l2 = document.createElementNS(SVG_NS, "line");
|
|
537
|
+
l2.setAttribute("x1", "12");
|
|
538
|
+
l2.setAttribute("y1", "8");
|
|
539
|
+
l2.setAttribute("x2", "12.01");
|
|
540
|
+
l2.setAttribute("y2", "8");
|
|
541
|
+
svg.appendChild(c);
|
|
542
|
+
svg.appendChild(l1);
|
|
543
|
+
svg.appendChild(l2);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case "loading": {
|
|
547
|
+
svg.setAttribute("data-fluix-icon", "spin");
|
|
548
|
+
const lines = [
|
|
549
|
+
["12", "2", "12", "6"],
|
|
550
|
+
["12", "18", "12", "22"],
|
|
551
|
+
["4.93", "4.93", "7.76", "7.76"],
|
|
552
|
+
["16.24", "16.24", "19.07", "19.07"],
|
|
553
|
+
["2", "12", "6", "12"],
|
|
554
|
+
["18", "12", "22", "12"],
|
|
555
|
+
["4.93", "19.07", "7.76", "16.24"],
|
|
556
|
+
["16.24", "7.76", "19.07", "4.93"]
|
|
557
|
+
];
|
|
558
|
+
for (const [x1, y1, x2, y2] of lines) {
|
|
559
|
+
const l = document.createElementNS(SVG_NS, "line");
|
|
560
|
+
l.setAttribute("x1", x1);
|
|
561
|
+
l.setAttribute("y1", y1);
|
|
562
|
+
l.setAttribute("x2", x2);
|
|
563
|
+
l.setAttribute("y2", y2);
|
|
564
|
+
svg.appendChild(l);
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "action": {
|
|
569
|
+
const c = document.createElementNS(SVG_NS, "circle");
|
|
570
|
+
c.setAttribute("cx", "12");
|
|
571
|
+
c.setAttribute("cy", "12");
|
|
572
|
+
c.setAttribute("r", "10");
|
|
573
|
+
const poly = document.createElementNS(SVG_NS, "polygon");
|
|
574
|
+
poly.setAttribute("points", "10 8 16 12 10 16 10 8");
|
|
575
|
+
poly.setAttribute("fill", "currentColor");
|
|
576
|
+
poly.setAttribute("stroke", "none");
|
|
577
|
+
svg.appendChild(c);
|
|
578
|
+
svg.appendChild(poly);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
default:
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
return svg;
|
|
585
|
+
}
|
|
586
|
+
function renderIconInto(container, icon, state) {
|
|
587
|
+
container.textContent = "";
|
|
588
|
+
if (icon != null) {
|
|
589
|
+
if (icon instanceof HTMLElement || icon instanceof SVGElement) {
|
|
590
|
+
container.appendChild(icon);
|
|
591
|
+
} else {
|
|
592
|
+
const span = document.createElement("span");
|
|
593
|
+
span.setAttribute("aria-hidden", "true");
|
|
594
|
+
span.textContent = String(icon);
|
|
595
|
+
container.appendChild(span);
|
|
596
|
+
}
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const svgIcon = createSvgIcon(state);
|
|
600
|
+
if (svgIcon) container.appendChild(svgIcon);
|
|
601
|
+
}
|
|
602
|
+
function getPillAlign2(position) {
|
|
603
|
+
if (position.includes("right")) return "right";
|
|
604
|
+
if (position.includes("center")) return "center";
|
|
605
|
+
return "left";
|
|
606
|
+
}
|
|
607
|
+
function applyAttrs(el, attrs) {
|
|
608
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
609
|
+
el.setAttribute(key, value);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function resolveOffsetValue(v) {
|
|
613
|
+
return typeof v === "number" ? `${v}px` : v;
|
|
614
|
+
}
|
|
615
|
+
function applyViewportOffset(el, offset, position) {
|
|
616
|
+
el.style.top = "";
|
|
617
|
+
el.style.right = "";
|
|
618
|
+
el.style.bottom = "";
|
|
619
|
+
el.style.left = "";
|
|
620
|
+
el.style.paddingLeft = "";
|
|
621
|
+
el.style.paddingRight = "";
|
|
622
|
+
if (offset == null) return;
|
|
623
|
+
let top;
|
|
624
|
+
let right;
|
|
625
|
+
let bottom;
|
|
626
|
+
let left;
|
|
627
|
+
if (typeof offset === "number" || typeof offset === "string") {
|
|
628
|
+
const v = resolveOffsetValue(offset);
|
|
629
|
+
top = v;
|
|
630
|
+
right = v;
|
|
631
|
+
bottom = v;
|
|
632
|
+
left = v;
|
|
633
|
+
} else {
|
|
634
|
+
if (offset.top != null) top = resolveOffsetValue(offset.top);
|
|
635
|
+
if (offset.right != null) right = resolveOffsetValue(offset.right);
|
|
636
|
+
if (offset.bottom != null) bottom = resolveOffsetValue(offset.bottom);
|
|
637
|
+
if (offset.left != null) left = resolveOffsetValue(offset.left);
|
|
638
|
+
}
|
|
639
|
+
if (position.startsWith("top") && top) el.style.top = top;
|
|
640
|
+
if (position.startsWith("bottom") && bottom) el.style.bottom = bottom;
|
|
641
|
+
if (position.endsWith("right") && right) el.style.right = right;
|
|
642
|
+
if (position.endsWith("left") && left) el.style.left = left;
|
|
643
|
+
if (position.endsWith("center")) {
|
|
644
|
+
if (left) el.style.paddingLeft = left;
|
|
645
|
+
if (right) el.style.paddingRight = right;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function setTimer(inst, fn, ms) {
|
|
649
|
+
const id = setTimeout(() => {
|
|
650
|
+
inst.timers.delete(id);
|
|
651
|
+
fn();
|
|
652
|
+
}, ms);
|
|
653
|
+
inst.timers.add(id);
|
|
654
|
+
return id;
|
|
655
|
+
}
|
|
656
|
+
function computeLayout(item, inst) {
|
|
657
|
+
const { ready, expanded: isExpanded } = inst.localState;
|
|
658
|
+
const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
|
|
659
|
+
const blur = Math.min(10, Math.max(6, roundness * 0.45));
|
|
660
|
+
const hasDesc = Boolean(item.description) || Boolean(item.button);
|
|
661
|
+
const isLoading = item.state === "loading";
|
|
662
|
+
const open = hasDesc && isExpanded && !isLoading;
|
|
663
|
+
const position = getPillAlign2(item.position);
|
|
664
|
+
const edge = item.position.startsWith("top") ? "bottom" : "top";
|
|
665
|
+
const resolvedPillWidth = Math.max(inst.pillWidth || HEIGHT, HEIGHT);
|
|
666
|
+
const pillHeight = HEIGHT + blur * 3;
|
|
667
|
+
const pillX = position === "right" ? WIDTH - resolvedPillWidth : position === "center" ? (WIDTH - resolvedPillWidth) / 2 : 0;
|
|
668
|
+
const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
|
|
669
|
+
const rawExpanded = hasDesc ? Math.max(minExpanded, HEIGHT + inst.contentHeight) : minExpanded;
|
|
670
|
+
if (open) {
|
|
671
|
+
inst.frozenExpanded = rawExpanded;
|
|
672
|
+
}
|
|
673
|
+
const expanded = open ? rawExpanded : inst.frozenExpanded;
|
|
674
|
+
const expandedContent = Math.max(0, expanded - HEIGHT);
|
|
675
|
+
const svgHeight = hasDesc ? Math.max(expanded, minExpanded) : HEIGHT;
|
|
676
|
+
return {
|
|
677
|
+
ready,
|
|
678
|
+
isExpanded,
|
|
679
|
+
roundness,
|
|
680
|
+
blur,
|
|
681
|
+
hasDesc,
|
|
682
|
+
isLoading,
|
|
683
|
+
open,
|
|
684
|
+
position,
|
|
685
|
+
edge,
|
|
686
|
+
resolvedPillWidth,
|
|
687
|
+
pillHeight,
|
|
688
|
+
pillX,
|
|
689
|
+
minExpanded,
|
|
690
|
+
expanded,
|
|
691
|
+
expandedContent,
|
|
692
|
+
svgHeight
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function createInstance(item, machine2) {
|
|
696
|
+
const localState = { ready: false, expanded: false };
|
|
697
|
+
const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
|
|
698
|
+
const blur = Math.min(10, Math.max(6, roundness * 0.45));
|
|
699
|
+
const filterId = `fluix-gooey-${item.id.replace(/[^a-z0-9-]/gi, "-")}`;
|
|
700
|
+
const hasDesc = Boolean(item.description) || Boolean(item.button);
|
|
701
|
+
const el = document.createElement("button");
|
|
702
|
+
el.type = "button";
|
|
703
|
+
const canvasDiv = document.createElement("div");
|
|
704
|
+
const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
|
|
705
|
+
const initialSvgHeight = hasDesc ? minExpanded : HEIGHT;
|
|
706
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
707
|
+
svg.setAttribute("data-fluix-svg", "");
|
|
708
|
+
svg.setAttribute("width", String(WIDTH));
|
|
709
|
+
svg.setAttribute("height", String(initialSvgHeight));
|
|
710
|
+
svg.setAttribute("viewBox", `0 0 ${WIDTH} ${initialSvgHeight}`);
|
|
711
|
+
svg.setAttribute("aria-hidden", "true");
|
|
712
|
+
const defs = document.createElementNS(SVG_NS, "defs");
|
|
713
|
+
const filter = document.createElementNS(SVG_NS, "filter");
|
|
714
|
+
filter.setAttribute("id", filterId);
|
|
715
|
+
filter.setAttribute("x", "-20%");
|
|
716
|
+
filter.setAttribute("y", "-20%");
|
|
717
|
+
filter.setAttribute("width", "140%");
|
|
718
|
+
filter.setAttribute("height", "140%");
|
|
719
|
+
filter.setAttribute("color-interpolation-filters", "sRGB");
|
|
720
|
+
const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
|
|
721
|
+
feBlur.setAttribute("in", "SourceGraphic");
|
|
722
|
+
feBlur.setAttribute("stdDeviation", String(blur));
|
|
723
|
+
feBlur.setAttribute("result", "blur");
|
|
724
|
+
const feCM = document.createElementNS(SVG_NS, "feColorMatrix");
|
|
725
|
+
feCM.setAttribute("in", "blur");
|
|
726
|
+
feCM.setAttribute("type", "matrix");
|
|
727
|
+
feCM.setAttribute("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10");
|
|
728
|
+
feCM.setAttribute("result", "goo");
|
|
729
|
+
const feComp = document.createElementNS(SVG_NS, "feComposite");
|
|
730
|
+
feComp.setAttribute("in", "SourceGraphic");
|
|
731
|
+
feComp.setAttribute("in2", "goo");
|
|
732
|
+
feComp.setAttribute("operator", "atop");
|
|
733
|
+
filter.appendChild(feBlur);
|
|
734
|
+
filter.appendChild(feCM);
|
|
735
|
+
filter.appendChild(feComp);
|
|
736
|
+
defs.appendChild(filter);
|
|
737
|
+
svg.appendChild(defs);
|
|
738
|
+
const g = document.createElementNS(SVG_NS, "g");
|
|
739
|
+
g.setAttribute("filter", `url(#${filterId})`);
|
|
740
|
+
const initialPillX = getPillAlign2(item.position) === "right" ? WIDTH - HEIGHT : getPillAlign2(item.position) === "center" ? (WIDTH - HEIGHT) / 2 : 0;
|
|
741
|
+
const pillEl = document.createElementNS(SVG_NS, "rect");
|
|
742
|
+
pillEl.setAttribute("data-fluix-pill", "");
|
|
743
|
+
pillEl.setAttribute("x", String(initialPillX));
|
|
744
|
+
pillEl.setAttribute("y", "0");
|
|
745
|
+
pillEl.setAttribute("width", String(HEIGHT));
|
|
746
|
+
pillEl.setAttribute("height", String(HEIGHT));
|
|
747
|
+
pillEl.setAttribute("rx", String(roundness));
|
|
748
|
+
pillEl.setAttribute("ry", String(roundness));
|
|
749
|
+
pillEl.setAttribute("fill", item.fill ?? "#FFFFFF");
|
|
750
|
+
const bodyEl = document.createElementNS(SVG_NS, "rect");
|
|
751
|
+
bodyEl.setAttribute("data-fluix-body", "");
|
|
752
|
+
bodyEl.setAttribute("x", "0");
|
|
753
|
+
bodyEl.setAttribute("y", String(HEIGHT));
|
|
754
|
+
bodyEl.setAttribute("width", String(WIDTH));
|
|
755
|
+
bodyEl.setAttribute("height", "0");
|
|
756
|
+
bodyEl.setAttribute("rx", String(roundness));
|
|
757
|
+
bodyEl.setAttribute("ry", String(roundness));
|
|
758
|
+
bodyEl.setAttribute("fill", item.fill ?? "#FFFFFF");
|
|
759
|
+
bodyEl.setAttribute("opacity", "0");
|
|
760
|
+
g.appendChild(pillEl);
|
|
761
|
+
g.appendChild(bodyEl);
|
|
762
|
+
svg.appendChild(g);
|
|
763
|
+
canvasDiv.appendChild(svg);
|
|
764
|
+
el.appendChild(canvasDiv);
|
|
765
|
+
const headerEl = document.createElement("div");
|
|
766
|
+
const headerStackEl = document.createElement("div");
|
|
767
|
+
headerStackEl.setAttribute("data-fluix-header-stack", "");
|
|
768
|
+
const innerEl = document.createElement("div");
|
|
769
|
+
innerEl.setAttribute("data-fluix-header-inner", "");
|
|
770
|
+
innerEl.setAttribute("data-layer", "current");
|
|
771
|
+
const badgeEl = document.createElement("div");
|
|
772
|
+
renderIconInto(badgeEl, item.icon, item.state);
|
|
773
|
+
const titleEl = document.createElement("span");
|
|
774
|
+
titleEl.textContent = item.title ?? item.state;
|
|
775
|
+
innerEl.appendChild(badgeEl);
|
|
776
|
+
innerEl.appendChild(titleEl);
|
|
777
|
+
headerStackEl.appendChild(innerEl);
|
|
778
|
+
headerEl.appendChild(headerStackEl);
|
|
779
|
+
el.appendChild(headerEl);
|
|
780
|
+
let contentEl = null;
|
|
781
|
+
let descriptionEl = null;
|
|
782
|
+
if (hasDesc) {
|
|
783
|
+
contentEl = document.createElement("div");
|
|
784
|
+
descriptionEl = document.createElement("div");
|
|
785
|
+
if (item.styles?.description) {
|
|
786
|
+
descriptionEl.className = item.styles.description;
|
|
787
|
+
}
|
|
788
|
+
if (item.description != null) {
|
|
789
|
+
if (typeof item.description === "string") {
|
|
790
|
+
descriptionEl.textContent = item.description;
|
|
791
|
+
} else if (item.description instanceof HTMLElement) {
|
|
792
|
+
descriptionEl.appendChild(item.description);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (item.button) {
|
|
796
|
+
const btn = document.createElement("button");
|
|
797
|
+
btn.type = "button";
|
|
798
|
+
btn.textContent = item.button.title;
|
|
799
|
+
if (item.styles?.button) {
|
|
800
|
+
btn.className = item.styles.button;
|
|
801
|
+
}
|
|
802
|
+
btn.addEventListener("click", (e) => {
|
|
803
|
+
e.stopPropagation();
|
|
804
|
+
item.button?.onClick();
|
|
805
|
+
});
|
|
806
|
+
descriptionEl.appendChild(btn);
|
|
807
|
+
}
|
|
808
|
+
contentEl.appendChild(descriptionEl);
|
|
809
|
+
el.appendChild(contentEl);
|
|
810
|
+
}
|
|
811
|
+
const attrs = Toaster.getAttrs(item, localState);
|
|
812
|
+
applyAttrs(el, attrs.root);
|
|
813
|
+
applyAttrs(canvasDiv, attrs.canvas);
|
|
814
|
+
applyAttrs(headerEl, attrs.header);
|
|
815
|
+
applyAttrs(badgeEl, attrs.badge);
|
|
816
|
+
if (item.styles?.badge) badgeEl.className = item.styles.badge;
|
|
817
|
+
applyAttrs(titleEl, attrs.title);
|
|
818
|
+
if (item.styles?.title) titleEl.className = item.styles.title;
|
|
819
|
+
if (contentEl) applyAttrs(contentEl, attrs.content);
|
|
820
|
+
if (descriptionEl) applyAttrs(descriptionEl, attrs.description);
|
|
821
|
+
if (item.button && descriptionEl) {
|
|
822
|
+
const btnEl = descriptionEl.querySelector("button");
|
|
823
|
+
if (btnEl) applyAttrs(btnEl, attrs.button);
|
|
824
|
+
}
|
|
825
|
+
const headerKey = `${item.state}-${item.title ?? item.state}`;
|
|
826
|
+
const inst = {
|
|
827
|
+
el,
|
|
828
|
+
pillEl,
|
|
829
|
+
bodyEl,
|
|
830
|
+
svgEl: svg,
|
|
831
|
+
headerEl,
|
|
832
|
+
innerEl,
|
|
833
|
+
headerStackEl,
|
|
834
|
+
contentEl,
|
|
835
|
+
descriptionEl,
|
|
836
|
+
localState,
|
|
837
|
+
pillWidth: 0,
|
|
838
|
+
contentHeight: 0,
|
|
839
|
+
frozenExpanded: HEIGHT * MIN_EXPAND_RATIO,
|
|
840
|
+
hovering: false,
|
|
841
|
+
pendingDismiss: false,
|
|
842
|
+
dismissRequested: false,
|
|
843
|
+
pillRo: null,
|
|
844
|
+
contentRo: null,
|
|
845
|
+
pillAnim: null,
|
|
846
|
+
prevPill: { x: initialPillX, width: HEIGHT, height: HEIGHT },
|
|
847
|
+
pillFirst: true,
|
|
848
|
+
headerKey,
|
|
849
|
+
headerPad: null,
|
|
850
|
+
connectHandle: null,
|
|
851
|
+
timers: /* @__PURE__ */ new Set(),
|
|
852
|
+
item
|
|
853
|
+
};
|
|
854
|
+
inst.pillRo = new ResizeObserver(() => {
|
|
855
|
+
requestAnimationFrame(() => {
|
|
856
|
+
measurePillWidth(inst);
|
|
857
|
+
applyVars(inst, inst.item);
|
|
858
|
+
animatePill(inst, inst.item);
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
inst.pillRo.observe(innerEl);
|
|
862
|
+
if (descriptionEl) {
|
|
863
|
+
inst.contentRo = new ResizeObserver(() => {
|
|
864
|
+
requestAnimationFrame(() => {
|
|
865
|
+
const h = descriptionEl.scrollHeight;
|
|
866
|
+
if (h !== inst.contentHeight) {
|
|
867
|
+
inst.contentHeight = h;
|
|
868
|
+
applyVars(inst, inst.item);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
inst.contentRo.observe(descriptionEl);
|
|
873
|
+
}
|
|
874
|
+
inst.connectHandle = Toaster.connect(
|
|
875
|
+
el,
|
|
876
|
+
{
|
|
877
|
+
onExpand: () => {
|
|
878
|
+
if (inst.item.exiting || inst.dismissRequested) return;
|
|
879
|
+
inst.localState.expanded = true;
|
|
880
|
+
applyUpdate(inst, inst.item);
|
|
881
|
+
},
|
|
882
|
+
onCollapse: () => {
|
|
883
|
+
if (inst.item.exiting || inst.dismissRequested) return;
|
|
884
|
+
if (inst.item.autopilot !== false) return;
|
|
885
|
+
inst.localState.expanded = false;
|
|
886
|
+
applyUpdate(inst, inst.item);
|
|
887
|
+
},
|
|
888
|
+
onDismiss: () => {
|
|
889
|
+
if (inst.dismissRequested) return;
|
|
890
|
+
inst.dismissRequested = true;
|
|
891
|
+
machine2.dismiss(item.id);
|
|
892
|
+
},
|
|
893
|
+
onHoverStart: () => {
|
|
894
|
+
inst.hovering = true;
|
|
895
|
+
},
|
|
896
|
+
onHoverEnd: () => {
|
|
897
|
+
inst.hovering = false;
|
|
898
|
+
if (inst.pendingDismiss) {
|
|
899
|
+
inst.pendingDismiss = false;
|
|
900
|
+
if (inst.dismissRequested) return;
|
|
901
|
+
inst.dismissRequested = true;
|
|
902
|
+
machine2.dismiss(inst.item.id);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
item
|
|
907
|
+
);
|
|
908
|
+
applyVars(inst, item);
|
|
909
|
+
setTimer(
|
|
910
|
+
inst,
|
|
911
|
+
() => {
|
|
912
|
+
inst.localState.ready = true;
|
|
913
|
+
applyUpdate(inst, inst.item);
|
|
914
|
+
},
|
|
915
|
+
32
|
|
916
|
+
);
|
|
917
|
+
setupAutoDismiss(inst, item, machine2);
|
|
918
|
+
setupAutopilot(inst, item);
|
|
919
|
+
measurePillWidth(inst);
|
|
920
|
+
return inst;
|
|
921
|
+
}
|
|
922
|
+
function measurePillWidth(inst) {
|
|
923
|
+
if (!inst.el.isConnected) return;
|
|
924
|
+
if (inst.headerPad === null) {
|
|
925
|
+
const cs = getComputedStyle(inst.headerEl);
|
|
926
|
+
inst.headerPad = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
|
|
927
|
+
}
|
|
928
|
+
const intrinsicWidth = inst.innerEl.getBoundingClientRect().width;
|
|
929
|
+
const w = intrinsicWidth + inst.headerPad + PILL_PADDING;
|
|
930
|
+
if (w > PILL_PADDING && w !== inst.pillWidth) {
|
|
931
|
+
inst.pillWidth = w;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function applyVars(inst, item) {
|
|
935
|
+
const layout = computeLayout(item, inst);
|
|
936
|
+
const { open, expanded, resolvedPillWidth, pillX, edge, expandedContent, svgHeight } = layout;
|
|
937
|
+
const vars = {
|
|
938
|
+
"--_h": `${open ? expanded : HEIGHT}px`,
|
|
939
|
+
"--_pw": `${resolvedPillWidth}px`,
|
|
940
|
+
"--_px": `${pillX}px`,
|
|
941
|
+
"--_ht": `translateY(${open ? edge === "bottom" ? 3 : -3 : 0}px) scale(${open ? 0.9 : 1})`,
|
|
942
|
+
"--_co": `${open ? 1 : 0}`,
|
|
943
|
+
"--_cy": `${open ? 0 : -14}px`,
|
|
944
|
+
"--_cm": `${open ? expandedContent : 0}px`,
|
|
945
|
+
"--_by": `${open ? HEIGHT - BODY_MERGE_OVERLAP : HEIGHT}px`,
|
|
946
|
+
"--_bh": `${open ? expandedContent : 0}px`,
|
|
947
|
+
"--_bo": `${open ? 1 : 0}`
|
|
948
|
+
};
|
|
949
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
950
|
+
inst.el.style.setProperty(key, value);
|
|
951
|
+
}
|
|
952
|
+
inst.svgEl.setAttribute("height", String(svgHeight));
|
|
953
|
+
inst.svgEl.setAttribute("viewBox", `0 0 ${WIDTH} ${svgHeight}`);
|
|
954
|
+
}
|
|
955
|
+
function animatePill(inst, item) {
|
|
956
|
+
const layout = computeLayout(item, inst);
|
|
957
|
+
const { open, resolvedPillWidth, pillX, pillHeight } = layout;
|
|
958
|
+
const prev = inst.prevPill;
|
|
959
|
+
const next = { x: pillX, width: resolvedPillWidth, height: open ? pillHeight : HEIGHT };
|
|
960
|
+
if (prev.x === next.x && prev.width === next.width && prev.height === next.height) return;
|
|
961
|
+
inst.pillAnim?.cancel();
|
|
962
|
+
if (!inst.localState.ready || inst.pillFirst) {
|
|
963
|
+
inst.pillFirst = false;
|
|
964
|
+
inst.pillEl.setAttribute("x", String(next.x));
|
|
965
|
+
inst.pillEl.setAttribute("width", String(next.width));
|
|
966
|
+
inst.pillEl.setAttribute("height", String(next.height));
|
|
967
|
+
inst.prevPill = next;
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const anim = animateSpring(
|
|
971
|
+
inst.pillEl,
|
|
972
|
+
{
|
|
973
|
+
x: { from: prev.x, to: next.x, unit: "px" },
|
|
974
|
+
width: { from: prev.width, to: next.width, unit: "px" },
|
|
975
|
+
height: { from: prev.height, to: next.height, unit: "px" }
|
|
976
|
+
},
|
|
977
|
+
FLUIX_SPRING
|
|
978
|
+
);
|
|
979
|
+
inst.pillAnim = anim;
|
|
980
|
+
inst.prevPill = next;
|
|
981
|
+
}
|
|
982
|
+
function setupAutoDismiss(inst, item, machine2) {
|
|
983
|
+
const duration = item.duration;
|
|
984
|
+
if (duration == null || duration <= 0) return;
|
|
985
|
+
setTimer(
|
|
986
|
+
inst,
|
|
987
|
+
() => {
|
|
988
|
+
if (inst.hovering) {
|
|
989
|
+
inst.pendingDismiss = true;
|
|
990
|
+
setTimer(
|
|
991
|
+
inst,
|
|
992
|
+
() => {
|
|
993
|
+
if (inst.dismissRequested) return;
|
|
994
|
+
inst.dismissRequested = true;
|
|
995
|
+
inst.pendingDismiss = false;
|
|
996
|
+
machine2.dismiss(item.id);
|
|
997
|
+
},
|
|
998
|
+
1200
|
|
999
|
+
);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
inst.pendingDismiss = false;
|
|
1003
|
+
inst.dismissRequested = true;
|
|
1004
|
+
machine2.dismiss(item.id);
|
|
1005
|
+
},
|
|
1006
|
+
duration
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
function setupAutopilot(inst, item, machine2) {
|
|
1010
|
+
if (!inst.localState.ready) return;
|
|
1011
|
+
if (item.autoExpandDelayMs != null && item.autoExpandDelayMs > 0) {
|
|
1012
|
+
setTimer(
|
|
1013
|
+
inst,
|
|
1014
|
+
() => {
|
|
1015
|
+
if (!inst.hovering) {
|
|
1016
|
+
inst.localState.expanded = true;
|
|
1017
|
+
applyUpdate(inst, inst.item);
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
item.autoExpandDelayMs
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (item.autoCollapseDelayMs != null && item.autoCollapseDelayMs > 0) {
|
|
1024
|
+
setTimer(
|
|
1025
|
+
inst,
|
|
1026
|
+
() => {
|
|
1027
|
+
if (!inst.hovering) {
|
|
1028
|
+
inst.localState.expanded = false;
|
|
1029
|
+
applyUpdate(inst, inst.item);
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
item.autoCollapseDelayMs
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function applyUpdate(inst, item, _machine) {
|
|
1037
|
+
inst.item = item;
|
|
1038
|
+
const attrs = Toaster.getAttrs(item, inst.localState);
|
|
1039
|
+
applyAttrs(inst.el, attrs.root);
|
|
1040
|
+
const canvasEl = inst.el.querySelector("[data-fluix-canvas]");
|
|
1041
|
+
if (canvasEl) applyAttrs(canvasEl, attrs.canvas);
|
|
1042
|
+
applyAttrs(inst.headerEl, attrs.header);
|
|
1043
|
+
const badgeEl = inst.innerEl.querySelector("[data-fluix-badge]");
|
|
1044
|
+
if (badgeEl) {
|
|
1045
|
+
applyAttrs(badgeEl, attrs.badge);
|
|
1046
|
+
if (item.styles?.badge) badgeEl.className = item.styles.badge;
|
|
1047
|
+
}
|
|
1048
|
+
const titleEl = inst.innerEl.querySelector("[data-fluix-title]");
|
|
1049
|
+
if (titleEl) {
|
|
1050
|
+
applyAttrs(titleEl, attrs.title);
|
|
1051
|
+
if (item.styles?.title) titleEl.className = item.styles.title;
|
|
1052
|
+
}
|
|
1053
|
+
if (inst.contentEl) applyAttrs(inst.contentEl, attrs.content);
|
|
1054
|
+
if (inst.descriptionEl) applyAttrs(inst.descriptionEl, attrs.description);
|
|
1055
|
+
if (item.button && inst.descriptionEl) {
|
|
1056
|
+
const btnEl = inst.descriptionEl.querySelector("button");
|
|
1057
|
+
if (btnEl) applyAttrs(btnEl, attrs.button);
|
|
1058
|
+
}
|
|
1059
|
+
inst.pillEl.setAttribute("fill", item.fill ?? "#FFFFFF");
|
|
1060
|
+
inst.bodyEl.setAttribute("fill", item.fill ?? "#FFFFFF");
|
|
1061
|
+
const newHeaderKey = `${item.state}-${item.title ?? item.state}`;
|
|
1062
|
+
if (newHeaderKey !== inst.headerKey) {
|
|
1063
|
+
crossfadeHeader(inst, item, attrs);
|
|
1064
|
+
inst.headerKey = newHeaderKey;
|
|
1065
|
+
}
|
|
1066
|
+
applyVars(inst, item);
|
|
1067
|
+
animatePill(inst, item);
|
|
1068
|
+
}
|
|
1069
|
+
function crossfadeHeader(inst, item, attrs) {
|
|
1070
|
+
const oldInner = inst.innerEl;
|
|
1071
|
+
oldInner.setAttribute("data-layer", "prev");
|
|
1072
|
+
oldInner.setAttribute("data-exiting", "true");
|
|
1073
|
+
const newInner = document.createElement("div");
|
|
1074
|
+
newInner.setAttribute("data-fluix-header-inner", "");
|
|
1075
|
+
newInner.setAttribute("data-layer", "current");
|
|
1076
|
+
const badgeEl = document.createElement("div");
|
|
1077
|
+
applyAttrs(badgeEl, attrs.badge);
|
|
1078
|
+
if (item.styles?.badge) badgeEl.className = item.styles.badge;
|
|
1079
|
+
renderIconInto(badgeEl, item.icon, item.state);
|
|
1080
|
+
const titleEl = document.createElement("span");
|
|
1081
|
+
applyAttrs(titleEl, attrs.title);
|
|
1082
|
+
if (item.styles?.title) titleEl.className = item.styles.title;
|
|
1083
|
+
titleEl.textContent = item.title ?? item.state;
|
|
1084
|
+
newInner.appendChild(badgeEl);
|
|
1085
|
+
newInner.appendChild(titleEl);
|
|
1086
|
+
inst.headerStackEl.insertBefore(newInner, oldInner);
|
|
1087
|
+
inst.innerEl = newInner;
|
|
1088
|
+
inst.pillRo.unobserve(oldInner);
|
|
1089
|
+
inst.pillRo.observe(newInner);
|
|
1090
|
+
setTimer(
|
|
1091
|
+
inst,
|
|
1092
|
+
() => {
|
|
1093
|
+
oldInner.remove();
|
|
1094
|
+
},
|
|
1095
|
+
HEADER_EXIT_MS
|
|
1096
|
+
);
|
|
1097
|
+
requestAnimationFrame(() => {
|
|
1098
|
+
measurePillWidth(inst);
|
|
1099
|
+
applyVars(inst, inst.item);
|
|
1100
|
+
animatePill(inst, inst.item);
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function destroyInstance(inst) {
|
|
1104
|
+
for (const t of inst.timers) clearTimeout(t);
|
|
1105
|
+
inst.timers.clear();
|
|
1106
|
+
inst.pillAnim?.cancel();
|
|
1107
|
+
inst.pillRo.disconnect();
|
|
1108
|
+
inst.contentRo?.disconnect();
|
|
1109
|
+
inst.connectHandle.destroy();
|
|
1110
|
+
inst.el.remove();
|
|
1111
|
+
}
|
|
1112
|
+
function createToaster(config) {
|
|
1113
|
+
const machine2 = Toaster.getMachine();
|
|
1114
|
+
let currentConfig = config;
|
|
1115
|
+
if (currentConfig) machine2.configure(currentConfig);
|
|
1116
|
+
const instances = /* @__PURE__ */ new Map();
|
|
1117
|
+
const viewports = /* @__PURE__ */ new Map();
|
|
1118
|
+
function ensureViewport(position, layout, offset) {
|
|
1119
|
+
let vp = viewports.get(position);
|
|
1120
|
+
if (!vp) {
|
|
1121
|
+
vp = document.createElement("section");
|
|
1122
|
+
const vpAttrs = Toaster.getViewportAttrs(position, layout);
|
|
1123
|
+
applyAttrs(vp, vpAttrs);
|
|
1124
|
+
applyViewportOffset(vp, offset, position);
|
|
1125
|
+
document.body.appendChild(vp);
|
|
1126
|
+
viewports.set(position, vp);
|
|
1127
|
+
}
|
|
1128
|
+
return vp;
|
|
1129
|
+
}
|
|
1130
|
+
function removeViewport(position) {
|
|
1131
|
+
const vp = viewports.get(position);
|
|
1132
|
+
if (vp) {
|
|
1133
|
+
vp.remove();
|
|
1134
|
+
viewports.delete(position);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
function sync() {
|
|
1138
|
+
const next = machine2.store.getSnapshot();
|
|
1139
|
+
const resolvedLayout = next.config?.layout ?? currentConfig?.layout ?? "stack";
|
|
1140
|
+
const resolvedOffset = next.config?.offset ?? currentConfig?.offset;
|
|
1141
|
+
const activePositions = /* @__PURE__ */ new Set();
|
|
1142
|
+
const nextIds = new Set(next.toasts.map((t) => t.id));
|
|
1143
|
+
for (const [id, inst] of instances) {
|
|
1144
|
+
if (!nextIds.has(id)) {
|
|
1145
|
+
destroyInstance(inst);
|
|
1146
|
+
instances.delete(id);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
for (const item of next.toasts) {
|
|
1150
|
+
activePositions.add(item.position);
|
|
1151
|
+
const vp = ensureViewport(item.position, resolvedLayout, resolvedOffset);
|
|
1152
|
+
const existing = instances.get(item.id);
|
|
1153
|
+
if (!existing) {
|
|
1154
|
+
const inst = createInstance(item, machine2);
|
|
1155
|
+
instances.set(item.id, inst);
|
|
1156
|
+
vp.appendChild(inst.el);
|
|
1157
|
+
} else if (existing.item.instanceId !== item.instanceId) {
|
|
1158
|
+
destroyInstance(existing);
|
|
1159
|
+
const inst = createInstance(item, machine2);
|
|
1160
|
+
instances.set(item.id, inst);
|
|
1161
|
+
vp.appendChild(inst.el);
|
|
1162
|
+
} else {
|
|
1163
|
+
applyUpdate(existing, item);
|
|
1164
|
+
if (existing.el.parentElement !== vp) {
|
|
1165
|
+
vp.appendChild(existing.el);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
for (const [position, vp] of viewports) {
|
|
1170
|
+
const vpAttrs = Toaster.getViewportAttrs(position, resolvedLayout);
|
|
1171
|
+
applyAttrs(vp, vpAttrs);
|
|
1172
|
+
applyViewportOffset(vp, resolvedOffset, position);
|
|
1173
|
+
}
|
|
1174
|
+
for (const [position] of viewports) {
|
|
1175
|
+
if (!activePositions.has(position)) {
|
|
1176
|
+
removeViewport(position);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
sync();
|
|
1181
|
+
const unsubscribe = machine2.store.subscribe(sync);
|
|
1182
|
+
return {
|
|
1183
|
+
destroy() {
|
|
1184
|
+
unsubscribe();
|
|
1185
|
+
for (const inst of instances.values()) {
|
|
1186
|
+
destroyInstance(inst);
|
|
1187
|
+
}
|
|
1188
|
+
instances.clear();
|
|
1189
|
+
for (const vp of viewports.values()) {
|
|
1190
|
+
vp.remove();
|
|
1191
|
+
}
|
|
1192
|
+
viewports.clear();
|
|
1193
|
+
},
|
|
1194
|
+
update(newConfig) {
|
|
1195
|
+
currentConfig = newConfig;
|
|
1196
|
+
machine2.configure(newConfig);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
exports.createToaster = createToaster;
|
|
1202
|
+
exports.fluix = fluix;
|
|
1203
|
+
|
|
1204
|
+
return exports;
|
|
1205
|
+
|
|
1206
|
+
})({});
|
|
1207
|
+
//# sourceMappingURL=index.global.js.map
|
|
1208
|
+
//# sourceMappingURL=index.global.js.map
|