@orion-studios/payload-studio 0.6.0-beta.45 → 0.6.0-beta.46

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.
@@ -157,10 +157,127 @@ var sanitizeBuilderCss = (value) => {
157
157
  }
158
158
  return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
159
159
  };
160
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
161
+ const trimmed = part.trim();
162
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
163
+ return trimmed;
164
+ }
165
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
166
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
167
+ }
168
+ return `${scope} ${trimmed}`;
169
+ }).filter(Boolean).join(", ");
170
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
171
+ const css = sanitizeBuilderCss(value);
172
+ if (!css) {
173
+ return "";
174
+ }
175
+ let output = "";
176
+ let cursor = 0;
177
+ const rulePattern = /([^{}]+)\{/g;
178
+ let match;
179
+ while ((match = rulePattern.exec(css)) !== null) {
180
+ const selectorStart = match.index;
181
+ const selector = match[1];
182
+ output += css.slice(cursor, selectorStart);
183
+ const trimmedSelector = selector.trim();
184
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
185
+ output += `${selector}{`;
186
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
187
+ output += `${selector}{`;
188
+ } else {
189
+ output += `${scopeSelector(selector, scope)} {`;
190
+ }
191
+ cursor = rulePattern.lastIndex;
192
+ }
193
+ output += css.slice(cursor);
194
+ return output;
195
+ };
196
+
197
+ // src/builder-v2/validation.ts
198
+ var hasMatch = (value, pattern) => pattern.test(value);
199
+ var validateBuilderV2Output = (input) => {
200
+ const html = typeof input.html === "string" ? input.html : "";
201
+ const css = typeof input.css === "string" ? input.css : "";
202
+ const issues = [];
203
+ if (!html.trim()) {
204
+ issues.push({
205
+ code: "empty-page",
206
+ message: "This page has no rendered content.",
207
+ path: "compiledHtml",
208
+ severity: "error"
209
+ });
210
+ }
211
+ if (!hasMatch(html, /<h1\b/i)) {
212
+ issues.push({
213
+ code: "missing-h1",
214
+ message: "Add one H1 so the published page has a clear primary heading.",
215
+ path: "compiledHtml",
216
+ severity: "warning"
217
+ });
218
+ }
219
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
220
+ issues.push({
221
+ code: "inline-event-handler",
222
+ message: "Inline event handlers are not allowed in published builder HTML.",
223
+ path: "compiledHtml",
224
+ severity: "error"
225
+ });
226
+ }
227
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
228
+ issues.push({
229
+ code: "unsafe-element",
230
+ message: "Script, object, and embed tags are not allowed in builder content.",
231
+ path: "compiledHtml",
232
+ severity: "error"
233
+ });
234
+ }
235
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
236
+ issues.push({
237
+ code: "unsafe-url",
238
+ message: "Links and media cannot use javascript URLs.",
239
+ path: "compiledHtml",
240
+ severity: "error"
241
+ });
242
+ }
243
+ if (hasMatch(css, /@import\s/i)) {
244
+ issues.push({
245
+ code: "css-import",
246
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
247
+ path: "compiledCss",
248
+ severity: "warning"
249
+ });
250
+ }
251
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
252
+ issues.push({
253
+ code: "fixed-position",
254
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
255
+ path: "compiledCss",
256
+ severity: "warning"
257
+ });
258
+ }
259
+ return issues;
260
+ };
261
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
160
262
 
161
263
  // src/builder-v2/editor/defaultBlocks.ts
