@frontlit/page-builder 0.1.0-alpha.2 → 0.1.0-alpha.3

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.
Files changed (98) hide show
  1. package/README.md +32 -0
  2. package/dist/blocks/index.d.ts +33 -21
  3. package/dist/blocks/index.js +3 -3
  4. package/dist/builder/index.d.ts +7 -8
  5. package/dist/builder/index.js +6 -5
  6. package/dist/{button-Bzt1GDO-.d.ts → button-OLKN3wPf.d.ts} +2 -2
  7. package/dist/{chunk-H4P5MFJI.js → chunk-36QHCIPM.js} +205 -251
  8. package/dist/chunk-36QHCIPM.js.map +1 -0
  9. package/dist/chunk-BBI7UHOA.js +56 -0
  10. package/dist/chunk-BBI7UHOA.js.map +1 -0
  11. package/dist/{chunk-KQKSLI3L.js → chunk-GIKTOAQS.js} +335 -122
  12. package/dist/chunk-GIKTOAQS.js.map +1 -0
  13. package/dist/chunk-HWOVSHIW.js +4 -0
  14. package/dist/{chunk-RUYOL7JZ.js.map → chunk-HWOVSHIW.js.map} +1 -1
  15. package/dist/{chunk-HDINUNI2.js → chunk-IU4KATI2.js} +69 -96
  16. package/dist/chunk-IU4KATI2.js.map +1 -0
  17. package/dist/{chunk-SGNRWWG3.js → chunk-J7Z4UW45.js} +530 -135
  18. package/dist/chunk-J7Z4UW45.js.map +1 -0
  19. package/dist/{chunk-UO7XGBGM.js → chunk-K4WCLHMS.js} +1182 -1033
  20. package/dist/chunk-K4WCLHMS.js.map +1 -0
  21. package/dist/components/index.d.ts +33 -8
  22. package/dist/components/index.js +42 -2
  23. package/dist/components/index.js.map +1 -1
  24. package/dist/data-slots.d.ts +3 -3
  25. package/dist/fonts/archivo/archivo-latin-variable.woff2 +0 -0
  26. package/dist/fonts/hanken-grotesk/hanken-grotesk-latin-variable.woff2 +0 -0
  27. package/dist/fonts/jetbrains-mono/jetbrains-mono-latin-variable.woff2 +0 -0
  28. package/dist/fonts/spline-sans-mono/spline-sans-mono-latin-variable.woff2 +0 -0
  29. package/dist/heading-DQCxQt38.d.ts +20 -0
  30. package/dist/image-Bk8kPXwP.d.ts +52 -0
  31. package/dist/index.d.ts +17 -10
  32. package/dist/index.js +7 -6
  33. package/dist/models/index.d.ts +5 -5
  34. package/dist/models/index.js +1 -1
  35. package/dist/{page-W3hQga4w.d.ts → page-B5Gmm-Ys.d.ts} +1 -1
  36. package/dist/primitives/index.d.ts +44 -9
  37. package/dist/primitives/index.js +1 -1
  38. package/dist/renderer/index.d.ts +12 -8
  39. package/dist/renderer/index.js +5 -4
  40. package/dist/{section-background-MsdE40Rk.d.ts → section-background-6V_LCi5e.d.ts} +1 -1
  41. package/dist/server/index.d.ts +103 -0
  42. package/dist/server/index.js +2649 -0
  43. package/dist/server/index.js.map +1 -0
  44. package/dist/server/islands/code-snippet.d.ts +17 -0
  45. package/dist/server/islands/code-snippet.js +72 -0
  46. package/dist/server/islands/code-snippet.js.map +1 -0
  47. package/dist/server/islands/email-form.d.ts +50 -0
  48. package/dist/server/islands/email-form.js +406 -0
  49. package/dist/server/islands/email-form.js.map +1 -0
  50. package/dist/server/islands/header-mobile-menu.d.ts +22 -0
  51. package/dist/server/islands/header-mobile-menu.js +422 -0
  52. package/dist/server/islands/header-mobile-menu.js.map +1 -0
  53. package/dist/server/islands/inline-embed.d.ts +28 -0
  54. package/dist/server/islands/inline-embed.js +84 -0
  55. package/dist/server/islands/inline-embed.js.map +1 -0
  56. package/dist/server/islands/marquee-track.d.ts +18 -0
  57. package/dist/server/islands/marquee-track.js +67 -0
  58. package/dist/server/islands/marquee-track.js.map +1 -0
  59. package/dist/server/islands/nav-link.d.ts +37 -0
  60. package/dist/server/islands/nav-link.js +67 -0
  61. package/dist/server/islands/nav-link.js.map +1 -0
  62. package/dist/server/islands/support-form.d.ts +17 -0
  63. package/dist/server/islands/support-form.js +469 -0
  64. package/dist/server/islands/support-form.js.map +1 -0
  65. package/dist/server/islands/theme-mode-switcher.d.ts +38 -0
  66. package/dist/server/islands/theme-mode-switcher.js +169 -0
  67. package/dist/server/islands/theme-mode-switcher.js.map +1 -0
  68. package/dist/server/islands/video-with-preview.d.ts +13 -0
  69. package/dist/server/islands/video-with-preview.js +269 -0
  70. package/dist/server/islands/video-with-preview.js.map +1 -0
  71. package/dist/server/primitives.d.ts +200 -0
  72. package/dist/server/primitives.js +888 -0
  73. package/dist/server/primitives.js.map +1 -0
  74. package/dist/server/schema.d.ts +11308 -0
  75. package/dist/server/schema.js +6179 -0
  76. package/dist/server/schema.js.map +1 -0
  77. package/dist/server/theme-CF3fIKvw.d.ts +9 -0
  78. package/dist/server/theme-style-5_AGy-4V.d.ts +183 -0
  79. package/dist/server/theme-style-BPaMwk88.d.ts +183 -0
  80. package/dist/server/theme.d.ts +28 -0
  81. package/dist/server/theme.js +1671 -0
  82. package/dist/server/theme.js.map +1 -0
  83. package/dist/server/widget-YH9BrOwY.d.ts +77 -0
  84. package/dist/styles.css +121 -15
  85. package/dist/{theme-style-Cbl7NM-T.d.ts → theme-style-BVd5tOsc.d.ts} +20 -0
  86. package/dist/theme.css +12 -0
  87. package/dist/{widget-B_RoG6se.d.ts → widget-CpE2HdgU.d.ts} +17 -14
  88. package/package.json +60 -11
  89. package/schema/page.v1.json +3855 -0
  90. package/schema/site.v1.json +3811 -0
  91. package/dist/alignment-dZDzN96G.d.ts +0 -5
  92. package/dist/chunk-H4P5MFJI.js.map +0 -1
  93. package/dist/chunk-HDINUNI2.js.map +0 -1
  94. package/dist/chunk-KQKSLI3L.js.map +0 -1
  95. package/dist/chunk-RUYOL7JZ.js +0 -4
  96. package/dist/chunk-SGNRWWG3.js.map +0 -1
  97. package/dist/chunk-UO7XGBGM.js.map +0 -1
  98. package/dist/image-DAWVx1zQ.d.ts +0 -31
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { blockRegistry, getAddableBlocks, getWidget, PageRenderer } from './chunk-HDINUNI2.js';
3
- import { generateUniqueId, FieldGroup, TextField, ToggleField } from './chunk-UO7XGBGM.js';
4
- import { ThemeModeSwitcher } from './chunk-H4P5MFJI.js';
5
- import { themes, cn, Button, Switch, Input, PageCard, PageCardContent, PageCardHeader, Link, Button2 } from './chunk-SGNRWWG3.js';
2
+ import { blockRegistry, resolveThemeColor, getAddableBlocks, getWidget, PageRenderer } from './chunk-IU4KATI2.js';
3
+ import { ThemeModeSwitcher } from './chunk-BBI7UHOA.js';
4
+ import { generateUniqueId, FieldGroup, TextField, ToggleField, TitleSizeField } from './chunk-K4WCLHMS.js';
5
+ import { themes, cn, Button, Switch, Input, PageCard, PageCardContent, PageCardHeader, Link, Button2, fluidFontSize, TAILWIND_TEXT_PX } from './chunk-J7Z4UW45.js';
6
6
  import { getMediaUrl } from './chunk-XU7ALPGQ.js';
