@fluix-ui/vanilla 0.0.7 → 0.0.9

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/dist/index.js CHANGED
@@ -1,125 +1,101 @@
1
- import { Toaster, NOTCH_DEFAULTS, createNotchMachine, getNotchAttrs, MENU_DEFAULTS, createMenuMachine, getMenuAttrs, connectMenu, FLUIX_SPRING, animateSpring, TOAST_DEFAULTS } from '@fluix-ui/core';
1
+ import { Toaster, createNotchMachine, NOTCH_DEFAULTS, MENU_DEFAULTS, createMenuMachine, getMenuAttrs, getNotchAttrs, connectMenu, FLUIX_SPRING, TOAST_DEFAULTS, animateSpring } from '@fluix-ui/core';
2
2
  export { fluix } from '@fluix-ui/core';
3
3
 
4
4
  // src/index.ts
5
- var WIDTH = 350;
6
- var HEIGHT = 40;
7
- var PILL_PADDING = 10;
8
- var MIN_EXPAND_RATIO = 2.25;
9
- var HEADER_EXIT_MS = 600 * 0.7;
10
- var BODY_MERGE_OVERLAP = 6;
5
+
6
+ // src/shared.ts
11
7
  var SVG_NS = "http://www.w3.org/2000/svg";
8
+ var GOO_MATRIX = "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10";
9
+ function applyAttrs(el2, attrs) {
10
+ for (const [key, value] of Object.entries(attrs)) {
11
+ el2.setAttribute(key, value);
12
+ }
13
+ }
14
+ function clearChildren(el2) {
15
+ while (el2.firstChild) el2.removeChild(el2.firstChild);
16
+ }
17
+ function createGooeyFilter(filterId, blur2) {
18
+ const defs = document.createElementNS(SVG_NS, "defs");
19
+ const filter = document.createElementNS(SVG_NS, "filter");
20
+ filter.setAttribute("id", filterId);
21
+ for (const [k, v] of Object.entries({ x: "-50%", y: "-50%", width: "200%", height: "200%" })) {
22
+ filter.setAttribute(k, v);
23
+ }
24
+ filter.setAttribute("color-interpolation-filters", "sRGB");
25
+ const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
26
+ feBlur.setAttribute("in", "SourceGraphic");
27
+ feBlur.setAttribute("stdDeviation", String(blur2));
28
+ feBlur.setAttribute("result", "blur");
29
+ const feCM = document.createElementNS(SVG_NS, "feColorMatrix");
30
+ feCM.setAttribute("in", "blur");
31
+ feCM.setAttribute("type", "matrix");
32
+ feCM.setAttribute("values", GOO_MATRIX);
33
+ feCM.setAttribute("result", "goo");
34
+ const feComp = document.createElementNS(SVG_NS, "feComposite");
35
+ feComp.setAttribute("in", "SourceGraphic");
36
+ feComp.setAttribute("in2", "goo");
37
+ feComp.setAttribute("operator", "atop");
38
+ filter.appendChild(feBlur);
39
+ filter.appendChild(feCM);
40
+ filter.appendChild(feComp);
41
+ defs.appendChild(filter);
42
+ const g = document.createElementNS(SVG_NS, "g");
43
+ g.setAttribute("filter", `url(#${filterId})`);
44
+ return { g, defs, feBlur };
45
+ }
46
+ function zeroRect(el2) {
47
+ for (const attr of ["x", "y", "width", "height", "rx", "ry"]) {
48
+ el2.setAttribute(attr, "0");
49
+ }
50
+ }
51
+
52
+ // src/toast/icons.ts
53
+ function el(tag, attrs) {
54
+ return { tag, attrs };
55
+ }
56
+ function line(x1, y1, x2, y2) {
57
+ return el("line", { x1, y1, x2, y2 });
58
+ }
59
+ var ICON_DEFS = {
60
+ success: { children: [el("polyline", { points: "20 6 9 17 4 12" })] },
61
+ error: { children: [line("18", "6", "6", "18"), line("6", "6", "18", "18")] },
62
+ warning: { children: [
63
+ el("path", { d: "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" }),
64
+ line("12", "9", "12", "13"),
65
+ line("12", "17", "12.01", "17")
66
+ ] },
67
+ info: { children: [
68
+ el("circle", { cx: "12", cy: "12", r: "10" }),
69
+ line("12", "16", "12", "12"),
70
+ line("12", "8", "12.01", "8")
71
+ ] },
72
+ loading: { spin: true, children: [
73
+ line("12", "2", "12", "6"),
74
+ line("12", "18", "12", "22"),
75
+ line("4.93", "4.93", "7.76", "7.76"),
76
+ line("16.24", "16.24", "19.07", "19.07"),
77
+ line("2", "12", "6", "12"),
78
+ line("18", "12", "22", "12"),
79
+ line("4.93", "19.07", "7.76", "16.24"),
80
+ line("16.24", "7.76", "19.07", "4.93")
81
+ ] },
82
+ action: { children: [
83
+ el("circle", { cx: "12", cy: "12", r: "10" }),
84
+ el("polygon", { points: "10 8 16 12 10 16 10 8", fill: "currentColor", stroke: "none" })
85
+ ] }
86
+ };
12
87
  function createSvgIcon(state) {
88
+ const def = ICON_DEFS[state];
89
+ if (!def) return null;
13
90
  const svg = document.createElementNS(SVG_NS, "svg");
14
- svg.setAttribute("width", "14");
15
- svg.setAttribute("height", "14");
16
- svg.setAttribute("viewBox", "0 0 24 24");
17
- svg.setAttribute("fill", "none");
18
- svg.setAttribute("stroke", "currentColor");
19
- svg.setAttribute("stroke-width", "2.5");
20
- svg.setAttribute("stroke-linecap", "round");
21
- svg.setAttribute("stroke-linejoin", "round");
22
- svg.setAttribute("aria-hidden", "true");
23
- switch (state) {
24
- case "success": {
25
- const p = document.createElementNS(SVG_NS, "polyline");
26
- p.setAttribute("points", "20 6 9 17 4 12");
27
- svg.appendChild(p);
28
- break;
29
- }
30
- case "error": {
31
- const l1 = document.createElementNS(SVG_NS, "line");
32
- l1.setAttribute("x1", "18");
33
- l1.setAttribute("y1", "6");
34
- l1.setAttribute("x2", "6");
35
- l1.setAttribute("y2", "18");
36
- const l2 = document.createElementNS(SVG_NS, "line");
37
- l2.setAttribute("x1", "6");
38
- l2.setAttribute("y1", "6");
39
- l2.setAttribute("x2", "18");
40
- l2.setAttribute("y2", "18");
41
- svg.appendChild(l1);
42
- svg.appendChild(l2);
43
- break;
44
- }
45
- case "warning": {
46
- const path = document.createElementNS(SVG_NS, "path");
47
- path.setAttribute(
48
- "d",
49
- "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"
50
- );
51
- const l1 = document.createElementNS(SVG_NS, "line");
52
- l1.setAttribute("x1", "12");
53
- l1.setAttribute("y1", "9");
54
- l1.setAttribute("x2", "12");
55
- l1.setAttribute("y2", "13");
56
- const l2 = document.createElementNS(SVG_NS, "line");
57
- l2.setAttribute("x1", "12");
58
- l2.setAttribute("y1", "17");
59
- l2.setAttribute("x2", "12.01");
60
- l2.setAttribute("y2", "17");
61
- svg.appendChild(path);
62
- svg.appendChild(l1);
63
- svg.appendChild(l2);
64
- break;
65
- }
66
- case "info": {
67
- const c = document.createElementNS(SVG_NS, "circle");
68
- c.setAttribute("cx", "12");
69
- c.setAttribute("cy", "12");
70
- c.setAttribute("r", "10");
71
- const l1 = document.createElementNS(SVG_NS, "line");
72
- l1.setAttribute("x1", "12");
73
- l1.setAttribute("y1", "16");
74
- l1.setAttribute("x2", "12");
75
- l1.setAttribute("y2", "12");
76
- const l2 = document.createElementNS(SVG_NS, "line");
77
- l2.setAttribute("x1", "12");
78
- l2.setAttribute("y1", "8");
79
- l2.setAttribute("x2", "12.01");
80
- l2.setAttribute("y2", "8");
81
- svg.appendChild(c);
82
- svg.appendChild(l1);
83
- svg.appendChild(l2);
84
- break;
85
- }
86
- case "loading": {
87
- svg.setAttribute("data-fluix-icon", "spin");
88
- const lines = [
89
- ["12", "2", "12", "6"],
90
- ["12", "18", "12", "22"],
91
- ["4.93", "4.93", "7.76", "7.76"],
92
- ["16.24", "16.24", "19.07", "19.07"],
93
- ["2", "12", "6", "12"],
94
- ["18", "12", "22", "12"],
95
- ["4.93", "19.07", "7.76", "16.24"],
96
- ["16.24", "7.76", "19.07", "4.93"]
97
- ];
98
- for (const [x1, y1, x2, y2] of lines) {
99
- const l = document.createElementNS(SVG_NS, "line");
100
- l.setAttribute("x1", x1);
101
- l.setAttribute("y1", y1);
102
- l.setAttribute("x2", x2);
103
- l.setAttribute("y2", y2);
104
- svg.appendChild(l);
105
- }
106
- break;
107
- }
108
- case "action": {
109
- const c = document.createElementNS(SVG_NS, "circle");
110
- c.setAttribute("cx", "12");
111
- c.setAttribute("cy", "12");
112
- c.setAttribute("r", "10");
113
- const poly = document.createElementNS(SVG_NS, "polygon");
114
- poly.setAttribute("points", "10 8 16 12 10 16 10 8");
115
- poly.setAttribute("fill", "currentColor");
116
- poly.setAttribute("stroke", "none");
117
- svg.appendChild(c);
118
- svg.appendChild(poly);
119
- break;
120
- }
121
- default:
122
- return null;
91
+ for (const [k, v] of Object.entries({ width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" })) {
92
+ svg.setAttribute(k, v);
93
+ }
94
+ if (def.spin) svg.setAttribute("data-fluix-icon", "spin");
95
+ for (const child of def.children) {
96
+ const node = document.createElementNS(SVG_NS, child.tag);
97
+ for (const [k, v] of Object.entries(child.attrs)) node.setAttribute(k, v);
98
+ svg.appendChild(node);
123
99
  }
124
100
  return svg;
125
101
  }
@@ -139,77 +115,25 @@ function renderIconInto(container, icon, state) {
139
115
  const svgIcon = createSvgIcon(state);
140
116
  if (svgIcon) container.appendChild(svgIcon);
141
117
  }
142
- function getPillAlign(position) {
143
- if (position.includes("right")) return "right";
144
- if (position.includes("center")) return "center";
145
- return "left";
146
- }
147
- function applyAttrs(el, attrs) {
148
- for (const [key, value] of Object.entries(attrs)) {
149
- el.setAttribute(key, value);
150
- }
151
- }
152
- function resolveOffsetValue(v) {
153
- return typeof v === "number" ? `${v}px` : v;
154
- }
155
- function applyViewportOffset(el, offset, position) {
156
- el.style.top = "";
157
- el.style.right = "";
158
- el.style.bottom = "";
159
- el.style.left = "";
160
- el.style.paddingLeft = "";
161
- el.style.paddingRight = "";
162
- if (offset == null) return;
163
- let top;
164
- let right;
165
- let bottom;
166
- let left;
167
- if (typeof offset === "number" || typeof offset === "string") {
168
- const v = resolveOffsetValue(offset);
169
- top = v;
170
- right = v;
171
- bottom = v;
172
- left = v;
173
- } else {
174
- if (offset.top != null) top = resolveOffsetValue(offset.top);
175
- if (offset.right != null) right = resolveOffsetValue(offset.right);
176
- if (offset.bottom != null) bottom = resolveOffsetValue(offset.bottom);
177
- if (offset.left != null) left = resolveOffsetValue(offset.left);
178
- }
179
- if (position.startsWith("top") && top) el.style.top = top;
180
- if (position.startsWith("bottom") && bottom) el.style.bottom = bottom;
181
- if (position.endsWith("right") && right) el.style.right = right;
182
- if (position.endsWith("left") && left) el.style.left = left;
183
- if (position.endsWith("center")) {
184
- if (left) el.style.paddingLeft = left;
185
- if (right) el.style.paddingRight = right;
186
- }
187
- }
188
- function setTimer(inst, fn, ms) {
189
- const id = setTimeout(() => {
190
- inst.timers.delete(id);
191
- fn();
192
- }, ms);
193
- inst.timers.add(id);
194
- return id;
195
- }
118
+ var PILL_PADDING = 10;
119
+ var MIN_EXPAND_RATIO = 2.25;
120
+ var HEADER_EXIT_MS = 600 * 0.7;
121
+ var BODY_MERGE_OVERLAP = 6;
196
122
  function computeLayout(item, inst) {
197
123
  const { ready, expanded: isExpanded } = inst.localState;
198
124
  const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
199
- const blur = Math.min(10, Math.max(6, roundness * 0.45));
125
+ const blur2 = Math.min(10, Math.max(6, roundness * 0.45));
200
126
  const hasDesc = Boolean(item.description) || Boolean(item.button);
201
127
  const isLoading = item.state === "loading";
202
128
  const open = hasDesc && isExpanded && !isLoading;
203
- const position = getPillAlign(item.position);
129
+ const position = getPillAlignLocal(item.position);
204
130
  const edge = item.position.startsWith("top") ? "bottom" : "top";
205
131
  const resolvedPillWidth = Math.max(inst.pillWidth || HEIGHT, HEIGHT);
206
- const pillHeight = HEIGHT + blur * 3;
132
+ const pillHeight = HEIGHT + blur2 * 3;
207
133
  const pillX = position === "right" ? WIDTH - resolvedPillWidth : position === "center" ? (WIDTH - resolvedPillWidth) / 2 : 0;
208
134
  const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
209
135
  const rawExpanded = hasDesc ? Math.max(minExpanded, HEIGHT + inst.contentHeight) : minExpanded;
210
- if (open) {
211
- inst.frozenExpanded = rawExpanded;
212
- }
136
+ if (open) inst.frozenExpanded = rawExpanded;
213
137
  const expanded = open ? rawExpanded : inst.frozenExpanded;
214
138
  const expandedContent = Math.max(0, expanded - HEIGHT);
215
139
  const svgHeight = hasDesc ? Math.max(expanded, minExpanded) : HEIGHT;
@@ -217,7 +141,7 @@ function computeLayout(item, inst) {
217
141
  ready,
218
142
  isExpanded,
219
143
  roundness,
220
- blur,
144
+ blur: blur2,
221
145
  hasDesc,
222
146
  isLoading,
223
147
  open,
@@ -232,76 +156,288 @@ function computeLayout(item, inst) {
232
156
  svgHeight
233
157
  };
234
158
  }
235
- function createInstance(item, machine) {
236
- const localState = { ready: false, expanded: false };
237
- const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
238
- const blur = Math.min(10, Math.max(6, roundness * 0.45));
159
+ function getPillAlignLocal(position) {
160
+ if (position.includes("right")) return "right";
161
+ if (position.includes("center")) return "center";
162
+ return "left";
163
+ }
164
+ function measurePillWidth(inst) {
165
+ if (!inst.el.isConnected) return;
166
+ if (inst.headerPad === null) {
167
+ const cs = getComputedStyle(inst.headerEl);
168
+ inst.headerPad = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
169
+ }
170
+ const w = inst.innerEl.getBoundingClientRect().width + inst.headerPad + PILL_PADDING;
171
+ if (w > PILL_PADDING && w !== inst.pillWidth) inst.pillWidth = w;
172
+ }
173
+ function applyVars(inst, item) {
174
+ const layout = computeLayout(item, inst);
175
+ const { open, expanded, resolvedPillWidth, pillX, edge, expandedContent, svgHeight } = layout;
176
+ const vars = {
177
+ "--_h": `${open ? expanded : HEIGHT}px`,
178
+ "--_pw": `${resolvedPillWidth}px`,
179
+ "--_px": `${pillX}px`,
180
+ "--_ht": `translateY(${open ? edge === "bottom" ? 3 : -3 : 0}px) scale(${open ? 0.9 : 1})`,
181
+ "--_co": `${open ? 1 : 0}`,
182
+ "--_cy": `${open ? 0 : -14}px`,
183
+ "--_cm": `${open ? expandedContent : 0}px`,
184
+ "--_by": `${open ? HEIGHT - BODY_MERGE_OVERLAP : HEIGHT}px`,
185
+ "--_bh": `${open ? expandedContent : 0}px`,
186
+ "--_bo": `${open ? 1 : 0}`
187
+ };
188
+ for (const [key, value] of Object.entries(vars)) inst.el.style.setProperty(key, value);
189
+ inst.svgEl.setAttribute("height", String(svgHeight));
190
+ inst.svgEl.setAttribute("viewBox", `0 0 ${WIDTH} ${svgHeight}`);
191
+ }
192
+ function animatePill(inst, item) {
193
+ const { open, resolvedPillWidth, pillX, pillHeight } = computeLayout(item, inst);
194
+ const prev = inst.prevPill;
195
+ const next = { x: pillX, width: resolvedPillWidth, height: open ? pillHeight : HEIGHT };
196
+ if (prev.x === next.x && prev.width === next.width && prev.height === next.height) return;
197
+ inst.pillAnim?.cancel();
198
+ if (!inst.localState.ready || inst.pillFirst) {
199
+ inst.pillFirst = false;
200
+ inst.pillEl.setAttribute("x", String(next.x));
201
+ inst.pillEl.setAttribute("width", String(next.width));
202
+ inst.pillEl.setAttribute("height", String(next.height));
203
+ inst.prevPill = next;
204
+ return;
205
+ }
206
+ inst.pillAnim = animateSpring(inst.pillEl, {
207
+ x: { from: prev.x, to: next.x, unit: "px" },
208
+ width: { from: prev.width, to: next.width, unit: "px" },
209
+ height: { from: prev.height, to: next.height, unit: "px" }
210
+ }, FLUIX_SPRING);
211
+ inst.prevPill = next;
212
+ }
213
+ function setTimer(inst, fn, ms) {
214
+ const id = setTimeout(() => {
215
+ inst.timers.delete(id);
216
+ fn();
217
+ }, ms);
218
+ inst.timers.add(id);
219
+ return id;
220
+ }
221
+ function setupAutoDismiss(inst, item, machine) {
222
+ const duration = item.duration;
223
+ if (duration == null || duration <= 0) return;
224
+ setTimer(inst, () => {
225
+ if (inst.hovering) {
226
+ inst.pendingDismiss = true;
227
+ setTimer(inst, () => {
228
+ if (inst.dismissRequested) return;
229
+ inst.dismissRequested = true;
230
+ inst.pendingDismiss = false;
231
+ machine.dismiss(item.id);
232
+ }, 1200);
233
+ return;
234
+ }
235
+ inst.pendingDismiss = false;
236
+ inst.dismissRequested = true;
237
+ machine.dismiss(item.id);
238
+ }, duration);
239
+ }
240
+ function setupAutopilot(inst, item, machine) {
241
+ if (item.autoExpandDelayMs != null && item.autoExpandDelayMs > 0) {
242
+ setTimer(inst, () => {
243
+ if (!inst.hovering) {
244
+ inst.localState.expanded = true;
245
+ applyUpdate(inst, inst.item);
246
+ }
247
+ }, item.autoExpandDelayMs);
248
+ }
249
+ if (item.autoCollapseDelayMs != null && item.autoCollapseDelayMs > 0) {
250
+ setTimer(inst, () => {
251
+ if (!inst.hovering) {
252
+ inst.localState.expanded = false;
253
+ applyUpdate(inst, inst.item);
254
+ }
255
+ }, item.autoCollapseDelayMs);
256
+ }
257
+ }
258
+ function updateDescription(inst, item, attrs) {
259
+ const hasDesc = Boolean(item.description) || Boolean(item.button);
260
+ if (hasDesc && !inst.contentEl) {
261
+ const contentEl = document.createElement("div");
262
+ const descriptionEl = document.createElement("div");
263
+ contentEl.appendChild(descriptionEl);
264
+ inst.el.appendChild(contentEl);
265
+ inst.contentEl = contentEl;
266
+ inst.descriptionEl = descriptionEl;
267
+ inst.contentRo = new ResizeObserver(() => {
268
+ requestAnimationFrame(() => {
269
+ const h = descriptionEl.scrollHeight;
270
+ if (h !== inst.contentHeight) {
271
+ inst.contentHeight = h;
272
+ applyVars(inst, inst.item);
273
+ }
274
+ });
275
+ });
276
+ inst.contentRo.observe(descriptionEl);
277
+ }
278
+ if (inst.contentEl) applyAttrs(inst.contentEl, attrs.content);
279
+ if (!inst.descriptionEl) return;
280
+ applyAttrs(inst.descriptionEl, attrs.description);
281
+ if (item.styles?.description) inst.descriptionEl.className = item.styles.description;
282
+ const existingBtn = inst.descriptionEl.querySelector("[data-fluix-button]");
283
+ inst.descriptionEl.textContent = "";
284
+ if (item.description != null) {
285
+ if (typeof item.description === "string") inst.descriptionEl.textContent = item.description;
286
+ else if (item.description instanceof HTMLElement) inst.descriptionEl.appendChild(item.description);
287
+ }
288
+ if (item.button) {
289
+ let btnEl = existingBtn;
290
+ if (!btnEl) {
291
+ btnEl = document.createElement("button");
292
+ btnEl.type = "button";
293
+ }
294
+ btnEl.textContent = item.button.title;
295
+ if (item.styles?.button) btnEl.className = item.styles.button;
296
+ applyAttrs(btnEl, attrs.button);
297
+ const newBtn = btnEl.cloneNode(true);
298
+ newBtn.addEventListener("click", (e) => {
299
+ e.stopPropagation();
300
+ item.button?.onClick();
301
+ });
302
+ inst.descriptionEl.appendChild(newBtn);
303
+ }
304
+ }
305
+ function applyUpdate(inst, item, _machine) {
306
+ inst.item = item;
307
+ const attrs = Toaster.getAttrs(item, inst.localState);
308
+ applyAttrs(inst.el, attrs.root);
309
+ const canvasEl = inst.el.querySelector("[data-fluix-canvas]");
310
+ if (canvasEl) applyAttrs(canvasEl, attrs.canvas);
311
+ applyAttrs(inst.headerEl, attrs.header);
312
+ const badgeEl = inst.innerEl.querySelector("[data-fluix-badge]");
313
+ if (badgeEl) {
314
+ applyAttrs(badgeEl, attrs.badge);
315
+ if (item.styles?.badge) badgeEl.className = item.styles.badge;
316
+ }
317
+ const titleEl = inst.innerEl.querySelector("[data-fluix-title]");
318
+ if (titleEl) {
319
+ applyAttrs(titleEl, attrs.title);
320
+ if (item.styles?.title) titleEl.className = item.styles.title;
321
+ }
322
+ updateDescription(inst, item, attrs);
323
+ inst.pillEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
324
+ inst.bodyEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
325
+ const newHeaderKey = `${item.state}-${item.title ?? item.state}`;
326
+ if (newHeaderKey !== inst.headerKey) {
327
+ crossfadeHeader(inst, item, attrs);
328
+ inst.headerKey = newHeaderKey;
329
+ }
330
+ applyVars(inst, item);
331
+ animatePill(inst, item);
332
+ }
333
+ function crossfadeHeader(inst, item, attrs) {
334
+ const oldInner = inst.innerEl;
335
+ oldInner.setAttribute("data-layer", "prev");
336
+ oldInner.setAttribute("data-exiting", "true");
337
+ const newInner = document.createElement("div");
338
+ newInner.setAttribute("data-fluix-header-inner", "");
339
+ newInner.setAttribute("data-layer", "current");
340
+ const badgeEl = document.createElement("div");
341
+ applyAttrs(badgeEl, attrs.badge);
342
+ if (item.styles?.badge) badgeEl.className = item.styles.badge;
343
+ renderIconInto(badgeEl, item.icon, item.state);
344
+ const titleEl = document.createElement("span");
345
+ applyAttrs(titleEl, attrs.title);
346
+ if (item.styles?.title) titleEl.className = item.styles.title;
347
+ titleEl.textContent = item.title ?? item.state;
348
+ newInner.appendChild(badgeEl);
349
+ newInner.appendChild(titleEl);
350
+ inst.headerStackEl.insertBefore(newInner, oldInner);
351
+ inst.innerEl = newInner;
352
+ inst.pillRo.unobserve(oldInner);
353
+ inst.pillRo.observe(newInner);
354
+ setTimer(inst, () => {
355
+ oldInner.remove();
356
+ }, HEADER_EXIT_MS);
357
+ requestAnimationFrame(() => {
358
+ measurePillWidth(inst);
359
+ applyVars(inst, inst.item);
360
+ animatePill(inst, inst.item);
361
+ });
362
+ }
363
+ function destroyInstance(inst) {
364
+ for (const t of inst.timers) clearTimeout(t);
365
+ inst.timers.clear();
366
+ inst.pillAnim?.cancel();
367
+ inst.pillRo.disconnect();
368
+ inst.contentRo?.disconnect();
369
+ inst.connectHandle.destroy();
370
+ inst.el.remove();
371
+ }
372
+
373
+ // src/toast/instance.ts
374
+ var WIDTH = 350;
375
+ var HEIGHT = 40;
376
+ var MIN_EXPAND_RATIO2 = 2.25;
377
+ function getPillAlign(position) {
378
+ if (position.includes("right")) return "right";
379
+ if (position.includes("center")) return "center";
380
+ return "left";
381
+ }
382
+ function buildInstanceSvg(item, roundness, blur2) {
239
383
  const filterId = `fluix-gooey-${item.id.replace(/[^a-z0-9-]/gi, "-")}`;
240
384
  const hasDesc = Boolean(item.description) || Boolean(item.button);
241
- const el = document.createElement("button");
242
- el.type = "button";
243
- const canvasDiv = document.createElement("div");
244
- const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
245
- const initialSvgHeight = hasDesc ? minExpanded : HEIGHT;
385
+ const initialSvgHeight = hasDesc ? HEIGHT * MIN_EXPAND_RATIO2 : HEIGHT;
246
386
  const svg = document.createElementNS(SVG_NS, "svg");
247
387
  svg.setAttribute("data-fluix-svg", "");
248
388
  svg.setAttribute("width", String(WIDTH));
249
389
  svg.setAttribute("height", String(initialSvgHeight));
250
390
  svg.setAttribute("viewBox", `0 0 ${WIDTH} ${initialSvgHeight}`);
251
391
  svg.setAttribute("aria-hidden", "true");
252
- const defs = document.createElementNS(SVG_NS, "defs");
253
- const filter = document.createElementNS(SVG_NS, "filter");
254
- filter.setAttribute("id", filterId);
255
- filter.setAttribute("x", "-20%");
256
- filter.setAttribute("y", "-20%");
257
- filter.setAttribute("width", "140%");
258
- filter.setAttribute("height", "140%");
259
- filter.setAttribute("color-interpolation-filters", "sRGB");
260
- const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
261
- feBlur.setAttribute("in", "SourceGraphic");
262
- feBlur.setAttribute("stdDeviation", String(blur));
263
- feBlur.setAttribute("result", "blur");
264
- const feCM = document.createElementNS(SVG_NS, "feColorMatrix");
265
- feCM.setAttribute("in", "blur");
266
- feCM.setAttribute("type", "matrix");
267
- feCM.setAttribute("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10");
268
- feCM.setAttribute("result", "goo");
269
- const feComp = document.createElementNS(SVG_NS, "feComposite");
270
- feComp.setAttribute("in", "SourceGraphic");
271
- feComp.setAttribute("in2", "goo");
272
- feComp.setAttribute("operator", "atop");
273
- filter.appendChild(feBlur);
274
- filter.appendChild(feCM);
275
- filter.appendChild(feComp);
276
- defs.appendChild(filter);
392
+ const { g, defs } = createGooeyFilter(filterId, blur2);
277
393
  svg.appendChild(defs);
278
- const g = document.createElementNS(SVG_NS, "g");
279
- g.setAttribute("filter", `url(#${filterId})`);
280
394
  const initialPillX = getPillAlign(item.position) === "right" ? WIDTH - HEIGHT : getPillAlign(item.position) === "center" ? (WIDTH - HEIGHT) / 2 : 0;
395
+ const fill = item.fill ?? "var(--fluix-surface-contrast)";
281
396
  const pillEl = document.createElementNS(SVG_NS, "rect");
282
397
  pillEl.setAttribute("data-fluix-pill", "");
283
- pillEl.setAttribute("x", String(initialPillX));
284
- pillEl.setAttribute("y", "0");
285
- pillEl.setAttribute("width", String(HEIGHT));
286
- pillEl.setAttribute("height", String(HEIGHT));
287
- pillEl.setAttribute("rx", String(roundness));
288
- pillEl.setAttribute("ry", String(roundness));
289
- pillEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
398
+ for (const [k, v] of Object.entries({ x: initialPillX, y: 0, width: HEIGHT, height: HEIGHT, rx: roundness, ry: roundness })) {
399
+ pillEl.setAttribute(k, String(v));
400
+ }
401
+ pillEl.setAttribute("fill", fill);
290
402
  const bodyEl = document.createElementNS(SVG_NS, "rect");
291
403
  bodyEl.setAttribute("data-fluix-body", "");
292
- bodyEl.setAttribute("x", "0");
293
- bodyEl.setAttribute("y", String(HEIGHT));
294
- bodyEl.setAttribute("width", String(WIDTH));
295
- bodyEl.setAttribute("height", "0");
296
- bodyEl.setAttribute("rx", String(roundness));
297
- bodyEl.setAttribute("ry", String(roundness));
298
- bodyEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
404
+ for (const [k, v] of Object.entries({ x: 0, y: HEIGHT, width: WIDTH, height: 0, rx: roundness, ry: roundness })) {
405
+ bodyEl.setAttribute(k, String(v));
406
+ }
407
+ bodyEl.setAttribute("fill", fill);
299
408
  bodyEl.setAttribute("opacity", "0");
300
409
  g.appendChild(pillEl);
301
410
  g.appendChild(bodyEl);
302
411
  svg.appendChild(g);
303
- canvasDiv.appendChild(svg);
304
- el.appendChild(canvasDiv);
412
+ return { svg, pillEl, bodyEl, initialPillX };
413
+ }
414
+ function buildInstanceContent(item) {
415
+ let contentEl = null;
416
+ let descriptionEl = null;
417
+ const hasDesc = Boolean(item.description) || Boolean(item.button);
418
+ if (!hasDesc) return { contentEl, descriptionEl };
419
+ contentEl = document.createElement("div");
420
+ descriptionEl = document.createElement("div");
421
+ if (item.styles?.description) descriptionEl.className = item.styles.description;
422
+ if (item.description != null) {
423
+ if (typeof item.description === "string") descriptionEl.textContent = item.description;
424
+ else if (item.description instanceof HTMLElement) descriptionEl.appendChild(item.description);
425
+ }
426
+ if (item.button) {
427
+ const btn = document.createElement("button");
428
+ btn.type = "button";
429
+ btn.textContent = item.button.title;
430
+ if (item.styles?.button) btn.className = item.styles.button;
431
+ btn.addEventListener("click", (e) => {
432
+ e.stopPropagation();
433
+ item.button?.onClick();
434
+ });
435
+ descriptionEl.appendChild(btn);
436
+ }
437
+ contentEl.appendChild(descriptionEl);
438
+ return { contentEl, descriptionEl };
439
+ }
440
+ function buildHeader(item) {
305
441
  const headerEl = document.createElement("div");
306
442
  const headerStackEl = document.createElement("div");
307
443
  headerStackEl.setAttribute("data-fluix-header-stack", "");
@@ -316,40 +452,24 @@ function createInstance(item, machine) {
316
452
  innerEl.appendChild(titleEl);
317
453
  headerStackEl.appendChild(innerEl);
318
454
  headerEl.appendChild(headerStackEl);
319
- el.appendChild(headerEl);
320
- let contentEl = null;
321
- let descriptionEl = null;
322
- if (hasDesc) {
323
- contentEl = document.createElement("div");
324
- descriptionEl = document.createElement("div");
325
- if (item.styles?.description) {
326
- descriptionEl.className = item.styles.description;
327
- }
328
- if (item.description != null) {
329
- if (typeof item.description === "string") {
330
- descriptionEl.textContent = item.description;
331
- } else if (item.description instanceof HTMLElement) {
332
- descriptionEl.appendChild(item.description);
333
- }
334
- }
335
- if (item.button) {
336
- const btn = document.createElement("button");
337
- btn.type = "button";
338
- btn.textContent = item.button.title;
339
- if (item.styles?.button) {
340
- btn.className = item.styles.button;
341
- }
342
- btn.addEventListener("click", (e) => {
343
- e.stopPropagation();
344
- item.button?.onClick();
345
- });
346
- descriptionEl.appendChild(btn);
347
- }
348
- contentEl.appendChild(descriptionEl);
349
- el.appendChild(contentEl);
350
- }
455
+ return { headerEl, headerStackEl, innerEl, badgeEl, titleEl };
456
+ }
457
+ function createInstance(item, machine) {
458
+ const localState = { ready: false, expanded: false };
459
+ const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
460
+ const blur2 = Math.min(10, Math.max(6, roundness * 0.45));
461
+ const el2 = document.createElement("button");
462
+ el2.type = "button";
463
+ const canvasDiv = document.createElement("div");
464
+ const { svg, pillEl, bodyEl, initialPillX } = buildInstanceSvg(item, roundness, blur2);
465
+ canvasDiv.appendChild(svg);
466
+ el2.appendChild(canvasDiv);
467
+ const { headerEl, headerStackEl, innerEl, badgeEl, titleEl } = buildHeader(item);
468
+ el2.appendChild(headerEl);
469
+ const { contentEl, descriptionEl } = buildInstanceContent(item);
470
+ if (contentEl) el2.appendChild(contentEl);
351
471
  const attrs = Toaster.getAttrs(item, localState);
352
- applyAttrs(el, attrs.root);
472
+ applyAttrs(el2, attrs.root);
353
473
  applyAttrs(canvasDiv, attrs.canvas);
354
474
  applyAttrs(headerEl, attrs.header);
355
475
  applyAttrs(badgeEl, attrs.badge);
@@ -362,9 +482,8 @@ function createInstance(item, machine) {
362
482
  const btnEl = descriptionEl.querySelector("button");
363
483
  if (btnEl) applyAttrs(btnEl, attrs.button);
364
484
  }
365
- const headerKey = `${item.state}-${item.title ?? item.state}`;
366
485
  const inst = {
367
- el,
486
+ el: el2,
368
487
  pillEl,
369
488
  bodyEl,
370
489
  svgEl: svg,
@@ -376,7 +495,7 @@ function createInstance(item, machine) {
376
495
  localState,
377
496
  pillWidth: 0,
378
497
  contentHeight: 0,
379
- frozenExpanded: HEIGHT * MIN_EXPAND_RATIO,
498
+ frozenExpanded: HEIGHT * MIN_EXPAND_RATIO2,
380
499
  hovering: false,
381
500
  pendingDismiss: false,
382
501
  dismissRequested: false,
@@ -385,865 +504,735 @@ function createInstance(item, machine) {
385
504
  pillAnim: null,
386
505
  prevPill: { x: initialPillX, width: HEIGHT, height: HEIGHT },
387
506
  pillFirst: true,
388
- headerKey,
507
+ headerKey: `${item.state}-${item.title ?? item.state}`,
389
508
  headerPad: null,
390
509
  connectHandle: null,
391
510
  timers: /* @__PURE__ */ new Set(),
392
511
  item
393
512
  };
513
+ wireObservers(inst);
514
+ wireConnect(inst, item, machine);
515
+ applyVars(inst, item);
516
+ setTimer(inst, () => {
517
+ inst.localState.ready = true;
518
+ applyUpdate(inst, inst.item);
519
+ setupAutopilot(inst, inst.item);
520
+ }, 32);
521
+ setupAutoDismiss(inst, item, machine);
522
+ measurePillWidth(inst);
523
+ return inst;
524
+ }
525
+ function wireObservers(inst, _item) {
394
526
  inst.pillRo = new ResizeObserver(() => {
395
527
  requestAnimationFrame(() => {
396
528
  measurePillWidth(inst);
397
529
  applyVars(inst, inst.item);
398
- animatePill(inst, inst.item);
399
530
  });
400
531
  });
401
- inst.pillRo.observe(innerEl);
402
- if (descriptionEl) {
532
+ inst.pillRo.observe(inst.innerEl);
533
+ if (inst.descriptionEl) {
534
+ const descEl = inst.descriptionEl;
403
535
  inst.contentRo = new ResizeObserver(() => {
404
536
  requestAnimationFrame(() => {
405
- const h = descriptionEl.scrollHeight;
537
+ const h = descEl.scrollHeight;
406
538
  if (h !== inst.contentHeight) {
407
539
  inst.contentHeight = h;
408
540
  applyVars(inst, inst.item);
409
541
  }
410
542
  });
411
543
  });
412
- inst.contentRo.observe(descriptionEl);
544
+ inst.contentRo.observe(descEl);
413
545
  }
414
- inst.connectHandle = Toaster.connect(
415
- el,
416
- {
417
- onExpand: () => {
418
- if (inst.item.exiting || inst.dismissRequested) return;
419
- inst.localState.expanded = true;
420
- applyUpdate(inst, inst.item);
421
- },
422
- onCollapse: () => {
423
- if (inst.item.exiting || inst.dismissRequested) return;
424
- if (inst.item.autopilot !== false) return;
425
- inst.localState.expanded = false;
426
- applyUpdate(inst, inst.item);
427
- },
428
- onDismiss: () => {
429
- if (inst.dismissRequested) return;
430
- inst.dismissRequested = true;
431
- machine.dismiss(item.id);
432
- },
433
- onHoverStart: () => {
434
- inst.hovering = true;
435
- },
436
- onHoverEnd: () => {
437
- inst.hovering = false;
438
- if (inst.pendingDismiss) {
439
- inst.pendingDismiss = false;
440
- if (inst.dismissRequested) return;
441
- inst.dismissRequested = true;
442
- machine.dismiss(inst.item.id);
443
- }
444
- }
445
- },
446
- item
447
- );
448
- applyVars(inst, item);
449
- setTimer(
450
- inst,
451
- () => {
452
- inst.localState.ready = true;
546
+ }
547
+ function wireConnect(inst, item, machine) {
548
+ inst.connectHandle = Toaster.connect(inst.el, {
549
+ onExpand: () => {
550
+ if (inst.item.exiting || inst.dismissRequested) return;
551
+ inst.localState.expanded = true;
453
552
  applyUpdate(inst, inst.item);
454
- setupAutopilot(inst, inst.item);
455
553
  },
456
- 32
457
- );
458
- setupAutoDismiss(inst, item, machine);
459
- measurePillWidth(inst);
460
- return inst;
461
- }
462
- function measurePillWidth(inst) {
463
- if (!inst.el.isConnected) return;
464
- if (inst.headerPad === null) {
465
- const cs = getComputedStyle(inst.headerEl);
466
- inst.headerPad = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
467
- }
468
- const intrinsicWidth = inst.innerEl.getBoundingClientRect().width;
469
- const w = intrinsicWidth + inst.headerPad + PILL_PADDING;
470
- if (w > PILL_PADDING && w !== inst.pillWidth) {
471
- inst.pillWidth = w;
472
- }
473
- }
474
- function applyVars(inst, item) {
475
- const layout = computeLayout(item, inst);
476
- const { open, expanded, resolvedPillWidth, pillX, edge, expandedContent, svgHeight } = layout;
477
- const vars = {
478
- "--_h": `${open ? expanded : HEIGHT}px`,
479
- "--_pw": `${resolvedPillWidth}px`,
480
- "--_px": `${pillX}px`,
481
- "--_ht": `translateY(${open ? edge === "bottom" ? 3 : -3 : 0}px) scale(${open ? 0.9 : 1})`,
482
- "--_co": `${open ? 1 : 0}`,
483
- "--_cy": `${open ? 0 : -14}px`,
484
- "--_cm": `${open ? expandedContent : 0}px`,
485
- "--_by": `${open ? HEIGHT - BODY_MERGE_OVERLAP : HEIGHT}px`,
486
- "--_bh": `${open ? expandedContent : 0}px`,
487
- "--_bo": `${open ? 1 : 0}`
488
- };
489
- for (const [key, value] of Object.entries(vars)) {
490
- inst.el.style.setProperty(key, value);
491
- }
492
- inst.svgEl.setAttribute("height", String(svgHeight));
493
- inst.svgEl.setAttribute("viewBox", `0 0 ${WIDTH} ${svgHeight}`);
494
- }
495
- function animatePill(inst, item) {
496
- const layout = computeLayout(item, inst);
497
- const { open, resolvedPillWidth, pillX, pillHeight } = layout;
498
- const prev = inst.prevPill;
499
- const next = { x: pillX, width: resolvedPillWidth, height: open ? pillHeight : HEIGHT };
500
- if (prev.x === next.x && prev.width === next.width && prev.height === next.height) return;
501
- inst.pillAnim?.cancel();
502
- if (!inst.localState.ready || inst.pillFirst) {
503
- inst.pillFirst = false;
504
- inst.pillEl.setAttribute("x", String(next.x));
505
- inst.pillEl.setAttribute("width", String(next.width));
506
- inst.pillEl.setAttribute("height", String(next.height));
507
- inst.prevPill = next;
508
- return;
509
- }
510
- const anim = animateSpring(
511
- inst.pillEl,
512
- {
513
- x: { from: prev.x, to: next.x, unit: "px" },
514
- width: { from: prev.width, to: next.width, unit: "px" },
515
- height: { from: prev.height, to: next.height, unit: "px" }
554
+ onCollapse: () => {
555
+ if (inst.item.exiting || inst.dismissRequested) return;
556
+ if (inst.item.autopilot !== false) return;
557
+ inst.localState.expanded = false;
558
+ applyUpdate(inst, inst.item);
516
559
  },
517
- FLUIX_SPRING
518
- );
519
- inst.pillAnim = anim;
520
- inst.prevPill = next;
521
- }
522
- function setupAutoDismiss(inst, item, machine) {
523
- const duration = item.duration;
524
- if (duration == null || duration <= 0) return;
525
- setTimer(
526
- inst,
527
- () => {
528
- if (inst.hovering) {
529
- inst.pendingDismiss = true;
530
- setTimer(
531
- inst,
532
- () => {
533
- if (inst.dismissRequested) return;
534
- inst.dismissRequested = true;
535
- inst.pendingDismiss = false;
536
- machine.dismiss(item.id);
537
- },
538
- 1200
539
- );
540
- return;
541
- }
542
- inst.pendingDismiss = false;
560
+ onDismiss: () => {
561
+ if (inst.dismissRequested) return;
543
562
  inst.dismissRequested = true;
544
563
  machine.dismiss(item.id);
545
564
  },
546
- duration
547
- );
548
- }
549
- function setupAutopilot(inst, item, machine) {
550
- if (item.autoExpandDelayMs != null && item.autoExpandDelayMs > 0) {
551
- setTimer(
552
- inst,
553
- () => {
554
- if (!inst.hovering) {
555
- inst.localState.expanded = true;
556
- applyUpdate(inst, inst.item);
557
- }
558
- },
559
- item.autoExpandDelayMs
560
- );
561
- }
562
- if (item.autoCollapseDelayMs != null && item.autoCollapseDelayMs > 0) {
563
- setTimer(
564
- inst,
565
- () => {
566
- if (!inst.hovering) {
567
- inst.localState.expanded = false;
568
- applyUpdate(inst, inst.item);
569
- }
570
- },
571
- item.autoCollapseDelayMs
572
- );
573
- }
574
- }
575
- function applyUpdate(inst, item, _machine) {
576
- inst.item = item;
577
- const attrs = Toaster.getAttrs(item, inst.localState);
578
- applyAttrs(inst.el, attrs.root);
579
- const canvasEl = inst.el.querySelector("[data-fluix-canvas]");
580
- if (canvasEl) applyAttrs(canvasEl, attrs.canvas);
581
- applyAttrs(inst.headerEl, attrs.header);
582
- const badgeEl = inst.innerEl.querySelector("[data-fluix-badge]");
583
- if (badgeEl) {
584
- applyAttrs(badgeEl, attrs.badge);
585
- if (item.styles?.badge) badgeEl.className = item.styles.badge;
586
- }
587
- const titleEl = inst.innerEl.querySelector("[data-fluix-title]");
588
- if (titleEl) {
589
- applyAttrs(titleEl, attrs.title);
590
- if (item.styles?.title) titleEl.className = item.styles.title;
591
- }
592
- const hasDesc = Boolean(item.description) || Boolean(item.button);
593
- if (hasDesc && !inst.contentEl) {
594
- const contentEl = document.createElement("div");
595
- const descriptionEl = document.createElement("div");
596
- contentEl.appendChild(descriptionEl);
597
- inst.el.appendChild(contentEl);
598
- inst.contentEl = contentEl;
599
- inst.descriptionEl = descriptionEl;
600
- inst.contentRo = new ResizeObserver(() => {
601
- requestAnimationFrame(() => {
602
- const h = descriptionEl.scrollHeight;
603
- if (h !== inst.contentHeight) {
604
- inst.contentHeight = h;
605
- applyVars(inst, inst.item);
606
- }
607
- });
608
- });
609
- inst.contentRo.observe(descriptionEl);
610
- }
611
- if (inst.contentEl) applyAttrs(inst.contentEl, attrs.content);
612
- if (inst.descriptionEl) {
613
- applyAttrs(inst.descriptionEl, attrs.description);
614
- if (item.styles?.description) {
615
- inst.descriptionEl.className = item.styles.description;
616
- }
617
- const existingBtn = inst.descriptionEl.querySelector("[data-fluix-button]");
618
- inst.descriptionEl.textContent = "";
619
- if (item.description != null) {
620
- if (typeof item.description === "string") {
621
- inst.descriptionEl.textContent = item.description;
622
- } else if (item.description instanceof HTMLElement) {
623
- inst.descriptionEl.appendChild(item.description);
624
- }
625
- }
626
- if (item.button) {
627
- let btnEl = existingBtn;
628
- if (!btnEl) {
629
- btnEl = document.createElement("button");
630
- btnEl.type = "button";
565
+ onHoverStart: () => {
566
+ inst.hovering = true;
567
+ },
568
+ onHoverEnd: () => {
569
+ inst.hovering = false;
570
+ if (inst.pendingDismiss) {
571
+ inst.pendingDismiss = false;
572
+ if (inst.dismissRequested) return;
573
+ inst.dismissRequested = true;
574
+ machine.dismiss(inst.item.id);
631
575
  }
632
- btnEl.textContent = item.button.title;
633
- if (item.styles?.button) btnEl.className = item.styles.button;
634
- applyAttrs(btnEl, attrs.button);
635
- const newBtn = btnEl.cloneNode(true);
636
- newBtn.addEventListener("click", (e) => {
637
- e.stopPropagation();
638
- item.button?.onClick();
639
- });
640
- inst.descriptionEl.appendChild(newBtn);
641
576
  }
577
+ }, item);
578
+ }
579
+
580
+ // src/toast/index.ts
581
+ function resolveOffsetValue(v) {
582
+ return typeof v === "number" ? `${v}px` : v;
583
+ }
584
+ function applyViewportOffset(el2, offset, position) {
585
+ el2.style.top = "";
586
+ el2.style.right = "";
587
+ el2.style.bottom = "";
588
+ el2.style.left = "";
589
+ el2.style.paddingLeft = "";
590
+ el2.style.paddingRight = "";
591
+ if (offset == null) return;
592
+ let top, right, bottom, left;
593
+ if (typeof offset === "number" || typeof offset === "string") {
594
+ const v = resolveOffsetValue(offset);
595
+ top = v;
596
+ right = v;
597
+ bottom = v;
598
+ left = v;
599
+ } else {
600
+ if (offset.top != null) top = resolveOffsetValue(offset.top);
601
+ if (offset.right != null) right = resolveOffsetValue(offset.right);
602
+ if (offset.bottom != null) bottom = resolveOffsetValue(offset.bottom);
603
+ if (offset.left != null) left = resolveOffsetValue(offset.left);
642
604
  }
643
- inst.pillEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
644
- inst.bodyEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
645
- const newHeaderKey = `${item.state}-${item.title ?? item.state}`;
646
- if (newHeaderKey !== inst.headerKey) {
647
- crossfadeHeader(inst, item, attrs);
648
- inst.headerKey = newHeaderKey;
605
+ if (position.startsWith("top") && top) el2.style.top = top;
606
+ if (position.startsWith("bottom") && bottom) el2.style.bottom = bottom;
607
+ if (position.endsWith("right") && right) el2.style.right = right;
608
+ if (position.endsWith("left") && left) el2.style.left = left;
609
+ if (position.endsWith("center")) {
610
+ if (left) el2.style.paddingLeft = left;
611
+ if (right) el2.style.paddingRight = right;
649
612
  }
650
- applyVars(inst, item);
651
- animatePill(inst, item);
652
- }
653
- function crossfadeHeader(inst, item, attrs) {
654
- const oldInner = inst.innerEl;
655
- oldInner.setAttribute("data-layer", "prev");
656
- oldInner.setAttribute("data-exiting", "true");
657
- const newInner = document.createElement("div");
658
- newInner.setAttribute("data-fluix-header-inner", "");
659
- newInner.setAttribute("data-layer", "current");
660
- const badgeEl = document.createElement("div");
661
- applyAttrs(badgeEl, attrs.badge);
662
- if (item.styles?.badge) badgeEl.className = item.styles.badge;
663
- renderIconInto(badgeEl, item.icon, item.state);
664
- const titleEl = document.createElement("span");
665
- applyAttrs(titleEl, attrs.title);
666
- if (item.styles?.title) titleEl.className = item.styles.title;
667
- titleEl.textContent = item.title ?? item.state;
668
- newInner.appendChild(badgeEl);
669
- newInner.appendChild(titleEl);
670
- inst.headerStackEl.insertBefore(newInner, oldInner);
671
- inst.innerEl = newInner;
672
- inst.pillRo.unobserve(oldInner);
673
- inst.pillRo.observe(newInner);
674
- setTimer(
675
- inst,
676
- () => {
677
- oldInner.remove();
678
- },
679
- HEADER_EXIT_MS
680
- );
681
- requestAnimationFrame(() => {
682
- measurePillWidth(inst);
683
- applyVars(inst, inst.item);
684
- animatePill(inst, inst.item);
685
- });
686
613
  }
687
- function destroyInstance(inst) {
688
- for (const t of inst.timers) clearTimeout(t);
689
- inst.timers.clear();
690
- inst.pillAnim?.cancel();
691
- inst.pillRo.disconnect();
692
- inst.contentRo?.disconnect();
693
- inst.connectHandle.destroy();
694
- inst.el.remove();
614
+ function ensureViewport(state, position, layout, offset) {
615
+ let vp = state.viewports.get(position);
616
+ if (!vp) {
617
+ vp = document.createElement("section");
618
+ applyAttrs(vp, Toaster.getViewportAttrs(position, layout));
619
+ applyViewportOffset(vp, offset, position);
620
+ document.body.appendChild(vp);
621
+ state.viewports.set(position, vp);
622
+ }
623
+ return vp;
695
624
  }
696
- function createToaster(config) {
697
- const machine = Toaster.getMachine();
698
- let currentConfig = config;
699
- if (currentConfig) machine.configure(currentConfig);
700
- const instances = /* @__PURE__ */ new Map();
701
- const viewports = /* @__PURE__ */ new Map();
702
- function ensureViewport(position, layout, offset) {
703
- let vp = viewports.get(position);
704
- if (!vp) {
705
- vp = document.createElement("section");
706
- const vpAttrs = Toaster.getViewportAttrs(position, layout);
707
- applyAttrs(vp, vpAttrs);
708
- applyViewportOffset(vp, offset, position);
709
- document.body.appendChild(vp);
710
- viewports.set(position, vp);
625
+ function sync(state) {
626
+ const next = state.machine.store.getSnapshot();
627
+ const layout = next.config?.layout ?? state.currentConfig?.layout ?? "stack";
628
+ const offset = next.config?.offset ?? state.currentConfig?.offset;
629
+ const activePositions = /* @__PURE__ */ new Set();
630
+ const nextIds = new Set(next.toasts.map((t) => t.id));
631
+ for (const [id, inst] of state.instances) {
632
+ if (!nextIds.has(id)) {
633
+ destroyInstance(inst);
634
+ state.instances.delete(id);
711
635
  }
712
- return vp;
713
636
  }
714
- function removeViewport(position) {
715
- const vp = viewports.get(position);
716
- if (vp) {
717
- vp.remove();
718
- viewports.delete(position);
637
+ for (const item of next.toasts) {
638
+ activePositions.add(item.position);
639
+ const vp = ensureViewport(state, item.position, layout, offset);
640
+ const existing = state.instances.get(item.id);
641
+ if (!existing) {
642
+ const inst = createInstance(item, state.machine);
643
+ state.instances.set(item.id, inst);
644
+ vp.appendChild(inst.el);
645
+ } else if (existing.item.instanceId !== item.instanceId) {
646
+ destroyInstance(existing);
647
+ const inst = createInstance(item, state.machine);
648
+ state.instances.set(item.id, inst);
649
+ vp.appendChild(inst.el);
650
+ } else {
651
+ applyUpdate(existing, item);
652
+ if (existing.el.parentElement !== vp) vp.appendChild(existing.el);
719
653
  }
720
654
  }
721
- function sync() {
722
- const next = machine.store.getSnapshot();
723
- const resolvedLayout = next.config?.layout ?? currentConfig?.layout ?? "stack";
724
- const resolvedOffset = next.config?.offset ?? currentConfig?.offset;
725
- const activePositions = /* @__PURE__ */ new Set();
726
- const nextIds = new Set(next.toasts.map((t) => t.id));
727
- for (const [id, inst] of instances) {
728
- if (!nextIds.has(id)) {
729
- destroyInstance(inst);
730
- instances.delete(id);
731
- }
732
- }
733
- for (const item of next.toasts) {
734
- activePositions.add(item.position);
735
- const vp = ensureViewport(item.position, resolvedLayout, resolvedOffset);
736
- const existing = instances.get(item.id);
737
- if (!existing) {
738
- const inst = createInstance(item, machine);
739
- instances.set(item.id, inst);
740
- vp.appendChild(inst.el);
741
- } else if (existing.item.instanceId !== item.instanceId) {
742
- destroyInstance(existing);
743
- const inst = createInstance(item, machine);
744
- instances.set(item.id, inst);
745
- vp.appendChild(inst.el);
746
- } else {
747
- applyUpdate(existing, item);
748
- if (existing.el.parentElement !== vp) {
749
- vp.appendChild(existing.el);
750
- }
751
- }
752
- }
753
- for (const [position, vp] of viewports) {
754
- const vpAttrs = Toaster.getViewportAttrs(position, resolvedLayout);
755
- applyAttrs(vp, vpAttrs);
756
- applyViewportOffset(vp, resolvedOffset, position);
757
- }
758
- for (const [position] of viewports) {
759
- if (!activePositions.has(position)) {
760
- removeViewport(position);
761
- }
655
+ for (const [position, vp] of state.viewports) {
656
+ applyAttrs(vp, Toaster.getViewportAttrs(position, layout));
657
+ applyViewportOffset(vp, offset, position);
658
+ }
659
+ for (const [position] of state.viewports) {
660
+ if (!activePositions.has(position)) {
661
+ state.viewports.get(position)?.remove();
662
+ state.viewports.delete(position);
762
663
  }
763
664
  }
764
- sync();
765
- const unsubscribe = machine.store.subscribe(sync);
665
+ }
666
+ function createToaster(config) {
667
+ const state = {
668
+ machine: Toaster.getMachine(),
669
+ instances: /* @__PURE__ */ new Map(),
670
+ viewports: /* @__PURE__ */ new Map(),
671
+ currentConfig: config
672
+ };
673
+ if (config) state.machine.configure(config);
674
+ sync(state);
675
+ const unsubscribe = state.machine.store.subscribe(() => sync(state));
766
676
  return {
767
677
  destroy() {
768
678
  unsubscribe();
769
- for (const inst of instances.values()) {
770
- destroyInstance(inst);
771
- }
772
- instances.clear();
773
- for (const vp of viewports.values()) {
774
- vp.remove();
775
- }
776
- viewports.clear();
679
+ for (const inst of state.instances.values()) destroyInstance(inst);
680
+ state.instances.clear();
681
+ for (const vp of state.viewports.values()) vp.remove();
682
+ state.viewports.clear();
777
683
  },
778
684
  update(newConfig) {
779
- currentConfig = newConfig;
780
- machine.configure(newConfig);
685
+ state.currentConfig = newConfig;
686
+ state.machine.configure(newConfig);
781
687
  }
782
688
  };
783
689
  }
784
- var SVG_NS2 = "http://www.w3.org/2000/svg";
785
- function applyAttrs2(el, attrs) {
786
- for (const [key, value] of Object.entries(attrs)) {
787
- el.setAttribute(key, value);
690
+ function setBlobAttrs(el2, x, y, w, h, rx, opacity) {
691
+ el2.setAttribute("x", String(x));
692
+ el2.setAttribute("y", String(y));
693
+ el2.setAttribute("width", String(w));
694
+ el2.setAttribute("height", String(h));
695
+ el2.setAttribute("rx", String(rx));
696
+ el2.setAttribute("ry", String(rx));
697
+ el2.setAttribute("opacity", opacity);
698
+ }
699
+ function springBlob(el2, from, to, sc) {
700
+ return animateSpring(el2, {
701
+ x: { from: from.x, to: to.x, unit: "px" },
702
+ y: { from: from.y, to: to.y, unit: "px" },
703
+ width: { from: from.w, to: to.w, unit: "px" },
704
+ height: { from: from.h, to: to.h, unit: "px" },
705
+ rx: { from: from.rx, to: to.rx, unit: "px" },
706
+ ry: { from: from.rx, to: to.rx, unit: "px" }
707
+ }, { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 });
708
+ }
709
+ function createHighlightTracker(el2, springConfig) {
710
+ let anim = null;
711
+ const prev = { x: 0, y: 0, w: 0, h: 0, visible: false };
712
+ function finish(a, x, y, w, h, rx, opacity) {
713
+ if (a) {
714
+ anim = a;
715
+ a.onfinish = () => {
716
+ anim = null;
717
+ setBlobAttrs(el2, x, y, w, h, rx, opacity);
718
+ };
719
+ } else {
720
+ setBlobAttrs(el2, x, y, w, h, rx, opacity);
721
+ }
788
722
  }
723
+ return {
724
+ onItemEnter(e, rootEl, isOpen, roundness) {
725
+ const target = e.target.closest("a, button");
726
+ if (!target || !isOpen) return;
727
+ const rootRect = rootEl.getBoundingClientRect();
728
+ const itemRect = target.getBoundingClientRect();
729
+ const padX = 8, padY = 4;
730
+ const overshoot = Math.max(6, roundness * 0.35);
731
+ const toW = target.offsetWidth + padX * 2;
732
+ const toH = Math.max(target.offsetHeight + padY * 2, rootRect.height + overshoot * 2);
733
+ const toX = itemRect.left + itemRect.width / 2 - rootRect.left - toW / 2;
734
+ const toY = itemRect.top + itemRect.height / 2 - rootRect.top - toH / 2;
735
+ if (anim) {
736
+ anim.cancel();
737
+ anim = null;
738
+ }
739
+ const from = {
740
+ x: prev.visible ? prev.x : toX + toW / 2,
741
+ y: prev.visible ? prev.y : toY + toH / 2,
742
+ w: prev.visible ? prev.w : 0,
743
+ h: prev.visible ? prev.h : 0,
744
+ rx: prev.visible ? prev.h / 2 : 0
745
+ };
746
+ finish(springBlob(el2, from, { x: toX, y: toY, w: toW, h: toH, rx: toH / 2 }, springConfig()), toX, toY, toW, toH, toH / 2, "1");
747
+ el2.setAttribute("opacity", "1");
748
+ prev.x = toX;
749
+ prev.y = toY;
750
+ prev.w = toW;
751
+ prev.h = toH;
752
+ prev.visible = true;
753
+ },
754
+ onItemLeave() {
755
+ if (!prev.visible) return;
756
+ const cx = prev.x + prev.w / 2, cy = prev.y + prev.h / 2;
757
+ finish(springBlob(el2, { x: prev.x, y: prev.y, w: prev.w, h: prev.h, rx: prev.h / 2 }, { x: cx, y: cy, w: 0, h: 0, rx: 0 }, springConfig()), cx, cy, 0, 0, 0, "0");
758
+ prev.visible = false;
759
+ },
760
+ reset(rootW, rootH) {
761
+ if (anim) {
762
+ anim.cancel();
763
+ anim = null;
764
+ }
765
+ setBlobAttrs(el2, rootW / 2, rootH / 2, 0, 0, 0, "0");
766
+ prev.visible = false;
767
+ },
768
+ cancelAnim() {
769
+ anim?.cancel();
770
+ }
771
+ };
789
772
  }
773
+
774
+ // src/notch/dom.ts
790
775
  function resolveContent(source) {
791
776
  if (source instanceof HTMLElement) return source;
792
777
  const span = document.createElement("span");
793
778
  span.textContent = source;
794
779
  return span;
795
780
  }
796
- function createNotch(container, options) {
797
- let {
798
- trigger = "click",
799
- position = "top-center",
800
- spring,
801
- dotSize = 36,
802
- roundness = NOTCH_DEFAULTS.roundness,
803
- theme = "dark",
804
- fill,
805
- open: controlledOpen,
806
- onOpenChange
807
- } = options;
808
- const springConfig = () => spring ?? FLUIX_SPRING;
809
- const machine = createNotchMachine({
810
- position,
811
- trigger,
812
- roundness,
813
- fill,
814
- spring
815
- });
816
- let snapshot = machine.store.getSnapshot();
817
- let prevOpenVal;
818
- const blur = () => Math.min(10, Math.max(6, roundness * 0.45));
819
- const collapsedW = () => dotSize;
820
- const collapsedH = () => dotSize;
821
- let contentSize = { w: 200, h: 44 };
822
- const hlPad = 12;
823
- const expandedW = () => contentSize.w + hlPad * 2;
824
- const expandedH = () => Math.max(contentSize.h + hlPad, dotSize);
825
- const targetW = () => snapshot.open ? expandedW() : collapsedW();
826
- const targetH = () => snapshot.open ? expandedH() : collapsedH();
827
- const rootW = () => Math.max(expandedW(), collapsedW());
828
- const rootH = () => Math.max(expandedH(), collapsedH());
829
- const prev = { w: 0, h: 0, initialized: false };
830
- let currentAnim = null;
831
- let highlightAnim = null;
832
- const hlPrev = { x: 0, y: 0, w: 0, h: 0, visible: false };
781
+ function buildNotchSvg(rW, rH, collW, collH, blurVal, fill) {
782
+ const svg = document.createElementNS(SVG_NS, "svg");
783
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
784
+ for (const [k, v] of Object.entries({ width: String(rW), height: String(rH), viewBox: `0 0 ${rW} ${rH}`, "aria-hidden": "true" })) svg.setAttribute(k, v);
785
+ const { g, defs, feBlur } = createGooeyFilter("fluix-notch-goo", blurVal);
786
+ svg.appendChild(defs);
787
+ const effectiveFill = fill ?? "var(--fluix-notch-bg)";
788
+ const svgRectEl = document.createElementNS(SVG_NS, "rect");
789
+ const cx = (rW - collW) / 2, cy = (rH - collH) / 2;
790
+ for (const [k, v] of Object.entries({ x: cx, y: cy, width: collW, height: collH, rx: collW / 2, ry: collH / 2 })) svgRectEl.setAttribute(k, String(v));
791
+ svgRectEl.setAttribute("fill", effectiveFill);
792
+ g.appendChild(svgRectEl);
793
+ const hoverBlobEl = document.createElementNS(SVG_NS, "rect");
794
+ for (const [k, v] of Object.entries({ x: cx, y: cy, width: 0, height: 0, rx: 0, ry: 0, opacity: "0" })) hoverBlobEl.setAttribute(k, String(v));
795
+ hoverBlobEl.setAttribute("fill", effectiveFill);
796
+ g.appendChild(hoverBlobEl);
797
+ svg.appendChild(g);
798
+ return { svg, svgRectEl, hoverBlobEl, feBlur };
799
+ }
800
+ var HL_PAD = 12;
801
+ function computeDims(cfg) {
802
+ const collW = cfg.dotSize, collH = cfg.dotSize;
803
+ const expW = cfg.contentSize.w + HL_PAD * 2;
804
+ const expH = Math.max(cfg.contentSize.h + HL_PAD, cfg.dotSize);
805
+ const rW = Math.max(expW, collW);
806
+ const rH = Math.max(expH, collH);
807
+ return { collW, collH, rW, rH };
808
+ }
809
+ function computeBlur(roundness) {
810
+ return Math.min(10, Math.max(6, roundness * 0.45));
811
+ }
812
+ function buildNotchDOM(cfg, pill, content, container, springCfg2) {
833
813
  const measureEl = document.createElement("div");
834
814
  measureEl.setAttribute("data-fluix-notch-measure", "");
835
- measureEl.appendChild(resolveContent(options.content).cloneNode(true));
815
+ measureEl.appendChild(resolveContent(content).cloneNode(true));
836
816
  container.appendChild(measureEl);
817
+ const d = computeDims(cfg);
837
818
  const rootEl = document.createElement("div");
838
- const attrs = getNotchAttrs({ open: snapshot.open, position, theme });
839
- applyAttrs2(rootEl, attrs.root);
840
- rootEl.style.width = `${rootW()}px`;
841
- rootEl.style.height = `${rootH()}px`;
819
+ const attrs = getNotchAttrs({ open: cfg.snapshot.open, position: cfg.position, theme: cfg.theme });
820
+ applyAttrs(rootEl, attrs.root);
821
+ rootEl.style.width = `${d.rW}px`;
822
+ rootEl.style.height = `${d.rH}px`;
842
823
  const canvasDiv = document.createElement("div");
843
- applyAttrs2(canvasDiv, attrs.canvas);
844
- const svg = document.createElementNS(SVG_NS2, "svg");
845
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
846
- svg.setAttribute("width", String(rootW()));
847
- svg.setAttribute("height", String(rootH()));
848
- svg.setAttribute("viewBox", `0 0 ${rootW()} ${rootH()}`);
849
- svg.setAttribute("aria-hidden", "true");
850
- const defs = document.createElementNS(SVG_NS2, "defs");
851
- const filter = document.createElementNS(SVG_NS2, "filter");
852
- filter.setAttribute("id", "fluix-notch-goo");
853
- filter.setAttribute("x", "-20%");
854
- filter.setAttribute("y", "-20%");
855
- filter.setAttribute("width", "140%");
856
- filter.setAttribute("height", "140%");
857
- filter.setAttribute("color-interpolation-filters", "sRGB");
858
- const feBlur = document.createElementNS(SVG_NS2, "feGaussianBlur");
859
- feBlur.setAttribute("in", "SourceGraphic");
860
- feBlur.setAttribute("stdDeviation", String(blur()));
861
- feBlur.setAttribute("result", "blur");
862
- const feCM = document.createElementNS(SVG_NS2, "feColorMatrix");
863
- feCM.setAttribute("in", "blur");
864
- feCM.setAttribute("type", "matrix");
865
- feCM.setAttribute("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10");
866
- feCM.setAttribute("result", "goo");
867
- const feComp = document.createElementNS(SVG_NS2, "feComposite");
868
- feComp.setAttribute("in", "SourceGraphic");
869
- feComp.setAttribute("in2", "goo");
870
- feComp.setAttribute("operator", "atop");
871
- filter.appendChild(feBlur);
872
- filter.appendChild(feCM);
873
- filter.appendChild(feComp);
874
- defs.appendChild(filter);
875
- svg.appendChild(defs);
876
- const gGroup = document.createElementNS(SVG_NS2, "g");
877
- gGroup.setAttribute("filter", "url(#fluix-notch-goo)");
878
- const svgRectEl = document.createElementNS(SVG_NS2, "rect");
879
- const cw = collapsedW();
880
- const ch = collapsedH();
881
- svgRectEl.setAttribute("x", String((rootW() - cw) / 2));
882
- svgRectEl.setAttribute("y", String((rootH() - ch) / 2));
883
- svgRectEl.setAttribute("width", String(cw));
884
- svgRectEl.setAttribute("height", String(ch));
885
- svgRectEl.setAttribute("rx", String(cw / 2));
886
- svgRectEl.setAttribute("ry", String(ch / 2));
887
- svgRectEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
888
- gGroup.appendChild(svgRectEl);
889
- svg.appendChild(gGroup);
890
- const hoverBlobEl = document.createElementNS(SVG_NS2, "rect");
891
- hoverBlobEl.setAttribute("x", String((rootW() - cw) / 2));
892
- hoverBlobEl.setAttribute("y", String((rootH() - ch) / 2));
893
- hoverBlobEl.setAttribute("width", "0");
894
- hoverBlobEl.setAttribute("height", "0");
895
- hoverBlobEl.setAttribute("rx", "0");
896
- hoverBlobEl.setAttribute("ry", "0");
897
- hoverBlobEl.setAttribute("opacity", "0");
898
- hoverBlobEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
899
- gGroup.appendChild(hoverBlobEl);
900
- canvasDiv.appendChild(svg);
824
+ applyAttrs(canvasDiv, attrs.canvas);
825
+ canvasDiv.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:visible;";
826
+ const svgRefs = buildNotchSvg(d.rW, d.rH, d.collW, d.collH, computeBlur(cfg.roundness), cfg.fill);
827
+ canvasDiv.appendChild(svgRefs.svg);
901
828
  rootEl.appendChild(canvasDiv);
829
+ const highlight = createHighlightTracker(svgRefs.hoverBlobEl, springCfg2);
902
830
  const pillDiv = document.createElement("div");
903
- applyAttrs2(pillDiv, attrs.pill);
904
- pillDiv.style.width = `${dotSize}px`;
905
- pillDiv.style.height = `${dotSize}px`;
906
- pillDiv.appendChild(resolveContent(options.pill));
831
+ applyAttrs(pillDiv, attrs.pill);
832
+ pillDiv.style.cssText = `position:absolute;z-index:10;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;align-items:center;justify-content:center;border-radius:50%;overflow:hidden;pointer-events:none;width:${cfg.dotSize}px;height:${cfg.dotSize}px;color:var(--fluix-notch-color);`;
833
+ pillDiv.appendChild(resolveContent(pill));
907
834
  rootEl.appendChild(pillDiv);
908
835
  const contentDiv = document.createElement("div");
909
- applyAttrs2(contentDiv, attrs.content);
910
- contentDiv.appendChild(resolveContent(options.content));
836
+ applyAttrs(contentDiv, attrs.content);
837
+ contentDiv.style.cssText = "position:absolute;z-index:10;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;color:var(--fluix-notch-color);";
838
+ contentDiv.appendChild(resolveContent(content));
911
839
  rootEl.appendChild(contentDiv);
912
840
  container.appendChild(rootEl);
913
- prev.w = cw;
914
- prev.h = ch;
915
- prev.initialized = true;
916
- let measureRaf = 0;
841
+ rootEl.setAttribute("role", "button");
842
+ rootEl.setAttribute("tabindex", "0");
843
+ rootEl.setAttribute("aria-expanded", String(cfg.snapshot.open));
844
+ return { rootEl, measureEl, pillDiv, contentDiv, highlight, ...svgRefs };
845
+ }
846
+
847
+ // src/notch/index.ts
848
+ var HL_PAD2 = 12;
849
+ function dims(ctx) {
850
+ const collW = ctx.dotSize, collH = ctx.dotSize;
851
+ const expW = ctx.contentSize.w + HL_PAD2 * 2;
852
+ const expH = Math.max(ctx.contentSize.h + HL_PAD2, ctx.dotSize);
853
+ const tW = ctx.snapshot.open ? expW : collW;
854
+ const tH = ctx.snapshot.open ? expH : collH;
855
+ const rW = Math.max(expW, collW);
856
+ const rH = Math.max(expH, collH);
857
+ return { collW, collH, expW, expH, tW, tH, rW, rH };
858
+ }
859
+ function blur(ctx) {
860
+ return Math.min(10, Math.max(6, ctx.roundness * 0.45));
861
+ }
862
+ function springCfg(ctx) {
863
+ return ctx.spring ?? FLUIX_SPRING;
864
+ }
865
+ function animateRect(ctx) {
866
+ const d = dims(ctx);
867
+ const toX = (d.rW - d.tW) / 2, toY = (d.rH - d.tH) / 2;
868
+ if (d.tW === ctx.prev.w && d.tH === ctx.prev.h) {
869
+ const el3 = ctx.dom.svgRectEl;
870
+ const rx = d.tW === d.collW && d.tH === d.collH ? d.collW / 2 : ctx.roundness;
871
+ for (const [k, v] of Object.entries({ x: toX, y: toY, width: d.tW, height: d.tH, rx, ry: rx })) el3.setAttribute(k, String(v));
872
+ return;
873
+ }
874
+ if (ctx.currentAnim) {
875
+ ctx.currentAnim.cancel();
876
+ ctx.currentAnim = null;
877
+ }
878
+ const fromW = ctx.prev.w, fromH = ctx.prev.h;
879
+ const fromX = (d.rW - fromW) / 2, fromY = (d.rH - fromH) / 2;
880
+ ctx.prev.w = d.tW;
881
+ ctx.prev.h = d.tH;
882
+ const wasCollapsed = fromW === d.collW && fromH === d.collH;
883
+ const isCollapsing = d.tW === d.collW && d.tH === d.collH;
884
+ const fromRx = wasCollapsed ? d.collW / 2 : ctx.roundness;
885
+ const toRx = isCollapsing ? d.collW / 2 : ctx.roundness;
886
+ const el2 = ctx.dom.svgRectEl;
887
+ const a = animateSpring(el2, {
888
+ width: { from: fromW, to: d.tW, unit: "px" },
889
+ height: { from: fromH, to: d.tH, unit: "px" },
890
+ x: { from: fromX, to: toX, unit: "px" },
891
+ y: { from: fromY, to: toY, unit: "px" },
892
+ rx: { from: fromRx, to: toRx, unit: "px" },
893
+ ry: { from: fromRx, to: toRx, unit: "px" }
894
+ }, springCfg(ctx));
895
+ const applyFinal = () => {
896
+ for (const [k, v] of Object.entries({ width: d.tW, height: d.tH, x: toX, y: toY, rx: toRx, ry: toRx })) el2.setAttribute(k, String(v));
897
+ };
898
+ if (a) {
899
+ ctx.currentAnim = a;
900
+ a.onfinish = () => {
901
+ ctx.currentAnim = null;
902
+ applyFinal();
903
+ };
904
+ } else applyFinal();
905
+ }
906
+ function updateLayout(ctx) {
907
+ const d = dims(ctx);
908
+ const isOpen = ctx.snapshot.open;
909
+ const newAttrs = getNotchAttrs({ open: isOpen, position: ctx.position, theme: ctx.theme });
910
+ applyAttrs(ctx.dom.rootEl, newAttrs.root);
911
+ ctx.dom.rootEl.setAttribute("aria-expanded", String(isOpen));
912
+ ctx.dom.rootEl.style.width = `${d.rW}px`;
913
+ ctx.dom.rootEl.style.height = `${d.rH}px`;
914
+ ctx.dom.svg.setAttribute("width", String(d.rW));
915
+ ctx.dom.svg.setAttribute("height", String(d.rH));
916
+ ctx.dom.svg.setAttribute("viewBox", `0 0 ${d.rW} ${d.rH}`);
917
+ ctx.dom.feBlur.setAttribute("stdDeviation", String(blur(ctx)));
918
+ const effectiveFill = ctx.fill ?? "var(--fluix-notch-bg)";
919
+ ctx.dom.svgRectEl.setAttribute("fill", effectiveFill);
920
+ ctx.dom.hoverBlobEl.setAttribute("fill", effectiveFill);
921
+ applyAttrs(ctx.dom.contentDiv, newAttrs.content);
922
+ ctx.dom.contentDiv.style.opacity = isOpen ? "1" : "0";
923
+ ctx.dom.contentDiv.style.pointerEvents = isOpen ? "auto" : "none";
924
+ ctx.dom.pillDiv.style.opacity = isOpen ? "0" : "1";
925
+ animateRect(ctx);
926
+ if (!isOpen) ctx.highlight.reset(d.rW, d.rH);
927
+ document.documentElement.style.setProperty("--fluix-notch-offset", `${d.rH}px`);
928
+ }
929
+ function createListeners(ctx) {
930
+ const handleOpen = () => {
931
+ ctx.controlledOpen === void 0 ? ctx.machine.open() : ctx.onOpenChange?.(true);
932
+ };
933
+ const handleClose = () => {
934
+ ctx.controlledOpen === void 0 ? ctx.machine.close() : ctx.onOpenChange?.(false);
935
+ };
936
+ const handleToggle = () => {
937
+ ctx.controlledOpen === void 0 ? ctx.machine.toggle() : ctx.onOpenChange?.(!ctx.snapshot.open);
938
+ };
939
+ return {
940
+ mouseenter: () => {
941
+ if (ctx.trigger === "hover") handleOpen();
942
+ },
943
+ mouseleave: () => {
944
+ if (ctx.trigger === "hover") {
945
+ handleClose();
946
+ ctx.highlight.reset(dims(ctx).rW, dims(ctx).rH);
947
+ return;
948
+ }
949
+ ctx.highlight.onItemLeave();
950
+ },
951
+ mouseover: (e) => {
952
+ ctx.highlight.onItemEnter(e, ctx.dom.rootEl, ctx.snapshot.open, ctx.roundness);
953
+ },
954
+ click: () => {
955
+ if (ctx.trigger === "click") handleToggle();
956
+ },
957
+ keydown: (e) => {
958
+ if (e.key === "Enter" || e.key === " ") {
959
+ e.preventDefault();
960
+ handleToggle();
961
+ } else if (e.key === "Escape" && ctx.snapshot.open) {
962
+ e.preventDefault();
963
+ handleClose();
964
+ }
965
+ }
966
+ };
967
+ }
968
+ function attachListeners(el2, ls) {
969
+ el2.addEventListener("mouseenter", ls.mouseenter);
970
+ el2.addEventListener("mouseleave", ls.mouseleave);
971
+ el2.addEventListener("mouseover", ls.mouseover);
972
+ el2.addEventListener("click", ls.click);
973
+ el2.addEventListener("keydown", ls.keydown);
974
+ }
975
+ function detachListeners(el2, ls) {
976
+ el2.removeEventListener("mouseenter", ls.mouseenter);
977
+ el2.removeEventListener("mouseleave", ls.mouseleave);
978
+ el2.removeEventListener("mouseover", ls.mouseover);
979
+ el2.removeEventListener("click", ls.click);
980
+ el2.removeEventListener("keydown", ls.keydown);
981
+ }
982
+ function notchUpdate(ctx, opts, listeners) {
983
+ if (opts.trigger !== void 0) ctx.trigger = opts.trigger;
984
+ if (opts.position !== void 0) ctx.position = opts.position;
985
+ if (opts.spring !== void 0) ctx.spring = opts.spring;
986
+ if (opts.dotSize !== void 0) ctx.dotSize = opts.dotSize;
987
+ if (opts.roundness !== void 0) ctx.roundness = opts.roundness;
988
+ if (opts.theme !== void 0) ctx.theme = opts.theme;
989
+ if (opts.fill !== void 0) ctx.fill = opts.fill;
990
+ if (opts.open !== void 0) ctx.controlledOpen = opts.open;
991
+ if (opts.onOpenChange !== void 0) ctx.onOpenChange = opts.onOpenChange;
992
+ if (opts.pill !== void 0) {
993
+ ctx.dom.pillDiv.textContent = "";
994
+ ctx.dom.pillDiv.appendChild(resolveContent(opts.pill));
995
+ }
996
+ if (opts.content !== void 0) {
997
+ ctx.dom.contentDiv.textContent = "";
998
+ ctx.dom.contentDiv.appendChild(resolveContent(opts.content));
999
+ ctx.dom.measureEl.textContent = "";
1000
+ ctx.dom.measureEl.appendChild(resolveContent(opts.content).cloneNode(true));
1001
+ }
1002
+ ctx.dom.pillDiv.style.width = `${ctx.dotSize}px`;
1003
+ ctx.dom.pillDiv.style.height = `${ctx.dotSize}px`;
1004
+ ctx.machine.configure({ position: ctx.position, trigger: ctx.trigger, roundness: ctx.roundness, fill: ctx.fill, spring: ctx.spring });
1005
+ if (ctx.controlledOpen !== void 0) {
1006
+ if (ctx.controlledOpen && !ctx.snapshot.open) ctx.machine.open();
1007
+ else if (!ctx.controlledOpen && ctx.snapshot.open) ctx.machine.close();
1008
+ }
1009
+ detachListeners(ctx.dom.rootEl, listeners);
1010
+ const newListeners = createListeners(ctx);
1011
+ Object.assign(listeners, newListeners);
1012
+ attachListeners(ctx.dom.rootEl, listeners);
1013
+ updateLayout(ctx);
1014
+ return listeners;
1015
+ }
1016
+ function createNotch(container, options) {
1017
+ const machine = createNotchMachine({
1018
+ position: options.position ?? "top-center",
1019
+ trigger: options.trigger ?? "click",
1020
+ roundness: options.roundness ?? NOTCH_DEFAULTS.roundness,
1021
+ fill: options.fill,
1022
+ spring: options.spring
1023
+ });
1024
+ const dotSize = options.dotSize ?? 36;
1025
+ const roundness = options.roundness ?? NOTCH_DEFAULTS.roundness;
1026
+ const spring = options.spring;
1027
+ const domCfg = {
1028
+ snapshot: machine.store.getSnapshot(),
1029
+ position: options.position ?? "top-center",
1030
+ theme: options.theme ?? "dark",
1031
+ dotSize,
1032
+ fill: options.fill,
1033
+ contentSize: { w: 200, h: 44 },
1034
+ roundness};
1035
+ const dom = buildNotchDOM(domCfg, options.pill, options.content, container, () => springCfg(ctx));
1036
+ const ctx = {
1037
+ trigger: options.trigger ?? "click",
1038
+ position: options.position ?? "top-center",
1039
+ spring,
1040
+ dotSize,
1041
+ roundness,
1042
+ theme: options.theme ?? "dark",
1043
+ fill: options.fill,
1044
+ controlledOpen: options.open,
1045
+ onOpenChange: options.onOpenChange,
1046
+ snapshot: machine.store.getSnapshot(),
1047
+ prevOpenVal: void 0,
1048
+ contentSize: { w: 200, h: 44 },
1049
+ currentAnim: null,
1050
+ measureRaf: 0,
1051
+ machine,
1052
+ prev: { w: dotSize, h: dotSize },
1053
+ highlight: dom.highlight,
1054
+ dom
1055
+ };
1056
+ let listeners = createListeners(ctx);
1057
+ attachListeners(ctx.dom.rootEl, listeners);
917
1058
  const measureObs = new ResizeObserver(() => {
918
- cancelAnimationFrame(measureRaf);
919
- measureRaf = requestAnimationFrame(() => {
920
- const r = measureEl.getBoundingClientRect();
1059
+ cancelAnimationFrame(ctx.measureRaf);
1060
+ ctx.measureRaf = requestAnimationFrame(() => {
1061
+ const r = ctx.dom.measureEl.getBoundingClientRect();
921
1062
  if (r.width > 0 && r.height > 0) {
922
1063
  const newSize = { w: Math.ceil(r.width), h: Math.ceil(r.height) };
923
- if (newSize.w !== contentSize.w || newSize.h !== contentSize.h) {
924
- contentSize = newSize;
925
- updateLayout();
1064
+ if (newSize.w !== ctx.contentSize.w || newSize.h !== ctx.contentSize.h) {
1065
+ ctx.contentSize = newSize;
1066
+ updateLayout(ctx);
926
1067
  }
927
1068
  }
928
1069
  });
929
1070
  });
930
- measureObs.observe(measureEl);
931
- function onItemEnter(e) {
932
- const target = e.target.closest("a, button");
933
- if (!target || !snapshot.open) return;
934
- const rootRect = rootEl.getBoundingClientRect();
935
- const itemW = target.offsetWidth;
936
- const itemH = target.offsetHeight;
937
- const itemRect = target.getBoundingClientRect();
938
- const itemCenterX = itemRect.left + itemRect.width / 2;
939
- const itemCenterY = itemRect.top + itemRect.height / 2;
940
- const padX = 8;
941
- const padY = 4;
942
- const blobOvershoot = Math.max(6, roundness * 0.35);
943
- const toW = itemW + padX * 2;
944
- const toH = Math.max(itemH + padY * 2, rootRect.height + blobOvershoot * 2);
945
- const toX = itemCenterX - rootRect.left - toW / 2;
946
- const toY = itemCenterY - rootRect.top - toH / 2;
947
- const toRx = toH / 2;
948
- const fromX = hlPrev.visible ? hlPrev.x : toX + toW / 2;
949
- const fromY = hlPrev.visible ? hlPrev.y : toY + toH / 2;
950
- const fromW = hlPrev.visible ? hlPrev.w : 0;
951
- const fromH = hlPrev.visible ? hlPrev.h : 0;
952
- const fromR = hlPrev.visible ? hlPrev.h / 2 : 0;
953
- if (highlightAnim) {
954
- highlightAnim.cancel();
955
- highlightAnim = null;
956
- }
957
- const sc = springConfig();
958
- const a = animateSpring(
959
- hoverBlobEl,
960
- {
961
- x: { from: fromX, to: toX, unit: "px" },
962
- y: { from: fromY, to: toY, unit: "px" },
963
- width: { from: fromW, to: toW, unit: "px" },
964
- height: { from: fromH, to: toH, unit: "px" },
965
- rx: { from: fromR, to: toRx, unit: "px" },
966
- ry: { from: fromR, to: toRx, unit: "px" }
967
- },
968
- { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 }
969
- );
970
- hlPrev.x = toX;
971
- hlPrev.y = toY;
972
- hlPrev.w = toW;
973
- hlPrev.h = toH;
974
- if (a) {
975
- highlightAnim = a;
976
- a.onfinish = () => {
977
- highlightAnim = null;
978
- hoverBlobEl.setAttribute("x", String(toX));
979
- hoverBlobEl.setAttribute("y", String(toY));
980
- hoverBlobEl.setAttribute("width", String(toW));
981
- hoverBlobEl.setAttribute("height", String(toH));
982
- hoverBlobEl.setAttribute("rx", String(toRx));
983
- hoverBlobEl.setAttribute("ry", String(toRx));
984
- hoverBlobEl.setAttribute("opacity", "1");
985
- };
986
- } else {
987
- hoverBlobEl.setAttribute("x", String(toX));
988
- hoverBlobEl.setAttribute("y", String(toY));
989
- hoverBlobEl.setAttribute("width", String(toW));
990
- hoverBlobEl.setAttribute("height", String(toH));
991
- hoverBlobEl.setAttribute("rx", String(toRx));
992
- hoverBlobEl.setAttribute("ry", String(toRx));
993
- hoverBlobEl.setAttribute("opacity", "1");
994
- }
995
- hoverBlobEl.setAttribute("opacity", "1");
996
- hlPrev.visible = true;
997
- }
998
- function resetHoverBlobImmediate() {
999
- if (highlightAnim) {
1000
- highlightAnim.cancel();
1001
- highlightAnim = null;
1002
- }
1003
- hoverBlobEl.setAttribute("x", String(rootW() / 2));
1004
- hoverBlobEl.setAttribute("y", String(rootH() / 2));
1005
- hoverBlobEl.setAttribute("width", "0");
1006
- hoverBlobEl.setAttribute("height", "0");
1007
- hoverBlobEl.setAttribute("rx", "0");
1008
- hoverBlobEl.setAttribute("ry", "0");
1009
- hoverBlobEl.setAttribute("opacity", "0");
1010
- hlPrev.visible = false;
1011
- }
1012
- function onItemLeave() {
1013
- if (!hlPrev.visible) return;
1014
- const cx = hlPrev.x + hlPrev.w / 2;
1015
- const cy = hlPrev.y + hlPrev.h / 2;
1016
- const sc = springConfig();
1017
- const a = animateSpring(
1018
- hoverBlobEl,
1019
- {
1020
- x: { from: hlPrev.x, to: cx, unit: "px" },
1021
- y: { from: hlPrev.y, to: cy, unit: "px" },
1022
- width: { from: hlPrev.w, to: 0, unit: "px" },
1023
- height: { from: hlPrev.h, to: 0, unit: "px" },
1024
- rx: { from: hlPrev.h / 2, to: 0, unit: "px" },
1025
- ry: { from: hlPrev.h / 2, to: 0, unit: "px" }
1026
- },
1027
- { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 }
1028
- );
1029
- if (a) {
1030
- highlightAnim = a;
1031
- a.onfinish = () => {
1032
- highlightAnim = null;
1033
- hoverBlobEl.setAttribute("x", String(cx));
1034
- hoverBlobEl.setAttribute("y", String(cy));
1035
- hoverBlobEl.setAttribute("width", "0");
1036
- hoverBlobEl.setAttribute("height", "0");
1037
- hoverBlobEl.setAttribute("rx", "0");
1038
- hoverBlobEl.setAttribute("ry", "0");
1039
- hoverBlobEl.setAttribute("opacity", "0");
1040
- };
1041
- } else {
1042
- hoverBlobEl.setAttribute("x", String(cx));
1043
- hoverBlobEl.setAttribute("y", String(cy));
1044
- hoverBlobEl.setAttribute("width", "0");
1045
- hoverBlobEl.setAttribute("height", "0");
1046
- hoverBlobEl.setAttribute("rx", "0");
1047
- hoverBlobEl.setAttribute("ry", "0");
1048
- hoverBlobEl.setAttribute("opacity", "0");
1049
- }
1050
- hlPrev.visible = false;
1051
- }
1052
- function handleOpen() {
1053
- if (controlledOpen === void 0) machine.open();
1054
- else onOpenChange?.(true);
1055
- }
1056
- function handleClose() {
1057
- if (controlledOpen === void 0) machine.close();
1058
- else onOpenChange?.(false);
1059
- }
1060
- function handleToggle() {
1061
- if (controlledOpen === void 0) machine.toggle();
1062
- else onOpenChange?.(!snapshot.open);
1063
- }
1064
- function onMouseEnter() {
1065
- if (trigger === "hover") handleOpen();
1066
- }
1067
- function onMouseLeave() {
1068
- if (trigger === "hover") {
1069
- handleClose();
1070
- resetHoverBlobImmediate();
1071
- return;
1072
- }
1073
- onItemLeave();
1074
- }
1075
- function onClick() {
1076
- if (trigger === "click") handleToggle();
1077
- }
1078
- rootEl.addEventListener("mouseenter", onMouseEnter);
1079
- rootEl.addEventListener("mouseleave", onMouseLeave);
1080
- rootEl.addEventListener("mouseover", onItemEnter);
1081
- rootEl.addEventListener("click", onClick);
1082
- function animateRect() {
1083
- const tw = targetW();
1084
- const th = targetH();
1085
- if (tw === prev.w && th === prev.h) return;
1086
- if (currentAnim) {
1087
- currentAnim.cancel();
1088
- currentAnim = null;
1089
- }
1090
- const rw = rootW();
1091
- const rh = rootH();
1092
- const fromW = prev.w;
1093
- const fromH = prev.h;
1094
- const fromX = (rw - fromW) / 2;
1095
- const fromY = (rh - fromH) / 2;
1096
- const toX = (rw - tw) / 2;
1097
- const toY = (rh - th) / 2;
1098
- prev.w = tw;
1099
- prev.h = th;
1100
- const isCollapsing = tw === collapsedW() && th === collapsedH();
1101
- const wasCollapsed = fromW === collapsedW() && fromH === collapsedH();
1102
- const fromRx = wasCollapsed ? collapsedW() / 2 : roundness;
1103
- const toRx = isCollapsing ? collapsedW() / 2 : roundness;
1104
- const a = animateSpring(
1105
- svgRectEl,
1106
- {
1107
- width: { from: fromW, to: tw, unit: "px" },
1108
- height: { from: fromH, to: th, unit: "px" },
1109
- x: { from: fromX, to: toX, unit: "px" },
1110
- y: { from: fromY, to: toY, unit: "px" },
1111
- rx: { from: fromRx, to: toRx, unit: "px" },
1112
- ry: { from: fromRx, to: toRx, unit: "px" }
1113
- },
1114
- springConfig()
1115
- );
1116
- if (a) {
1117
- currentAnim = a;
1118
- a.onfinish = () => {
1119
- currentAnim = null;
1120
- svgRectEl.setAttribute("width", String(tw));
1121
- svgRectEl.setAttribute("height", String(th));
1122
- svgRectEl.setAttribute("x", String(toX));
1123
- svgRectEl.setAttribute("y", String(toY));
1124
- svgRectEl.setAttribute("rx", String(toRx));
1125
- svgRectEl.setAttribute("ry", String(toRx));
1126
- };
1127
- } else {
1128
- svgRectEl.setAttribute("width", String(tw));
1129
- svgRectEl.setAttribute("height", String(th));
1130
- svgRectEl.setAttribute("x", String(toX));
1131
- svgRectEl.setAttribute("y", String(toY));
1132
- svgRectEl.setAttribute("rx", String(toRx));
1133
- svgRectEl.setAttribute("ry", String(toRx));
1134
- }
1135
- }
1136
- function updateLayout() {
1137
- const isOpen = snapshot.open;
1138
- const newAttrs = getNotchAttrs({ open: isOpen, position, theme });
1139
- applyAttrs2(rootEl, newAttrs.root);
1140
- rootEl.style.width = `${rootW()}px`;
1141
- rootEl.style.height = `${rootH()}px`;
1142
- svg.setAttribute("width", String(rootW()));
1143
- svg.setAttribute("height", String(rootH()));
1144
- svg.setAttribute("viewBox", `0 0 ${rootW()} ${rootH()}`);
1145
- feBlur.setAttribute("stdDeviation", String(blur()));
1146
- svgRectEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
1147
- hoverBlobEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
1148
- applyAttrs2(contentDiv, newAttrs.content);
1149
- animateRect();
1150
- if (!isOpen) {
1151
- resetHoverBlobImmediate();
1152
- }
1153
- document.documentElement.style.setProperty(
1154
- "--fluix-notch-offset",
1155
- `${rootH()}px`
1156
- );
1157
- }
1071
+ measureObs.observe(ctx.dom.measureEl);
1158
1072
  const unsubscribe = machine.store.subscribe(() => {
1159
- const next = machine.store.getSnapshot();
1160
- snapshot = next;
1161
- if (controlledOpen !== void 0) {
1162
- if (controlledOpen && !next.open) machine.open();
1163
- else if (!controlledOpen && next.open) machine.close();
1164
- }
1165
- if (prevOpenVal !== void 0 && prevOpenVal !== next.open) {
1166
- onOpenChange?.(next.open);
1073
+ ctx.snapshot = machine.store.getSnapshot();
1074
+ if (ctx.controlledOpen !== void 0) {
1075
+ if (ctx.controlledOpen && !ctx.snapshot.open) machine.open();
1076
+ else if (!ctx.controlledOpen && ctx.snapshot.open) machine.close();
1167
1077
  }
1168
- prevOpenVal = next.open;
1169
- updateLayout();
1078
+ if (ctx.prevOpenVal !== void 0 && ctx.prevOpenVal !== ctx.snapshot.open) ctx.onOpenChange?.(ctx.snapshot.open);
1079
+ ctx.prevOpenVal = ctx.snapshot.open;
1080
+ updateLayout(ctx);
1170
1081
  });
1171
- updateLayout();
1172
- document.documentElement.style.setProperty(
1173
- "--fluix-notch-offset",
1174
- `${rootH()}px`
1175
- );
1082
+ updateLayout(ctx);
1083
+ document.documentElement.style.setProperty("--fluix-notch-offset", `${dims(ctx).rH}px`);
1176
1084
  return {
1177
1085
  open() {
1178
- handleOpen();
1086
+ ctx.controlledOpen === void 0 ? machine.open() : ctx.onOpenChange?.(true);
1179
1087
  },
1180
1088
  close() {
1181
- handleClose();
1089
+ ctx.controlledOpen === void 0 ? machine.close() : ctx.onOpenChange?.(false);
1182
1090
  },
1183
1091
  toggle() {
1184
- handleToggle();
1092
+ ctx.controlledOpen === void 0 ? machine.toggle() : ctx.onOpenChange?.(!ctx.snapshot.open);
1185
1093
  },
1186
1094
  destroy() {
1187
1095
  unsubscribe();
1188
- cancelAnimationFrame(measureRaf);
1096
+ cancelAnimationFrame(ctx.measureRaf);
1189
1097
  measureObs.disconnect();
1190
- currentAnim?.cancel();
1191
- highlightAnim?.cancel();
1192
- rootEl.removeEventListener("mouseenter", onMouseEnter);
1193
- rootEl.removeEventListener("mouseleave", onMouseLeave);
1194
- rootEl.removeEventListener("mouseover", onItemEnter);
1195
- rootEl.removeEventListener("click", onClick);
1098
+ ctx.currentAnim?.cancel();
1099
+ ctx.highlight.cancelAnim();
1100
+ detachListeners(ctx.dom.rootEl, listeners);
1196
1101
  machine.destroy();
1197
- measureEl.remove();
1198
- rootEl.remove();
1102
+ ctx.dom.measureEl.remove();
1103
+ ctx.dom.rootEl.remove();
1199
1104
  document.documentElement.style.removeProperty("--fluix-notch-offset");
1200
1105
  },
1201
1106
  update(opts) {
1202
- if (opts.trigger !== void 0) trigger = opts.trigger;
1203
- if (opts.position !== void 0) position = opts.position;
1204
- if (opts.spring !== void 0) spring = opts.spring;
1205
- if (opts.dotSize !== void 0) dotSize = opts.dotSize;
1206
- if (opts.roundness !== void 0) roundness = opts.roundness;
1207
- if (opts.theme !== void 0) theme = opts.theme;
1208
- if (opts.fill !== void 0) fill = opts.fill;
1209
- if (opts.open !== void 0) controlledOpen = opts.open;
1210
- if (opts.onOpenChange !== void 0) onOpenChange = opts.onOpenChange;
1211
- if (opts.pill !== void 0) {
1212
- pillDiv.textContent = "";
1213
- pillDiv.appendChild(resolveContent(opts.pill));
1214
- }
1215
- if (opts.content !== void 0) {
1216
- contentDiv.textContent = "";
1217
- contentDiv.appendChild(resolveContent(opts.content));
1218
- measureEl.textContent = "";
1219
- measureEl.appendChild(resolveContent(opts.content).cloneNode(true));
1220
- }
1221
- pillDiv.style.width = `${dotSize}px`;
1222
- pillDiv.style.height = `${dotSize}px`;
1223
- machine.configure({ position, trigger, roundness, fill, spring });
1224
- if (controlledOpen !== void 0) {
1225
- if (controlledOpen && !snapshot.open) machine.open();
1226
- else if (!controlledOpen && snapshot.open) machine.close();
1227
- }
1228
- rootEl.removeEventListener("mouseenter", onMouseEnter);
1229
- rootEl.removeEventListener("mouseleave", onMouseLeave);
1230
- rootEl.removeEventListener("click", onClick);
1231
- rootEl.addEventListener("mouseenter", onMouseEnter);
1232
- rootEl.addEventListener("mouseleave", onMouseLeave);
1233
- rootEl.addEventListener("click", onClick);
1234
- updateLayout();
1107
+ listeners = notchUpdate(ctx, opts, listeners);
1235
1108
  }
1236
1109
  };
1237
1110
  }
1238
- var SVG_NS3 = "http://www.w3.org/2000/svg";
1239
- var GOO_MATRIX = "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10";
1240
- function applyAttrs3(el, attrs) {
1241
- for (const [key, value] of Object.entries(attrs)) {
1242
- el.setAttribute(key, value);
1111
+ function buildSvgIndicator(svg, isTab, filterId, resolvedBlur, fill, indicatorAttrs) {
1112
+ clearChildren(svg);
1113
+ const effectiveFill = fill ?? "var(--fluix-menu-indicator)";
1114
+ if (isTab) {
1115
+ const indicatorEl2 = document.createElementNS(SVG_NS, "path");
1116
+ applyAttrs(indicatorEl2, indicatorAttrs);
1117
+ indicatorEl2.setAttribute("d", "");
1118
+ indicatorEl2.setAttribute("opacity", "0");
1119
+ indicatorEl2.style.fill = effectiveFill;
1120
+ svg.appendChild(indicatorEl2);
1121
+ return { indicatorEl: indicatorEl2, ghostIndicatorEl: null };
1122
+ }
1123
+ const { g, defs } = createGooeyFilter(filterId, resolvedBlur);
1124
+ svg.appendChild(defs);
1125
+ const ghostIndicatorEl = document.createElementNS(SVG_NS, "rect");
1126
+ zeroRect(ghostIndicatorEl);
1127
+ ghostIndicatorEl.setAttribute("opacity", "0");
1128
+ ghostIndicatorEl.style.fill = effectiveFill;
1129
+ const indicatorEl = document.createElementNS(SVG_NS, "rect");
1130
+ applyAttrs(indicatorEl, indicatorAttrs);
1131
+ zeroRect(indicatorEl);
1132
+ indicatorEl.setAttribute("opacity", "0");
1133
+ indicatorEl.style.fill = effectiveFill;
1134
+ g.appendChild(ghostIndicatorEl);
1135
+ g.appendChild(indicatorEl);
1136
+ svg.appendChild(g);
1137
+ return { indicatorEl, ghostIndicatorEl };
1138
+ }
1139
+ function buildMenuDOM(attrs, filterId, variant, resolvedBlur, fill) {
1140
+ const navEl = document.createElement("nav");
1141
+ applyAttrs(navEl, attrs.root);
1142
+ navEl.setAttribute("aria-label", "Fluix menu");
1143
+ const canvasDiv = document.createElement("div");
1144
+ applyAttrs(canvasDiv, attrs.canvas);
1145
+ const svg = document.createElementNS(SVG_NS, "svg");
1146
+ svg.setAttribute("xmlns", SVG_NS);
1147
+ for (const [k, v] of Object.entries({ width: "1", height: "1", viewBox: "0 0 1 1", "aria-hidden": "true" })) svg.setAttribute(k, v);
1148
+ const { indicatorEl, ghostIndicatorEl } = buildSvgIndicator(svg, variant === "tab", filterId, resolvedBlur, fill, attrs.indicator);
1149
+ canvasDiv.appendChild(svg);
1150
+ navEl.appendChild(canvasDiv);
1151
+ const listDiv = document.createElement("div");
1152
+ applyAttrs(listDiv, attrs.list);
1153
+ navEl.appendChild(listDiv);
1154
+ return { navEl, svg, listDiv, buttonMap: /* @__PURE__ */ new Map(), indicatorEl, ghostIndicatorEl };
1155
+ }
1156
+ function createItemButton(ctx, item) {
1157
+ const btn = document.createElement("button");
1158
+ btn.type = "button";
1159
+ const active = ctx.snapshot.activeId === item.id;
1160
+ applyAttrs(btn, ctx.attrs.item({ id: item.id, active, disabled: item.disabled }));
1161
+ if (item.disabled) btn.disabled = true;
1162
+ btn.textContent = item.label;
1163
+ btn.addEventListener("click", () => {
1164
+ if (item.disabled) return;
1165
+ if (ctx.controlledActiveId === void 0) ctx.machine.setActive(item.id);
1166
+ else ctx.onActiveChange?.(item.id);
1167
+ });
1168
+ ctx.buttonMap.set(item.id, btn);
1169
+ ctx.listDiv.appendChild(btn);
1170
+ }
1171
+ function makeConnection(ctx) {
1172
+ return connectMenu({
1173
+ root: ctx.navEl,
1174
+ indicator: ctx.indicatorEl,
1175
+ ghostIndicator: ctx.ghostIndicatorEl,
1176
+ getActiveId: () => ctx.snapshot.activeId,
1177
+ onSelect(id) {
1178
+ if (ctx.controlledActiveId === void 0) ctx.machine.setActive(id);
1179
+ else ctx.onActiveChange?.(id);
1180
+ },
1181
+ spring: ctx.spring ?? FLUIX_SPRING,
1182
+ variant: ctx.variant,
1183
+ orientation: ctx.orientation
1184
+ });
1185
+ }
1186
+ function measure(ctx) {
1187
+ const rect = ctx.navEl.getBoundingClientRect();
1188
+ const w = Math.ceil(rect.width);
1189
+ const h = Math.ceil(rect.height);
1190
+ if (ctx.size.width !== w || ctx.size.height !== h) {
1191
+ ctx.size = { width: w, height: h };
1192
+ const sw = Math.max(1, w);
1193
+ const sh = Math.max(1, h);
1194
+ ctx.svg.setAttribute("width", String(sw));
1195
+ ctx.svg.setAttribute("height", String(sh));
1196
+ ctx.svg.setAttribute("viewBox", `0 0 ${sw} ${sh}`);
1197
+ ctx.connection?.sync(false);
1198
+ }
1199
+ }
1200
+ function menuUpdate(ctx, opts) {
1201
+ const prevVariant = ctx.variant;
1202
+ if (opts.orientation !== void 0) ctx.orientation = opts.orientation;
1203
+ if (opts.variant !== void 0) ctx.variant = opts.variant;
1204
+ if (opts.theme !== void 0) ctx.theme = opts.theme;
1205
+ if (opts.activeId !== void 0) ctx.controlledActiveId = opts.activeId;
1206
+ if (opts.onActiveChange !== void 0) ctx.onActiveChange = opts.onActiveChange;
1207
+ if (opts.spring !== void 0) ctx.spring = opts.spring;
1208
+ if (opts.roundness !== void 0) ctx.roundness = opts.roundness;
1209
+ if (opts.blur !== void 0) ctx.blurProp = opts.blur;
1210
+ if (opts.fill !== void 0) ctx.fill = opts.fill;
1211
+ ctx.machine.configure({ orientation: ctx.orientation, variant: ctx.variant, spring: ctx.spring, roundness: ctx.roundness, blur: ctx.blurProp, fill: ctx.fill });
1212
+ if (ctx.controlledActiveId !== void 0) ctx.machine.setActive(ctx.controlledActiveId ?? null);
1213
+ ctx.attrs = getMenuAttrs({ orientation: ctx.orientation, theme: ctx.theme, variant: ctx.variant });
1214
+ applyAttrs(ctx.navEl, ctx.attrs.root);
1215
+ if (prevVariant !== ctx.variant) {
1216
+ const resolvedBlur = ctx.blurProp ?? Math.min(10, Math.max(6, ctx.roundness * 0.45));
1217
+ const refs = buildSvgIndicator(ctx.svg, ctx.variant === "tab", ctx.filterId, resolvedBlur, ctx.fill, ctx.attrs.indicator);
1218
+ ctx.indicatorEl = refs.indicatorEl;
1219
+ ctx.ghostIndicatorEl = refs.ghostIndicatorEl;
1243
1220
  }
1221
+ if (opts.items !== void 0) {
1222
+ ctx.items = opts.items;
1223
+ clearChildren(ctx.listDiv);
1224
+ ctx.buttonMap.clear();
1225
+ for (const item of ctx.items) createItemButton(ctx, item);
1226
+ }
1227
+ ctx.connection.destroy();
1228
+ ctx.connection = makeConnection(ctx);
1229
+ requestAnimationFrame(() => {
1230
+ measure(ctx);
1231
+ ctx.connection.sync(false);
1232
+ });
1244
1233
  }
1245
1234
  function createMenu(container, options) {
1246
- let {
1235
+ const {
1247
1236
  orientation = MENU_DEFAULTS.orientation,
1248
1237
  variant = "pill",
1249
1238
  theme = "dark",
@@ -1255,8 +1244,6 @@ function createMenu(container, options) {
1255
1244
  fill,
1256
1245
  items
1257
1246
  } = options;
1258
- const springConfig = () => spring ?? FLUIX_SPRING;
1259
- const resolvedBlur = () => blurProp ?? Math.min(10, Math.max(6, roundness * 0.45));
1260
1247
  const machine = createMenuMachine({
1261
1248
  orientation,
1262
1249
  variant,
@@ -1266,217 +1253,69 @@ function createMenu(container, options) {
1266
1253
  fill,
1267
1254
  initialActiveId: controlledActiveId ?? null
1268
1255
  });
1269
- let snapshot = machine.store.getSnapshot();
1270
- let lastActiveNotified = snapshot.activeId;
1271
1256
  const attrs = getMenuAttrs({ orientation, theme, variant });
1272
1257
  const filterId = `fluix-menu-goo-${Math.random().toString(36).slice(2, 8)}`;
1273
- const isTab = variant === "tab";
1274
- const navEl = document.createElement("nav");
1275
- applyAttrs3(navEl, attrs.root);
1276
- navEl.setAttribute("aria-label", "Fluix menu");
1277
- const canvasDiv = document.createElement("div");
1278
- applyAttrs3(canvasDiv, attrs.canvas);
1279
- const svg = document.createElementNS(SVG_NS3, "svg");
1280
- svg.setAttribute("xmlns", SVG_NS3);
1281
- svg.setAttribute("width", "1");
1282
- svg.setAttribute("height", "1");
1283
- svg.setAttribute("viewBox", "0 0 1 1");
1284
- svg.setAttribute("aria-hidden", "true");
1285
- let indicatorEl;
1286
- if (isTab) {
1287
- indicatorEl = document.createElementNS(SVG_NS3, "path");
1288
- applyAttrs3(indicatorEl, attrs.indicator);
1289
- indicatorEl.setAttribute("d", "");
1290
- indicatorEl.setAttribute("opacity", "0");
1291
- indicatorEl.setAttribute("fill", fill ?? "var(--fluix-menu-indicator)");
1292
- svg.appendChild(indicatorEl);
1293
- } else {
1294
- const defs = document.createElementNS(SVG_NS3, "defs");
1295
- const filter = document.createElementNS(SVG_NS3, "filter");
1296
- filter.setAttribute("id", filterId);
1297
- filter.setAttribute("x", "-20%");
1298
- filter.setAttribute("y", "-20%");
1299
- filter.setAttribute("width", "140%");
1300
- filter.setAttribute("height", "140%");
1301
- filter.setAttribute("color-interpolation-filters", "sRGB");
1302
- const feBlur = document.createElementNS(SVG_NS3, "feGaussianBlur");
1303
- feBlur.setAttribute("in", "SourceGraphic");
1304
- feBlur.setAttribute("stdDeviation", String(resolvedBlur()));
1305
- feBlur.setAttribute("result", "blur");
1306
- const feCM = document.createElementNS(SVG_NS3, "feColorMatrix");
1307
- feCM.setAttribute("in", "blur");
1308
- feCM.setAttribute("type", "matrix");
1309
- feCM.setAttribute("values", GOO_MATRIX);
1310
- feCM.setAttribute("result", "goo");
1311
- const feComp = document.createElementNS(SVG_NS3, "feComposite");
1312
- feComp.setAttribute("in", "SourceGraphic");
1313
- feComp.setAttribute("in2", "goo");
1314
- feComp.setAttribute("operator", "atop");
1315
- filter.appendChild(feBlur);
1316
- filter.appendChild(feCM);
1317
- filter.appendChild(feComp);
1318
- defs.appendChild(filter);
1319
- svg.appendChild(defs);
1320
- const gGroup = document.createElementNS(SVG_NS3, "g");
1321
- gGroup.setAttribute("filter", `url(#${filterId})`);
1322
- indicatorEl = document.createElementNS(SVG_NS3, "rect");
1323
- applyAttrs3(indicatorEl, attrs.indicator);
1324
- indicatorEl.setAttribute("x", "0");
1325
- indicatorEl.setAttribute("y", "0");
1326
- indicatorEl.setAttribute("width", "0");
1327
- indicatorEl.setAttribute("height", "0");
1328
- indicatorEl.setAttribute("rx", "0");
1329
- indicatorEl.setAttribute("ry", "0");
1330
- indicatorEl.setAttribute("opacity", "0");
1331
- indicatorEl.setAttribute("fill", fill ?? "var(--fluix-menu-indicator)");
1332
- gGroup.appendChild(indicatorEl);
1333
- svg.appendChild(gGroup);
1334
- }
1335
- canvasDiv.appendChild(svg);
1336
- navEl.appendChild(canvasDiv);
1337
- const listDiv = document.createElement("div");
1338
- applyAttrs3(listDiv, attrs.list);
1339
- const buttonMap = /* @__PURE__ */ new Map();
1340
- function createItemButton(item) {
1341
- const btn = document.createElement("button");
1342
- btn.type = "button";
1343
- const active = snapshot.activeId === item.id;
1344
- const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
1345
- applyAttrs3(btn, itemAttrs);
1346
- if (item.disabled) btn.disabled = true;
1347
- btn.textContent = item.label;
1348
- btn.addEventListener("click", () => {
1349
- if (item.disabled) return;
1350
- if (controlledActiveId === void 0) {
1351
- machine.setActive(item.id);
1352
- } else {
1353
- onActiveChange?.(item.id);
1354
- }
1355
- });
1356
- buttonMap.set(item.id, btn);
1357
- listDiv.appendChild(btn);
1358
- }
1359
- for (const item of items) {
1360
- createItemButton(item);
1361
- }
1362
- navEl.appendChild(listDiv);
1363
- container.appendChild(navEl);
1364
- let size = { width: 0, height: 0 };
1365
- let measureRaf = 0;
1366
- const measure = () => {
1367
- const rect = navEl.getBoundingClientRect();
1368
- const w = Math.ceil(rect.width);
1369
- const h = Math.ceil(rect.height);
1370
- if (size.width !== w || size.height !== h) {
1371
- size = { width: w, height: h };
1372
- updateSvgSize();
1373
- connection?.sync(false);
1374
- }
1258
+ const resolvedBlur = blurProp ?? Math.min(10, Math.max(6, roundness * 0.45));
1259
+ const dom = buildMenuDOM(attrs, filterId, variant, resolvedBlur, fill);
1260
+ const ctx = {
1261
+ orientation,
1262
+ variant,
1263
+ theme,
1264
+ controlledActiveId,
1265
+ onActiveChange,
1266
+ spring,
1267
+ roundness,
1268
+ blurProp,
1269
+ fill,
1270
+ items,
1271
+ attrs,
1272
+ filterId,
1273
+ machine,
1274
+ snapshot: machine.store.getSnapshot(),
1275
+ lastActiveNotified: machine.store.getSnapshot().activeId,
1276
+ ...dom,
1277
+ connection: null,
1278
+ size: { width: 0, height: 0 },
1279
+ measureRaf: 0
1375
1280
  };
1281
+ for (const item of items) createItemButton(ctx, item);
1282
+ container.appendChild(ctx.navEl);
1376
1283
  const resizeObs = new ResizeObserver(() => {
1377
- cancelAnimationFrame(measureRaf);
1378
- measureRaf = requestAnimationFrame(measure);
1379
- });
1380
- resizeObs.observe(navEl);
1381
- function updateSvgSize() {
1382
- const w = Math.max(1, size.width);
1383
- const h = Math.max(1, size.height);
1384
- svg.setAttribute("width", String(w));
1385
- svg.setAttribute("height", String(h));
1386
- svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
1387
- }
1388
- let connection = connectMenu({
1389
- root: navEl,
1390
- indicator: indicatorEl,
1391
- getActiveId: () => snapshot.activeId,
1392
- onSelect(id) {
1393
- if (controlledActiveId === void 0) {
1394
- machine.setActive(id);
1395
- } else {
1396
- onActiveChange?.(id);
1397
- }
1398
- },
1399
- spring: springConfig(),
1400
- variant,
1401
- orientation
1284
+ cancelAnimationFrame(ctx.measureRaf);
1285
+ ctx.measureRaf = requestAnimationFrame(() => measure(ctx));
1402
1286
  });
1287
+ resizeObs.observe(ctx.navEl);
1288
+ ctx.connection = makeConnection(ctx);
1403
1289
  requestAnimationFrame(() => {
1404
- measure();
1405
- connection.sync(false);
1290
+ measure(ctx);
1291
+ ctx.connection.sync(false);
1406
1292
  });
1407
1293
  const unsubscribe = machine.store.subscribe(() => {
1408
- const next = machine.store.getSnapshot();
1409
- snapshot = next;
1410
- for (const item of items) {
1411
- const btn = buttonMap.get(item.id);
1412
- if (btn) {
1413
- const active = next.activeId === item.id;
1414
- const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
1415
- applyAttrs3(btn, itemAttrs);
1416
- }
1294
+ ctx.snapshot = machine.store.getSnapshot();
1295
+ for (const item of ctx.items) {
1296
+ const btn = ctx.buttonMap.get(item.id);
1297
+ if (btn) applyAttrs(btn, ctx.attrs.item({ id: item.id, active: ctx.snapshot.activeId === item.id, disabled: item.disabled }));
1417
1298
  }
1418
- if (next.activeId && lastActiveNotified !== next.activeId && onActiveChange) {
1419
- onActiveChange(next.activeId);
1299
+ if (ctx.snapshot.activeId && ctx.lastActiveNotified !== ctx.snapshot.activeId && ctx.onActiveChange) {
1300
+ ctx.onActiveChange(ctx.snapshot.activeId);
1420
1301
  }
1421
- lastActiveNotified = next.activeId;
1422
- connection.sync(false);
1302
+ ctx.lastActiveNotified = ctx.snapshot.activeId;
1303
+ ctx.connection.sync(false);
1423
1304
  });
1424
1305
  return {
1425
1306
  setActive(id) {
1426
1307
  machine.setActive(id);
1427
1308
  },
1428
1309
  update(opts) {
1429
- if (opts.orientation !== void 0) orientation = opts.orientation;
1430
- if (opts.variant !== void 0) variant = opts.variant;
1431
- if (opts.theme !== void 0) theme = opts.theme;
1432
- if (opts.activeId !== void 0) controlledActiveId = opts.activeId;
1433
- if (opts.onActiveChange !== void 0) onActiveChange = opts.onActiveChange;
1434
- if (opts.spring !== void 0) spring = opts.spring;
1435
- if (opts.roundness !== void 0) roundness = opts.roundness;
1436
- if (opts.blur !== void 0) blurProp = opts.blur;
1437
- if (opts.fill !== void 0) fill = opts.fill;
1438
- machine.configure({ orientation, variant, spring, roundness, blur: blurProp, fill });
1439
- if (controlledActiveId !== void 0) {
1440
- machine.setActive(controlledActiveId ?? null);
1441
- }
1442
- const newAttrs = getMenuAttrs({ orientation, theme, variant });
1443
- applyAttrs3(navEl, newAttrs.root);
1444
- if (opts.items !== void 0) {
1445
- items = opts.items;
1446
- listDiv.innerHTML = "";
1447
- buttonMap.clear();
1448
- for (const item of items) {
1449
- createItemButton(item);
1450
- }
1451
- }
1452
- connection.destroy();
1453
- connection = connectMenu({
1454
- root: navEl,
1455
- indicator: indicatorEl,
1456
- getActiveId: () => snapshot.activeId,
1457
- onSelect(id) {
1458
- if (controlledActiveId === void 0) {
1459
- machine.setActive(id);
1460
- } else {
1461
- onActiveChange?.(id);
1462
- }
1463
- },
1464
- spring: springConfig(),
1465
- variant,
1466
- orientation
1467
- });
1468
- requestAnimationFrame(() => {
1469
- measure();
1470
- connection.sync(false);
1471
- });
1310
+ menuUpdate(ctx, opts);
1472
1311
  },
1473
1312
  destroy() {
1474
1313
  unsubscribe();
1475
- cancelAnimationFrame(measureRaf);
1314
+ cancelAnimationFrame(ctx.measureRaf);
1476
1315
  resizeObs.disconnect();
1477
- connection.destroy();
1316
+ ctx.connection.destroy();
1478
1317
  machine.destroy();
1479
- navEl.remove();
1318
+ ctx.navEl.remove();
1480
1319
  }
1481
1320
  };
1482
1321
  }