@sanmid/flux 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ npm install @sanmid/flux -D
14
14
 
15
15
  Or: `pnpm add -D @sanmid/flux` · `yarn add -D @sanmid/flux`
16
16
 
17
- Icons, `clsx`, `tailwind-merge`, and `framer-motion` are **dependencies of Flux** — you do not install them separately. You only need **`react` and `react-dom` (18+)** in your app (Flux lists them as peer dependencies so one React instance is shared with your UI).
17
+ Icons, `clsx`, and `tailwind-merge` are **dependencies of Flux** — you do not install them separately. You only need **`react` and `react-dom` (18+)** in your app (Flux lists them as peer dependencies so one React instance is shared with your UI).
18
18
 
19
19
  ---
20
20
 
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
2
1
  import { createContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useContext } from 'react';
3
2
  import { flushSync, createPortal } from 'react-dom';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
4
  import { X, CornerUpLeft, Check, Copy, AlignHorizontalJustifyStart, AlignHorizontalJustifyCenter, AlignHorizontalJustifyEnd, AlignVerticalJustifyStart, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, Rows3, SquareSquare, AlignVerticalSpaceAround, AlignHorizontalSpaceAround, ArrowUpDown, ArrowLeftRight, AlignLeft, AlignCenter, AlignRight, ArrowUpToLine, FoldVertical, ArrowDownToLine, SunDim, Minus, Plus, Eye, Ruler, SquareDashed, ChevronDown, Link2, Square, MoveDown, MoveRight } from 'lucide-react';
6
5
  import { clsx } from 'clsx';
7
6
  import { twMerge } from 'tailwind-merge';
@@ -149,10 +148,23 @@ function BoxModelOverlay({ enabled, target }) {
149
148
  /* @__PURE__ */ jsx("div", { ref: contentRef, className: band, "aria-hidden": true })
150
149
  ] });
151
150
  }
151
+ function usePrefersReducedMotion() {
152
+ const [reduced, setReduced] = useState(false);
153
+ useEffect(() => {
154
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
155
+ const sync = () => setReduced(mq.matches);
156
+ sync();
157
+ mq.addEventListener("change", sync);
158
+ return () => mq.removeEventListener("change", sync);
159
+ }, []);
160
+ return reduced;
161
+ }
152
162
  var MONO = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace";
153
163
  var PROMPT_MS = 22;
154
164
  var LINE_GAP_MS = 300;
155
165
  var AFTER_LAST_MS = 450;