7
- import React2, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
7
+ import React3, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
8
8
  import * as Icons from 'lucide-react';
9
9
  import { Smartphone, Tablet, Monitor, Palette, Globe, Plus, HelpCircle, X, ImageIcon, Sparkles, ExternalLink, Check, ChevronLeft, LayoutTemplate, Layers3, Pin, ChevronDown, FilePenLine, Brush, ChevronRight, Type, SquareMousePointer, Columns3, Pencil, Info } from 'lucide-react';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -14,12 +14,20 @@ import { ImageUploadDialog } from '@frontlit/media-uploader';
14
14
  // src/utils/debounce.ts
15
15
  function debounce(fn, wait) {
16
16
  let timeout;
17
- return function(...args) {
17
+ let pending = null;
18
+ const debounced = function(...args) {
19
+ pending = args;
18
20
  clearTimeout(timeout);
19
- timeout = setTimeout(() => {
20
- fn(...args);
21
- }, wait);
21
+ timeout = setTimeout(debounced.flush, wait);
22
22
  };
23
+ debounced.flush = () => {
24
+ clearTimeout(timeout);
25
+ if (!pending) return;
26
+ const args = pending;
27
+ pending = null;
28
+ fn(...args);
29
+ };
30
+ return debounced;
23
31
  }
24
32
 
25
33
  // src/utils/color-mode.ts
@@ -46,6 +54,12 @@ function createWidgetInstance(name, overrides) {
46
54
  }
47
55
  };
48
56
  }
