@sanmid/flux 0.1.4 → 0.1.7

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.
@@ -0,0 +1,240 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type DesignEditorProps = {
4
+ /**
5
+ * When true, the editor mounts in production builds.
6
+ * Default is **development only** (`process.env.NODE_ENV === "development"`).
7
+ */
8
+ force?: boolean;
9
+ /**
10
+ * Classic margin / border / padding overlay bands on the selected node.
11
+ * Default **false** — only the mint selection ring is shown unless you opt in.
12
+ */
13
+ showBoxModel?: boolean;
14
+ /**
15
+ * When the overlay is active, block default actions on interactive page nodes (buttons, links, inputs, …)
16
+ * via capture-phase listeners. Opt-in for host apps (e.g. playgrounds); default **false**.
17
+ */
18
+ blockPageInteractions?: boolean;
19
+ };
20
+ /**
21
+ * Visual design editor overlay. **Does not render in production** unless `force` is set.
22
+ */
23
+ declare function DesignEditor({ force, showBoxModel, blockPageInteractions, }: DesignEditorProps): react_jsx_runtime.JSX.Element | null;
24
+
25
+ /** Persisted for copy prompt after a successful DOM reorder. */
26
+ type FluxStructuralReorder = {
27
+ type: "reorder";
28
+ containerSelector: string;
29
+ containerComponents: string[];
30
+ originalOrder: string[];
31
+ newOrder: string[];
32
+ childrenType: "array" | "static";
33
+ containerSourceFile: {
34
+ fileName: string;
35
+ lineNumber: number;
36
+ columnNumber?: number;
37
+ } | null;
38
+ childSources?: {
39
+ label: string;
40
+ sourceFile: {
41
+ fileName: string;
42
+ lineNumber: number;
43
+ columnNumber?: number;
44
+ };
45
+ }[];
46
+ timestamp: number;
47
+ };
48
+
49
+ /**
50
+ * Live preview engine using Constructable Stylesheets when supported,
51
+ * otherwise a single <style> tag in document.head.
52
+ *
53
+ * Applies CSS changes without mutating the host element's inline styles.
54
+ */
55
+ interface AppliedRule {
56
+ selector: string;
57
+ property: string;
58
+ value: string;
59
+ }
60
+ declare class PreviewEngine {
61
+ private sheet;
62
+ private fallbackStyle;
63
+ private rules;
64
+ private attached;
65
+ constructor();
66
+ attach(): void;
67
+ detach(): void;
68
+ applyChange(selector: string, property: string, value: string): void;
69
+ removeChange(selector: string, property: string): void;
70
+ removeAllChanges(selector: string): void;
71
+ clearAll(): void;
72
+ getChanges(): readonly AppliedRule[];
73
+ /** Last preview value applied for this selector + property (e.g. `100%` when Fill), or undefined. */
74
+ getPreviewValue(selector: string, property: string): string | undefined;
75
+ destroy(): void;
76
+ private flush;
77
+ }
78
+ /**
79
+ * Generate a unique CSS selector for an element using @medv/finder.
80
+ * Falls back to a manual path if finder throws (e.g., SVG elements).
81
+ */
82
+ declare function getUniqueSelector(el: Element): string;
83
+ /**
84
+ * `parent > :nth-child(n)` so preview rules (e.g. `order`) target the exact direct child.
85
+ * Finder-only selectors often collide for sibling elements; this stays stable for flex/grid children.
86
+ */
87
+ declare function getDirectChildSelector(parent: Element, child: Element): string;
88
+ /**
89
+ * Selector used for {@link PreviewEngine} rules. Verifies each candidate with
90
+ * `querySelectorAll(sel).length === 1` and that node is `el`, so injected rules
91
+ * cannot hit multiple elements (e.g. all `button`s). Last resort: a temporary
92
+ * `data-flux-preview-target` id — call {@link removePreviewTargetAttribute} when deselecting.
93
+ */
94
+ declare function getReliableSelectorForPreview(el: Element): {
95
+ selector: string;
96
+ usedAttrFallback: boolean;
97
+ };
98
+ declare function removePreviewTargetAttribute(el: Element | null | undefined): void;
99
+
100
+ interface StyleSnapshot {
101
+ fontFamily: string;
102
+ fontSize: string;
103
+ fontWeight: string;
104
+ lineHeight: string;
105
+ letterSpacing: string;
106
+ color: string;
107
+ textAlign: string;
108
+ width: string;
109
+ height: string;
110
+ padding: string;
111
+ margin: string;
112
+ paddingTop: string;
113
+ paddingRight: string;
114
+ paddingBottom: string;
115
+ paddingLeft: string;
116
+ marginTop: string;
117
+ marginRight: string;
118
+ marginBottom: string;
119
+ marginLeft: string;
120
+ /** CSS `position` (static, relative, absolute, fixed, sticky). */
121
+ position: string;
122
+ /** Inset offsets for positioned layout (`top` / `right` / `bottom` / `left`). */
123
+ top: string;
124
+ right: string;
125
+ bottom: string;
126
+ left: string;
127
+ /** CSS `align-self` — item alignment in flex/grid. */
128
+ alignSelf: string;
129
+ /** CSS `justify-self` — grid item alignment on row axis. */
130
+ justifySelf: string;
131
+ overflow: string;
132
+ display: string;
133
+ flexDirection: string;
134
+ justifyContent: string;
135
+ alignItems: string;
136
+ gap: string;
137
+ rowGap: string;
138
+ columnGap: string;
139
+ gridTemplateColumns: string;
140
+ gridTemplateRows: string;
141
+ backgroundColor: string;
142
+ /** Computed `background-image` (gradients count as fill for `hasFill`-style checks). */
143
+ backgroundImage: string;
144
+ borderTopWidth: string;
145
+ borderRightWidth: string;
146
+ borderBottomWidth: string;
147
+ borderLeftWidth: string;
148
+ borderTopLeftRadius: string;
149
+ borderTopRightRadius: string;
150
+ borderBottomRightRadius: string;
151
+ borderBottomLeftRadius: string;
152
+ borderTopColor: string;
153
+ borderRightColor: string;
154
+ borderBottomColor: string;
155
+ borderLeftColor: string;
156
+ borderTopStyle: string;
157
+ borderRightStyle: string;
158
+ borderBottomStyle: string;
159
+ borderLeftStyle: string;
160
+ borderColor: string;
161
+ borderRadius: string;
162
+ borderStyle: string;
163
+ boxShadow: string;
164
+ opacity: string;
165
+ }
166
+ type DesignEditorElement = Element & {
167
+ style: CSSStyleDeclaration;
168
+ };
169
+ declare function isStyleableElement(target: EventTarget | null): target is DesignEditorElement;
170
+ declare function getComputedSnapshot(el: Element): StyleSnapshot;
171
+ declare function getElementPath(el: Element): string;
172
+ declare function getElementSelector(el: Element): string;
173
+
174
+ /**
175
+ * Border-box dimensions in viewport CSS pixels, rounded for the overlay label.
176
+ */
177
+ declare function formatSelectionDimensions(width: number, height: number): string;
178
+
179
+ /**
180
+ * React fiber traversal for extracting component hierarchy and source file locations.
181
+ * Works with React 18+ dev builds that attach `__reactFiber$` to DOM nodes.
182
+ */
183
+ interface ReactComponentInfo {
184
+ components: string[];
185
+ sourceFile: {
186
+ fileName: string;
187
+ lineNumber: number;
188
+ columnNumber?: number;
189
+ } | null;
190
+ }
191
+ declare function getExactSourceLocation(el: Element): string | null;
192
+ declare function getReactComponentStack(el: Element): string | null;
193
+ declare function getReactComponentInfo(el: Element): ReactComponentInfo;
194
+
195
+ /**
196
+ * Lazy token scanner — walks document.styleSheets to discover CSS custom properties
197
+ * and utility class tokens. Scans on-demand per category, cached until stylesheet count changes.
198
+ */
199
+ type TokenCategory = "colors" | "spacing" | "typography" | "borders" | "effects" | "layout";
200
+ type CssFramework = "tailwind" | "css-modules" | "custom" | "unknown";
201
+ interface DesignToken {
202
+ className: string;
203
+ property: string;
204
+ value: string;
205
+ category: TokenCategory;
206
+ }
207
+ interface CssVariable {
208
+ name: string;
209
+ value: string;
210
+ category: TokenCategory;
211
+ }
212
+ interface TokenScanResult {
213
+ tokens: DesignToken[];
214
+ variables: CssVariable[];
215
+ framework: CssFramework;
216
+ }
217
+ declare function scanTokens(): TokenScanResult;
218
+ /** One-line summary for AI prompts (design-token preamble). */
219
+ declare function summarizeTokenSystem(): string | null;
220
+ declare function detectFramework(): CssFramework;
221
+ declare function getTokensForProperty(cssProperty: string): DesignToken[];
222
+ declare function getVariablesForProperty(cssProperty: string): CssVariable[];
223
+ /**
224
+ * Variables that appear in real CSS rules for this property first, then category fallback.
225
+ */
226
+ declare function getVariablesForPropertySmart(cssPropertyKebab: string): CssVariable[];
227
+
228
+ /**
229
+ * Maps CSS property names (kebab-case) to custom property names that appear
230
+ * in stylesheet values via var(--name), so pickers can prefer vars actually
231
+ * used for that property in the host app.
232
+ */
233
+ declare function invalidateVariableUsageCache(): void;
234
+ /** Custom property names (--foo) that appear in rules for this CSS property. */
235
+ declare function getVariablesUsedForCssProperty(kebabProperty: string): string[];
236
+
237
+ /** Collect `font-family` values from parsed stylesheets (best-effort; skips cross-origin). */
238
+ declare function scanStylesheetFontFamilies(): string[];
239
+
240
+ export { type CssFramework, type CssVariable, DesignEditor, type DesignEditorElement, type DesignEditorProps, type DesignToken, type FluxStructuralReorder, PreviewEngine, type ReactComponentInfo, type StyleSnapshot, type TokenCategory, type TokenScanResult, detectFramework, formatSelectionDimensions, getComputedSnapshot, getDirectChildSelector, getElementPath, getElementSelector, getExactSourceLocation, getReactComponentInfo, getReactComponentStack, getReliableSelectorForPreview, getTokensForProperty, getUniqueSelector, getVariablesForProperty, getVariablesForPropertySmart, getVariablesUsedForCssProperty, invalidateVariableUsageCache, isStyleableElement, removePreviewTargetAttribute, scanStylesheetFontFamilies, scanTokens, summarizeTokenSystem };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
1
+ "use client";
2
2
  import { createContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useContext } from 'react';
