@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.
@@ -68,7 +68,7 @@ var Fluix = (function (exports) {
68
68
  samples.push([t, 1]);
69
69
  return samples;
70
70
  }
71
- function animateSpring(el, properties, config) {
71
+ function animateSpring(el2, properties, config) {
72
72
  const samples = simulateSpring(config);
73
73
  const totalTime = samples[samples.length - 1][0];
74
74
  const durationMs = Math.round(totalTime * 1e3);
@@ -85,7 +85,7 @@ var Fluix = (function (exports) {
85
85
  keyframes.push(frame);
86
86
  }
87
87
  try {
88
- return el.animate(keyframes, {
88
+ return el2.animate(keyframes, {
89
89
  duration: durationMs,
90
90
  fill: "forwards",
91
91
  easing: "linear"
@@ -216,13 +216,17 @@ var Fluix = (function (exports) {
216
216
  });
217
217
  }
218
218
  function dismiss(id) {
219
- const { toasts } = store.getSnapshot();
220
- const item = toasts.find((t) => t.id === id);
221
- if (!item || item.exiting) return;
222
- store.update((prev) => ({
223
- ...prev,
224
- toasts: prev.toasts.map((t) => t.id === id ? { ...t, exiting: true } : t)
225
- }));
219
+ let shouldScheduleRemoval = false;
220
+ store.update((prev) => {
221
+ const item = prev.toasts.find((t) => t.id === id);
222
+ if (!item || item.exiting) return prev;
223
+ shouldScheduleRemoval = true;
224
+ return {
225
+ ...prev,
226
+ toasts: prev.toasts.map((t) => t.id === id ? { ...t, exiting: true } : t)
227
+ };
228
+ });
229
+ if (!shouldScheduleRemoval) return;
226
230
  const timer = setTimeout(() => {
227
231
  exitTimers.delete(id);
228
232
  store.update((prev) => ({
@@ -233,16 +237,20 @@ var Fluix = (function (exports) {
233
237
  exitTimers.set(id, timer);
234
238
  }
235
239
  function clear(position) {
236
- store.update((prev) => ({
237
- ...prev,
238
- toasts: position ? prev.toasts.filter((t) => t.position !== position) : []
239
- }));
240
+ store.update((prev) => {
241
+ if (prev.toasts.length === 0) return prev;
242
+ const filtered = position ? prev.toasts.filter((t) => t.position !== position) : [];
243
+ if (filtered.length === prev.toasts.length) return prev;
244
+ return { ...prev, toasts: filtered };
245
+ });
240
246
  }
241
247
  function configure(config) {
242
- store.update((prev) => ({
243
- ...prev,
244
- config: { ...prev.config, ...config }
245
- }));
248
+ store.update((prev) => {
249
+ const merged = { ...prev.config, ...config };
250
+ const keys = Object.keys(merged);
251
+ if (keys.every((k) => prev.config[k] === merged[k])) return prev;
252
+ return { ...prev, config: merged };
253
+ });
246
254
  }
247
255
  function destroy() {
248
256
  for (const timer of exitTimers.values()) clearTimeout(timer);
@@ -492,7 +500,12 @@ var Fluix = (function (exports) {
492
500
  });
493
501
  }
494
502
  function configure(config) {
495
- store.update((prev) => ({ ...prev, config: { ...prev.config, ...config } }));
503
+ store.update((prev) => {
504
+ const merged = { ...prev.config, ...config };
505
+ const keys = Object.keys(merged);
506
+ if (keys.every((k) => prev.config[k] === merged[k])) return prev;
507
+ return { ...prev, config: merged };
508
+ });
496
509
  }
497
510
  function destroy() {
498
511
  }
@@ -537,7 +550,12 @@ var Fluix = (function (exports) {
537
550
  });
538
551
  }
539
552
  function configure(config) {
540
- store.update((prev) => ({ ...prev, config: { ...prev.config, ...config } }));
553
+ store.update((prev) => {
554
+ const merged = { ...prev.config, ...config };
555
+ const keys = Object.keys(merged);
556
+ if (keys.every((k) => prev.config[k] === merged[k])) return prev;
557
+ return { ...prev, config: merged };
558
+ });
541
559
  }
542
560
  function destroy() {
543
561
  }
@@ -689,7 +707,11 @@ var Fluix = (function (exports) {
689
707
  if (!a || !b) return false;
690
708
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height && a.radius === b.radius && a.visible === b.visible;
691
709
  }
710
+ var springCache = /* @__PURE__ */ new Map();
692
711
  function simulateSpringValues(config) {
712
+ const key = `${config.stiffness}-${config.damping}-${config.mass}`;
713
+ const cached = springCache.get(key);
714
+ if (cached) return cached;
693
715
  const { stiffness, damping, mass } = config;
694
716
  const dt = 1 / 120;
695
717
  const maxDuration = 3;
@@ -709,6 +731,7 @@ var Fluix = (function (exports) {
709
731
  if (Math.abs(position - 1) < 1e-3 && Math.abs(velocity) < 1e-3) break;
710
732
  }
711
733
  samples.push(1);
734
+ springCache.set(key, samples);
712
735
  return samples;
713
736
  }
714
737
  function lerp(a, b, t) {
@@ -918,74 +941,146 @@ var Fluix = (function (exports) {
918
941
  requestAnimationFrame(tick);
919
942
  return handle;
920
943
  }
921
- var STRETCH_MS = 150;
922
- function animatePillMorph(rect, from, to, spring) {
923
- const stretchedX = Math.min(from.x, to.x);
924
- const stretchedRight = Math.max(from.x + from.width, to.x + to.width);
925
- const stretchedWidth = stretchedRight - stretchedX;
926
- const contractSamples = simulateSpringValues({
927
- stiffness: spring.stiffness * 2.5,
928
- damping: spring.damping * 1.6,
944
+ function setRectAttrs(el2, x, y, w, h, r) {
945
+ el2.setAttribute("x", String(x));
946
+ el2.setAttribute("y", String(y));
947
+ el2.setAttribute("width", String(Math.max(0, w)));
948
+ el2.setAttribute("height", String(Math.max(0, h)));
949
+ el2.setAttribute("rx", String(Math.max(0, r)));
950
+ el2.setAttribute("ry", String(Math.max(0, r)));
951
+ }
952
+ function animatePillMorph(rect, ghost, from, to, spring, _orientation) {
953
+ const growSamples = simulateSpringValues({
954
+ stiffness: spring.stiffness * 1.4,
955
+ damping: spring.damping * 1.2,
929
956
  mass: spring.mass
930
957
  });
931
- const contractCount = contractSamples.length;
932
- const contractMs = contractCount / 120 * 1e3;
933
- const totalMs = STRETCH_MS + contractMs;
958
+ const growCount = growSamples.length;
959
+ const growMs = growCount / 120 * 1e3;
960
+ const totalMs = growMs;
961
+ const ghostDuration = totalMs * 0.8;
934
962
  const startTime = performance.now();
935
963
  let cancelled = false;
936
964
  const handle = {
937
965
  onfinish: null,
938
966
  cancel() {
939
967
  cancelled = true;
968
+ if (ghost) ghost.setAttribute("opacity", "0");
940
969
  }
941
970
  };
942
- function applyRect(x, y, w, h, r) {
943
- rect.setAttribute("x", String(x));
944
- rect.setAttribute("y", String(y));
945
- rect.setAttribute("width", String(w));
946
- rect.setAttribute("height", String(h));
947
- rect.setAttribute("rx", String(r));
948
- rect.setAttribute("ry", String(r));
971
+ if (ghost) {
972
+ setRectAttrs(ghost, from.x, from.y, from.width, from.height, from.radius);
973
+ ghost.setAttribute("opacity", "1");
949
974
  }
975
+ const toCx = to.x + to.width / 2;
976
+ const toCy = to.y + to.height / 2;
977
+ setRectAttrs(rect, toCx, toCy, 0, 0, 0);
950
978
  function tick() {
951
979
  if (cancelled) return;
952
980
  const elapsed = performance.now() - startTime;
953
- if (elapsed < STRETCH_MS) {
954
- const t = easeOutCubic(elapsed / STRETCH_MS);
955
- applyRect(
956
- lerp(from.x, stretchedX, t),
957
- lerp(from.y, to.y, t),
958
- lerp(from.width, stretchedWidth, t),
959
- lerp(from.height, to.height, t),
960
- lerp(from.radius, to.radius, t)
961
- );
962
- } else {
963
- const phaseElapsed = elapsed - STRETCH_MS;
964
- const phaseProgress = Math.min(phaseElapsed / contractMs, 1);
965
- const idx = Math.min(Math.floor(phaseProgress * (contractCount - 1)), contractCount - 1);
966
- const t = contractSamples[idx];
967
- applyRect(
968
- lerp(stretchedX, to.x, t),
969
- to.y,
970
- lerp(stretchedWidth, to.width, t),
971
- to.height,
972
- to.radius
973
- );
981
+ const progress = Math.min(elapsed / totalMs, 1);
982
+ const idx = Math.min(Math.floor(progress * (growCount - 1)), growCount - 1);
983
+ const gt = growSamples[idx];
984
+ setRectAttrs(
985
+ rect,
986
+ lerp(toCx, to.x, gt),
987
+ lerp(toCy, to.y, gt),
988
+ lerp(0, to.width, gt),
989
+ lerp(0, to.height, gt),
990
+ lerp(0, to.radius, gt)
991
+ );
992
+ if (ghost) {
993
+ if (elapsed < ghostDuration) {
994
+ const raw = elapsed / ghostDuration;
995
+ const shrink = raw * raw;
996
+ const scale = 1 - shrink;
997
+ const drift = easeOutCubic(raw) * 0.25;
998
+ const cx = lerp(from.x + from.width / 2, to.x + to.width / 2, drift);
999
+ const cy = lerp(from.y + from.height / 2, to.y + to.height / 2, drift);
1000
+ const w = from.width * scale;
1001
+ const h = from.height * scale;
1002
+ setRectAttrs(
1003
+ ghost,
1004
+ cx - w / 2,
1005
+ cy - h / 2,
1006
+ w,
1007
+ h,
1008
+ from.radius * scale
1009
+ );
1010
+ } else {
1011
+ ghost.setAttribute("opacity", "0");
1012
+ }
974
1013
  }
975
- if (elapsed < totalMs) {
1014
+ if (progress < 1) {
976
1015
  requestAnimationFrame(tick);
977
1016
  } else {
1017
+ if (ghost) ghost.setAttribute("opacity", "0");
978
1018
  handle.onfinish?.();
979
1019
  }
980
1020
  }
981
1021
  requestAnimationFrame(tick);
982
1022
  return handle;
983
1023
  }
1024
+ function buildPillWaypoints(root, oldId, newId, padding, variant, orientation, from, to) {
1025
+ if (!oldId || !newId) return [from, to];
1026
+ const items = Array.from(root.querySelectorAll(ITEM_SELECTOR));
1027
+ const ids = items.map((el2) => el2.dataset["menuId"] ?? "");
1028
+ const oldIdx = ids.indexOf(oldId);
1029
+ const newIdx = ids.indexOf(newId);
1030
+ if (oldIdx === -1 || newIdx === -1 || Math.abs(newIdx - oldIdx) <= 1) {
1031
+ return [from, to];
1032
+ }
1033
+ const step = newIdx > oldIdx ? 1 : -1;
1034
+ const waypoints = [from];
1035
+ for (let i = oldIdx + step; i !== newIdx; i += step) {
1036
+ const id = ids[i];
1037
+ const frame = readItemFrame(root, id, padding, variant, orientation);
1038
+ if (frame) waypoints.push(frame);
1039
+ }
1040
+ waypoints.push(to);
1041
+ return waypoints;
1042
+ }
1043
+ function chainPillMorphs(rect, ghost, waypoints, spring, orientation) {
1044
+ if (waypoints.length <= 2) {
1045
+ return animatePillMorph(rect, ghost, waypoints[0], waypoints[waypoints.length - 1], spring);
1046
+ }
1047
+ const steps = waypoints.length - 1;
1048
+ let currentStep = 0;
1049
+ let activeAnim = null;
1050
+ const stepSpring = {
1051
+ stiffness: spring.stiffness * (1 + steps * 0.6),
1052
+ damping: spring.damping * (1 + steps * 0.3),
1053
+ mass: spring.mass
1054
+ };
1055
+ const handle = {
1056
+ onfinish: null,
1057
+ cancel() {
1058
+ activeAnim?.cancel();
1059
+ activeAnim = null;
1060
+ }
1061
+ };
1062
+ function runStep() {
1063
+ if (currentStep >= steps) {
1064
+ handle.onfinish?.();
1065
+ return;
1066
+ }
1067
+ const from = waypoints[currentStep];
1068
+ const to = waypoints[currentStep + 1];
1069
+ activeAnim = animatePillMorph(rect, ghost, from, to, stepSpring);
1070
+ activeAnim.onfinish = () => {
1071
+ currentStep++;
1072
+ setRectAttrs(rect, to.x, to.y, to.width, to.height, to.radius);
1073
+ runStep();
1074
+ };
1075
+ }
1076
+ runStep();
1077
+ return handle;
1078
+ }
984
1079
  function connectMenu(options) {
985
1080
  const spring = options.spring ?? FLUIX_SPRING;
986
- const padding = options.padding ?? 6;
987
1081
  const variant = options.variant;
988
1082
  const orientation = options.orientation;
1083
+ const padding = options.padding ?? (variant === "pill" ? 0 : 6);
989
1084
  const cleanups = [];
990
1085
  let currentAnimation = null;
991
1086
  let lastFrame = null;
@@ -997,11 +1092,11 @@ var Fluix = (function (exports) {
997
1092
  let animNewId = null;
998
1093
  let animPhase = null;
999
1094
  function setItemState(id, state) {
1000
- const el = options.root.querySelector(
1095
+ const el2 = options.root.querySelector(
1001
1096
  `${ITEM_SELECTOR}[data-menu-id="${CSS.escape(id)}"]`
1002
1097
  );
1003
- if (el && el.dataset["state"] !== state) {
1004
- el.dataset["state"] = state;
1098
+ if (el2 && el2.dataset["state"] !== state) {
1099
+ el2.dataset["state"] = state;
1005
1100
  }
1006
1101
  }
1007
1102
  function enforceAnimStates() {
@@ -1021,6 +1116,9 @@ var Fluix = (function (exports) {
1021
1116
  }
1022
1117
  function apply(frame) {
1023
1118
  applyFrame(options.indicator, frame, variant, orientation);
1119
+ if (options.ghostIndicator) {
1120
+ options.ghostIndicator.setAttribute("opacity", "0");
1121
+ }
1024
1122
  }
1025
1123
  const updateIndicator = (immediate = false) => {
1026
1124
  const activeId = options.getActiveId();
@@ -1133,11 +1231,23 @@ var Fluix = (function (exports) {
1133
1231
  onEnterStart
1134
1232
  );
1135
1233
  } else {
1136
- animation = animatePillMorph(
1137
- options.indicator,
1234
+ const waypoints = buildPillWaypoints(
1235
+ options.root,
1236
+ oldActiveId,
1237
+ newActiveId,
1238
+ padding,
1239
+ variant,
1240
+ orientation,
1138
1241
  from,
1139
- to,
1140
- { stiffness: spring.stiffness ?? 170, damping: spring.damping ?? 18, mass: spring.mass ?? 1 }
1242
+ to
1243
+ );
1244
+ const springConfig = { stiffness: spring.stiffness ?? 170, damping: spring.damping ?? 18, mass: spring.mass ?? 1 };
1245
+ const ghost = options.ghostIndicator ?? null;
1246
+ animation = chainPillMorphs(
1247
+ options.indicator,
1248
+ ghost,
1249
+ waypoints,
1250
+ springConfig
1141
1251
  );
1142
1252
  }
1143
1253
  if (!animation) {
@@ -1162,7 +1272,7 @@ var Fluix = (function (exports) {
1162
1272
  }
1163
1273
  };
1164
1274
  };
1165
- const sync = (immediate = false) => {
1275
+ const sync2 = (immediate = false) => {
1166
1276
  cancelAnimationFrame(rafId);
1167
1277
  rafId = requestAnimationFrame(() => updateIndicator(immediate));
1168
1278
  };
@@ -1180,7 +1290,7 @@ var Fluix = (function (exports) {
1180
1290
  cleanups.push(() => options.root.removeEventListener("click", handleClick));
1181
1291
  const scheduleSync = () => {
1182
1292
  if (animPhase) return;
1183
- sync(false);
1293
+ sync2(false);
1184
1294
  };
1185
1295
  resizeObserver = new ResizeObserver(scheduleSync);
1186
1296
  resizeObserver.observe(options.root);
@@ -1191,18 +1301,24 @@ var Fluix = (function (exports) {
1191
1301
  resizeObserver?.disconnect();
1192
1302
  resizeObserver = null;
1193
1303
  });
1194
- mutationObserver = new MutationObserver(() => {
1195
- if (animPhase) {
1196
- enforceAnimStates();
1197
- return;
1198
- }
1304
+ function rebuildResizeObserver() {
1199
1305
  if (!resizeObserver) return;
1200
1306
  resizeObserver.disconnect();
1201
1307
  resizeObserver.observe(options.root);
1202
1308
  for (const item of options.root.querySelectorAll(ITEM_SELECTOR)) {
1203
1309
  resizeObserver.observe(item);
1204
1310
  }
1205
- sync(false);
1311
+ }
1312
+ mutationObserver = new MutationObserver((mutations) => {
1313
+ if (animPhase) {
1314
+ enforceAnimStates();
1315
+ return;
1316
+ }
1317
+ const hasStructuralChange = mutations.some((m) => m.type === "childList");
1318
+ if (hasStructuralChange) {
1319
+ rebuildResizeObserver();
1320
+ }
1321
+ sync2(false);
1206
1322
  });
1207
1323
  mutationObserver.observe(options.root, {
1208
1324
  childList: true,
@@ -1214,9 +1330,9 @@ var Fluix = (function (exports) {
1214
1330
  mutationObserver?.disconnect();
1215
1331
  mutationObserver = null;
1216
1332
  });
1217
- sync(true);
1333
+ sync2(true);
1218
1334
  return {
1219
- sync,
1335
+ sync: sync2,
1220
1336
  destroy() {
1221
1337
  cancelAnimationFrame(rafId);
1222
1338
  if (currentAnimation) {
@@ -1229,125 +1345,99 @@ var Fluix = (function (exports) {
1229
1345
  };
1230
1346
  }
1231
1347
 
1232
- // src/toast.ts
1233
- var WIDTH = 350;
1234
- var HEIGHT = 40;
1235
- var PILL_PADDING = 10;
1236
- var MIN_EXPAND_RATIO = 2.25;
1237
- var HEADER_EXIT_MS = 600 * 0.7;
1238
- var BODY_MERGE_OVERLAP = 6;
1348
+ // src/shared.ts
1239
1349
  var SVG_NS = "http://www.w3.org/2000/svg";
1350
+ var GOO_MATRIX = "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10";
1351
+ function applyAttrs(el2, attrs) {
1352
+ for (const [key, value] of Object.entries(attrs)) {
1353
+ el2.setAttribute(key, value);
1354
+ }
1355
+ }
1356
+ function clearChildren(el2) {
1357
+ while (el2.firstChild) el2.removeChild(el2.firstChild);
1358
+ }
1359
+ function createGooeyFilter(filterId, blur2) {
1360
+ const defs = document.createElementNS(SVG_NS, "defs");
1361
+ const filter = document.createElementNS(SVG_NS, "filter");
1362
+ filter.setAttribute("id", filterId);
1363
+ for (const [k, v] of Object.entries({ x: "-50%", y: "-50%", width: "200%", height: "200%" })) {
1364
+ filter.setAttribute(k, v);
1365
+ }
1366
+ filter.setAttribute("color-interpolation-filters", "sRGB");
1367
+ const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
1368
+ feBlur.setAttribute("in", "SourceGraphic");
1369
+ feBlur.setAttribute("stdDeviation", String(blur2));
1370
+ feBlur.setAttribute("result", "blur");
1371
+ const feCM = document.createElementNS(SVG_NS, "feColorMatrix");
1372
+ feCM.setAttribute("in", "blur");
1373
+ feCM.setAttribute("type", "matrix");
1374
+ feCM.setAttribute("values", GOO_MATRIX);
1375
+ feCM.setAttribute("result", "goo");
1376
+ const feComp = document.createElementNS(SVG_NS, "feComposite");
1377
+ feComp.setAttribute("in", "SourceGraphic");
1378
+ feComp.setAttribute("in2", "goo");
1379
+ feComp.setAttribute("operator", "atop");
1380
+ filter.appendChild(feBlur);
1381
+ filter.appendChild(feCM);
1382
+ filter.appendChild(feComp);
1383
+ defs.appendChild(filter);
1384
+ const g = document.createElementNS(SVG_NS, "g");
1385
+ g.setAttribute("filter", `url(#${filterId})`);
1386
+ return { g, defs, feBlur };
1387
+ }
1388
+ function zeroRect(el2) {
1389
+ for (const attr of ["x", "y", "width", "height", "rx", "ry"]) {
1390
+ el2.setAttribute(attr, "0");
1391
+ }
1392
+ }
1393
+
1394
+ // src/toast/icons.ts
1395
+ function el(tag, attrs) {
1396
+ return { tag, attrs };
1397
+ }
1398
+ function line(x1, y1, x2, y2) {
1399
+ return el("line", { x1, y1, x2, y2 });
1400
+ }
1401
+ var ICON_DEFS = {
1402
+ success: { children: [el("polyline", { points: "20 6 9 17 4 12" })] },
1403
+ error: { children: [line("18", "6", "6", "18"), line("6", "6", "18", "18")] },
1404
+ warning: { children: [
1405
+ 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" }),
1406
+ line("12", "9", "12", "13"),
1407
+ line("12", "17", "12.01", "17")
1408
+ ] },
1409
+ info: { children: [
1410
+ el("circle", { cx: "12", cy: "12", r: "10" }),
1411
+ line("12", "16", "12", "12"),
1412
+ line("12", "8", "12.01", "8")
1413
+ ] },
1414
+ loading: { spin: true, children: [
1415
+ line("12", "2", "12", "6"),
1416
+ line("12", "18", "12", "22"),
1417
+ line("4.93", "4.93", "7.76", "7.76"),
1418
+ line("16.24", "16.24", "19.07", "19.07"),
1419
+ line("2", "12", "6", "12"),
1420
+ line("18", "12", "22", "12"),
1421
+ line("4.93", "19.07", "7.76", "16.24"),
1422
+ line("16.24", "7.76", "19.07", "4.93")
1423
+ ] },
1424
+ action: { children: [
1425
+ el("circle", { cx: "12", cy: "12", r: "10" }),
1426
+ el("polygon", { points: "10 8 16 12 10 16 10 8", fill: "currentColor", stroke: "none" })
1427
+ ] }
1428
+ };
1240
1429
  function createSvgIcon(state) {
1430
+ const def = ICON_DEFS[state];
1431
+ if (!def) return null;
1241
1432
  const svg = document.createElementNS(SVG_NS, "svg");
1242
- svg.setAttribute("width", "14");
1243
- svg.setAttribute("height", "14");
1244
- svg.setAttribute("viewBox", "0 0 24 24");
1245
- svg.setAttribute("fill", "none");
1246
- svg.setAttribute("stroke", "currentColor");
1247
- svg.setAttribute("stroke-width", "2.5");
1248
- svg.setAttribute("stroke-linecap", "round");
1249
- svg.setAttribute("stroke-linejoin", "round");
1250
- svg.setAttribute("aria-hidden", "true");
1251
- switch (state) {
1252
- case "success": {
1253
- const p = document.createElementNS(SVG_NS, "polyline");
1254
- p.setAttribute("points", "20 6 9 17 4 12");
1255
- svg.appendChild(p);
1256
- break;
1257
- }
1258
- case "error": {
1259
- const l1 = document.createElementNS(SVG_NS, "line");
1260
- l1.setAttribute("x1", "18");
1261
- l1.setAttribute("y1", "6");
1262
- l1.setAttribute("x2", "6");
1263
- l1.setAttribute("y2", "18");
1264
- const l2 = document.createElementNS(SVG_NS, "line");
1265
- l2.setAttribute("x1", "6");
1266
- l2.setAttribute("y1", "6");
1267
- l2.setAttribute("x2", "18");
1268
- l2.setAttribute("y2", "18");
1269
- svg.appendChild(l1);
1270
- svg.appendChild(l2);
1271
- break;
1272
- }
1273
- case "warning": {
1274
- const path = document.createElementNS(SVG_NS, "path");
1275
- path.setAttribute(
1276
- "d",
1277
- "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"
1278
- );
1279
- const l1 = document.createElementNS(SVG_NS, "line");
1280
- l1.setAttribute("x1", "12");
1281
- l1.setAttribute("y1", "9");
1282
- l1.setAttribute("x2", "12");
1283
- l1.setAttribute("y2", "13");
1284
- const l2 = document.createElementNS(SVG_NS, "line");
1285
- l2.setAttribute("x1", "12");
1286
- l2.setAttribute("y1", "17");
1287
- l2.setAttribute("x2", "12.01");
1288
- l2.setAttribute("y2", "17");
1289
- svg.appendChild(path);
1290
- svg.appendChild(l1);
1291
- svg.appendChild(l2);
1292
- break;
1293
- }
1294
- case "info": {
1295
- const c = document.createElementNS(SVG_NS, "circle");
1296
- c.setAttribute("cx", "12");
1297
- c.setAttribute("cy", "12");
1298
- c.setAttribute("r", "10");
1299
- const l1 = document.createElementNS(SVG_NS, "line");
1300
- l1.setAttribute("x1", "12");
1301
- l1.setAttribute("y1", "16");
1302
- l1.setAttribute("x2", "12");
1303
- l1.setAttribute("y2", "12");
1304
- const l2 = document.createElementNS(SVG_NS, "line");
1305
- l2.setAttribute("x1", "12");
1306
- l2.setAttribute("y1", "8");
1307
- l2.setAttribute("x2", "12.01");
1308
- l2.setAttribute("y2", "8");
1309
- svg.appendChild(c);
1310
- svg.appendChild(l1);
1311
- svg.appendChild(l2);
1312
- break;
1313
- }
1314
- case "loading": {
1315
- svg.setAttribute("data-fluix-icon", "spin");
1316
- const lines = [
1317
- ["12", "2", "12", "6"],
1318
- ["12", "18", "12", "22"],
1319
- ["4.93", "4.93", "7.76", "7.76"],
1320
- ["16.24", "16.24", "19.07", "19.07"],
1321
- ["2", "12", "6", "12"],
1322
- ["18", "12", "22", "12"],
1323
- ["4.93", "19.07", "7.76", "16.24"],
1324
- ["16.24", "7.76", "19.07", "4.93"]
1325
- ];
1326
- for (const [x1, y1, x2, y2] of lines) {
1327
- const l = document.createElementNS(SVG_NS, "line");
1328
- l.setAttribute("x1", x1);
1329
- l.setAttribute("y1", y1);
1330
- l.setAttribute("x2", x2);
1331
- l.setAttribute("y2", y2);
1332
- svg.appendChild(l);
1333
- }
1334
- break;
1335
- }
1336
- case "action": {
1337
- const c = document.createElementNS(SVG_NS, "circle");
1338
- c.setAttribute("cx", "12");
1339
- c.setAttribute("cy", "12");
1340
- c.setAttribute("r", "10");
1341
- const poly = document.createElementNS(SVG_NS, "polygon");
1342
- poly.setAttribute("points", "10 8 16 12 10 16 10 8");
1343
- poly.setAttribute("fill", "currentColor");
1344
- poly.setAttribute("stroke", "none");
1345
- svg.appendChild(c);
1346
- svg.appendChild(poly);
1347
- break;
1348
- }
1349
- default:
1350
- return null;
1433
+ 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" })) {
1434
+ svg.setAttribute(k, v);
1435
+ }
1436
+ if (def.spin) svg.setAttribute("data-fluix-icon", "spin");
1437
+ for (const child of def.children) {
1438
+ const node = document.createElementNS(SVG_NS, child.tag);
1439
+ for (const [k, v] of Object.entries(child.attrs)) node.setAttribute(k, v);
1440
+ svg.appendChild(node);
1351
1441
  }
1352
1442
  return svg;
1353
1443
  }
@@ -1367,77 +1457,27 @@ var Fluix = (function (exports) {
1367
1457
  const svgIcon = createSvgIcon(state);
1368
1458
  if (svgIcon) container.appendChild(svgIcon);
1369
1459
  }
1370
- function getPillAlign2(position) {
1371
- if (position.includes("right")) return "right";
1372
- if (position.includes("center")) return "center";
1373
- return "left";
1374
- }
1375
- function applyAttrs(el, attrs) {
1376
- for (const [key, value] of Object.entries(attrs)) {
1377
- el.setAttribute(key, value);
1378
- }
1379
- }
1380
- function resolveOffsetValue(v) {
1381
- return typeof v === "number" ? `${v}px` : v;
1382
- }
1383
- function applyViewportOffset(el, offset, position) {
1384
- el.style.top = "";
1385
- el.style.right = "";
1386
- el.style.bottom = "";
1387
- el.style.left = "";
1388
- el.style.paddingLeft = "";
1389
- el.style.paddingRight = "";
1390
- if (offset == null) return;
1391
- let top;
1392
- let right;
1393
- let bottom;
1394
- let left;
1395
- if (typeof offset === "number" || typeof offset === "string") {
1396
- const v = resolveOffsetValue(offset);
1397
- top = v;
1398
- right = v;
1399
- bottom = v;
1400
- left = v;
1401
- } else {
1402
- if (offset.top != null) top = resolveOffsetValue(offset.top);
1403
- if (offset.right != null) right = resolveOffsetValue(offset.right);
1404
- if (offset.bottom != null) bottom = resolveOffsetValue(offset.bottom);
1405
- if (offset.left != null) left = resolveOffsetValue(offset.left);
1406
- }
1407
- if (position.startsWith("top") && top) el.style.top = top;
1408
- if (position.startsWith("bottom") && bottom) el.style.bottom = bottom;
1409
- if (position.endsWith("right") && right) el.style.right = right;
1410
- if (position.endsWith("left") && left) el.style.left = left;
1411
- if (position.endsWith("center")) {
1412
- if (left) el.style.paddingLeft = left;
1413
- if (right) el.style.paddingRight = right;
1414
- }
1415
- }
1416
- function setTimer(inst, fn, ms) {
1417
- const id = setTimeout(() => {
1418
- inst.timers.delete(id);
1419
- fn();
1420
- }, ms);
1421
- inst.timers.add(id);
1422
- return id;
1423
- }
1460
+
1461
+ // src/toast/update.ts
1462
+ var PILL_PADDING = 10;
1463
+ var MIN_EXPAND_RATIO = 2.25;
1464
+ var HEADER_EXIT_MS = 600 * 0.7;
1465
+ var BODY_MERGE_OVERLAP = 6;
1424
1466
  function computeLayout(item, inst) {
1425
1467
  const { ready, expanded: isExpanded } = inst.localState;
1426
1468
  const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
1427
- const blur = Math.min(10, Math.max(6, roundness * 0.45));
1469
+ const blur2 = Math.min(10, Math.max(6, roundness * 0.45));
1428
1470
  const hasDesc = Boolean(item.description) || Boolean(item.button);
1429
1471
  const isLoading = item.state === "loading";
1430
1472
  const open = hasDesc && isExpanded && !isLoading;
1431
- const position = getPillAlign2(item.position);
1473
+ const position = getPillAlignLocal(item.position);
1432
1474
  const edge = item.position.startsWith("top") ? "bottom" : "top";
1433
1475
  const resolvedPillWidth = Math.max(inst.pillWidth || HEIGHT, HEIGHT);
1434
- const pillHeight = HEIGHT + blur * 3;
1476
+ const pillHeight = HEIGHT + blur2 * 3;
1435
1477
  const pillX = position === "right" ? WIDTH - resolvedPillWidth : position === "center" ? (WIDTH - resolvedPillWidth) / 2 : 0;
1436
1478
  const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
1437
1479
  const rawExpanded = hasDesc ? Math.max(minExpanded, HEIGHT + inst.contentHeight) : minExpanded;
1438
- if (open) {
1439
- inst.frozenExpanded = rawExpanded;
1440
- }
1480
+ if (open) inst.frozenExpanded = rawExpanded;
1441
1481
  const expanded = open ? rawExpanded : inst.frozenExpanded;
1442
1482
  const expandedContent = Math.max(0, expanded - HEIGHT);
1443
1483
  const svgHeight = hasDesc ? Math.max(expanded, minExpanded) : HEIGHT;
@@ -1445,7 +1485,7 @@ var Fluix = (function (exports) {
1445
1485
  ready,
1446
1486
  isExpanded,
1447
1487
  roundness,
1448
- blur,
1488
+ blur: blur2,
1449
1489
  hasDesc,
1450
1490
  isLoading,
1451
1491
  open,
@@ -1460,244 +1500,19 @@ var Fluix = (function (exports) {
1460
1500
  svgHeight
1461
1501
  };
1462
1502
  }
1463
- function createInstance(item, machine2) {
1464
- const localState = { ready: false, expanded: false };
1465
- const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
1466
- const blur = Math.min(10, Math.max(6, roundness * 0.45));
1467
- const filterId = `fluix-gooey-${item.id.replace(/[^a-z0-9-]/gi, "-")}`;
1468
- const hasDesc = Boolean(item.description) || Boolean(item.button);
1469
- const el = document.createElement("button");
1470
- el.type = "button";
1471
- const canvasDiv = document.createElement("div");
1472
- const minExpanded = HEIGHT * MIN_EXPAND_RATIO;
1473
- const initialSvgHeight = hasDesc ? minExpanded : HEIGHT;
1474
- const svg = document.createElementNS(SVG_NS, "svg");
1475
- svg.setAttribute("data-fluix-svg", "");
1476
- svg.setAttribute("width", String(WIDTH));
1477
- svg.setAttribute("height", String(initialSvgHeight));
1478
- svg.setAttribute("viewBox", `0 0 ${WIDTH} ${initialSvgHeight}`);
1479
- svg.setAttribute("aria-hidden", "true");
1480
- const defs = document.createElementNS(SVG_NS, "defs");
1481
- const filter = document.createElementNS(SVG_NS, "filter");
1482
- filter.setAttribute("id", filterId);
1483
- filter.setAttribute("x", "-20%");
1484
- filter.setAttribute("y", "-20%");
1485
- filter.setAttribute("width", "140%");
1486
- filter.setAttribute("height", "140%");
1487
- filter.setAttribute("color-interpolation-filters", "sRGB");
1488
- const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
1489
- feBlur.setAttribute("in", "SourceGraphic");
1490
- feBlur.setAttribute("stdDeviation", String(blur));
1491
- feBlur.setAttribute("result", "blur");
1492
- const feCM = document.createElementNS(SVG_NS, "feColorMatrix");
1493
- feCM.setAttribute("in", "blur");
1494
- feCM.setAttribute("type", "matrix");
1495
- feCM.setAttribute("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10");
1496
- feCM.setAttribute("result", "goo");
1497
- const feComp = document.createElementNS(SVG_NS, "feComposite");
1498
- feComp.setAttribute("in", "SourceGraphic");
1499
- feComp.setAttribute("in2", "goo");
1500
- feComp.setAttribute("operator", "atop");
1501
- filter.appendChild(feBlur);
1502
- filter.appendChild(feCM);
1503
- filter.appendChild(feComp);
1504
- defs.appendChild(filter);
1505
- svg.appendChild(defs);
1506
- const g = document.createElementNS(SVG_NS, "g");
1507
- g.setAttribute("filter", `url(#${filterId})`);
1508
- const initialPillX = getPillAlign2(item.position) === "right" ? WIDTH - HEIGHT : getPillAlign2(item.position) === "center" ? (WIDTH - HEIGHT) / 2 : 0;
1509
- const pillEl = document.createElementNS(SVG_NS, "rect");
1510
- pillEl.setAttribute("data-fluix-pill", "");
1511
- pillEl.setAttribute("x", String(initialPillX));
1512
- pillEl.setAttribute("y", "0");
1513
- pillEl.setAttribute("width", String(HEIGHT));
1514
- pillEl.setAttribute("height", String(HEIGHT));
1515
- pillEl.setAttribute("rx", String(roundness));
1516
- pillEl.setAttribute("ry", String(roundness));
1517
- pillEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
1518
- const bodyEl = document.createElementNS(SVG_NS, "rect");
1519
- bodyEl.setAttribute("data-fluix-body", "");
1520
- bodyEl.setAttribute("x", "0");
1521
- bodyEl.setAttribute("y", String(HEIGHT));
1522
- bodyEl.setAttribute("width", String(WIDTH));
1523
- bodyEl.setAttribute("height", "0");
1524
- bodyEl.setAttribute("rx", String(roundness));
1525
- bodyEl.setAttribute("ry", String(roundness));
1526
- bodyEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
1527
- bodyEl.setAttribute("opacity", "0");
1528
- g.appendChild(pillEl);
1529
- g.appendChild(bodyEl);
1530
- svg.appendChild(g);
1531
- canvasDiv.appendChild(svg);
1532
- el.appendChild(canvasDiv);
1533
- const headerEl = document.createElement("div");
1534
- const headerStackEl = document.createElement("div");
1535
- headerStackEl.setAttribute("data-fluix-header-stack", "");
1536
- const innerEl = document.createElement("div");
1537
- innerEl.setAttribute("data-fluix-header-inner", "");
1538
- innerEl.setAttribute("data-layer", "current");
1539
- const badgeEl = document.createElement("div");
1540
- renderIconInto(badgeEl, item.icon, item.state);
1541
- const titleEl = document.createElement("span");
1542
- titleEl.textContent = item.title ?? item.state;
1543
- innerEl.appendChild(badgeEl);
1544
- innerEl.appendChild(titleEl);
1545
- headerStackEl.appendChild(innerEl);
1546
- headerEl.appendChild(headerStackEl);
1547
- el.appendChild(headerEl);
1548
- let contentEl = null;
1549
- let descriptionEl = null;
1550
- if (hasDesc) {
1551
- contentEl = document.createElement("div");
1552
- descriptionEl = document.createElement("div");
1553
- if (item.styles?.description) {
1554
- descriptionEl.className = item.styles.description;
1555
- }
1556
- if (item.description != null) {
1557
- if (typeof item.description === "string") {
1558
- descriptionEl.textContent = item.description;
1559
- } else if (item.description instanceof HTMLElement) {
1560
- descriptionEl.appendChild(item.description);
1561
- }
1562
- }
1563
- if (item.button) {
1564
- const btn = document.createElement("button");
1565
- btn.type = "button";
1566
- btn.textContent = item.button.title;
1567
- if (item.styles?.button) {
1568
- btn.className = item.styles.button;
1569
- }
1570
- btn.addEventListener("click", (e) => {
1571
- e.stopPropagation();
1572
- item.button?.onClick();
1573
- });
1574
- descriptionEl.appendChild(btn);
1575
- }
1576
- contentEl.appendChild(descriptionEl);
1577
- el.appendChild(contentEl);
1578
- }
1579
- const attrs = Toaster.getAttrs(item, localState);
1580
- applyAttrs(el, attrs.root);
1581
- applyAttrs(canvasDiv, attrs.canvas);
1582
- applyAttrs(headerEl, attrs.header);
1583
- applyAttrs(badgeEl, attrs.badge);
1584
- if (item.styles?.badge) badgeEl.className = item.styles.badge;
1585
- applyAttrs(titleEl, attrs.title);
1586
- if (item.styles?.title) titleEl.className = item.styles.title;
1587
- if (contentEl) applyAttrs(contentEl, attrs.content);
1588
- if (descriptionEl) applyAttrs(descriptionEl, attrs.description);
1589
- if (item.button && descriptionEl) {
1590
- const btnEl = descriptionEl.querySelector("button");
1591
- if (btnEl) applyAttrs(btnEl, attrs.button);
1592
- }
1593
- const headerKey = `${item.state}-${item.title ?? item.state}`;
1594
- const inst = {
1595
- el,
1596
- pillEl,
1597
- bodyEl,
1598
- svgEl: svg,
1599
- headerEl,
1600
- innerEl,
1601
- headerStackEl,
1602
- contentEl,
1603
- descriptionEl,
1604
- localState,
1605
- pillWidth: 0,
1606
- contentHeight: 0,
1607
- frozenExpanded: HEIGHT * MIN_EXPAND_RATIO,
1608
- hovering: false,
1609
- pendingDismiss: false,
1610
- dismissRequested: false,
1611
- pillRo: null,
1612
- contentRo: null,
1613
- pillAnim: null,
1614
- prevPill: { x: initialPillX, width: HEIGHT, height: HEIGHT },
1615
- pillFirst: true,
1616
- headerKey,
1617
- headerPad: null,
1618
- connectHandle: null,
1619
- timers: /* @__PURE__ */ new Set(),
1620
- item
1621
- };
1622
- inst.pillRo = new ResizeObserver(() => {
1623
- requestAnimationFrame(() => {
1624
- measurePillWidth(inst);
1625
- applyVars(inst, inst.item);
1626
- animatePill(inst, inst.item);
1627
- });
1628
- });
1629
- inst.pillRo.observe(innerEl);
1630
- if (descriptionEl) {
1631
- inst.contentRo = new ResizeObserver(() => {
1632
- requestAnimationFrame(() => {
1633
- const h = descriptionEl.scrollHeight;
1634
- if (h !== inst.contentHeight) {
1635
- inst.contentHeight = h;
1636
- applyVars(inst, inst.item);
1637
- }
1638
- });
1639
- });
1640
- inst.contentRo.observe(descriptionEl);
1641
- }
1642
- inst.connectHandle = Toaster.connect(
1643
- el,
1644
- {
1645
- onExpand: () => {
1646
- if (inst.item.exiting || inst.dismissRequested) return;
1647
- inst.localState.expanded = true;
1648
- applyUpdate(inst, inst.item);
1649
- },
1650
- onCollapse: () => {
1651
- if (inst.item.exiting || inst.dismissRequested) return;
1652
- if (inst.item.autopilot !== false) return;
1653
- inst.localState.expanded = false;
1654
- applyUpdate(inst, inst.item);
1655
- },
1656
- onDismiss: () => {
1657
- if (inst.dismissRequested) return;
1658
- inst.dismissRequested = true;
1659
- machine2.dismiss(item.id);
1660
- },
1661
- onHoverStart: () => {
1662
- inst.hovering = true;
1663
- },
1664
- onHoverEnd: () => {
1665
- inst.hovering = false;
1666
- if (inst.pendingDismiss) {
1667
- inst.pendingDismiss = false;
1668
- if (inst.dismissRequested) return;
1669
- inst.dismissRequested = true;
1670
- machine2.dismiss(inst.item.id);
1671
- }
1672
- }
1673
- },
1674
- item
1675
- );
1676
- applyVars(inst, item);
1677
- setTimer(
1678
- inst,
1679
- () => {
1680
- inst.localState.ready = true;
1681
- applyUpdate(inst, inst.item);
1682
- setupAutopilot(inst, inst.item);
1683
- },
1684
- 32
1685
- );
1686
- setupAutoDismiss(inst, item, machine2);
1687
- measurePillWidth(inst);
1688
- return inst;
1689
- }
1690
- function measurePillWidth(inst) {
1691
- if (!inst.el.isConnected) return;
1692
- if (inst.headerPad === null) {
1693
- const cs = getComputedStyle(inst.headerEl);
1694
- inst.headerPad = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
1695
- }
1696
- const intrinsicWidth = inst.innerEl.getBoundingClientRect().width;
1697
- const w = intrinsicWidth + inst.headerPad + PILL_PADDING;
1698
- if (w > PILL_PADDING && w !== inst.pillWidth) {
1699
- inst.pillWidth = w;
1503
+ function getPillAlignLocal(position) {
1504
+ if (position.includes("right")) return "right";
1505
+ if (position.includes("center")) return "center";
1506
+ return "left";
1507
+ }
1508
+ function measurePillWidth(inst) {
1509
+ if (!inst.el.isConnected) return;
1510
+ if (inst.headerPad === null) {
1511
+ const cs = getComputedStyle(inst.headerEl);
1512
+ inst.headerPad = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
1700
1513
  }
1514
+ const w = inst.innerEl.getBoundingClientRect().width + inst.headerPad + PILL_PADDING;
1515
+ if (w > PILL_PADDING && w !== inst.pillWidth) inst.pillWidth = w;
1701
1516
  }
1702
1517
  function applyVars(inst, item) {
1703
1518
  const layout = computeLayout(item, inst);
@@ -1714,15 +1529,12 @@ var Fluix = (function (exports) {
1714
1529
  "--_bh": `${open ? expandedContent : 0}px`,
1715
1530
  "--_bo": `${open ? 1 : 0}`
1716
1531
  };
1717
- for (const [key, value] of Object.entries(vars)) {
1718
- inst.el.style.setProperty(key, value);
1719
- }
1532
+ for (const [key, value] of Object.entries(vars)) inst.el.style.setProperty(key, value);
1720
1533
  inst.svgEl.setAttribute("height", String(svgHeight));
1721
1534
  inst.svgEl.setAttribute("viewBox", `0 0 ${WIDTH} ${svgHeight}`);
1722
1535
  }
1723
1536
  function animatePill(inst, item) {
1724
- const layout = computeLayout(item, inst);
1725
- const { open, resolvedPillWidth, pillX, pillHeight } = layout;
1537
+ const { open, resolvedPillWidth, pillX, pillHeight } = computeLayout(item, inst);
1726
1538
  const prev = inst.prevPill;
1727
1539
  const next = { x: pillX, width: resolvedPillWidth, height: open ? pillHeight : HEIGHT };
1728
1540
  if (prev.x === next.x && prev.width === next.width && prev.height === next.height) return;
@@ -1735,88 +1547,59 @@ var Fluix = (function (exports) {
1735
1547
  inst.prevPill = next;
1736
1548
  return;
1737
1549
  }
1738
- const anim = animateSpring(
1739
- inst.pillEl,
1740
- {
1741
- x: { from: prev.x, to: next.x, unit: "px" },
1742
- width: { from: prev.width, to: next.width, unit: "px" },
1743
- height: { from: prev.height, to: next.height, unit: "px" }
1744
- },
1745
- FLUIX_SPRING
1746
- );
1747
- inst.pillAnim = anim;
1550
+ inst.pillAnim = animateSpring(inst.pillEl, {
1551
+ x: { from: prev.x, to: next.x, unit: "px" },
1552
+ width: { from: prev.width, to: next.width, unit: "px" },
1553
+ height: { from: prev.height, to: next.height, unit: "px" }
1554
+ }, FLUIX_SPRING);
1748
1555
  inst.prevPill = next;
1749
1556
  }
1557
+ function setTimer(inst, fn, ms) {
1558
+ const id = setTimeout(() => {
1559
+ inst.timers.delete(id);
1560
+ fn();
1561
+ }, ms);
1562
+ inst.timers.add(id);
1563
+ return id;
1564
+ }
1750
1565
  function setupAutoDismiss(inst, item, machine2) {
1751
1566
  const duration = item.duration;
1752
1567
  if (duration == null || duration <= 0) return;
1753
- setTimer(
1754
- inst,
1755
- () => {
1756
- if (inst.hovering) {
1757
- inst.pendingDismiss = true;
1758
- setTimer(
1759
- inst,
1760
- () => {
1761
- if (inst.dismissRequested) return;
1762
- inst.dismissRequested = true;
1763
- inst.pendingDismiss = false;
1764
- machine2.dismiss(item.id);
1765
- },
1766
- 1200
1767
- );
1768
- return;
1769
- }
1770
- inst.pendingDismiss = false;
1771
- inst.dismissRequested = true;
1772
- machine2.dismiss(item.id);
1773
- },
1774
- duration
1775
- );
1568
+ setTimer(inst, () => {
1569
+ if (inst.hovering) {
1570
+ inst.pendingDismiss = true;
1571
+ setTimer(inst, () => {
1572
+ if (inst.dismissRequested) return;
1573
+ inst.dismissRequested = true;
1574
+ inst.pendingDismiss = false;
1575
+ machine2.dismiss(item.id);
1576
+ }, 1200);
1577
+ return;
1578
+ }
1579
+ inst.pendingDismiss = false;
1580
+ inst.dismissRequested = true;
1581
+ machine2.dismiss(item.id);
1582
+ }, duration);
1776
1583
  }
1777
1584
  function setupAutopilot(inst, item, machine2) {
1778
1585
  if (item.autoExpandDelayMs != null && item.autoExpandDelayMs > 0) {
1779
- setTimer(
1780
- inst,
1781
- () => {
1782
- if (!inst.hovering) {
1783
- inst.localState.expanded = true;
1784
- applyUpdate(inst, inst.item);
1785
- }
1786
- },
1787
- item.autoExpandDelayMs
1788
- );
1586
+ setTimer(inst, () => {
1587
+ if (!inst.hovering) {
1588
+ inst.localState.expanded = true;
1589
+ applyUpdate(inst, inst.item);
1590
+ }
1591
+ }, item.autoExpandDelayMs);
1789
1592
  }
1790
1593
  if (item.autoCollapseDelayMs != null && item.autoCollapseDelayMs > 0) {
1791
- setTimer(
1792
- inst,
1793
- () => {
1794
- if (!inst.hovering) {
1795
- inst.localState.expanded = false;
1796
- applyUpdate(inst, inst.item);
1797
- }
1798
- },
1799
- item.autoCollapseDelayMs
1800
- );
1594
+ setTimer(inst, () => {
1595
+ if (!inst.hovering) {
1596
+ inst.localState.expanded = false;
1597
+ applyUpdate(inst, inst.item);
1598
+ }
1599
+ }, item.autoCollapseDelayMs);
1801
1600
  }
1802
1601
  }
1803
- function applyUpdate(inst, item, _machine) {
1804
- inst.item = item;
1805
- const attrs = Toaster.getAttrs(item, inst.localState);
1806
- applyAttrs(inst.el, attrs.root);
1807
- const canvasEl = inst.el.querySelector("[data-fluix-canvas]");
1808
- if (canvasEl) applyAttrs(canvasEl, attrs.canvas);
1809
- applyAttrs(inst.headerEl, attrs.header);
1810
- const badgeEl = inst.innerEl.querySelector("[data-fluix-badge]");
1811
- if (badgeEl) {
1812
- applyAttrs(badgeEl, attrs.badge);
1813
- if (item.styles?.badge) badgeEl.className = item.styles.badge;
1814
- }
1815
- const titleEl = inst.innerEl.querySelector("[data-fluix-title]");
1816
- if (titleEl) {
1817
- applyAttrs(titleEl, attrs.title);
1818
- if (item.styles?.title) titleEl.className = item.styles.title;
1819
- }
1602
+ function updateDescription(inst, item, attrs) {
1820
1603
  const hasDesc = Boolean(item.description) || Boolean(item.button);
1821
1604
  if (hasDesc && !inst.contentEl) {
1822
1605
  const contentEl = document.createElement("div");
@@ -1837,37 +1620,50 @@ var Fluix = (function (exports) {
1837
1620
  inst.contentRo.observe(descriptionEl);
1838
1621
  }
1839
1622
  if (inst.contentEl) applyAttrs(inst.contentEl, attrs.content);
1840
- if (inst.descriptionEl) {
1841
- applyAttrs(inst.descriptionEl, attrs.description);
1842
- if (item.styles?.description) {
1843
- inst.descriptionEl.className = item.styles.description;
1844
- }
1845
- const existingBtn = inst.descriptionEl.querySelector("[data-fluix-button]");
1846
- inst.descriptionEl.textContent = "";
1847
- if (item.description != null) {
1848
- if (typeof item.description === "string") {
1849
- inst.descriptionEl.textContent = item.description;
1850
- } else if (item.description instanceof HTMLElement) {
1851
- inst.descriptionEl.appendChild(item.description);
1852
- }
1853
- }
1854
- if (item.button) {
1855
- let btnEl = existingBtn;
1856
- if (!btnEl) {
1857
- btnEl = document.createElement("button");
1858
- btnEl.type = "button";
1859
- }
1860
- btnEl.textContent = item.button.title;
1861
- if (item.styles?.button) btnEl.className = item.styles.button;
1862
- applyAttrs(btnEl, attrs.button);
1863
- const newBtn = btnEl.cloneNode(true);
1864
- newBtn.addEventListener("click", (e) => {
1865
- e.stopPropagation();
1866
- item.button?.onClick();
1867
- });
1868
- inst.descriptionEl.appendChild(newBtn);
1869
- }
1623
+ if (!inst.descriptionEl) return;
1624
+ applyAttrs(inst.descriptionEl, attrs.description);
1625
+ if (item.styles?.description) inst.descriptionEl.className = item.styles.description;
1626
+ const existingBtn = inst.descriptionEl.querySelector("[data-fluix-button]");
1627
+ inst.descriptionEl.textContent = "";
1628
+ if (item.description != null) {
1629
+ if (typeof item.description === "string") inst.descriptionEl.textContent = item.description;
1630
+ else if (item.description instanceof HTMLElement) inst.descriptionEl.appendChild(item.description);
1631
+ }
1632
+ if (item.button) {
1633
+ let btnEl = existingBtn;
1634
+ if (!btnEl) {
1635
+ btnEl = document.createElement("button");
1636
+ btnEl.type = "button";
1637
+ }
1638
+ btnEl.textContent = item.button.title;
1639
+ if (item.styles?.button) btnEl.className = item.styles.button;
1640
+ applyAttrs(btnEl, attrs.button);
1641
+ const newBtn = btnEl.cloneNode(true);
1642
+ newBtn.addEventListener("click", (e) => {
1643
+ e.stopPropagation();
1644
+ item.button?.onClick();
1645
+ });
1646
+ inst.descriptionEl.appendChild(newBtn);
1870
1647
  }
1648
+ }
1649
+ function applyUpdate(inst, item, _machine) {
1650
+ inst.item = item;
1651
+ const attrs = Toaster.getAttrs(item, inst.localState);
1652
+ applyAttrs(inst.el, attrs.root);
1653
+ const canvasEl = inst.el.querySelector("[data-fluix-canvas]");
1654
+ if (canvasEl) applyAttrs(canvasEl, attrs.canvas);
1655
+ applyAttrs(inst.headerEl, attrs.header);
1656
+ const badgeEl = inst.innerEl.querySelector("[data-fluix-badge]");
1657
+ if (badgeEl) {
1658
+ applyAttrs(badgeEl, attrs.badge);
1659
+ if (item.styles?.badge) badgeEl.className = item.styles.badge;
1660
+ }
1661
+ const titleEl = inst.innerEl.querySelector("[data-fluix-title]");
1662
+ if (titleEl) {
1663
+ applyAttrs(titleEl, attrs.title);
1664
+ if (item.styles?.title) titleEl.className = item.styles.title;
1665
+ }
1666
+ updateDescription(inst, item, attrs);
1871
1667
  inst.pillEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
1872
1668
  inst.bodyEl.setAttribute("fill", item.fill ?? "var(--fluix-surface-contrast)");
1873
1669
  const newHeaderKey = `${item.state}-${item.title ?? item.state}`;
@@ -1899,13 +1695,9 @@ var Fluix = (function (exports) {
1899
1695
  inst.innerEl = newInner;
1900
1696
  inst.pillRo.unobserve(oldInner);
1901
1697
  inst.pillRo.observe(newInner);
1902
- setTimer(
1903
- inst,
1904
- () => {
1905
- oldInner.remove();
1906
- },
1907
- HEADER_EXIT_MS
1908
- );
1698
+ setTimer(inst, () => {
1699
+ oldInner.remove();
1700
+ }, HEADER_EXIT_MS);
1909
1701
  requestAnimationFrame(() => {
1910
1702
  measurePillWidth(inst);
1911
1703
  applyVars(inst, inst.item);
@@ -1921,560 +1713,874 @@ var Fluix = (function (exports) {
1921
1713
  inst.connectHandle.destroy();
1922
1714
  inst.el.remove();
1923
1715
  }
1924
- function createToaster(config) {
1925
- const machine2 = Toaster.getMachine();
1926
- let currentConfig = config;
1927
- if (currentConfig) machine2.configure(currentConfig);
1928
- const instances = /* @__PURE__ */ new Map();
1929
- const viewports = /* @__PURE__ */ new Map();
1930
- function ensureViewport(position, layout, offset) {
1931
- let vp = viewports.get(position);
1932
- if (!vp) {
1933
- vp = document.createElement("section");
1934
- const vpAttrs = Toaster.getViewportAttrs(position, layout);
1935
- applyAttrs(vp, vpAttrs);
1936
- applyViewportOffset(vp, offset, position);
1937
- document.body.appendChild(vp);
1938
- viewports.set(position, vp);
1939
- }
1940
- return vp;
1716
+
1717
+ // src/toast/instance.ts
1718
+ var WIDTH = 350;
1719
+ var HEIGHT = 40;
1720
+ var MIN_EXPAND_RATIO2 = 2.25;
1721
+ function getPillAlign2(position) {
1722
+ if (position.includes("right")) return "right";
1723
+ if (position.includes("center")) return "center";
1724
+ return "left";
1725
+ }
1726
+ function buildInstanceSvg(item, roundness, blur2) {
1727
+ const filterId = `fluix-gooey-${item.id.replace(/[^a-z0-9-]/gi, "-")}`;
1728
+ const hasDesc = Boolean(item.description) || Boolean(item.button);
1729
+ const initialSvgHeight = hasDesc ? HEIGHT * MIN_EXPAND_RATIO2 : HEIGHT;
1730
+ const svg = document.createElementNS(SVG_NS, "svg");
1731
+ svg.setAttribute("data-fluix-svg", "");
1732
+ svg.setAttribute("width", String(WIDTH));
1733
+ svg.setAttribute("height", String(initialSvgHeight));
1734
+ svg.setAttribute("viewBox", `0 0 ${WIDTH} ${initialSvgHeight}`);
1735
+ svg.setAttribute("aria-hidden", "true");
1736
+ const { g, defs } = createGooeyFilter(filterId, blur2);
1737
+ svg.appendChild(defs);
1738
+ const initialPillX = getPillAlign2(item.position) === "right" ? WIDTH - HEIGHT : getPillAlign2(item.position) === "center" ? (WIDTH - HEIGHT) / 2 : 0;
1739
+ const fill = item.fill ?? "var(--fluix-surface-contrast)";
1740
+ const pillEl = document.createElementNS(SVG_NS, "rect");
1741
+ pillEl.setAttribute("data-fluix-pill", "");
1742
+ for (const [k, v] of Object.entries({ x: initialPillX, y: 0, width: HEIGHT, height: HEIGHT, rx: roundness, ry: roundness })) {
1743
+ pillEl.setAttribute(k, String(v));
1941
1744
  }
1942
- function removeViewport(position) {
1943
- const vp = viewports.get(position);
1944
- if (vp) {
1945
- vp.remove();
1946
- viewports.delete(position);
1947
- }
1745
+ pillEl.setAttribute("fill", fill);
1746
+ const bodyEl = document.createElementNS(SVG_NS, "rect");
1747
+ bodyEl.setAttribute("data-fluix-body", "");
1748
+ for (const [k, v] of Object.entries({ x: 0, y: HEIGHT, width: WIDTH, height: 0, rx: roundness, ry: roundness })) {
1749
+ bodyEl.setAttribute(k, String(v));
1948
1750
  }
1949
- function sync() {
1950
- const next = machine2.store.getSnapshot();
1951
- const resolvedLayout = next.config?.layout ?? currentConfig?.layout ?? "stack";
1952
- const resolvedOffset = next.config?.offset ?? currentConfig?.offset;
1953
- const activePositions = /* @__PURE__ */ new Set();
1954
- const nextIds = new Set(next.toasts.map((t) => t.id));
1955
- for (const [id, inst] of instances) {
1956
- if (!nextIds.has(id)) {
1957
- destroyInstance(inst);
1958
- instances.delete(id);
1959
- }
1960
- }
1961
- for (const item of next.toasts) {
1962
- activePositions.add(item.position);
1963
- const vp = ensureViewport(item.position, resolvedLayout, resolvedOffset);
1964
- const existing = instances.get(item.id);
1965
- if (!existing) {
1966
- const inst = createInstance(item, machine2);
1967
- instances.set(item.id, inst);
1968
- vp.appendChild(inst.el);
1969
- } else if (existing.item.instanceId !== item.instanceId) {
1970
- destroyInstance(existing);
1971
- const inst = createInstance(item, machine2);
1972
- instances.set(item.id, inst);
1973
- vp.appendChild(inst.el);
1974
- } else {
1975
- applyUpdate(existing, item);
1976
- if (existing.el.parentElement !== vp) {
1977
- vp.appendChild(existing.el);
1751
+ bodyEl.setAttribute("fill", fill);
1752
+ bodyEl.setAttribute("opacity", "0");
1753
+ g.appendChild(pillEl);
1754
+ g.appendChild(bodyEl);
1755
+ svg.appendChild(g);
1756
+ return { svg, pillEl, bodyEl, initialPillX };
1757
+ }
1758
+ function buildInstanceContent(item) {
1759
+ let contentEl = null;
1760
+ let descriptionEl = null;
1761
+ const hasDesc = Boolean(item.description) || Boolean(item.button);
1762
+ if (!hasDesc) return { contentEl, descriptionEl };
1763
+ contentEl = document.createElement("div");
1764
+ descriptionEl = document.createElement("div");
1765
+ if (item.styles?.description) descriptionEl.className = item.styles.description;
1766
+ if (item.description != null) {
1767
+ if (typeof item.description === "string") descriptionEl.textContent = item.description;
1768
+ else if (item.description instanceof HTMLElement) descriptionEl.appendChild(item.description);
1769
+ }
1770
+ if (item.button) {
1771
+ const btn = document.createElement("button");
1772
+ btn.type = "button";
1773
+ btn.textContent = item.button.title;
1774
+ if (item.styles?.button) btn.className = item.styles.button;
1775
+ btn.addEventListener("click", (e) => {
1776
+ e.stopPropagation();
1777
+ item.button?.onClick();
1778
+ });
1779
+ descriptionEl.appendChild(btn);
1780
+ }
1781
+ contentEl.appendChild(descriptionEl);
1782
+ return { contentEl, descriptionEl };
1783
+ }
1784
+ function buildHeader(item) {
1785
+ const headerEl = document.createElement("div");
1786
+ const headerStackEl = document.createElement("div");
1787
+ headerStackEl.setAttribute("data-fluix-header-stack", "");
1788
+ const innerEl = document.createElement("div");
1789
+ innerEl.setAttribute("data-fluix-header-inner", "");
1790
+ innerEl.setAttribute("data-layer", "current");
1791
+ const badgeEl = document.createElement("div");
1792
+ renderIconInto(badgeEl, item.icon, item.state);
1793
+ const titleEl = document.createElement("span");
1794
+ titleEl.textContent = item.title ?? item.state;
1795
+ innerEl.appendChild(badgeEl);
1796
+ innerEl.appendChild(titleEl);
1797
+ headerStackEl.appendChild(innerEl);
1798
+ headerEl.appendChild(headerStackEl);
1799
+ return { headerEl, headerStackEl, innerEl, badgeEl, titleEl };
1800
+ }
1801
+ function createInstance(item, machine2) {
1802
+ const localState = { ready: false, expanded: false };
1803
+ const roundness = item.roundness ?? TOAST_DEFAULTS.roundness;
1804
+ const blur2 = Math.min(10, Math.max(6, roundness * 0.45));
1805
+ const el2 = document.createElement("button");
1806
+ el2.type = "button";
1807
+ const canvasDiv = document.createElement("div");
1808
+ const { svg, pillEl, bodyEl, initialPillX } = buildInstanceSvg(item, roundness, blur2);
1809
+ canvasDiv.appendChild(svg);
1810
+ el2.appendChild(canvasDiv);
1811
+ const { headerEl, headerStackEl, innerEl, badgeEl, titleEl } = buildHeader(item);
1812
+ el2.appendChild(headerEl);
1813
+ const { contentEl, descriptionEl } = buildInstanceContent(item);
1814
+ if (contentEl) el2.appendChild(contentEl);
1815
+ const attrs = Toaster.getAttrs(item, localState);
1816
+ applyAttrs(el2, attrs.root);
1817
+ applyAttrs(canvasDiv, attrs.canvas);
1818
+ applyAttrs(headerEl, attrs.header);
1819
+ applyAttrs(badgeEl, attrs.badge);
1820
+ if (item.styles?.badge) badgeEl.className = item.styles.badge;
1821
+ applyAttrs(titleEl, attrs.title);
1822
+ if (item.styles?.title) titleEl.className = item.styles.title;
1823
+ if (contentEl) applyAttrs(contentEl, attrs.content);
1824
+ if (descriptionEl) applyAttrs(descriptionEl, attrs.description);
1825
+ if (item.button && descriptionEl) {
1826
+ const btnEl = descriptionEl.querySelector("button");
1827
+ if (btnEl) applyAttrs(btnEl, attrs.button);
1828
+ }
1829
+ const inst = {
1830
+ el: el2,
1831
+ pillEl,
1832
+ bodyEl,
1833
+ svgEl: svg,
1834
+ headerEl,
1835
+ innerEl,
1836
+ headerStackEl,
1837
+ contentEl,
1838
+ descriptionEl,
1839
+ localState,
1840
+ pillWidth: 0,
1841
+ contentHeight: 0,
1842
+ frozenExpanded: HEIGHT * MIN_EXPAND_RATIO2,
1843
+ hovering: false,
1844
+ pendingDismiss: false,
1845
+ dismissRequested: false,
1846
+ pillRo: null,
1847
+ contentRo: null,
1848
+ pillAnim: null,
1849
+ prevPill: { x: initialPillX, width: HEIGHT, height: HEIGHT },
1850
+ pillFirst: true,
1851
+ headerKey: `${item.state}-${item.title ?? item.state}`,
1852
+ headerPad: null,
1853
+ connectHandle: null,
1854
+ timers: /* @__PURE__ */ new Set(),
1855
+ item
1856
+ };
1857
+ wireObservers(inst);
1858
+ wireConnect(inst, item, machine2);
1859
+ applyVars(inst, item);
1860
+ setTimer(inst, () => {
1861
+ inst.localState.ready = true;
1862
+ applyUpdate(inst, inst.item);
1863
+ setupAutopilot(inst, inst.item);
1864
+ }, 32);
1865
+ setupAutoDismiss(inst, item, machine2);
1866
+ measurePillWidth(inst);
1867
+ return inst;
1868
+ }
1869
+ function wireObservers(inst, _item) {
1870
+ inst.pillRo = new ResizeObserver(() => {
1871
+ requestAnimationFrame(() => {
1872
+ measurePillWidth(inst);
1873
+ applyVars(inst, inst.item);
1874
+ });
1875
+ });
1876
+ inst.pillRo.observe(inst.innerEl);
1877
+ if (inst.descriptionEl) {
1878
+ const descEl = inst.descriptionEl;
1879
+ inst.contentRo = new ResizeObserver(() => {
1880
+ requestAnimationFrame(() => {
1881
+ const h = descEl.scrollHeight;
1882
+ if (h !== inst.contentHeight) {
1883
+ inst.contentHeight = h;
1884
+ applyVars(inst, inst.item);
1978
1885
  }
1886
+ });
1887
+ });
1888
+ inst.contentRo.observe(descEl);
1889
+ }
1890
+ }
1891
+ function wireConnect(inst, item, machine2) {
1892
+ inst.connectHandle = Toaster.connect(inst.el, {
1893
+ onExpand: () => {
1894
+ if (inst.item.exiting || inst.dismissRequested) return;
1895
+ inst.localState.expanded = true;
1896
+ applyUpdate(inst, inst.item);
1897
+ },
1898
+ onCollapse: () => {
1899
+ if (inst.item.exiting || inst.dismissRequested) return;
1900
+ if (inst.item.autopilot !== false) return;
1901
+ inst.localState.expanded = false;
1902
+ applyUpdate(inst, inst.item);
1903
+ },
1904
+ onDismiss: () => {
1905
+ if (inst.dismissRequested) return;
1906
+ inst.dismissRequested = true;
1907
+ machine2.dismiss(item.id);
1908
+ },
1909
+ onHoverStart: () => {
1910
+ inst.hovering = true;
1911
+ },
1912
+ onHoverEnd: () => {
1913
+ inst.hovering = false;
1914
+ if (inst.pendingDismiss) {
1915
+ inst.pendingDismiss = false;
1916
+ if (inst.dismissRequested) return;
1917
+ inst.dismissRequested = true;
1918
+ machine2.dismiss(inst.item.id);
1979
1919
  }
1980
1920
  }
1981
- for (const [position, vp] of viewports) {
1982
- const vpAttrs = Toaster.getViewportAttrs(position, resolvedLayout);
1983
- applyAttrs(vp, vpAttrs);
1984
- applyViewportOffset(vp, resolvedOffset, position);
1921
+ }, item);
1922
+ }
1923
+
1924
+ // src/toast/index.ts
1925
+ function resolveOffsetValue(v) {
1926
+ return typeof v === "number" ? `${v}px` : v;
1927
+ }
1928
+ function applyViewportOffset(el2, offset, position) {
1929
+ el2.style.top = "";
1930
+ el2.style.right = "";
1931
+ el2.style.bottom = "";
1932
+ el2.style.left = "";
1933
+ el2.style.paddingLeft = "";
1934
+ el2.style.paddingRight = "";
1935
+ if (offset == null) return;
1936
+ let top, right, bottom, left;
1937
+ if (typeof offset === "number" || typeof offset === "string") {
1938
+ const v = resolveOffsetValue(offset);
1939
+ top = v;
1940
+ right = v;
1941
+ bottom = v;
1942
+ left = v;
1943
+ } else {
1944
+ if (offset.top != null) top = resolveOffsetValue(offset.top);
1945
+ if (offset.right != null) right = resolveOffsetValue(offset.right);
1946
+ if (offset.bottom != null) bottom = resolveOffsetValue(offset.bottom);
1947
+ if (offset.left != null) left = resolveOffsetValue(offset.left);
1948
+ }
1949
+ if (position.startsWith("top") && top) el2.style.top = top;
1950
+ if (position.startsWith("bottom") && bottom) el2.style.bottom = bottom;
1951
+ if (position.endsWith("right") && right) el2.style.right = right;
1952
+ if (position.endsWith("left") && left) el2.style.left = left;
1953
+ if (position.endsWith("center")) {
1954
+ if (left) el2.style.paddingLeft = left;
1955
+ if (right) el2.style.paddingRight = right;
1956
+ }
1957
+ }
1958
+ function ensureViewport(state, position, layout, offset) {
1959
+ let vp = state.viewports.get(position);
1960
+ if (!vp) {
1961
+ vp = document.createElement("section");
1962
+ applyAttrs(vp, Toaster.getViewportAttrs(position, layout));
1963
+ applyViewportOffset(vp, offset, position);
1964
+ document.body.appendChild(vp);
1965
+ state.viewports.set(position, vp);
1966
+ }
1967
+ return vp;
1968
+ }
1969
+ function sync(state) {
1970
+ const next = state.machine.store.getSnapshot();
1971
+ const layout = next.config?.layout ?? state.currentConfig?.layout ?? "stack";
1972
+ const offset = next.config?.offset ?? state.currentConfig?.offset;
1973
+ const activePositions = /* @__PURE__ */ new Set();
1974
+ const nextIds = new Set(next.toasts.map((t) => t.id));
1975
+ for (const [id, inst] of state.instances) {
1976
+ if (!nextIds.has(id)) {
1977
+ destroyInstance(inst);
1978
+ state.instances.delete(id);
1979
+ }
1980
+ }
1981
+ for (const item of next.toasts) {
1982
+ activePositions.add(item.position);
1983
+ const vp = ensureViewport(state, item.position, layout, offset);
1984
+ const existing = state.instances.get(item.id);
1985
+ if (!existing) {
1986
+ const inst = createInstance(item, state.machine);
1987
+ state.instances.set(item.id, inst);
1988
+ vp.appendChild(inst.el);
1989
+ } else if (existing.item.instanceId !== item.instanceId) {
1990
+ destroyInstance(existing);
1991
+ const inst = createInstance(item, state.machine);
1992
+ state.instances.set(item.id, inst);
1993
+ vp.appendChild(inst.el);
1994
+ } else {
1995
+ applyUpdate(existing, item);
1996
+ if (existing.el.parentElement !== vp) vp.appendChild(existing.el);
1985
1997
  }
1986
- for (const [position] of viewports) {
1987
- if (!activePositions.has(position)) {
1988
- removeViewport(position);
1989
- }
1998
+ }
1999
+ for (const [position, vp] of state.viewports) {
2000
+ applyAttrs(vp, Toaster.getViewportAttrs(position, layout));
2001
+ applyViewportOffset(vp, offset, position);
2002
+ }
2003
+ for (const [position] of state.viewports) {
2004
+ if (!activePositions.has(position)) {
2005
+ state.viewports.get(position)?.remove();
2006
+ state.viewports.delete(position);
1990
2007
  }
1991
2008
  }
1992
- sync();
1993
- const unsubscribe = machine2.store.subscribe(sync);
2009
+ }
2010
+ function createToaster(config) {
2011
+ const state = {
2012
+ machine: Toaster.getMachine(),
2013
+ instances: /* @__PURE__ */ new Map(),
2014
+ viewports: /* @__PURE__ */ new Map(),
2015
+ currentConfig: config
2016
+ };
2017
+ if (config) state.machine.configure(config);
2018
+ sync(state);
2019
+ const unsubscribe = state.machine.store.subscribe(() => sync(state));
1994
2020
  return {
1995
2021
  destroy() {
1996
2022
  unsubscribe();
1997
- for (const inst of instances.values()) {
1998
- destroyInstance(inst);
1999
- }
2000
- instances.clear();
2001
- for (const vp of viewports.values()) {
2002
- vp.remove();
2003
- }
2004
- viewports.clear();
2023
+ for (const inst of state.instances.values()) destroyInstance(inst);
2024
+ state.instances.clear();
2025
+ for (const vp of state.viewports.values()) vp.remove();
2026
+ state.viewports.clear();
2005
2027
  },
2006
2028
  update(newConfig) {
2007
- currentConfig = newConfig;
2008
- machine2.configure(newConfig);
2029
+ state.currentConfig = newConfig;
2030
+ state.machine.configure(newConfig);
2009
2031
  }
2010
2032
  };
2011
2033
  }
2012
2034
 
2013
- // src/notch.ts
2014
- var SVG_NS2 = "http://www.w3.org/2000/svg";
2015
- function applyAttrs2(el, attrs) {
2016
- for (const [key, value] of Object.entries(attrs)) {
2017
- el.setAttribute(key, value);
2035
+ // src/notch/highlight.ts
2036
+ function setBlobAttrs(el2, x, y, w, h, rx, opacity) {
2037
+ el2.setAttribute("x", String(x));
2038
+ el2.setAttribute("y", String(y));
2039
+ el2.setAttribute("width", String(w));
2040
+ el2.setAttribute("height", String(h));
2041
+ el2.setAttribute("rx", String(rx));
2042
+ el2.setAttribute("ry", String(rx));
2043
+ el2.setAttribute("opacity", opacity);
2044
+ }
2045
+ function springBlob(el2, from, to, sc) {
2046
+ return animateSpring(el2, {
2047
+ x: { from: from.x, to: to.x, unit: "px" },
2048
+ y: { from: from.y, to: to.y, unit: "px" },
2049
+ width: { from: from.w, to: to.w, unit: "px" },
2050
+ height: { from: from.h, to: to.h, unit: "px" },
2051
+ rx: { from: from.rx, to: to.rx, unit: "px" },
2052
+ ry: { from: from.rx, to: to.rx, unit: "px" }
2053
+ }, { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 });
2054
+ }
2055
+ function createHighlightTracker(el2, springConfig) {
2056
+ let anim = null;
2057
+ const prev = { x: 0, y: 0, w: 0, h: 0, visible: false };
2058
+ function finish(a, x, y, w, h, rx, opacity) {
2059
+ if (a) {
2060
+ anim = a;
2061
+ a.onfinish = () => {
2062
+ anim = null;
2063
+ setBlobAttrs(el2, x, y, w, h, rx, opacity);
2064
+ };
2065
+ } else {
2066
+ setBlobAttrs(el2, x, y, w, h, rx, opacity);
2067
+ }
2018
2068
  }
2069
+ return {
2070
+ onItemEnter(e, rootEl, isOpen, roundness) {
2071
+ const target = e.target.closest("a, button");
2072
+ if (!target || !isOpen) return;
2073
+ const rootRect = rootEl.getBoundingClientRect();
2074
+ const itemRect = target.getBoundingClientRect();
2075
+ const padX = 8, padY = 4;
2076
+ const overshoot = Math.max(6, roundness * 0.35);
2077
+ const toW = target.offsetWidth + padX * 2;
2078
+ const toH = Math.max(target.offsetHeight + padY * 2, rootRect.height + overshoot * 2);
2079
+ const toX = itemRect.left + itemRect.width / 2 - rootRect.left - toW / 2;
2080
+ const toY = itemRect.top + itemRect.height / 2 - rootRect.top - toH / 2;
2081
+ if (anim) {
2082
+ anim.cancel();
2083
+ anim = null;
2084
+ }
2085
+ const from = {
2086
+ x: prev.visible ? prev.x : toX + toW / 2,
2087
+ y: prev.visible ? prev.y : toY + toH / 2,
2088
+ w: prev.visible ? prev.w : 0,
2089
+ h: prev.visible ? prev.h : 0,
2090
+ rx: prev.visible ? prev.h / 2 : 0
2091
+ };
2092
+ finish(springBlob(el2, from, { x: toX, y: toY, w: toW, h: toH, rx: toH / 2 }, springConfig()), toX, toY, toW, toH, toH / 2, "1");
2093
+ el2.setAttribute("opacity", "1");
2094
+ prev.x = toX;
2095
+ prev.y = toY;
2096
+ prev.w = toW;
2097
+ prev.h = toH;
2098
+ prev.visible = true;
2099
+ },
2100
+ onItemLeave() {
2101
+ if (!prev.visible) return;
2102
+ const cx = prev.x + prev.w / 2, cy = prev.y + prev.h / 2;
2103
+ 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");
2104
+ prev.visible = false;
2105
+ },
2106
+ reset(rootW, rootH) {
2107
+ if (anim) {
2108
+ anim.cancel();
2109
+ anim = null;
2110
+ }
2111
+ setBlobAttrs(el2, rootW / 2, rootH / 2, 0, 0, 0, "0");
2112
+ prev.visible = false;
2113
+ },
2114
+ cancelAnim() {
2115
+ anim?.cancel();
2116
+ }
2117
+ };
2019
2118
  }
2119
+
2120
+ // src/notch/dom.ts
2020
2121
  function resolveContent(source) {
2021
2122
  if (source instanceof HTMLElement) return source;
2022
2123
  const span = document.createElement("span");
2023
2124
  span.textContent = source;
2024
2125
  return span;
2025
2126
  }
2026
- function createNotch(container, options) {
2027
- let {
2028
- trigger = "click",
2029
- position = "top-center",
2030
- spring,
2031
- dotSize = 36,
2032
- roundness = NOTCH_DEFAULTS.roundness,
2033
- theme = "dark",
2034
- fill,
2035
- open: controlledOpen,
2036
- onOpenChange
2037
- } = options;
2038
- const springConfig = () => spring ?? FLUIX_SPRING;
2039
- const machine2 = createNotchMachine({
2040
- position,
2041
- trigger,
2042
- roundness,
2043
- fill,
2044
- spring
2045
- });
2046
- let snapshot = machine2.store.getSnapshot();
2047
- let prevOpenVal;
2048
- const blur = () => Math.min(10, Math.max(6, roundness * 0.45));
2049
- const collapsedW = () => dotSize;
2050
- const collapsedH = () => dotSize;
2051
- let contentSize = { w: 200, h: 44 };
2052
- const hlPad = 12;
2053
- const expandedW = () => contentSize.w + hlPad * 2;
2054
- const expandedH = () => Math.max(contentSize.h + hlPad, dotSize);
2055
- const targetW = () => snapshot.open ? expandedW() : collapsedW();
2056
- const targetH = () => snapshot.open ? expandedH() : collapsedH();
2057
- const rootW = () => Math.max(expandedW(), collapsedW());
2058
- const rootH = () => Math.max(expandedH(), collapsedH());
2059
- const prev = { w: 0, h: 0, initialized: false };
2060
- let currentAnim = null;
2061
- let highlightAnim = null;
2062
- const hlPrev = { x: 0, y: 0, w: 0, h: 0, visible: false };
2127
+ function buildNotchSvg(rW, rH, collW, collH, blurVal, fill) {
2128
+ const svg = document.createElementNS(SVG_NS, "svg");
2129
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
2130
+ 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);
2131
+ const { g, defs, feBlur } = createGooeyFilter("fluix-notch-goo", blurVal);
2132
+ svg.appendChild(defs);
2133
+ const effectiveFill = fill ?? "var(--fluix-notch-bg)";
2134
+ const svgRectEl = document.createElementNS(SVG_NS, "rect");
2135
+ const cx = (rW - collW) / 2, cy = (rH - collH) / 2;
2136
+ 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));
2137
+ svgRectEl.setAttribute("fill", effectiveFill);
2138
+ g.appendChild(svgRectEl);
2139
+ const hoverBlobEl = document.createElementNS(SVG_NS, "rect");
2140
+ 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));
2141
+ hoverBlobEl.setAttribute("fill", effectiveFill);
2142
+ g.appendChild(hoverBlobEl);
2143
+ svg.appendChild(g);
2144
+ return { svg, svgRectEl, hoverBlobEl, feBlur };
2145
+ }
2146
+ var HL_PAD = 12;
2147
+ function computeDims(cfg) {
2148
+ const collW = cfg.dotSize, collH = cfg.dotSize;
2149
+ const expW = cfg.contentSize.w + HL_PAD * 2;
2150
+ const expH = Math.max(cfg.contentSize.h + HL_PAD, cfg.dotSize);
2151
+ const rW = Math.max(expW, collW);
2152
+ const rH = Math.max(expH, collH);
2153
+ return { collW, collH, rW, rH };
2154
+ }
2155
+ function computeBlur(roundness) {
2156
+ return Math.min(10, Math.max(6, roundness * 0.45));
2157
+ }
2158
+ function buildNotchDOM(cfg, pill, content, container, springCfg2) {
2063
2159
  const measureEl = document.createElement("div");
2064
2160
  measureEl.setAttribute("data-fluix-notch-measure", "");
2065
- measureEl.appendChild(resolveContent(options.content).cloneNode(true));
2161
+ measureEl.appendChild(resolveContent(content).cloneNode(true));
2066
2162
  container.appendChild(measureEl);
2163
+ const d = computeDims(cfg);
2067
2164
  const rootEl = document.createElement("div");
2068
- const attrs = getNotchAttrs({ open: snapshot.open, position, theme });
2069
- applyAttrs2(rootEl, attrs.root);
2070
- rootEl.style.width = `${rootW()}px`;
2071
- rootEl.style.height = `${rootH()}px`;
2165
+ const attrs = getNotchAttrs({ open: cfg.snapshot.open, position: cfg.position, theme: cfg.theme });
2166
+ applyAttrs(rootEl, attrs.root);
2167
+ rootEl.style.width = `${d.rW}px`;
2168
+ rootEl.style.height = `${d.rH}px`;
2072
2169
  const canvasDiv = document.createElement("div");
2073
- applyAttrs2(canvasDiv, attrs.canvas);
2074
- const svg = document.createElementNS(SVG_NS2, "svg");
2075
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
2076
- svg.setAttribute("width", String(rootW()));
2077
- svg.setAttribute("height", String(rootH()));
2078
- svg.setAttribute("viewBox", `0 0 ${rootW()} ${rootH()}`);
2079
- svg.setAttribute("aria-hidden", "true");
2080
- const defs = document.createElementNS(SVG_NS2, "defs");
2081
- const filter = document.createElementNS(SVG_NS2, "filter");
2082
- filter.setAttribute("id", "fluix-notch-goo");
2083
- filter.setAttribute("x", "-20%");
2084
- filter.setAttribute("y", "-20%");
2085
- filter.setAttribute("width", "140%");
2086
- filter.setAttribute("height", "140%");
2087
- filter.setAttribute("color-interpolation-filters", "sRGB");
2088
- const feBlur = document.createElementNS(SVG_NS2, "feGaussianBlur");
2089
- feBlur.setAttribute("in", "SourceGraphic");
2090
- feBlur.setAttribute("stdDeviation", String(blur()));
2091
- feBlur.setAttribute("result", "blur");
2092
- const feCM = document.createElementNS(SVG_NS2, "feColorMatrix");
2093
- feCM.setAttribute("in", "blur");
2094
- feCM.setAttribute("type", "matrix");
2095
- feCM.setAttribute("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10");
2096
- feCM.setAttribute("result", "goo");
2097
- const feComp = document.createElementNS(SVG_NS2, "feComposite");
2098
- feComp.setAttribute("in", "SourceGraphic");
2099
- feComp.setAttribute("in2", "goo");
2100
- feComp.setAttribute("operator", "atop");
2101
- filter.appendChild(feBlur);
2102
- filter.appendChild(feCM);
2103
- filter.appendChild(feComp);
2104
- defs.appendChild(filter);
2105
- svg.appendChild(defs);
2106
- const gGroup = document.createElementNS(SVG_NS2, "g");
2107
- gGroup.setAttribute("filter", "url(#fluix-notch-goo)");
2108
- const svgRectEl = document.createElementNS(SVG_NS2, "rect");
2109
- const cw = collapsedW();
2110
- const ch = collapsedH();
2111
- svgRectEl.setAttribute("x", String((rootW() - cw) / 2));
2112
- svgRectEl.setAttribute("y", String((rootH() - ch) / 2));
2113
- svgRectEl.setAttribute("width", String(cw));
2114
- svgRectEl.setAttribute("height", String(ch));
2115
- svgRectEl.setAttribute("rx", String(cw / 2));
2116
- svgRectEl.setAttribute("ry", String(ch / 2));
2117
- svgRectEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
2118
- gGroup.appendChild(svgRectEl);
2119
- svg.appendChild(gGroup);
2120
- const hoverBlobEl = document.createElementNS(SVG_NS2, "rect");
2121
- hoverBlobEl.setAttribute("x", String((rootW() - cw) / 2));
2122
- hoverBlobEl.setAttribute("y", String((rootH() - ch) / 2));
2123
- hoverBlobEl.setAttribute("width", "0");
2124
- hoverBlobEl.setAttribute("height", "0");
2125
- hoverBlobEl.setAttribute("rx", "0");
2126
- hoverBlobEl.setAttribute("ry", "0");
2127
- hoverBlobEl.setAttribute("opacity", "0");
2128
- hoverBlobEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
2129
- gGroup.appendChild(hoverBlobEl);
2130
- canvasDiv.appendChild(svg);
2170
+ applyAttrs(canvasDiv, attrs.canvas);
2171
+ canvasDiv.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:visible;";
2172
+ const svgRefs = buildNotchSvg(d.rW, d.rH, d.collW, d.collH, computeBlur(cfg.roundness), cfg.fill);
2173
+ canvasDiv.appendChild(svgRefs.svg);
2131
2174
  rootEl.appendChild(canvasDiv);
2175
+ const highlight = createHighlightTracker(svgRefs.hoverBlobEl, springCfg2);
2132
2176
  const pillDiv = document.createElement("div");
2133
- applyAttrs2(pillDiv, attrs.pill);
2134
- pillDiv.style.width = `${dotSize}px`;
2135
- pillDiv.style.height = `${dotSize}px`;
2136
- pillDiv.appendChild(resolveContent(options.pill));
2177
+ applyAttrs(pillDiv, attrs.pill);
2178
+ 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);`;
2179
+ pillDiv.appendChild(resolveContent(pill));
2137
2180
  rootEl.appendChild(pillDiv);
2138
2181
  const contentDiv = document.createElement("div");
2139
- applyAttrs2(contentDiv, attrs.content);
2140
- contentDiv.appendChild(resolveContent(options.content));
2182
+ applyAttrs(contentDiv, attrs.content);
2183
+ 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);";
2184
+ contentDiv.appendChild(resolveContent(content));
2141
2185
  rootEl.appendChild(contentDiv);
2142
2186
  container.appendChild(rootEl);
2143
- prev.w = cw;
2144
- prev.h = ch;
2145
- prev.initialized = true;
2146
- let measureRaf = 0;
2187
+ rootEl.setAttribute("role", "button");
2188
+ rootEl.setAttribute("tabindex", "0");
2189
+ rootEl.setAttribute("aria-expanded", String(cfg.snapshot.open));
2190
+ return { rootEl, measureEl, pillDiv, contentDiv, highlight, ...svgRefs };
2191
+ }
2192
+
2193
+ // src/notch/index.ts
2194
+ var HL_PAD2 = 12;
2195
+ function dims(ctx) {
2196
+ const collW = ctx.dotSize, collH = ctx.dotSize;
2197
+ const expW = ctx.contentSize.w + HL_PAD2 * 2;
2198
+ const expH = Math.max(ctx.contentSize.h + HL_PAD2, ctx.dotSize);
2199
+ const tW = ctx.snapshot.open ? expW : collW;
2200
+ const tH = ctx.snapshot.open ? expH : collH;
2201
+ const rW = Math.max(expW, collW);
2202
+ const rH = Math.max(expH, collH);
2203
+ return { collW, collH, expW, expH, tW, tH, rW, rH };
2204
+ }
2205
+ function blur(ctx) {
2206
+ return Math.min(10, Math.max(6, ctx.roundness * 0.45));
2207
+ }
2208
+ function springCfg(ctx) {
2209
+ return ctx.spring ?? FLUIX_SPRING;
2210
+ }
2211
+ function animateRect(ctx) {
2212
+ const d = dims(ctx);
2213
+ const toX = (d.rW - d.tW) / 2, toY = (d.rH - d.tH) / 2;
2214
+ if (d.tW === ctx.prev.w && d.tH === ctx.prev.h) {
2215
+ const el3 = ctx.dom.svgRectEl;
2216
+ const rx = d.tW === d.collW && d.tH === d.collH ? d.collW / 2 : ctx.roundness;
2217
+ 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));
2218
+ return;
2219
+ }
2220
+ if (ctx.currentAnim) {
2221
+ ctx.currentAnim.cancel();
2222
+ ctx.currentAnim = null;
2223
+ }
2224
+ const fromW = ctx.prev.w, fromH = ctx.prev.h;
2225
+ const fromX = (d.rW - fromW) / 2, fromY = (d.rH - fromH) / 2;
2226
+ ctx.prev.w = d.tW;
2227
+ ctx.prev.h = d.tH;
2228
+ const wasCollapsed = fromW === d.collW && fromH === d.collH;
2229
+ const isCollapsing = d.tW === d.collW && d.tH === d.collH;
2230
+ const fromRx = wasCollapsed ? d.collW / 2 : ctx.roundness;
2231
+ const toRx = isCollapsing ? d.collW / 2 : ctx.roundness;
2232
+ const el2 = ctx.dom.svgRectEl;
2233
+ const a = animateSpring(el2, {
2234
+ width: { from: fromW, to: d.tW, unit: "px" },
2235
+ height: { from: fromH, to: d.tH, unit: "px" },
2236
+ x: { from: fromX, to: toX, unit: "px" },
2237
+ y: { from: fromY, to: toY, unit: "px" },
2238
+ rx: { from: fromRx, to: toRx, unit: "px" },
2239
+ ry: { from: fromRx, to: toRx, unit: "px" }
2240
+ }, springCfg(ctx));
2241
+ const applyFinal = () => {
2242
+ 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));
2243
+ };
2244
+ if (a) {
2245
+ ctx.currentAnim = a;
2246
+ a.onfinish = () => {
2247
+ ctx.currentAnim = null;
2248
+ applyFinal();
2249
+ };
2250
+ } else applyFinal();
2251
+ }
2252
+ function updateLayout(ctx) {
2253
+ const d = dims(ctx);
2254
+ const isOpen = ctx.snapshot.open;
2255
+ const newAttrs = getNotchAttrs({ open: isOpen, position: ctx.position, theme: ctx.theme });
2256
+ applyAttrs(ctx.dom.rootEl, newAttrs.root);
2257
+ ctx.dom.rootEl.setAttribute("aria-expanded", String(isOpen));
2258
+ ctx.dom.rootEl.style.width = `${d.rW}px`;
2259
+ ctx.dom.rootEl.style.height = `${d.rH}px`;
2260
+ ctx.dom.svg.setAttribute("width", String(d.rW));
2261
+ ctx.dom.svg.setAttribute("height", String(d.rH));
2262
+ ctx.dom.svg.setAttribute("viewBox", `0 0 ${d.rW} ${d.rH}`);
2263
+ ctx.dom.feBlur.setAttribute("stdDeviation", String(blur(ctx)));
2264
+ const effectiveFill = ctx.fill ?? "var(--fluix-notch-bg)";
2265
+ ctx.dom.svgRectEl.setAttribute("fill", effectiveFill);
2266
+ ctx.dom.hoverBlobEl.setAttribute("fill", effectiveFill);
2267
+ applyAttrs(ctx.dom.contentDiv, newAttrs.content);
2268
+ ctx.dom.contentDiv.style.opacity = isOpen ? "1" : "0";
2269
+ ctx.dom.contentDiv.style.pointerEvents = isOpen ? "auto" : "none";
2270
+ ctx.dom.pillDiv.style.opacity = isOpen ? "0" : "1";
2271
+ animateRect(ctx);
2272
+ if (!isOpen) ctx.highlight.reset(d.rW, d.rH);
2273
+ document.documentElement.style.setProperty("--fluix-notch-offset", `${d.rH}px`);
2274
+ }
2275
+ function createListeners(ctx) {
2276
+ const handleOpen = () => {
2277
+ ctx.controlledOpen === void 0 ? ctx.machine.open() : ctx.onOpenChange?.(true);
2278
+ };
2279
+ const handleClose = () => {
2280
+ ctx.controlledOpen === void 0 ? ctx.machine.close() : ctx.onOpenChange?.(false);
2281
+ };
2282
+ const handleToggle = () => {
2283
+ ctx.controlledOpen === void 0 ? ctx.machine.toggle() : ctx.onOpenChange?.(!ctx.snapshot.open);
2284
+ };
2285
+ return {
2286
+ mouseenter: () => {
2287
+ if (ctx.trigger === "hover") handleOpen();
2288
+ },
2289
+ mouseleave: () => {
2290
+ if (ctx.trigger === "hover") {
2291
+ handleClose();
2292
+ ctx.highlight.reset(dims(ctx).rW, dims(ctx).rH);
2293
+ return;
2294
+ }
2295
+ ctx.highlight.onItemLeave();
2296
+ },
2297
+ mouseover: (e) => {
2298
+ ctx.highlight.onItemEnter(e, ctx.dom.rootEl, ctx.snapshot.open, ctx.roundness);
2299
+ },
2300
+ click: () => {
2301
+ if (ctx.trigger === "click") handleToggle();
2302
+ },
2303
+ keydown: (e) => {
2304
+ if (e.key === "Enter" || e.key === " ") {
2305
+ e.preventDefault();
2306
+ handleToggle();
2307
+ } else if (e.key === "Escape" && ctx.snapshot.open) {
2308
+ e.preventDefault();
2309
+ handleClose();
2310
+ }
2311
+ }
2312
+ };
2313
+ }
2314
+ function attachListeners(el2, ls) {
2315
+ el2.addEventListener("mouseenter", ls.mouseenter);
2316
+ el2.addEventListener("mouseleave", ls.mouseleave);
2317
+ el2.addEventListener("mouseover", ls.mouseover);
2318
+ el2.addEventListener("click", ls.click);
2319
+ el2.addEventListener("keydown", ls.keydown);
2320
+ }
2321
+ function detachListeners(el2, ls) {
2322
+ el2.removeEventListener("mouseenter", ls.mouseenter);
2323
+ el2.removeEventListener("mouseleave", ls.mouseleave);
2324
+ el2.removeEventListener("mouseover", ls.mouseover);
2325
+ el2.removeEventListener("click", ls.click);
2326
+ el2.removeEventListener("keydown", ls.keydown);
2327
+ }
2328
+ function notchUpdate(ctx, opts, listeners) {
2329
+ if (opts.trigger !== void 0) ctx.trigger = opts.trigger;
2330
+ if (opts.position !== void 0) ctx.position = opts.position;
2331
+ if (opts.spring !== void 0) ctx.spring = opts.spring;
2332
+ if (opts.dotSize !== void 0) ctx.dotSize = opts.dotSize;
2333
+ if (opts.roundness !== void 0) ctx.roundness = opts.roundness;
2334
+ if (opts.theme !== void 0) ctx.theme = opts.theme;
2335
+ if (opts.fill !== void 0) ctx.fill = opts.fill;
2336
+ if (opts.open !== void 0) ctx.controlledOpen = opts.open;
2337
+ if (opts.onOpenChange !== void 0) ctx.onOpenChange = opts.onOpenChange;
2338
+ if (opts.pill !== void 0) {
2339
+ ctx.dom.pillDiv.textContent = "";
2340
+ ctx.dom.pillDiv.appendChild(resolveContent(opts.pill));
2341
+ }
2342
+ if (opts.content !== void 0) {
2343
+ ctx.dom.contentDiv.textContent = "";
2344
+ ctx.dom.contentDiv.appendChild(resolveContent(opts.content));
2345
+ ctx.dom.measureEl.textContent = "";
2346
+ ctx.dom.measureEl.appendChild(resolveContent(opts.content).cloneNode(true));
2347
+ }
2348
+ ctx.dom.pillDiv.style.width = `${ctx.dotSize}px`;
2349
+ ctx.dom.pillDiv.style.height = `${ctx.dotSize}px`;
2350
+ ctx.machine.configure({ position: ctx.position, trigger: ctx.trigger, roundness: ctx.roundness, fill: ctx.fill, spring: ctx.spring });
2351
+ if (ctx.controlledOpen !== void 0) {
2352
+ if (ctx.controlledOpen && !ctx.snapshot.open) ctx.machine.open();
2353
+ else if (!ctx.controlledOpen && ctx.snapshot.open) ctx.machine.close();
2354
+ }
2355
+ detachListeners(ctx.dom.rootEl, listeners);
2356
+ const newListeners = createListeners(ctx);
2357
+ Object.assign(listeners, newListeners);
2358
+ attachListeners(ctx.dom.rootEl, listeners);
2359
+ updateLayout(ctx);
2360
+ return listeners;
2361
+ }
2362
+ function createNotch(container, options) {
2363
+ const machine2 = createNotchMachine({
2364
+ position: options.position ?? "top-center",
2365
+ trigger: options.trigger ?? "click",
2366
+ roundness: options.roundness ?? NOTCH_DEFAULTS.roundness,
2367
+ fill: options.fill,
2368
+ spring: options.spring
2369
+ });
2370
+ const dotSize = options.dotSize ?? 36;
2371
+ const roundness = options.roundness ?? NOTCH_DEFAULTS.roundness;
2372
+ const spring = options.spring;
2373
+ const domCfg = {
2374
+ snapshot: machine2.store.getSnapshot(),
2375
+ position: options.position ?? "top-center",
2376
+ theme: options.theme ?? "dark",
2377
+ dotSize,
2378
+ fill: options.fill,
2379
+ contentSize: { w: 200, h: 44 },
2380
+ roundness};
2381
+ const dom = buildNotchDOM(domCfg, options.pill, options.content, container, () => springCfg(ctx));
2382
+ const ctx = {
2383
+ trigger: options.trigger ?? "click",
2384
+ position: options.position ?? "top-center",
2385
+ spring,
2386
+ dotSize,
2387
+ roundness,
2388
+ theme: options.theme ?? "dark",
2389
+ fill: options.fill,
2390
+ controlledOpen: options.open,
2391
+ onOpenChange: options.onOpenChange,
2392
+ snapshot: machine2.store.getSnapshot(),
2393
+ prevOpenVal: void 0,
2394
+ contentSize: { w: 200, h: 44 },
2395
+ currentAnim: null,
2396
+ measureRaf: 0,
2397
+ machine: machine2,
2398
+ prev: { w: dotSize, h: dotSize },
2399
+ highlight: dom.highlight,
2400
+ dom
2401
+ };
2402
+ let listeners = createListeners(ctx);
2403
+ attachListeners(ctx.dom.rootEl, listeners);
2147
2404
  const measureObs = new ResizeObserver(() => {
2148
- cancelAnimationFrame(measureRaf);
2149
- measureRaf = requestAnimationFrame(() => {
2150
- const r = measureEl.getBoundingClientRect();
2405
+ cancelAnimationFrame(ctx.measureRaf);
2406
+ ctx.measureRaf = requestAnimationFrame(() => {
2407
+ const r = ctx.dom.measureEl.getBoundingClientRect();
2151
2408
  if (r.width > 0 && r.height > 0) {
2152
2409
  const newSize = { w: Math.ceil(r.width), h: Math.ceil(r.height) };
2153
- if (newSize.w !== contentSize.w || newSize.h !== contentSize.h) {
2154
- contentSize = newSize;
2155
- updateLayout();
2410
+ if (newSize.w !== ctx.contentSize.w || newSize.h !== ctx.contentSize.h) {
2411
+ ctx.contentSize = newSize;
2412
+ updateLayout(ctx);
2156
2413
  }
2157
2414
  }
2158
2415
  });
2159
2416
  });
2160
- measureObs.observe(measureEl);
2161
- function onItemEnter(e) {
2162
- const target = e.target.closest("a, button");
2163
- if (!target || !snapshot.open) return;
2164
- const rootRect = rootEl.getBoundingClientRect();
2165
- const itemW = target.offsetWidth;
2166
- const itemH = target.offsetHeight;
2167
- const itemRect = target.getBoundingClientRect();
2168
- const itemCenterX = itemRect.left + itemRect.width / 2;
2169
- const itemCenterY = itemRect.top + itemRect.height / 2;
2170
- const padX = 8;
2171
- const padY = 4;
2172
- const blobOvershoot = Math.max(6, roundness * 0.35);
2173
- const toW = itemW + padX * 2;
2174
- const toH = Math.max(itemH + padY * 2, rootRect.height + blobOvershoot * 2);
2175
- const toX = itemCenterX - rootRect.left - toW / 2;
2176
- const toY = itemCenterY - rootRect.top - toH / 2;
2177
- const toRx = toH / 2;
2178
- const fromX = hlPrev.visible ? hlPrev.x : toX + toW / 2;
2179
- const fromY = hlPrev.visible ? hlPrev.y : toY + toH / 2;
2180
- const fromW = hlPrev.visible ? hlPrev.w : 0;
2181
- const fromH = hlPrev.visible ? hlPrev.h : 0;
2182
- const fromR = hlPrev.visible ? hlPrev.h / 2 : 0;
2183
- if (highlightAnim) {
2184
- highlightAnim.cancel();
2185
- highlightAnim = null;
2186
- }
2187
- const sc = springConfig();
2188
- const a = animateSpring(
2189
- hoverBlobEl,
2190
- {
2191
- x: { from: fromX, to: toX, unit: "px" },
2192
- y: { from: fromY, to: toY, unit: "px" },
2193
- width: { from: fromW, to: toW, unit: "px" },
2194
- height: { from: fromH, to: toH, unit: "px" },
2195
- rx: { from: fromR, to: toRx, unit: "px" },
2196
- ry: { from: fromR, to: toRx, unit: "px" }
2197
- },
2198
- { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 }
2199
- );
2200
- hlPrev.x = toX;
2201
- hlPrev.y = toY;
2202
- hlPrev.w = toW;
2203
- hlPrev.h = toH;
2204
- if (a) {
2205
- highlightAnim = a;
2206
- a.onfinish = () => {
2207
- highlightAnim = null;
2208
- hoverBlobEl.setAttribute("x", String(toX));
2209
- hoverBlobEl.setAttribute("y", String(toY));
2210
- hoverBlobEl.setAttribute("width", String(toW));
2211
- hoverBlobEl.setAttribute("height", String(toH));
2212
- hoverBlobEl.setAttribute("rx", String(toRx));
2213
- hoverBlobEl.setAttribute("ry", String(toRx));
2214
- hoverBlobEl.setAttribute("opacity", "1");
2215
- };
2216
- } else {
2217
- hoverBlobEl.setAttribute("x", String(toX));
2218
- hoverBlobEl.setAttribute("y", String(toY));
2219
- hoverBlobEl.setAttribute("width", String(toW));
2220
- hoverBlobEl.setAttribute("height", String(toH));
2221
- hoverBlobEl.setAttribute("rx", String(toRx));
2222
- hoverBlobEl.setAttribute("ry", String(toRx));
2223
- hoverBlobEl.setAttribute("opacity", "1");
2224
- }
2225
- hoverBlobEl.setAttribute("opacity", "1");
2226
- hlPrev.visible = true;
2227
- }
2228
- function resetHoverBlobImmediate() {
2229
- if (highlightAnim) {
2230
- highlightAnim.cancel();
2231
- highlightAnim = null;
2232
- }
2233
- hoverBlobEl.setAttribute("x", String(rootW() / 2));
2234
- hoverBlobEl.setAttribute("y", String(rootH() / 2));
2235
- hoverBlobEl.setAttribute("width", "0");
2236
- hoverBlobEl.setAttribute("height", "0");
2237
- hoverBlobEl.setAttribute("rx", "0");
2238
- hoverBlobEl.setAttribute("ry", "0");
2239
- hoverBlobEl.setAttribute("opacity", "0");
2240
- hlPrev.visible = false;
2241
- }
2242
- function onItemLeave() {
2243
- if (!hlPrev.visible) return;
2244
- const cx = hlPrev.x + hlPrev.w / 2;
2245
- const cy = hlPrev.y + hlPrev.h / 2;
2246
- const sc = springConfig();
2247
- const a = animateSpring(
2248
- hoverBlobEl,
2249
- {
2250
- x: { from: hlPrev.x, to: cx, unit: "px" },
2251
- y: { from: hlPrev.y, to: cy, unit: "px" },
2252
- width: { from: hlPrev.w, to: 0, unit: "px" },
2253
- height: { from: hlPrev.h, to: 0, unit: "px" },
2254
- rx: { from: hlPrev.h / 2, to: 0, unit: "px" },
2255
- ry: { from: hlPrev.h / 2, to: 0, unit: "px" }
2256
- },
2257
- { ...sc, stiffness: (sc.stiffness ?? 300) * 1.2 }
2258
- );
2259
- if (a) {
2260
- highlightAnim = a;
2261
- a.onfinish = () => {
2262
- highlightAnim = null;
2263
- hoverBlobEl.setAttribute("x", String(cx));
2264
- hoverBlobEl.setAttribute("y", String(cy));
2265
- hoverBlobEl.setAttribute("width", "0");
2266
- hoverBlobEl.setAttribute("height", "0");
2267
- hoverBlobEl.setAttribute("rx", "0");
2268
- hoverBlobEl.setAttribute("ry", "0");
2269
- hoverBlobEl.setAttribute("opacity", "0");
2270
- };
2271
- } else {
2272
- hoverBlobEl.setAttribute("x", String(cx));
2273
- hoverBlobEl.setAttribute("y", String(cy));
2274
- hoverBlobEl.setAttribute("width", "0");
2275
- hoverBlobEl.setAttribute("height", "0");
2276
- hoverBlobEl.setAttribute("rx", "0");
2277
- hoverBlobEl.setAttribute("ry", "0");
2278
- hoverBlobEl.setAttribute("opacity", "0");
2279
- }
2280
- hlPrev.visible = false;
2281
- }
2282
- function handleOpen() {
2283
- if (controlledOpen === void 0) machine2.open();
2284
- else onOpenChange?.(true);
2285
- }
2286
- function handleClose() {
2287
- if (controlledOpen === void 0) machine2.close();
2288
- else onOpenChange?.(false);
2289
- }
2290
- function handleToggle() {
2291
- if (controlledOpen === void 0) machine2.toggle();
2292
- else onOpenChange?.(!snapshot.open);
2293
- }
2294
- function onMouseEnter() {
2295
- if (trigger === "hover") handleOpen();
2296
- }
2297
- function onMouseLeave() {
2298
- if (trigger === "hover") {
2299
- handleClose();
2300
- resetHoverBlobImmediate();
2301
- return;
2302
- }
2303
- onItemLeave();
2304
- }
2305
- function onClick() {
2306
- if (trigger === "click") handleToggle();
2307
- }
2308
- rootEl.addEventListener("mouseenter", onMouseEnter);
2309
- rootEl.addEventListener("mouseleave", onMouseLeave);
2310
- rootEl.addEventListener("mouseover", onItemEnter);
2311
- rootEl.addEventListener("click", onClick);
2312
- function animateRect() {
2313
- const tw = targetW();
2314
- const th = targetH();
2315
- if (tw === prev.w && th === prev.h) return;
2316
- if (currentAnim) {
2317
- currentAnim.cancel();
2318
- currentAnim = null;
2319
- }
2320
- const rw = rootW();
2321
- const rh = rootH();
2322
- const fromW = prev.w;
2323
- const fromH = prev.h;
2324
- const fromX = (rw - fromW) / 2;
2325
- const fromY = (rh - fromH) / 2;
2326
- const toX = (rw - tw) / 2;
2327
- const toY = (rh - th) / 2;
2328
- prev.w = tw;
2329
- prev.h = th;
2330
- const isCollapsing = tw === collapsedW() && th === collapsedH();
2331
- const wasCollapsed = fromW === collapsedW() && fromH === collapsedH();
2332
- const fromRx = wasCollapsed ? collapsedW() / 2 : roundness;
2333
- const toRx = isCollapsing ? collapsedW() / 2 : roundness;
2334
- const a = animateSpring(
2335
- svgRectEl,
2336
- {
2337
- width: { from: fromW, to: tw, unit: "px" },
2338
- height: { from: fromH, to: th, unit: "px" },
2339
- x: { from: fromX, to: toX, unit: "px" },
2340
- y: { from: fromY, to: toY, unit: "px" },
2341
- rx: { from: fromRx, to: toRx, unit: "px" },
2342
- ry: { from: fromRx, to: toRx, unit: "px" }
2343
- },
2344
- springConfig()
2345
- );
2346
- if (a) {
2347
- currentAnim = a;
2348
- a.onfinish = () => {
2349
- currentAnim = null;
2350
- svgRectEl.setAttribute("width", String(tw));
2351
- svgRectEl.setAttribute("height", String(th));
2352
- svgRectEl.setAttribute("x", String(toX));
2353
- svgRectEl.setAttribute("y", String(toY));
2354
- svgRectEl.setAttribute("rx", String(toRx));
2355
- svgRectEl.setAttribute("ry", String(toRx));
2356
- };
2357
- } else {
2358
- svgRectEl.setAttribute("width", String(tw));
2359
- svgRectEl.setAttribute("height", String(th));
2360
- svgRectEl.setAttribute("x", String(toX));
2361
- svgRectEl.setAttribute("y", String(toY));
2362
- svgRectEl.setAttribute("rx", String(toRx));
2363
- svgRectEl.setAttribute("ry", String(toRx));
2364
- }
2365
- }
2366
- function updateLayout() {
2367
- const isOpen = snapshot.open;
2368
- const newAttrs = getNotchAttrs({ open: isOpen, position, theme });
2369
- applyAttrs2(rootEl, newAttrs.root);
2370
- rootEl.style.width = `${rootW()}px`;
2371
- rootEl.style.height = `${rootH()}px`;
2372
- svg.setAttribute("width", String(rootW()));
2373
- svg.setAttribute("height", String(rootH()));
2374
- svg.setAttribute("viewBox", `0 0 ${rootW()} ${rootH()}`);
2375
- feBlur.setAttribute("stdDeviation", String(blur()));
2376
- svgRectEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
2377
- hoverBlobEl.setAttribute("fill", fill ?? "var(--fluix-notch-bg)");
2378
- applyAttrs2(contentDiv, newAttrs.content);
2379
- animateRect();
2380
- if (!isOpen) {
2381
- resetHoverBlobImmediate();
2382
- }
2383
- document.documentElement.style.setProperty(
2384
- "--fluix-notch-offset",
2385
- `${rootH()}px`
2386
- );
2387
- }
2417
+ measureObs.observe(ctx.dom.measureEl);
2388
2418
  const unsubscribe = machine2.store.subscribe(() => {
2389
- const next = machine2.store.getSnapshot();
2390
- snapshot = next;
2391
- if (controlledOpen !== void 0) {
2392
- if (controlledOpen && !next.open) machine2.open();
2393
- else if (!controlledOpen && next.open) machine2.close();
2394
- }
2395
- if (prevOpenVal !== void 0 && prevOpenVal !== next.open) {
2396
- onOpenChange?.(next.open);
2397
- }
2398
- prevOpenVal = next.open;
2399
- updateLayout();
2419
+ ctx.snapshot = machine2.store.getSnapshot();
2420
+ if (ctx.controlledOpen !== void 0) {
2421
+ if (ctx.controlledOpen && !ctx.snapshot.open) machine2.open();
2422
+ else if (!ctx.controlledOpen && ctx.snapshot.open) machine2.close();
2423
+ }
2424
+ if (ctx.prevOpenVal !== void 0 && ctx.prevOpenVal !== ctx.snapshot.open) ctx.onOpenChange?.(ctx.snapshot.open);
2425
+ ctx.prevOpenVal = ctx.snapshot.open;
2426
+ updateLayout(ctx);
2400
2427
  });
2401
- updateLayout();
2402
- document.documentElement.style.setProperty(
2403
- "--fluix-notch-offset",
2404
- `${rootH()}px`
2405
- );
2428
+ updateLayout(ctx);
2429
+ document.documentElement.style.setProperty("--fluix-notch-offset", `${dims(ctx).rH}px`);
2406
2430
  return {
2407
2431
  open() {
2408
- handleOpen();
2432
+ ctx.controlledOpen === void 0 ? machine2.open() : ctx.onOpenChange?.(true);
2409
2433
  },
2410
2434
  close() {
2411
- handleClose();
2435
+ ctx.controlledOpen === void 0 ? machine2.close() : ctx.onOpenChange?.(false);
2412
2436
  },
2413
2437
  toggle() {
2414
- handleToggle();
2438
+ ctx.controlledOpen === void 0 ? machine2.toggle() : ctx.onOpenChange?.(!ctx.snapshot.open);
2415
2439
  },
2416
2440
  destroy() {
2417
2441
  unsubscribe();
2418
- cancelAnimationFrame(measureRaf);
2442
+ cancelAnimationFrame(ctx.measureRaf);
2419
2443
  measureObs.disconnect();
2420
- currentAnim?.cancel();
2421
- highlightAnim?.cancel();
2422
- rootEl.removeEventListener("mouseenter", onMouseEnter);
2423
- rootEl.removeEventListener("mouseleave", onMouseLeave);
2424
- rootEl.removeEventListener("mouseover", onItemEnter);
2425
- rootEl.removeEventListener("click", onClick);
2426
- measureEl.remove();
2427
- rootEl.remove();
2444
+ ctx.currentAnim?.cancel();
2445
+ ctx.highlight.cancelAnim();
2446
+ detachListeners(ctx.dom.rootEl, listeners);
2447
+ machine2.destroy();
2448
+ ctx.dom.measureEl.remove();
2449
+ ctx.dom.rootEl.remove();
2428
2450
  document.documentElement.style.removeProperty("--fluix-notch-offset");
2429
2451
  },
2430
2452
  update(opts) {
2431
- if (opts.trigger !== void 0) trigger = opts.trigger;
2432
- if (opts.position !== void 0) position = opts.position;
2433
- if (opts.spring !== void 0) spring = opts.spring;
2434
- if (opts.dotSize !== void 0) dotSize = opts.dotSize;
2435
- if (opts.roundness !== void 0) roundness = opts.roundness;
2436
- if (opts.theme !== void 0) theme = opts.theme;
2437
- if (opts.fill !== void 0) fill = opts.fill;
2438
- if (opts.open !== void 0) controlledOpen = opts.open;
2439
- if (opts.onOpenChange !== void 0) onOpenChange = opts.onOpenChange;
2440
- if (opts.pill !== void 0) {
2441
- pillDiv.textContent = "";
2442
- pillDiv.appendChild(resolveContent(opts.pill));
2443
- }
2444
- if (opts.content !== void 0) {
2445
- contentDiv.textContent = "";
2446
- contentDiv.appendChild(resolveContent(opts.content));
2447
- measureEl.textContent = "";
2448
- measureEl.appendChild(resolveContent(opts.content).cloneNode(true));
2449
- }
2450
- pillDiv.style.width = `${dotSize}px`;
2451
- pillDiv.style.height = `${dotSize}px`;
2452
- machine2.configure({ position, trigger, roundness, fill, spring });
2453
- if (controlledOpen !== void 0) {
2454
- if (controlledOpen && !snapshot.open) machine2.open();
2455
- else if (!controlledOpen && snapshot.open) machine2.close();
2456
- }
2457
- rootEl.removeEventListener("mouseenter", onMouseEnter);
2458
- rootEl.removeEventListener("mouseleave", onMouseLeave);
2459
- rootEl.removeEventListener("click", onClick);
2460
- rootEl.addEventListener("mouseenter", onMouseEnter);
2461
- rootEl.addEventListener("mouseleave", onMouseLeave);
2462
- rootEl.addEventListener("click", onClick);
2463
- updateLayout();
2453
+ listeners = notchUpdate(ctx, opts, listeners);
2464
2454
  }
2465
2455
  };
2466
2456
  }
2467
2457
 
2468
- // src/menu.ts
2469
- var SVG_NS3 = "http://www.w3.org/2000/svg";
2470
- var GOO_MATRIX = "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10";
2471
- function applyAttrs3(el, attrs) {
2472
- for (const [key, value] of Object.entries(attrs)) {
2473
- el.setAttribute(key, value);
2458
+ // src/menu/index.ts
2459
+ function buildSvgIndicator(svg, isTab, filterId, resolvedBlur, fill, indicatorAttrs) {
2460
+ clearChildren(svg);
2461
+ const effectiveFill = fill ?? "var(--fluix-menu-indicator)";
2462
+ if (isTab) {
2463
+ const indicatorEl2 = document.createElementNS(SVG_NS, "path");
2464
+ applyAttrs(indicatorEl2, indicatorAttrs);
2465
+ indicatorEl2.setAttribute("d", "");
2466
+ indicatorEl2.setAttribute("opacity", "0");
2467
+ indicatorEl2.style.fill = effectiveFill;
2468
+ svg.appendChild(indicatorEl2);
2469
+ return { indicatorEl: indicatorEl2, ghostIndicatorEl: null };
2470
+ }
2471
+ const { g, defs } = createGooeyFilter(filterId, resolvedBlur);
2472
+ svg.appendChild(defs);
2473
+ const ghostIndicatorEl = document.createElementNS(SVG_NS, "rect");
2474
+ zeroRect(ghostIndicatorEl);
2475
+ ghostIndicatorEl.setAttribute("opacity", "0");
2476
+ ghostIndicatorEl.style.fill = effectiveFill;
2477
+ const indicatorEl = document.createElementNS(SVG_NS, "rect");
2478
+ applyAttrs(indicatorEl, indicatorAttrs);
2479
+ zeroRect(indicatorEl);
2480
+ indicatorEl.setAttribute("opacity", "0");
2481
+ indicatorEl.style.fill = effectiveFill;
2482
+ g.appendChild(ghostIndicatorEl);
2483
+ g.appendChild(indicatorEl);
2484
+ svg.appendChild(g);
2485
+ return { indicatorEl, ghostIndicatorEl };
2486
+ }
2487
+ function buildMenuDOM(attrs, filterId, variant, resolvedBlur, fill) {
2488
+ const navEl = document.createElement("nav");
2489
+ applyAttrs(navEl, attrs.root);
2490
+ navEl.setAttribute("aria-label", "Fluix menu");
2491
+ const canvasDiv = document.createElement("div");
2492
+ applyAttrs(canvasDiv, attrs.canvas);
2493
+ const svg = document.createElementNS(SVG_NS, "svg");
2494
+ svg.setAttribute("xmlns", SVG_NS);
2495
+ for (const [k, v] of Object.entries({ width: "1", height: "1", viewBox: "0 0 1 1", "aria-hidden": "true" })) svg.setAttribute(k, v);
2496
+ const { indicatorEl, ghostIndicatorEl } = buildSvgIndicator(svg, variant === "tab", filterId, resolvedBlur, fill, attrs.indicator);
2497
+ canvasDiv.appendChild(svg);
2498
+ navEl.appendChild(canvasDiv);
2499
+ const listDiv = document.createElement("div");
2500
+ applyAttrs(listDiv, attrs.list);
2501
+ navEl.appendChild(listDiv);
2502
+ return { navEl, svg, listDiv, buttonMap: /* @__PURE__ */ new Map(), indicatorEl, ghostIndicatorEl };
2503
+ }
2504
+ function createItemButton(ctx, item) {
2505
+ const btn = document.createElement("button");
2506
+ btn.type = "button";
2507
+ const active = ctx.snapshot.activeId === item.id;
2508
+ applyAttrs(btn, ctx.attrs.item({ id: item.id, active, disabled: item.disabled }));
2509
+ if (item.disabled) btn.disabled = true;
2510
+ btn.textContent = item.label;
2511
+ btn.addEventListener("click", () => {
2512
+ if (item.disabled) return;
2513
+ if (ctx.controlledActiveId === void 0) ctx.machine.setActive(item.id);
2514
+ else ctx.onActiveChange?.(item.id);
2515
+ });
2516
+ ctx.buttonMap.set(item.id, btn);
2517
+ ctx.listDiv.appendChild(btn);
2518
+ }
2519
+ function makeConnection(ctx) {
2520
+ return connectMenu({
2521
+ root: ctx.navEl,
2522
+ indicator: ctx.indicatorEl,
2523
+ ghostIndicator: ctx.ghostIndicatorEl,
2524
+ getActiveId: () => ctx.snapshot.activeId,
2525
+ onSelect(id) {
2526
+ if (ctx.controlledActiveId === void 0) ctx.machine.setActive(id);
2527
+ else ctx.onActiveChange?.(id);
2528
+ },
2529
+ spring: ctx.spring ?? FLUIX_SPRING,
2530
+ variant: ctx.variant,
2531
+ orientation: ctx.orientation
2532
+ });
2533
+ }
2534
+ function measure(ctx) {
2535
+ const rect = ctx.navEl.getBoundingClientRect();
2536
+ const w = Math.ceil(rect.width);
2537
+ const h = Math.ceil(rect.height);
2538
+ if (ctx.size.width !== w || ctx.size.height !== h) {
2539
+ ctx.size = { width: w, height: h };
2540
+ const sw = Math.max(1, w);
2541
+ const sh = Math.max(1, h);
2542
+ ctx.svg.setAttribute("width", String(sw));
2543
+ ctx.svg.setAttribute("height", String(sh));
2544
+ ctx.svg.setAttribute("viewBox", `0 0 ${sw} ${sh}`);
2545
+ ctx.connection?.sync(false);
2474
2546
  }
2475
2547
  }
2548
+ function menuUpdate(ctx, opts) {
2549
+ const prevVariant = ctx.variant;
2550
+ if (opts.orientation !== void 0) ctx.orientation = opts.orientation;
2551
+ if (opts.variant !== void 0) ctx.variant = opts.variant;
2552
+ if (opts.theme !== void 0) ctx.theme = opts.theme;
2553
+ if (opts.activeId !== void 0) ctx.controlledActiveId = opts.activeId;
2554
+ if (opts.onActiveChange !== void 0) ctx.onActiveChange = opts.onActiveChange;
2555
+ if (opts.spring !== void 0) ctx.spring = opts.spring;
2556
+ if (opts.roundness !== void 0) ctx.roundness = opts.roundness;
2557
+ if (opts.blur !== void 0) ctx.blurProp = opts.blur;
2558
+ if (opts.fill !== void 0) ctx.fill = opts.fill;
2559
+ ctx.machine.configure({ orientation: ctx.orientation, variant: ctx.variant, spring: ctx.spring, roundness: ctx.roundness, blur: ctx.blurProp, fill: ctx.fill });
2560
+ if (ctx.controlledActiveId !== void 0) ctx.machine.setActive(ctx.controlledActiveId ?? null);
2561
+ ctx.attrs = getMenuAttrs({ orientation: ctx.orientation, theme: ctx.theme, variant: ctx.variant });
2562
+ applyAttrs(ctx.navEl, ctx.attrs.root);
2563
+ if (prevVariant !== ctx.variant) {
2564
+ const resolvedBlur = ctx.blurProp ?? Math.min(10, Math.max(6, ctx.roundness * 0.45));
2565
+ const refs = buildSvgIndicator(ctx.svg, ctx.variant === "tab", ctx.filterId, resolvedBlur, ctx.fill, ctx.attrs.indicator);
2566
+ ctx.indicatorEl = refs.indicatorEl;
2567
+ ctx.ghostIndicatorEl = refs.ghostIndicatorEl;
2568
+ }
2569
+ if (opts.items !== void 0) {
2570
+ ctx.items = opts.items;
2571
+ clearChildren(ctx.listDiv);
2572
+ ctx.buttonMap.clear();
2573
+ for (const item of ctx.items) createItemButton(ctx, item);
2574
+ }
2575
+ ctx.connection.destroy();
2576
+ ctx.connection = makeConnection(ctx);
2577
+ requestAnimationFrame(() => {
2578
+ measure(ctx);
2579
+ ctx.connection.sync(false);
2580
+ });
2581
+ }
2476
2582
  function createMenu(container, options) {
2477
- let {
2583
+ const {
2478
2584
  orientation = MENU_DEFAULTS.orientation,
2479
2585
  variant = "pill",
2480
2586
  theme = "dark",
@@ -2486,8 +2592,6 @@ var Fluix = (function (exports) {
2486
2592
  fill,
2487
2593
  items
2488
2594
  } = options;
2489
- const springConfig = () => spring ?? FLUIX_SPRING;
2490
- const resolvedBlur = () => blurProp ?? Math.min(10, Math.max(6, roundness * 0.45));
2491
2595
  const machine2 = createMenuMachine({
2492
2596
  orientation,
2493
2597
  variant,
@@ -2497,216 +2601,69 @@ var Fluix = (function (exports) {
2497
2601
  fill,
2498
2602
  initialActiveId: controlledActiveId ?? null
2499
2603
  });
2500
- let snapshot = machine2.store.getSnapshot();
2501
- let lastActiveNotified = snapshot.activeId;
2502
2604
  const attrs = getMenuAttrs({ orientation, theme, variant });
2503
2605
  const filterId = `fluix-menu-goo-${Math.random().toString(36).slice(2, 8)}`;
2504
- const isTab = variant === "tab";
2505
- const navEl = document.createElement("nav");
2506
- applyAttrs3(navEl, attrs.root);
2507
- navEl.setAttribute("aria-label", "Fluix menu");
2508
- const canvasDiv = document.createElement("div");
2509
- applyAttrs3(canvasDiv, attrs.canvas);
2510
- const svg = document.createElementNS(SVG_NS3, "svg");
2511
- svg.setAttribute("xmlns", SVG_NS3);
2512
- svg.setAttribute("width", "1");
2513
- svg.setAttribute("height", "1");
2514
- svg.setAttribute("viewBox", "0 0 1 1");
2515
- svg.setAttribute("aria-hidden", "true");
2516
- let indicatorEl;
2517
- if (isTab) {
2518
- indicatorEl = document.createElementNS(SVG_NS3, "path");
2519
- applyAttrs3(indicatorEl, attrs.indicator);
2520
- indicatorEl.setAttribute("d", "");
2521
- indicatorEl.setAttribute("opacity", "0");
2522
- indicatorEl.setAttribute("fill", fill ?? "var(--fluix-menu-indicator)");
2523
- svg.appendChild(indicatorEl);
2524
- } else {
2525
- const defs = document.createElementNS(SVG_NS3, "defs");
2526
- const filter = document.createElementNS(SVG_NS3, "filter");
2527
- filter.setAttribute("id", filterId);
2528
- filter.setAttribute("x", "-20%");
2529
- filter.setAttribute("y", "-20%");
2530
- filter.setAttribute("width", "140%");
2531
- filter.setAttribute("height", "140%");
2532
- filter.setAttribute("color-interpolation-filters", "sRGB");
2533
- const feBlur = document.createElementNS(SVG_NS3, "feGaussianBlur");
2534
- feBlur.setAttribute("in", "SourceGraphic");
2535
- feBlur.setAttribute("stdDeviation", String(resolvedBlur()));
2536
- feBlur.setAttribute("result", "blur");
2537
- const feCM = document.createElementNS(SVG_NS3, "feColorMatrix");
2538
- feCM.setAttribute("in", "blur");
2539
- feCM.setAttribute("type", "matrix");
2540
- feCM.setAttribute("values", GOO_MATRIX);
2541
- feCM.setAttribute("result", "goo");
2542
- const feComp = document.createElementNS(SVG_NS3, "feComposite");
2543
- feComp.setAttribute("in", "SourceGraphic");
2544
- feComp.setAttribute("in2", "goo");
2545
- feComp.setAttribute("operator", "atop");
2546
- filter.appendChild(feBlur);
2547
- filter.appendChild(feCM);
2548
- filter.appendChild(feComp);
2549
- defs.appendChild(filter);
2550
- svg.appendChild(defs);
2551
- const gGroup = document.createElementNS(SVG_NS3, "g");
2552
- gGroup.setAttribute("filter", `url(#${filterId})`);
2553
- indicatorEl = document.createElementNS(SVG_NS3, "rect");
2554
- applyAttrs3(indicatorEl, attrs.indicator);
2555
- indicatorEl.setAttribute("x", "0");
2556
- indicatorEl.setAttribute("y", "0");
2557
- indicatorEl.setAttribute("width", "0");
2558
- indicatorEl.setAttribute("height", "0");
2559
- indicatorEl.setAttribute("rx", "0");
2560
- indicatorEl.setAttribute("ry", "0");
2561
- indicatorEl.setAttribute("opacity", "0");
2562
- indicatorEl.setAttribute("fill", fill ?? "var(--fluix-menu-indicator)");
2563
- gGroup.appendChild(indicatorEl);
2564
- svg.appendChild(gGroup);
2565
- }
2566
- canvasDiv.appendChild(svg);
2567
- navEl.appendChild(canvasDiv);
2568
- const listDiv = document.createElement("div");
2569
- applyAttrs3(listDiv, attrs.list);
2570
- const buttonMap = /* @__PURE__ */ new Map();
2571
- function createItemButton(item) {
2572
- const btn = document.createElement("button");
2573
- btn.type = "button";
2574
- const active = snapshot.activeId === item.id;
2575
- const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
2576
- applyAttrs3(btn, itemAttrs);
2577
- if (item.disabled) btn.disabled = true;
2578
- btn.textContent = item.label;
2579
- btn.addEventListener("click", () => {
2580
- if (item.disabled) return;
2581
- if (controlledActiveId === void 0) {
2582
- machine2.setActive(item.id);
2583
- } else {
2584
- onActiveChange?.(item.id);
2585
- }
2586
- });
2587
- buttonMap.set(item.id, btn);
2588
- listDiv.appendChild(btn);
2589
- }
2590
- for (const item of items) {
2591
- createItemButton(item);
2592
- }
2593
- navEl.appendChild(listDiv);
2594
- container.appendChild(navEl);
2595
- let size = { width: 0, height: 0 };
2596
- let measureRaf = 0;
2597
- const measure = () => {
2598
- const rect = navEl.getBoundingClientRect();
2599
- const w = Math.ceil(rect.width);
2600
- const h = Math.ceil(rect.height);
2601
- if (size.width !== w || size.height !== h) {
2602
- size = { width: w, height: h };
2603
- updateSvgSize();
2604
- connection?.sync(false);
2605
- }
2606
+ const resolvedBlur = blurProp ?? Math.min(10, Math.max(6, roundness * 0.45));
2607
+ const dom = buildMenuDOM(attrs, filterId, variant, resolvedBlur, fill);
2608
+ const ctx = {
2609
+ orientation,
2610
+ variant,
2611
+ theme,
2612
+ controlledActiveId,
2613
+ onActiveChange,
2614
+ spring,
2615
+ roundness,
2616
+ blurProp,
2617
+ fill,
2618
+ items,
2619
+ attrs,
2620
+ filterId,
2621
+ machine: machine2,
2622
+ snapshot: machine2.store.getSnapshot(),
2623
+ lastActiveNotified: machine2.store.getSnapshot().activeId,
2624
+ ...dom,
2625
+ connection: null,
2626
+ size: { width: 0, height: 0 },
2627
+ measureRaf: 0
2606
2628
  };
2629
+ for (const item of items) createItemButton(ctx, item);
2630
+ container.appendChild(ctx.navEl);
2607
2631
  const resizeObs = new ResizeObserver(() => {
2608
- cancelAnimationFrame(measureRaf);
2609
- measureRaf = requestAnimationFrame(measure);
2610
- });
2611
- resizeObs.observe(navEl);
2612
- function updateSvgSize() {
2613
- const w = Math.max(1, size.width);
2614
- const h = Math.max(1, size.height);
2615
- svg.setAttribute("width", String(w));
2616
- svg.setAttribute("height", String(h));
2617
- svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
2618
- }
2619
- let connection = connectMenu({
2620
- root: navEl,
2621
- indicator: indicatorEl,
2622
- getActiveId: () => snapshot.activeId,
2623
- onSelect(id) {
2624
- if (controlledActiveId === void 0) {
2625
- machine2.setActive(id);
2626
- } else {
2627
- onActiveChange?.(id);
2628
- }
2629
- },
2630
- spring: springConfig(),
2631
- variant,
2632
- orientation
2632
+ cancelAnimationFrame(ctx.measureRaf);
2633
+ ctx.measureRaf = requestAnimationFrame(() => measure(ctx));
2633
2634
  });
2635
+ resizeObs.observe(ctx.navEl);
2636
+ ctx.connection = makeConnection(ctx);
2634
2637
  requestAnimationFrame(() => {
2635
- measure();
2636
- connection.sync(false);
2638
+ measure(ctx);
2639
+ ctx.connection.sync(false);
2637
2640
  });
2638
2641
  const unsubscribe = machine2.store.subscribe(() => {
2639
- const next = machine2.store.getSnapshot();
2640
- snapshot = next;
2641
- for (const item of items) {
2642
- const btn = buttonMap.get(item.id);
2643
- if (btn) {
2644
- const active = next.activeId === item.id;
2645
- const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
2646
- applyAttrs3(btn, itemAttrs);
2647
- }
2642
+ ctx.snapshot = machine2.store.getSnapshot();
2643
+ for (const item of ctx.items) {
2644
+ const btn = ctx.buttonMap.get(item.id);
2645
+ if (btn) applyAttrs(btn, ctx.attrs.item({ id: item.id, active: ctx.snapshot.activeId === item.id, disabled: item.disabled }));
2648
2646
  }
2649
- if (next.activeId && lastActiveNotified !== next.activeId && onActiveChange) {
2650
- onActiveChange(next.activeId);
2647
+ if (ctx.snapshot.activeId && ctx.lastActiveNotified !== ctx.snapshot.activeId && ctx.onActiveChange) {
2648
+ ctx.onActiveChange(ctx.snapshot.activeId);
2651
2649
  }
2652
- lastActiveNotified = next.activeId;
2653
- connection.sync(false);
2650
+ ctx.lastActiveNotified = ctx.snapshot.activeId;
2651
+ ctx.connection.sync(false);
2654
2652
  });
2655
2653
  return {
2656
2654
  setActive(id) {
2657
2655
  machine2.setActive(id);
2658
2656
  },
2659
2657
  update(opts) {
2660
- if (opts.orientation !== void 0) orientation = opts.orientation;
2661
- if (opts.variant !== void 0) variant = opts.variant;
2662
- if (opts.theme !== void 0) theme = opts.theme;
2663
- if (opts.activeId !== void 0) controlledActiveId = opts.activeId;
2664
- if (opts.onActiveChange !== void 0) onActiveChange = opts.onActiveChange;
2665
- if (opts.spring !== void 0) spring = opts.spring;
2666
- if (opts.roundness !== void 0) roundness = opts.roundness;
2667
- if (opts.blur !== void 0) blurProp = opts.blur;
2668
- if (opts.fill !== void 0) fill = opts.fill;
2669
- machine2.configure({ orientation, variant, spring, roundness, blur: blurProp, fill });
2670
- if (controlledActiveId !== void 0) {
2671
- machine2.setActive(controlledActiveId ?? null);
2672
- }
2673
- const newAttrs = getMenuAttrs({ orientation, theme, variant });
2674
- applyAttrs3(navEl, newAttrs.root);
2675
- if (opts.items !== void 0) {
2676
- items = opts.items;
2677
- listDiv.innerHTML = "";
2678
- buttonMap.clear();
2679
- for (const item of items) {
2680
- createItemButton(item);
2681
- }
2682
- }
2683
- connection.destroy();
2684
- connection = connectMenu({
2685
- root: navEl,
2686
- indicator: indicatorEl,
2687
- getActiveId: () => snapshot.activeId,
2688
- onSelect(id) {
2689
- if (controlledActiveId === void 0) {
2690
- machine2.setActive(id);
2691
- } else {
2692
- onActiveChange?.(id);
2693
- }
2694
- },
2695
- spring: springConfig(),
2696
- variant,
2697
- orientation
2698
- });
2699
- requestAnimationFrame(() => {
2700
- measure();
2701
- connection.sync(false);
2702
- });
2658
+ menuUpdate(ctx, opts);
2703
2659
  },
2704
2660
  destroy() {
2705
2661
  unsubscribe();
2706
- cancelAnimationFrame(measureRaf);
2662
+ cancelAnimationFrame(ctx.measureRaf);
2707
2663
  resizeObs.disconnect();
2708
- connection.destroy();
2709
- navEl.remove();
2664
+ ctx.connection.destroy();
2665
+ machine2.destroy();
2666
+ ctx.navEl.remove();
2710
2667
  }
2711
2668
  };
2712
2669
  }