57
+ function withDefaultSettings(layout) {
58
+ return layout.map((widget) => {
59
+ const defaults = blockRegistry[widget.name]?.getDefaultSettings?.();
60
+ return defaults ? { ...widget, settings: { ...defaults, ...widget.settings } } : widget;
61
+ });
62
+ }
49
63
  function ensureRequiredSharedWidgets(layout) {
50
64
  const header = layout.find((widget) => widget.name === "header") ?? createWidgetInstance("header");
51
65
  const footer = layout.find((widget) => widget.name === "footer") ?? createWidgetInstance("footer");
@@ -213,7 +227,11 @@ var THEME_FONT_VARS = [
213
227
  "--font-bebas-neue",
214
228
  "--font-lato",
215
229
  "--font-pt-sans",
216
- "--font-quicksand"
230
+ "--font-quicksand",
231
+ "--font-archivo",
232
+ "--font-jetbrains-mono",
233
+ "--font-hanken-grotesk",
234
+ "--font-spline-sans-mono"
217
235
  ];
218
236
  function readCssVar(sourceDoc, name) {
219
237
  const view = sourceDoc.defaultView;
@@ -250,6 +268,7 @@ function PreviewFrame({
250
268
  const iframe = iframeRef.current;
251
269
  if (!iframe) return;
252
270
  let observer;
271
+ let resizeObserver;
253
272
  const init = () => {
254
273
  const doc = iframe.contentDocument;
255
274
  if (!doc) return;
@@ -268,12 +287,21 @@ function PreviewFrame({
268
287
  characterData: true
269
288
  });
270
289
  setMountNode(doc.body);
290
+ resizeObserver?.disconnect();
291
+ const measure = () => iframe.style.setProperty(
292
+ "--pb-scrollbar",
293
+ `${Math.max(0, iframe.clientWidth - doc.documentElement.clientWidth)}px`
294
+ );
295
+ resizeObserver = new ResizeObserver(measure);
296
+ resizeObserver.observe(doc.documentElement);
297
+ measure();
271
298
  };
272
299
  if (iframe.contentDocument?.readyState === "complete") init();
273
300
  iframe.addEventListener("load", init);
274
301
  return () => {
275
302
  iframe.removeEventListener("load", init);
276
303
  observer?.disconnect();
304
+ resizeObserver?.disconnect();
277
305
  };
278
306
  }, []);
279
307
  return /* @__PURE__ */ jsx("iframe", { ref: iframeRef, title, className, children: mountNode ? createPortal(children, mountNode) : null });
@@ -335,6 +363,67 @@ function BlockPicker({
335
363
  }
336
364
  );
337
365
  }
366
+ function ConfirmDialog({
367
+ title,
368
+ description,
369
+ confirmLabel,
370
+ onConfirm,
371
+ onClose
372
+ }) {
373
+ const cancelRef = useRef(null);
374
+ useEffect(() => {
375
+ cancelRef.current?.focus();
376
+ const onKeyDown = (event) => {
377
+ if (event.key === "Escape") onClose();
378
+ };
379
+ document.addEventListener("keydown", onKeyDown);
380
+ return () => document.removeEventListener("keydown", onKeyDown);
381
+ }, [onClose]);
382
+ return /* @__PURE__ */ jsx(
383
+ "div",
384
+ {
385
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
386
+ onClick: onClose,
387
+ children: /* @__PURE__ */ jsxs(
388
+ "div",
389
+ {
390
+ role: "alertdialog",
391
+ "aria-modal": "true",
392
+ "aria-label": title,
393
+ className: "flex w-full max-w-sm flex-col gap-4 rounded-lg border border-border bg-background p-5 shadow-xl",
394
+ onClick: (e) => e.stopPropagation(),
395
+ children: [
396
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
397
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: title }),
398
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-5 text-muted-foreground", children: description })
399
+ ] }),
400
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
401
+ /* @__PURE__ */ jsx(
402
+ "button",
403
+ {
404
+ ref: cancelRef,
405
+ type: "button",
406
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-input bg-background px-3 text-xs font-medium text-foreground hover:bg-muted",
407
+ onClick: onClose,
408
+ children: "Cancel"
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsx(
412
+ "button",
413
+ {
414
+ type: "button",
415
+ className: "inline-flex h-8 items-center justify-center rounded-lg bg-destructive px-3 text-xs font-medium text-destructive-foreground hover:bg-destructive/90",
416
+ onClick: onConfirm,
417
+ children: confirmLabel
418
+ }
419
+ )
420
+ ] })
421
+ ]
422
+ }
423
+ )
424
+ }
425
+ );
426
+ }
338
427
  function PanelHeader({
339
428
  title,
340
429
  onClose,
@@ -401,7 +490,6 @@ function PageBuilderSlider({
401
490
  onChange,
402
491
  tooltip,
403
492
  className,
404
- allowsReset = false,
405
493
  unit = "px"
406
494
  }) {
407
495
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2.5", className), children: [
@@ -424,20 +512,7 @@ function PageBuilderSlider({
424
512
  className: "h-7 w-12 rounded-md border border-input bg-muted/20 px-1.5 py-1 text-center text-xs font-semibold text-foreground outline-none transition-colors focus:border-ring focus:bg-background focus:ring-2 focus:ring-ring/20"
425
513
  }
426
514
  ),
427
- unit && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-muted-foreground", children: unit }),
428
- allowsReset && /* @__PURE__ */ jsx(
429
- "button",
430
- {
431
- type: "button",
432
- title: "Reset",
433
- className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
434
- onClick: (e) => {
435
- e.preventDefault();
436
- onChange();
437
- },
438
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-muted-foreground hover:text-foreground" })
439
- }
440
- )
515
+ unit && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-muted-foreground", children: unit })
441
516
  ] })
442
517
  ] }),
