@orion-studios/payload-studio 0.6.0-beta.13 → 0.6.0-beta.131

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 (38) hide show
  1. package/dist/admin/client.js +2061 -538
  2. package/dist/admin/client.mjs +2078 -568
  3. package/dist/admin/index.js +124 -15
  4. package/dist/admin/index.mjs +1 -1
  5. package/dist/admin-app/client.js +14 -5
  6. package/dist/admin-app/client.mjs +1 -1
  7. package/dist/admin-app/styles.css +277 -1
  8. package/dist/admin.css +98 -2
  9. package/dist/builder-v2/client.d.mts +18 -0
  10. package/dist/builder-v2/client.d.ts +18 -0
  11. package/dist/builder-v2/client.js +3837 -0
  12. package/dist/builder-v2/client.mjs +3712 -0
  13. package/dist/builder-v2/index.d.mts +248 -0
  14. package/dist/builder-v2/index.d.ts +248 -0
  15. package/dist/builder-v2/index.js +805 -0
  16. package/dist/builder-v2/index.mjs +755 -0
  17. package/dist/builder-v2/styles.css +2514 -0
  18. package/dist/{chunk-PF3EBZXF.mjs → chunk-7ZMXZRBP.mjs} +39 -3
  19. package/dist/{chunk-XZQILJK3.mjs → chunk-JC3UV74N.mjs} +124 -15
  20. package/dist/{chunk-KPIX7OSV.mjs → chunk-NF37A575.mjs} +14 -5
  21. package/dist/{chunk-XKUTZ7IU.mjs → chunk-NGLIA2OE.mjs} +53 -2
  22. package/dist/{chunk-OTHERBGX.mjs → chunk-ZADL33R6.mjs} +1 -1
  23. package/dist/{index-Cv-6qnrw.d.mts → index-D5zrOdyv.d.mts} +3 -1
  24. package/dist/{index-Crx_MtPw.d.ts → index-Dv-Alx4h.d.ts} +3 -1
  25. package/dist/index.d.mts +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +215 -19
  28. package/dist/index.mjs +4 -4
  29. package/dist/nextjs/index.js +39 -3
  30. package/dist/nextjs/index.mjs +2 -2
  31. package/dist/studio-pages/builder.css +66 -5
  32. package/dist/studio-pages/client.js +618 -73
  33. package/dist/studio-pages/client.mjs +641 -96
  34. package/dist/studio-pages/index.d.mts +1 -1
  35. package/dist/studio-pages/index.d.ts +1 -1
  36. package/dist/studio-pages/index.js +91 -4
  37. package/dist/studio-pages/index.mjs +2 -2
  38. package/package.json +22 -3
