@jbpark/live-editor 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { _ as DRAGGABLE_ITEMS, b as __toESM, d as require_lib$1, f as require_lib, g as DEFAULT_TEMPLATE, p as BINDING_PROP } from "./document-DJDRM79h.mjs";
2
- import { a as detectTypeScript, c as generateSections, d as replaceSections, i as compile, l as getCachedScriptBlob, n as clearCompilationCache, o as extractSections, r as cn, s as generateSection, t as baseModules, u as preloadScripts } from "./utils-D1tlgtgj.mjs";
3
- import { _ as parseArrayExpression, c as extract, d as getCurrentValue, f as parseBinding, g as extractObjectProperties, h as extractNodeValue, i as replaceIds, l as nanoid, m as createNodeFromValue, n as clone, o as update, p as arrayExpressionToCode, r as fillIds, t as validateBindingValue, u as findEditableChildren, v as parseValue, y as generateCode } from "./ast-DiVN0l7x.mjs";
1
+ import { _ as DEFAULT_TEMPLATE, f as require_lib$1, m as BINDING_PROP, n as createSectionPreviewCache, p as require_lib, v as DRAGGABLE_ITEMS, x as __toESM } from "./document-CBw2uF_P.mjs";
2
+ import { a as detectTypeScript, d as replaceSections, i as compile, l as getCachedScriptBlob, n as clearCompilationCache, o as extractSections, r as cn, s as generateSection, t as baseModules, u as preloadScripts } from "./utils-BjYg1Sh8.mjs";
3
+ import { _ as parseArrayExpression, c as extract, d as getCurrentValue, f as parseBinding, g as extractObjectProperties, h as extractNodeValue, i as replaceIds, l as nanoid, m as createNodeFromValue, n as clone, o as update, p as arrayExpressionToCode, r as fillIds, t as validateBindingValue, u as findEditableChildren, v as parseValue, y as generateCode } from "./ast-C0FNDLFQ.mjs";
4
4
  import { generateTailwindCSS } from "./utils/tailwind/index.mjs";
5
5
  import "@jbpark/ui-kit/style.css";
6
6
  import React, { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -9,7 +9,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { DndContext, DragOverlay, PointerSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
10
10
  import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
11
11
  import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
12
- import { useDebouncedValue, useEventListener, useMultiSelect, useMutationObserver, useResizeObserver, useResponsiveSize } from "@jbpark/use-hooks";
12
+ import { useDebounce, useDebouncedValue, useEventListener, useMultiSelect, useMutationObserver, useResizeObserver, useResponsiveSize } from "@jbpark/use-hooks";
13
13
  import { AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Bell, Bookmark, Calendar, Camera, Check, ChevronDown, ChevronRight, Clock, Copy, Download, Edit, Eye, Filter, Heart, Home, Image, Info, LayoutGrid, Link, Mail, Map as Map$1, Menu, MessageCircle, Phone, Plus, Search, Settings, Share, ShoppingCart, Star, Trash, Upload as Upload$1, User, X } from "lucide-react";
14
14
  import { v4 } from "uuid";
15
15
  import { createPortal } from "react-dom";
@@ -70,6 +70,42 @@ const ContextProvider = ({ children }) => {
70
70
  });
71
71
  };
72
72
 