443
518
  /* @__PURE__ */ jsx(
@@ -464,14 +539,15 @@ function ColorSelector({
464
539
  allowReset = true,
465
540
  description
466
541
  }) {
467
- const [localValue, setLocalValue] = React2.useState(value);
468
- React2.useEffect(() => {
542
+ const [localValue, setLocalValue] = React3.useState(value);
543
+ React3.useEffect(() => {
469
544
  setLocalValue(value);
470
545
  }, [value]);
471
546
  const handleChange = (newValue) => {
472
547
  setLocalValue(newValue);
473
548
  onChange(newValue);
474
549
  };
550
+ const invalid = Boolean(localValue) && !resolveThemeColor(localValue);
475
551
  const swatchValue = /^#[0-9a-fA-F]{6}$/.test(localValue) ? localValue : "#000000";
476
552
  return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-center justify-between gap-2", className), children: [
477
553
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
@@ -494,9 +570,14 @@ function ColorSelector({
494
570
  {
495
571
  type: "text",
496
572
  value: localValue ?? "",
497
- placeholder: "#000000",
573
+ placeholder: "#000000 or oklch(\u2026)",
574
+ title: "A hex colour, or a CSS colour such as oklch(0.6 0.2 30)",
575
+ "aria-invalid": invalid || void 0,
498
576
  onChange: (e) => handleChange(e.currentTarget.value),
499
- className: "h-7 w-20 min-w-0 rounded-md border border-input bg-background px-2 py-1 text-xs text-foreground outline-none focus:ring-2 focus:ring-ring/20",
577
+ className: cn(
578
+ "h-7 w-28 min-w-0 rounded-md border border-input bg-background px-2 py-1 text-xs text-foreground outline-none focus:ring-2 focus:ring-ring/20",
579
+ invalid && "border-destructive focus:ring-destructive/30"
580
+ ),
500
581
  "aria-label": `${title} hex value`
501
582
  }
502
583
  ),
@@ -538,10 +619,6 @@ function MaxWidthSelector({
538
619
  ([_, v]) => v === (value ?? themeDefault)
539
620
  )?.[0] || "5";
540
621
  const handleChange = (newValue) => {
541
- if (newValue === void 0) {
542
- onChange();
543
- return;
544
- }
545
622
  onChange(getMaxWidthTWClass(newValue));
546
623
  };
547
624
  return /* @__PURE__ */ jsx(
@@ -554,7 +631,6 @@ function MaxWidthSelector({
554
631
  value: parseInt(numericValue),
555
632
  tooltip: "Maximum width of the content",
556
633
  className,
557
- allowsReset: true,
558
634
  unit: ""
559
635
  }
560
636
  );
@@ -587,10 +663,6 @@ function VerticalPaddingSelector({
587
663
  }) {
588
664
  const numericValue = Object.entries(PADDING_MAP).find(([_, v]) => v === (value ?? themeDefault))?.[0] || "6";
589
665
  const handleChange = (newValue) => {
590
- if (newValue === void 0) {
591
- onChange();
592
- return;
593
- }
594
666
  onChange(getVerticalPaddingTWClass(newValue));
595
667
  };
596
668
  return /* @__PURE__ */ jsx(
@@ -2099,6 +2171,13 @@ function BlockSettings({
2099
2171
  onChange
2100
2172
  }
2101
2173
  ),
2174
+ definition.titleSize && /* @__PURE__ */ jsx(FieldGroup, { title: "Title", children: /* @__PURE__ */ jsx(
2175
+ TitleSizeField,
2176
+ {
2177
+ settings,
2178
+ onChange: (partial) => onChange({ ...settings, ...partial })
2179
+ }
2180
+ ) }),
2102
2181
  /* @__PURE__ */ jsx(
2103
2182
  DesignFieldGroup,
2104
2183
  {
@@ -2368,7 +2447,8 @@ var fontFamilyOptions = {
2368
2447
  { label: "Roboto", value: "font-roboto" },
2369
2448
  { label: "Mulish", value: "font-mulish" },
2370
2449
  { label: "Nunito", value: "font-nunito" },
2371
- { label: "Work Sans", value: "font-work-sans" }
2450
+ { label: "Work Sans", value: "font-work-sans" },
2451
+ { label: "Hanken Grotesk", value: "font-hanken-grotesk" }
2372
2452
  ],
2373
2453
  Serif: [
2374
2454
  { label: "Merriweather", value: "font-merriweather" },
@@ -2383,16 +2463,30 @@ var fontFamilyOptions = {
2383
2463
  { label: "Raleway", value: "font-raleway" },
2384
2464
  { label: "Rubik", value: "font-rubik" },
2385
2465
  { label: "Oswald", value: "font-oswald" },
2386
- { label: "Bebas Neue", value: "font-bebas-neue" }
2466
+ { label: "Bebas Neue", value: "font-bebas-neue" },
2467
+ { label: "Archivo", value: "font-archivo" }
2387
2468
  ],
2388
2469
  Modern: [
2389
2470
  { label: "Lato", value: "font-lato" },
2390
2471
  { label: "PT Sans", value: "font-pt-sans" },
2391
2472
  { label: "Quicksand", value: "font-quicksand" }
2473
+ ],
2474
+ Mono: [
2475
+ { label: "JetBrains Mono", value: "font-jetbrains-mono" },
2476
+ { label: "Spline Sans Mono", value: "font-spline-sans-mono" }
2392
2477
  ]
2393
2478
  };
2394
- function TypographySelector({ value, onChange }) {
2479
+ function defaultFluid(fontSize) {
2480
+ const max = TAILWIND_TEXT_PX[fontSize ?? ""] ?? 48;
2481
+ return { min: Math.round(max * 0.66), max };
2482
+ }
2483
+ function TypographySelector({
2484
+ value,
2485
+ onChange,
2486
+ allowFluid
2487
+ }) {
2395
2488
  const update = (patch) => onChange({ ...value, ...patch });
2489
+ const fluid = value?.fluid;
2396
2490
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6 p-2", children: [
2397
2491
  /* @__PURE__ */ jsx("div", { className: "h-[100px] overflow-auto rounded-lg border p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
2398
2492
  /* @__PURE__ */ jsx(PanelLabel, { children: "Preview" }),
@@ -2407,6 +2501,7 @@ function TypographySelector({ value, onChange }) {
2407
2501
  value?.letterSpacing || "",
2408
2502
  value?.textTransform || "normal-case"
2409
2503
  ),
2504
+ style: fluid ? { fontSize: fluidFontSize(fluid) } : void 0,
2410
2505
  children: "The quick brown fox jumps over the lazy dog"
2411
2506
  }
2412
2507
  )
@@ -2427,11 +2522,56 @@ function TypographySelector({ value, onChange }) {
2427
2522
  }
2428
2523
  )
2429
2524
  ] }),
2525
+ allowFluid && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border p-2", children: [
2526
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-xs font-medium text-foreground", children: [
2527
+ "Scale with screen size",
2528
+ /* @__PURE__ */ jsx(
2529
+ "input",
2530
+ {
2531
+ type: "checkbox",
2532
+ checked: Boolean(fluid),
2533
+ onChange: (e) => update({
2534
+ fluid: e.target.checked ? defaultFluid(value.fontSize) : void 0
2535
+ })
2536
+ }
2537
+ )
2538
+ ] }),
2539
+ fluid && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2540
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2541
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "fluid-min", children: "Phone size (px)" }),
2542
+ /* @__PURE__ */ jsx(
2543
+ PanelInput2,
2544
+ {
2545
+ id: "fluid-min",
2546
+ type: "number",
2547
+ min: 8,
2548
+ max: 200,
2549
+ value: fluid.min,
2550
+ onChange: (e) => update({ fluid: { ...fluid, min: Number(e.target.value) || 0 } })
2551
+ }
2552
+ )
2553
+ ] }),
2554
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2555
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "fluid-max", children: "Desktop size (px)" }),
2556
+ /* @__PURE__ */ jsx(
2557
+ PanelInput2,
2558
+ {
2559
+ id: "fluid-max",
2560
+ type: "number",
2561
+ min: 8,
2562
+ max: 200,
2563
+ value: fluid.max,
2564
+ onChange: (e) => update({ fluid: { ...fluid, max: Number(e.target.value) || 0 } })
2565
+ }
2566
+ )
2567
+ ] })
2568
+ ] })
2569
+ ] }),
2430
2570
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2431
2571
  /* @__PURE__ */ jsx(
2432
2572
  PanelSelect2,
2433
2573
  {
2434
- title: "Font Size",
2574
+ title: fluid ? "Font Size (unused while scaling)" : "Font Size",
2435
2575
  value: value?.fontSize || "",
2436
2576
  onChange: (v) => update({ fontSize: v }),
2437
2577
  options: fontSizeOptions,
@@ -2772,7 +2912,24 @@ function StructureSelector({ type, theme, onChange }) {
2772
2912
  updatePadding({ y });
2773
2913
  }
2774
2914
  }
2775
- )
2915
+ ),
2916
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-xs font-medium text-foreground", children: [
2917
+ "Hairline between sections",
2918
+ /* @__PURE__ */ jsx(
2919
+ "input",
2920
+ {
2921
+ type: "checkbox",
2922
+ checked: Boolean(sectionValue?.dividers),
2923
+ onChange: (e) => onChange({
2924
+ ...theme,
2925
+ structure: {
2926
+ ...theme.structure,
2927
+ section: { ...sectionValue, dividers: e.target.checked || void 0 }
2928
+ }
2929
+ })
2930
+ }
2931
+ )
2932
+ ] })
2776
2933
  ] });