@@ -0,0 +1,3712 @@
1
+ 'use client';
2
+ "use client";
3
+ import {
4
+ MAX_DIRECT_UPLOAD_BYTES,
5
+ optimizeImageForUpload
6
+ } from "../chunk-ROTPP5CU.mjs";
7
+
8
+ // src/builder-v2/editor/GrapesPageEditor.tsx
9
+ import { useEffect, useRef, useState } from "react";
10
+
11
+ // src/builder-v2/projectData.ts
12
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
13
+ var createEmptyBuilderV2ProjectData = (title = "Untitled Page") => ({
14
+ assets: [],
15
+ pages: [
16
+ {
17
+ component: {
18
+ components: [
19
+ {
20
+ attributes: {
21
+ class: "orion-builder-v2-section"
22
+ },
23
+ components: [
24
+ {
25
+ content: title,
26
+ tagName: "h1",
27
+ type: "text"
28
+ },
29
+ {
30
+ content: "Start building this page by dragging blocks from the panel.",
31
+ tagName: "p",
32
+ type: "text"
33
+ }
34
+ ],
35
+ tagName: "section"
36
+ }
37
+ ],
38
+ type: "wrapper"
39
+ },
40
+ id: "page",
41
+ name: title
42
+ }
43
+ ],
44
+ styles: []
45
+ });
46
+ var normalizeBuilderV2ProjectData = (value, fallbackTitle = "Untitled Page") => {
47
+ if (!isRecord(value)) {
48
+ return createEmptyBuilderV2ProjectData(fallbackTitle);
49
+ }
50
+ return value;
51
+ };
52
+
53
+ // src/builder-v2/runtime/placeholders.ts
54
+ var placeholderPattern = /<(?<tag>[a-zA-Z][a-zA-Z0-9-]*)\b(?<attrs>[^>]*)data-orion-component=["'](?<component>[^"']+)["'](?<rest>[^>]*)>/gis;
55
+ var attrPattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)=(?:"([^"]*)"|'([^']*)')/g;
56
+ var decodeHtmlAttribute = (value) => value.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
57
+ var parseAttributes = (value) => {
58
+ const props = {};
59
+ let match;
60
+ while ((match = attrPattern.exec(value)) !== null) {
61
+ const name = match[1];
62
+ const rawValue = match[2] ?? match[3] ?? "";
63
+ if (!name.startsWith("data-")) {
64
+ continue;
65
+ }
66
+ if (name === "data-orion-component" || name === "data-orion-id") {
67
+ continue;
68
+ }
69
+ if (name === "data-orion-props") {
70
+ const decoded = decodeHtmlAttribute(rawValue);
71
+ try {
72
+ const parsed = JSON.parse(decoded);
73
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
74
+ Object.assign(props, parsed);
75
+ }
76
+ } catch {
77
+ props.propsJson = decoded;
78
+ }
79
+ continue;
80
+ }
81
+ const propName = name.replace(/^data-orion-/, "").replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase());
82
+ props[propName] = decodeHtmlAttribute(rawValue);
83
+ }
84
+ return props;
85
+ };
86
+ var extractAttribute = (attrs, name) => {
87
+ attrPattern.lastIndex = 0;
88
+ let match;
89
+ while ((match = attrPattern.exec(attrs)) !== null) {
90
+ if (match[1] === name) {
91
+ return decodeHtmlAttribute(match[2] ?? match[3] ?? "");
92
+ }
93
+ }
94
+ return null;
95
+ };
96
+ var extractWrapperAttributes = (attrs) => {
97
+ const id = extractAttribute(attrs, "id") || void 0;
98
+ const className = extractAttribute(attrs, "class") || void 0;
99
+ const style = extractAttribute(attrs, "style") || void 0;
100
+ return id || className || style ? { className, id, style } : void 0;
101
+ };
102
+ var parseBuilderV2DynamicComponents = (html) => {
103
+ const instances = [];
104
+ let match;
105
+ while ((match = placeholderPattern.exec(html)) !== null) {
106
+ const attrs = `${match.groups?.attrs ?? ""} ${match.groups?.rest ?? ""}`;
107
+ const component = match.groups?.component ?? "";
108
+ if (!component) {
109
+ continue;
110
+ }
111
+ instances.push({
112
+ component,
113
+ id: extractAttribute(attrs, "data-orion-id") || `component-${instances.length + 1}`,
114
+ props: parseAttributes(attrs),
115
+ wrapperAttributes: extractWrapperAttributes(attrs)
116
+ });
117
+ }
118
+ return instances;
119
+ };
120
+
121
+ // src/builder-v2/sanitize.ts
122
+ import sanitizeHtml from "sanitize-html";
123
+ var allowedTags = sanitizeHtml.defaults.allowedTags.concat([
124
+ "article",
125
+ "aside",
126
+ "button",
127
+ "figure",
128
+ "figcaption",
129
+ "footer",
130
+ "header",
131
+ "main",
132
+ "nav",
133
+ "section",
134
+ "source",
135
+ "video"
136
+ ]);
137
+ var allowedAttributes = {
138
+ ...sanitizeHtml.defaults.allowedAttributes,
139
+ "*": [
140
+ "aria-*",
141
+ "class",
142
+ "data-*",
143
+ "id",
144
+ "role",
145
+ "style",
146
+ "title"
147
+ ],
148
+ a: ["aria-*", "class", "data-*", "href", "id", "name", "rel", "style", "target", "title"],
149
+ button: ["aria-*", "class", "data-*", "disabled", "id", "style", "title", "type"],
150
+ iframe: ["allow", "allowfullscreen", "class", "data-*", "height", "loading", "src", "style", "title", "width"],
151
+ img: ["alt", "class", "data-*", "height", "id", "loading", "sizes", "src", "srcset", "style", "title", "width"],
152
+ source: ["media", "src", "srcset", "type"],
153
+ video: ["autoplay", "class", "controls", "height", "loop", "muted", "playsinline", "poster", "preload", "src", "style", "width"]
154
+ };
155
+ var allowedIframeHosts = [
156
+ "calendar.google.com",
157
+ "calendly.com",
158
+ "player.vimeo.com",
159
+ "www.google.com",
160
+ "www.youtube.com",
161
+ "youtube.com"
162
+ ];
163
+ var sanitizeBuilderHtml = (value) => {
164
+ if (typeof value !== "string" || value.trim().length === 0) {
165
+ return "";
166
+ }
167
+ return sanitizeHtml(value, {
168
+ allowedAttributes,
169
+ allowedIframeHostnames: allowedIframeHosts,
170
+ allowedSchemes: ["http", "https", "mailto", "tel"],
171
+ allowedSchemesByTag: {
172
+ img: ["http", "https", "data"]
173
+ },
174
+ allowedTags,
175
+ allowProtocolRelative: false,
176
+ parseStyleAttributes: false
177
+ });
178
+ };
179
+ var sanitizeBuilderCss = (value) => {
180
+ if (typeof value !== "string" || value.trim().length === 0) {
181
+ return "";
182
+ }
183
+ return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
184
+ };
185
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
186
+ const trimmed = part.trim();
187
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
188
+ return trimmed;
189
+ }
190
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
191
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
192
+ }
193
+ return `${scope} ${trimmed}`;
194
+ }).filter(Boolean).join(", ");
195
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
196
+ const css = sanitizeBuilderCss(value);
197
+ if (!css) {
198
+ return "";
199
+ }
200
+ let output = "";
201
+ let cursor = 0;
202
+ const rulePattern = /([^{}]+)\{/g;
203
+ let match;
204
+ while ((match = rulePattern.exec(css)) !== null) {
205
+ const selectorStart = match.index;
206
+ const selector = match[1];
207
+ output += css.slice(cursor, selectorStart);
208
+ const trimmedSelector = selector.trim();
209
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
210
+ output += `${selector}{`;
211
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
212
+ output += `${selector}{`;
213
+ } else {
214
+ output += `${scopeSelector(selector, scope)} {`;
215
+ }
216
+ cursor = rulePattern.lastIndex;
217
+ }
218
+ output += css.slice(cursor);
219
+ return output;
220
+ };
221
+
222
+ // src/builder-v2/validation.ts
223
+ var hasMatch = (value, pattern) => pattern.test(value);
224
+ var validateBuilderV2Output = (input) => {
225
+ const html = typeof input.html === "string" ? input.html : "";
226
+ const css = typeof input.css === "string" ? input.css : "";
227
+ const issues = [];
228
+ if (!html.trim()) {
229
+ issues.push({
230
+ code: "empty-page",
231
+ message: "This page has no rendered content.",
232
+ path: "compiledHtml",
233
+ severity: "error"
234
+ });
235
+ }
236
+ if (!hasMatch(html, /<h1\b/i)) {
237
+ issues.push({
238
+ code: "missing-h1",
239
+ message: "Add one H1 so the published page has a clear primary heading.",
240
+ path: "compiledHtml",
241
+ severity: "warning"
242
+ });
243
+ }
244
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
245
+ issues.push({
246
+ code: "inline-event-handler",
247
+ message: "Inline event handlers are not allowed in published builder HTML.",
248
+ path: "compiledHtml",
249
+ severity: "error"
250
+ });
251
+ }
252
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
253
+ issues.push({
254
+ code: "unsafe-element",
255
+ message: "Script, object, and embed tags are not allowed in builder content.",
256
+ path: "compiledHtml",
257
+ severity: "error"
258
+ });
259
+ }
260
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
261
+ issues.push({
262
+ code: "unsafe-url",
263
+ message: "Links and media cannot use javascript URLs.",
264
+ path: "compiledHtml",
265
+ severity: "error"
266
+ });
267
+ }
268
+ if (hasMatch(css, /@import\s/i)) {
269
+ issues.push({
270
+ code: "css-import",
271
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
272
+ path: "compiledCss",
273
+ severity: "warning"
274
+ });
275
+ }
276
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
277
+ issues.push({
278
+ code: "fixed-position",
279
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
280
+ path: "compiledCss",
281
+ severity: "warning"
282
+ });
283
+ }
284
+ return issues;
285
+ };
286
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
287
+
288
+ // src/builder-v2/editor/defaultBlocks.ts
289
+ var blockPreview = (variant) => `
290
+ <div class="orion-builder-v2-block-preview is-${variant}" aria-hidden="true">
291
+ <span class="wire wire-kicker"></span>
292
+ <span class="wire wire-title"></span>
293
+ <span class="wire wire-copy"></span>
294
+ <span class="wire wire-copy is-short"></span>
295
+ <span class="wire wire-button"></span>
296
+ <span class="wire wire-image"></span>
297
+ <span class="wire wire-card"></span>
298
+ <span class="wire wire-card"></span>
299
+ <span class="wire wire-card"></span>
300
+ </div>
301
+ `;
302
+ var registerOrionBuilderV2Blocks = (editor) => {
303
+ const blocks = editor.Blocks;
304
+ blocks.add("orion-section", {
305
+ category: "Layout",
306
+ content: `
307
+ <section class="orion-builder-v2-section">
308
+ <div class="orion-builder-v2-container">
309
+ <h2>Section Heading</h2>
310
+ <p>Add supporting content here.</p>
311
+ </div>
312
+ </section>
313
+ `,
314
+ label: "Section",
315
+ media: blockPreview("section")
316
+ });
317
+ blocks.add("orion-hero", {
318
+ category: "Sections",
319
+ content: `
320
+ <section class="orion-builder-v2-hero">
321
+ <div class="orion-builder-v2-container">
322
+ <p class="orion-builder-v2-kicker">Optional kicker</p>
323
+ <h1>Build a stronger website</h1>
324
+ <p>Use this area for a clear value proposition and call to action.</p>
325
+ <a class="orion-builder-v2-button" href="/contact">Get started</a>
326
+ </div>
327
+ </section>
328
+ `,
329
+ label: "Hero",
330
+ media: blockPreview("hero")
331
+ });
332
+ blocks.add("orion-columns", {
333
+ category: "Layout",
334
+ content: `
335
+ <section class="orion-builder-v2-section">
336
+ <div class="orion-builder-v2-container orion-builder-v2-grid is-3">
337
+ <article class="orion-builder-v2-card"><h3>Column One</h3><p>Add content.</p></article>
338
+ <article class="orion-builder-v2-card"><h3>Column Two</h3><p>Add content.</p></article>
339
+ <article class="orion-builder-v2-card"><h3>Column Three</h3><p>Add content.</p></article>
340
+ </div>
341
+ </section>
342
+ `,
343
+ label: "Columns",
344
+ media: blockPreview("columns")
345
+ });
346
+ blocks.add("orion-card-grid", {
347
+ category: "Sections",
348
+ content: `
349
+ <section class="orion-builder-v2-section">
350
+ <div class="orion-builder-v2-container">
351
+ <p class="orion-builder-v2-kicker">Featured</p>
352
+ <h2>Cards that explain the offer</h2>
353
+ <div class="orion-builder-v2-grid is-3">
354
+ <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>
355
+ <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>
356
+ <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>
357
+ </div>
358
+ </div>
359
+ </section>
360
+ `,
361
+ label: "Card grid",
362
+ media: blockPreview("card-grid")
363
+ });
364
+ blocks.add("orion-gallery", {
365
+ category: "Media",
366
+ content: `
367
+ <section class="orion-builder-v2-section">
368
+ <div class="orion-builder-v2-container">
369
+ <h2>Gallery</h2>
370
+ <div class="orion-builder-v2-gallery">
371
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
372
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
373
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
374
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
375
+ </div>
376
+ </div>
377
+ </section>
378
+ `,
379
+ label: "Gallery",
380
+ media: blockPreview("gallery")
381
+ });
382
+ blocks.add("orion-testimonials", {
383
+ category: "Sections",
384
+ content: `
385
+ <section class="orion-builder-v2-section is-muted">
386
+ <div class="orion-builder-v2-container">
387
+ <p class="orion-builder-v2-kicker">Testimonials</p>
388
+ <h2>What clients say</h2>
389
+ <div class="orion-builder-v2-grid is-3">
390
+ <blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
391
+ <blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
392
+ <blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
393
+ </div>
394
+ </div>
395
+ </section>
396
+ `,
397
+ label: "Testimonials",
398
+ media: blockPreview("testimonials")
399
+ });
400
+ blocks.add("orion-faq", {
401
+ category: "Sections",
402
+ content: `
403
+ <section class="orion-builder-v2-section">
404
+ <div class="orion-builder-v2-container is-narrow">
405
+ <p class="orion-builder-v2-kicker">FAQ</p>
406
+ <h2>Questions answered</h2>
407
+ <details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
408
+ <details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
409
+ <details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
410
+ </div>
411
+ </section>
412
+ `,
413
+ label: "FAQ",
414
+ media: blockPreview("faq")
415
+ });
416
+ blocks.add("orion-pricing", {
417
+ category: "Commerce",
418
+ content: `
419
+ <section class="orion-builder-v2-section">
420
+ <div class="orion-builder-v2-container">
421
+ <p class="orion-builder-v2-kicker">Pricing</p>
422
+ <h2>Simple package options</h2>
423
+ <div class="orion-builder-v2-grid is-3">
424
+ <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>
425
+ <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>
426
+ <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>
427
+ </div>
428
+ </div>
429
+ </section>
430
+ `,
431
+ label: "Pricing",
432
+ media: blockPreview("pricing")
433
+ });
434
+ blocks.add("orion-button", {
435
+ category: "Basic",
436
+ content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
437
+ label: "Button",
438
+ media: blockPreview("button")
439
+ });
440
+ blocks.add("orion-image", {
441
+ category: "Basic",
442
+ content: '<img alt="Image" class="orion-builder-v2-image" src="https://placehold.co/1200x700" />',
443
+ label: "Image",
444
+ media: blockPreview("image")
445
+ });
446
+ blocks.add("orion-form-embed", {
447
+ category: "Dynamic",
448
+ content: `
449
+ <div
450
+ class="orion-builder-v2-dynamic-placeholder"
451
+ data-orion-component="formEmbed"
452
+ data-orion-form-slug="contact"
453
+ >
454
+ <strong>Form Embed</strong>
455
+ <span>contact</span>
456
+ </div>
457
+ `,
458
+ label: "Form",
459
+ media: blockPreview("form")
460
+ });
461
+ };
462
+
463
+ // src/builder-v2/editor/projectComponents.ts
464
+ var normalizeDefinition = (type, value) => {
465
+ if (!value || typeof value === "function") {
466
+ return null;
467
+ }
468
+ return {
469
+ ...value,
470
+ type: value.type || type
471
+ };
472
+ };
473
+ var decodeHtmlAttribute2 = (value) => {
474
+ if (typeof value !== "string") {
475
+ return "";
476
+ }
477
+ let decoded = value;
478
+ for (let index = 0; index < 3; index += 1) {
479
+ const next = decoded.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
480
+ if (next === decoded) {
481
+ break;
482
+ }
483
+ decoded = next;
484
+ }
485
+ return decoded;
486
+ };
487
+ var attrToPropName = (name) => name.replace(/^data-orion-/, "").replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase());
488
+ var propToAttrName = (name) => `data-orion-${name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`;
489
+ var headingFieldNames = /* @__PURE__ */ new Set(["headline", "label", "question", "title", "value"]);
490
+ var textKindForField = (field, element) => /^h[1-6]$/i.test(element.tagName) || headingFieldNames.has(field) ? "heading" : "body";
491
+ var markActiveEditableField = (model, field, element) => {
492
+ const computed = window.getComputedStyle(element);
493
+ model.addAttributes?.({
494
+ "data-orion-active-edit-field": field,
495
+ "data-orion-active-text-kind": textKindForField(field, element),
496
+ "data-orion-active-font-family": computed.fontFamily,
497
+ "data-orion-active-font-size": computed.fontSize,
498
+ "data-orion-active-font-weight": computed.fontWeight,
499
+ "data-orion-active-line-height": computed.lineHeight,
500
+ "data-orion-active-letter-spacing": computed.letterSpacing,
501
+ "data-orion-active-color": computed.color,
502
+ "data-orion-active-text-align": computed.textAlign
503
+ });
504
+ };
505
+ var propsFromAttributes = (attributes = {}) => {
506
+ const props = {};
507
+ Object.keys(attributes).forEach((name) => {
508
+ if (!name.startsWith("data-") || name === "data-orion-component" || name === "data-orion-id") {
509
+ return;
510
+ }
511
+ const value = decodeHtmlAttribute2(attributes[name]);
512
+ if (name === "data-orion-props") {
513
+ try {
514
+ const parsed = JSON.parse(value);
515
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
516
+ Object.assign(props, parsed);
517
+ }
518
+ } catch {
519
+ props.propsJson = value;
520
+ }
521
+ return;
522
+ }
523
+ props[attrToPropName(name)] = value;
524
+ });
525
+ return props;
526
+ };
527
+ var parseJsonRecord = (value) => {
528
+ const decoded = decodeHtmlAttribute2(value);
529
+ if (!decoded) {
530
+ return {};
531
+ }
532
+ try {
533
+ const parsed = JSON.parse(decoded);
534
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
535
+ } catch {
536
+ return {};
537
+ }
538
+ };
539
+ var serializeJsonRecord = (value) => JSON.stringify(value);
540
+ var normalizeCssColor = (value) => typeof value === "string" && value.trim() && value.trim() !== "none" ? value.trim() : "";
541
+ var withoutUndoTracking = (editor, callback) => {
542
+ const undoManager = editor?.UndoManager;
543
+ if (typeof undoManager?.skip === "function") {
544
+ undoManager.skip(callback);
545
+ return;
546
+ }
547
+ if (typeof undoManager?.stop === "function" && typeof undoManager?.start === "function") {
548
+ undoManager.stop();
549
+ try {
550
+ callback();
551
+ } finally {
552
+ undoManager.start();
553
+ }
554
+ return;
555
+ }
556
+ callback();
557
+ };
558
+ var defaultBackgroundForComponent = (type, props) => {
559
+ const block = parseJsonRecord(props.block);
560
+ const explicit = normalizeCssColor(block.backgroundColor) || normalizeCssColor(props.backgroundColor);
561
+ if (explicit) {
562
+ return explicit;
563
+ }
564
+ if (type === "xoFeatureGrid" || type === "xoRichText") {
565
+ return "#fbfaf7";
566
+ }
567
+ if (type === "xoHero" && !props.backgroundImageURL && !props.imageURL) {
568
+ return "#fbfaf7";
569
+ }
570
+ return "";
571
+ };
572
+ var syncModelBackgroundToBlock = (model) => {
573
+ const attrs = model.getAttributes?.() || {};
574
+ const block = parseJsonRecord(attrs["data-orion-block"]);
575
+ const style = model.getStyle?.() || {};
576
+ const backgroundColor = normalizeCssColor(style["background-color"] || style.backgroundColor);
577
+ if (!backgroundColor || Object.keys(block).length === 0) {
578
+ return;
579
+ }
580
+ model.addAttributes?.({
581
+ "data-orion-background-color": backgroundColor,
582
+ "data-orion-block": serializeJsonRecord({
583
+ ...block,
584
+ backgroundColor
585
+ })
586
+ });
587
+ };
588
+ var spacingLonghands = [
589
+ "margin-top",
590
+ "margin-bottom",
591
+ "margin-left",
592
+ "margin-right",
593
+ "padding-top",
594
+ "padding-bottom",
595
+ "padding-left",
596
+ "padding-right"
597
+ ];
598
+ var hasSpacingStyle = (style, property) => Boolean(style[property] || style[property.replace(/-([a-z])/g, (_, char) => char.toUpperCase())]);
599
+ var hoistPreviewSpacingToModel = (view) => {
600
+ const element = view.el;
601
+ const preview = element?.querySelector?.(".xo-builder-preview");
602
+ if (!preview) {
603
+ return;
604
+ }
605
+ const model = view.model;
606
+ const style = model.getStyle?.() || {};
607
+ const computed = window.getComputedStyle(preview);
608
+ const nextStyle = {};
609
+ spacingLonghands.forEach((property) => {
610
+ const isPadding = property.startsWith("padding-");
611
+ if (hasSpacingStyle(style, property) || hasSpacingStyle(style, isPadding ? "padding" : "margin")) {
612
+ return;
613
+ }
614
+ const value = computed.getPropertyValue(property).trim();
615
+ if (value && (isPadding || value !== "0px")) {
616
+ nextStyle[property] = value;
617
+ }
618
+ });
619
+ if (Object.keys(nextStyle).length > 0) {
620
+ model.addStyle?.(nextStyle);
621
+ }
622
+ preview.style.margin = "0";
623
+ preview.style.marginTop = "0";
624
+ preview.style.marginRight = "0";
625
+ preview.style.marginBottom = "0";
626
+ preview.style.marginLeft = "0";
627
+ preview.style.padding = "0";
628
+ preview.style.paddingTop = "0";
629
+ preview.style.paddingRight = "0";
630
+ preview.style.paddingBottom = "0";
631
+ preview.style.paddingLeft = "0";
632
+ };
633
+ var previewForDefinition = (definition, props) => definition.editorPreview?.(props) || `<div class="orion-builder-v2-dynamic-placeholder"><strong>${definition.label}</strong></div>`;
634
+ var blockPreviewForDefinition = (type) => {
635
+ const normalizedType = type.replace(/[^a-z0-9-]/gi, "-");
636
+ return `
637
+ <div class="orion-builder-v2-block-preview is-project is-${normalizedType}" aria-hidden="true">
638
+ <span class="wire wire-kicker"></span>
639
+ <span class="wire wire-title"></span>
640
+ <span class="wire wire-copy"></span>
641
+ <span class="wire wire-image"></span>
642
+ <span class="wire wire-card"></span>
643
+ <span class="wire wire-card"></span>
644
+ <span class="wire wire-card"></span>
645
+ </div>
646
+ `;
647
+ };
648
+ var lockPreviewChildren = (component) => {
649
+ const children = typeof component.components === "function" ? component.components() : null;
650
+ const childList = children && typeof children === "object" && "forEach" in children ? children : null;
651
+ childList?.forEach((child) => {
652
+ if (typeof child.set === "function") {
653
+ child.set({
654
+ copyable: false,
655
+ draggable: false,
656
+ droppable: false,
657
+ editable: false,
658
+ highlightable: false,
659
+ hoverable: false,
660
+ removable: false,
661
+ selectable: false
662
+ });
663
+ }
664
+ lockPreviewChildren(child);
665
+ });
666
+ };
667
+ var parseJsonArray = (value) => {
668
+ const decoded = decodeHtmlAttribute2(value);
669
+ if (!decoded) {
670
+ return [];
671
+ }
672
+ try {
673
+ const parsed = JSON.parse(decoded);
674
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
675
+ } catch {
676
+ return [];
677
+ }
678
+ };
679
+ var updateJsonListAttribute = ({
680
+ field,
681
+ index,
682
+ listAttr,
683
+ model,
684
+ value
685
+ }) => {
686
+ const attrs = model.getAttributes?.() || {};
687
+ const list = parseJsonArray(attrs[listAttr]);
688
+ if (!list[index]) {
689
+ return;
690
+ }
691
+ list[index] = {
692
+ ...list[index],
693
+ [field]: value
694
+ };
695
+ model.addAttributes?.({
696
+ [listAttr]: JSON.stringify(list)
697
+ });
698
+ };
699
+ var snapshotModel = (model, emptyListAttr) => {
700
+ const attributes = { ...model.getAttributes?.() || {} };
701
+ if (emptyListAttr && !attributes[emptyListAttr]) {
702
+ attributes[emptyListAttr] = "[]";
703
+ }
704
+ if (emptyListAttr === "data-orion-items-json") {
705
+ const block = parseJsonRecord(attributes["data-orion-block"]);
706
+ if (Object.keys(block).length > 0) {
707
+ attributes["data-orion-block"] = serializeJsonRecord({
708
+ ...block,
709
+ items: parseJsonArray(attributes[emptyListAttr])
710
+ });
711
+ }
712
+ }
713
+ return {
714
+ attributes,
715
+ style: { ...model.getStyle?.() || {} }
716
+ };
717
+ };
718
+ var pushModelHistory = ({
719
+ after,
720
+ before,
721
+ editor,
722
+ model
723
+ }) => {
724
+ editor?.trigger?.("orion:component-history", {
725
+ after,
726
+ before,
727
+ component: model
728
+ });
729
+ };
730
+ var addJsonListItem = ({
731
+ listAttr,
732
+ model,
733
+ template
734
+ }) => {
735
+ const attrs = model.getAttributes?.() || {};
736
+ const list = parseJsonArray(attrs[listAttr]);
737
+ list.push(template);
738
+ model.addAttributes?.({
739
+ [listAttr]: JSON.stringify(list)
740
+ });
741
+ };
742
+ var duplicateJsonListItem = ({
743
+ index,
744
+ listAttr,
745
+ model
746
+ }) => {
747
+ const attrs = model.getAttributes?.() || {};
748
+ const list = parseJsonArray(attrs[listAttr]);
749
+ const item = list[index];
750
+ if (!item) {
751
+ return;
752
+ }
753
+ list.splice(index + 1, 0, {
754
+ ...item,
755
+ title: typeof item.title === "string" ? `${item.title} copy` : item.title
756
+ });
757
+ model.addAttributes?.({
758
+ [listAttr]: JSON.stringify(list)
759
+ });
760
+ };
761
+ var removeJsonListItem = ({
762
+ index,
763
+ listAttr,
764
+ model
765
+ }) => {
766
+ const attrs = model.getAttributes?.() || {};
767
+ const list = parseJsonArray(attrs[listAttr]);
768
+ if (!list[index]) {
769
+ return;
770
+ }
771
+ list.splice(index, 1);
772
+ model.addAttributes?.({
773
+ [listAttr]: JSON.stringify(list)
774
+ });
775
+ };
776
+ var parseTemplateAttribute = (value) => {
777
+ const decoded = decodeHtmlAttribute2(value);
778
+ if (!decoded) {
779
+ return {
780
+ description: "Describe this item.",
781
+ imageURL: "",
782
+ title: "New item"
783
+ };
784
+ }
785
+ try {
786
+ const parsed = JSON.parse(decoded);
787
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
788
+ description: "Describe this item.",
789
+ imageURL: "",
790
+ title: "New item"
791
+ };
792
+ } catch {
793
+ return {
794
+ description: "Describe this item.",
795
+ imageURL: "",
796
+ title: "New item"
797
+ };
798
+ }
799
+ };
800
+ var chooseAsset = (editor, currentSrc, callback) => {
801
+ const assetManager = editor?.AssetManager;
802
+ if (!assetManager?.open) {
803
+ const src = window.prompt("Image URL");
804
+ if (src) {
805
+ callback(src);
806
+ }
807
+ return;
808
+ }
809
+ if (currentSrc) {
810
+ assetManager.add?.({
811
+ name: `Current image - ${currentSrc.split("/").pop() || currentSrc}`,
812
+ src: currentSrc,
813
+ type: "image"
814
+ });
815
+ }
816
+ assetManager.open({
817
+ select(asset) {
818
+ const src = typeof asset === "string" ? asset : asset && typeof asset === "object" && "get" in asset && typeof asset.get === "function" ? String(asset.get("src") || "") : asset && typeof asset === "object" && "src" in asset ? String(asset.src || "") : "";
819
+ if (src) {
820
+ callback(src);
821
+ }
822
+ assetManager.close?.();
823
+ }
824
+ });
825
+ };
826
+ var bindEditablePreview = (view, editor) => {
827
+ const root = view.el;
828
+ if (!root) {
829
+ return;
830
+ }
831
+ root.querySelectorAll("[data-orion-action]").forEach((element) => {
832
+ const action = element.dataset.orionAction || "";
833
+ const listName = element.dataset.orionEditList || "";
834
+ const listIndex = Number(element.dataset.orionEditIndex);
835
+ const listAttr = listName ? propToAttrName(listName) : "";
836
+ element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
837
+ const runAction = (event) => {
838
+ event.preventDefault();
839
+ event.stopPropagation();
840
+ if (typeof event.stopImmediatePropagation === "function") {
841
+ event.stopImmediatePropagation();
842
+ }
843
+ editor.select?.(view.model);
844
+ if (!listAttr) {
845
+ return;
846
+ }
847
+ const before = snapshotModel(view.model, listAttr);
848
+ const runListMutation = (callback) => {
849
+ withoutUndoTracking(editor, callback);
850
+ const after = snapshotModel(view.model, listAttr);
851
+ if (JSON.stringify(before) !== JSON.stringify(after)) {
852
+ pushModelHistory({
853
+ after,
854
+ before,
855
+ editor,
856
+ model: view.model
857
+ });
858
+ }
859
+ };
860
+ if (action === "add-list-item") {
861
+ runListMutation(() => {
862
+ addJsonListItem({
863
+ listAttr,
864
+ model: view.model,
865
+ template: parseTemplateAttribute(element.dataset.orionItemTemplate)
866
+ });
867
+ });
868
+ return;
869
+ }
870
+ if (!Number.isInteger(listIndex)) {
871
+ return;
872
+ }
873
+ if (action === "duplicate-list-item") {
874
+ runListMutation(() => {
875
+ duplicateJsonListItem({
876
+ index: listIndex,
877
+ listAttr,
878
+ model: view.model
879
+ });
880
+ });
881
+ return;
882
+ }
883
+ if (action === "remove-list-item") {
884
+ runListMutation(() => {
885
+ removeJsonListItem({
886
+ index: listIndex,
887
+ listAttr,
888
+ model: view.model
889
+ });
890
+ });
891
+ }
892
+ };
893
+ element.addEventListener("pointerdown", runAction, true);
894
+ element.addEventListener("keydown", (event) => {
895
+ if (event.key === "Enter" || event.key === " ") {
896
+ runAction(event);
897
+ }
898
+ });
899
+ });
900
+ root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
901
+ const field = element.dataset.orionEditField || "";
902
+ const listName = element.dataset.orionEditList || "";
903
+ const listIndex = Number(element.dataset.orionEditIndex);
904
+ const attrName = listName ? propToAttrName(listName) : propToAttrName(field);
905
+ const isImage = element.dataset.orionEditKind === "image" || element instanceof HTMLImageElement;
906
+ const placeholder = element.dataset.orionPlaceholder || "";
907
+ element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
908
+ element.style.cursor = "text";
909
+ element.addEventListener("pointerdown", (event) => {
910
+ event.stopPropagation();
911
+ if (typeof event.stopImmediatePropagation === "function") {
912
+ event.stopImmediatePropagation();
913
+ }
914
+ if (!isImage) {
915
+ markActiveEditableField(view.model, field, element);
916
+ }
917
+ editor.select?.(view.model);
918
+ }, true);
919
+ element.addEventListener("click", (event) => {
920
+ event.stopPropagation();
921
+ if (!isImage) {
922
+ markActiveEditableField(view.model, field, element);
923
+ }
924
+ editor.select?.(view.model);
925
+ if (!isImage) {
926
+ return;
927
+ }
928
+ element.style.cursor = "pointer";
929
+ const currentSrc = element instanceof HTMLImageElement ? element.currentSrc || element.src || "" : element.style.backgroundImage.replace(/^url\(["']?/, "").replace(/["']?\)$/, "");
930
+ chooseAsset(editor, currentSrc, (src) => {
931
+ if (listName && Number.isInteger(listIndex)) {
932
+ updateJsonListAttribute({
933
+ field,
934
+ index: listIndex,
935
+ listAttr: attrName,
936
+ model: view.model,
937
+ value: src
938
+ });
939
+ return;
940
+ }
941
+ view.model.addAttributes?.({
942
+ [attrName]: src
943
+ });
944
+ });
945
+ });
946
+ if (isImage) {
947
+ return;
948
+ }
949
+ element.setAttribute("contenteditable", "true");
950
+ element.setAttribute("spellcheck", "true");
951
+ const commit = () => {
952
+ const typedValue = element.innerText.trim();
953
+ const value = placeholder && typedValue === placeholder ? "" : typedValue;
954
+ if (listName && Number.isInteger(listIndex)) {
955
+ updateJsonListAttribute({
956
+ field,
957
+ index: listIndex,
958
+ listAttr: attrName,
959
+ model: view.model,
960
+ value
961
+ });
962
+ return;
963
+ }
964
+ view.model.addAttributes?.({
965
+ [attrName]: value
966
+ });
967
+ };
968
+ element.addEventListener("blur", commit);
969
+ element.addEventListener("keydown", (event) => {
970
+ if (event.key === "Enter" && !event.shiftKey) {
971
+ event.preventDefault();
972
+ element.blur();
973
+ }
974
+ });
975
+ });
976
+ };
977
+ var registerProjectDynamicComponents = (editor, adapter) => {
978
+ const components = adapter?.components || {};
979
+ Object.keys(components).forEach((type) => {
980
+ const definition = normalizeDefinition(type, components[type]);
981
+ if (!definition) {
982
+ return;
983
+ }
984
+ editor.DomComponents.addType(`orion-${type}`, {
985
+ model: {
986
+ defaults: {
987
+ attributes: {
988
+ "data-orion-component": type,
989
+ "data-orion-id": `${type}-${Date.now()}`,
990
+ ...definition.designControls ? { "data-orion-design-controls": JSON.stringify(definition.designControls) } : {}
991
+ },
992
+ components: "",
993
+ droppable: false,
994
+ highlightable: true,
995
+ hoverable: true,
996
+ selectable: true,
997
+ tagName: "div",
998
+ traits: [
999
+ ...(definition.traits || []).map((trait) => ({
1000
+ label: trait.label,
1001
+ name: `data-orion-${trait.name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`,
1002
+ options: trait.options,
1003
+ placeholder: trait.placeholder,
1004
+ type: trait.type
1005
+ }))
1006
+ ]
1007
+ }
1008
+ },
1009
+ view: {
1010
+ init() {
1011
+ this.listenTo(this.model, "change:attributes", this.renderPreview);
1012
+ this.listenTo(this.model, "change:style", () => syncModelBackgroundToBlock(this.model));
1013
+ this.renderPreview();
1014
+ },
1015
+ renderPreview() {
1016
+ const attributes = this.model.getAttributes?.() || {};
1017
+ const props = propsFromAttributes(attributes);
1018
+ withoutUndoTracking(editor, () => {
1019
+ const backgroundColor = defaultBackgroundForComponent(type, props);
1020
+ if (backgroundColor && !normalizeCssColor(this.model.getStyle?.()?.["background-color"])) {
1021
+ this.model.addStyle?.({ "background-color": backgroundColor });
1022
+ this.model.addAttributes?.({ "data-orion-background-color": backgroundColor });
1023
+ }
1024
+ this.model.components(previewForDefinition(definition, props));
1025
+ hoistPreviewSpacingToModel(this);
1026
+ lockPreviewChildren(this.model);
1027
+ });
1028
+ queueMicrotask(() => bindEditablePreview(this, editor));
1029
+ }
1030
+ }
1031
+ });
1032
+ editor.Blocks.add(`orion-dynamic-${type}`, {
1033
+ category: "Project",
1034
+ content: {
1035
+ type: `orion-${type}`
1036
+ },
1037
+ label: definition.label,
1038
+ media: blockPreviewForDefinition(type)
1039
+ });
1040
+ });
1041
+ };
1042
+
1043
+ // src/builder-v2/editor/GrapesPageEditor.tsx
1044
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1045
+ var quickLayoutOptions = [
1046
+ { description: "Normal page section flow.", label: "Natural", value: "natural" },
1047
+ { description: "Stack children vertically.", label: "Stacked", value: "stacked" },
1048
+ { description: "Place children in one flexible row.", label: "Row", value: "row" },
1049
+ { description: "Split content into two columns.", label: "Two column", value: "two-column" },
1050
+ { description: "Use a three-column card/grid layout.", label: "Three column", value: "three-column" },
1051
+ { description: "Center content in the section.", label: "Centered", value: "centered" },
1052
+ { description: "Prepare the section for layered content.", label: "Overlay", value: "overlay" }
1053
+ ];
1054
+ var defaultQuickDesignControls = {
1055
+ alignment: true,
1056
+ layout: quickLayoutOptions.map((option) => option.value),
1057
+ spacing: true
1058
+ };
1059
+ var quickDesignControlsByComponentType = {
1060
+ xoCta: {
1061
+ alignment: true,
1062
+ layout: ["centered", "natural"],
1063
+ spacing: true
1064
+ },
1065
+ xoFaq: {
1066
+ alignment: true,
1067
+ layout: ["natural", "stacked"],
1068
+ spacing: true
1069
+ },
1070
+ xoFeatureGrid: {
1071
+ alignment: true,
1072
+ layout: ["natural", "two-column", "three-column"],
1073
+ spacing: true
1074
+ },
1075
+ xoForm: {
1076
+ alignment: true,
1077
+ layout: ["natural", "centered"],
1078
+ spacing: true
1079
+ },
1080
+ xoHero: {
1081
+ alignment: true,
1082
+ layout: ["natural", "centered", "overlay"],
1083
+ spacing: true
1084
+ },
1085
+ xoMarquee: {
1086
+ alignment: false,
1087
+ layout: [],
1088
+ spacing: true
1089
+ },
1090
+ xoRichText: {
1091
+ alignment: true,
1092
+ layout: ["natural", "centered"],
1093
+ spacing: true
1094
+ },
1095
+ xoSection: defaultQuickDesignControls
1096
+ };
1097
+ var quickSpacingOptions = [
1098
+ { label: "Compact", value: "compact" },
1099
+ { label: "Medium", value: "medium" },
1100
+ { label: "Spacious", value: "spacious" }
1101
+ ];
1102
+ var horizontalAlignOptions = [
1103
+ { label: "Left", value: "left" },
1104
+ { label: "Center", value: "center" },
1105
+ { label: "Right", value: "right" }
1106
+ ];
1107
+ var verticalAlignOptions = [
1108
+ { label: "Top", value: "top" },
1109
+ { label: "Middle", value: "middle" },
1110
+ { label: "Bottom", value: "bottom" }
1111
+ ];
1112
+ var builderSettingHelp = {
1113
+ "align items": "Controls how flex children align across the section, such as top, center, bottom, or stretch.",
1114
+ background: "Sets the full background style for the selected element. Use carefully when combining images, colors, or gradients.",
1115
+ "background color": "Sets the background color behind the selected element content.",
1116
+ "background image url": "The image shown behind this section. Use a media URL from the site media library or uploaded asset.",
1117
+ border: "Controls the selected element border width, style, and color.",
1118
+ "border radius": "Rounds the corners of the selected element.",
1119
+ bottom: "Offsets positioned elements from the bottom. This only applies when Position is relative, absolute, or fixed.",
1120
+ "box shadow": "Adds depth behind the selected element.",
1121
+ color: "Sets the text color for the selected element.",
1122
+ description: "Supporting body copy for this section.",
1123
+ display: "Controls how the element participates in layout. Flex unlocks child alignment and gap controls; none hides the element.",
1124
+ eyebrow: "Small label text shown above the main title or headline.",
1125
+ "flex direction": "Controls whether flex children flow in a row or column. Only applies when Display is flex.",
1126
+ "flex wrap": "Controls whether flex children can wrap onto another row. Only applies when Display is flex.",
1127
+ "font family": "Controls the typeface used by the selected text.",
1128
+ "font size": "Controls how large the selected text appears.",
1129
+ "font weight": "Controls the thickness of the selected text.",
1130
+ "form type": "Chooses which site form this section should render.",
1131
+ gap: "Adds space between child elements. This is most useful when Display is flex or grid.",
1132
+ headline: "Main heading text for this section.",
1133
+ height: "Sets an exact element height. Use sparingly because fixed heights can clip content on smaller screens.",
1134
+ kicker: "Small label text shown above the main hero headline.",
1135
+ left: "Offsets positioned elements from the left. This only applies when Position is relative, absolute, or fixed.",
1136
+ "letter spacing": "Adjusts the space between letters.",
1137
+ "line height": "Controls vertical spacing between lines of text.",
1138
+ margin: "Adds space outside the selected element.",
1139
+ "min height": "Sets the minimum height while still allowing content to grow. Usually better than fixed Height for sections.",
1140
+ opacity: "Controls transparency. Lower values make the selected element more see-through.",
1141
+ padding: "Adds space inside the selected element between its border and content.",
1142
+ position: "Controls whether the element stays in normal flow or can be offset/pinned.",
1143
+ "primary label": "Text for the primary call-to-action button.",
1144
+ "primary link": "Destination URL for the primary call-to-action button.",
1145
+ right: "Offsets positioned elements from the right. This only applies when Position is relative, absolute, or fixed.",
1146
+ "secondary label": "Text for the secondary call-to-action button.",
1147
+ "secondary link": "Destination URL for the secondary call-to-action button.",
1148
+ style: "Selects the visual variant for this component.",
1149
+ subheadline: "Supporting text below the main hero headline.",
1150
+ subtitle: "Supporting text below the section title.",
1151
+ text: "Editable rich text or body content for this section.",
1152
+ "text align": "Controls horizontal text alignment.",
1153
+ title: "Main title text for this section.",
1154
+ top: "Offsets positioned elements from the top. This only applies when Position is relative, absolute, or fixed.",
1155
+ variant: "Selects the layout or visual variant for this component.",
1156
+ width: "Sets the element width. Applies to block, inline-block, flex, grid, absolute, and fixed elements."
1157
+ };
1158
+ var normalizeHelpKey = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
1159
+ var readDetailedLayoutControlState = (component) => {
1160
+ const style = component?.getStyle?.() || {};
1161
+ return {
1162
+ display: getStyleString(style, "display") || "block",
1163
+ position: getStyleString(style, "position") || "static"
1164
+ };
1165
+ };
1166
+ var decorateBuilderSettingHelp = (root = document) => {
1167
+ const elements = root.querySelectorAll(
1168
+ [
1169
+ ".orion-builder-v2-choice",
1170
+ ".orion-builder-v2-segmented button",
1171
+ ".orion-builder-v2-toggle",
1172
+ ".gjs-sm-label",
1173
+ ".gjs-label",
1174
+ ".gjs-trt-trait__label",
1175
+ ".gjs-sm-radio-item-label",
1176
+ ".gjs-sm-sector-title"
1177
+ ].join(",")
1178
+ );
1179
+ elements.forEach((element) => {
1180
+ const text = normalizeHelpKey(element.textContent || element.getAttribute("aria-label") || "");
1181
+ const help = builderSettingHelp[text];
1182
+ if (!help) {
1183
+ return;
1184
+ }
1185
+ element.setAttribute("aria-description", help);
1186
+ element.dataset.builderSettingHelp = help;
1187
+ if (!element.querySelector(":scope > .orion-builder-v2-help-trigger")) {
1188
+ const trigger = document.createElement("span");
1189
+ trigger.setAttribute("aria-label", `Show help: ${element.textContent?.trim() || "setting"}`);
1190
+ trigger.className = "orion-builder-v2-help-trigger";
1191
+ trigger.dataset.builderHelpText = help;
1192
+ trigger.role = "button";
1193
+ trigger.tabIndex = 0;
1194
+ trigger.textContent = "?";
1195
+ element.appendChild(trigger);
1196
+ }
1197
+ });
1198
+ };
1199
+ var findColorInputForControl = (element) => {
1200
+ const field = element.closest(".gjs-field-color") || element.closest(".gjs-sm-property") || element.closest("label");
1201
+ return field?.querySelector(
1202
+ 'input:not([type="hidden"]):not([disabled]), input[type="color"]:not([disabled])'
1203
+ ) || null;
1204
+ };
1205
+ var applyColorValueToInput = (input, color) => {
1206
+ input.value = color;
1207
+ input.dispatchEvent(new Event("input", { bubbles: true }));
1208
+ input.dispatchEvent(new Event("change", { bubbles: true }));
1209
+ };
1210
+ var openScreenColorPickerForInput = async (input) => {
1211
+ const EyeDropper = window.EyeDropper;
1212
+ if (!EyeDropper) {
1213
+ return false;
1214
+ }
1215
+ const result = await new EyeDropper().open();
1216
+ applyColorValueToInput(input, result.sRGBHex);
1217
+ return true;
1218
+ };
1219
+ var decorateBuilderColorEyeDroppers = (root = document) => {
1220
+ const supportsEyeDropper = Boolean(window.EyeDropper);
1221
+ const colorFields = root.querySelectorAll(".gjs-field-color");
1222
+ colorFields.forEach((field) => {
1223
+ const input = findColorInputForControl(field);
1224
+ const swatch = field.querySelector(".gjs-field-colorp");
1225
+ if (!supportsEyeDropper || !input || !swatch || field.querySelector(":scope > .orion-builder-v2-eyedropper")) {
1226
+ return;
1227
+ }
1228
+ field.classList.add("has-orion-eyedropper");
1229
+ const button = document.createElement("button");
1230
+ button.type = "button";
1231
+ button.className = "orion-builder-v2-eyedropper";
1232
+ button.dataset.orionColorEyedropper = "true";
1233
+ button.setAttribute("aria-label", "Pick color from screen");
1234
+ button.setAttribute("title", "Pick color from screen");
1235
+ button.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14.7 4.3a2.4 2.4 0 0 1 3.4 0l1.6 1.6a2.4 2.4 0 0 1 0 3.4l-1.2 1.2 1 1a1 1 0 1 1-1.4 1.4l-1-1-8.9 8.9H4v-4.2l8.9-8.9-1-1a1 1 0 0 1 1.4-1.4l1 1 1.4-1.4Zm-.4 4.8-8.3 8.3V19h1.6l8.3-8.3-1.6-1.6Z" fill="currentColor"/></svg>';
1236
+ swatch.insertAdjacentElement("afterend", button);
1237
+ });
1238
+ };
1239
+ var decorateBuilderNumericSteppers = (root = document) => {
1240
+ const panel = document.getElementById("orion-builder-v2-styles");
1241
+ if (!panel || !("querySelector" in root)) {
1242
+ return;
1243
+ }
1244
+ stepperProperties.forEach((property) => {
1245
+ const control = panel.querySelector(`.gjs-sm-property__${CSS.escape(property)}`);
1246
+ const field = control?.querySelector(".gjs-field");
1247
+ const input = field?.querySelector("input");
1248
+ if (!field || !input || field.querySelector(".orion-builder-v2-stepper")) {
1249
+ return;
1250
+ }
1251
+ field.classList.add("has-orion-builder-stepper");
1252
+ const stepper = document.createElement("span");
1253
+ stepper.className = "orion-builder-v2-stepper";
1254
+ const updateValue = (direction) => {
1255
+ const current = Number.parseFloat(input.value || "0");
1256
+ const next = Number.isFinite(current) ? current + direction : direction;
1257
+ input.value = Number.isInteger(next) ? String(next) : String(Number(next.toFixed(2)));
1258
+ input.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText" }));
1259
+ input.dispatchEvent(new Event("change", { bubbles: true }));
1260
+ };
1261
+ [
1262
+ { className: "is-up", direction: 1, label: "Increase", text: "\u25B2" },
1263
+ { className: "is-down", direction: -1, label: "Decrease", text: "\u25BC" }
1264
+ ].forEach((step) => {
1265
+ const button = document.createElement("button");
1266
+ button.type = "button";
1267
+ button.className = `orion-builder-v2-stepper-button ${step.className}`;
1268
+ button.setAttribute("aria-label", `${step.label} ${property.replace(/-/g, " ")}`);
1269
+ button.textContent = step.text;
1270
+ button.addEventListener("click", (event) => {
1271
+ event.preventDefault();
1272
+ event.stopPropagation();
1273
+ updateValue(step.direction);
1274
+ input.focus();
1275
+ });
1276
+ stepper.appendChild(button);
1277
+ });
1278
+ field.appendChild(stepper);
1279
+ });
1280
+ };
1281
+ var decorateBuilderControls = (root = document) => {
1282
+ decorateBuilderSettingHelp(root);
1283
+ decorateBuilderColorEyeDroppers(root);
1284
+ decorateBuilderNumericSteppers(root);
1285
+ };
1286
+ var closeBuilderHelpPopovers = (except) => {
1287
+ document.querySelectorAll(".is-builder-help-open").forEach((element) => {
1288
+ if (element !== except) {
1289
+ element.classList.remove("is-builder-help-open");
1290
+ element.classList.remove("is-builder-help-left", "is-builder-help-center", "is-builder-help-right");
1291
+ }
1292
+ });
1293
+ };
1294
+ var openBuilderHelpPopover = (trigger, wrapper, isOpen) => {
1295
+ if (!wrapper) {
1296
+ return;
1297
+ }
1298
+ wrapper.classList.remove("is-builder-help-left", "is-builder-help-center", "is-builder-help-right");
1299
+ if (!isOpen) {
1300
+ wrapper.classList.remove("is-builder-help-open");
1301
+ return;
1302
+ }
1303
+ const triggerRect = trigger.getBoundingClientRect();
1304
+ const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1305
+ const tooltipHalfWidth = 140;
1306
+ const viewportGutter = 18;
1307
+ const triggerCenter = triggerRect.left + triggerRect.width / 2;
1308
+ if (triggerCenter - tooltipHalfWidth < viewportGutter) {
1309
+ wrapper.classList.add("is-builder-help-left");
1310
+ } else if (triggerCenter + tooltipHalfWidth > viewportWidth - viewportGutter) {
1311
+ wrapper.classList.add("is-builder-help-right");
1312
+ } else {
1313
+ wrapper.classList.add("is-builder-help-center");
1314
+ }
1315
+ wrapper.classList.add("is-builder-help-open");
1316
+ };
1317
+ var positionColorPickerFromAnchor = (anchor) => {
1318
+ if (!anchor) {
1319
+ return;
1320
+ }
1321
+ window.requestAnimationFrame(() => {
1322
+ const picker = document.querySelector(".sp-container.gjs-editor-sp:not(.sp-hidden)");
1323
+ if (!picker) {
1324
+ return;
1325
+ }
1326
+ const anchorRect = anchor.getBoundingClientRect();
1327
+ const pickerRect = picker.getBoundingClientRect();
1328
+ const viewportPadding = 12;
1329
+ const left = Math.min(
1330
+ Math.max(viewportPadding, anchorRect.right - pickerRect.width),
1331
+ Math.max(viewportPadding, window.innerWidth - pickerRect.width - viewportPadding)
1332
+ );
1333
+ const top = Math.min(anchorRect.bottom + 8, Math.max(viewportPadding, window.innerHeight - pickerRect.height - viewportPadding));
1334
+ picker.style.left = `${left}px`;
1335
+ picker.style.right = "auto";
1336
+ picker.style.top = `${top}px`;
1337
+ });
1338
+ };
1339
+ var detailedLayoutClassNames = ({ display, position }) => {
1340
+ const normalizedDisplay = normalizeHelpKey(display || "block");
1341
+ const normalizedPosition = normalizeHelpKey(position || "static");
1342
+ const supportsGap = normalizedDisplay === "flex" || normalizedDisplay === "grid";
1343
+ const supportsFlexControls = normalizedDisplay === "flex";
1344
+ const supportsSize = normalizedDisplay === "block" || normalizedDisplay === "inline-block" || normalizedDisplay === "flex" || normalizedDisplay === "grid" || normalizedPosition === "absolute" || normalizedPosition === "fixed";
1345
+ const supportsOffsets = normalizedPosition === "relative" || normalizedPosition === "absolute" || normalizedPosition === "fixed";
1346
+ return [
1347
+ supportsGap ? "can-use-gap" : "cannot-use-gap",
1348
+ supportsFlexControls ? "can-use-flex-controls" : "cannot-use-flex-controls",
1349
+ supportsOffsets ? "can-use-position-offsets" : "cannot-use-position-offsets",
1350
+ supportsSize ? "can-use-sizing-controls" : "cannot-use-sizing-controls"
1351
+ ].join(" ");
1352
+ };
1353
+ var HelpTrigger = ({ help, label }) => /* @__PURE__ */ jsx(
1354
+ "span",
1355
+ {
1356
+ "aria-label": `Show help: ${label}`,
1357
+ className: "orion-builder-v2-help-trigger",
1358
+ "data-builder-help-text": help,
1359
+ role: "button",
1360
+ tabIndex: 0,
1361
+ children: "?"
1362
+ }
1363
+ );
1364
+ var layoutStyleReset = {
1365
+ "align-items": "",
1366
+ "display": "",
1367
+ "flex-direction": "",
1368
+ "flex-wrap": "",
1369
+ "gap": "",
1370
+ "grid-template-columns": "",
1371
+ "justify-content": "",
1372
+ "margin-left": "",
1373
+ "margin-right": "",
1374
+ "min-height": "",
1375
+ "place-items": "",
1376
+ "position": "",
1377
+ "text-align": ""
1378
+ };
1379
+ var quickLayoutStyles = {
1380
+ centered: {
1381
+ ...layoutStyleReset,
1382
+ display: "block",
1383
+ "margin-left": "auto",
1384
+ "margin-right": "auto",
1385
+ "text-align": "center"
1386
+ },
1387
+ natural: layoutStyleReset,
1388
+ overlay: {
1389
+ ...layoutStyleReset,
1390
+ display: "grid",
1391
+ "min-height": "420px",
1392
+ "place-items": "center",
1393
+ position: "relative",
1394
+ "text-align": "center"
1395
+ },
1396
+ row: {
1397
+ ...layoutStyleReset,
1398
+ display: "flex",
1399
+ "flex-direction": "row",
1400
+ "flex-wrap": "wrap",
1401
+ gap: "24px"
1402
+ },
1403
+ stacked: {
1404
+ ...layoutStyleReset,
1405
+ display: "block"
1406
+ },
1407
+ "three-column": {
1408
+ ...layoutStyleReset,
1409
+ display: "grid",
1410
+ gap: "24px",
1411
+ "grid-template-columns": "repeat(3, minmax(0, 1fr))"
1412
+ },
1413
+ "two-column": {
1414
+ ...layoutStyleReset,
1415
+ display: "grid",
1416
+ gap: "24px",
1417
+ "grid-template-columns": "repeat(2, minmax(0, 1fr))"
1418
+ }
1419
+ };
1420
+ var quickSpacingStyles = {
1421
+ compact: "32px 24px",
1422
+ medium: "64px 24px",
1423
+ spacious: "96px 24px"
1424
+ };
1425
+ var quickHorizontalAlignStyles = {
1426
+ center: { "justify-content": "center", "text-align": "center" },
1427
+ left: { "justify-content": "flex-start", "text-align": "left" },
1428
+ right: { "justify-content": "flex-end", "text-align": "right" }
1429
+ };
1430
+ var quickVerticalAlignStyles = {
1431
+ bottom: "flex-end",
1432
+ middle: "center",
1433
+ top: "flex-start"
1434
+ };
1435
+ var canvasSelectionCss = `
1436
+ .gjs-selected,
1437
+ [data-gjs-highlightable].gjs-selected {
1438
+ outline-color: #c7643d !important;
1439
+ outline-width: 2px !important;
1440
+ }
1441
+
1442
+ .gjs-hovered,
1443
+ [data-gjs-highlightable].gjs-hovered {
1444
+ outline-color: #c7643d !important;
1445
+ }
1446
+
1447
+ .orion-builder-v2-selected-block {
1448
+ outline: 2px solid #c7643d !important;
1449
+ outline-offset: -2px !important;
1450
+ position: relative;
1451
+ }
1452
+
1453
+ .xo-builder-preview-grid {
1454
+ display: grid;
1455
+ gap: 22px;
1456
+ grid-template-columns: 1fr;
1457
+ }
1458
+
1459
+ .xo-builder-preview-grid.is-catalog,
1460
+ .xo-builder-preview-grid.is-contact,
1461
+ .xo-builder-preview-grid.is-stacked-image-cards {
1462
+ grid-template-columns: 1fr;
1463
+ }
1464
+
1465
+ @media (min-width: 768px) {
1466
+ .xo-builder-preview-grid,
1467
+ .xo-builder-preview-grid.is-image-tiles,
1468
+ .xo-builder-preview-grid.is-stacked-image-cards {
1469
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1470
+ }
1471
+
1472
+ .xo-builder-preview-grid.is-catalog {
1473
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1474
+ }
1475
+
1476
+ .xo-builder-preview-grid.is-contact {
1477
+ grid-template-columns: 1fr;
1478
+ }
1479
+ }
1480
+
1481
+ @media (min-width: 1280px) {
1482
+ .xo-builder-preview-grid.is-catalog {
1483
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1484
+ }
1485
+ }
1486
+ `;
1487
+ var isQuickHorizontalAlign = (value) => value === "center" || value === "left" || value === "right";
1488
+ var isQuickVerticalAlign = (value) => value === "bottom" || value === "middle" || value === "top";
1489
+ var getStyleString = (style, property) => {
1490
+ const value = style[property];
1491
+ return typeof value === "string" ? value : "";
1492
+ };
1493
+ var parseOrionBlockAttribute = (value) => {
1494
+ const decoded = decodeHtmlAttribute3(value);
1495
+ if (!decoded) {
1496
+ return {};
1497
+ }
1498
+ try {
1499
+ const parsed = JSON.parse(decoded);
1500
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1501
+ } catch {
1502
+ return {};
1503
+ }
1504
+ };
1505
+ var serializeOrionBlockAttribute = (block) => JSON.stringify(block);
1506
+ var readNestedRecord = (value, key) => {
1507
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1508
+ return {};
1509
+ }
1510
+ const nested = value[key];
1511
+ return nested && typeof nested === "object" && !Array.isArray(nested) ? nested : {};
1512
+ };
1513
+ var firstString = (...values) => {
1514
+ for (const value of values) {
1515
+ if (typeof value === "string" && value.trim().length > 0) {
1516
+ return value.trim();
1517
+ }
1518
+ }
1519
+ return "";
1520
+ };
1521
+ var normalizeCssSizeValue = (value) => {
1522
+ if (typeof value !== "string") {
1523
+ return "";
1524
+ }
1525
+ const trimmed = value.trim();
1526
+ return /^-?\d*\.?\d+(px|pt|rem|em|%)?$/.test(trimmed) || trimmed === "normal" ? trimmed : "";
1527
+ };
1528
+ var normalizeCssColorValue = (value) => {
1529
+ if (typeof value !== "string") {
1530
+ return "";
1531
+ }
1532
+ const trimmed = value.trim();
1533
+ return trimmed && trimmed !== "none" ? trimmed : "";
1534
+ };
1535
+ var getStyleColorValue = (style, property) => normalizeCssColorValue(
1536
+ style[property] || style[property.replace(/-([a-z])/g, (_, char) => char.toUpperCase())]
1537
+ );
1538
+ var normalizeFontFamilyValue = (value) => {
1539
+ if (typeof value !== "string" || !value.trim()) {
1540
+ return "";
1541
+ }
1542
+ const trimmed = value.trim();
1543
+ const normalized = trimmed.replace(/["']/g, "").toLowerCase();
1544
+ if (normalized === "inherit") {
1545
+ return "inherit";
1546
+ }
1547
+ if (normalized.includes("caveat") || normalized.includes("brush script")) {
1548
+ return '"Caveat", "Brush Script MT", cursive';
1549
+ }
1550
+ if (normalized.includes("georgia")) {
1551
+ return 'Georgia, "Times New Roman", serif';
1552
+ }
1553
+ if (normalized.includes("times new roman") && !normalized.includes("georgia")) {
1554
+ return '"Times New Roman", Times, serif';
1555
+ }
1556
+ if (normalized.includes("arial") || normalized.includes("helvetica")) {
1557
+ return "Arial, Helvetica, sans-serif";
1558
+ }
1559
+ if (normalized.includes("inter") || normalized.includes("segoe ui") || normalized.includes("system-ui")) {
1560
+ return 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
1561
+ }
1562
+ return trimmed;
1563
+ };
1564
+ var normalizeFontWeightValue = (value) => {
1565
+ if (typeof value !== "string" && typeof value !== "number") {
1566
+ return "";
1567
+ }
1568
+ const trimmed = String(value).trim();
1569
+ return /^(normal|bold|bolder|lighter|[1-9]00)$/.test(trimmed) ? trimmed : "";
1570
+ };
1571
+ var normalizeStyleTextAlignment = (value) => value === "center" || value === "left" || value === "right" ? value : "";
1572
+ var typographyKey = (kind, property) => `${kind}${property}`;
1573
+ var readStylePanelValue = (property) => {
1574
+ const panel = document.getElementById("orion-builder-v2-styles");
1575
+ const control = panel?.querySelector(`.gjs-sm-property__${CSS.escape(property)}`);
1576
+ return control || null;
1577
+ };
1578
+ var readStylePanelLength = (property) => {
1579
+ const control = readStylePanelValue(property);
1580
+ const value = control?.querySelector("input")?.value.trim() || "";
1581
+ const unit = control?.querySelector("select")?.value.trim() || "";
1582
+ if (!value) {
1583
+ return "";
1584
+ }
1585
+ if (value === "normal") {
1586
+ return value;
1587
+ }
1588
+ return `${value}${unit}`;
1589
+ };
1590
+ var readStylePanelSelect = (property) => (readStylePanelValue(property)?.querySelector("select")?.value || "").trim();
1591
+ var readStylePanelInput = (property) => (readStylePanelValue(property)?.querySelector("input")?.value || "").trim();
1592
+ var readStylePanelColor = (property) => {
1593
+ const control = readStylePanelValue(property);
1594
+ const inputValue = (control?.querySelector("input")?.value || "").trim();
1595
+ if (inputValue && inputValue !== "none") {
1596
+ return inputValue;
1597
+ }
1598
+ const swatchColor = (control?.querySelector(".gjs-field-color-picker")?.style.backgroundColor || "").trim();
1599
+ return swatchColor;
1600
+ };
1601
+ var readStylePanelRadio = (property) => (readStylePanelValue(property)?.querySelector("input:checked")?.value || "").trim();
1602
+ var propertyNameFromStyleControl = (element) => {
1603
+ const property = element?.closest(".gjs-sm-property");
1604
+ const className = Array.from(property?.classList || []).find((name) => name.startsWith("gjs-sm-property__"));
1605
+ return className?.replace(/^gjs-sm-property__/, "") || "";
1606
+ };
1607
+ var writeStylePanelLength = (property, value) => {
1608
+ const control = readStylePanelValue(property);
1609
+ const input = control?.querySelector("input");
1610
+ if (!input) {
1611
+ return;
1612
+ }
1613
+ const match = value.trim().match(/^(-?\d*\.?\d+)([a-z%]*)$/i);
1614
+ if (!match) {
1615
+ return;
1616
+ }
1617
+ const [, amount, unit = "px"] = match;
1618
+ const unitSelect = control?.querySelector("select");
1619
+ input.value = amount;
1620
+ if (unitSelect) {
1621
+ unitSelect.value = unit || "px";
1622
+ }
1623
+ };
1624
+ var spacingProperties = [
1625
+ "margin-top",
1626
+ "margin-bottom",
1627
+ "margin-left",
1628
+ "margin-right",
1629
+ "padding-top",
1630
+ "padding-bottom",
1631
+ "padding-left",
1632
+ "padding-right"
1633
+ ];
1634
+ var spacingTargetForComponent = (component) => {
1635
+ const element = component?.getEl?.();
1636
+ return element || null;
1637
+ };
1638
+ var liveStyleProperties = /* @__PURE__ */ new Set([
1639
+ "align-items",
1640
+ "bottom",
1641
+ "flex-direction",
1642
+ "flex-wrap",
1643
+ "font-size",
1644
+ "font-weight",
1645
+ "gap",
1646
+ "height",
1647
+ "justify-content",
1648
+ "left",
1649
+ "letter-spacing",
1650
+ "line-height",
1651
+ "margin",
1652
+ "margin-bottom",
1653
+ "margin-left",
1654
+ "margin-right",
1655
+ "margin-top",
1656
+ "min-height",
1657
+ "opacity",
1658
+ "padding",
1659
+ "padding-bottom",
1660
+ "padding-left",
1661
+ "padding-right",
1662
+ "padding-top",
1663
+ "right",
1664
+ "top",
1665
+ "width"
1666
+ ]);
1667
+ var stepperProperties = /* @__PURE__ */ new Set([
1668
+ "bottom",
1669
+ "font-size",
1670
+ "gap",
1671
+ "height",
1672
+ "left",
1673
+ "letter-spacing",
1674
+ "line-height",
1675
+ "margin-bottom",
1676
+ "margin-left",
1677
+ "margin-right",
1678
+ "margin-top",
1679
+ "min-height",
1680
+ "padding-bottom",
1681
+ "padding-left",
1682
+ "padding-right",
1683
+ "padding-top",
1684
+ "right",
1685
+ "top",
1686
+ "width"
1687
+ ]);
1688
+ var hasExplicitHorizontalAlign = (block) => {
1689
+ const settings = readNestedRecord(block, "settings");
1690
+ const typography = readNestedRecord(settings, "typography");
1691
+ return Boolean(
1692
+ firstString(
1693
+ block.horizontalAlign,
1694
+ block.textHeadingAlign,
1695
+ block.textBodyAlign,
1696
+ typography.headingAlign,
1697
+ typography.bodyAlign
1698
+ )
1699
+ );
1700
+ };
1701
+ var hasExplicitVerticalAlign = (block) => Boolean(firstString(block.verticalAlign));
1702
+ var inferComponentHorizontalAlign = (attributes, block) => {
1703
+ const componentType = String(attributes["data-orion-component"] || "");
1704
+ const settings = readNestedRecord(block, "settings");
1705
+ const typography = readNestedRecord(settings, "typography");
1706
+ const blockType = firstString(block.blockType, componentType);
1707
+ const variant = firstString(block.variant, attributes["data-orion-variant"]);
1708
+ const inferred = firstString(
1709
+ block.horizontalAlign,
1710
+ block.textHeadingAlign,
1711
+ block.textBodyAlign,
1712
+ attributes["data-orion-horizontal-align"],
1713
+ variant === "quoteBanner" ? "" : typography.headingAlign,
1714
+ variant === "quoteBanner" ? "" : typography.bodyAlign
1715
+ );
1716
+ if (isQuickHorizontalAlign(inferred)) {
1717
+ return inferred;
1718
+ }
1719
+ if (componentType === "xoHero" || componentType === "xoCta" || componentType === "xoRichText" || blockType === "cta" || blockType === "richText" || variant === "centered" || variant === "quoteBanner") {
1720
+ return "center";
1721
+ }
1722
+ return "left";
1723
+ };
1724
+ var inferComponentVerticalAlign = (attributes, block) => {
1725
+ const componentType = String(attributes["data-orion-component"] || "");
1726
+ const blockType = firstString(block.blockType, componentType);
1727
+ const variant = firstString(block.variant, attributes["data-orion-variant"]);
1728
+ const inferred = firstString(block.verticalAlign, attributes["data-orion-vertical-align"]);
1729
+ if (isQuickVerticalAlign(inferred)) {
1730
+ return inferred;
1731
+ }
1732
+ if (componentType === "xoHero" || componentType === "xoCta" || componentType === "xoRichText" || blockType === "cta" || blockType === "richText" || variant === "quoteBanner") {
1733
+ return "middle";
1734
+ }
1735
+ return "top";
1736
+ };
1737
+ var readQuickLayoutState = (component) => {
1738
+ const style = component?.getStyle?.() || {};
1739
+ const attributes = component?.getAttributes?.() || {};
1740
+ const orionBlock = parseOrionBlockAttribute(attributes["data-orion-block"]);
1741
+ const display = getStyleString(style, "display");
1742
+ const gridColumns = getStyleString(style, "grid-template-columns");
1743
+ const flexDirection = getStyleString(style, "flex-direction");
1744
+ const textAlign = getStyleString(style, "text-align");
1745
+ const placeItems = getStyleString(style, "place-items");
1746
+ const padding = getStyleString(style, "padding");
1747
+ const position = getStyleString(style, "position");
1748
+ const justifyContent = getStyleString(style, "justify-content");
1749
+ const alignItems = getStyleString(style, "align-items");
1750
+ const layout = display === "none" ? "natural" : gridColumns.includes("repeat(3") ? "three-column" : gridColumns.includes("repeat(2") ? "two-column" : display === "flex" && flexDirection !== "column" ? "row" : placeItems === "center" || position === "relative" ? "overlay" : textAlign === "center" ? "centered" : display === "block" ? "stacked" : "natural";
1751
+ const spacing = padding.includes("96px") ? "spacious" : padding.includes("32px") ? "compact" : "medium";
1752
+ const storedHorizontalAlign = attributes["data-orion-horizontal-align"];
1753
+ const storedVerticalAlign = attributes["data-orion-vertical-align"];
1754
+ const inferredHorizontalAlign = inferComponentHorizontalAlign(attributes, orionBlock);
1755
+ const inferredVerticalAlign = inferComponentVerticalAlign(attributes, orionBlock);
1756
+ const horizontalAlign = isQuickHorizontalAlign(storedHorizontalAlign) && hasExplicitHorizontalAlign(orionBlock) ? storedHorizontalAlign : textAlign === "right" || justifyContent === "flex-end" ? "right" : textAlign === "center" || justifyContent === "center" ? "center" : inferredHorizontalAlign;
1757
+ const verticalAlign = isQuickVerticalAlign(storedVerticalAlign) && hasExplicitVerticalAlign(orionBlock) ? storedVerticalAlign : alignItems === "flex-end" ? "bottom" : alignItems === "center" || placeItems === "center" ? "middle" : inferredVerticalAlign;
1758
+ return {
1759
+ hidden: display === "none",
1760
+ horizontalAlign,
1761
+ layout,
1762
+ spacing,
1763
+ sticky: position === "sticky" || position === "fixed",
1764
+ verticalAlign
1765
+ };
1766
+ };
1767
+ var decodeHtmlAttribute3 = (value) => {
1768
+ if (typeof value !== "string") {
1769
+ return "";
1770
+ }
1771
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
1772
+ };
1773
+ var parseItemsJson = (value) => {
1774
+ const decoded = decodeHtmlAttribute3(value);
1775
+ if (!decoded) {
1776
+ return [];
1777
+ }
1778
+ try {
1779
+ const parsed = JSON.parse(decoded);
1780
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
1781
+ } catch {
1782
+ return [];
1783
+ }
1784
+ };
1785
+ var parseQuickDesignControls = (value) => {
1786
+ if (typeof value !== "string" || !value.trim()) {
1787
+ return defaultQuickDesignControls;
1788
+ }
1789
+ try {
1790
+ const parsed = JSON.parse(value);
1791
+ const layout = Array.isArray(parsed.layout) ? parsed.layout.filter(
1792
+ (item) => quickLayoutOptions.some((option) => option.value === item)
1793
+ ) : defaultQuickDesignControls.layout;
1794
+ return {
1795
+ alignment: parsed.alignment !== false,
1796
+ layout,
1797
+ spacing: parsed.spacing !== false
1798
+ };
1799
+ } catch {
1800
+ return defaultQuickDesignControls;
1801
+ }
1802
+ };
1803
+ var quickDesignControlsForSelection = (selectionSummary, component) => {
1804
+ if (!selectionSummary || !component) {
1805
+ return defaultQuickDesignControls;
1806
+ }
1807
+ const attrs = component.getAttributes?.() || {};
1808
+ const componentType = String(attrs["data-orion-component"] || selectionSummary.type || "");
1809
+ if (!attrs["data-orion-design-controls"] && quickDesignControlsByComponentType[componentType]) {
1810
+ return quickDesignControlsByComponentType[componentType];
1811
+ }
1812
+ return parseQuickDesignControls(attrs["data-orion-design-controls"]);
1813
+ };
1814
+ var postToParent = (payload) => {
1815
+ window.parent?.postMessage(
1816
+ {
1817
+ source: "payload-visual-builder-child",
1818
+ ...payload
1819
+ },
1820
+ "*"
1821
+ );
1822
+ };
1823
+ var buildSavePayload = (editor, status, projectData) => ({
1824
+ builderMode: "grapes-v2",
1825
+ compiledCss: scopeBuilderCss(editor.getCss()),
1826
+ compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
1827
+ projectData,
1828
+ status
1829
+ });
1830
+ var parsePayloadErrorMessage = async (response, fallback) => {
1831
+ try {
1832
+ const json = await response.json();
1833
+ return json.errors?.[0]?.message || json.message || fallback;
1834
+ } catch {
1835
+ const raw = await response.text();
1836
+ return raw.trim() || fallback;
1837
+ }
1838
+ };
1839
+ var getRelationID = (value) => {
1840
+ if (typeof value === "number" || typeof value === "string") {
1841
+ return value;
1842
+ }
1843
+ if (!value || typeof value !== "object") {
1844
+ return null;
1845
+ }
1846
+ const id = value.id;
1847
+ return typeof id === "number" || typeof id === "string" ? id : null;
1848
+ };
1849
+ var normalizeAssetSrc = (value) => {
1850
+ const trimmed = value.trim();
1851
+ if (!trimmed) {
1852
+ return "";
1853
+ }
1854
+ if (/^(https?:)?\/\//i.test(trimmed) || trimmed.startsWith("/")) {
1855
+ return trimmed;
1856
+ }
1857
+ return `/${trimmed}`;
1858
+ };
1859
+ var mediaDocImageCandidates = (doc) => {
1860
+ const candidates = [];
1861
+ if (doc.sizes && typeof doc.sizes === "object") {
1862
+ Object.values(doc.sizes).forEach((size) => {
1863
+ if (size?.url) {
1864
+ candidates.push({ src: normalizeAssetSrc(size.url), width: size.width });
1865
+ } else if (size?.filename) {
1866
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(size.filename)}`, width: size.width });
1867
+ }
1868
+ });
1869
+ }
1870
+ if (typeof doc.thumbnailURL === "string" && doc.thumbnailURL.length > 0) {
1871
+ candidates.push({ src: normalizeAssetSrc(doc.thumbnailURL), width: 360 });
1872
+ }
1873
+ if (typeof doc.url === "string" && doc.url.length > 0) {
1874
+ candidates.push({ src: normalizeAssetSrc(doc.url), width: doc.width });
1875
+ }
1876
+ if (typeof doc.filename === "string" && doc.filename.length > 0) {
1877
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(doc.filename)}`, width: doc.width });
1878
+ }
1879
+ return candidates.filter((candidate, index, all) => candidate.src && all.findIndex((item) => item.src === candidate.src) === index);
1880
+ };
1881
+ var pickMediaAssetSrc = (doc) => {
1882
+ const candidates = mediaDocImageCandidates(doc);
1883
+ if (candidates.length === 0) {
1884
+ return "";
1885
+ }
1886
+ const absolute = candidates.find((candidate) => /^(https?:)?\/\//i.test(candidate.src));
1887
+ if (absolute) {
1888
+ return absolute.src;
1889
+ }
1890
+ const fullSize = candidates.find((candidate) => candidate.src === doc.url);
1891
+ if (fullSize) {
1892
+ return fullSize.src;
1893
+ }
1894
+ const widest = [...candidates].sort((left, right) => (right.width || 0) - (left.width || 0))[0];
1895
+ return widest?.src || candidates[0]?.src || "";
1896
+ };
1897
+ var mediaDocToAsset = (doc) => {
1898
+ const id = getRelationID(doc);
1899
+ const filename = typeof doc.filename === "string" ? doc.filename : "";
1900
+ const src = pickMediaAssetSrc(doc);
1901
+ if (id === null || !src) {
1902
+ return null;
1903
+ }
1904
+ return {
1905
+ alt: typeof doc.alt === "string" ? doc.alt : "",
1906
+ id,
1907
+ name: filename || String(id),
1908
+ src,
1909
+ type: "image"
1910
+ };
1911
+ };
1912
+ var imageCanLoad = (src) => new Promise((resolve) => {
1913
+ if (!src) {
1914
+ resolve(false);
1915
+ return;
1916
+ }
1917
+ const image = new Image();
1918
+ image.onload = () => resolve(true);
1919
+ image.onerror = () => resolve(false);
1920
+ image.src = src;
1921
+ });
1922
+ var pruneBrokenImageAssets = async (editor) => {
1923
+ const assetManager = editor.AssetManager;
1924
+ const existingAssets = assetManager.getAll?.() || [];
1925
+ for (const asset of existingAssets) {
1926
+ const src = typeof asset.get === "function" ? String(asset.get("src") || "") : "";
1927
+ if (src && !await imageCanLoad(src)) {
1928
+ assetManager.remove?.(asset);
1929
+ }
1930
+ }
1931
+ };
1932
+ var extractUploadedMedia = (value) => {
1933
+ const candidate = value && typeof value === "object" && "doc" in value ? value.doc : value;
1934
+ if (!candidate || typeof candidate !== "object") {
1935
+ return null;
1936
+ }
1937
+ const id = getRelationID(candidate);
1938
+ if (id === null) {
1939
+ return null;
1940
+ }
1941
+ const typed = candidate;
1942
+ return {
1943
+ alt: typeof typed.alt === "string" ? typed.alt : "",
1944
+ filename: typeof typed.filename === "string" ? typed.filename : "",
1945
+ id,
1946
+ sizes: typed.sizes && typeof typed.sizes === "object" ? typed.sizes : void 0,
1947
+ thumbnailURL: typeof typed.thumbnailURL === "string" ? typed.thumbnailURL : "",
1948
+ url: typeof typed.url === "string" ? typed.url : "",
1949
+ width: typeof typed.width === "number" ? typed.width : void 0
1950
+ };
1951
+ };
1952
+ var loadPayloadMediaAssets = async (editor) => {
1953
+ const response = await fetch(`/api/media?depth=1&limit=100&sort=-updatedAt&_=${Date.now()}`, {
1954
+ cache: "no-store",
1955
+ credentials: "include"
1956
+ });
1957
+ if (!response.ok) {
1958
+ return;
1959
+ }
1960
+ const json = await response.json();
1961
+ const candidateAssets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
1962
+ const assets = [];
1963
+ for (const asset of candidateAssets) {
1964
+ if (await imageCanLoad(asset.src)) {
1965
+ assets.push(asset);
1966
+ }
1967
+ }
1968
+ if (assets.length > 0) {
1969
+ editor.AssetManager.add(assets);
1970
+ }
1971
+ await pruneBrokenImageAssets(editor);
1972
+ };
1973
+ var uploadPayloadMediaAssets = async (editor, files) => {
1974
+ const fileArray = Array.from(files);
1975
+ const uploadedAssets = [];
1976
+ for (const file of fileArray) {
1977
+ const optimizedFile = await optimizeImageForUpload(file);
1978
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
1979
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
1980
+ }
1981
+ const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
1982
+ const formData = new FormData();
1983
+ formData.set("_payload", JSON.stringify({ alt: fallbackAlt || "Uploaded image" }));
1984
+ formData.set("alt", fallbackAlt || "Uploaded image");
1985
+ formData.set("file", optimizedFile);
1986
+ const response = await fetch("/api/media", {
1987
+ body: formData,
1988
+ credentials: "include",
1989
+ method: "POST"
1990
+ });
1991
+ if (!response.ok) {
1992
+ throw new Error(await parsePayloadErrorMessage(response, "Could not upload image."));
1993
+ }
1994
+ const uploaded = extractUploadedMedia(await response.json());
1995
+ const asset = uploaded ? mediaDocToAsset(uploaded) : null;
1996
+ if (asset) {
1997
+ uploadedAssets.push(asset);
1998
+ }
1999
+ }
2000
+ if (uploadedAssets.length > 0) {
2001
+ editor.AssetManager.add(uploadedAssets);
2002
+ }
2003
+ };
2004
+ function GrapesPageEditor({
2005
+ adapter,
2006
+ autosaveIntervalMs = 3e4,
2007
+ initialData,
2008
+ pageID
2009
+ }) {
2010
+ const containerRef = useRef(null);
2011
+ const editorRef = useRef(null);
2012
+ const autosaveTimerRef = useRef(null);
2013
+ const saveRef = useRef(async () => void 0);
2014
+ const selectedComponentRef = useRef(null);
2015
+ const lastSelectedComponentRef = useRef(null);
2016
+ const colorPickerAnchorRef = useRef(null);
2017
+ const sidebarPointerActiveRef = useRef(false);
2018
+ const stylePanelEditActiveRef = useRef(false);
2019
+ const historyRestoreActiveRef = useRef(false);
2020
+ const stylePanelHistoryBeforeRef = useRef(null);
2021
+ const lastComponentSnapshotRef = useRef(/* @__PURE__ */ new WeakMap());
2022
+ const customUndoStackRef = useRef([]);
2023
+ const customRedoStackRef = useRef([]);
2024
+ const syncingOrionBlockStylesRef = useRef(false);
2025
+ const [error, setError] = useState("");
2026
+ const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
2027
+ const [isDirty, setIsDirty] = useState(false);
2028
+ const [lastSavedAt, setLastSavedAt] = useState("");
2029
+ const [loading, setLoading] = useState(true);
2030
+ const [publicationState, setPublicationState] = useState("live");
2031
+ const [quickLayoutState, setQuickLayoutState] = useState(() => readQuickLayoutState(null));
2032
+ const [selectedDevice, setSelectedDevice] = useState("desktop");
2033
+ const [detailedLayoutControlState, setDetailedLayoutControlState] = useState(
2034
+ () => readDetailedLayoutControlState(null)
2035
+ );
2036
+ const [saving, setSaving] = useState(null);
2037
+ const [saveMessage, setSaveMessage] = useState("");
2038
+ const [selectionSummary, setSelectionSummary] = useState(null);
2039
+ const [openInspectorPanels, setOpenInspectorPanels] = useState({});
2040
+ const [openSidebarPanels, setOpenSidebarPanels] = useState({});
2041
+ const [validationIssues, setValidationIssues] = useState([]);
2042
+ const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
2043
+ const pageTree = initialData?.meta?.pageTree || [];
2044
+ const role = initialData?.meta?.permissions?.role || "client";
2045
+ const canUseDeveloperControls = role === "developer";
2046
+ const quickDesignControls = quickDesignControlsForSelection(selectionSummary, selectedComponentRef.current);
2047
+ const availableQuickLayoutOptions = quickLayoutOptions.filter((option) => quickDesignControls.layout.includes(option.value));
2048
+ const summarizeSelectedComponent = (component) => {
2049
+ if (!component) {
2050
+ return null;
2051
+ }
2052
+ const attrs = component.getAttributes?.() || {};
2053
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
2054
+ const rawName = String(component.get?.("name") || componentType || "Section");
2055
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
2056
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
2057
+ index,
2058
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
2059
+ }));
2060
+ return {
2061
+ items: items.length > 0 ? items : void 0,
2062
+ label: label || "Section",
2063
+ type: componentType
2064
+ };
2065
+ };
2066
+ const getCurrentStyleTarget = () => selectedComponentRef.current || lastSelectedComponentRef.current || (editorRef.current?.getSelected?.() || null);
2067
+ const findOrionBlockByElement = (element) => {
2068
+ const target = element?.closest("[data-orion-block], [data-orion-component]");
2069
+ const targetID = target?.id || "";
2070
+ if (!targetID) {
2071
+ return null;
2072
+ }
2073
+ const editor = editorRef.current;
2074
+ const wrapper = editor?.DomComponents?.getWrapper?.() || null;
2075
+ const component = wrapper?.find?.(`#${CSS.escape(targetID)}`)?.[0] || null;
2076
+ return isOrionBlockComponent(component) ? component : null;
2077
+ };
2078
+ const findOrionBlockAncestor = (component) => {
2079
+ let current = component;
2080
+ const visited = /* @__PURE__ */ new Set();
2081
+ while (current && !visited.has(current)) {
2082
+ visited.add(current);
2083
+ if (isOrionBlockComponent(current)) {
2084
+ return current;
2085
+ }
2086
+ current = current.parent?.() || null;
2087
+ }
2088
+ return null;
2089
+ };
2090
+ const isOrionBlockComponent = (component) => Boolean(component?.getAttributes?.()?.["data-orion-component"]) || Object.keys(parseOrionBlockAttribute(component?.getAttributes?.()?.["data-orion-block"])).length > 0;
2091
+ const findSelectedCanvasOrionBlock = () => {
2092
+ const editor = editorRef.current;
2093
+ const wrapper = editor?.DomComponents?.getWrapper?.() || null;
2094
+ const candidates = [
2095
+ ...wrapper?.find?.("[data-orion-block]") || [],
2096
+ ...wrapper?.find?.("[data-orion-component]") || []
2097
+ ];
2098
+ return candidates.find((candidate) => {
2099
+ const element = candidate.getEl?.();
2100
+ return Boolean(
2101
+ element?.classList.contains("gjs-selected") || element?.matches("[data-gjs-highlightable].gjs-selected, .gjs-selected")
2102
+ );
2103
+ }) || null;
2104
+ };
2105
+ const getCurrentOrionBlockTarget = (component = getCurrentStyleTarget()) => {
2106
+ const ancestor = findOrionBlockAncestor(component);
2107
+ if (ancestor) {
2108
+ return ancestor;
2109
+ }
2110
+ const canvasSelected = findSelectedCanvasOrionBlock();
2111
+ if (canvasSelected) {
2112
+ return canvasSelected;
2113
+ }
2114
+ const editor = editorRef.current;
2115
+ const selected = editor?.getSelected?.() || null;
2116
+ const selectedAncestor = findOrionBlockAncestor(selected);
2117
+ if (selectedAncestor) {
2118
+ return selectedAncestor;
2119
+ }
2120
+ const selectedID = firstString(selected?.getAttributes?.()?.id, component?.getAttributes?.()?.id);
2121
+ const wrapper = editor?.DomComponents?.getWrapper?.() || null;
2122
+ if (!wrapper) {
2123
+ return component || selected || null;
2124
+ }
2125
+ const candidates = [
2126
+ ...selectedID ? wrapper.find?.(`#${selectedID}`) || [] : [],
2127
+ ...wrapper.find?.("[data-orion-block]") || [],
2128
+ ...wrapper.find?.("[data-orion-component]") || []
2129
+ ];
2130
+ return candidates.find(
2131
+ (candidate) => isOrionBlockComponent(candidate)
2132
+ ) || component || selected || null;
2133
+ };
2134
+ const selectStyleManagerTarget = (component) => {
2135
+ if (!component) {
2136
+ return;
2137
+ }
2138
+ const editor = editorRef.current;
2139
+ editor?.StyleManager?.select?.(component);
2140
+ };
2141
+ const markSelectedCanvasBlock = (component) => {
2142
+ const canvasDocument = editorRef.current?.Canvas?.getDocument?.();
2143
+ canvasDocument?.querySelectorAll(".orion-builder-v2-selected-block").forEach((element) => element.classList.remove("orion-builder-v2-selected-block"));
2144
+ component?.getEl?.()?.classList.add("orion-builder-v2-selected-block");
2145
+ };
2146
+ const updateSelectedItems = (updater) => {
2147
+ const component = getCurrentOrionBlockTarget();
2148
+ if (!component) {
2149
+ return;
2150
+ }
2151
+ const attrs = component.getAttributes?.() || {};
2152
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
2153
+ const nextItems = updater(currentItems);
2154
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2155
+ const before = snapshotComponent(component);
2156
+ if (before && currentItems.length === 0) {
2157
+ before.attributes["data-orion-items-json"] = "[]";
2158
+ if (Object.keys(block).length > 0) {
2159
+ before.attributes["data-orion-block"] = serializeOrionBlockAttribute({
2160
+ ...block,
2161
+ items: []
2162
+ });
2163
+ }
2164
+ }
2165
+ withoutUndoTracking2(() => {
2166
+ component.addAttributes?.({
2167
+ "data-orion-items-json": JSON.stringify(nextItems),
2168
+ ...Object.keys(block).length > 0 ? {
2169
+ "data-orion-block": serializeOrionBlockAttribute({
2170
+ ...block,
2171
+ items: nextItems
2172
+ })
2173
+ } : {}
2174
+ });
2175
+ });
2176
+ pushCustomHistoryEntry(before, snapshotComponent(component));
2177
+ setSelectionSummary(summarizeSelectedComponent(component));
2178
+ };
2179
+ const refreshSelectedState = (component) => {
2180
+ setSelectionSummary(summarizeSelectedComponent(component));
2181
+ setQuickLayoutState(readQuickLayoutState(component));
2182
+ setDetailedLayoutControlState(readDetailedLayoutControlState(component));
2183
+ window.requestAnimationFrame(() => decorateBuilderControls());
2184
+ };
2185
+ const applySelectedStyle = (style) => {
2186
+ const component = selectedComponentRef.current;
2187
+ if (!component) {
2188
+ return;
2189
+ }
2190
+ component.addStyle?.(style);
2191
+ rememberComponentSnapshot(component);
2192
+ refreshSelectedState(component);
2193
+ };
2194
+ const applySelectedStyleAndAttributes = (style, attributes) => {
2195
+ const component = selectedComponentRef.current;
2196
+ if (!component) {
2197
+ return;
2198
+ }
2199
+ component.addStyle?.(style);
2200
+ component.addAttributes?.(attributes);
2201
+ rememberComponentSnapshot(component);
2202
+ refreshSelectedState(component);
2203
+ };
2204
+ const applyLiveStylePanelValue = (target) => {
2205
+ const property = propertyNameFromStyleControl(target);
2206
+ if (!property || !liveStyleProperties.has(property)) {
2207
+ return;
2208
+ }
2209
+ const selected = getCurrentOrionBlockTarget(getCurrentStyleTarget());
2210
+ if (!selected) {
2211
+ return;
2212
+ }
2213
+ const value = property === "font-weight" ? normalizeFontWeightValue(readStylePanelSelect(property)) || readStylePanelInput(property) : property === "opacity" ? readStylePanelInput(property) : readStylePanelLength(property) || readStylePanelInput(property) || readStylePanelSelect(property) || readStylePanelRadio(property);
2214
+ selectedComponentRef.current = selected;
2215
+ lastSelectedComponentRef.current = selected;
2216
+ selected.addStyle?.({ [property]: value });
2217
+ editorRef.current?.select?.(selected);
2218
+ };
2219
+ const updateSelectedOrionBlock = (updates) => {
2220
+ const component = selectedComponentRef.current;
2221
+ if (!component) {
2222
+ return;
2223
+ }
2224
+ const attrs = component.getAttributes?.() || {};
2225
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2226
+ if (Object.keys(block).length === 0) {
2227
+ return;
2228
+ }
2229
+ component.addAttributes?.({
2230
+ "data-orion-block": serializeOrionBlockAttribute({
2231
+ ...block,
2232
+ ...updates
2233
+ })
2234
+ });
2235
+ rememberComponentSnapshot(component);
2236
+ };
2237
+ const syncSelectedTypographyStyleToOrionBlock = (component = selectedComponentRef.current) => {
2238
+ if (!component) {
2239
+ return;
2240
+ }
2241
+ const attrs = component.getAttributes?.() || {};
2242
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2243
+ if (Object.keys(block).length === 0) {
2244
+ return;
2245
+ }
2246
+ const kind = attrs["data-orion-active-text-kind"] === "heading" ? "heading" : "body";
2247
+ const style = component.getStyle?.() || {};
2248
+ const settings = readNestedRecord(block, "settings");
2249
+ const typography = readNestedRecord(settings, "typography");
2250
+ const nextTypography = { ...typography };
2251
+ const fontFamily = normalizeFontFamilyValue(style["font-family"]);
2252
+ const fontSize = normalizeCssSizeValue(style["font-size"]);
2253
+ const fontWeight = normalizeFontWeightValue(style["font-weight"]);
2254
+ const lineHeight = normalizeCssSizeValue(style["line-height"]);
2255
+ const letterSpacing = normalizeCssSizeValue(style["letter-spacing"]);
2256
+ const color = normalizeCssColorValue(style.color);
2257
+ const textAlign = normalizeStyleTextAlignment(style["text-align"]);
2258
+ if (fontFamily) {
2259
+ nextTypography[typographyKey(kind, "FontFamily")] = fontFamily;
2260
+ }
2261
+ if (fontSize) {
2262
+ nextTypography[typographyKey(kind, "FontSize")] = fontSize;
2263
+ }
2264
+ if (fontWeight) {
2265
+ nextTypography[typographyKey(kind, "FontWeight")] = fontWeight;
2266
+ }
2267
+ if (lineHeight) {
2268
+ nextTypography[typographyKey(kind, "LineHeight")] = lineHeight;
2269
+ }
2270
+ if (letterSpacing) {
2271
+ nextTypography[typographyKey(kind, "LetterSpacing")] = letterSpacing;
2272
+ }
2273
+ if (color) {
2274
+ nextTypography[typographyKey(kind, "Color")] = color;
2275
+ }
2276
+ if (textAlign) {
2277
+ nextTypography[typographyKey(kind, "Align")] = textAlign;
2278
+ if (kind === "heading") {
2279
+ block.textHeadingAlign = textAlign;
2280
+ } else {
2281
+ block.textBodyAlign = textAlign;
2282
+ }
2283
+ block.horizontalAlign = textAlign;
2284
+ }
2285
+ component.addAttributes?.({
2286
+ "data-orion-block": serializeOrionBlockAttribute({
2287
+ ...block,
2288
+ settings: {
2289
+ ...settings,
2290
+ typography: nextTypography
2291
+ }
2292
+ })
2293
+ });
2294
+ };
2295
+ const syncSelectedDecorationStyleToOrionBlock = (component = selectedComponentRef.current) => {
2296
+ const target = getCurrentOrionBlockTarget(component);
2297
+ if (!target) {
2298
+ return;
2299
+ }
2300
+ const attrs = target.getAttributes?.() || {};
2301
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2302
+ if (Object.keys(block).length === 0) {
2303
+ return;
2304
+ }
2305
+ const backgroundColor = getStyleColorValue(component?.getStyle?.() || target.getStyle?.() || {}, "background-color");
2306
+ if (!backgroundColor) {
2307
+ return;
2308
+ }
2309
+ target.addAttributes?.({
2310
+ "data-orion-background-color": backgroundColor,
2311
+ "data-orion-block": serializeOrionBlockAttribute({
2312
+ ...block,
2313
+ backgroundColor
2314
+ })
2315
+ });
2316
+ };
2317
+ const syncSelectedTypographyControlsToOrionBlock = (component = selectedComponentRef.current) => {
2318
+ if (!component) {
2319
+ return;
2320
+ }
2321
+ const attrs = component.getAttributes?.() || {};
2322
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2323
+ if (Object.keys(block).length === 0) {
2324
+ return;
2325
+ }
2326
+ const kind = attrs["data-orion-active-text-kind"] === "heading" ? "heading" : "body";
2327
+ const settings = readNestedRecord(block, "settings");
2328
+ const typography = readNestedRecord(settings, "typography");
2329
+ const nextTypography = { ...typography };
2330
+ const nextStyle = {};
2331
+ const fontFamilyControl = readStylePanelValue("font-family");
2332
+ const fontFamily = normalizeFontFamilyValue(readStylePanelSelect("font-family"));
2333
+ const fontSize = normalizeCssSizeValue(readStylePanelLength("font-size"));
2334
+ const fontWeight = normalizeFontWeightValue(readStylePanelSelect("font-weight"));
2335
+ const lineHeight = normalizeCssSizeValue(readStylePanelLength("line-height"));
2336
+ const letterSpacing = normalizeCssSizeValue(readStylePanelLength("letter-spacing"));
2337
+ const color = normalizeCssColorValue(readStylePanelInput("color"));
2338
+ const textAlign = normalizeStyleTextAlignment(readStylePanelRadio("text-align"));
2339
+ if (fontFamilyControl) {
2340
+ if (fontFamily) {
2341
+ nextTypography[typographyKey(kind, "FontFamily")] = fontFamily;
2342
+ nextStyle["font-family"] = fontFamily;
2343
+ } else {
2344
+ delete nextTypography[typographyKey(kind, "FontFamily")];
2345
+ nextStyle["font-family"] = "";
2346
+ component.removeStyle?.("font-family");
2347
+ }
2348
+ }
2349
+ if (fontSize) {
2350
+ nextTypography[typographyKey(kind, "FontSize")] = fontSize;
2351
+ nextStyle["font-size"] = fontSize;
2352
+ }
2353
+ if (fontWeight) {
2354
+ nextTypography[typographyKey(kind, "FontWeight")] = fontWeight;
2355
+ nextStyle["font-weight"] = fontWeight;
2356
+ }
2357
+ if (lineHeight) {
2358
+ nextTypography[typographyKey(kind, "LineHeight")] = lineHeight;
2359
+ nextStyle["line-height"] = lineHeight;
2360
+ }
2361
+ if (letterSpacing) {
2362
+ nextTypography[typographyKey(kind, "LetterSpacing")] = letterSpacing;
2363
+ nextStyle["letter-spacing"] = letterSpacing;
2364
+ }
2365
+ if (color) {
2366
+ nextTypography[typographyKey(kind, "Color")] = color;
2367
+ nextStyle.color = color;
2368
+ }
2369
+ if (textAlign) {
2370
+ nextTypography[typographyKey(kind, "Align")] = textAlign;
2371
+ nextStyle["text-align"] = textAlign;
2372
+ if (kind === "heading") {
2373
+ block.textHeadingAlign = textAlign;
2374
+ } else {
2375
+ block.textBodyAlign = textAlign;
2376
+ }
2377
+ block.horizontalAlign = textAlign;
2378
+ }
2379
+ if (Object.keys(nextStyle).length > 0) {
2380
+ component.addStyle?.(nextStyle);
2381
+ }
2382
+ component.addAttributes?.({
2383
+ "data-orion-block": serializeOrionBlockAttribute({
2384
+ ...block,
2385
+ settings: {
2386
+ ...settings,
2387
+ typography: nextTypography
2388
+ }
2389
+ })
2390
+ });
2391
+ };
2392
+ const syncSelectedDecorationControlsToOrionBlock = (component = selectedComponentRef.current) => {
2393
+ if (historyRestoreActiveRef.current) {
2394
+ return;
2395
+ }
2396
+ const target = getCurrentOrionBlockTarget(component);
2397
+ if (!target) {
2398
+ return;
2399
+ }
2400
+ const attrs = target.getAttributes?.() || {};
2401
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2402
+ if (Object.keys(block).length === 0) {
2403
+ return;
2404
+ }
2405
+ const panelBackgroundColor = normalizeCssColorValue(readStylePanelColor("background-color"));
2406
+ const styleBackgroundColor = getStyleColorValue(component?.getStyle?.() || target.getStyle?.() || {}, "background-color");
2407
+ const backgroundColor = normalizeCssColorValue(panelBackgroundColor === "none" ? "" : panelBackgroundColor) || styleBackgroundColor;
2408
+ if (!backgroundColor) {
2409
+ return;
2410
+ }
2411
+ if (getStyleColorValue(target.getStyle?.() || {}, "background-color") !== backgroundColor) {
2412
+ target.addStyle?.({ "background-color": backgroundColor });
2413
+ }
2414
+ if (component && component !== target && getStyleColorValue(component.getStyle?.() || {}, "background-color") !== backgroundColor) {
2415
+ component.addStyle?.({ "background-color": backgroundColor });
2416
+ }
2417
+ target.addAttributes?.({
2418
+ "data-orion-background-color": backgroundColor,
2419
+ "data-orion-block": serializeOrionBlockAttribute({
2420
+ ...block,
2421
+ backgroundColor
2422
+ })
2423
+ });
2424
+ };
2425
+ const syncAllOrionBlockStylesToAttributes = () => {
2426
+ if (syncingOrionBlockStylesRef.current) {
2427
+ return;
2428
+ }
2429
+ const editor = editorRef.current;
2430
+ const wrapper = editor?.DomComponents?.getWrapper?.() || null;
2431
+ const blocks = wrapper?.find?.("[data-orion-block]") || [];
2432
+ syncingOrionBlockStylesRef.current = true;
2433
+ try {
2434
+ blocks.forEach((component) => {
2435
+ const attrs = component.getAttributes?.() || {};
2436
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2437
+ if (Object.keys(block).length === 0) {
2438
+ return;
2439
+ }
2440
+ const backgroundColor = getStyleColorValue(component.getStyle?.() || {}, "background-color");
2441
+ if (!backgroundColor || normalizeCssColorValue(block.backgroundColor) === backgroundColor && normalizeCssColorValue(attrs["data-orion-background-color"]) === backgroundColor) {
2442
+ return;
2443
+ }
2444
+ component.addAttributes?.({
2445
+ "data-orion-background-color": backgroundColor,
2446
+ "data-orion-block": serializeOrionBlockAttribute({
2447
+ ...block,
2448
+ backgroundColor
2449
+ })
2450
+ });
2451
+ });
2452
+ } finally {
2453
+ syncingOrionBlockStylesRef.current = false;
2454
+ }
2455
+ };
2456
+ const hydrateSelectedTypographyStyleFromOrionBlock = (component) => {
2457
+ if (!component) {
2458
+ return;
2459
+ }
2460
+ const attrs = component.getAttributes?.() || {};
2461
+ const block = parseOrionBlockAttribute(attrs["data-orion-block"]);
2462
+ if (Object.keys(block).length === 0) {
2463
+ return;
2464
+ }
2465
+ const kind = attrs["data-orion-active-text-kind"] === "heading" ? "heading" : "body";
2466
+ const settings = readNestedRecord(block, "settings");
2467
+ const typography = readNestedRecord(settings, "typography");
2468
+ const nextStyle = {};
2469
+ const currentStyle = component.getStyle?.() || {};
2470
+ const fontFamily = normalizeFontFamilyValue(firstString(typography[typographyKey(kind, "FontFamily")], attrs["data-orion-active-font-family"]));
2471
+ const fontSize = firstString(typography[typographyKey(kind, "FontSize")], attrs["data-orion-active-font-size"]);
2472
+ const fontWeight = normalizeFontWeightValue(firstString(typography[typographyKey(kind, "FontWeight")], attrs["data-orion-active-font-weight"]));
2473
+ const lineHeight = firstString(typography[typographyKey(kind, "LineHeight")], attrs["data-orion-active-line-height"]);
2474
+ const letterSpacing = firstString(typography[typographyKey(kind, "LetterSpacing")], attrs["data-orion-active-letter-spacing"]);
2475
+ const color = firstString(typography[typographyKey(kind, "Color")], attrs["data-orion-active-color"]);
2476
+ const textAlign = firstString(typography[typographyKey(kind, "Align")], kind === "heading" ? block.textHeadingAlign : block.textBodyAlign, attrs["data-orion-active-text-align"]);
2477
+ if (fontFamily && !currentStyle["font-family"]) {
2478
+ nextStyle["font-family"] = fontFamily;
2479
+ }
2480
+ if (fontSize && !currentStyle["font-size"]) {
2481
+ nextStyle["font-size"] = fontSize;
2482
+ }
2483
+ if (fontWeight && !currentStyle["font-weight"]) {
2484
+ nextStyle["font-weight"] = fontWeight;
2485
+ }
2486
+ if (lineHeight && !currentStyle["line-height"]) {
2487
+ nextStyle["line-height"] = lineHeight;
2488
+ }
2489
+ if (letterSpacing && letterSpacing !== "normal" && !currentStyle["letter-spacing"]) {
2490
+ nextStyle["letter-spacing"] = letterSpacing;
2491
+ }
2492
+ if (color && !currentStyle.color) {
2493
+ nextStyle.color = color;
2494
+ }
2495
+ if (textAlign && !currentStyle["text-align"]) {
2496
+ nextStyle["text-align"] = textAlign;
2497
+ }
2498
+ if (Object.keys(nextStyle).length > 0) {
2499
+ component.addStyle?.(nextStyle);
2500
+ }
2501
+ };
2502
+ const rehydrateSelectedComponentControls = (component) => {
2503
+ if (!component || component !== selectedComponentRef.current) {
2504
+ return;
2505
+ }
2506
+ hydrateSelectedTypographyStyleFromOrionBlock(component);
2507
+ rememberComponentSnapshot(component);
2508
+ markSelectedCanvasBlock(component);
2509
+ refreshSelectedState(component);
2510
+ window.requestAnimationFrame(() => {
2511
+ const element = spacingTargetForComponent(component);
2512
+ if (!element) {
2513
+ return;
2514
+ }
2515
+ const computed = window.getComputedStyle(element);
2516
+ spacingProperties.forEach((property) => {
2517
+ writeStylePanelLength(property, computed.getPropertyValue(property));
2518
+ });
2519
+ });
2520
+ };
2521
+ const applyQuickLayout = (layout) => {
2522
+ const nextStyle = quickLayoutStyles[layout];
2523
+ applySelectedStyleAndAttributes(
2524
+ {
2525
+ ...nextStyle,
2526
+ ...quickLayoutState.spacing ? { padding: quickSpacingStyles[quickLayoutState.spacing] } : {},
2527
+ ...quickLayoutState.hidden ? { display: "none" } : {},
2528
+ ...quickLayoutState.sticky ? { position: "sticky", top: "0px", "z-index": "10" } : {}
2529
+ },
2530
+ {
2531
+ "data-orion-layout-mode": layout
2532
+ }
2533
+ );
2534
+ };
2535
+ const applyQuickSpacing = (spacing) => {
2536
+ applySelectedStyle({ padding: quickSpacingStyles[spacing] });
2537
+ };
2538
+ const applyQuickHorizontalAlign = (alignment) => {
2539
+ updateSelectedOrionBlock({
2540
+ horizontalAlign: alignment,
2541
+ textBodyAlign: alignment,
2542
+ textHeadingAlign: alignment
2543
+ });
2544
+ applySelectedStyleAndAttributes(quickHorizontalAlignStyles[alignment], {
2545
+ "data-orion-horizontal-align": alignment
2546
+ });
2547
+ };
2548
+ const applyQuickVerticalAlign = (alignment) => {
2549
+ updateSelectedOrionBlock({
2550
+ verticalAlign: alignment
2551
+ });
2552
+ applySelectedStyleAndAttributes(
2553
+ {
2554
+ "align-items": quickVerticalAlignStyles[alignment],
2555
+ "place-items": "",
2556
+ width: "100%"
2557
+ },
2558
+ {
2559
+ "data-orion-vertical-align": alignment
2560
+ }
2561
+ );
2562
+ };
2563
+ const toggleSticky = () => {
2564
+ applySelectedStyle(
2565
+ quickLayoutState.sticky ? { position: "", top: "", "z-index": "" } : { position: "sticky", top: "0px", "z-index": "10" }
2566
+ );
2567
+ };
2568
+ const toggleHidden = () => {
2569
+ applySelectedStyle(
2570
+ quickLayoutState.hidden ? { display: quickLayoutStyles[quickLayoutState.layout].display || "" } : { display: "none" }
2571
+ );
2572
+ };
2573
+ const toggleInspectorPanel = (panel) => {
2574
+ setOpenInspectorPanels((current) => ({
2575
+ ...current,
2576
+ [panel]: !current[panel]
2577
+ }));
2578
+ };
2579
+ const toggleSidebarPanel = (panel) => {
2580
+ setOpenSidebarPanels((current) => ({
2581
+ ...current,
2582
+ [panel]: !current[panel]
2583
+ }));
2584
+ };
2585
+ const updateHistoryState = (editor) => {
2586
+ const next = {
2587
+ canRedo: customRedoStackRef.current.length > 0 || editor.UndoManager.hasRedo(),
2588
+ canUndo: customUndoStackRef.current.length > 0 || editor.UndoManager.hasUndo()
2589
+ };
2590
+ setHistoryState(next);
2591
+ postToParent({
2592
+ ...next,
2593
+ type: "history-state"
2594
+ });
2595
+ };
2596
+ const snapshotComponent = (component) => {
2597
+ if (!component) {
2598
+ return null;
2599
+ }
2600
+ const parent = component.parent?.();
2601
+ const collection = parent?.components?.();
2602
+ const index = typeof collection?.indexOf === "function" ? collection.indexOf(component) : void 0;
2603
+ return {
2604
+ attributes: { ...component.getAttributes?.() || {} },
2605
+ component,
2606
+ index,
2607
+ parent,
2608
+ style: { ...component.getStyle?.() || {} }
2609
+ };
2610
+ };
2611
+ const rememberComponentSnapshot = (component) => {
2612
+ const snapshot = snapshotComponent(component);
2613
+ if (snapshot) {
2614
+ lastComponentSnapshotRef.current.set(snapshot.component, snapshot);
2615
+ }
2616
+ return snapshot;
2617
+ };
2618
+ const getPreviousComponentSnapshot = (component) => component ? lastComponentSnapshotRef.current.get(component) || snapshotComponent(component) : null;
2619
+ const withoutUndoTracking2 = (callback) => {
2620
+ const undoManager = editorRef.current?.UndoManager;
2621
+ if (typeof undoManager?.skip === "function") {
2622
+ undoManager.skip(callback);
2623
+ return;
2624
+ }
2625
+ if (typeof undoManager?.stop === "function" && typeof undoManager?.start === "function") {
2626
+ undoManager.stop();
2627
+ try {
2628
+ callback();
2629
+ } finally {
2630
+ undoManager.start();
2631
+ }
2632
+ return;
2633
+ }
2634
+ callback();
2635
+ };
2636
+ const isComponentAttached = (component) => Boolean(component.parent?.());
2637
+ const addComponentFromSnapshot = (snapshot) => {
2638
+ const parent = snapshot.parent;
2639
+ if (isComponentAttached(snapshot.component) || !parent?.components) {
2640
+ return;
2641
+ }
2642
+ parent.components?.().add?.(snapshot.component, { at: snapshot.index });
2643
+ };
2644
+ const removeComponentFromSnapshot = (snapshot) => {
2645
+ ;
2646
+ snapshot.component.remove?.();
2647
+ };
2648
+ const restoreComponentSnapshot = (snapshot) => {
2649
+ addComponentFromSnapshot(snapshot);
2650
+ const currentStyle = snapshot.component.getStyle?.() || {};
2651
+ const currentAttributes = snapshot.component.getAttributes?.() || {};
2652
+ const staleAttributes = Object.keys(currentAttributes).filter((key) => !(key in snapshot.attributes));
2653
+ snapshot.component.addStyle?.({
2654
+ ...Object.fromEntries(Object.keys(currentStyle).map((key) => [key, ""])),
2655
+ ...Object.fromEntries(Object.entries(snapshot.style).map(([key, value]) => [key, String(value)]))
2656
+ });
2657
+ if (staleAttributes.length > 0) {
2658
+ ;
2659
+ snapshot.component.removeAttributes?.(staleAttributes);
2660
+ }
2661
+ snapshot.component.addAttributes?.({ ...snapshot.attributes });
2662
+ selectedComponentRef.current = snapshot.component;
2663
+ lastSelectedComponentRef.current = snapshot.component;
2664
+ editorRef.current?.select?.(snapshot.component);
2665
+ selectStyleManagerTarget(snapshot.component);
2666
+ lastComponentSnapshotRef.current.set(snapshot.component, snapshot);
2667
+ markSelectedCanvasBlock(snapshot.component);
2668
+ refreshSelectedState(snapshot.component);
2669
+ };
2670
+ const restoreCustomHistoryEntry = (entry, target) => {
2671
+ const snapshot = entry[target];
2672
+ const otherSnapshot = target === "before" ? entry.after : entry.before;
2673
+ if (!snapshot) {
2674
+ if (otherSnapshot) {
2675
+ removeComponentFromSnapshot(otherSnapshot);
2676
+ }
2677
+ refreshSelectedState(null);
2678
+ return;
2679
+ }
2680
+ restoreComponentSnapshot(snapshot);
2681
+ };
2682
+ const equivalentHistoryAttributes = (before, after) => {
2683
+ const beforeBlock = parseOrionBlockAttribute(before["data-orion-block"]);
2684
+ const afterBlock = parseOrionBlockAttribute(after["data-orion-block"]);
2685
+ const beforeComparable = { ...before };
2686
+ const afterComparable = { ...after };
2687
+ if (Object.keys(beforeBlock).length > 0 && Object.keys(afterBlock).length > 0 && JSON.stringify(beforeBlock.items || []) === JSON.stringify(afterBlock.items || [])) {
2688
+ beforeComparable["data-orion-block"] = "";
2689
+ afterComparable["data-orion-block"] = "";
2690
+ }
2691
+ return JSON.stringify(beforeComparable) === JSON.stringify(afterComparable);
2692
+ };
2693
+ const pushCustomHistoryEntry = (before, after) => {
2694
+ if (!before && !after) {
2695
+ return;
2696
+ }
2697
+ if (before && after && before.component !== after.component) {
2698
+ return;
2699
+ }
2700
+ if (before && after && JSON.stringify(before.style) === JSON.stringify(after.style) && equivalentHistoryAttributes(before.attributes, after.attributes)) {
2701
+ return;
2702
+ }
2703
+ customUndoStackRef.current.push({ after, before });
2704
+ customRedoStackRef.current = [];
2705
+ if (after) {
2706
+ lastComponentSnapshotRef.current.set(after.component, after);
2707
+ }
2708
+ if (customUndoStackRef.current.length > 100) {
2709
+ customUndoStackRef.current.shift();
2710
+ }
2711
+ if (editorRef.current) {
2712
+ updateHistoryState(editorRef.current);
2713
+ }
2714
+ };
2715
+ const completeHistoryRestore = (editor) => {
2716
+ window.requestAnimationFrame(() => {
2717
+ window.setTimeout(() => {
2718
+ syncAllOrionBlockStylesToAttributes();
2719
+ const selected = getCurrentOrionBlockTarget(getCurrentStyleTarget()) || selectedComponentRef.current;
2720
+ if (selected) {
2721
+ selectedComponentRef.current = selected;
2722
+ lastSelectedComponentRef.current = selected;
2723
+ selectStyleManagerTarget(selected);
2724
+ markSelectedCanvasBlock(selected);
2725
+ refreshSelectedState(selected);
2726
+ }
2727
+ updateHistoryState(editor);
2728
+ historyRestoreActiveRef.current = false;
2729
+ }, 120);
2730
+ });
2731
+ };
2732
+ const runHistoryAction = (action) => {
2733
+ const editor = editorRef.current;
2734
+ if (!editor) {
2735
+ return;
2736
+ }
2737
+ const customEntry = action === "undo" ? customUndoStackRef.current.pop() : customRedoStackRef.current.pop();
2738
+ if (customEntry) {
2739
+ historyRestoreActiveRef.current = true;
2740
+ restoreCustomHistoryEntry(customEntry, action === "undo" ? "before" : "after");
2741
+ if (action === "undo") {
2742
+ customRedoStackRef.current.push(customEntry);
2743
+ } else {
2744
+ customUndoStackRef.current.push(customEntry);
2745
+ }
2746
+ completeHistoryRestore(editor);
2747
+ return;
2748
+ }
2749
+ historyRestoreActiveRef.current = true;
2750
+ if (action === "undo") {
2751
+ editor.UndoManager.undo();
2752
+ } else {
2753
+ editor.UndoManager.redo();
2754
+ }
2755
+ completeHistoryRestore(editor);
2756
+ };
2757
+ const runValidation = (editor) => {
2758
+ const issues = validateBuilderV2Output({
2759
+ css: editor.getCss(),
2760
+ html: editor.getHtml()
2761
+ });
2762
+ setValidationIssues(issues);
2763
+ postToParent({ issues, type: "validation-state" });
2764
+ return issues;
2765
+ };
2766
+ const setDevice = (device) => {
2767
+ const editor = editorRef.current;
2768
+ if (!editor) {
2769
+ return;
2770
+ }
2771
+ editor.setDevice(device);
2772
+ setSelectedDevice(device);
2773
+ window.requestAnimationFrame(() => {
2774
+ editor.refresh();
2775
+ });
2776
+ };
2777
+ const syncStylePanelInput = (event) => {
2778
+ const target = event.target;
2779
+ if (!target?.closest("#orion-builder-v2-styles")) {
2780
+ return;
2781
+ }
2782
+ const activeElement = document.activeElement;
2783
+ if (!sidebarPointerActiveRef.current && !activeElement?.closest("#orion-builder-v2-styles")) {
2784
+ return;
2785
+ }
2786
+ applyLiveStylePanelValue(target);
2787
+ stylePanelEditActiveRef.current = true;
2788
+ if (!stylePanelHistoryBeforeRef.current) {
2789
+ stylePanelHistoryBeforeRef.current = snapshotComponent(getCurrentOrionBlockTarget(getCurrentStyleTarget()));
2790
+ }
2791
+ window.setTimeout(() => {
2792
+ const selected = getCurrentOrionBlockTarget(getCurrentStyleTarget());
2793
+ if (selected) {
2794
+ selectedComponentRef.current = selected;
2795
+ lastSelectedComponentRef.current = selected;
2796
+ }
2797
+ syncSelectedTypographyControlsToOrionBlock(selected);
2798
+ syncSelectedDecorationControlsToOrionBlock(selected);
2799
+ syncAllOrionBlockStylesToAttributes();
2800
+ if (selected) {
2801
+ editorRef.current?.select?.(selected);
2802
+ }
2803
+ refreshSelectedState(getCurrentStyleTarget());
2804
+ pushCustomHistoryEntry(stylePanelHistoryBeforeRef.current, snapshotComponent(selected));
2805
+ stylePanelHistoryBeforeRef.current = null;
2806
+ window.setTimeout(() => {
2807
+ stylePanelEditActiveRef.current = false;
2808
+ }, 120);
2809
+ }, 80);
2810
+ };
2811
+ const syncStylePanelChange = () => {
2812
+ stylePanelEditActiveRef.current = true;
2813
+ if (!stylePanelHistoryBeforeRef.current) {
2814
+ stylePanelHistoryBeforeRef.current = snapshotComponent(getCurrentOrionBlockTarget(getCurrentStyleTarget()));
2815
+ }
2816
+ window.setTimeout(() => {
2817
+ const selected = getCurrentOrionBlockTarget(getCurrentStyleTarget());
2818
+ if (selected) {
2819
+ selectedComponentRef.current = selected;
2820
+ lastSelectedComponentRef.current = selected;
2821
+ }
2822
+ syncSelectedTypographyControlsToOrionBlock(selected);
2823
+ syncSelectedDecorationControlsToOrionBlock(selected);
2824
+ syncAllOrionBlockStylesToAttributes();
2825
+ if (selected) {
2826
+ editorRef.current?.select?.(selected);
2827
+ }
2828
+ refreshSelectedState(getCurrentStyleTarget());
2829
+ pushCustomHistoryEntry(stylePanelHistoryBeforeRef.current, snapshotComponent(selected));
2830
+ stylePanelHistoryBeforeRef.current = null;
2831
+ window.setTimeout(() => {
2832
+ stylePanelEditActiveRef.current = false;
2833
+ }, 120);
2834
+ }, 80);
2835
+ };
2836
+ useEffect(() => {
2837
+ let active = true;
2838
+ const init = async () => {
2839
+ if (!containerRef.current || editorRef.current) {
2840
+ return;
2841
+ }
2842
+ try {
2843
+ const grapesjs = (await import("grapesjs")).default;
2844
+ if (!active || !containerRef.current) {
2845
+ return;
2846
+ }
2847
+ const projectData = normalizeBuilderV2ProjectData(initialData?.projectData, initialData?.title);
2848
+ const editor = grapesjs.init({
2849
+ assetManager: {
2850
+ uploadFile: async (event) => {
2851
+ const target = event.target;
2852
+ const dataTransfer = "dataTransfer" in event ? event.dataTransfer : null;
2853
+ const files = dataTransfer?.files || target?.files;
2854
+ if (!files || files.length === 0) {
2855
+ return void 0;
2856
+ }
2857
+ await uploadPayloadMediaAssets(editorRef.current || editor, files).catch((uploadError) => {
2858
+ setError(uploadError instanceof Error ? uploadError.message : "Could not upload image.");
2859
+ });
2860
+ }
2861
+ },
2862
+ blockManager: {
2863
+ appendTo: "#orion-builder-v2-blocks"
2864
+ },
2865
+ canvasCss: canvasSelectionCss,
2866
+ container: containerRef.current,
2867
+ deviceManager: {
2868
+ devices: [
2869
+ { id: "desktop", name: "Desktop", width: "" },
2870
+ { id: "tablet", name: "Tablet", width: "768px" },
2871
+ { id: "mobile", name: "Mobile", width: "390px" }
2872
+ ]
2873
+ },
2874
+ fromElement: false,
2875
+ height: "100%",
2876
+ panels: {
2877
+ defaults: []
2878
+ },
2879
+ selectorManager: {
2880
+ componentFirst: true
2881
+ },
2882
+ storageManager: false,
2883
+ styleManager: {
2884
+ appendTo: "#orion-builder-v2-styles",
2885
+ sectors: [
2886
+ {
2887
+ name: "Detailed layout",
2888
+ open: false,
2889
+ properties: [
2890
+ "display",
2891
+ "position",
2892
+ "top",
2893
+ "right",
2894
+ "bottom",
2895
+ "left",
2896
+ "width",
2897
+ "height",
2898
+ "min-height",
2899
+ "margin",
2900
+ "padding",
2901
+ "flex-direction",
2902
+ "justify-content",
2903
+ "align-items",
2904
+ "gap",
2905
+ "flex-wrap"
2906
+ ]
2907
+ },
2908
+ {
2909
+ name: "Typography",
2910
+ open: false,
2911
+ properties: [
2912
+ {
2913
+ name: "Font family",
2914
+ property: "font-family",
2915
+ type: "select",
2916
+ options: [
2917
+ { id: "inherit", label: "Inherit" },
2918
+ { id: "Arial, Helvetica, sans-serif", label: "Arial" },
2919
+ { id: 'Georgia, "Times New Roman", serif', label: "Georgia" },
2920
+ { id: '"Times New Roman", Times, serif', label: "Times New Roman" },
2921
+ { id: 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', label: "Inter/System" },
2922
+ { id: '"Caveat", "Brush Script MT", cursive', label: "Script" }
2923
+ ]
2924
+ },
2925
+ {
2926
+ name: "Font size",
2927
+ property: "font-size",
2928
+ type: "number",
2929
+ units: ["pt", "px", "rem", "em", "%"]
2930
+ },
2931
+ "font-weight",
2932
+ {
2933
+ name: "Line height",
2934
+ property: "line-height",
2935
+ type: "number",
2936
+ units: ["pt", "px", "rem", "em", "%", ""]
2937
+ },
2938
+ {
2939
+ name: "Letter spacing",
2940
+ property: "letter-spacing",
2941
+ type: "number",
2942
+ units: ["pt", "px", "em", "rem"]
2943
+ },
2944
+ "color",
2945
+ "text-align"
2946
+ ]
2947
+ },
2948
+ {
2949
+ name: "Decoration",
2950
+ open: false,
2951
+ properties: ["background-color", "background", "border", "border-radius", "box-shadow", "opacity"]
2952
+ }
2953
+ ]
2954
+ },
2955
+ traitManager: {
2956
+ appendTo: "#orion-builder-v2-traits"
2957
+ },
2958
+ width: "auto"
2959
+ });
2960
+ editorRef.current = editor;
2961
+ if (typeof window !== "undefined" && window.location.hostname === "localhost") {
2962
+ ;
2963
+ window.__orionBuilderV2Editor = editor;
2964
+ }
2965
+ const hasProjectBlocks = Object.keys(adapter?.components || {}).length > 0;
2966
+ if (!hasProjectBlocks) {
2967
+ registerOrionBuilderV2Blocks(editor);
2968
+ }
2969
+ registerProjectDynamicComponents(editor, adapter);
2970
+ editor.loadProjectData(projectData);
2971
+ void loadPayloadMediaAssets(editor);
2972
+ editor.on("update", () => {
2973
+ const hasDirtyChanges = editor.getDirtyCount() > 0;
2974
+ refreshSelectedState(selectedComponentRef.current);
2975
+ setIsDirty(hasDirtyChanges);
2976
+ postToParent({ dirty: hasDirtyChanges, type: "dirty-state" });
2977
+ updateHistoryState(editor);
2978
+ runValidation(editor);
2979
+ setSaveMessage("Unsaved changes");
2980
+ if (autosaveTimerRef.current) {
2981
+ window.clearTimeout(autosaveTimerRef.current);
2982
+ }
2983
+ autosaveTimerRef.current = window.setTimeout(() => {
2984
+ if (editor.getDirtyCount() > 0) {
2985
+ void saveRef.current("draft", { autosave: true });
2986
+ }
2987
+ }, autosaveIntervalMs);
2988
+ });
2989
+ editor.on("orion:component-history", (entry) => {
2990
+ if (!entry || typeof entry !== "object" || !("component" in entry) || !("before" in entry) || !("after" in entry)) {
2991
+ return;
2992
+ }
2993
+ const typedEntry = entry;
2994
+ if (!typedEntry.component || !typedEntry.before || !typedEntry.after) {
2995
+ return;
2996
+ }
2997
+ pushCustomHistoryEntry(
2998
+ {
2999
+ attributes: { ...typedEntry.before.attributes || {} },
3000
+ component: typedEntry.component,
3001
+ style: { ...typedEntry.before.style || {} }
3002
+ },
3003
+ {
3004
+ attributes: { ...typedEntry.after.attributes || {} },
3005
+ component: typedEntry.component,
3006
+ style: { ...typedEntry.after.style || {} }
3007
+ }
3008
+ );
3009
+ });
3010
+ editor.on("component:add", (component) => {
3011
+ if (historyRestoreActiveRef.current) {
3012
+ return;
3013
+ }
3014
+ const typed = component;
3015
+ if (!isOrionBlockComponent(typed)) {
3016
+ return;
3017
+ }
3018
+ window.setTimeout(() => {
3019
+ if (historyRestoreActiveRef.current) {
3020
+ return;
3021
+ }
3022
+ pushCustomHistoryEntry(null, snapshotComponent(typed));
3023
+ }, 0);
3024
+ });
3025
+ editor.on("component:selected", (component) => {
3026
+ const typed = component;
3027
+ const target = getCurrentOrionBlockTarget(typed) || typed;
3028
+ selectedComponentRef.current = target;
3029
+ lastSelectedComponentRef.current = target;
3030
+ if (target !== typed) {
3031
+ editor.select?.(target);
3032
+ }
3033
+ selectStyleManagerTarget(target);
3034
+ markSelectedCanvasBlock(target);
3035
+ rehydrateSelectedComponentControls(target);
3036
+ window.setTimeout(() => {
3037
+ selectStyleManagerTarget(target);
3038
+ markSelectedCanvasBlock(target);
3039
+ rehydrateSelectedComponentControls(target);
3040
+ }, 120);
3041
+ });
3042
+ editor.on("component:update:attributes", (component) => {
3043
+ if (historyRestoreActiveRef.current) {
3044
+ return;
3045
+ }
3046
+ rehydrateSelectedComponentControls(component);
3047
+ });
3048
+ editor.on("component:deselected", () => {
3049
+ const activeElement = document.activeElement;
3050
+ if (sidebarPointerActiveRef.current || activeElement?.closest("#orion-builder-v2-styles, #orion-builder-v2-traits, .orion-builder-v2-sidebar")) {
3051
+ window.requestAnimationFrame(() => {
3052
+ const selected = selectedComponentRef.current;
3053
+ if (selected) {
3054
+ editor.select?.(selected);
3055
+ }
3056
+ refreshSelectedState(selected);
3057
+ });
3058
+ return;
3059
+ }
3060
+ selectedComponentRef.current = null;
3061
+ markSelectedCanvasBlock(null);
3062
+ refreshSelectedState(null);
3063
+ });
3064
+ const bindCanvasSelectionTracking = () => {
3065
+ const canvasDocument = editor.Canvas?.getDocument?.();
3066
+ if (!canvasDocument) {
3067
+ return;
3068
+ }
3069
+ const trackCanvasSelection = (event) => {
3070
+ const target = findOrionBlockByElement(event.target);
3071
+ if (!target) {
3072
+ return;
3073
+ }
3074
+ selectedComponentRef.current = target;
3075
+ lastSelectedComponentRef.current = target;
3076
+ rememberComponentSnapshot(target);
3077
+ editor.select?.(target);
3078
+ selectStyleManagerTarget(target);
3079
+ markSelectedCanvasBlock(target);
3080
+ refreshSelectedState(target);
3081
+ };
3082
+ canvasDocument.addEventListener("pointerdown", trackCanvasSelection, true);
3083
+ canvasDocument.addEventListener("click", trackCanvasSelection, true);
3084
+ };
3085
+ bindCanvasSelectionTracking();
3086
+ editor.on("canvas:frame:load", bindCanvasSelectionTracking);
3087
+ editor.on("style:property:update", () => {
3088
+ const selected = getCurrentStyleTarget();
3089
+ if (historyRestoreActiveRef.current) {
3090
+ refreshSelectedState(selected);
3091
+ return;
3092
+ }
3093
+ const historyTarget = getCurrentOrionBlockTarget(selected);
3094
+ const before = getPreviousComponentSnapshot(historyTarget);
3095
+ syncSelectedTypographyStyleToOrionBlock(selected);
3096
+ syncSelectedDecorationStyleToOrionBlock(selected);
3097
+ if (stylePanelEditActiveRef.current) {
3098
+ syncSelectedDecorationControlsToOrionBlock(selected);
3099
+ }
3100
+ window.setTimeout(() => {
3101
+ syncAllOrionBlockStylesToAttributes();
3102
+ pushCustomHistoryEntry(before, snapshotComponent(historyTarget));
3103
+ }, 80);
3104
+ refreshSelectedState(selected);
3105
+ });
3106
+ editor.on("component:styleUpdate:background-color", () => {
3107
+ window.setTimeout(() => {
3108
+ if (historyRestoreActiveRef.current) {
3109
+ return;
3110
+ }
3111
+ syncAllOrionBlockStylesToAttributes();
3112
+ }, 160);
3113
+ });
3114
+ editor.on("style:target", () => {
3115
+ const selected = getCurrentOrionBlockTarget(getCurrentStyleTarget());
3116
+ if (selected) {
3117
+ selectedComponentRef.current = selected;
3118
+ lastSelectedComponentRef.current = selected;
3119
+ window.setTimeout(() => {
3120
+ if (historyRestoreActiveRef.current) {
3121
+ return;
3122
+ }
3123
+ selectStyleManagerTarget(selected);
3124
+ refreshSelectedState(selected);
3125
+ }, 160);
3126
+ }
3127
+ });
3128
+ setSelectedDevice(editor.getDevice() || "desktop");
3129
+ runValidation(editor);
3130
+ updateHistoryState(editor);
3131
+ decorateBuilderControls();
3132
+ setLoading(false);
3133
+ } catch (initError) {
3134
+ setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
3135
+ setLoading(false);
3136
+ }
3137
+ };
3138
+ void init();
3139
+ return () => {
3140
+ active = false;
3141
+ if (autosaveTimerRef.current) {
3142
+ window.clearTimeout(autosaveTimerRef.current);
3143
+ }
3144
+ editorRef.current?.destroy();
3145
+ editorRef.current = null;
3146
+ };
3147
+ }, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
3148
+ const save = async (status, options = {}) => {
3149
+ const editor = editorRef.current;
3150
+ if (!editor || saving) {
3151
+ return;
3152
+ }
3153
+ const issues = runValidation(editor);
3154
+ if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
3155
+ const message = "Resolve blocking validation errors before publishing.";
3156
+ setSaveMessage(message);
3157
+ postToParent({
3158
+ message,
3159
+ ok: false,
3160
+ status,
3161
+ type: "save-result"
3162
+ });
3163
+ return;
3164
+ }
3165
+ setSaving(status);
3166
+ try {
3167
+ syncAllOrionBlockStylesToAttributes();
3168
+ const projectData = editor.getProjectData();
3169
+ const payload = buildSavePayload(editor, status, projectData);
3170
+ const dynamicComponents = parseBuilderV2DynamicComponents(String(payload.compiledHtml || ""));
3171
+ const endpoint = status === "draft" ? `/api/pages/${pageID}?draft=true` : `/api/pages/${pageID}`;
3172
+ const response = await fetch(endpoint, {
3173
+ body: JSON.stringify(
3174
+ status === "published" ? {
3175
+ _status: "published",
3176
+ builderDynamicComponents: dynamicComponents,
3177
+ builderMode: "grapes-v2",
3178
+ builderProjectData: projectData,
3179
+ builderPublishedProjectData: projectData,
3180
+ builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
3181
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
3182
+ builderValidationIssues: issues,
3183
+ compiledCss: payload.compiledCss,
3184
+ compiledHtml: payload.compiledHtml
3185
+ } : {
3186
+ _status: "draft",
3187
+ builderAutosaveProjectData: options.autosave ? projectData : null,
3188
+ builderDynamicComponents: dynamicComponents,
3189
+ builderMode: "grapes-v2",
3190
+ builderProjectData: projectData,
3191
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
3192
+ builderValidationIssues: issues,
3193
+ compiledCss: payload.compiledCss,
3194
+ compiledHtml: payload.compiledHtml
3195
+ }
3196
+ ),
3197
+ credentials: "include",
3198
+ headers: {
3199
+ "Content-Type": "application/json"
3200
+ },
3201
+ method: "PATCH"
3202
+ });
3203
+ if (!response.ok) {
3204
+ if (response.status === 401 || response.status === 403) {
3205
+ postToParent({ type: "session-expired" });
3206
+ }
3207
+ throw new Error(await parsePayloadErrorMessage(response, "Could not save this page."));
3208
+ }
3209
+ editor.clearDirtyCount();
3210
+ setIsDirty(false);
3211
+ setPublicationState(status === "published" ? "live" : "draft");
3212
+ postToParent({
3213
+ dirty: false,
3214
+ type: "dirty-state"
3215
+ });
3216
+ postToParent({
3217
+ message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
3218
+ ok: true,
3219
+ status,
3220
+ type: "save-result"
3221
+ });
3222
+ setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
3223
+ setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
3224
+ } catch (saveError) {
3225
+ const message = saveError instanceof Error ? saveError.message : "Could not save.";
3226
+ setSaveMessage(message);
3227
+ postToParent({
3228
+ message,
3229
+ ok: false,
3230
+ status,
3231
+ type: "save-result"
3232
+ });
3233
+ } finally {
3234
+ setSaving(null);
3235
+ }
3236
+ };
3237
+ useEffect(() => {
3238
+ saveRef.current = save;
3239
+ }, [saving]);
3240
+ useEffect(() => {
3241
+ decorateBuilderControls();
3242
+ const refreshControls = () => refreshSelectedState(selectedComponentRef.current);
3243
+ const onStylePanelInput = (event) => {
3244
+ const target = event.target;
3245
+ if (target?.closest("#orion-builder-v2-styles")) {
3246
+ const activeElement = document.activeElement;
3247
+ if (!sidebarPointerActiveRef.current && !activeElement?.closest("#orion-builder-v2-styles")) {
3248
+ return;
3249
+ }
3250
+ applyLiveStylePanelValue(target);
3251
+ syncStylePanelChange();
3252
+ }
3253
+ };
3254
+ const onDocumentClick = async (event) => {
3255
+ const target = event.target;
3256
+ const helpTrigger = target?.closest(".orion-builder-v2-help-trigger");
3257
+ if (helpTrigger) {
3258
+ event.preventDefault();
3259
+ event.stopPropagation();
3260
+ const wrapper = helpTrigger.parentElement;
3261
+ const willOpen = !wrapper?.classList.contains("is-builder-help-open");
3262
+ closeBuilderHelpPopovers(wrapper);
3263
+ openBuilderHelpPopover(helpTrigger, wrapper, willOpen);
3264
+ return;
3265
+ }
3266
+ const eyeDropperButton = target?.closest('[data-orion-color-eyedropper="true"]');
3267
+ if (eyeDropperButton) {
3268
+ event.preventDefault();
3269
+ event.stopPropagation();
3270
+ const input = findColorInputForControl(eyeDropperButton);
3271
+ if (input) {
3272
+ try {
3273
+ await openScreenColorPickerForInput(input);
3274
+ refreshControls();
3275
+ } catch {
3276
+ }
3277
+ }
3278
+ return;
3279
+ }
3280
+ closeBuilderHelpPopovers();
3281
+ const colorAnchor = target?.closest(".gjs-field-colorp, .gjs-field-colorp-c, .gjs-field-color-picker");
3282
+ if (colorAnchor) {
3283
+ colorPickerAnchorRef.current = colorAnchor.closest(".gjs-field-colorp") || colorAnchor;
3284
+ positionColorPickerFromAnchor(colorPickerAnchorRef.current);
3285
+ window.setTimeout(() => positionColorPickerFromAnchor(colorPickerAnchorRef.current), 80);
3286
+ }
3287
+ };
3288
+ const onDocumentKeydown = (event) => {
3289
+ const target = event.target;
3290
+ const helpTrigger = target?.closest(".orion-builder-v2-help-trigger");
3291
+ if (!helpTrigger || event.key !== "Enter" && event.key !== " ") {
3292
+ return;
3293
+ }
3294
+ event.preventDefault();
3295
+ const wrapper = helpTrigger.parentElement;
3296
+ const willOpen = !wrapper?.classList.contains("is-builder-help-open");
3297
+ closeBuilderHelpPopovers(wrapper);
3298
+ openBuilderHelpPopover(helpTrigger, wrapper, willOpen);
3299
+ };
3300
+ const observer = new MutationObserver(() => {
3301
+ decorateBuilderControls();
3302
+ });
3303
+ observer.observe(document.body, {
3304
+ childList: true,
3305
+ subtree: true
3306
+ });
3307
+ document.addEventListener("change", refreshControls);
3308
+ document.addEventListener("change", onStylePanelInput, true);
3309
+ document.addEventListener("click", onDocumentClick, true);
3310
+ document.addEventListener("input", refreshControls);
3311
+ document.addEventListener("input", onStylePanelInput, true);
3312
+ document.addEventListener("keydown", onDocumentKeydown, true);
3313
+ return () => {
3314
+ observer.disconnect();
3315
+ document.removeEventListener("change", refreshControls);
3316
+ document.removeEventListener("change", onStylePanelInput, true);
3317
+ document.removeEventListener("click", onDocumentClick, true);
3318
+ document.removeEventListener("input", refreshControls);
3319
+ document.removeEventListener("input", onStylePanelInput, true);
3320
+ document.removeEventListener("keydown", onDocumentKeydown, true);
3321
+ };
3322
+ }, []);
3323
+ useEffect(() => {
3324
+ const onMessage = (event) => {
3325
+ const data = event.data;
3326
+ const editor = editorRef.current;
3327
+ if (!data || data.source !== "payload-visual-builder-parent" || !editor) {
3328
+ return;
3329
+ }
3330
+ if (data.type === "dirty-check-request") {
3331
+ postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
3332
+ updateHistoryState(editor);
3333
+ return;
3334
+ }
3335
+ if (data.type === "history-check-request") {
3336
+ updateHistoryState(editor);
3337
+ return;
3338
+ }
3339
+ if (data.type === "undo") {
3340
+ runHistoryAction("undo");
3341
+ return;
3342
+ }
3343
+ if (data.type === "redo") {
3344
+ runHistoryAction("redo");
3345
+ return;
3346
+ }
3347
+ if (data.type === "save" && (data.status === "draft" || data.status === "published")) {
3348
+ void save(data.status);
3349
+ }
3350
+ };
3351
+ window.addEventListener("message", onMessage);
3352
+ return () => window.removeEventListener("message", onMessage);
3353
+ }, [saving]);
3354
+ const statusLabel = saving ? saving === "published" ? "Publishing..." : "Saving draft..." : isDirty ? "Unsaved changes" : saveMessage && saveMessage !== "Unsaved changes" ? saveMessage : "All changes saved";
3355
+ const statusMeta = lastSavedAt ? `Last saved ${lastSavedAt}` : "Ready";
3356
+ const liveStatusLabel = isDirty ? "Save draft to update" : publicationState === "draft" ? "Unpublished draft changes" : "Live is up to date";
3357
+ const renderInspectorPanel = (panel, title, children) => {
3358
+ const isOpen = Boolean(openInspectorPanels[panel]);
3359
+ const panelContentID = `orion-builder-v2-inspector-panel-${panel}`;
3360
+ return /* @__PURE__ */ jsxs("div", { className: `orion-builder-v2-panel orion-builder-v2-collapsible-panel ${isOpen ? "is-open" : "is-collapsed"}`, children: [
3361
+ /* @__PURE__ */ jsxs(
3362
+ "button",
3363
+ {
3364
+ "aria-controls": panelContentID,
3365
+ "aria-expanded": isOpen,
3366
+ className: "orion-builder-v2-panel-toggle",
3367
+ onClick: () => toggleInspectorPanel(panel),
3368
+ type: "button",
3369
+ children: [
3370
+ /* @__PURE__ */ jsx("span", { children: title }),
3371
+ /* @__PURE__ */ jsx("span", { className: "orion-builder-v2-panel-toggle-icon", "aria-hidden": "true", children: isOpen ? "\u2212" : "+" })
3372
+ ]
3373
+ }
3374
+ ),
3375
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-panel-body", hidden: !isOpen, id: panelContentID, children })
3376
+ ] });
3377
+ };
3378
+ const renderSidebarPanel = (panel, title, children) => {
3379
+ const isOpen = Boolean(openSidebarPanels[panel]);
3380
+ const panelContentID = `orion-builder-v2-sidebar-panel-${panel}`;
3381
+ return /* @__PURE__ */ jsxs("div", { className: `orion-builder-v2-panel orion-builder-v2-collapsible-panel ${isOpen ? "is-open" : "is-collapsed"}`, children: [
3382
+ /* @__PURE__ */ jsxs(
3383
+ "button",
3384
+ {
3385
+ "aria-controls": panelContentID,
3386
+ "aria-expanded": isOpen,
3387
+ className: "orion-builder-v2-panel-toggle",
3388
+ onClick: () => toggleSidebarPanel(panel),
3389
+ type: "button",
3390
+ children: [
3391
+ /* @__PURE__ */ jsx("span", { children: title }),
3392
+ /* @__PURE__ */ jsx("span", { className: "orion-builder-v2-panel-toggle-icon", "aria-hidden": "true", children: isOpen ? "\u2212" : "+" })
3393
+ ]
3394
+ }
3395
+ ),
3396
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-panel-body", hidden: !isOpen, id: panelContentID, children })
3397
+ ] });
3398
+ };
3399
+ return /* @__PURE__ */ jsxs(
3400
+ "div",
3401
+ {
3402
+ className: `orion-builder-v2-editor ${detailedLayoutClassNames(detailedLayoutControlState)}`,
3403
+ onPointerDownCapture: (event) => {
3404
+ const target = event.target;
3405
+ sidebarPointerActiveRef.current = Boolean(
3406
+ target?.closest("#orion-builder-v2-styles, #orion-builder-v2-traits, .orion-builder-v2-sidebar, .orion-builder-v2-inspector")
3407
+ );
3408
+ },
3409
+ onPointerUpCapture: () => {
3410
+ window.setTimeout(() => {
3411
+ sidebarPointerActiveRef.current = false;
3412
+ }, 0);
3413
+ },
3414
+ onChangeCapture: syncStylePanelInput,
3415
+ onInputCapture: syncStylePanelInput,
3416
+ children: [
3417
+ /* @__PURE__ */ jsxs("header", { className: "orion-builder-v2-topbar", children: [
3418
+ /* @__PURE__ */ jsxs("div", { children: [
3419
+ /* @__PURE__ */ jsx("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
3420
+ /* @__PURE__ */ jsx("h1", { children: initialData?.title || "Untitled page" })
3421
+ ] }),
3422
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
3423
+ /* @__PURE__ */ jsxs("div", { className: `orion-builder-v2-sync is-${isDirty ? "dirty" : publicationState}`, children: [
3424
+ /* @__PURE__ */ jsx("strong", { children: statusLabel }),
3425
+ /* @__PURE__ */ jsx("span", { children: liveStatusLabel }),
3426
+ /* @__PURE__ */ jsx("small", { children: statusMeta })
3427
+ ] }),
3428
+ ["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ jsx(
3429
+ "button",
3430
+ {
3431
+ "aria-pressed": selectedDevice === device,
3432
+ className: "orion-builder-v2-tool",
3433
+ onClick: () => setDevice(device),
3434
+ type: "button",
3435
+ children: device
3436
+ },
3437
+ device
3438
+ )),
3439
+ /* @__PURE__ */ jsx(
3440
+ "button",
3441
+ {
3442
+ className: "orion-builder-v2-tool",
3443
+ disabled: !historyState.canUndo,
3444
+ onClick: () => {
3445
+ runHistoryAction("undo");
3446
+ },
3447
+ type: "button",
3448
+ children: "Undo"
3449
+ }
3450
+ ),
3451
+ /* @__PURE__ */ jsx(
3452
+ "button",
3453
+ {
3454
+ className: "orion-builder-v2-tool",
3455
+ disabled: !historyState.canRedo,
3456
+ onClick: () => {
3457
+ runHistoryAction("redo");
3458
+ },
3459
+ type: "button",
3460
+ children: "Redo"
3461
+ }
3462
+ ),
3463
+ /* @__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" }),
3464
+ /* @__PURE__ */ jsx("button", { className: "orion-builder-v2-tool is-publish", disabled: Boolean(saving), onClick: () => void save("published"), type: "button", children: saving === "published" ? "Publishing..." : "Publish" })
3465
+ ] })
3466
+ ] }),
3467
+ /* @__PURE__ */ jsxs("aside", { className: "orion-builder-v2-sidebar", children: [
3468
+ renderSidebarPanel("pages", "Pages", /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-page-tree", children: pageTree.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No pages loaded." }) : pageTree.map((page) => /* @__PURE__ */ jsx(
3469
+ "a",
3470
+ {
3471
+ className: "orion-builder-v2-page-link",
3472
+ href: `${editorPageBasePath}/${page.id}`,
3473
+ onClick: (event) => {
3474
+ const target = `${editorPageBasePath}/${page.id}`;
3475
+ if (window.parent && window.parent !== window) {
3476
+ event.preventDefault();
3477
+ window.parent.location.href = target;
3478
+ }
3479
+ },
3480
+ children: /* @__PURE__ */ jsx("span", { children: page.title })
3481
+ },
3482
+ page.id
3483
+ )) })),
3484
+ renderSidebarPanel("blocks", "Blocks", /* @__PURE__ */ jsxs(Fragment, { children: [
3485
+ /* @__PURE__ */ jsx("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
3486
+ /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-blocks" })
3487
+ ] })),
3488
+ renderSidebarPanel("validation", "Validation", /* @__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: [
3489
+ /* @__PURE__ */ jsx("strong", { children: issue.message }),
3490
+ /* @__PURE__ */ jsx("span", { children: issue.severity })
3491
+ ] }, `${issue.code}-${issue.path}`)) }))
3492
+ ] }),
3493
+ /* @__PURE__ */ jsxs("main", { className: "orion-builder-v2-main", children: [
3494
+ loading ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
3495
+ error ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-error", children: error }) : null,
3496
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-canvas", ref: containerRef })
3497
+ ] }),
3498
+ /* @__PURE__ */ jsxs("aside", { className: "orion-builder-v2-inspector", "aria-label": "Selected section settings", children: [
3499
+ renderInspectorPanel("section", selectionSummary ? selectionSummary.label : "No section selected", /* @__PURE__ */ jsxs(Fragment, { children: [
3500
+ /* @__PURE__ */ jsx("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
3501
+ /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
3502
+ ] })),
3503
+ selectionSummary?.items ? renderInspectorPanel("items", "Items", /* @__PURE__ */ jsxs(Fragment, { children: [
3504
+ /* @__PURE__ */ jsx("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
3505
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-item-row", children: [
3506
+ /* @__PURE__ */ jsxs("label", { className: "orion-builder-v2-item-title", children: [
3507
+ /* @__PURE__ */ jsx("span", { children: "Title" }),
3508
+ /* @__PURE__ */ jsx(
3509
+ "input",
3510
+ {
3511
+ "aria-label": `Item ${item.index + 1} title`,
3512
+ onChange: (event) => {
3513
+ const title = event.target.value;
3514
+ updateSelectedItems(
3515
+ (items) => items.map(
3516
+ (currentItem, index) => index === item.index ? {
3517
+ ...currentItem,
3518
+ title
3519
+ } : currentItem
3520
+ )
3521
+ );
3522
+ },
3523
+ type: "text",
3524
+ value: item.title
3525
+ }
3526
+ )
3527
+ ] }),
3528
+ /* @__PURE__ */ jsx(
3529
+ "button",
3530
+ {
3531
+ onClick: () => {
3532
+ updateSelectedItems((items) => {
3533
+ const source = items[item.index];
3534
+ if (!source) {
3535
+ return items;
3536
+ }
3537
+ const copy = [...items];
3538
+ copy.splice(item.index + 1, 0, {
3539
+ ...source,
3540
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
3541
+ });
3542
+ return copy;
3543
+ });
3544
+ },
3545
+ type: "button",
3546
+ children: "Duplicate"
3547
+ }
3548
+ ),
3549
+ /* @__PURE__ */ jsx(
3550
+ "button",
3551
+ {
3552
+ onClick: () => {
3553
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
3554
+ },
3555
+ type: "button",
3556
+ children: "Remove"
3557
+ }
3558
+ )
3559
+ ] }, `${item.index}-${item.title}`)) }),
3560
+ /* @__PURE__ */ jsx(
3561
+ "button",
3562
+ {
3563
+ className: "orion-builder-v2-wide-action",
3564
+ onClick: () => {
3565
+ updateSelectedItems((items) => [
3566
+ ...items,
3567
+ {
3568
+ description: "Describe this feature.",
3569
+ imageURL: "",
3570
+ title: "New feature"
3571
+ }
3572
+ ]);
3573
+ },
3574
+ type: "button",
3575
+ children: "Add item"
3576
+ }
3577
+ )
3578
+ ] })) : null,
3579
+ renderInspectorPanel("design", "Design", /* @__PURE__ */ jsxs(Fragment, { children: [
3580
+ /* @__PURE__ */ jsx("p", { children: "Use quick controls for common layout changes, then fine tune exact values in Detailed design below." }),
3581
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-quick-layout", "aria-label": "Quick layout controls", children: [
3582
+ availableQuickLayoutOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-control-group", children: [
3583
+ /* @__PURE__ */ jsxs("h3", { children: [
3584
+ "Layout",
3585
+ /* @__PURE__ */ jsx(HelpTrigger, { help: "Choose a common arrangement for the selected section, such as stacked, row, columns, centered, or overlay.", label: "Layout" })
3586
+ ] }),
3587
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-choice-grid", children: availableQuickLayoutOptions.map((option) => /* @__PURE__ */ jsxs(
3588
+ "button",
3589
+ {
3590
+ "aria-pressed": quickLayoutState.layout === option.value,
3591
+ className: "orion-builder-v2-choice",
3592
+ disabled: !selectionSummary,
3593
+ onClick: () => applyQuickLayout(option.value),
3594
+ type: "button",
3595
+ children: [
3596
+ /* @__PURE__ */ jsxs("strong", { children: [
3597
+ option.label,
3598
+ /* @__PURE__ */ jsx(HelpTrigger, { help: option.description, label: option.label })
3599
+ ] }),
3600
+ /* @__PURE__ */ jsx("span", { children: option.description })
3601
+ ]
3602
+ },
3603
+ option.value
3604
+ )) })
3605
+ ] }) : null,
3606
+ quickDesignControls.alignment ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-control-group", children: [
3607
+ /* @__PURE__ */ jsxs("h3", { children: [
3608
+ "Alignment",
3609
+ /* @__PURE__ */ jsx(HelpTrigger, { help: "Control where content sits inside the selected section horizontally and vertically.", label: "Alignment" })
3610
+ ] }),
3611
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-segmented", role: "group", "aria-label": "Horizontal alignment", children: horizontalAlignOptions.map((option) => /* @__PURE__ */ jsxs(
3612
+ "button",
3613
+ {
3614
+ "aria-pressed": quickLayoutState.horizontalAlign === option.value,
3615
+ disabled: !selectionSummary,
3616
+ onClick: () => applyQuickHorizontalAlign(option.value),
3617
+ type: "button",
3618
+ children: [
3619
+ option.label,
3620
+ /* @__PURE__ */ jsx(HelpTrigger, { help: `${option.label} aligns content horizontally within the selected section.`, label: option.label })
3621
+ ]
3622
+ },
3623
+ option.value
3624
+ )) }),
3625
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-segmented", role: "group", "aria-label": "Vertical alignment", children: verticalAlignOptions.map((option) => /* @__PURE__ */ jsxs(
3626
+ "button",
3627
+ {
3628
+ "aria-pressed": quickLayoutState.verticalAlign === option.value,
3629
+ disabled: !selectionSummary,
3630
+ onClick: () => applyQuickVerticalAlign(option.value),
3631
+ type: "button",
3632
+ children: [
3633
+ option.label,
3634
+ /* @__PURE__ */ jsx(HelpTrigger, { help: `${option.label} aligns content vertically within the selected section.`, label: option.label })
3635
+ ]
3636
+ },
3637
+ option.value
3638
+ )) })
3639
+ ] }) : null,
3640
+ quickDesignControls.spacing ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-control-group", children: [
3641
+ /* @__PURE__ */ jsxs("h3", { children: [
3642
+ "Spacing",
3643
+ /* @__PURE__ */ jsx(HelpTrigger, { help: "Set a quick inside-padding preset for the selected section.", label: "Spacing" })
3644
+ ] }),
3645
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-segmented", role: "group", "aria-label": "Section spacing", children: quickSpacingOptions.map((option) => /* @__PURE__ */ jsxs(
3646
+ "button",
3647
+ {
3648
+ "aria-pressed": quickLayoutState.spacing === option.value,
3649
+ disabled: !selectionSummary,
3650
+ onClick: () => applyQuickSpacing(option.value),
3651
+ type: "button",
3652
+ children: [
3653
+ option.label,
3654
+ /* @__PURE__ */ jsx(HelpTrigger, { help: `${option.label} spacing changes the selected section's inside padding.`, label: option.label })
3655
+ ]
3656
+ },
3657
+ option.value
3658
+ )) })
3659
+ ] }) : null,
3660
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-toggle-list", children: [
3661
+ /* @__PURE__ */ jsx(
3662
+ "button",
3663
+ {
3664
+ "aria-pressed": quickLayoutState.sticky,
3665
+ className: "orion-builder-v2-toggle",
3666
+ disabled: !selectionSummary,
3667
+ onClick: toggleSticky,
3668
+ type: "button",
3669
+ children: /* @__PURE__ */ jsxs("span", { children: [
3670
+ /* @__PURE__ */ jsxs("strong", { children: [
3671
+ "Sticky section",
3672
+ /* @__PURE__ */ jsx(HelpTrigger, { help: "Keep this selected section visible while visitors scroll past it.", label: "Sticky section" })
3673
+ ] }),
3674
+ /* @__PURE__ */ jsx("small", { children: "Keep this section pinned while scrolling." })
3675
+ ] })
3676
+ }
3677
+ ),
3678
+ /* @__PURE__ */ jsx(
3679
+ "button",
3680
+ {
3681
+ "aria-pressed": quickLayoutState.hidden,
3682
+ className: "orion-builder-v2-toggle",
3683
+ disabled: !selectionSummary,
3684
+ onClick: toggleHidden,
3685
+ type: "button",
3686
+ children: /* @__PURE__ */ jsxs("span", { children: [
3687
+ /* @__PURE__ */ jsxs("strong", { children: [
3688
+ "Hide section",
3689
+ /* @__PURE__ */ jsx(HelpTrigger, { help: "Hide this selected section on the page without deleting its content.", label: "Hide section" })
3690
+ ] }),
3691
+ /* @__PURE__ */ jsx("small", { children: "Keep it saved but do not show it on the page." })
3692
+ ] })
3693
+ }
3694
+ )
3695
+ ] })
3696
+ ] })
3697
+ ] })),
3698
+ renderInspectorPanel("detailed-design", "Detailed design", /* @__PURE__ */ jsxs(Fragment, { children: [
3699
+ /* @__PURE__ */ jsxs("p", { children: [
3700
+ "Fine tune exact spacing, color, typography, borders, size, and layout values for the selected element.",
3701
+ canUseDeveloperControls ? " Developer access is active." : ""
3702
+ ] }),
3703
+ /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-styles" })
3704
+ ] }))
3705
+ ] })
3706
+ ]
3707
+ }
3708
+ );
3709
+ }
3710
+ export {
3711
+ GrapesPageEditor
3712
+ };