73
+ //#endregion
74
+ //#region src/components/frame/measure.ts
75
+ const FALLBACK_PROBE_HEIGHT = 812;
76
+ const computeProbeHeight = (scrollContainerClientHeight, wrapperInsets) => {
77
+ const usable = scrollContainerClientHeight - wrapperInsets;
78
+ return usable > 0 ? usable : null;
79
+ };
80
+ const isVisuallyHidden = (computed) => computed.visibility === "hidden" || computed.opacity === "0";
81
+ const parseTranslateY = (transform) => {
82
+ if (!transform || transform === "none") return 0;
83
+ const translateYMatch = transform.match(/translateY\(\s*([+-]?\d*\.?\d+)/);
84
+ if (translateYMatch?.[1]) return parseFloat(translateYMatch[1]);
85
+ const translateMatch = transform.match(/translate\([^,]+,\s*([+-]?\d*\.?\d+)/);
86
+ if (translateMatch?.[1]) return parseFloat(translateMatch[1]);
87
+ const matrixMatch = transform.match(/matrix\(\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([+-]?\d*\.?\d+)/);
88
+ return matrixMatch?.[1] ? parseFloat(matrixMatch[1]) : 0;
89
+ };
90
+ const estimatePositionedElementHeight = (offsetHeight, transform, probeHeight) => {
91
+ const offsetY = parseTranslateY(transform);
92
+ return Math.min(offsetY + offsetHeight, probeHeight);
93
+ };
94
+
95
+ //#endregion
96
+ //#region src/components/frame/viewport-units.ts
97
+ const UNIT_MAP = {
98
+ vh: "cqh",
99
+ svh: "cqh",
100
+ lvh: "cqh",
101
+ dvh: "cqh",
102
+ vmin: "cqmin",
103
+ vmax: "cqmax"
104
+ };
105
+ const UNITS_PATTERN = Object.keys(UNIT_MAP).sort((a, b) => b.length - a.length).join("|");
106
+ const VIEWPORT_UNIT_RE = new RegExp(`(?<![\\w.-])(-?(?:\\d+\\.?\\d*|\\.\\d+))(${UNITS_PATTERN})(?![a-zA-Z0-9_-])`, "gi");
107
+ const convertViewportUnits = (css) => css.replace(VIEWPORT_UNIT_RE, (_match, number, unit) => number + UNIT_MAP[unit.toLowerCase()]);
108
+
73
109
  //#endregion
74
110
  //#region src/components/frame/iframe.tsx
75
111
  const EMPTY_STRING_ARRAY = [];
@@ -113,7 +149,7 @@ const IFrame = ({ title = "Live Preview", sandbox, style = {}, scripts = EMPTY_S
113
149
  const fragment = doc.createDocumentFragment();
114
150
  newStyles.forEach((content) => {
115
151
  const style = doc.createElement("style");
116
- style.textContent = content;
152
+ style.textContent = convertViewportUnits(content);
117
153
  fragment.appendChild(style);
118
154
  });
119
155
  doc.head.appendChild(fragment);
@@ -187,7 +223,8 @@ const IFrame = ({ title = "Live Preview", sandbox, style = {}, scripts = EMPTY_S
187
223
  styleEl.id = styleId;
188
224
  doc.head.appendChild(styleEl);
189
225
  }
190
- if (styleEl.textContent !== css) styleEl.textContent = css;
226
+ const convertedCss = convertViewportUnits(css);
227
+ if (styleEl.textContent !== convertedCss) styleEl.textContent = convertedCss;
191
228
  });
192
229
  for (let index = styles.length; index < prevStyleCountRef.current; index++) doc.getElementById(`injected-style-${index}`)?.remove();
193
230
  prevStyleCountRef.current = styles.length;
@@ -205,50 +242,74 @@ const IFrame = ({ title = "Live Preview", sandbox, style = {}, scripts = EMPTY_S
205
242
  for (let index = stylesheets.length; index < prevStylesheetCountRef.current; index++) doc.getElementById(`injected-stylesheet-${index}`)?.remove();
206
243
  prevStylesheetCountRef.current = stylesheets.length;
207
244
  }, [styles, stylesheets]);
245
+ const CONTAINER_STYLE_ID = "autoheight-container";
246
+ const ensureContainerStyle = (doc, probeHeight) => {
247
+ let styleEl = doc.getElementById(CONTAINER_STYLE_ID);
248
+ if (!styleEl) {
249
+ styleEl = doc.createElement("style");
250
+ styleEl.id = CONTAINER_STYLE_ID;
251
+ doc.head?.appendChild(styleEl);
252
+ }
253
+ styleEl.textContent = `html { container-type: size !important; height: ${probeHeight}px !important; }`;
254
+ };
255
+ const MEASUREMENT_OVERRIDE_STYLE_ID = "autoheight-measurement-overrides";
256
+ const withMeasurementOverrides = (doc, measure) => {
257
+ let styleEl = doc.getElementById(MEASUREMENT_OVERRIDE_STYLE_ID);
258
+ if (!styleEl) {
259
+ styleEl = doc.createElement("style");
260
+ styleEl.id = MEASUREMENT_OVERRIDE_STYLE_ID;
261
+ styleEl.media = "not all";
262
+ styleEl.textContent = [
263
+ "*, *::before, *::after { transition: none !important; }",
264
+ "html, body { scrollbar-width: none !important; }",
265
+ "html::-webkit-scrollbar, body::-webkit-scrollbar { display: none !important; }"
266
+ ].join("\n");
267
+ doc.head?.appendChild(styleEl);
268
+ }
269
+ styleEl.media = "all";
270
+ measure();
271
+ styleEl.media = "not all";
272
+ };
208
273
  const updateHeight = useCallback(() => {
209
274
  if (!shouldAutoHeight || !mountNode || !iframeRef.current) return;
210
275
  const iframe = iframeRef.current;
276
+ const doc = iframe.contentDocument;
277
+ const win = doc?.defaultView;
278
+ if (!doc || !win) return;
211
279
  const scrollParent = iframe.closest("[data-frame-container]");
212
- const savedScrollTop = scrollParent?.scrollTop ?? 0;
213
- iframe.style.height = "0px";
214
- let childrenHeight = 0;
215
- Array.from(mountNode.children).forEach((child) => {
216
- const el = child;
217
- childrenHeight = Math.max(childrenHeight, el.scrollHeight);
218
- });
219
- const win = iframe.contentDocument?.defaultView;
220
- mountNode.querySelectorAll("[data-floating-ui-focusable]").forEach((popup) => {
221
- if (popup.offsetHeight > 0) {
222
- let offsetY = 0;
223
- const transform = popup.style.transform;
224
- if (transform) {
225
- const match = transform.match(/translate\([^,]+,\s*([+-]?\d+(?:\.\d+)?)/);
226
- if (match?.[1]) offsetY = parseFloat(match[1]);
227
- }
228
- const estimatedHeight = offsetY + popup.offsetHeight;
229
- childrenHeight = Math.max(childrenHeight, estimatedHeight);
230
- }
231
- });
232
- if (win) Array.from(mountNode.children).forEach((child) => {
233
- const el = child;
234
- const style = win.getComputedStyle(el);
235
- if (style.position === "fixed" || style.position === "absolute") {
236
- if (el.offsetHeight > 0) {
237
- let offsetY = 0;
238
- const transform = el.style.transform;
239
- if (transform) {
240
- const match = transform.match(/translate\([^,]+,\s*([+-]?\d+(?:\.\d+)?)/);
241
- if (match?.[1]) offsetY = parseFloat(match[1]);
242
- }
243
- const estimatedHeight = offsetY + el.offsetHeight;
244
- childrenHeight = Math.max(childrenHeight, estimatedHeight);
280
+ let probeHeight;
281
+ if (!scrollParent) probeHeight = FALLBACK_PROBE_HEIGHT;
282
+ else {
283
+ let wrapperInsets = 0;
284
+ let node = iframe.parentElement;
285
+ while (node && node !== scrollParent) {
286
+ const style = win.getComputedStyle(node);
287
+ wrapperInsets += parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth) + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
288
+ node = node.parentElement;
289
+ }
290
+ const computed = computeProbeHeight(scrollParent.clientHeight, wrapperInsets);
291
+ if (computed === null) return;
292
+ probeHeight = computed;
293
+ }
294
+ let contentHeight = 0;
295
+ withMeasurementOverrides(doc, () => {
296
+ ensureContainerStyle(doc, probeHeight);
297
+ contentHeight = mountNode.scrollHeight;
298
+ mountNode.querySelectorAll("*").forEach((el) => {
299
+ const style = win.getComputedStyle(el);
300
+ if (isVisuallyHidden(style)) return;
301
+ if ((style.position === "fixed" || style.position === "absolute") && el.offsetHeight > 0) {
302
+ const estimatedHeight = estimatePositionedElementHeight(el.offsetHeight, style.transform, probeHeight);
303
+ contentHeight = Math.max(contentHeight, estimatedHeight);
245
304
  }
246
- }
305
+ });
247
306
  });
248
- const contentHeight = Math.max(mountNode.scrollHeight, childrenHeight);
249
307
  if (contentHeight > 0) iframe.style.height = `${Math.ceil(contentHeight)}px`;
250
- if (scrollParent) scrollParent.scrollTop = savedScrollTop;
251
308
  }, [shouldAutoHeight, mountNode]);
309
+ useEffect(() => {
310
+ if (shouldAutoHeight) return;
311
+ iframeRef.current?.contentDocument?.getElementById(CONTAINER_STYLE_ID)?.remove();
312
+ }, [shouldAutoHeight]);
252
313
  useEffect(() => {
253
314
  updateHeight();
254
315
  }, [updateHeight]);
@@ -12236,6 +12297,43 @@ const parseDateValue = (value) => {
12236
12297
  const formatDateValue = (date) => {
12237
12298
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
12238
12299
  };
12300
+ const COLOR_COMMIT_DELAY = 75;
12301
+ const ColorPickerField = ({ id, label, value, onChange }) => {
12302
+ const [liveValue, setLiveValue] = useState(value);
12303
+ const [prevValue, setPrevValue] = useState(value);
12304
+ const lastCommittedRef = useRef(value);
12305
+ if (value !== prevValue) {
12306
+ setPrevValue(value);
12307
+ setLiveValue(value);
12308
+ }
12309
+ useEffect(() => {
12310
+ lastCommittedRef.current = value;
12311
+ }, [value]);
12312
+ const commit = (next) => {
12313
+ if (next === lastCommittedRef.current) return;
12314
+ lastCommittedRef.current = next;
12315
+ onChange?.({
12316
+ id,
12317
+ label,
12318
+ value: next
12319
+ });
12320
+ };
12321
+ const debouncedCommit = useDebounce(() => commit(liveValue), {
12322
+ delay: COLOR_COMMIT_DELAY,
12323
+ autoInvoke: false
12324
+ });
12325
+ return /* @__PURE__ */ jsx(ColorPicker, {
12326
+ showText: true,
12327
+ value: liveValue,
12328
+ onChange: (next) => {
12329
+ setLiveValue(next);
12330
+ debouncedCommit();
12331
+ },
12332
+ onOpenChange: (open) => {
12333
+ if (!open) commit(liveValue);
12334
+ }
12335
+ });
12336
+ };
12239
12337
  const ICON_OPTIONS = Object.entries(ICON_MAP).map(([name, Icon]) => ({
12240
12338
  label: /* @__PURE__ */ jsxs("span", {
12241
12339
  className: "flex items-center gap-2",
@@ -12350,16 +12448,11 @@ const Field = ({ binding, id, value, onChange }) => {
12350
12448
  });
12351
12449
  }
12352
12450
  });
12353
- if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPicker, {
12354
- showText: true,
12451
+ if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPickerField, {
12452
+ id,
12453
+ label: binding.label,
12355
12454
  value: normalizeToHex(stringValue),
12356
- onChange: (next) => {
12357
- if (next !== value) onChange?.({
12358
- id,
12359
- label: binding.label,
12360
- value: next
12361
- });
12362
- }
12455
+ onChange
12363
12456
  });
12364
12457
  if (binding.type === "date") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
12365
12458
  defaultValue: parseDateValue(stringValue),
@@ -12639,7 +12732,12 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
12639
12732
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
12640
12733
  const value = _value || DEFAULT_TEMPLATE;
12641
12734
  const sections = useMemo(() => extractSections(value), [value]);
12642
- const previews = useMemo(() => generateSections(sections.map((s) => s.code), value), [sections, value]);
12735
+ const [previewCache] = useState(() => createSectionPreviewCache());
12736
+ const previews = useMemo(() => previewCache.compute(value, sections), [
12737
+ previewCache,
12738
+ sections,
12739
+ value
12740
+ ]);
12643
12741
  const onDragStart = (_) => {};
12644
12742
  const onDragOver = (_e) => {};
12645
12743
  const onDragEnd = (event) => {