2777
2934
  }
2778
2935
  var sections = [
@@ -2798,6 +2955,7 @@ var sections = [
2798
2955
  }
2799
2956
  ];
2800
2957
  var typographyDisplayNames = {
2958
+ display: "Display (hero)",
2801
2959
  header1: "Header 1",
2802
2960
  header2: "Header 2",
2803
2961
  header3: "Header 3",
@@ -2810,12 +2968,26 @@ var typographyDisplayNames = {
2810
2968
  caption: "Caption",
2811
2969
  link: "Link",
2812
2970
  button: "Button Text",
2813
- input: "Input Text"
2971
+ input: "Input Text",
2972
+ code: "Code"
2814
2973
  };
2974
+ var DEFAULT_CODE_TYPOGRAPHY = {
2975
+ fontFamily: "font-jetbrains-mono",
2976
+ fontSize: "text-sm",
2977
+ fontWeight: "font-normal",
2978
+ lineHeight: "leading-relaxed"
2979
+ };
2980
+ var fluidTypography = /* @__PURE__ */ new Set([
2981
+ "display",
2982
+ "header1",
2983
+ "header2",
2984
+ "header3",
2985
+ "header4"
2986
+ ]);
2815
2987
  var typographyCategories = [
2816
2988
  {
2817
2989
  name: "Headers",
2818
- items: ["header1", "header2", "header3", "header4", "preheader"]
2990
+ items: ["display", "header1", "header2", "header3", "header4", "preheader"]
2819
2991
  },
2820
2992
  {
2821
2993
  name: "Subheaders",
@@ -2827,7 +2999,7 @@ var typographyCategories = [
2827
2999
  },
2828
3000
  {
2829
3001
  name: "Interactive Elements",
2830
- items: ["link", "button", "input"]
3002
+ items: ["link", "button", "input", "code"]
2831
3003
  }
2832
3004
  ];
2833
3005
  var colorCategories = [
@@ -2941,34 +3113,28 @@ function ThemeEditor({
2941
3113
  onCustomThemesChange
2942
3114
  }) {
2943
3115
  const [navigationStack, setNavigationStack] = useState([]);
2944
- const [collapsedCategories, setCollapsedCategories] = useState(() => {
2945
- const initial = {};
2946
- typographyCategories.forEach((category) => {
2947
- initial[category.name] = true;
2948
- });
2949
- return initial;
2950
- });
2951
3116
  const navigateTo = useCallback((item) => {
2952
3117
  setNavigationStack((prev) => [...prev, item]);
2953
3118
  }, []);
2954
3119
  const navigateBack = useCallback(() => {
2955
3120
  setNavigationStack((prev) => prev.slice(0, -1));
2956
3121
  }, []);
2957
- const toggleCategory = useCallback((categoryName) => {
2958
- setCollapsedCategories((prev) => ({
2959
- ...prev,
2960
- [categoryName]: !prev[categoryName]
2961
- }));
2962
- }, []);
2963
- const applyStyle = useCallback(
2964
- (style) => {
2965
- const updated = { ...theme, theme: style };
2966
- onApply(updated);
3122
+ const commitTheme = useCallback(
3123
+ (updated) => {
3124
+ if (updated.id === appliedThemeId) {
3125
+ onApply(updated);
3126
+ } else {
3127
+ onPreview(updated);
3128
+ }
2967
3129
  onCustomThemesChange(
2968
3130
  customThemes.map((t) => t.id === updated.id ? updated : t)
2969
3131
  );
2970
3132
  },
2971
- [theme, customThemes, onApply, onCustomThemesChange]
3133
+ [appliedThemeId, customThemes, onApply, onPreview, onCustomThemesChange]
3134
+ );
3135
+ const applyStyle = useCallback(
3136
+ (style) => commitTheme({ ...theme, theme: style }),
3137
+ [theme, commitTheme]
2972
3138
  );
2973
3139
  const startEditing = useCallback(
2974
3140
  (themeItem, isSystem) => {
@@ -2982,10 +3148,10 @@ function ThemeEditor({
2982
3148
  };
2983
3149
  onCustomThemesChange([...customThemes, target]);
2984
3150
  }
2985
- onApply(target);
3151
+ onPreview(target);
2986
3152
  setNavigationStack([{ id: "categories", label: target.name }]);
2987
3153
  },
2988
- [systemThemes, customThemes, onApply, onCustomThemesChange]
3154
+ [systemThemes, customThemes, onPreview, onCustomThemesChange]
2989
3155
  );
2990
3156
  const renderDrillButton = (label, onClick) => /* @__PURE__ */ jsxs(
2991
3157
  "button",
@@ -3097,37 +3263,49 @@ function ThemeEditor({
3097
3263
  ) }) }, category.key)) })
3098
3264
  ] });