166
+ var EASE_OUT = "cubic-bezier(0.4, 0, 0.2, 1)";
167
+ var DIALOG_EASE = "cubic-bezier(0.34, 1.3, 0.64, 1)";
156
168
  function TrafficLightsSvg() {
157
169
  return /* @__PURE__ */ jsxs("svg", { width: "52", height: "12", viewBox: "0 0 52 12", "aria-hidden": true, className: "shrink-0", children: [
158
170
  /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: "#FF5F57" }),
@@ -293,11 +305,12 @@ function TerminalTyping({
293
305
  }
294
306
  );
295
307
  }
296
- var EASE_OUT = [0.4, 0, 0.2, 1];
297
308
  var DISMISS_AFTER_TYPING_MS = 5200;
298
309
  function CopySuccessOverlay({ open, onClose }) {
299
- const reduceMotion = useReducedMotion();
310
+ const reduceMotion = usePrefersReducedMotion();
300
311
  const [typingComplete, setTypingComplete] = useState(false);
312
+ const [mounted, setMounted] = useState(false);
313
+ const [animIn, setAnimIn] = useState(false);
301
314
  const handleTypingComplete = useCallback(() => setTypingComplete(true), []);
302
315
  useEffect(() => {
303
316
  if (!open) {
@@ -320,36 +333,57 @@ function CopySuccessOverlay({ open, onClose }) {
320
333
  const t = window.setTimeout(onClose, DISMISS_AFTER_TYPING_MS);
321
334
  return () => window.clearTimeout(t);
322
335
  }, [open, typingComplete, onClose]);
336
+ useLayoutEffect(() => {
337
+ if (open) setMounted(true);
338
+ }, [open]);
339
+ useEffect(() => {
340
+ if (!open) {
341
+ setAnimIn(false);
342
+ return;
343
+ }
344
+ setAnimIn(false);
345
+ const id = requestAnimationFrame(() => {
346
+ requestAnimationFrame(() => setAnimIn(true));
347
+ });
348
+ return () => cancelAnimationFrame(id);
349
+ }, [open]);
350
+ const onBackdropTransitionEnd = useCallback((e) => {
351
+ if (e.target !== e.currentTarget) return;
352
+ if (e.propertyName !== "opacity") return;
353
+ if (!open) setMounted(false);
354
+ }, [open]);
323
355
  if (typeof document === "undefined") return null;
324
356
  const running = open && !typingComplete && !reduceMotion;
357
+ const backdropStyle = {
358
+ opacity: animIn ? 1 : 0,
359
+ transition: reduceMotion ? "none" : `opacity 220ms ${EASE_OUT}`
360
+ };
361
+ const dialogStyle = reduceMotion ? {
362
+ opacity: animIn ? 1 : 0,
363
+ transition: `opacity 180ms ${EASE_OUT}`
364
+ } : {
365
+ opacity: animIn ? 1 : 0,
366
+ transform: animIn ? "scale(1) translateY(0)" : "scale(0.94) translateY(14px)",
367
+ transition: `opacity 240ms ${EASE_OUT}, transform 450ms ${DIALOG_EASE}`
368
+ };
325
369
  return createPortal(
326
- /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
327
- motion.div,
370
+ /* @__PURE__ */ jsx(Fragment, { children: mounted && /* @__PURE__ */ jsx(
371
+ "div",
328
372
  {
329
373
  className: "fixed inset-0 z-[10020] flex items-center justify-center bg-black/50 p-4 backdrop-blur-[3px]",
330
- style: { fontFamily: MONO },
331
- initial: { opacity: 0 },
332
- animate: { opacity: 1 },
333
- exit: { opacity: 0 },
334
- transition: { duration: 0.22, ease: EASE_OUT },
374
+ style: { ...backdropStyle, fontFamily: MONO },
335
375
  onClick: onClose,
376
+ onTransitionEnd: onBackdropTransitionEnd,
336
377
  role: "presentation",
337
378
  children: /* @__PURE__ */ jsxs(
338
- motion.div,
379
+ "div",
339
380
  {
340
381
  role: "dialog",
341
382
  "aria-modal": "true",
342
383
  "aria-labelledby": "flux-copy-success-title",
343
384
  className: "max-h-[min(90vh,640px)] w-full max-w-[520px] overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-[0_24px_80px_rgba(0,0,0,0.2)]",
344
- onClick: (e) => e.stopPropagation(),
345
- initial: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.94, y: 14 },
346
- animate: reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, y: 0 },
347
- exit: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.97, y: 8 },
348
- transition: reduceMotion ? { duration: 0.18, ease: EASE_OUT } : {
349
- opacity: { duration: 0.24, ease: EASE_OUT },
350
- scale: { type: "spring", stiffness: 420, damping: 32, mass: 0.85 },
351
- y: { type: "spring", stiffness: 420, damping: 34, mass: 0.85 }
352
- },
385
+ style: dialogStyle,
386
+ onClick: (ev) => ev.stopPropagation(),
353
387
  children: [
354
388
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-zinc-200/90 bg-zinc-50/90 px-3 py-2", children: [
355
389
  /* @__PURE__ */ jsx(TrafficLightsSvg, {}),
@@ -374,8 +408,7 @@ function CopySuccessOverlay({ open, onClose }) {
374
408
  ]
375
409
  }
376
410
  )
377
- },
378
- "flux-copy-success-overlay"
411
+ }
379
412
  ) }),
380
413
  document.body
381
414
  );
@@ -7459,6 +7492,7 @@ function DesignEditorImpl({
7459
7492
  blockPageInteractions
7460
7493
  }) {
7461
7494
  const [enabled, setEnabled] = useState(false);
7495
+ const [isExiting, setIsExiting] = useState(false);
7462
7496
  const [width, setWidth] = useState(DEFAULT_WIDTH);
7463
7497
  const [layoutW, setLayoutW] = useState(0);
7464
7498
  const resizeRef = useRef(null);
@@ -7572,6 +7606,9 @@ button[data-flux-floating-toggle]:hover {
7572
7606
  flushSync(() => {
7573
7607
  setExitClipVp(getEditorToggleViewportCenter());
7574
7608
  });
7609
+ flushSync(() => {
7610
+ setIsExiting(true);
7611
+ });
7575
7612
  flushSync(() => {
7576
7613
  setEnabled(false);
7577
7614
  });
@@ -7695,7 +7732,11 @@ button[data-flux-floating-toggle]:hover {
7695
7732
  }
7696
7733
  return copyCombinedPromptToClipboard(items, structuralReorders);
7697
7734
  }, [getCopyBatchItems, structuralReorders]);
7698
- const reduceMotion = useReducedMotion();
7735
+ const reduceMotion = usePrefersReducedMotion();
7736
+ const panelRef = useRef(null);
7737
+ const panelVisible = enabled || isExiting;
7738
+ const irisOpenDoneRef = useRef(false);
7739
+ const exitAnimStartedRef = useRef(false);
7699
7740
  const themeRevealDuration = useMemo(() => readThemeTransitionDurationSeconds(), []);
7700
7741
  const effectiveWidth = useMemo(() => {
7701
7742
  const lw = layoutW > 0 ? layoutW : layoutViewportWidth();
@@ -7706,10 +7747,80 @@ button[data-flux-floating-toggle]:hover {
7706
7747
  const openO = viewportToPanelClipOrigin(openClipVpRef.current.x, openClipVpRef.current.y, effectiveWidth);
7707
7748
  const exitVpForClip = exitClipVp ?? defaultExitViewport();
7708
7749
  const closeO = viewportToPanelClipOrigin(exitVpForClip.x, exitVpForClip.y, effectiveWidth);
7709
- const irisAt = exitClipVp != null ? closeO : openO;
7710
- const panelContent = /* @__PURE__ */ jsx(AnimatePresence, { onExitComplete: () => setExitClipVp(null), children: enabled && /* @__PURE__ */ jsxs(
7711
- motion.div,
7750
+ const easeReveal = `cubic-bezier(${THEME_REVEAL_EASE.join(",")})`;
7751
+ const easeExit = `cubic-bezier(${IRIS_EXIT_EASE.join(",")})`;
7752
+ useEffect(() => {
7753
+ if (!enabled) irisOpenDoneRef.current = false;
7754
+ }, [enabled]);
7755
+ useEffect(() => {
7756
+ if (!isExiting) exitAnimStartedRef.current = false;
7757
+ }, [isExiting]);
7758
+ useLayoutEffect(() => {
7759
+ if (!panelVisible || !enabled || isExiting) return;
7760
+ if (irisOpenDoneRef.current) return;
7761
+ const el = panelRef.current;
7762
+ if (!el) return;
7763
+ irisOpenDoneRef.current = true;
7764
+ if (reduceMotion) {
7765
+ el.style.transition = "";
7766
+ el.style.clipPath = "none";
7767
+ return;
7768
+ }
7769
+ el.style.transition = "none";
7770
+ el.style.clipPath = `circle(0px at ${openO.ox}px ${openO.oy}px)`;
7771
+ const id = requestAnimationFrame(() => {
7772
+ requestAnimationFrame(() => {
7773
+ el.style.transition = `clip-path ${themeRevealDuration}s ${easeReveal}`;
7774
+ el.style.clipPath = `circle(150vmax at ${openO.ox}px ${openO.oy}px)`;
7775
+ });
7776
+ });
7777
+ return () => cancelAnimationFrame(id);
7778
+ }, [
7779
+ panelVisible,
7780
+ enabled,
7781
+ isExiting,
7782
+ reduceMotion,
7783
+ themeRevealDuration,
7784
+ easeReveal,
7785
+ openO.ox,
7786
+ openO.oy
7787
+ ]);
7788
+ useLayoutEffect(() => {
7789
+ if (!isExiting || enabled) return;
7790
+ if (exitAnimStartedRef.current) return;
7791
+ const el = panelRef.current;
7792
+ if (!el) return;
7793
+ exitAnimStartedRef.current = true;
7794
+ if (reduceMotion) {
7795
+ el.style.transition = "";
7796
+ el.style.clipPath = `circle(0px at ${closeO.ox}px ${closeO.oy}px)`;
7797
+ queueMicrotask(() => {
7798
+ setIsExiting(false);
7799
+ setExitClipVp(null);
7800
+ });
7801
+ return;
7802
+ }
7803
+ el.style.transition = "none";
7804
+ el.style.clipPath = `circle(150vmax at ${openO.ox}px ${openO.oy}px)`;
7805
+ void el.offsetHeight;
7806
+ el.style.transition = `clip-path ${themeRevealDuration}s ${easeExit}`;
7807
+ el.style.clipPath = `circle(0px at ${closeO.ox}px ${closeO.oy}px)`;
7808
+ }, [isExiting, enabled, reduceMotion, themeRevealDuration, easeExit, openO.ox, openO.oy, closeO.ox, closeO.oy]);
7809
+ const onPanelTransitionEnd = useCallback(
7810
+ (e) => {
7811
+ if (e.propertyName !== "clip-path") return;
7812
+ if (!isExiting || enabled || reduceMotion) return;
7813
+ setIsExiting(false);
7814
+ setExitClipVp(null);
7815
+ },
7816
+ [isExiting, enabled, reduceMotion]
7817
+ );
7818
+ const panelContent = /* @__PURE__ */ jsx(Fragment, { children: panelVisible && /* @__PURE__ */ jsxs(
7819
+ "div",
7712
7820
  {
7821
+ ref: panelRef,
7822
+ "data-flux-ui": true,
7823
+ onTransitionEnd: onPanelTransitionEnd,
7713
7824
  style: {
7714
7825
  position: "fixed",
7715
7826
  top: 8,
@@ -7726,18 +7837,8 @@ button[data-flux-floating-toggle]:hover {
7726
7837
  color: "#D9D9D9",
7727
7838
  colorScheme: "dark",
7728
7839
  WebkitFontSmoothing: "antialiased",
7729
- pointerEvents: "auto"
7730
- },
7731
- initial: reduceMotion === true ? { clipPath: "none" } : { clipPath: `circle(0px at ${openO.ox}px ${openO.oy}px)` },
7732
- animate: reduceMotion === true ? { clipPath: "none" } : { clipPath: `circle(150vmax at ${irisAt.ox}px ${irisAt.oy}px)` },
7733
- exit: reduceMotion === true ? { clipPath: "none", transition: { duration: 0 } } : {
7734
- clipPath: `circle(0px at ${closeO.ox}px ${closeO.oy}px)`,
7735
- transition: {
7736
- clipPath: { duration: themeRevealDuration, ease: IRIS_EXIT_EASE }
7737
- }
7738
- },
7739
- transition: reduceMotion === true ? { duration: 0 } : {
7740
- clipPath: { duration: themeRevealDuration, ease: THEME_REVEAL_EASE }
7840
+ pointerEvents: "auto",
7841
+ willChange: reduceMotion ? void 0 : "clip-path"
7741
7842
  },
7742
7843
  children: [
7743
7844
  /* @__PURE__ */ jsx(
@@ -7764,6 +7865,9 @@ button[data-flux-floating-toggle]:hover {
7764
7865
  flushSync(() => {
7765
7866
  setExitClipVp(getEditorToggleViewportCenter());
7766
7867
  });
7868
+ flushSync(() => {
7869
+ setIsExiting(true);
7870
+ });
7767
7871
  flushSync(() => {
7768
7872
  setEnabled(false);
7769
7873
  });
@@ -7794,8 +7898,7 @@ button[data-flux-floating-toggle]:hover {
7794
7898
  selectedElement ? getElementPath(selectedElement) : "none"
7795
7899
  ) })
7796
7900
  ]
7797
- },
7798
- "flux-panel"
7901
+ }
7799
7902
  ) });
7800
7903
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7801
7904
  !enabled && /* @__PURE__ */ jsx(