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