3099
3265
  };
3100
- const renderTypographyList = () => /* @__PURE__ */ jsx("div", { className: "space-y-1", children: typographyCategories.map((category) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
3101
- /* @__PURE__ */ jsxs(
3102
- "button",
3103
- {
3104
- type: "button",
3105
- className: "flex w-full items-center rounded px-2 py-1.5 text-sm transition-colors hover:bg-muted",
3106
- onClick: () => toggleCategory(category.name),
3107
- children: [
3108
- /* @__PURE__ */ jsx(
3109
- ChevronDown,
3266
+ const renderTypographyView = () => {
3267
+ const updateTypography = (key, value) => applyStyle({
3268
+ ...theme.theme,
3269
+ typography: { ...theme.theme.typography, [key]: value }
3270
+ });
3271
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
3272
+ /* @__PURE__ */ jsx(
3273
+ PanelSelect2,
3274
+ {
3275
+ title: "Highlighted title words (*like this*)",
3276
+ value: theme.theme.highlight ?? "color",
3277
+ onChange: (highlight) => applyStyle({ ...theme.theme, highlight }),
3278
+ options: [
3279
+ { label: "Primary colour text", value: "color" },
3280
+ { label: "Marker in primary colour", value: "marker" }
3281
+ ]
3282
+ }
3283
+ ),
3284
+ typographyCategories.map((category) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3285
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground", children: category.name }),
3286
+ category.items.map((typography) => {
3287
+ const key = typography;
3288
+ const value = theme.theme.typography[key] ?? (key === "display" ? theme.theme.typography.header1 : void 0) ?? (key === "code" ? DEFAULT_CODE_TYPOGRAPHY : void 0);
3289
+ if (!value) return null;
3290
+ return /* @__PURE__ */ jsx(
3291
+ AccordionSection,
3110
3292
  {
3111
- className: cn(
3112
- "mr-2 h-4 w-4 transition-transform",
3113
- collapsedCategories[category.name] ? "rotate-[-90deg]" : "rotate-0"
3293
+ title: typographyDisplayNames[typography] || capitalize(typography),
3294
+ children: /* @__PURE__ */ jsx(
3295
+ TypographySelector,
3296
+ {
3297
+ value,
3298
+ allowFluid: fluidTypography.has(typography),
3299
+ onChange: (value2) => updateTypography(key, value2)
3300
+ }
3114
3301
  )
3115
- }
3116
- ),
3117
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: category.name })
3118
- ]
3119
- }
3120
- ),
3121
- !collapsedCategories[category.name] && /* @__PURE__ */ jsx("div", { children: category.items.map(
3122
- (typography) => renderDrillButton(
3123
- typographyDisplayNames[typography] || capitalize(typography),
3124
- () => navigateTo({
3125
- id: typography,
3126
- label: typographyDisplayNames[typography] || capitalize(typography)
3302
+ },
3303
+ typography
3304
+ );
3127
3305
  })
3128
- )
3129
- ) })
3130
- ] }, category.name)) });
3306
+ ] }, category.name))
3307
+ ] });
3308
+ };
3131
3309
  const renderInteractivesList = () => /* @__PURE__ */ jsx("div", { className: "space-y-1", children: Object.keys(interactiveDisplayNames).map(
3132
3310
  (interactive) => renderDrillButton(
3133
3311
  interactiveDisplayNames[interactive] || capitalize(interactive),
@@ -3152,22 +3330,6 @@ function ThemeEditor({
3152
3330
  if (!currentItem) {
3153
3331
  return renderRootView();
3154
3332
  }
3155
- if (parentItem?.id === "typography" && currentItem.id in theme.theme.typography) {
3156
- const key = currentItem.id;
3157
- return /* @__PURE__ */ jsx(
3158
- TypographySelector,
3159
- {
3160
- value: theme.theme.typography[key],
3161
- onChange: (value) => applyStyle({
3162
- ...theme.theme,
3163
- typography: {
3164
- ...theme.theme.typography,
3165
- [key]: value
3166
- }
3167
- })
3168
- }
3169
- );
3170
- }
3171
3333
  if (parentItem?.id === "interactives" && currentItem.id in theme.theme.interactives) {
3172
3334
  return /* @__PURE__ */ jsx(
3173
3335
  InteractiveSelector,
@@ -3194,7 +3356,7 @@ function ThemeEditor({
3194
3356
  case "colors":
3195
3357
  return renderColorsView();
3196
3358
  case "typography":
3197
- return renderTypographyList();
3359
+ return renderTypographyView();
3198
3360
  case "interactives":
3199
3361
  return renderInteractivesList();
3200
3362
  case "structure":
@@ -3216,7 +3378,35 @@ function ThemeEditor({
3216
3378
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
3217
3379
  }
3218
3380
  ),
3219
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: navigationStack[navigationStack.length - 1].label })
3381
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: navigationStack.length === 1 ? theme.name : navigationStack[navigationStack.length - 1].label })
3382
+ ] }),
3383
+ navigationStack.length === 1 && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pb-2", children: [
3384
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3385
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "theme-name", children: "Theme name" }),
3386
+ /* @__PURE__ */ jsx(
3387
+ PanelInput2,
3388
+ {
3389
+ id: "theme-name",
3390
+ value: theme.name,
3391
+ onChange: (e) => commitTheme({ ...theme, name: e.target.value })
3392
+ }
3393
+ )
3394
+ ] }),
3395
+ theme.id === appliedThemeId ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
3396
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
3397
+ "This is your site theme"
3398
+ ] }) : /* @__PURE__ */ jsxs(
3399
+ "button",
3400
+ {
3401
+ type: "button",
3402
+ className: "inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90",
3403
+ onClick: () => onApply(theme),
3404
+ children: [
3405
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
3406
+ "Use this theme"
3407
+ ]
3408
+ }
3409
+ )
3220
3410
  ] }),