3
3
  import { flushSync, createPortal } from 'react-dom';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
  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
6
  import { clsx } from 'clsx';
7
7
  import { twMerge } from 'tailwind-merge';
@@ -149,10 +149,23 @@ function BoxModelOverlay({ enabled, target }) {
149
149
  /* @__PURE__ */ jsx("div", { ref: contentRef, className: band, "aria-hidden": true })
150
150
  ] });
151
151
  }
152
+ function usePrefersReducedMotion() {
153
+ const [reduced, setReduced] = useState(false);
154
+ useEffect(() => {
155
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
156
+ const sync = () => setReduced(mq.matches);
157
+ sync();
158
+ mq.addEventListener("change", sync);
159
+ return () => mq.removeEventListener("change", sync);
160
+ }, []);
161
+ return reduced;
162
+ }
152
163
  var MONO = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace";
153
164
  var PROMPT_MS = 22;
154
165
  var LINE_GAP_MS = 300;
155
166
  var AFTER_LAST_MS = 450;
167
+ var EASE_OUT = "cubic-bezier(0.4, 0, 0.2, 1)";
168
+ var DIALOG_EASE = "cubic-bezier(0.34, 1.3, 0.64, 1)";
156
169
  function TrafficLightsSvg() {
157
170
  return /* @__PURE__ */ jsxs("svg", { width: "52", height: "12", viewBox: "0 0 52 12", "aria-hidden": true, className: "shrink-0", children: [
158
171
  /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: "#FF5F57" }),
@@ -293,11 +306,12 @@ function TerminalTyping({
293
306
  }
294
307
  );
295
308
  }
296
- var EASE_OUT = [0.4, 0, 0.2, 1];
297
309
  var DISMISS_AFTER_TYPING_MS = 5200;
298
310
  function CopySuccessOverlay({ open, onClose }) {
299
- const reduceMotion = useReducedMotion();
311
+ const reduceMotion = usePrefersReducedMotion();
300
312
  const [typingComplete, setTypingComplete] = useState(false);
313
+ const [mounted, setMounted] = useState(false);
314
+ const [animIn, setAnimIn] = useState(false);
301
315
  const handleTypingComplete = useCallback(() => setTypingComplete(true), []);
302
316
  useEffect(() => {
303
317
  if (!open) {
@@ -320,36 +334,57 @@ function CopySuccessOverlay({ open, onClose }) {
320
334
  const t = window.setTimeout(onClose, DISMISS_AFTER_TYPING_MS);
321
335
  return () => window.clearTimeout(t);
322
336
  }, [open, typingComplete, onClose]);
337
+ useLayoutEffect(() => {
338
+ if (open) setMounted(true);
339
+ }, [open]);
340
+ useEffect(() => {
341
+ if (!open) {
342
+ setAnimIn(false);
343
+ return;
344
+ }
345
+ setAnimIn(false);
346
+ const id = requestAnimationFrame(() => {
347
+ requestAnimationFrame(() => setAnimIn(true));
348
+ });
349
+ return () => cancelAnimationFrame(id);
350
+ }, [open]);
351
+ const onBackdropTransitionEnd = useCallback((e) => {
352
+ if (e.target !== e.currentTarget) return;
353
+ if (e.propertyName !== "opacity") return;
354
+ if (!open) setMounted(false);
355
+ }, [open]);
323
356
  if (typeof document === "undefined") return null;
324
357
  const running = open && !typingComplete && !reduceMotion;
358
+ const backdropStyle = {
359
+ opacity: animIn ? 1 : 0,
360
+ transition: reduceMotion ? "none" : `opacity 220ms ${EASE_OUT}`
361
+ };
362
+ const dialogStyle = reduceMotion ? {
363
+ opacity: animIn ? 1 : 0,
364
+ transition: `opacity 180ms ${EASE_OUT}`
365
+ } : {
366
+ opacity: animIn ? 1 : 0,
367
+ transform: animIn ? "scale(1) translateY(0)" : "scale(0.94) translateY(14px)",
368
+ transition: `opacity 240ms ${EASE_OUT}, transform 450ms ${DIALOG_EASE}`
369
+ };
325
370
  return createPortal(
326
- /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
327
- motion.div,
371
+ /* @__PURE__ */ jsx(Fragment, { children: mounted && /* @__PURE__ */ jsx(
372
+ "div",
328
373
  {
329
374
  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 },
375
+ style: { ...backdropStyle, fontFamily: MONO },
335
376
  onClick: onClose,
377
+ onTransitionEnd: onBackdropTransitionEnd,
336
378
  role: "presentation",
337
379
  children: /* @__PURE__ */ jsxs(
338
- motion.div,
380
+ "div",
339
381
  {
340
382
  role: "dialog",
341
383
  "aria-modal": "true",
342
384
  "aria-labelledby": "flux-copy-success-title",
343
385
  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
- },
386
+ style: dialogStyle,
387
+ onClick: (ev) => ev.stopPropagation(),
353
388
  children: [
354
389
  /* @__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
390
  /* @__PURE__ */ jsx(TrafficLightsSvg, {}),
@@ -374,8 +409,7 @@ function CopySuccessOverlay({ open, onClose }) {
374
409
  ]
375
410
  }
376
411
  )
377
- },
378
- "flux-copy-success-overlay"
412
+ }
379
413
  ) }),
380
414
  document.body
381
415
  );
@@ -7366,6 +7400,9 @@ async function writeTextToClipboard(text) {
7366
7400
  async function copyCombinedPromptToClipboard(items, structuralReorders) {
7367
7401
  return writeTextToClipboard(buildCombinedCopyPrompt(items, structuralReorders));
7368
7402
  }
7403
+
7404
+ // src/host-styles.ts
7405
+ var fluxHostStyles = ".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-right-0{right:0}.-right-1{right:-.25rem}.-top-0{top:0}.-top-1{top:-.25rem}.bottom-2{bottom:.5rem}.left-0{left:0}.right-0{right:0}.right-1{right:.25rem}.right-2{right:.5rem}.right-4{right:1rem}.right-8{right:2rem}.top-0{top:0}.top-1{top:.25rem}.top-2{top:.5rem}.top-4{top:1rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-\\[10003\\]{z-index:10003}.z-\\[10020\\]{z-index:10020}.z-\\[1\\]{z-index:1}.z-\\[9997\\]{z-index:9997}.z-\\[9998\\]{z-index:9998}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.-mt-px{margin-top:-1px}.mb-2{margin-bottom:.5rem}.ml-px{margin-left:1px}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.box-border{box-sizing:border-box}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.size-10{width:2.5rem;height:2.5rem}.size-11{width:2.75rem;height:2.75rem}.size-3{width:.75rem;height:.75rem}.size-3\\.5{width:.875rem;height:.875rem}.size-4{width:1rem;height:1rem}.size-9{width:2.25rem;height:2.25rem}.size-\\[14px\\]{width:14px;height:14px}.size-\\[5px\\]{width:5px;height:5px}.h-3{height:.75rem}.h-4{height:1rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-\\[min\\(90vh\\2c 640px\\)\\]{max-height:min(90vh,640px)}.min-h-0{min-height:0}.w-0{width:0}.w-0\\.5{width:.125rem}.w-1{width:.25rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-\\[4\\.5rem\\]{width:4.5rem}.w-\\[5\\.5rem\\]{width:5.5rem}.w-\\[52px\\]{width:52px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\\[2rem\\]{min-width:2rem}.min-w-full{min-width:100%}.max-w-\\[520px\\]{max-width:520px}.max-w-\\[5rem\\]{max-width:5rem}.max-w-\\[min\\(18rem\\2c calc\\(100vw-20px\\)\\)\\]{max-width:min(18rem,calc(100vw - 20px))}.max-w-full{max-width:100%}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-1{--tw-translate-y:-0.25rem}.-translate-y-1,.scale-\\[0\\.92\\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-\\[0\\.92\\]{--tw-scale-x:0.92;--tw-scale-y:0.92}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-col-resize{cursor:col-resize}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-\\[13px\\]{gap:13px}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-small{border-radius:8px}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-0{border-width:0}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-\\[\\#202020\\]{--tw-border-opacity:1;border-color:rgb(32 32 32/var(--tw-border-opacity,1))}.border-\\[\\#444\\]{--tw-border-opacity:1;border-color:rgb(68 68 68/var(--tw-border-opacity,1))}.border-\\[\\#555\\]{--tw-border-opacity:1;border-color:rgb(85 85 85/var(--tw-border-opacity,1))}.border-amber-800{--tw-border-opacity:1;border-color:rgb(146 64 14/var(--tw-border-opacity,1))}.border-amber-800\\/35{border-color:rgba(146,64,14,.35)}.border-zinc-100{--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1))}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-zinc-200\\/90{border-color:hsla(240,6%,90%,.9)}.bg-\\[\\#202020\\]{--tw-bg-opacity:1;background-color:rgb(32 32 32/var(--tw-bg-opacity,1))}.bg-\\[\\#2a2a2a\\]{--tw-bg-opacity:1;background-color:rgb(42 42 42/var(--tw-bg-opacity,1))}.bg-\\[\\#333333\\]{--tw-bg-opacity:1;background-color:rgb(51 51 51/var(--tw-bg-opacity,1))}.bg-\\[\\#555\\]{--tw-bg-opacity:1;background-color:rgb(85 85 85/var(--tw-bg-opacity,1))}.bg-\\[\\#F3F3F3\\]{--tw-bg-opacity:1;background-color:rgb(243 243 243/var(--tw-bg-opacity,1))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\\/50{background-color:rgba(0,0,0,.5)}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-zinc-300{--tw-bg-opacity:1;background-color:rgb(212 212 216/var(--tw-bg-opacity,1))}.bg-zinc-400{--tw-bg-opacity:1;background-color:rgb(161 161 170/var(--tw-bg-opacity,1))}.bg-zinc-50{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.bg-zinc-50\\/90{background-color:hsla(0,0%,98%,.9)}.\\!fill-none{fill:none!important}.p-0{padding:0}.p-0\\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\\.5{padding:.375rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-0{padding-left:0;padding-right:0}.px-0\\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0{padding-top:0;padding-bottom:0}.py-0\\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-0{padding-left:0}.pl-0\\.5{padding-left:.125rem}.pr-0{padding-right:0}.pr-10{padding-right:2.5rem}.pr-11{padding-right:2.75rem}.pr-6{padding-right:1.5rem}.pr-7{padding-right:1.75rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-\\[\\'Inter\\'\\2c system-ui\\2c sans-serif\\]{font-family:Inter,system-ui,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\\[10px\\]{font-size:10px}.text-\\[11px\\]{font-size:11px}.text-\\[12px\\]{font-size:12px}.text-\\[15px\\]{font-size:15px}.text-\\[7px\\]{font-size:7px}.text-base{font-size:1rem;line-height:1.5rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\\[14px\\]{line-height:14px}.leading-\\[15\\.6px\\]{line-height:15.6px}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-tight{letter-spacing:-.025em}.text-\\[\\#202020\\]{--tw-text-opacity:1;color:rgb(32 32 32/var(--tw-text-opacity,1))}.text-\\[\\#333333\\]{--tw-text-opacity:1;color:rgb(51 51 51/var(--tw-text-opacity,1))}.text-\\[\\#71717a\\]{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.text-\\[\\#7C7C7C\\]{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity,1))}.text-\\[\\#93c47d\\]{--tw-text-opacity:1;color:rgb(147 196 125/var(--tw-text-opacity,1))}.text-\\[\\#D9D9D9\\]{--tw-text-opacity:1;color:rgb(217 217 217/var(--tw-text-opacity,1))}.text-\\[\\#b45309\\]{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-\\[\\#c2410c\\]{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity,1))}.text-\\[\\#d9d9d9\\]{--tw-text-opacity:1;color:rgb(217 217 217/var(--tw-text-opacity,1))}.text-emerald-600{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-zinc-400{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.text-zinc-500{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.text-zinc-700{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.text-zinc-800{--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1))}.text-zinc-900{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-\\[0_24px_80px_rgba\\(0\\2c 0\\2c 0\\2c 0\\.2\\)\\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\\[0_24px_80px_rgba\\(0\\2c 0\\2c 0\\2c 0\\.2\\)\\]{--tw-shadow:0 24px 80px rgba(0,0,0,.2);--tw-shadow-colored:0 24px 80px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.\\!outline{outline-style:solid!important}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\\[3px\\]{--tw-backdrop-blur:blur(3px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[transform\\]{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-opacity{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\\[font-synthesis\\:none\\]{font-synthesis:none}.hover\\:bg-\\[\\#2a2a2a\\]:hover{--tw-bg-opacity:1;background-color:rgb(42 42 42/var(--tw-bg-opacity,1))}.hover\\:bg-\\[\\#333333\\]:hover{--tw-bg-opacity:1;background-color:rgb(51 51 51/var(--tw-bg-opacity,1))}.hover\\:text-\\[\\#D9D9D9\\]:hover{--tw-text-opacity:1;color:rgb(217 217 217/var(--tw-text-opacity,1))}.hover\\:brightness-110:hover{--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus-visible\\:outline:focus-visible{outline-style:solid}.focus-visible\\:outline-2:focus-visible{outline-width:2px}.focus-visible\\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\\:outline-\\[\\#D9D9D9\\]\\/40:focus-visible{outline-color:hsla(0,0%,85%,.4)}.active\\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:focus-within .group-focus-within\\:opacity-100{opacity:1}.group:hover .group-hover\\:opacity-100{opacity:1}@media (min-width:640px){.sm\\:max-w-\\[200px\\]{max-width:200px}.sm\\:flex-row{flex-direction:row}.sm\\:items-start{align-items:flex-start}.sm\\:justify-between{justify-content:space-between}.sm\\:gap-6{gap:1.5rem}.sm\\:px-4{padding-left:1rem;padding-right:1rem}.sm\\:px-5{padding-left:1.25rem;padding-right:1.25rem}.sm\\:pt-4{padding-top:1rem}}.\\[\\&\\:\\:-webkit-details-marker\\]\\:hidden::-webkit-details-marker{display:none}.\\[\\&_svg\\]\\:-mt-px svg{margin-top:-1px}.\\[\\&_svg\\]\\:\\!fill-none svg{fill:none!important}";
7369
7406
  var MIN_WIDTH = 240;
7370
7407
  var MAX_WIDTH = 560;
7371
7408
  var DEFAULT_WIDTH = 277;
@@ -7459,6 +7496,7 @@ function DesignEditorImpl({
7459
7496
  blockPageInteractions
7460
7497
  }) {
7461
7498
  const [enabled, setEnabled] = useState(false);
7499
+ const [isExiting, setIsExiting] = useState(false);
7462
7500
  const [width, setWidth] = useState(DEFAULT_WIDTH);
7463
7501
  const [layoutW, setLayoutW] = useState(0);
7464
7502
  const resizeRef = useRef(null);
@@ -7484,6 +7522,15 @@ function DesignEditorImpl({
7484
7522
  setMenuPortalTarget(null);
7485
7523
  };
7486
7524
  }, []);
7525
+ useEffect(() => {
7526
+ const id = "flux-host-tailwind";
7527
+ if (typeof document === "undefined" || document.getElementById(id)) return;
7528
+ const el = document.createElement("style");
7529
+ el.id = id;
7530
+ el.setAttribute("data-flux-host-tailwind", "");
7531
+ el.textContent = fluxHostStyles;
7532
+ document.head.appendChild(el);
7533
+ }, []);
7487
7534
  useEffect(() => {
7488
7535
  const id = "flux-floating-toggle-host-styles";
7489
7536
  if (document.getElementById(id)) return;
@@ -7572,6 +7619,9 @@ button[data-flux-floating-toggle]:hover {
7572
7619
  flushSync(() => {
7573
7620
  setExitClipVp(getEditorToggleViewportCenter());
7574
7621
  });
7622
+ flushSync(() => {
7623
+ setIsExiting(true);
7624
+ });
7575
7625
  flushSync(() => {
7576
7626
  setEnabled(false);
7577
7627
  });
@@ -7695,7 +7745,11 @@ button[data-flux-floating-toggle]:hover {
7695
7745
  }
7696
7746
  return copyCombinedPromptToClipboard(items, structuralReorders);
7697
7747
  }, [getCopyBatchItems, structuralReorders]);
7698
- const reduceMotion = useReducedMotion();
7748
+ const reduceMotion = usePrefersReducedMotion();
7749
+ const panelRef = useRef(null);
7750
+ const panelVisible = enabled || isExiting;
7751
+ const irisOpenDoneRef = useRef(false);
7752
+ const exitAnimStartedRef = useRef(false);
7699
7753
  const themeRevealDuration = useMemo(() => readThemeTransitionDurationSeconds(), []);
7700
7754
  const effectiveWidth = useMemo(() => {
7701
7755
  const lw = layoutW > 0 ? layoutW : layoutViewportWidth();
@@ -7706,10 +7760,80 @@ button[data-flux-floating-toggle]:hover {
7706
7760
  const openO = viewportToPanelClipOrigin(openClipVpRef.current.x, openClipVpRef.current.y, effectiveWidth);
7707
7761
  const exitVpForClip = exitClipVp ?? defaultExitViewport();
7708
7762
  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,
7763
+ const easeReveal = `cubic-bezier(${THEME_REVEAL_EASE.join(",")})`;
7764
+ const easeExit = `cubic-bezier(${IRIS_EXIT_EASE.join(",")})`;
7765
+ useEffect(() => {
7766
+ if (!enabled) irisOpenDoneRef.current = false;
7767
+ }, [enabled]);
7768
+ useEffect(() => {
7769
+ if (!isExiting) exitAnimStartedRef.current = false;
7770
+ }, [isExiting]);
7771
+ useLayoutEffect(() => {
7772
+ if (!panelVisible || !enabled || isExiting) return;
7773
+ if (irisOpenDoneRef.current) return;
7774
+ const el = panelRef.current;
7775
+ if (!el) return;
7776
+ irisOpenDoneRef.current = true;
7777
+ if (reduceMotion) {
7778
+ el.style.transition = "";
7779
+ el.style.clipPath = "none";
7780
+ return;
7781
+ }
7782
+ el.style.transition = "none";
7783
+ el.style.clipPath = `circle(0px at ${openO.ox}px ${openO.oy}px)`;
7784
+ const id = requestAnimationFrame(() => {
7785
+ requestAnimationFrame(() => {
7786
+ el.style.transition = `clip-path ${themeRevealDuration}s ${easeReveal}`;
7787
+ el.style.clipPath = `circle(150vmax at ${openO.ox}px ${openO.oy}px)`;
7788
+ });
7789
+ });
7790
+ return () => cancelAnimationFrame(id);
7791
+ }, [
7792
+ panelVisible,
7793
+ enabled,
7794
+ isExiting,
7795
+ reduceMotion,
7796
+ themeRevealDuration,
7797
+ easeReveal,
7798
+ openO.ox,
7799
+ openO.oy
7800
+ ]);
7801
+ useLayoutEffect(() => {
7802
+ if (!isExiting || enabled) return;
7803
+ if (exitAnimStartedRef.current) return;
7804
+ const el = panelRef.current;
7805
+ if (!el) return;
7806
+ exitAnimStartedRef.current = true;
7807
+ if (reduceMotion) {
7808
+ el.style.transition = "";
7809
+ el.style.clipPath = `circle(0px at ${closeO.ox}px ${closeO.oy}px)`;
7810
+ queueMicrotask(() => {
7811
+ setIsExiting(false);
7812
+ setExitClipVp(null);
7813
+ });
7814
+ return;
7815
+ }
7816
+ el.style.transition = "none";
7817
+ el.style.clipPath = `circle(150vmax at ${openO.ox}px ${openO.oy}px)`;
7818
+ void el.offsetHeight;
7819
+ el.style.transition = `clip-path ${themeRevealDuration}s ${easeExit}`;
7820
+ el.style.clipPath = `circle(0px at ${closeO.ox}px ${closeO.oy}px)`;
7821
+ }, [isExiting, enabled, reduceMotion, themeRevealDuration, easeExit, openO.ox, openO.oy, closeO.ox, closeO.oy]);
7822
+ const onPanelTransitionEnd = useCallback(
7823
+ (e) => {
7824
+ if (e.propertyName !== "clip-path") return;
7825
+ if (!isExiting || enabled || reduceMotion) return;
7826
+ setIsExiting(false);
7827
+ setExitClipVp(null);
7828
+ },
7829
+ [isExiting, enabled, reduceMotion]
7830
+ );
7831
+ const panelContent = /* @__PURE__ */ jsx(Fragment, { children: panelVisible && /* @__PURE__ */ jsxs(
7832
+ "div",
7712
7833
  {
7834
+ ref: panelRef,
7835
+ "data-flux-ui": true,
7836
+ onTransitionEnd: onPanelTransitionEnd,
7713
7837
  style: {
7714
7838
  position: "fixed",
7715
7839
  top: 8,
@@ -7726,18 +7850,8 @@ button[data-flux-floating-toggle]:hover {
7726
7850
  color: "#D9D9D9",
7727
7851
  colorScheme: "dark",
7728
7852
  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 }
7853
+ pointerEvents: "auto",
7854
+ willChange: reduceMotion ? void 0 : "clip-path"
7741
7855
  },
7742
7856
  children: [
7743
7857
  /* @__PURE__ */ jsx(
@@ -7764,6 +7878,9 @@ button[data-flux-floating-toggle]:hover {
7764
7878
  flushSync(() => {
7765
7879
  setExitClipVp(getEditorToggleViewportCenter());
7766
7880
  });
7881
+ flushSync(() => {
7882
+ setIsExiting(true);
7883
+ });
7767
7884
  flushSync(() => {
7768
7885
  setEnabled(false);
7769
7886
  });
@@ -7794,8 +7911,7 @@ button[data-flux-floating-toggle]:hover {
7794
7911
  selectedElement ? getElementPath(selectedElement) : "none"
7795
7912
  ) })
7796
7913
  ]
7797
- },
7798
- "flux-panel"
7914
+ }
7799
7915
  ) });
7800
7916
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7801
7917
  !enabled && /* @__PURE__ */ jsx(