162
264
  var registerOrionBuilderV2Blocks = (editor) => {
163
265
  const blocks = editor.Blocks;
266
+ blocks.add("orion-nav", {
267
+ category: "Chrome",
268
+ content: `
269
+ <header class="orion-builder-v2-nav">
270
+ <a class="orion-builder-v2-logo" href="/">Brand</a>
271
+ <nav aria-label="Primary">
272
+ <a href="/">Home</a>
273
+ <a href="/about">About</a>
274
+ <a href="/contact">Contact</a>
275
+ </nav>
276
+ <a class="orion-builder-v2-button" href="/contact">Start</a>
277
+ </header>
278
+ `,
279
+ label: "Nav"
280
+ });
164
281
  blocks.add("orion-section", {
165
282
  category: "Layout",
166
283
  content: `
@@ -200,6 +317,89 @@ var registerOrionBuilderV2Blocks = (editor) => {
200
317
  `,
201
318
  label: "Columns"
202
319
  });
320
+ blocks.add("orion-card-grid", {
321
+ category: "Sections",
322
+ content: `
323
+ <section class="orion-builder-v2-section">
324
+ <div class="orion-builder-v2-container">
325
+ <p class="orion-builder-v2-kicker">Featured</p>
326
+ <h2>Cards that explain the offer</h2>
327
+ <div class="orion-builder-v2-grid is-3">
328
+ <article class="orion-builder-v2-card"><h3>Service One</h3><p>Describe the outcome clients care about.</p><a href="/contact">Learn more</a></article>
329
+ <article class="orion-builder-v2-card"><h3>Service Two</h3><p>Keep the card concise and scannable.</p><a href="/contact">Learn more</a></article>
330
+ <article class="orion-builder-v2-card"><h3>Service Three</h3><p>Use the same structure for visual rhythm.</p><a href="/contact">Learn more</a></article>
331
+ </div>
332
+ </div>
333
+ </section>
334
+ `,
335
+ label: "Card grid"
336
+ });
337
+ blocks.add("orion-gallery", {
338
+ category: "Media",
339
+ content: `
340
+ <section class="orion-builder-v2-section">
341
+ <div class="orion-builder-v2-container">
342
+ <h2>Gallery</h2>
343
+ <div class="orion-builder-v2-gallery">
344
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
345
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
346
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
347
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
348
+ </div>
349
+ </div>
350
+ </section>
351
+ `,
352
+ label: "Gallery"
353
+ });
354
+ blocks.add("orion-testimonials", {
355
+ category: "Sections",
356
+ content: `
357
+ <section class="orion-builder-v2-section is-muted">
358
+ <div class="orion-builder-v2-container">
359
+ <p class="orion-builder-v2-kicker">Testimonials</p>
360
+ <h2>What clients say</h2>
361
+ <div class="orion-builder-v2-grid is-3">
362
+ <blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
363
+ <blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
364
+ <blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
365
+ </div>
366
+ </div>
367
+ </section>
368
+ `,
369
+ label: "Testimonials"
370
+ });
371
+ blocks.add("orion-faq", {
372
+ category: "Sections",
373
+ content: `
374
+ <section class="orion-builder-v2-section">
375
+ <div class="orion-builder-v2-container is-narrow">
376
+ <p class="orion-builder-v2-kicker">FAQ</p>
377
+ <h2>Questions answered</h2>
378
+ <details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
379
+ <details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
380
+ <details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
381
+ </div>
382
+ </section>
383
+ `,
384
+ label: "FAQ"
385
+ });
386
+ blocks.add("orion-pricing", {
387
+ category: "Commerce",
388
+ content: `
389
+ <section class="orion-builder-v2-section">
390
+ <div class="orion-builder-v2-container">
391
+ <p class="orion-builder-v2-kicker">Pricing</p>
392
+ <h2>Simple package options</h2>
393
+ <div class="orion-builder-v2-grid is-3">
394
+ <article class="orion-builder-v2-card"><h3>Starter</h3><p class="orion-builder-v2-price">$500</p><p>Best for focused launches.</p><a class="orion-builder-v2-button" href="/contact">Choose Starter</a></article>
395
+ <article class="orion-builder-v2-card is-featured"><h3>Growth</h3><p class="orion-builder-v2-price">$1,500</p><p>Best for expanding teams.</p><a class="orion-builder-v2-button" href="/contact">Choose Growth</a></article>
396
+ <article class="orion-builder-v2-card"><h3>Custom</h3><p class="orion-builder-v2-price">Quote</p><p>Best for complex builds.</p><a class="orion-builder-v2-button" href="/contact">Talk with us</a></article>
397
+ </div>
398
+ </div>
399
+ </section>
400
+ `,
401
+ label: "Pricing"
402
+ });
203
403
  blocks.add("orion-button", {
204
404
  category: "Basic",
205
405
  content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
@@ -224,6 +424,22 @@ var registerOrionBuilderV2Blocks = (editor) => {
224
424
  `,
225
425
  label: "Form"
226
426
  });
427
+ blocks.add("orion-footer", {
428
+ category: "Chrome",
429
+ content: `
430
+ <footer class="orion-builder-v2-footer">
431
+ <div>
432
+ <strong>Brand</strong>
433
+ <p>Short positioning line for the site footer.</p>
434
+ </div>
435
+ <nav aria-label="Footer">
436
+ <a href="/privacy">Privacy</a>
437
+ <a href="/contact">Contact</a>
438
+ </nav>
439
+ </footer>
440
+ `,
441
+ label: "Footer"
442
+ });
227
443
  };
228
444
 
229
445
  // src/builder-v2/editor/projectComponents.ts
@@ -293,7 +509,7 @@ var postToParent = (payload) => {
293
509
  };
294
510
  var buildSavePayload = (editor, status, projectData) => ({
295
511
  builderMode: "grapes-v2",
296
- compiledCss: sanitizeBuilderCss(editor.getCss()),
512
+ compiledCss: scopeBuilderCss(editor.getCss()),
297
513
  compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
298
514
  projectData,
299
515
  status
@@ -392,12 +608,52 @@ var uploadPayloadMediaAssets = async (editor, files) => {
392
608
  editor.AssetManager.add(uploadedAssets);
393
609
  }
394
610
  };
395
- function GrapesPageEditor({ adapter, initialData, pageID }) {
611
+ function GrapesPageEditor({
612
+ adapter,
613
+ autosaveIntervalMs = 3e4,
614
+ initialData,
615
+ pageID
616
+ }) {
396
617
  const containerRef = useRef(null);
397
618
  const editorRef = useRef(null);
619
+ const autosaveTimerRef = useRef(null);
620
+ const saveRef = useRef(async () => void 0);
398
621
  const [error, setError] = useState("");
622
+ const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
623
+ const [lastSavedAt, setLastSavedAt] = useState("");
399
624
  const [loading, setLoading] = useState(true);
625
+ const [selectedDevice, setSelectedDevice] = useState("desktop");
400
626
  const [saving, setSaving] = useState(null);
627
+ const [saveMessage, setSaveMessage] = useState("");
628
+ const [validationIssues, setValidationIssues] = useState([]);
629
+ const updateHistoryState = (editor) => {
630
+ const next = {
631
+ canRedo: editor.UndoManager.hasRedo(),
632
+ canUndo: editor.UndoManager.hasUndo()
633
+ };
634
+ setHistoryState(next);
635
+ postToParent({
636
+ ...next,
637
+ type: "history-state"
638
+ });
639
+ };
640
+ const runValidation = (editor) => {
641
+ const issues = validateBuilderV2Output({
642
+ css: editor.getCss(),
643
+ html: editor.getHtml()
644
+ });
645
+ setValidationIssues(issues);
646
+ postToParent({ issues, type: "validation-state" });
647
+ return issues;
648
+ };
649
+ const setDevice = (device) => {
650
+ const editor = editorRef.current;
651
+ if (!editor) {
652
+ return;
653
+ }
654
+ editor.setDevice(device);
655
+ setSelectedDevice(device);
656
+ };
401
657
  useEffect(() => {
402
658
  let active = true;
403
659
  const init = async () => {
@@ -481,12 +737,24 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
481
737
  void loadPayloadMediaAssets(editor);
482
738
  editor.on("update", () => {
483
739
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
484
- postToParent({
485
- canRedo: editor.UndoManager.hasRedo(),
486
- canUndo: editor.UndoManager.hasUndo(),
487
- type: "history-state"
488
- });
740
+ updateHistoryState(editor);
741
+ runValidation(editor);
742
+ setSaveMessage("Unsaved changes");
743
+ if (autosaveTimerRef.current) {
744
+ window.clearTimeout(autosaveTimerRef.current);
745
+ }
746
+ autosaveTimerRef.current = window.setTimeout(() => {
747
+ if (editor.getDirtyCount() > 0) {
748
+ void saveRef.current("draft", { autosave: true });
749
+ }
750
+ }, autosaveIntervalMs);
751
+ });
752
+ editor.on("component:selected", () => {
753
+ setSaveMessage("Selection ready");
489
754
  });
755
+ setSelectedDevice(editor.getDevice() || "desktop");
756
+ runValidation(editor);
757
+ updateHistoryState(editor);
490
758
  setLoading(false);
491
759
  } catch (initError) {
492
760
  setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
@@ -496,15 +764,30 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
496
764
  void init();
497
765
  return () => {
498
766
  active = false;
767
+ if (autosaveTimerRef.current) {
768
+ window.clearTimeout(autosaveTimerRef.current);
769
+ }
499
770
  editorRef.current?.destroy();
500
771
  editorRef.current = null;
501
772
  };
502
- }, [adapter, initialData?.projectData, initialData?.title]);
503
- const save = async (status) => {
773
+ }, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
774
+ const save = async (status, options = {}) => {
504
775
  const editor = editorRef.current;
505
776
  if (!editor || saving) {
506
777
  return;
507
778
  }
779
+ const issues = runValidation(editor);
780
+ if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
781
+ const message = "Resolve blocking validation errors before publishing.";
782
+ setSaveMessage(message);
783
+ postToParent({
784
+ message,
785
+ ok: false,
786
+ status,
787
+ type: "save-result"
788
+ });
789
+ return;
790
+ }
508
791
  setSaving(status);
509
792
  try {
510
793
  const projectData = editor.getProjectData();
@@ -519,15 +802,19 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
519
802
  builderMode: "grapes-v2",
520
803
  builderProjectData: projectData,
521
804
  builderPublishedProjectData: projectData,
522
- builderValidationIssues: [],
805
+ builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
806
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
807
+ builderValidationIssues: issues,
523
808
  compiledCss: payload.compiledCss,
524
809
  compiledHtml: payload.compiledHtml
525
810
  } : {
526
811
  _status: "draft",
812
+ builderAutosaveProjectData: options.autosave ? projectData : null,
527
813
  builderDynamicComponents: dynamicComponents,
528
814
  builderMode: "grapes-v2",
529
815
  builderProjectData: projectData,
530
- builderValidationIssues: [],
816
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
817
+ builderValidationIssues: issues,
531
818
  compiledCss: payload.compiledCss,
532
819
  compiledHtml: payload.compiledHtml
533
820
  }
@@ -550,14 +837,18 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
550
837
  type: "dirty-state"
551
838
  });
552
839
  postToParent({
553
- message: status === "published" ? "Published." : "Draft saved.",
840
+ message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
554
841
  ok: true,
555
842
  status,
556
843
  type: "save-result"
557
844
  });
845
+ setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
846
+ setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
558
847
  } catch (saveError) {
848
+ const message = saveError instanceof Error ? saveError.message : "Could not save.";
849
+ setSaveMessage(message);
559
850
  postToParent({
560
- message: saveError instanceof Error ? saveError.message : "Could not save.",
851
+ message,
561
852
  ok: false,
562
853
  status,
563
854
  type: "save-result"
@@ -566,6 +857,9 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
566
857
  setSaving(null);
567
858
  }
568
859
  };
860
+ useEffect(() => {
861
+ saveRef.current = save;
862
+ }, [saving]);
569
863
  useEffect(() => {
570
864
  const onMessage = (event) => {
571
865
  const data = event.data;
@@ -575,19 +869,11 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
575
869
  }
576
870
  if (data.type === "dirty-check-request") {
577
871
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
578
- postToParent({
579
- canRedo: editor.UndoManager.hasRedo(),
580
- canUndo: editor.UndoManager.hasUndo(),
581
- type: "history-state"
582
- });
872
+ updateHistoryState(editor);
583
873
  return;
584
874
  }
585
875
  if (data.type === "history-check-request") {
586
- postToParent({
587
- canRedo: editor.UndoManager.hasRedo(),
588
- canUndo: editor.UndoManager.hasUndo(),
589
- type: "history-state"
590
- });
876
+ updateHistoryState(editor);
591
877
  return;
592
878
  }
593
879
  if (data.type === "undo") {
@@ -606,19 +892,82 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
606
892
  return () => window.removeEventListener("message", onMessage);
607
893
  }, [saving]);
608
894
  return /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-editor", children: [
895
+ /* @__PURE__ */ jsxs("header", { className: "orion-builder-v2-topbar", children: [
896
+ /* @__PURE__ */ jsxs("div", { children: [
897
+ /* @__PURE__ */ jsx("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
898
+ /* @__PURE__ */ jsx("h1", { children: initialData?.title || "Untitled page" })
899
+ ] }),
900
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
901
+ ["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ jsx(
902
+ "button",
903
+ {
904
+ "aria-pressed": selectedDevice === device,
905
+ className: "orion-builder-v2-tool",
906
+ onClick: () => setDevice(device),
907
+ type: "button",
908
+ children: device
909
+ },
910
+ device
911
+ )),
912
+ /* @__PURE__ */ jsx(
913
+ "button",
914
+ {
915
+ className: "orion-builder-v2-tool",
916
+ disabled: !historyState.canUndo,
917
+ onClick: () => {
918
+ editorRef.current?.UndoManager.undo();
919
+ editorRef.current && updateHistoryState(editorRef.current);
920
+ },
921
+ type: "button",
922
+ children: "Undo"
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsx(
926
+ "button",
927
+ {
928
+ className: "orion-builder-v2-tool",
929
+ disabled: !historyState.canRedo,
930
+ onClick: () => {
931
+ editorRef.current?.UndoManager.redo();
932
+ editorRef.current && updateHistoryState(editorRef.current);
933
+ },
934
+ type: "button",
935
+ children: "Redo"
936
+ }
937
+ ),
938
+ /* @__PURE__ */ jsx("button", { className: "orion-builder-v2-tool is-primary", disabled: Boolean(saving), onClick: () => void save("draft"), type: "button", children: saving === "draft" ? "Saving..." : "Save draft" }),
939
+ /* @__PURE__ */ jsx("button", { className: "orion-builder-v2-tool is-publish", disabled: Boolean(saving), onClick: () => void save("published"), type: "button", children: saving === "published" ? "Publishing..." : "Publish" })
940
+ ] })
941
+ ] }),
609
942
  /* @__PURE__ */ jsxs("aside", { className: "orion-builder-v2-sidebar", children: [
610
943
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
611
944
  /* @__PURE__ */ jsx("h2", { children: "Blocks" }),
945
+ /* @__PURE__ */ jsx("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
612
946
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-blocks" })
613
947
  ] }),
614
948
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
615
- /* @__PURE__ */ jsx("h2", { children: "Settings" }),
949
+ /* @__PURE__ */ jsx("h2", { children: "Inspector" }),
950
+ /* @__PURE__ */ jsx("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
616
951
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
952
+ ] }),
953
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
954
+ /* @__PURE__ */ jsx("h2", { children: "Validation" }),
955
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-validation-list", children: validationIssues.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No issues found." }) : validationIssues.map((issue) => /* @__PURE__ */ jsxs("div", { className: `orion-builder-v2-validation is-${issue.severity}`, children: [
956
+ /* @__PURE__ */ jsx("strong", { children: issue.message }),
957
+ /* @__PURE__ */ jsx("span", { children: issue.severity })
958
+ ] }, `${issue.code}-${issue.path}`)) })
617
959
  ] })
618
960
  ] }),
619
961
  /* @__PURE__ */ jsxs("main", { className: "orion-builder-v2-main", children: [
620
962
  loading ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
621
963
  error ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-error", children: error }) : null,
964
+ saveMessage || lastSavedAt ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-save-status", children: [
965
+ /* @__PURE__ */ jsx("strong", { children: saveMessage || "Ready" }),
966
+ lastSavedAt ? /* @__PURE__ */ jsxs("span", { children: [
967
+ "Last saved ",
968
+ lastSavedAt
969
+ ] }) : null
970
+ ] }) : null,
622
971
  /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-canvas", ref: containerRef })
623
972
  ] })
624
973
  ] });
@@ -15,18 +15,76 @@ type BuilderV2ValidationIssue = {
15
15
  path: string;
16
16
  severity: 'error' | 'warning';
17
17
  };
18
+ type BuilderV2Role = 'admin' | 'client' | 'developer' | 'editor';
19
+ type BuilderV2PermissionSet = {
20
+ canEditCustomCode?: boolean;
21
+ canManageGlobalStyles?: boolean;
22
+ canManageReusableSections?: boolean;
23
+ canPublish?: boolean;
24
+ canUseEmbeds?: boolean;
25
+ role?: BuilderV2Role;
26
+ };
18
27
  type BuilderV2CompiledOutput = {
19
28
  css: string;
20
29
  dynamicComponents: BuilderV2DynamicComponentInstance[];
21
30
  html: string;
31
+ validationIssues?: BuilderV2ValidationIssue[];
22
32
  };
23
33
  type BuilderV2ProjectData = Record<string, unknown>;
34
+ type BuilderV2ThemeTokens = {
35
+ buttons?: Record<string, unknown>;
36
+ colors?: Record<string, string>;
37
+ fonts?: Record<string, string>;
38
+ radii?: Record<string, string>;
39
+ shadows?: Record<string, string>;
40
+ spacing?: Record<string, string>;
41
+ typography?: Record<string, unknown>;
42
+ };
43
+ type BuilderV2PageTreeNode = {
44
+ children?: BuilderV2PageTreeNode[];
45
+ id: number | string;
46
+ parentId?: number | string | null;
47
+ path: string;
48
+ slug: string;
49
+ status?: 'draft' | 'published';
50
+ title: string;
51
+ };
52
+ type BuilderV2VersionSnapshot = {
53
+ compiledCss?: string;
54
+ compiledHtml?: string;
55
+ createdAt: string;
56
+ id: string;
57
+ label: string;
58
+ projectData?: BuilderV2ProjectData | null;
59
+ published?: boolean;
60
+ };
61
+ type BuilderV2ReusableSection = {
62
+ category?: string;
63
+ id: string;
64
+ name: string;
65
+ projectData: BuilderV2ProjectData;
66
+ thumbnail?: string;
67
+ };
68
+ type BuilderV2EditorMeta = {
69
+ pageTree?: BuilderV2PageTreeNode[];
70
+ permissions?: BuilderV2PermissionSet;
71
+ themeTokens?: BuilderV2ThemeTokens;
72
+ };
24
73
  type BuilderV2PageData = {
74
+ builderAutosaveProjectData?: BuilderV2ProjectData | null;
25
75
  builderDynamicComponents?: BuilderV2DynamicComponentInstance[];
76
+ builderLastPublishedAt?: string;
77
+ builderLastSavedAt?: string;
78
+ builderLockedAreas?: string[];
26
79
  builderMode?: string;
27
80
  builderProjectData?: BuilderV2ProjectData | null;
28
81
  builderPublishedProjectData?: BuilderV2ProjectData | null;
82
+ builderPublishedSnapshot?: BuilderV2VersionSnapshot | null;
83
+ builderReusableSections?: BuilderV2ReusableSection[];
84
+ builderSeo?: Record<string, unknown> | null;
85
+ builderThemeTokens?: BuilderV2ThemeTokens | null;
29
86
  builderValidationIssues?: BuilderV2ValidationIssue[];
87
+ builderVersions?: BuilderV2VersionSnapshot[];
30
88
  compiledCss?: string;
31
89
  compiledHtml?: string;
32
90
  id?: number | string;
@@ -70,11 +128,13 @@ type BuilderV2TraitDefinition = {
70
128
  type BuilderV2ProjectAdapter = {
71
129
  components?: Record<string, BuilderV2ProjectComponentDefinition | BuilderV2RuntimeComponent>;
72
130
  dataProviders?: Record<string, unknown>;
131
+ defaultThemeTokens?: BuilderV2ThemeTokens;
73
132
  id: string;
74
133
  label?: string;
75
134
  themeTokens?: Record<string, unknown>;
76
135
  };
77
136
  type BuilderV2EditorInitialData = {
137
+ meta?: BuilderV2EditorMeta;
78
138
  projectData?: BuilderV2ProjectData | null;
79
139
  title?: string;
80
140
  };
@@ -145,6 +205,19 @@ declare const createBuilderV2PageService: ({ collectionSlug, payload, user, }: C
145
205
  declare const createEmptyBuilderV2ProjectData: (title?: string) => BuilderV2ProjectData;
146
206
  declare const normalizeBuilderV2ProjectData: (value: unknown, fallbackTitle?: string) => BuilderV2ProjectData;
147
207
 
208
+ type PageLike = {
209
+ id: number | string;
210
+ parent?: number | string | {
211
+ id?: number | string;
212
+ } | null;
213
+ parentId?: number | string | null;
214
+ path?: string | null;
215
+ slug?: string | null;
216
+ title?: string | null;
217
+ _status?: 'draft' | 'published';
218
+ };
219
+ declare const buildBuilderV2PageTree: (pages: PageLike[]) => BuilderV2PageTreeNode[];
220
+
148
221
  declare function BuilderPageRuntime({ adapter, className, page }: BuilderV2RuntimeProps): react_jsx_runtime.JSX.Element;
149
222
 
150
223
  declare const parseBuilderV2DynamicComponents: (html: string) => BuilderV2DynamicComponentInstance[];
@@ -152,5 +225,12 @@ declare const splitBuilderV2HtmlIntoChunks: (html: string) => BuilderV2Renderabl
152
225
 
153
226
  declare const sanitizeBuilderHtml: (value: unknown) => string;
154
227
  declare const sanitizeBuilderCss: (value: unknown) => string;
228
+ declare const scopeBuilderCss: (value: unknown, scope?: string) => string;
229
+
230
+ declare const validateBuilderV2Output: (input: {
231
+ css?: unknown;
232
+ html?: unknown;
233
+ }) => BuilderV2ValidationIssue[];
234
+ declare const hasBlockingBuilderV2Issues: (issues: BuilderV2ValidationIssue[]) => boolean;
155
235
 
156
- export { BuilderPageRuntime, type BuilderV2Asset, type BuilderV2CompiledOutput, type BuilderV2DynamicComponentInstance, type BuilderV2EditorInitialData, type BuilderV2FieldOptions, type BuilderV2Mode, type BuilderV2PageData, type BuilderV2PagePayloadDoc, type BuilderV2ProjectAdapter, type BuilderV2ProjectComponentDefinition, type BuilderV2ProjectData, type BuilderV2RenderResult, type BuilderV2RenderableChunk, type BuilderV2RuntimeComponent, type BuilderV2RuntimeComponentProps, type BuilderV2RuntimeProps, type BuilderV2TraitDefinition, type BuilderV2ValidationIssue, type CreateBuilderV2PageServiceArgs, type SaveBuilderV2PageInput, appendBuilderV2PageFields, compileBuilderV2Output, createBuilderV2PageFields, createBuilderV2PageService, createEmptyBuilderV2ProjectData, normalizeBuilderV2ProjectData, parseBuilderV2DynamicComponents, sanitizeBuilderCss, sanitizeBuilderHtml, splitBuilderV2HtmlIntoChunks, toBuilderV2EditorInitialData };
236
+ export { BuilderPageRuntime, type BuilderV2Asset, type BuilderV2CompiledOutput, type BuilderV2DynamicComponentInstance, type BuilderV2EditorInitialData, type BuilderV2FieldOptions, type BuilderV2Mode, type BuilderV2PageData, type BuilderV2PagePayloadDoc, type BuilderV2PageTreeNode, type BuilderV2PermissionSet, type BuilderV2ProjectAdapter, type BuilderV2ProjectComponentDefinition, type BuilderV2ProjectData, type BuilderV2RenderResult, type BuilderV2RenderableChunk, type BuilderV2RuntimeComponent, type BuilderV2RuntimeComponentProps, type BuilderV2RuntimeProps, type BuilderV2ThemeTokens, type BuilderV2TraitDefinition, type BuilderV2ValidationIssue, type BuilderV2VersionSnapshot, type CreateBuilderV2PageServiceArgs, type SaveBuilderV2PageInput, appendBuilderV2PageFields, buildBuilderV2PageTree, compileBuilderV2Output, createBuilderV2PageFields, createBuilderV2PageService, createEmptyBuilderV2ProjectData, hasBlockingBuilderV2Issues, normalizeBuilderV2ProjectData, parseBuilderV2DynamicComponents, sanitizeBuilderCss, sanitizeBuilderHtml, scopeBuilderCss, splitBuilderV2HtmlIntoChunks, toBuilderV2EditorInitialData, validateBuilderV2Output };