3221
3411
  /* @__PURE__ */ jsx("div", { className: "py-2", children: getCurrentView() })
3222
3412
  ] })
@@ -3370,7 +3560,7 @@ function PageBuilder({
3370
3560
  className
3371
3561
  }) {
3372
3562
  const [layout, setLayout] = useState(
3373
- () => initialLayout ? ensureRequiredSharedWidgets(initialLayout) : createDefaultLayout()
3563
+ () => initialLayout ? ensureRequiredSharedWidgets(withDefaultSettings(initialLayout)) : createDefaultLayout()
3374
3564
  );
3375
3565
  const [theme, setTheme] = useState(() => initialTheme ?? getDefaultTheme());
3376
3566
  const [previewTheme, setPreviewTheme] = useState(null);
@@ -3385,6 +3575,7 @@ function PageBuilder({
3385
3575
  const [device, setDevice] = useState("desktop");
3386
3576
  const [panel, setPanel] = useState(null);
3387
3577
  const [insertAfter, setInsertAfter] = useState(null);
3578
+ const [pendingDeleteId, setPendingDeleteId] = useState(null);
3388
3579
  const [movedWidgetId, setMovedWidgetId] = useState(null);
3389
3580
  const movedTimeoutRef = useRef(null);
3390
3581
  useEffect(() => {
@@ -3409,6 +3600,7 @@ function PageBuilder({
3409
3600
  useEffect(() => {
3410
3601
  emitChange({ layout, theme, seo, customThemes });
3411
3602
  }, [layout, theme, seo, customThemes]);
3603
+ useEffect(() => emitChange.flush, [emitChange]);
3412
3604
  const addableBlocks = useMemo(() => getAddableBlocks(), []);
3413
3605
  function updateWidgetSettings(widgetId, settings) {
3414
3606
  setLayout(
@@ -3431,6 +3623,9 @@ function PageBuilder({
3431
3623
  [next[index], next[targetIndex]] = [next[targetIndex], next[index]];
3432
3624
  return next;
3433
3625
  });
3626
+ highlightWidget(widgetId);
3627
+ }
3628
+ function highlightWidget(widgetId) {
3434
3629
  if (movedTimeoutRef.current) clearTimeout(movedTimeoutRef.current);
3435
3630
  setMovedWidgetId(widgetId);
3436
3631
  movedTimeoutRef.current = setTimeout(() => setMovedWidgetId(null), 1500);
@@ -3456,7 +3651,11 @@ function PageBuilder({
3456
3651
  return [...prev.slice(0, footerIndex), instance, ...prev.slice(footerIndex)];
3457
3652
  }
3458
3653
  const afterIndex = prev.findIndex((w) => w.widgetId === insertAfter);
3459
- if (afterIndex === -1) return [...prev, instance];
3654
+ if (afterIndex === -1 || prev[afterIndex].name === "footer") {
3655
+ const footerIndex = prev.findIndex((w) => w.name === "footer");
3656
+ if (footerIndex === -1) return [...prev, instance];
3657
+ return [...prev.slice(0, footerIndex), instance, ...prev.slice(footerIndex)];
3658
+ }
3460
3659
  return [
3461
3660
  ...prev.slice(0, afterIndex + 1),
3462
3661
  instance,
@@ -3464,6 +3663,7 @@ function PageBuilder({
3464
3663
  ];
3465
3664
  });
3466
3665
  setInsertAfter(null);
3666
+ highlightWidget(instance.widgetId);
3467
3667
  setPanel({ mode: "block", widgetId: instance.widgetId });
3468
3668
  }
3469
3669
  const selectedWidgetId = panel?.mode === "block" ? panel.widgetId : null;
@@ -3500,7 +3700,7 @@ function PageBuilder({
3500
3700
  onDeviceChange: setDevice,
3501
3701
  onOpenTheme: () => setPanel({ mode: "theme" }),
3502
3702
  onOpenSeo: () => setPanel({ mode: "seo" }),
3503
- onAddBlock: () => setInsertAfter("end"),
3703
+ onAddBlock: () => setInsertAfter(selectedWidgetId ?? "end"),
3504
3704
  allowThemeEditing
3505
3705
  }
3506
3706
  ),
@@ -3521,8 +3721,9 @@ function PageBuilder({
3521
3721
  {
3522
3722
  className: cn(
3523
3723
  "h-full w-full max-w-full transition-[max-width] duration-300",
3524
- device === "mobile" && "max-w-[390px] border-x",
3525
- device === "tablet" && "max-w-[768px] border-x"
3724
+ // Device width plus the 2px border and the measured scrollbar.
3725
+ device === "mobile" && "max-w-[calc(392px_+_var(--pb-scrollbar,0px))] border-x",
3726
+ device === "tablet" && "max-w-[calc(770px_+_var(--pb-scrollbar,0px))] border-x"
3526
3727
  ),
3527
3728
  children: /* @__PURE__ */ jsx(
3528
3729
  PageRenderer,
@@ -3531,7 +3732,6 @@ function PageBuilder({
3531
3732
  theme: paintedTheme,
3532
3733
  pageData,
3533
3734
  nextTheme: resolvedMode,
3534
- themeMode: mode,
3535
3735
  onThemeModeChange: setMode,
3536
3736
  editing: true,
3537
3737
  selectedWidgetId: selectedWidgetId ?? void 0,
@@ -3539,7 +3739,7 @@ function PageBuilder({
3539
3739
  onSelectWidget: (id) => setPanel({ mode: "block", widgetId: id }),
3540
3740
  onMoveWidgetUp: (id) => moveWidget(id, -1),
3541
3741
  onMoveWidgetDown: (id) => moveWidget(id, 1),
3542
- onDeleteWidget: deleteWidget,
3742
+ onDeleteWidget: setPendingDeleteId,
3543
3743
  onAddWidgetBelow: (id) => setInsertAfter(id),
3544
3744
  linkComponent
3545
3745
  }
@@ -3587,6 +3787,19 @@ function PageBuilder({
3587
3787
  ) }),
3588
3788
  showSeoPanel && /* @__PURE__ */ jsx(SettingsPanel, { title: "SEO", onClose: closePanel, children: /* @__PURE__ */ jsx(SeoEditor, { seo, onChange: setSeo }) })
3589
3789
  ] }),
3790
+ pendingDeleteId !== null && /* @__PURE__ */ jsx(
3791
+ ConfirmDialog,
3792
+ {
3793
+ title: `Delete ${getWidget(layout.find((w) => w.widgetId === pendingDeleteId)?.name ?? "")?.metadata.displayName ?? "block"}?`,
3794
+ description: "This block and its content will be removed from the page draft.",
3795
+ confirmLabel: "Delete",
3796
+ onConfirm: () => {
3797
+ deleteWidget(pendingDeleteId);
3798
+ setPendingDeleteId(null);
3799
+ },
3800
+ onClose: () => setPendingDeleteId(null)
3801
+ }
3802
+ ),
3590
3803
  insertAfter !== null && /* @__PURE__ */ jsx(
3591
3804
  BlockPicker,
3592
3805
  {
@@ -3600,6 +3813,6 @@ function PageBuilder({
3600
3813
  );
3601
3814
  }
3602
3815
 
3603
- export { BlockPicker, BuilderTopBar, ColorSelector, ContentPaddingSelector, MaxWidthSelector, PATTERN_PRESETS, PageBuilder, PageBuilderPropertyHeader, PageBuilderSlider, SectionBackgroundPanel, SeoEditor, SettingsPanel, ThemeEditor, VerticalPaddingSelector, createDefaultLayout, createWidgetInstance, ensureRequiredSharedWidgets, getDefaultTheme, getMaxWidthTWClass, getVerticalPaddingTWClass };
3604
- //# sourceMappingURL=chunk-KQKSLI3L.js.map
3605
- //# sourceMappingURL=chunk-KQKSLI3L.js.map
3816
+ export { BlockPicker, BuilderTopBar, ColorSelector, ContentPaddingSelector, MaxWidthSelector, PATTERN_PRESETS, PageBuilder, PageBuilderPropertyHeader, PageBuilderSlider, SectionBackgroundPanel, SeoEditor, SettingsPanel, ThemeEditor, VerticalPaddingSelector, createDefaultLayout, createWidgetInstance, ensureRequiredSharedWidgets, getDefaultTheme, getMaxWidthTWClass, getVerticalPaddingTWClass, withDefaultSettings };
3817
+ //# sourceMappingURL=chunk-GIKTOAQS.js.map
3818
+ //# sourceMappingURL=chunk-GIKTOAQS.js.map