@jbpark/live-editor 1.9.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.d.mts CHANGED
@@ -133,17 +133,26 @@ declare const Core: ({
133
133
  }: Props$2) => react_jsx_runtime0.JSX.Element;
134
134
  //#endregion
135
135
  //#region src/components/editor/editor.d.ts
136
+ interface EditorRenderData {
137
+ value: string;
138
+ onChange: (value: string) => void;
139
+ formatCode: (code: string) => Promise<string>;
140
+ }
136
141
  interface Props$1 extends Omit<Props$2, 'onSave' | 'onError'> {
137
142
  defaultValue?: string;
138
143
  debounce?: number;
144
+ renderEditor?: (data: EditorRenderData) => React.ReactNode;
139
145
  }
140
146
  declare const Editor$1: ({
141
147
  defaultValue,
142
148
  value: _value,
143
149
  debounce,
144
150
  onChange: _onChange,
151
+ renderEditor,
152
+ fragment,
153
+ prettierOptions,
145
154
  ...props
146
- }: Props$1) => react_jsx_runtime0.JSX.Element;
155
+ }: Props$1) => string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime0.JSX.Element | null | undefined;
147
156
  //#endregion
148
157
  //#region src/components/editor/index.d.ts
149
158
  type EditorComponent = typeof Editor$1 & {
@@ -252,8 +261,11 @@ declare const App: {
252
261
  value: _value,
253
262
  debounce,
254
263
  onChange: _onChange,
264
+ renderEditor,
265
+ fragment,
266
+ prettierOptions,
255
267
  ...props
256
- }: Props$1) => react_jsx_runtime0.JSX.Element) & {
268
+ }: Props$1) => string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime0.JSX.Element | null | undefined) & {
257
269
  Core: ({
258
270
  value,
259
271
  theme,
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]);
@@ -11351,8 +11412,8 @@ GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
11351
11412
  GutterMarker.prototype.point = true;
11352
11413
 
11353
11414
  //#endregion
11354
- //#region src/components/editor/core.tsx
11355
- const prettierInitialOptions = {
11415
+ //#region src/components/editor/use-format-code.ts
11416
+ const DEFAULT_PRETTIER_OPTIONS = {
11356
11417
  tabWidth: 2,
11357
11418
  singleQuote: true,
11358
11419
  trailingComma: "all",
@@ -11360,13 +11421,12 @@ const prettierInitialOptions = {
11360
11421
  arrowParens: "avoid",
11361
11422
  printWidth: 60
11362
11423
  };
11363
- const Core = ({ value, theme, height, className, prettierOptions, fragment, raw, onChange, onSave: _onSave, onError, ...props }) => {
11364
- const editorRef = useRef(null);
11424
+ const useFormatCode = ({ fragment, prettierOptions } = {}) => {
11365
11425
  const prettierConfig = useMemo(() => ({
11366
- ...prettierInitialOptions,
11426
+ ...DEFAULT_PRETTIER_OPTIONS,
11367
11427
  ...prettierOptions
11368
11428
  }), [prettierOptions]);
11369
- const formatCode = useCallback(async (code) => {
11429
+ return useCallback(async (code) => {
11370
11430
  const isTypeScript = detectTypeScript(code);
11371
11431
  const source = fragment ? `<>${code}</>` : code;
11372
11432
  const formatted = await prettier.format(source, {
@@ -11381,6 +11441,16 @@ const Core = ({ value, theme, height, className, prettierOptions, fragment, raw,
11381
11441
  if (fragment) return formatted.replace(/^<>\n?/, "").replace(/\n?<\/>;?\s*$/, "").replace(/^ {2}/gm, "").trim();
11382
11442
  return formatted;
11383
11443
  }, [prettierConfig, fragment]);
11444
+ };
11445
+
11446
+ //#endregion
11447
+ //#region src/components/editor/core.tsx
11448
+ const Core = ({ value, theme, height, className, prettierOptions, fragment, raw, onChange, onSave: _onSave, onError, ...props }) => {
11449
+ const editorRef = useRef(null);
11450
+ const formatCode = useFormatCode({
11451
+ fragment,
11452
+ prettierOptions
11453
+ });
11384
11454
  const onSave = useCallback(async (val) => {
11385
11455
  try {
11386
11456
  const currentView = editorRef.current?.view;
@@ -12227,6 +12297,43 @@ const parseDateValue = (value) => {
12227
12297
  const formatDateValue = (date) => {
12228
12298
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
12229
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
+ };
12230
12337
  const ICON_OPTIONS = Object.entries(ICON_MAP).map(([name, Icon]) => ({
12231
12338
  label: /* @__PURE__ */ jsxs("span", {
12232
12339
  className: "flex items-center gap-2",
@@ -12341,16 +12448,11 @@ const Field = ({ binding, id, value, onChange }) => {
12341
12448
  });
12342
12449
  }
12343
12450
  });
12344
- if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPicker, {
12345
- showText: true,
12451
+ if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPickerField, {
12452
+ id,
12453
+ label: binding.label,
12346
12454
  value: normalizeToHex(stringValue),
12347
- onChange: (next) => {
12348
- if (next !== value) onChange?.({
12349
- id,
12350
- label: binding.label,
12351
- value: next
12352
- });
12353
- }
12455
+ onChange
12354
12456
  });
12355
12457
  if (binding.type === "date") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
12356
12458
  defaultValue: parseDateValue(stringValue),
@@ -12630,7 +12732,12 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
12630
12732
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
12631
12733
  const value = _value || DEFAULT_TEMPLATE;
12632
12734
  const sections = useMemo(() => extractSections(value), [value]);
12633
- 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
+ ]);
12634
12741
  const onDragStart = (_) => {};
12635
12742
  const onDragOver = (_e) => {};
12636
12743
  const onDragEnd = (event) => {
@@ -12857,7 +12964,7 @@ Dnd.DraggableItem = DraggableItem;
12857
12964
 
12858
12965
  //#endregion
12859
12966
  //#region src/components/editor/editor.tsx
12860
- const Editor$1 = ({ defaultValue, value: _value, debounce = 1e3, onChange: _onChange, ...props }) => {
12967
+ const Editor$1 = ({ defaultValue, value: _value, debounce = 1e3, onChange: _onChange, renderEditor, fragment, prettierOptions, ...props }) => {
12861
12968
  const { setCode } = usePreview();
12862
12969
  const { setError } = useError();
12863
12970
  const value = _value.trim() === "" ? defaultValue || DEFAULT_TEMPLATE : _value;
@@ -12870,15 +12977,26 @@ const Editor$1 = ({ defaultValue, value: _value, debounce = 1e3, onChange: _onCh
12870
12977
  const onError = useCallback((error) => {
12871
12978
  setError(error);
12872
12979
  }, [setError]);
12980
+ const formatCode = useFormatCode({
12981
+ fragment,
12982
+ prettierOptions
12983
+ });
12873
12984
  const debouncedValue = useDebouncedValue(value, debounce);
12874
12985
  useEffect(() => {
12875
12986
  setCode(debouncedValue);
12876
12987
  }, [debouncedValue, setCode]);
12988
+ if (renderEditor) return renderEditor({
12989
+ value,
12990
+ onChange,
12991
+ formatCode
12992
+ });
12877
12993
  return /* @__PURE__ */ jsx(Core, {
12878
12994
  value,
12879
12995
  onChange,
12880
12996
  onSave,
12881
12997
  onError,
12998
+ fragment,
12999
+ prettierOptions,
12882
13000
  ...props
12883
13001
  });
12884
13002
  };