@ohhwells/bridge 0.1.64-next.176 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,7 +46,6 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
- EmptySection: () => EmptySection,
50
49
  ItemActionToolbar: () => ItemActionToolbar,
51
50
  ItemInteractionLayer: () => ItemInteractionLayer,
52
51
  LinkEditorPanel: () => LinkEditorPanel,
@@ -170,7 +169,6 @@ function applyTreeToState(state, payload) {
170
169
  const entry = {
171
170
  id: payload.id,
172
171
  label: payload.label ?? "Generated section",
173
- ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
174
172
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
175
173
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
176
174
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -193,317 +191,6 @@ function deleteSectionFromState(state, sectionId) {
193
191
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
194
192
  }
195
193
 
196
- // src/lib/brand-chrome.ts
197
- var BRAND_NAME_KEY = "__ohw_brand_name";
198
- var BRAND_TITLE_KEY = "__ohw_site_title";
199
- var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
200
- var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
201
- BRAND_NAME_KEY,
202
- BRAND_TITLE_KEY,
203
- BRAND_FAVICON_LETTER_KEY
204
- ]);
205
- function upsertMeta(selector, attr, token, value) {
206
- let el = document.head.querySelector(selector);
207
- if (!el) {
208
- el = document.createElement("meta");
209
- el.setAttribute(attr, token);
210
- document.head.appendChild(el);
211
- }
212
- if (el.getAttribute("content") !== value) el.setAttribute("content", value);
213
- }
214
- function escapeXml(value) {
215
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
216
- }
217
- function applyLetterFavicon(letter) {
218
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#111827"/><text x="32" y="46" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-size="40" font-weight="700" text-anchor="middle" fill="#ffffff">${escapeXml(letter)}</text></svg>`;
219
- const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
220
- let link = document.head.querySelector('link[rel="icon"]');
221
- if (!link) {
222
- link = document.createElement("link");
223
- link.rel = "icon";
224
- document.head.appendChild(link);
225
- }
226
- link.type = "image/svg+xml";
227
- if (link.href !== href) link.href = href;
228
- }
229
- function applyBrandChrome(content) {
230
- const name = content[BRAND_NAME_KEY];
231
- if (typeof name === "string" && name.length > 0) {
232
- document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
233
- if (el.textContent !== name) el.textContent = name;
234
- if (el.getAttribute("title") !== name) el.setAttribute("title", name);
235
- });
236
- }
237
- const title = content[BRAND_TITLE_KEY];
238
- if (typeof title === "string" && title.length > 0) {
239
- if (document.title !== title) document.title = title;
240
- upsertMeta('meta[property="og:title"]', "property", "og:title", title);
241
- upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
242
- }
243
- const letter = content[BRAND_FAVICON_LETTER_KEY];
244
- if (typeof letter === "string" && letter.length > 0) {
245
- applyLetterFavicon(letter);
246
- }
247
- }
248
-
249
- // src/lib/brand-kit.ts
250
- var BRAND_KIT_KEY = "__ohw_brand";
251
- var BRAND_VAR_PREFIX = "--ohw-brand-";
252
- var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
253
- (role) => `${BRAND_VAR_PREFIX}${role}`
254
- );
255
- var FONT_VARS = {
256
- heading: ["--font-heading", "--font-display", "--brand-font-heading"],
257
- body: ["--font-body", "--brand-font-body"]
258
- };
259
- var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
260
- function brandColorVars(kit) {
261
- const { dark, primary, accent, light } = kit.palette;
262
- const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
263
- return {
264
- [`${BRAND_VAR_PREFIX}primary`]: primary,
265
- [`${BRAND_VAR_PREFIX}accent`]: accent,
266
- [`${BRAND_VAR_PREFIX}light`]: light,
267
- [`${BRAND_VAR_PREFIX}dark`]: dark,
268
- [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
269
- [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
270
- [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
271
- };
272
- }
273
- function parseBrandKit(raw) {
274
- if (!raw) return null;
275
- try {
276
- const parsed = JSON.parse(raw);
277
- const p = parsed?.palette;
278
- const f = parsed?.fonts;
279
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
280
- return null;
281
- }
282
- return {
283
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
284
- fonts: { heading: f.heading, body: f.body }
285
- };
286
- } catch {
287
- return null;
288
- }
289
- }
290
- function familyOf(stack) {
291
- const first = stack.split(",")[0]?.trim() ?? "";
292
- return first.replace(/^['"]|['"]$/g, "");
293
- }
294
- function loadBrandFonts(families) {
295
- const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
296
- if (unique.length === 0) return;
297
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
298
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
299
- let link = document.getElementById(BRAND_FONT_LINK_ID);
300
- if (!link) {
301
- link = document.createElement("link");
302
- link.id = BRAND_FONT_LINK_ID;
303
- link.rel = "stylesheet";
304
- document.head.appendChild(link);
305
- }
306
- if (link.href !== href) link.href = href;
307
- }
308
- function applyBrandToDom(kit) {
309
- const root = document.documentElement;
310
- if (!kit) {
311
- for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
312
- for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
313
- document.getElementById(BRAND_FONT_LINK_ID)?.remove();
314
- return;
315
- }
316
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
317
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
318
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
319
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
320
- }
321
-
322
- // src/lib/section-styles.ts
323
- var STYLE_STORE_KEY = "__ohw_styles";
324
- var STYLE_SHEET_ID = "ohw-section-styles";
325
- function parseStyleStore(raw) {
326
- if (!raw) return null;
327
- try {
328
- const parsed = JSON.parse(raw);
329
- if (parsed?.v !== 1) return null;
330
- return {
331
- v: 1,
332
- sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
333
- nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
334
- };
335
- } catch {
336
- return null;
337
- }
338
- }
339
- var BG_VALUES = {
340
- surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
341
- accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
342
- "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
343
- };
344
- var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
345
- function styleSheetCss() {
346
- const rules = [];
347
- for (const [tone, value] of Object.entries(BG_VALUES)) {
348
- rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
349
- }
350
- rules.push(
351
- `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
352
- );
353
- rules.push(
354
- `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
355
- `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
356
- );
357
- for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
358
- rules.push(
359
- `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
360
- );
361
- }
362
- rules.push(
363
- `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
364
- );
365
- for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
366
- rules.push(
367
- `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
368
- );
369
- }
370
- const pad = { tight: 40, balanced: 64, airy: 96 };
371
- for (const [spacing, px] of Object.entries(pad)) {
372
- rules.push(
373
- `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
374
- );
375
- }
376
- return rules.join("\n");
377
- }
378
- var STYLE_FONT_LINK_ID = "ohw-style-fonts";
379
- function loadStyleFonts(families) {
380
- const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
381
- const existing = document.getElementById(STYLE_FONT_LINK_ID);
382
- if (unique.length === 0) {
383
- existing?.remove();
384
- return;
385
- }
386
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
387
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
388
- let link = existing;
389
- if (!link) {
390
- link = document.createElement("link");
391
- link.id = STYLE_FONT_LINK_ID;
392
- link.rel = "stylesheet";
393
- document.head.appendChild(link);
394
- }
395
- if (link.href !== href) link.href = href;
396
- }
397
- var SECTION_ATTRS = {
398
- sectionBackground: "data-ohw-style-bg",
399
- textDistribution: "data-ohw-style-distribution",
400
- headlineScale: "data-ohw-style-headline",
401
- imageAspect: "data-ohw-style-aspect",
402
- spacing: "data-ohw-style-spacing"
403
- };
404
- var NODE_WROTE_ATTR = "data-ohw-style-node";
405
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
406
- function saveInline(el, prop) {
407
- const attr = `data-ohw-style-prev-${prop}`;
408
- if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
409
- }
410
- function restoreInline(el, prop) {
411
- const attr = `data-ohw-style-prev-${prop}`;
412
- if (!el.hasAttribute(attr)) return;
413
- const prev = el.getAttribute(attr) ?? "";
414
- if (prev) el.style.setProperty(prop, prev);
415
- else el.style.removeProperty(prop);
416
- el.removeAttribute(attr);
417
- }
418
- function ensureStyleSheet() {
419
- let el = document.getElementById(STYLE_SHEET_ID);
420
- if (!el) {
421
- el = document.createElement("style");
422
- el.id = STYLE_SHEET_ID;
423
- document.head.appendChild(el);
424
- }
425
- const css = styleSheetCss();
426
- if (el.textContent !== css) el.textContent = css;
427
- }
428
- function clearSectionAttrs(root) {
429
- for (const attr of Object.values(SECTION_ATTRS)) {
430
- for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
431
- }
432
- for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
433
- restoreInline(el, "background");
434
- el.removeAttribute("data-ohw-style-bgcolor");
435
- }
436
- }
437
- function clearNodeProps(root) {
438
- for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
439
- const h = el;
440
- for (const prop of NODE_PROPS) restoreInline(h, prop);
441
- h.removeAttribute(NODE_WROTE_ATTR);
442
- }
443
- }
444
- function buttonSurfaceOf(el) {
445
- return el.closest("a, button") ?? el;
446
- }
447
- function applyStylesToDom(store) {
448
- ensureStyleSheet();
449
- clearSectionAttrs(document);
450
- clearNodeProps(document);
451
- loadStyleFonts(
452
- store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
453
- );
454
- if (!store) return;
455
- for (const [sectionId, override] of Object.entries(store.sections)) {
456
- const sections = document.querySelectorAll(
457
- `[data-ohw-section="${CSS.escape(sectionId)}"]`
458
- );
459
- for (const section of Array.from(sections)) {
460
- for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
461
- const value = override[prop];
462
- if (value === void 0) continue;
463
- if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
464
- section.setAttribute(attr, String(value).replace(":", "-"));
465
- }
466
- if (override.sectionBackgroundColor !== void 0) {
467
- saveInline(section, "background");
468
- section.style.setProperty("background", override.sectionBackgroundColor, "important");
469
- section.setAttribute("data-ohw-style-bgcolor", "");
470
- }
471
- }
472
- }
473
- for (const [key, override] of Object.entries(store.nodes)) {
474
- const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
475
- for (const el of Array.from(nodes)) {
476
- if (override.color !== void 0) {
477
- saveInline(el, "color");
478
- el.style.setProperty("color", override.color, "important");
479
- el.setAttribute(NODE_WROTE_ATTR, "");
480
- }
481
- if (override.fontFamily !== void 0) {
482
- saveInline(el, "font-family");
483
- el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
484
- el.setAttribute(NODE_WROTE_ATTR, "");
485
- }
486
- if (override.fontSize !== void 0) {
487
- saveInline(el, "font-size");
488
- el.style.setProperty("font-size", `${override.fontSize}px`, "important");
489
- el.setAttribute(NODE_WROTE_ATTR, "");
490
- }
491
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
492
- const surface = buttonSurfaceOf(el);
493
- if (override.buttonBackground !== void 0) {
494
- saveInline(surface, "background");
495
- surface.style.setProperty("background", override.buttonBackground, "important");
496
- }
497
- if (override.buttonText !== void 0) {
498
- saveInline(surface, "color");
499
- surface.style.setProperty("color", override.buttonText, "important");
500
- }
501
- surface.setAttribute(NODE_WROTE_ATTR, "");
502
- }
503
- }
504
- }
505
- }
506
-
507
194
  // src/ui/ai-tree/aiSectionsManager.tsx
508
195
  var import_react_dom = require("react-dom");
509
196
  var import_client = require("react-dom/client");
@@ -518,8 +205,7 @@ function lucideByName(name) {
518
205
  }
519
206
  var typeStyle = (spec, font) => ({
520
207
  fontFamily: font,
521
- // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
522
- fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
208
+ fontSize: spec.size,
523
209
  lineHeight: spec.line,
524
210
  fontWeight: spec.weight
525
211
  });
@@ -546,8 +232,6 @@ var AI_RESPONSIVE_CSS = [
546
232
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
547
233
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
548
234
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
549
- " [data-ai-responsive] { overflow-x: hidden; }",
550
- " [data-ai-responsive] img { max-width: 100%; }",
551
235
  "}"
552
236
  ].join("\n");
553
237
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -1555,20 +1239,6 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1555
1239
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1556
1240
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1557
1241
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1558
- const toneBackground = (() => {
1559
- const { dark, primary, light } = resolvedBrand.palette;
1560
- switch (settings.sectionBackground) {
1561
- case "surface":
1562
- return `color-mix(in srgb, ${light} 94%, ${dark})`;
1563
- case "accent":
1564
- return primary;
1565
- case "accent-soft":
1566
- return `color-mix(in srgb, ${primary} 12%, ${light})`;
1567
- default:
1568
- return void 0;
1569
- }
1570
- })();
1571
- const distributed = !isOverlay && settings.textDistribution;
1572
1242
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1573
1243
  "section",
1574
1244
  {
@@ -1578,11 +1248,10 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1578
1248
  style: {
1579
1249
  position: "relative",
1580
1250
  padding: `${pad}px 0`,
1581
- background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1251
+ background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1582
1252
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1583
1253
  backgroundSize: "cover",
1584
- backgroundPosition: "center",
1585
- color: settings.sectionBackground === "accent" ? resolvedBrand.palette.light : void 0
1254
+ backgroundPosition: "center"
1586
1255
  },
1587
1256
  children: [
1588
1257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
@@ -1606,24 +1275,10 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1606
1275
  display: "grid",
1607
1276
  gridTemplateColumns: "repeat(12, 1fr)",
1608
1277
  gap: AI_TREE_TOKENS.spacing6,
1609
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1278
+ alignItems: settings.verticalPosition === "top" ? "start" : "center",
1610
1279
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1611
1280
  },
1612
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1613
- "div",
1614
- {
1615
- "data-ai-cell": "",
1616
- style: {
1617
- gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1618
- minWidth: 0,
1619
- // space-between: each column becomes a flex column whose content spreads over
1620
- // the full row height instead of clumping at the top.
1621
- ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1622
- },
1623
- children: renderNode(block, ctx, `r${r2}.b${b}`)
1624
- },
1625
- b
1626
- ))
1281
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1627
1282
  },
1628
1283
  r2
1629
1284
  ))
@@ -1639,34 +1294,17 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1639
1294
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1640
1295
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1641
1296
  var REMOVED_ATTR = "data-ohw-ai-removed";
1642
- function readRootVar(name) {
1643
- if (typeof document === "undefined") return "";
1644
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1645
- }
1646
- function deriveBrandOverride() {
1647
- const dark = readRootVar("--ohw-brand-dark");
1648
- const primary = readRootVar("--ohw-brand-primary");
1649
- const light = readRootVar("--ohw-brand-light");
1650
- if (!dark || !primary || !light) return null;
1651
- const accent = readRootVar("--ohw-brand-accent");
1652
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1653
- const body = readRootVar("--font-body");
1654
- return {
1655
- palette: { dark, primary, accent: accent || dark, light },
1656
- fonts: {
1657
- heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1658
- body: body || AI_DEFAULT_BRAND.fonts.body
1659
- }
1660
- };
1661
- }
1662
1297
  function deriveTemplateBrand() {
1663
- const dark = readRootVar("--color-dark");
1664
- const primary = readRootVar("--color-primary");
1665
- const light = readRootVar("--color-light");
1298
+ if (typeof document === "undefined") return null;
1299
+ const cs = getComputedStyle(document.documentElement);
1300
+ const read = (name) => cs.getPropertyValue(name).trim();
1301
+ const dark = read("--color-dark");
1302
+ const primary = read("--color-primary");
1303
+ const light = read("--color-light");
1666
1304
  if (!dark || !primary || !light) return null;
1667
- const accent = readRootVar("--color-accent");
1668
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1669
- const body = readRootVar("--font-body");
1305
+ const accent = read("--color-accent");
1306
+ const heading = read("--font-heading") || read("--font-display");
1307
+ const body = read("--font-body");
1670
1308
  return {
1671
1309
  palette: { dark, primary, accent: accent || dark, light },
1672
1310
  fonts: {
@@ -1758,12 +1396,8 @@ function syncReplacedOriginals(state) {
1758
1396
  }
1759
1397
  function applyAiSectionsToDom(state, options) {
1760
1398
  if (typeof document === "undefined") return;
1761
- const brandOverride = deriveBrandOverride();
1762
1399
  const templateBrand = deriveTemplateBrand();
1763
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1764
- const pagePath = window.location.pathname;
1765
- const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1766
- const activeIds = new Set(pageSections.map((entry) => entry.id));
1400
+ const activeIds = new Set(state.sections.map((entry) => entry.id));
1767
1401
  for (const [id, section] of mounted) {
1768
1402
  if (!activeIds.has(id)) {
1769
1403
  section.root.unmount();
@@ -1771,8 +1405,8 @@ function applyAiSectionsToDom(state, options) {
1771
1405
  mounted.delete(id);
1772
1406
  }
1773
1407
  }
1774
- for (const entry of pageSections) {
1775
- const serialized = JSON.stringify(entry) + brandKey;
1408
+ for (const entry of state.sections) {
1409
+ const serialized = JSON.stringify(entry);
1776
1410
  const existing = mounted.get(entry.id);
1777
1411
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1778
1412
  continue;
@@ -1786,7 +1420,6 @@ function applyAiSectionsToDom(state, options) {
1786
1420
  mounted.delete(entry.id);
1787
1421
  }
1788
1422
  container.setAttribute("data-ohw-section", entry.id);
1789
- container.setAttribute("data-ohw-instance", entry.id);
1790
1423
  container.setAttribute("data-ohw-section-label", entry.label);
1791
1424
  placeContainer(container, entry);
1792
1425
  const root = mounted.get(entry.id)?.root ?? (0, import_client.createRoot)(container);
@@ -1797,7 +1430,7 @@ function applyAiSectionsToDom(state, options) {
1797
1430
  AiTreeRenderer,
1798
1431
  {
1799
1432
  tree: entry.tree,
1800
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1433
+ brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1801
1434
  resolveMedia,
1802
1435
  editKeyPrefix: `ai.${entry.id}`
1803
1436
  }
@@ -2414,7 +2047,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2414
2047
  const autoId = (0, import_react5.useId)();
2415
2048
  const insertAfter = insertAfterProp ?? autoId;
2416
2049
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2417
- const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2050
+ const [loading, setLoading] = (0, import_react5.useState)(true);
2418
2051
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2419
2052
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2420
2053
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2588,10 +2221,8 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2588
2221
  "*"
2589
2222
  );
2590
2223
  };
2224
+ if (!inEditor && !loading && !schedule) return null;
2591
2225
  const sectionId = `scheduling-${insertAfter}`;
2592
- if (!inEditor && !loading && !schedule) {
2593
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2594
- }
2595
2226
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2596
2227
  "section",
2597
2228
  {
@@ -6589,6 +6220,7 @@ function getChromeStyle(state) {
6589
6220
  }
6590
6221
  function ClampedToolbarSlot({
6591
6222
  placement,
6223
+ align = "center",
6592
6224
  children
6593
6225
  }) {
6594
6226
  const slotRef = (0, import_react7.useRef)(null);
@@ -6610,7 +6242,7 @@ function ClampedToolbarSlot({
6610
6242
  Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
6611
6243
  );
6612
6244
  const offsetX = clampedCenter - centerX;
6613
- slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
6245
+ slot.style.transform = align === "left" ? "none" : `translateX(calc(-50% + ${offsetX}px))`;
6614
6246
  };
6615
6247
  clamp();
6616
6248
  const ro = new ResizeObserver(clamp);
@@ -6621,19 +6253,20 @@ function ClampedToolbarSlot({
6621
6253
  ro.disconnect();
6622
6254
  window.removeEventListener("resize", clamp);
6623
6255
  };
6624
- }, [placement, children]);
6256
+ }, [placement, children, align]);
6625
6257
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
6626
6258
  "div",
6627
6259
  {
6628
6260
  ref: slotRef,
6629
6261
  className: cn(
6630
- "pointer-events-auto absolute left-1/2",
6262
+ "pointer-events-auto absolute",
6263
+ align === "left" ? "left-0" : "left-1/2",
6631
6264
  placement === "top" ? "bottom-full" : "top-full"
6632
6265
  ),
6633
6266
  style: {
6634
6267
  marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
6635
6268
  marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
6636
- transform: "translateX(-50%)"
6269
+ transform: align === "left" ? "none" : "translateX(-50%)"
6637
6270
  },
6638
6271
  "data-ohw-item-toolbar-anchor": placement,
6639
6272
  children
@@ -6702,6 +6335,7 @@ function ItemInteractionLayer({
6702
6335
  onItemClick,
6703
6336
  itemDragSurface = true,
6704
6337
  chromeGap,
6338
+ toolbarAlign = "center",
6705
6339
  className
6706
6340
  }) {
6707
6341
  if (state === "default") return null;
@@ -6789,8 +6423,8 @@ function ItemInteractionLayer({
6789
6423
  )
6790
6424
  }
6791
6425
  ),
6792
- showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ClampedToolbarSlot, { placement: "top", children: toolbar }),
6793
- showToolbar && state === "active-bottom" && !useDetachedBelowToolbar && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ClampedToolbarSlot, { placement: "bottom", children: toolbar }),
6426
+ showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ClampedToolbarSlot, { align: toolbarAlign, placement: "top", children: toolbar }),
6427
+ showToolbar && state === "active-bottom" && !useDetachedBelowToolbar && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ClampedToolbarSlot, { align: toolbarAlign, placement: "bottom", children: toolbar }),
6794
6428
  useDetachedBelowToolbar && toolbarBelowRect ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
6795
6429
  DetachedBelowToolbarSlot,
6796
6430
  {
@@ -6804,14 +6438,624 @@ function ItemInteractionLayer({
6804
6438
  );
6805
6439
  }
6806
6440
 
6441
+ // src/lib/forms.ts
6442
+ var FORM_SELECTOR = '[data-ohw-editable="form"]';
6443
+ function getFormElement(el) {
6444
+ if (!el) return null;
6445
+ return el.closest(FORM_SELECTOR);
6446
+ }
6447
+ function formKeyOf(form) {
6448
+ return form.getAttribute("data-ohw-key");
6449
+ }
6450
+ function formHasLongText(form) {
6451
+ return form.querySelector("textarea") !== null;
6452
+ }
6453
+ function collectFormFields(form) {
6454
+ const fields = {};
6455
+ form.querySelectorAll(
6456
+ "input, textarea, select"
6457
+ ).forEach((input) => {
6458
+ const type = input.type;
6459
+ if (type === "submit" || type === "button" || type === "reset") return;
6460
+ const key = input.getAttribute("name") ?? input.getAttribute("data-ohw-key");
6461
+ if (!key) return;
6462
+ if (type === "checkbox") {
6463
+ fields[key] = input.checked ? "yes" : "no";
6464
+ return;
6465
+ }
6466
+ fields[key] = input.value;
6467
+ });
6468
+ return fields;
6469
+ }
6470
+ function formStateContainer(form) {
6471
+ return form.closest("[data-ohw-editable-state]") ?? form.parentElement ?? form;
6472
+ }
6473
+ function showSuccess(form, message) {
6474
+ const container = formStateContainer(form);
6475
+ const views = container.querySelectorAll("[data-ohw-state-view]");
6476
+ if (views.length > 0) {
6477
+ views.forEach((view) => {
6478
+ view.style.display = view.getAttribute("data-ohw-state-view") === "success" ? "block" : "none";
6479
+ });
6480
+ return;
6481
+ }
6482
+ const text = message || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6483
+ const note = document.createElement("p");
6484
+ note.setAttribute("data-ohw-form-success", "");
6485
+ note.setAttribute("role", "status");
6486
+ note.innerHTML = text;
6487
+ form.replaceChildren(note);
6488
+ }
6489
+ function showSubmitError(form, message = "Something went wrong. Please try again.") {
6490
+ let note = form.querySelector("[data-ohw-form-error]");
6491
+ if (!note) {
6492
+ note = document.createElement("p");
6493
+ note.setAttribute("data-ohw-form-error", "");
6494
+ note.setAttribute("role", "alert");
6495
+ note.style.marginTop = "8px";
6496
+ form.appendChild(note);
6497
+ }
6498
+ note.textContent = message;
6499
+ note.style.display = "";
6500
+ }
6501
+ function clearSubmitError(form) {
6502
+ const note = form.querySelector("[data-ohw-form-error]");
6503
+ if (note) note.style.display = "none";
6504
+ }
6505
+ var SUCCESS_TEXT_ATTR = "data-ohw-form-success-text";
6506
+ var HIDDEN_ATTR = "data-ohw-form-hidden";
6507
+ var DEFAULT_SUCCESS_TEXT = "Thanks! We'll be in touch.";
6508
+ function formSuccessKey(formKey) {
6509
+ return `${formKey}-success`;
6510
+ }
6511
+ function ensureSuccessTextEl(form, formKey, initialText) {
6512
+ let el = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
6513
+ if (el) return el;
6514
+ el = document.createElement("p");
6515
+ el.setAttribute(SUCCESS_TEXT_ATTR, "");
6516
+ el.setAttribute("data-ohw-editable", "text");
6517
+ el.setAttribute("data-ohw-key", formSuccessKey(formKey));
6518
+ el.innerHTML = initialText;
6519
+ el.style.display = "none";
6520
+ el.style.margin = "56px 4px 16px";
6521
+ form.appendChild(el);
6522
+ return el;
6523
+ }
6524
+ function successInitialFor(form, formKey, content) {
6525
+ return content[formSuccessKey(formKey)] || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6526
+ }
6527
+ function setFormViewState(form, formKey, state, initialText) {
6528
+ const success = ensureSuccessTextEl(form, formKey, initialText);
6529
+ Array.from(form.children).forEach((child) => {
6530
+ if (!(child instanceof HTMLElement) || child === success) return;
6531
+ if (state === "success") {
6532
+ if (!child.hasAttribute(HIDDEN_ATTR)) {
6533
+ child.setAttribute(HIDDEN_ATTR, child.style.display);
6534
+ child.style.display = "none";
6535
+ }
6536
+ } else if (child.hasAttribute(HIDDEN_ATTR)) {
6537
+ child.style.display = child.getAttribute(HIDDEN_ATTR) ?? "";
6538
+ child.removeAttribute(HIDDEN_ATTR);
6539
+ }
6540
+ });
6541
+ success.style.display = state === "success" ? "" : "none";
6542
+ }
6543
+ var BOUND_ATTR = "data-ohw-form-bound";
6544
+ var publishedFormContext = {
6545
+ apiUrl: "",
6546
+ subdomain: "",
6547
+ content: {}
6548
+ };
6549
+ function bindPublishedForms(apiUrl, subdomain, content = {}) {
6550
+ publishedFormContext = { apiUrl, subdomain, content };
6551
+ if (document.documentElement.hasAttribute(BOUND_ATTR)) return;
6552
+ document.documentElement.setAttribute(BOUND_ATTR, "");
6553
+ document.addEventListener(
6554
+ "submit",
6555
+ async (e) => {
6556
+ const target = e.target;
6557
+ const form = target?.closest(FORM_SELECTOR);
6558
+ if (!form || !(form instanceof HTMLFormElement)) return;
6559
+ const formKey = formKeyOf(form);
6560
+ if (!formKey) return;
6561
+ e.preventDefault();
6562
+ clearSubmitError(form);
6563
+ const filled = collectFormFields(form);
6564
+ if (!Object.values(filled).some((value) => value.trim() !== "")) {
6565
+ showSubmitError(form, "Please fill in at least one field.");
6566
+ return;
6567
+ }
6568
+ const submitButton = form.querySelector(
6569
+ 'button[type="submit"], input[type="submit"], button:not([type])'
6570
+ );
6571
+ if (submitButton) submitButton.disabled = true;
6572
+ try {
6573
+ const { apiUrl: api, subdomain: site, content: latest } = publishedFormContext;
6574
+ const response = await fetch(`${api}/api/public/sites/${site}/forms/submissions`, {
6575
+ method: "POST",
6576
+ headers: { "Content-Type": "application/json" },
6577
+ body: JSON.stringify({
6578
+ formKey,
6579
+ fields: filled,
6580
+ hasLongText: formHasLongText(form)
6581
+ })
6582
+ });
6583
+ if (!response.ok) throw new Error(`submit failed: ${response.status}`);
6584
+ showSuccess(form, latest[formSuccessKey(formKey)]);
6585
+ } catch {
6586
+ showSubmitError(form);
6587
+ if (submitButton) submitButton.disabled = false;
6588
+ }
6589
+ },
6590
+ true
6591
+ );
6592
+ }
6593
+
6594
+ // src/lib/form-fields.ts
6595
+ var FIELD_DEFAULTS = {
6596
+ "short-text": { label: "Short text", placeholder: "Type placeholder text..." },
6597
+ "long-text": { label: "Long text", placeholder: "How can we help?" },
6598
+ email: { label: "Email", placeholder: "hello@example.com" },
6599
+ phone: { label: "Phone number", placeholder: "(555) 000-0000" }
6600
+ };
6601
+ var FIELD_TYPES = [
6602
+ { type: "short-text", label: "Short text" },
6603
+ { type: "long-text", label: "Long text" },
6604
+ { type: "email", label: "Email" },
6605
+ { type: "phone", label: "Phone" }
6606
+ ];
6607
+ var FIELD_ATTR = "data-ohw-form-field";
6608
+ var FIELD_TYPE_ATTR = "data-ohw-field-type";
6609
+ var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
6610
+ function fieldsKey(formKey) {
6611
+ return `${formKey}-fields`;
6612
+ }
6613
+ function inputTagFor(type) {
6614
+ if (type === "long-text") return { tag: "textarea" };
6615
+ if (type === "email") return { tag: "input", inputType: "email" };
6616
+ if (type === "phone") return { tag: "input", inputType: "tel" };
6617
+ return { tag: "input", inputType: "text" };
6618
+ }
6619
+ function inferType(input) {
6620
+ if (input.tagName === "TEXTAREA") return "long-text";
6621
+ const type = input.type;
6622
+ if (type === "email") return "email";
6623
+ if (type === "tel") return "phone";
6624
+ return "short-text";
6625
+ }
6626
+ function ensureFieldLabel(wrapper, key) {
6627
+ const existing = fieldLabelOf(wrapper);
6628
+ if (existing) {
6629
+ if (!existing.hasAttribute("data-ohw-editable")) {
6630
+ existing.setAttribute("data-ohw-editable", "text");
6631
+ existing.setAttribute("data-ohw-key", `${key}-label`);
6632
+ }
6633
+ return existing;
6634
+ }
6635
+ const input = fieldInputOf(wrapper);
6636
+ const label = document.createElement("label");
6637
+ label.setAttribute("data-ohw-editable", "text");
6638
+ label.setAttribute("data-ohw-key", `${key}-label`);
6639
+ label.setAttribute("data-ohw-field-label", "");
6640
+ label.style.display = "block";
6641
+ label.style.marginBottom = "6px";
6642
+ label.style.fontSize = "13px";
6643
+ label.style.fontWeight = "500";
6644
+ label.style.lineHeight = "1.3";
6645
+ const fromPlaceholder = input?.getAttribute("placeholder")?.trim();
6646
+ const text = fromPlaceholder && fromPlaceholder.length < 40 ? fromPlaceholder : key.replace(/[-_]/g, " ");
6647
+ label.textContent = text;
6648
+ if (input && input.parentElement === wrapper) wrapper.insertBefore(label, input);
6649
+ else wrapper.insertBefore(label, wrapper.firstChild);
6650
+ return label;
6651
+ }
6652
+ function listFieldWrappers(form) {
6653
+ return Array.from(form.querySelectorAll(`[${FIELD_ATTR}]`));
6654
+ }
6655
+ function getFieldWrapper(el) {
6656
+ return el?.closest(`[${FIELD_ATTR}]`) ?? null;
6657
+ }
6658
+ function fieldInputOf(wrapper) {
6659
+ return wrapper.querySelector("input, textarea");
6660
+ }
6661
+ function fieldLabelOf(wrapper) {
6662
+ return wrapper.querySelector("label");
6663
+ }
6664
+ function fieldKeyOf(wrapper) {
6665
+ return wrapper.getAttribute(FIELD_ATTR) ?? "";
6666
+ }
6667
+ function fieldTypeOf(wrapper) {
6668
+ return wrapper.getAttribute(FIELD_TYPE_ATTR) ?? "short-text";
6669
+ }
6670
+ function isFieldRequired(wrapper) {
6671
+ return fieldInputOf(wrapper)?.hasAttribute("required") ?? false;
6672
+ }
6673
+ function markFormFields(form) {
6674
+ form.querySelectorAll("input, textarea").forEach((input) => {
6675
+ const type = input.type;
6676
+ if (type === "submit" || type === "button" || type === "reset" || type === "hidden") return;
6677
+ if (getFieldWrapper(input)) return;
6678
+ let wrapper = input;
6679
+ while (wrapper.parentElement && wrapper.parentElement !== form && wrapper.parentElement.querySelectorAll("input, textarea").length === 1) {
6680
+ wrapper = wrapper.parentElement;
6681
+ }
6682
+ if (wrapper === input) {
6683
+ const box = document.createElement("div");
6684
+ box.setAttribute("data-ohw-field-box", "");
6685
+ input.replaceWith(box);
6686
+ box.appendChild(input);
6687
+ wrapper = box;
6688
+ }
6689
+ const key = input.getAttribute("name") ?? input.getAttribute("data-ohw-key") ?? `field-${Date.now()}`;
6690
+ wrapper.setAttribute(FIELD_ATTR, key);
6691
+ wrapper.setAttribute(FIELD_TYPE_ATTR, inferType(input));
6692
+ ensureFieldLabel(wrapper, key);
6693
+ syncRequiredMark(wrapper);
6694
+ });
6695
+ return listFieldWrappers(form);
6696
+ }
6697
+ function readFieldsFromDom(form) {
6698
+ return listFieldWrappers(form).map((wrapper) => {
6699
+ const input = fieldInputOf(wrapper);
6700
+ return {
6701
+ key: fieldKeyOf(wrapper),
6702
+ type: fieldTypeOf(wrapper),
6703
+ label: fieldLabelText(fieldLabelOf(wrapper)).trim(),
6704
+ // While a field is selected its placeholder text lives in the value (see
6705
+ // beginPlaceholderEdit), so reading the attribute would store an empty one — whoever
6706
+ // happens to save at that moment must still record what the owner typed.
6707
+ placeholder: input ? input.hasAttribute(PLACEHOLDER_EDIT_ATTR) ? input.value : input.getAttribute("placeholder") ?? "" : "",
6708
+ required: Boolean(input?.hasAttribute("required"))
6709
+ };
6710
+ });
6711
+ }
6712
+ function parseFieldSpecs(raw) {
6713
+ if (!raw) return null;
6714
+ try {
6715
+ const parsed = JSON.parse(raw);
6716
+ return Array.isArray(parsed) ? parsed : null;
6717
+ } catch {
6718
+ return null;
6719
+ }
6720
+ }
6721
+ function isDefaultText(value, pick) {
6722
+ const trimmed = value.trim().replace(/\s*\*$/, "");
6723
+ if (!trimmed) return true;
6724
+ return Object.values(FIELD_DEFAULTS).some((defaults) => pick(defaults) === trimmed);
6725
+ }
6726
+ function applyFieldType(wrapper, type) {
6727
+ const input = fieldInputOf(wrapper);
6728
+ if (!input) return;
6729
+ const defaults = FIELD_DEFAULTS[type];
6730
+ const placeholder = input.getAttribute("placeholder") ?? "";
6731
+ const label = fieldLabelOf(wrapper);
6732
+ const followsDefaults = {
6733
+ // A label the owner wrote stays; one that still reads as a type name (or as the old
6734
+ // placeholder the template shipped) follows the new type.
6735
+ label: isDefaultText(label?.textContent ?? "", (d) => d.label) || fieldLabelText(label).trim() === placeholder.trim()
6736
+ };
6737
+ const { tag, inputType } = inputTagFor(type);
6738
+ wrapper.setAttribute(FIELD_TYPE_ATTR, type);
6739
+ const shedSize = (el) => {
6740
+ if (type === "long-text") return;
6741
+ el.style.removeProperty("height");
6742
+ el.style.removeProperty("min-height");
6743
+ el.style.removeProperty("resize");
6744
+ el.removeAttribute("rows");
6745
+ };
6746
+ const applyDefaults = (el) => {
6747
+ el.setAttribute("placeholder", defaults.placeholder);
6748
+ if (label && followsDefaults.label) {
6749
+ label.textContent = defaults.label;
6750
+ }
6751
+ };
6752
+ if (input.tagName.toLowerCase() === tag) {
6753
+ if (inputType) input.type = inputType;
6754
+ shedSize(input);
6755
+ applyDefaults(input);
6756
+ return;
6757
+ }
6758
+ const next = document.createElement(tag);
6759
+ Array.from(input.attributes).forEach((attr) => {
6760
+ if (attr.name === "type") return;
6761
+ next.setAttribute(attr.name, attr.value);
6762
+ });
6763
+ if (inputType) next.setAttribute("type", inputType);
6764
+ shedSize(next);
6765
+ if (type === "long-text") next.setAttribute("rows", "4");
6766
+ input.replaceWith(next);
6767
+ applyDefaults(next);
6768
+ }
6769
+ var REQUIRED_MARK_ATTR = "data-ohw-field-required";
6770
+ function fieldLabelText(label) {
6771
+ if (!label) return "";
6772
+ return (label.textContent ?? "").replace(/\s*\*\s*$/, "").trimEnd();
6773
+ }
6774
+ function syncRequiredMark(wrapper) {
6775
+ const label = fieldLabelOf(wrapper);
6776
+ if (!label) return;
6777
+ label.querySelectorAll("[data-ohw-required-mark]").forEach((el) => el.remove());
6778
+ const words = fieldLabelText(label);
6779
+ if ((label.textContent ?? "") !== words) label.textContent = words;
6780
+ if (isFieldRequired(wrapper)) label.setAttribute(REQUIRED_MARK_ATTR, "");
6781
+ else label.removeAttribute(REQUIRED_MARK_ATTR);
6782
+ }
6783
+ function setFieldRequired(wrapper, required) {
6784
+ const input = fieldInputOf(wrapper);
6785
+ const label = fieldLabelOf(wrapper);
6786
+ if (!input) return;
6787
+ if (required) input.setAttribute("required", "");
6788
+ else input.removeAttribute("required");
6789
+ if (label) syncRequiredMark(wrapper);
6790
+ }
6791
+ function beginPlaceholderEdit(wrapper) {
6792
+ const input = fieldInputOf(wrapper);
6793
+ if (!input || input.hasAttribute(PLACEHOLDER_EDIT_ATTR)) return;
6794
+ input.setAttribute(PLACEHOLDER_EDIT_ATTR, "");
6795
+ input.value = input.getAttribute("placeholder") ?? "";
6796
+ input.setAttribute("placeholder", "");
6797
+ }
6798
+ function commitPlaceholderEdit(wrapper) {
6799
+ if (!wrapper) return false;
6800
+ const input = fieldInputOf(wrapper);
6801
+ if (!input || !input.hasAttribute(PLACEHOLDER_EDIT_ATTR)) return false;
6802
+ const typed = input.value;
6803
+ input.removeAttribute(PLACEHOLDER_EDIT_ATTR);
6804
+ input.value = "";
6805
+ const previous = input.getAttribute("placeholder") ?? "";
6806
+ input.setAttribute("placeholder", typed);
6807
+ return previous !== typed;
6808
+ }
6809
+ function setFieldPlaceholder(wrapper, placeholder) {
6810
+ fieldInputOf(wrapper)?.setAttribute("placeholder", placeholder);
6811
+ }
6812
+ function uniqueKey(form, base) {
6813
+ const taken = new Set(listFieldWrappers(form).map(fieldKeyOf));
6814
+ if (!taken.has(base)) return base;
6815
+ let n = 2;
6816
+ while (taken.has(`${base}-${n}`)) n += 1;
6817
+ return `${base}-${n}`;
6818
+ }
6819
+ function insertField(form, type) {
6820
+ const existing = listFieldWrappers(form);
6821
+ const source = existing[existing.length - 1] ?? null;
6822
+ const key = uniqueKey(form, type);
6823
+ let wrapper;
6824
+ if (source) {
6825
+ wrapper = source.cloneNode(true);
6826
+ source.after(wrapper);
6827
+ } else {
6828
+ wrapper = document.createElement("div");
6829
+ const label2 = document.createElement("label");
6830
+ const input2 = document.createElement("input");
6831
+ wrapper.append(label2, input2);
6832
+ const submit = form.querySelector('button, input[type="submit"]');
6833
+ if (submit) submit.before(wrapper);
6834
+ else form.appendChild(wrapper);
6835
+ }
6836
+ wrapper.setAttribute(FIELD_ATTR, key);
6837
+ wrapper.setAttribute(FIELD_TYPE_ATTR, type);
6838
+ applyFieldType(wrapper, type);
6839
+ const defaults = FIELD_DEFAULTS[type];
6840
+ const input = fieldInputOf(wrapper);
6841
+ if (input) {
6842
+ input.setAttribute("name", key);
6843
+ input.removeAttribute("required");
6844
+ input.setAttribute("placeholder", defaults.placeholder);
6845
+ input.value = "";
6846
+ }
6847
+ const label = ensureFieldLabel(wrapper, key);
6848
+ label.textContent = defaults.label;
6849
+ label.setAttribute("data-ohw-key", `${key}-label`);
6850
+ return wrapper;
6851
+ }
6852
+ function duplicateField(form, wrapper) {
6853
+ const copy = wrapper.cloneNode(true);
6854
+ const key = uniqueKey(form, `${fieldKeyOf(wrapper)}-copy`);
6855
+ copy.setAttribute(FIELD_ATTR, key);
6856
+ const input = fieldInputOf(copy);
6857
+ if (input) {
6858
+ input.setAttribute("name", key);
6859
+ input.removeAttribute("data-ohw-key");
6860
+ input.value = "";
6861
+ }
6862
+ const label = fieldLabelOf(copy);
6863
+ if (label) label.setAttribute("data-ohw-key", `${key}-label`);
6864
+ wrapper.after(copy);
6865
+ return copy;
6866
+ }
6867
+ function removeField(wrapper) {
6868
+ wrapper.remove();
6869
+ }
6870
+ function applyFieldOrder(form, keys) {
6871
+ const wrappers = listFieldWrappers(form);
6872
+ const byKey = new Map(wrappers.map((wrapper) => [fieldKeyOf(wrapper), wrapper]));
6873
+ const ordered = keys.map((key) => byKey.get(key)).filter((w) => Boolean(w));
6874
+ if (ordered.length !== wrappers.length) return;
6875
+ const slots = wrappers.map((wrapper) => {
6876
+ const slot = document.createComment("ohw-field-slot");
6877
+ wrapper.replaceWith(slot);
6878
+ return slot;
6879
+ });
6880
+ ordered.forEach((wrapper, index) => slots[index].replaceWith(wrapper));
6881
+ }
6882
+ function moveField(form, key, toIndex) {
6883
+ const keys = listFieldWrappers(form).map(fieldKeyOf);
6884
+ const from = keys.indexOf(key);
6885
+ if (from === -1) return;
6886
+ const rest = keys.filter((k) => k !== key);
6887
+ rest.splice(Math.max(0, Math.min(rest.length, toIndex)), 0, key);
6888
+ applyFieldOrder(form, rest);
6889
+ }
6890
+ function reconcileFieldsFromContent(form, content) {
6891
+ const formKey = form.getAttribute("data-ohw-key");
6892
+ if (!formKey) return;
6893
+ markFormFields(form);
6894
+ const stored = parseFieldSpecs(content[fieldsKey(formKey)]);
6895
+ if (!stored) return;
6896
+ const byKey = new Map(listFieldWrappers(form).map((wrapper) => [fieldKeyOf(wrapper), wrapper]));
6897
+ stored.forEach((spec) => {
6898
+ let wrapper = byKey.get(spec.key) ?? null;
6899
+ if (!wrapper) {
6900
+ wrapper = insertField(form, spec.type);
6901
+ if (!wrapper) return;
6902
+ wrapper.setAttribute(FIELD_ATTR, spec.key);
6903
+ const input = fieldInputOf(wrapper);
6904
+ if (input) {
6905
+ input.setAttribute("name", spec.key);
6906
+ }
6907
+ fieldLabelOf(wrapper)?.setAttribute("data-ohw-key", `${spec.key}-label`);
6908
+ byKey.set(spec.key, wrapper);
6909
+ }
6910
+ applyFieldType(wrapper, spec.type);
6911
+ setFieldRequired(wrapper, spec.required);
6912
+ setFieldPlaceholder(wrapper, spec.placeholder);
6913
+ const label = fieldLabelOf(wrapper);
6914
+ if (label && spec.label) label.textContent = spec.label.replace(/\s*\*\s*$/, "").trimEnd();
6915
+ syncRequiredMark(wrapper);
6916
+ });
6917
+ const wanted = new Set(stored.map((spec) => spec.key));
6918
+ listFieldWrappers(form).forEach((wrapper) => {
6919
+ if (!wanted.has(fieldKeyOf(wrapper))) wrapper.remove();
6920
+ });
6921
+ const currentOrder = listFieldWrappers(form).map(fieldKeyOf).join("\0");
6922
+ const storedOrder = stored.map((spec) => spec.key).join("\0");
6923
+ if (currentOrder !== storedOrder) {
6924
+ applyFieldOrder(form, stored.map((spec) => spec.key));
6925
+ }
6926
+ }
6927
+
6928
+ // src/ui/form-field-toolbar.tsx
6929
+ var import_lucide_react4 = require("lucide-react");
6930
+ var import_jsx_runtime13 = require("react/jsx-runtime");
6931
+ var TYPE_ICONS = {
6932
+ "short-text": import_lucide_react4.Type,
6933
+ "long-text": import_lucide_react4.TextQuote,
6934
+ email: import_lucide_react4.AtSign,
6935
+ phone: import_lucide_react4.Phone
6936
+ };
6937
+ function FormFieldToolbar({
6938
+ type,
6939
+ required,
6940
+ onTypeChange,
6941
+ onRequiredToggle,
6942
+ onDuplicate,
6943
+ onDelete
6944
+ }) {
6945
+ const TypeIcon = TYPE_ICONS[type];
6946
+ const typeLabel = FIELD_TYPES.find((entry) => entry.type === type)?.label ?? "Short text";
6947
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6948
+ "div",
6949
+ {
6950
+ "data-ohw-field-toolbar": "",
6951
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
6952
+ children: [
6953
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenu, { children: [
6954
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6955
+ "button",
6956
+ {
6957
+ type: "button",
6958
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted/70",
6959
+ "data-ohw-field-type-trigger": "",
6960
+ children: [
6961
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TypeIcon, { size: 14, strokeWidth: 1.75, "aria-hidden": true }),
6962
+ typeLabel,
6963
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.ChevronDown, { size: 13, className: "text-muted-foreground", "aria-hidden": true })
6964
+ ]
6965
+ }
6966
+ ) }),
6967
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
6968
+ const Icon = TYPE_ICONS[entry.type];
6969
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6970
+ DropdownMenuItem,
6971
+ {
6972
+ onSelect: () => onTypeChange(entry.type),
6973
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
6974
+ children: [
6975
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
6976
+ entry.label
6977
+ ]
6978
+ },
6979
+ entry.type
6980
+ );
6981
+ }) })
6982
+ ] }),
6983
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
6984
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6985
+ "button",
6986
+ {
6987
+ type: "button",
6988
+ "aria-pressed": required,
6989
+ onClick: onRequiredToggle,
6990
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted/70"),
6991
+ "data-ohw-field-required": "",
6992
+ children: [
6993
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
6994
+ "Required"
6995
+ ]
6996
+ }
6997
+ ),
6998
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
6999
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenu, { children: [
7000
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7001
+ "button",
7002
+ {
7003
+ type: "button",
7004
+ title: "More",
7005
+ "aria-label": "More",
7006
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/70",
7007
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
7008
+ }
7009
+ ) }),
7010
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
7011
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
7012
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7013
+ "Duplicate"
7014
+ ] }),
7015
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { variant: "destructive", onSelect: onDelete, className: "rounded-md py-2 text-[13px]", children: [
7016
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Trash2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7017
+ "Delete"
7018
+ ] })
7019
+ ] })
7020
+ ] })
7021
+ ]
7022
+ }
7023
+ );
7024
+ }
7025
+ function FieldTypePicker({ onPick }) {
7026
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7027
+ "div",
7028
+ {
7029
+ "data-ohw-field-type-picker": "",
7030
+ className: "pointer-events-auto grid w-[420px] grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
7031
+ children: FIELD_TYPES.map((entry) => {
7032
+ const Icon = TYPE_ICONS[entry.type];
7033
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7034
+ "button",
7035
+ {
7036
+ type: "button",
7037
+ onClick: () => onPick(entry.type),
7038
+ className: "flex h-[104px] flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-primary hover:bg-primary/5",
7039
+ children: [
7040
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
7041
+ entry.label
7042
+ ]
7043
+ },
7044
+ entry.type
7045
+ );
7046
+ })
7047
+ }
7048
+ );
7049
+ }
7050
+
6807
7051
  // src/ui/MediaOverlay.tsx
6808
7052
  var React7 = __toESM(require("react"), 1);
6809
- var import_lucide_react4 = require("lucide-react");
7053
+ var import_lucide_react5 = require("lucide-react");
6810
7054
 
6811
7055
  // src/ui/button.tsx
6812
7056
  var React6 = __toESM(require("react"), 1);
6813
7057
  var import_radix_ui5 = require("radix-ui");
6814
- var import_jsx_runtime13 = require("react/jsx-runtime");
7058
+ var import_jsx_runtime14 = require("react/jsx-runtime");
6815
7059
  var buttonVariants = cva(
6816
7060
  "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
6817
7061
  {
@@ -6835,7 +7079,7 @@ var buttonVariants = cva(
6835
7079
  var Button = React6.forwardRef(
6836
7080
  ({ className, variant, size, asChild = false, ...props }, ref) => {
6837
7081
  const Comp = asChild ? import_radix_ui5.Slot.Root : "button";
6838
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7082
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6839
7083
  Comp,
6840
7084
  {
6841
7085
  ref,
@@ -6849,7 +7093,7 @@ var Button = React6.forwardRef(
6849
7093
  Button.displayName = "Button";
6850
7094
 
6851
7095
  // src/ui/MediaOverlay.tsx
6852
- var import_jsx_runtime14 = require("react/jsx-runtime");
7096
+ var import_jsx_runtime15 = require("react/jsx-runtime");
6853
7097
  var MEDIA_UPLOAD_FADE_MS = 300;
6854
7098
  var VIDEO_SETTINGS_BAR_INSET = 8;
6855
7099
  var OVERLAY_BUTTON_STYLE = {
@@ -6907,7 +7151,7 @@ function MediaOverlay({
6907
7151
  return () => anim.cancel();
6908
7152
  }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
6909
7153
  if (isUploading) {
6910
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7154
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6911
7155
  "div",
6912
7156
  {
6913
7157
  ref: skeletonRef,
@@ -6916,11 +7160,11 @@ function MediaOverlay({
6916
7160
  "data-ohw-media-skeleton": "",
6917
7161
  "aria-hidden": true,
6918
7162
  style: { ...box, pointerEvents: "none" },
6919
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("style", { children: SKELETON_CSS })
7163
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("style", { children: SKELETON_CSS })
6920
7164
  }
6921
7165
  );
6922
7166
  }
6923
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
7167
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
6924
7168
  "div",
6925
7169
  {
6926
7170
  "data-ohw-bridge": "",
@@ -6936,7 +7180,7 @@ function MediaOverlay({
6936
7180
  },
6937
7181
  onClick: (e) => e.stopPropagation(),
6938
7182
  children: [
6939
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7183
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6940
7184
  Button,
6941
7185
  {
6942
7186
  "data-ohw-media-overlay": "",
@@ -6951,10 +7195,10 @@ function MediaOverlay({
6951
7195
  e.stopPropagation();
6952
7196
  onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
6953
7197
  },
6954
- children: autoplay ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.Pause, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.Play, { size: 14 })
7198
+ children: autoplay ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Pause, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Play, { size: 14 })
6955
7199
  }
6956
7200
  ),
6957
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7201
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6958
7202
  Button,
6959
7203
  {
6960
7204
  "data-ohw-media-overlay": "",
@@ -6969,15 +7213,15 @@ function MediaOverlay({
6969
7213
  e.stopPropagation();
6970
7214
  onVideoSettingsChange?.(hover.key, { muted: !muted });
6971
7215
  },
6972
- children: muted ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.VolumeX, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.Volume2, { size: 14 })
7216
+ children: muted ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.VolumeX, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Volume2, { size: 14 })
6973
7217
  }
6974
7218
  )
6975
7219
  ]
6976
7220
  }
6977
7221
  ) : null;
6978
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7222
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
6979
7223
  settingsBar,
6980
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7224
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6981
7225
  "div",
6982
7226
  {
6983
7227
  "data-ohw-bridge": "",
@@ -6994,7 +7238,7 @@ function MediaOverlay({
6994
7238
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
6995
7239
  },
6996
7240
  onClick: () => onReplace(hover.key),
6997
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
7241
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
6998
7242
  Button,
6999
7243
  {
7000
7244
  "data-ohw-media-overlay": "",
@@ -7012,7 +7256,7 @@ function MediaOverlay({
7012
7256
  onReplace(hover.key);
7013
7257
  },
7014
7258
  children: [
7015
- isVideo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react4.ImageIcon, { size: 14 }),
7259
+ isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
7016
7260
  isVideo ? "Replace video" : "Replace image"
7017
7261
  ]
7018
7262
  }
@@ -7023,8 +7267,8 @@ function MediaOverlay({
7023
7267
  }
7024
7268
 
7025
7269
  // src/ui/CarouselOverlay.tsx
7026
- var import_lucide_react5 = require("lucide-react");
7027
- var import_jsx_runtime15 = require("react/jsx-runtime");
7270
+ var import_lucide_react6 = require("lucide-react");
7271
+ var import_jsx_runtime16 = require("react/jsx-runtime");
7028
7272
  var OVERLAY_BUTTON_STYLE2 = {
7029
7273
  pointerEvents: "auto",
7030
7274
  fontFamily: "Inter, sans-serif",
@@ -7036,7 +7280,7 @@ function CarouselOverlay({
7036
7280
  onEdit
7037
7281
  }) {
7038
7282
  const { rect } = hover;
7039
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7283
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7040
7284
  "div",
7041
7285
  {
7042
7286
  "data-ohw-bridge": "",
@@ -7054,7 +7298,7 @@ function CarouselOverlay({
7054
7298
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
7055
7299
  },
7056
7300
  onClick: () => onEdit(hover.key),
7057
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7301
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7058
7302
  Button,
7059
7303
  {
7060
7304
  "data-ohw-carousel-overlay": "",
@@ -7068,7 +7312,7 @@ function CarouselOverlay({
7068
7312
  onEdit(hover.key);
7069
7313
  },
7070
7314
  children: [
7071
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.GalleryHorizontal, { size: 14 }),
7315
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
7072
7316
  "Edit gallery"
7073
7317
  ]
7074
7318
  }
@@ -7079,7 +7323,7 @@ function CarouselOverlay({
7079
7323
 
7080
7324
  // src/ui/ai-section/AiSectionOverlay.tsx
7081
7325
  var import_react8 = require("react");
7082
- var import_lucide_react6 = require("lucide-react");
7326
+ var import_lucide_react7 = require("lucide-react");
7083
7327
 
7084
7328
  // src/lib/sections.ts
7085
7329
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
@@ -7109,13 +7353,9 @@ function parseSectionsFromHtml(html) {
7109
7353
  }
7110
7354
 
7111
7355
  // src/ui/ai-section/AiSectionOverlay.tsx
7112
- var import_jsx_runtime16 = require("react/jsx-runtime");
7113
- function findSectionElement(instanceId) {
7114
- const escaped = CSS.escape(instanceId);
7115
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7116
- }
7117
- function readRect(instanceId) {
7118
- const el = findSectionElement(instanceId);
7356
+ var import_jsx_runtime17 = require("react/jsx-runtime");
7357
+ function readRect(sectionId) {
7358
+ const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7119
7359
  if (!el) return null;
7120
7360
  const r2 = el.getBoundingClientRect();
7121
7361
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -7138,7 +7378,7 @@ function useLiveSectionRect(sectionId) {
7138
7378
  const opts = { capture: true, passive: true };
7139
7379
  window.addEventListener("scroll", update, opts);
7140
7380
  window.addEventListener("resize", update);
7141
- const el = findSectionElement(sectionId);
7381
+ const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7142
7382
  const ro = el ? new ResizeObserver(update) : null;
7143
7383
  if (el && ro) ro.observe(el);
7144
7384
  const interval = setInterval(update, 500);
@@ -7151,14 +7391,6 @@ function useLiveSectionRect(sectionId) {
7151
7391
  }, [sectionId]);
7152
7392
  return rect;
7153
7393
  }
7154
- function computeSectionBoundaryFlags(instanceId) {
7155
- const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7156
- (el) => !el.parentElement?.closest("[data-ohw-section]")
7157
- );
7158
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7159
- if (index === -1) return { isFirst: true, isLast: true };
7160
- return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7161
- }
7162
7394
  var PRIMARY2 = "#0885FE";
7163
7395
  function edgeAwareRadius(rect) {
7164
7396
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7177,8 +7409,8 @@ function ReviewButton({
7177
7409
  color
7178
7410
  }) {
7179
7411
  const [hover, setHover] = (0, import_react8.useState)(false);
7180
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { position: "relative" }, children: [
7181
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7412
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { position: "relative" }, children: [
7413
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7182
7414
  "button",
7183
7415
  {
7184
7416
  type: "button",
@@ -7202,7 +7434,7 @@ function ReviewButton({
7202
7434
  children
7203
7435
  }
7204
7436
  ),
7205
- hover && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7437
+ hover && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7206
7438
  "div",
7207
7439
  {
7208
7440
  style: {
@@ -7232,7 +7464,6 @@ function AiSectionOverlay({
7232
7464
  }) {
7233
7465
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
7234
7466
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
7235
- const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
7236
7467
  const reviewIdRef = (0, import_react8.useRef)(null);
7237
7468
  reviewIdRef.current = reviewId;
7238
7469
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -7241,7 +7472,7 @@ function AiSectionOverlay({
7241
7472
  (el) => {
7242
7473
  postToParent2({
7243
7474
  type: "ow:section-selected",
7244
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7475
+ sectionId: el?.dataset.ohwSection ?? null,
7245
7476
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7246
7477
  });
7247
7478
  },
@@ -7250,7 +7481,7 @@ function AiSectionOverlay({
7250
7481
  const selectFromElement = (0, import_react8.useCallback)(
7251
7482
  (el, options) => {
7252
7483
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7253
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7484
+ const id = sectionEl?.dataset.ohwSection ?? null;
7254
7485
  if (id === selectedIdRef.current) return;
7255
7486
  setSelectedId(id);
7256
7487
  if (options?.report !== false) report(sectionEl);
@@ -7291,10 +7522,9 @@ function AiSectionOverlay({
7291
7522
  }
7292
7523
  const found = readRect(sectionId) != null;
7293
7524
  setReviewId(found ? sectionId : null);
7294
- setReviewButtonsHidden(e.data.hideButtons === true);
7295
7525
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7296
7526
  if (found) {
7297
- document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7527
+ document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7298
7528
  }
7299
7529
  }
7300
7530
  };
@@ -7313,7 +7543,7 @@ function AiSectionOverlay({
7313
7543
  return;
7314
7544
  }
7315
7545
  const sec = t.closest("[data-ohw-section]");
7316
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7546
+ setHoveredId(sec?.dataset.ohwSection ?? null);
7317
7547
  };
7318
7548
  const onLeave = () => setHoveredId(null);
7319
7549
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7345,31 +7575,11 @@ function AiSectionOverlay({
7345
7575
  },
7346
7576
  [postToParent2]
7347
7577
  );
7348
- const activeSelectionId = reviewId ? null : selectedId;
7349
- const selectionRect = useLiveSectionRect(activeSelectionId);
7578
+ const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7350
7579
  const reviewRect = useLiveSectionRect(reviewId);
7351
7580
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7352
- (0, import_react8.useEffect)(() => {
7353
- if (!activeSelectionId || !selectionRect) {
7354
- postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7355
- return;
7356
- }
7357
- const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7358
- postToParent2({
7359
- type: "ow:section-rect",
7360
- instanceId: activeSelectionId,
7361
- rect: {
7362
- top: selectionRect.top + window.scrollY,
7363
- left: selectionRect.left + window.scrollX,
7364
- width: selectionRect.width,
7365
- height: selectionRect.height
7366
- },
7367
- isFirst,
7368
- isLast
7369
- });
7370
- }, [activeSelectionId, selectionRect, postToParent2]);
7371
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7372
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7581
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
7582
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7373
7583
  "div",
7374
7584
  {
7375
7585
  "data-ohw-ai-section-hover": "",
@@ -7387,7 +7597,7 @@ function AiSectionOverlay({
7387
7597
  }
7388
7598
  }
7389
7599
  ),
7390
- selectionRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7600
+ selectionRect && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7391
7601
  "div",
7392
7602
  {
7393
7603
  "data-ohw-ai-section-selected": "",
@@ -7405,7 +7615,7 @@ function AiSectionOverlay({
7405
7615
  }
7406
7616
  }
7407
7617
  ),
7408
- reviewRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7618
+ reviewRect && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7409
7619
  "div",
7410
7620
  {
7411
7621
  "data-ohw-ai-review": "",
@@ -7418,16 +7628,13 @@ function AiSectionOverlay({
7418
7628
  border: `2px solid ${PRIMARY2}`,
7419
7629
  borderRadius: edgeAwareRadius(reviewRect),
7420
7630
  zIndex: 2147483200,
7421
- // The veil itself: swallows clicks so the section stays locked until decided. This
7422
- // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7423
- // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7424
- // Accept/Discard resolves to the media beneath and opens the file picker.
7631
+ // The veil itself: swallows clicks so the section stays locked until decided.
7425
7632
  background: "rgba(8, 133, 254, 0.04)",
7426
7633
  pointerEvents: "auto",
7427
7634
  cursor: "default"
7428
7635
  },
7429
7636
  onClick: (e) => e.stopPropagation(),
7430
- children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7637
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7431
7638
  "div",
7432
7639
  {
7433
7640
  style: {
@@ -7440,8 +7647,8 @@ function AiSectionOverlay({
7440
7647
  paddingTop: 12
7441
7648
  },
7442
7649
  children: [
7443
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ReviewButton, { label: "Accept", onClick: () => decide("accept"), background: PRIMARY2, color: "#ffffff", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.Check, { size: 16, strokeWidth: 2.5, "aria-hidden": true }) }),
7444
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ReviewButton, { label: "Discard", onClick: () => decide("discard"), background: "#EFF6FF", color: "#0c0a09", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.X, { size: 16, strokeWidth: 2, "aria-hidden": true }) })
7650
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ReviewButton, { label: "Accept", onClick: () => decide("accept"), background: PRIMARY2, color: "#ffffff", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.Check, { size: 16, strokeWidth: 2.5, "aria-hidden": true }) }),
7651
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ReviewButton, { label: "Discard", onClick: () => decide("discard"), background: "#EFF6FF", color: "#0c0a09", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, strokeWidth: 2, "aria-hidden": true }) })
7445
7652
  ]
7446
7653
  }
7447
7654
  )
@@ -7800,23 +8007,23 @@ var import_react12 = require("react");
7800
8007
  // src/ui/dialog.tsx
7801
8008
  var React8 = __toESM(require("react"), 1);
7802
8009
  var import_radix_ui6 = require("radix-ui");
7803
- var import_lucide_react7 = require("lucide-react");
7804
- var import_jsx_runtime17 = require("react/jsx-runtime");
8010
+ var import_lucide_react8 = require("lucide-react");
8011
+ var import_jsx_runtime18 = require("react/jsx-runtime");
7805
8012
  function Dialog2({
7806
8013
  ...props
7807
8014
  }) {
7808
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
8015
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
7809
8016
  }
7810
8017
  function DialogPortal({
7811
8018
  ...props
7812
8019
  }) {
7813
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
8020
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
7814
8021
  }
7815
8022
  function DialogOverlay({
7816
8023
  className,
7817
8024
  ...props
7818
8025
  }) {
7819
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8026
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7820
8027
  import_radix_ui6.Dialog.Overlay,
7821
8028
  {
7822
8029
  "data-slot": "dialog-overlay",
@@ -7829,9 +8036,9 @@ function DialogOverlay({
7829
8036
  var DialogContent = React8.forwardRef(
7830
8037
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
7831
8038
  const positionMode = container ? "absolute" : "fixed";
7832
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(DialogPortal, { container: container ?? void 0, children: [
7833
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
7834
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8039
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogPortal, { container: container ?? void 0, children: [
8040
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
8041
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
7835
8042
  import_radix_ui6.Dialog.Content,
7836
8043
  {
7837
8044
  ref,
@@ -7847,13 +8054,13 @@ var DialogContent = React8.forwardRef(
7847
8054
  ...props,
7848
8055
  children: [
7849
8056
  children,
7850
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8057
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7851
8058
  import_radix_ui6.Dialog.Close,
7852
8059
  {
7853
8060
  type: "button",
7854
8061
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
7855
8062
  "aria-label": "Close",
7856
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, "aria-hidden": true })
8063
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
7857
8064
  }
7858
8065
  ) : null
7859
8066
  ]
@@ -7867,13 +8074,13 @@ function DialogHeader({
7867
8074
  className,
7868
8075
  ...props
7869
8076
  }) {
7870
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
8077
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
7871
8078
  }
7872
8079
  function DialogFooter({
7873
8080
  className,
7874
8081
  ...props
7875
8082
  }) {
7876
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8083
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7877
8084
  "div",
7878
8085
  {
7879
8086
  className: cn("flex items-center justify-end gap-2", className),
@@ -7881,7 +8088,7 @@ function DialogFooter({
7881
8088
  }
7882
8089
  );
7883
8090
  }
7884
- var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8091
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7885
8092
  import_radix_ui6.Dialog.Title,
7886
8093
  {
7887
8094
  ref,
@@ -7893,7 +8100,7 @@ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE
7893
8100
  }
7894
8101
  ));
7895
8102
  DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
7896
- var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8103
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7897
8104
  import_radix_ui6.Dialog.Description,
7898
8105
  {
7899
8106
  ref,
@@ -7905,63 +8112,63 @@ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
7905
8112
  var DialogClose = import_radix_ui6.Dialog.Close;
7906
8113
 
7907
8114
  // src/ui/link-modal/LinkEditorPanel.tsx
7908
- var import_lucide_react11 = require("lucide-react");
8115
+ var import_lucide_react12 = require("lucide-react");
7909
8116
 
7910
8117
  // src/ui/link-modal/DestinationBreadcrumb.tsx
7911
- var import_lucide_react8 = require("lucide-react");
7912
- var import_jsx_runtime18 = require("react/jsx-runtime");
8118
+ var import_lucide_react9 = require("lucide-react");
8119
+ var import_jsx_runtime19 = require("react/jsx-runtime");
7913
8120
  function DestinationBreadcrumb({
7914
8121
  pageTitle,
7915
8122
  sectionLabel
7916
8123
  }) {
7917
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
7918
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
7919
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-3", children: [
7920
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
7921
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
7922
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
8124
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
8125
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
8126
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-3", children: [
8127
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", children: [
8128
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
8129
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
7923
8130
  ] }),
7924
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7925
- import_lucide_react8.ArrowRight,
8131
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
8132
+ import_lucide_react9.ArrowRight,
7926
8133
  {
7927
8134
  size: 16,
7928
8135
  className: "shrink-0 text-muted-foreground",
7929
8136
  "aria-hidden": true
7930
8137
  }
7931
8138
  ),
7932
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
7933
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7934
- import_lucide_react8.GalleryVertical,
8139
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
8140
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
8141
+ import_lucide_react9.GalleryVertical,
7935
8142
  {
7936
8143
  size: 16,
7937
8144
  className: "shrink-0 text-foreground",
7938
8145
  "aria-hidden": true
7939
8146
  }
7940
8147
  ),
7941
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
8148
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
7942
8149
  ] })
7943
8150
  ] })
7944
8151
  ] });
7945
8152
  }
7946
8153
 
7947
8154
  // src/ui/link-modal/SectionTreeItem.tsx
7948
- var import_lucide_react9 = require("lucide-react");
7949
- var import_jsx_runtime19 = require("react/jsx-runtime");
8155
+ var import_lucide_react10 = require("lucide-react");
8156
+ var import_jsx_runtime20 = require("react/jsx-runtime");
7950
8157
  function SectionTreeItem({
7951
8158
  section,
7952
8159
  onSelect,
7953
8160
  selected
7954
8161
  }) {
7955
8162
  const interactive = Boolean(onSelect);
7956
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
7957
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
8163
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
8164
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7958
8165
  "div",
7959
8166
  {
7960
8167
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
7961
8168
  "aria-hidden": true
7962
8169
  }
7963
8170
  ),
7964
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
8171
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
7965
8172
  "div",
7966
8173
  {
7967
8174
  role: interactive ? "button" : void 0,
@@ -7979,15 +8186,15 @@ function SectionTreeItem({
7979
8186
  interactive && selected && "border-primary"
7980
8187
  ),
7981
8188
  children: [
7982
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7983
- import_lucide_react9.GalleryVertical,
8189
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
8190
+ import_lucide_react10.GalleryVertical,
7984
8191
  {
7985
8192
  size: 16,
7986
8193
  className: "shrink-0 text-foreground",
7987
8194
  "aria-hidden": true
7988
8195
  }
7989
8196
  ),
7990
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
8197
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
7991
8198
  ]
7992
8199
  }
7993
8200
  )
@@ -7999,10 +8206,10 @@ var import_react9 = require("react");
7999
8206
 
8000
8207
  // src/ui/input.tsx
8001
8208
  var React9 = __toESM(require("react"), 1);
8002
- var import_jsx_runtime20 = require("react/jsx-runtime");
8209
+ var import_jsx_runtime21 = require("react/jsx-runtime");
8003
8210
  var Input = React9.forwardRef(
8004
8211
  ({ className, type, ...props }, ref) => {
8005
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
8212
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8006
8213
  "input",
8007
8214
  {
8008
8215
  type,
@@ -8021,9 +8228,9 @@ Input.displayName = "Input";
8021
8228
 
8022
8229
  // src/ui/label.tsx
8023
8230
  var import_radix_ui7 = require("radix-ui");
8024
- var import_jsx_runtime21 = require("react/jsx-runtime");
8231
+ var import_jsx_runtime22 = require("react/jsx-runtime");
8025
8232
  function Label({ className, ...props }) {
8026
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8233
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8027
8234
  import_radix_ui7.Label.Root,
8028
8235
  {
8029
8236
  "data-slot": "label",
@@ -8034,12 +8241,12 @@ function Label({ className, ...props }) {
8034
8241
  }
8035
8242
 
8036
8243
  // src/ui/link-modal/UrlOrPageInput.tsx
8037
- var import_lucide_react10 = require("lucide-react");
8038
- var import_jsx_runtime22 = require("react/jsx-runtime");
8244
+ var import_lucide_react11 = require("lucide-react");
8245
+ var import_jsx_runtime23 = require("react/jsx-runtime");
8039
8246
  function FieldChevron({
8040
8247
  onClick
8041
8248
  }) {
8042
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8249
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8043
8250
  "button",
8044
8251
  {
8045
8252
  type: "button",
@@ -8047,7 +8254,7 @@ function FieldChevron({
8047
8254
  onClick,
8048
8255
  "aria-label": "Open page list",
8049
8256
  tabIndex: -1,
8050
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16 })
8257
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.ChevronDown, { size: 16 })
8051
8258
  }
8052
8259
  );
8053
8260
  }
@@ -8108,19 +8315,19 @@ function UrlOrPageInput({
8108
8315
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
8109
8316
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
8110
8317
  );
8111
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
8112
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
8113
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
8114
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
8115
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8116
- import_lucide_react10.File,
8318
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
8319
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
8320
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
8321
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
8322
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8323
+ import_lucide_react11.File,
8117
8324
  {
8118
8325
  size: 16,
8119
8326
  className: "shrink-0 text-foreground",
8120
8327
  "aria-hidden": true
8121
8328
  }
8122
8329
  ) }) : null,
8123
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8330
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8124
8331
  Input,
8125
8332
  {
8126
8333
  ref: inputRef,
@@ -8146,7 +8353,7 @@ function UrlOrPageInput({
8146
8353
  )
8147
8354
  }
8148
8355
  ),
8149
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8356
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8150
8357
  "button",
8151
8358
  {
8152
8359
  type: "button",
@@ -8154,26 +8361,26 @@ function UrlOrPageInput({
8154
8361
  onMouseDown: clearSelection,
8155
8362
  "aria-label": "Clear selected page",
8156
8363
  tabIndex: -1,
8157
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.X, { size: 16, "aria-hidden": true })
8364
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.X, { size: 16, "aria-hidden": true })
8158
8365
  }
8159
8366
  ) : null,
8160
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
8367
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
8161
8368
  ] }),
8162
- dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8369
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8163
8370
  "div",
8164
8371
  {
8165
8372
  "data-ohw-link-page-dropdown": "",
8166
8373
  className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
8167
8374
  onMouseDown: (e) => e.preventDefault(),
8168
- children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8375
+ children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8169
8376
  "button",
8170
8377
  {
8171
8378
  type: "button",
8172
8379
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
8173
8380
  onClick: () => onPageSelect(page),
8174
8381
  children: [
8175
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
8176
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate", children: page.title })
8382
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
8383
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate", children: page.title })
8177
8384
  ]
8178
8385
  },
8179
8386
  page.path
@@ -8181,34 +8388,34 @@ function UrlOrPageInput({
8181
8388
  }
8182
8389
  ) : null
8183
8390
  ] }),
8184
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
8391
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
8185
8392
  ] });
8186
8393
  }
8187
8394
 
8188
8395
  // src/ui/link-modal/LinkEditorPanel.tsx
8189
- var import_jsx_runtime23 = require("react/jsx-runtime");
8396
+ var import_jsx_runtime24 = require("react/jsx-runtime");
8190
8397
  function LinkEditorPanel({ state, onClose }) {
8191
8398
  const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
8192
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8193
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8399
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
8400
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8194
8401
  "button",
8195
8402
  {
8196
8403
  type: "button",
8197
8404
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
8198
8405
  "aria-label": "Close",
8199
8406
  onClick: onClose,
8200
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.X, { size: 16, "aria-hidden": true })
8407
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
8201
8408
  }
8202
8409
  ) }),
8203
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
8204
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
8205
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8410
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
8411
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
8412
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8206
8413
  DestinationBreadcrumb,
8207
8414
  {
8208
8415
  pageTitle: state.selectedPage.title,
8209
8416
  sectionLabel: state.selectedSection.label
8210
8417
  }
8211
- ) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8418
+ ) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8212
8419
  UrlOrPageInput,
8213
8420
  {
8214
8421
  value: state.searchValue,
@@ -8221,8 +8428,8 @@ function LinkEditorPanel({ state, onClose }) {
8221
8428
  urlError: state.urlError
8222
8429
  }
8223
8430
  ),
8224
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
8225
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8431
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
8432
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8226
8433
  Button,
8227
8434
  {
8228
8435
  type: "button",
@@ -8233,15 +8440,15 @@ function LinkEditorPanel({ state, onClose }) {
8233
8440
  children: "Choose a section"
8234
8441
  }
8235
8442
  ),
8236
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
8237
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
8238
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: "Pick a section this link should scroll to." })
8443
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
8444
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
8445
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: "Pick a section this link should scroll to." })
8239
8446
  ] })
8240
8447
  ] }) : null,
8241
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
8448
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
8242
8449
  ] }),
8243
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
8244
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8450
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
8451
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8245
8452
  Button,
8246
8453
  {
8247
8454
  type: "button",
@@ -8256,7 +8463,7 @@ function LinkEditorPanel({ state, onClose }) {
8256
8463
  children: state.secondaryLabel
8257
8464
  }
8258
8465
  ),
8259
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8466
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8260
8467
  Button,
8261
8468
  {
8262
8469
  type: "button",
@@ -8277,9 +8484,9 @@ function LinkEditorPanel({ state, onClose }) {
8277
8484
  // src/ui/link-modal/SectionPickerOverlay.tsx
8278
8485
  var import_react10 = require("react");
8279
8486
  var import_react_dom2 = require("react-dom");
8280
- var import_lucide_react12 = require("lucide-react");
8487
+ var import_lucide_react13 = require("lucide-react");
8281
8488
  var import_navigation2 = require("next/navigation");
8282
- var import_jsx_runtime24 = require("react/jsx-runtime");
8489
+ var import_jsx_runtime25 = require("react/jsx-runtime");
8283
8490
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
8284
8491
  function rectsEqual(a, b) {
8285
8492
  if (a.size !== b.size) return false;
@@ -8508,7 +8715,7 @@ function SectionPickerOverlay({
8508
8715
  const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
8509
8716
  if (!portalRoot) return null;
8510
8717
  return (0, import_react_dom2.createPortal)(
8511
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
8718
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
8512
8719
  "div",
8513
8720
  {
8514
8721
  "data-ohw-section-picker": "",
@@ -8518,12 +8725,12 @@ function SectionPickerOverlay({
8518
8725
  role: "dialog",
8519
8726
  "aria-label": "Choose a section",
8520
8727
  children: [
8521
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8728
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8522
8729
  "div",
8523
8730
  {
8524
8731
  className: "pointer-events-auto fixed left-5 z-[2]",
8525
8732
  style: { top: chromeClip.top + 20 },
8526
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
8733
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
8527
8734
  Button,
8528
8735
  {
8529
8736
  type: "button",
@@ -8532,14 +8739,14 @@ function SectionPickerOverlay({
8532
8739
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
8533
8740
  onClick: onBack,
8534
8741
  children: [
8535
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
8742
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react13.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
8536
8743
  "Back"
8537
8744
  ]
8538
8745
  }
8539
8746
  )
8540
8747
  }
8541
8748
  ),
8542
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8749
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8543
8750
  "div",
8544
8751
  {
8545
8752
  className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
@@ -8552,7 +8759,7 @@ function SectionPickerOverlay({
8552
8759
  children: "Click on section to select"
8553
8760
  }
8554
8761
  ),
8555
- !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8762
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8556
8763
  "div",
8557
8764
  {
8558
8765
  className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md px-3 py-2 text-sm text-muted-foreground shadow-sm",
@@ -8560,14 +8767,14 @@ function SectionPickerOverlay({
8560
8767
  children: "Loading page preview\u2026"
8561
8768
  }
8562
8769
  ) : null,
8563
- isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
8770
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
8564
8771
  isOnTargetPage ? liveSections.map((section) => {
8565
8772
  const rect = rects.get(section.id);
8566
8773
  if (!rect || rect.width <= 0 || rect.height <= 0) return null;
8567
8774
  const isSelected = selectedId === section.id;
8568
8775
  const isHovered = hoveredId === section.id;
8569
8776
  const isLit = isSelected || isHovered;
8570
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
8777
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
8571
8778
  "button",
8572
8779
  {
8573
8780
  type: "button",
@@ -8582,7 +8789,7 @@ function SectionPickerOverlay({
8582
8789
  "aria-label": `Select section ${section.label}`,
8583
8790
  onClick: () => handleSelect(section),
8584
8791
  children: [
8585
- isLit ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8792
+ isLit ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8586
8793
  "span",
8587
8794
  {
8588
8795
  className: "pointer-events-none absolute",
@@ -8595,13 +8802,13 @@ function SectionPickerOverlay({
8595
8802
  "aria-hidden": true
8596
8803
  }
8597
8804
  ) : null,
8598
- isSelected ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8805
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8599
8806
  "span",
8600
8807
  {
8601
8808
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
8602
8809
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
8603
8810
  "aria-hidden": true,
8604
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Check, { className: "size-5" })
8811
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react13.Check, { className: "size-5" })
8605
8812
  }
8606
8813
  ) : null
8607
8814
  ]
@@ -8781,7 +8988,7 @@ function useLinkModalState({
8781
8988
  }
8782
8989
 
8783
8990
  // src/ui/link-modal/LinkPopover.tsx
8784
- var import_jsx_runtime25 = require("react/jsx-runtime");
8991
+ var import_jsx_runtime26 = require("react/jsx-runtime");
8785
8992
  function postToParent(data) {
8786
8993
  window.parent?.postMessage(data, "*");
8787
8994
  }
@@ -8877,15 +9084,15 @@ function LinkPopover({
8877
9084
  );
8878
9085
  };
8879
9086
  }, [open, sectionPickerActive]);
8880
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
8881
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9087
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
9088
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
8882
9089
  Dialog2,
8883
9090
  {
8884
9091
  open: open && !sectionPickerActive,
8885
9092
  onOpenChange: (next) => {
8886
9093
  if (!next) onClose?.();
8887
9094
  },
8888
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9095
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
8889
9096
  DialogContent,
8890
9097
  {
8891
9098
  ref: panelRef,
@@ -8895,12 +9102,12 @@ function LinkPopover({
8895
9102
  "data-ohw-bridge": "",
8896
9103
  showCloseButton: false,
8897
9104
  className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
8898
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LinkEditorPanel, { state, onClose })
9105
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LinkEditorPanel, { state, onClose })
8899
9106
  }
8900
9107
  )
8901
9108
  }
8902
9109
  ),
8903
- sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9110
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
8904
9111
  SectionPickerOverlay,
8905
9112
  {
8906
9113
  pagePath: state.selectedPage.path,
@@ -10126,13 +10333,14 @@ function listSocialItems(row) {
10126
10333
  return isSocialItem(anchor) ? anchor : null;
10127
10334
  }).filter((item) => item !== null);
10128
10335
  }
10129
- function socialRowUnit(item) {
10130
- const row = findSocialsRow(item);
10336
+ function socialRowUnit(item, knownRow) {
10337
+ const row = knownRow ?? findSocialsRow(item);
10338
+ if (!row || !row.contains(item)) return null;
10131
10339
  let node = item;
10132
10340
  while (node.parentElement && node.parentElement !== row) {
10133
10341
  node = node.parentElement;
10134
10342
  }
10135
- return node;
10343
+ return node.parentElement === row ? node : null;
10136
10344
  }
10137
10345
  function listSocialsRows(root = document) {
10138
10346
  const rows = /* @__PURE__ */ new Set();
@@ -10151,7 +10359,8 @@ function markSocialsRows(root = document) {
10151
10359
  listSocialsRows(root).forEach((row) => {
10152
10360
  row.setAttribute(SOCIALS_ROW_ATTR, "");
10153
10361
  const items = listSocialItems(row);
10154
- if (items[0]) rowTemplates.set(rowKeyOf(row), socialRowUnit(items[0]).outerHTML);
10362
+ const firstUnit = items[0] ? socialRowUnit(items[0], row) : null;
10363
+ if (firstUnit) rowTemplates.set(rowKeyOf(row), firstUnit.outerHTML);
10155
10364
  items.forEach((item, index) => {
10156
10365
  item.setAttribute(SOCIALS_ITEM_ATTR, String(index));
10157
10366
  const iconKey = socialIconKey(item);
@@ -10295,7 +10504,8 @@ function removeSocialItem(item, content) {
10295
10504
  const previousContent = Object.fromEntries(
10296
10505
  removedKeys.filter((key) => key in content).map((key) => [key, content[key]])
10297
10506
  );
10298
- const unit = socialRowUnit(item);
10507
+ const unit = socialRowUnit(item, row);
10508
+ if (!unit) return null;
10299
10509
  const nextSibling = unit.nextElementSibling;
10300
10510
  unit.remove();
10301
10511
  markSocialsRows(row.ownerDocument);
@@ -10318,7 +10528,9 @@ function applySocialsOrder(order, root = document) {
10318
10528
  const byKey = new Map(listSocialItems(row).map((item) => [socialHrefKey(item), item]));
10319
10529
  wanted.forEach((key) => {
10320
10530
  const item = byKey.get(key);
10321
- if (item) row.appendChild(socialRowUnit(item));
10531
+ if (!item) return;
10532
+ const unit = socialRowUnit(item, row);
10533
+ if (unit) row.appendChild(unit);
10322
10534
  });
10323
10535
  });
10324
10536
  markSocialsRows(root);
@@ -10348,7 +10560,7 @@ function reconcileSocialsFromContent(content, root = document) {
10348
10560
  });
10349
10561
  if (surviving.length) {
10350
10562
  present.forEach((item) => {
10351
- if (!surviving.includes(item)) socialRowUnit(item).remove();
10563
+ if (!surviving.includes(item)) socialRowUnit(item)?.remove();
10352
10564
  });
10353
10565
  }
10354
10566
  });
@@ -11327,296 +11539,42 @@ function deleteFooterColumn(column) {
11327
11539
  };
11328
11540
  }
11329
11541
 
11330
- // src/lib/logo-identity.ts
11331
- var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11332
- var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11333
- var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11334
- var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11335
- var LOGO_ALT_KEY = "logo-alt";
11336
- var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11337
- var PLACEHOLDER_BUSINESS_NAME = "Business name";
11338
- function resolveLogoDisplayText(text) {
11339
- const trimmed = (text ?? "").trim();
11340
- return trimmed || PLACEHOLDER_BUSINESS_NAME;
11341
- }
11342
- function isFooterLogoRoot(root) {
11343
- return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11344
- }
11345
- function imageKeyForRoot(root) {
11346
- return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11347
- }
11348
- function textKeyForRoot(root) {
11349
- return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11350
- }
11351
- function ensureLogoHrefKey(root) {
11352
- if (!(root instanceof HTMLAnchorElement)) return;
11353
- if (root.hasAttribute("data-ohw-href-key")) return;
11354
- root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11355
- }
11356
- function applyLogoIdentity(text, isPlaceholder) {
11357
- const display = resolveLogoDisplayText(text);
11358
- const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11359
- for (const key of LOGO_TEXT_KEYS) {
11360
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11361
- if (el.textContent !== display) el.textContent = display;
11362
- });
11363
- }
11364
- for (const key of LOGO_IMAGE_KEYS) {
11365
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11366
- const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11367
- if (img) img.alt = display;
11368
- });
11369
- }
11370
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11371
- if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11372
- else el.removeAttribute("data-ohw-placeholder");
11373
- });
11374
- return display;
11375
- }
11376
- function applyLogoImage(url, alt) {
11377
- const displayAlt = resolveLogoDisplayText(alt);
11378
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11379
- ensureLogoHrefKey(root);
11380
- const imageKey = imageKeyForRoot(root);
11381
- const textKey = textKeyForRoot(root);
11382
- let img = root.querySelector(`img[data-ohw-key="${imageKey}"]`) ?? (root.querySelector(`[data-ohw-key="${imageKey}"]`) instanceof HTMLImageElement ? root.querySelector(`[data-ohw-key="${imageKey}"]`) : null) ?? root.querySelector("img");
11383
- let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11384
- if (url) {
11385
- if (!img) {
11386
- img = document.createElement("img");
11387
- img.setAttribute("data-ohw-editable", "image");
11388
- img.setAttribute("data-ohw-key", imageKey);
11389
- img.alt = displayAlt;
11390
- img.style.height = "";
11391
- img.style.maxHeight = "none";
11392
- img.style.width = "auto";
11393
- img.style.display = "block";
11394
- img.style.objectFit = "contain";
11395
- root.insertBefore(img, root.firstChild);
11396
- } else {
11397
- img.setAttribute("data-ohw-editable", "image");
11398
- img.setAttribute("data-ohw-key", imageKey);
11399
- }
11400
- img.removeAttribute("srcset");
11401
- img.removeAttribute("sizes");
11402
- img.src = url;
11403
- img.alt = displayAlt;
11404
- img.style.display = "block";
11405
- if (textEl) textEl.style.display = "none";
11406
- root.removeAttribute("data-ohw-placeholder");
11407
- return;
11408
- }
11409
- if (img) {
11410
- img.removeAttribute("src");
11411
- img.removeAttribute("srcset");
11412
- img.removeAttribute("sizes");
11413
- img.alt = displayAlt;
11414
- img.style.display = "none";
11415
- }
11416
- if (!textEl) {
11417
- textEl = document.createElement("span");
11418
- textEl.setAttribute("data-ohw-editable", "plain");
11419
- textEl.setAttribute("data-ohw-key", textKey);
11420
- root.appendChild(textEl);
11421
- }
11422
- textEl.style.display = "";
11423
- if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11424
- if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11425
- root.setAttribute("data-ohw-placeholder", "");
11426
- } else {
11427
- root.removeAttribute("data-ohw-placeholder");
11428
- }
11429
- });
11430
- }
11431
- function applyLogoHref(href) {
11432
- const target = href.trim() || "/";
11433
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11434
- ensureLogoHrefKey(root);
11435
- if (root instanceof HTMLAnchorElement) {
11436
- root.setAttribute("href", target);
11437
- }
11438
- });
11439
- for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11440
- }
11441
- function readLogoIdentityFromDom() {
11442
- let imageUrl = null;
11443
- for (const key of LOGO_IMAGE_KEYS) {
11444
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
11445
- const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11446
- const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11447
- if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11448
- imageUrl = img.currentSrc || img.src;
11449
- break;
11450
- }
11451
- }
11452
- let text = PLACEHOLDER_BUSINESS_NAME;
11453
- let isPlaceholder = true;
11454
- for (const key of LOGO_TEXT_KEYS) {
11455
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
11456
- if (el?.textContent?.trim()) {
11457
- text = el.textContent.trim();
11458
- const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11459
- isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11460
- break;
11461
- }
11462
- }
11463
- if (imageUrl) {
11464
- const logoImg = document.querySelector(
11465
- '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11466
- );
11467
- const alt = logoImg?.alt?.trim() || text;
11468
- isPlaceholder = false;
11469
- const hrefEl = document.querySelector(
11470
- 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11471
- );
11472
- const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11473
- return { text, isPlaceholder, imageUrl, href: href2, alt };
11474
- }
11475
- const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11476
- const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11477
- return { text, isPlaceholder, imageUrl: null, href, alt: text };
11478
- }
11479
- function applyLogoFromContent(content) {
11480
- const hasLogoIdentity = LOGO_PLACEHOLDER_KEY in content || LOGO_TEXT_KEYS.some((key) => key in content) || LOGO_IMAGE_KEYS.some((key) => key in content) || LOGO_ALT_KEY in content || LOGO_HREF_KEYS.some((key) => key in content);
11481
- if (!hasLogoIdentity) return false;
11482
- const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11483
- const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11484
- const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11485
- const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11486
- const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11487
- const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11488
- if (logoImageUrl) {
11489
- applyLogoImage(logoImageUrl, logoAlt);
11490
- } else {
11491
- if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11492
- applyLogoIdentity(logoText, logoIsPlaceholder);
11493
- }
11494
- const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11495
- if (typeof logoHref === "string" && logoHref.trim()) {
11496
- applyLogoHref(logoHref);
11497
- }
11498
- return true;
11499
- }
11500
-
11501
- // src/lib/logo-size.ts
11502
- var LOGO_SIZE_DEFAULTS = {
11503
- navbar: 28,
11504
- footer: 32
11505
- };
11506
- var LOGO_SIZE_MIN = 16;
11507
- var LOGO_SIZE_MAX = 80;
11508
- var LOGO_SIZE_DESKTOP_KEYS = {
11509
- navbar: "nav-logo-size",
11510
- footer: "footer-logo-size"
11511
- };
11512
- var LOGO_SIZE_MOBILE_KEYS = {
11513
- navbar: "nav-logo-size-mobile",
11514
- footer: "footer-logo-size-mobile"
11515
- };
11516
- var LOGO_SIZE_KEYS = [
11517
- LOGO_SIZE_DESKTOP_KEYS.navbar,
11518
- LOGO_SIZE_DESKTOP_KEYS.footer,
11519
- LOGO_SIZE_MOBILE_KEYS.navbar,
11520
- LOGO_SIZE_MOBILE_KEYS.footer
11521
- ];
11522
- function isFooterLogoRoot2(root) {
11523
- return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11524
- }
11525
- function getLogoPlacement(root) {
11526
- return isFooterLogoRoot2(root) ? "footer" : "navbar";
11527
- }
11528
- function parseLogoSizePx(raw, fallback) {
11529
- if (raw == null || raw === "") return fallback;
11530
- const n = Number.parseFloat(raw);
11531
- if (!Number.isFinite(n)) return fallback;
11532
- return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11533
- }
11534
- function isMobileLogoSizeFollowing(content, placement) {
11535
- const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11536
- return raw == null || raw.trim() === "";
11537
- }
11538
- function resolveDesktopLogoSize(content, placement) {
11539
- return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11540
- }
11541
- function resolveMobileLogoSize(content, placement) {
11542
- if (isMobileLogoSizeFollowing(content, placement)) {
11543
- return resolveDesktopLogoSize(content, placement);
11544
- }
11545
- return parseLogoSizePx(
11546
- content[LOGO_SIZE_MOBILE_KEYS[placement]],
11547
- resolveDesktopLogoSize(content, placement)
11548
- );
11542
+ // src/lib/add-footer-column.ts
11543
+ function buildFooterColumnEditContentPatch(result) {
11544
+ return {
11545
+ [result.headingKey]: result.heading,
11546
+ [result.hrefKey]: result.href,
11547
+ [result.labelKey]: result.label,
11548
+ [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
11549
+ };
11549
11550
  }
11550
- function setRootSizeVars(root, desktopPx, mobilePx, following) {
11551
- root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11552
- if (following) {
11553
- root.style.removeProperty("--ohw-logo-size-mobile");
11554
- } else {
11555
- root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11551
+ function addFooterColumnWithPersist({
11552
+ postToParent: postToParent2
11553
+ }) {
11554
+ if (!canAddFooterColumn()) {
11555
+ postToParent2({
11556
+ type: "ow:toast",
11557
+ title: `Maximum ${MAX_FOOTER_COLUMNS} columns`,
11558
+ toastType: "error"
11559
+ });
11560
+ return null;
11556
11561
  }
11557
- root.querySelectorAll("img").forEach((img) => {
11558
- img.style.height = "";
11559
- img.style.maxHeight = "none";
11560
- img.style.width = "auto";
11561
- img.style.objectFit = "contain";
11562
- });
11563
- }
11564
- function applyLogoSizes(content) {
11565
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11566
- const placement = getLogoPlacement(root);
11567
- const desktop = resolveDesktopLogoSize(content, placement);
11568
- const following = isMobileLogoSizeFollowing(content, placement);
11569
- const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11570
- setRootSizeVars(root, desktop, mobile, following);
11571
- });
11572
- }
11573
- function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11574
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11575
- if (getLogoPlacement(root) !== placement) return;
11576
- setRootSizeVars(root, desktopPx, mobilePx, following);
11562
+ const result = insertFooterColumn();
11563
+ const patch = buildFooterColumnEditContentPatch(result);
11564
+ setStoredLinkHref(result.hrefKey, result.href);
11565
+ postToParent2({
11566
+ type: "ow:change",
11567
+ nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
11577
11568
  });
11578
- }
11579
- function logoHasUploadedImage(logoEl) {
11580
- if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11581
- const img = logoEl.querySelector('img[data-ohw-key="nav-logo-image"], img[data-ohw-key="footer-logo"], img[data-ohw-key="footer-logo-image"]') ?? logoEl.querySelector("img");
11582
- if (!img) return false;
11583
- const src = img.getAttribute("src")?.trim() ?? "";
11584
- if (!src || src.startsWith("data:")) return false;
11585
- if (img.style.display === "none") return false;
11586
- return true;
11587
- }
11588
- function getLogoInteractionRect(logoEl) {
11589
- if (logoHasUploadedImage(logoEl)) {
11590
- const img = logoEl.querySelector('img[data-ohw-key="nav-logo-image"], img[data-ohw-key="footer-logo"], img[data-ohw-key="footer-logo-image"]') ?? logoEl.querySelector("img");
11591
- if (img) {
11592
- const r2 = img.getBoundingClientRect();
11593
- if (r2.width > 0 && r2.height > 0) return r2;
11594
- }
11595
- }
11596
- const text = logoEl.querySelector(
11597
- '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11598
- );
11599
- if (text) {
11600
- const style = window.getComputedStyle(text);
11601
- if (style.display !== "none" && style.visibility !== "hidden") {
11602
- const r2 = text.getBoundingClientRect();
11603
- if (r2.width > 0 && r2.height > 0) return r2;
11604
- }
11605
- }
11606
- return logoEl.getBoundingClientRect();
11607
- }
11608
- function readLogoSizeState(content, placement) {
11609
- const desktopPx = resolveDesktopLogoSize(content, placement);
11610
- const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11611
- const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11612
- return { desktopPx, mobilePx, mobileFollowing };
11569
+ postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
11570
+ enforceLinkHrefs();
11571
+ return result;
11613
11572
  }
11614
11573
 
11615
11574
  // src/lib/site-wide-scope.ts
11616
11575
  function getLogoElement(el) {
11617
11576
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11618
11577
  if (marked) return marked;
11619
- if (el.closest('[data-ohw-editable="icon"]')) return null;
11620
11578
  const root = el.closest("nav, [data-ohw-nav-root], footer");
11621
11579
  if (!root) return null;
11622
11580
  const anchor = el.closest("a");
@@ -11650,42 +11608,10 @@ function isSiteWideScopeActive(args) {
11650
11608
  return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11651
11609
  }
11652
11610
 
11653
- // src/lib/add-footer-column.ts
11654
- function buildFooterColumnEditContentPatch(result) {
11655
- return {
11656
- [result.headingKey]: result.heading,
11657
- [result.hrefKey]: result.href,
11658
- [result.labelKey]: result.label,
11659
- [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
11660
- };
11661
- }
11662
- function addFooterColumnWithPersist({
11663
- postToParent: postToParent2
11664
- }) {
11665
- if (!canAddFooterColumn()) {
11666
- postToParent2({
11667
- type: "ow:toast",
11668
- title: `Maximum ${MAX_FOOTER_COLUMNS} columns`,
11669
- toastType: "error"
11670
- });
11671
- return null;
11672
- }
11673
- const result = insertFooterColumn();
11674
- const patch = buildFooterColumnEditContentPatch(result);
11675
- setStoredLinkHref(result.hrefKey, result.href);
11676
- postToParent2({
11677
- type: "ow:change",
11678
- nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
11679
- });
11680
- postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
11681
- enforceLinkHrefs();
11682
- return result;
11683
- }
11684
-
11685
11611
  // src/ui/FloatingPanel.tsx
11686
11612
  var import_react13 = require("react");
11687
- var import_lucide_react13 = require("lucide-react");
11688
- var import_jsx_runtime26 = require("react/jsx-runtime");
11613
+ var import_lucide_react14 = require("lucide-react");
11614
+ var import_jsx_runtime27 = require("react/jsx-runtime");
11689
11615
  var PANEL_WIDTH = 256;
11690
11616
  var EDGE_MARGIN = 16;
11691
11617
  function getVisibleClip(parentScroll) {
@@ -11800,7 +11726,7 @@ function FloatingPanel({
11800
11726
  }, [open]);
11801
11727
  (0, import_react13.useEffect)(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
11802
11728
  if (!open) return null;
11803
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11729
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11804
11730
  "div",
11805
11731
  {
11806
11732
  ref: panelRef,
@@ -11817,7 +11743,7 @@ function FloatingPanel({
11817
11743
  onPointerDown: (e) => e.stopPropagation(),
11818
11744
  onClick: (e) => e.stopPropagation(),
11819
11745
  children: [
11820
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11746
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11821
11747
  "div",
11822
11748
  {
11823
11749
  "data-ohw-floating-panel-header": "",
@@ -11827,14 +11753,14 @@ function FloatingPanel({
11827
11753
  onPointerUp: endDrag,
11828
11754
  onPointerCancel: endDrag,
11829
11755
  children: [
11830
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
11831
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center gap-2", children: [
11832
- icon ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "shrink-0 text-foreground", children: icon }) : null,
11833
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "min-w-0 flex-1 text-lg font-semibold leading-7 text-foreground", children: title })
11756
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
11757
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center gap-2", children: [
11758
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "shrink-0 text-foreground", children: icon }) : null,
11759
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "min-w-0 flex-1 text-lg font-semibold leading-7 text-foreground", children: title })
11834
11760
  ] }),
11835
- context ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "w-full text-sm leading-5 text-muted-foreground", children: context }) : null
11761
+ context ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "w-full text-sm leading-5 text-muted-foreground", children: context }) : null
11836
11762
  ] }),
11837
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
11763
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11838
11764
  "button",
11839
11765
  {
11840
11766
  type: "button",
@@ -11846,13 +11772,13 @@ function FloatingPanel({
11846
11772
  onClose();
11847
11773
  },
11848
11774
  onPointerDown: (e) => e.stopPropagation(),
11849
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.X, { size: 16, "aria-hidden": true })
11775
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.X, { size: 16, "aria-hidden": true })
11850
11776
  }
11851
11777
  )
11852
11778
  ]
11853
11779
  }
11854
11780
  ),
11855
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
11781
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11856
11782
  "div",
11857
11783
  {
11858
11784
  "data-ohw-floating-panel-body": "",
@@ -11865,117 +11791,6 @@ function FloatingPanel({
11865
11791
  );
11866
11792
  }
11867
11793
 
11868
- // src/ui/logo-size-panel.tsx
11869
- var import_lucide_react14 = require("lucide-react");
11870
- var import_jsx_runtime27 = require("react/jsx-runtime");
11871
- function SizeSlider({
11872
- value,
11873
- onChange
11874
- }) {
11875
- const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11876
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-3", children: [
11877
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11878
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11879
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11880
- value,
11881
- " px"
11882
- ] })
11883
- ] }),
11884
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11885
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11886
- "div",
11887
- {
11888
- className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11889
- style: { width: `${pct}%` }
11890
- }
11891
- ),
11892
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11893
- "input",
11894
- {
11895
- type: "range",
11896
- min: LOGO_SIZE_MIN,
11897
- max: LOGO_SIZE_MAX,
11898
- step: 1,
11899
- value,
11900
- "aria-label": "Logo size",
11901
- className: cn(
11902
- "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11903
- "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11904
- "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11905
- "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11906
- "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11907
- "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11908
- "[&::-moz-range-thumb]:bg-background"
11909
- ),
11910
- onChange: (e) => onChange(Number(e.target.value))
11911
- }
11912
- )
11913
- ] })
11914
- ] });
11915
- }
11916
- function LogoSizePanel({
11917
- viewport,
11918
- sizePx,
11919
- mobileFollowing = true,
11920
- onSizeChange,
11921
- onCustomizeMobile,
11922
- onResetMobile,
11923
- onUpdateEverywhere,
11924
- className
11925
- }) {
11926
- const showFollowing = viewport === "mobile" && mobileFollowing;
11927
- const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11928
- const showDesktopSlider = viewport === "desktop";
11929
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11930
- showFollowing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
11931
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start gap-1", children: [
11932
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.Link, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11933
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11934
- ] }),
11935
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Mobile uses the desktop size until you customize it. Change the desktop size and it follows automatically." }),
11936
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11937
- Button,
11938
- {
11939
- type: "button",
11940
- variant: "outline",
11941
- size: "sm",
11942
- className: "h-9 w-full min-w-0 cursor-pointer",
11943
- onClick: onCustomizeMobile,
11944
- children: "Customize for mobile"
11945
- }
11946
- )
11947
- ] }) : null,
11948
- showDesktopSlider || showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11949
- showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11950
- Button,
11951
- {
11952
- type: "button",
11953
- variant: "outline",
11954
- size: "sm",
11955
- className: "h-9 w-full min-w-0 cursor-pointer",
11956
- onClick: onResetMobile,
11957
- children: "Reset to desktop size"
11958
- }
11959
- ) : null,
11960
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-px w-full bg-border", role: "separator" }),
11961
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11962
- Button,
11963
- {
11964
- type: "button",
11965
- variant: "outline",
11966
- size: "sm",
11967
- className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11968
- onClick: onUpdateEverywhere,
11969
- children: [
11970
- "Update logo everywhere",
11971
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.ArrowUpRight, { size: 16, "aria-hidden": true })
11972
- ]
11973
- }
11974
- ),
11975
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11976
- ] });
11977
- }
11978
-
11979
11794
  // src/ui/socials-display-panel.tsx
11980
11795
  var import_jsx_runtime28 = require("react/jsx-runtime");
11981
11796
  function DisplaySwitch({
@@ -12744,8 +12559,10 @@ function getLinkHref3(el) {
12744
12559
  }
12745
12560
  function collectEditableNodes(extraContent, root = document) {
12746
12561
  const isScoped = root !== document;
12747
- const editableEls = Array.from(root.querySelectorAll("[data-ohw-editable]"));
12748
- if (isScoped && root instanceof HTMLElement && root.matches("[data-ohw-editable]")) {
12562
+ const editableEls = Array.from(
12563
+ root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
12564
+ );
12565
+ if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
12749
12566
  editableEls.unshift(root);
12750
12567
  }
12751
12568
  const nodes = editableEls.map((el) => {
@@ -12820,18 +12637,6 @@ function collectEditableNodes(extraContent, root = document) {
12820
12637
  }
12821
12638
  if (extraContent && !isScoped) {
12822
12639
  applyNavFooterDeleteOverrides(byKey, extraContent);
12823
- for (const key of LOGO_IMAGE_KEYS) {
12824
- if (!(key in extraContent)) continue;
12825
- byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12826
- }
12827
- for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12828
- if (!(key in extraContent)) continue;
12829
- byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12830
- }
12831
- for (const key of LOGO_SIZE_KEYS) {
12832
- if (!(key in extraContent)) continue;
12833
- byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12834
- }
12835
12640
  }
12836
12641
  return Array.from(byKey.values());
12837
12642
  }
@@ -13300,10 +13105,21 @@ function parseSchedulingInsertAfter(insertAfter) {
13300
13105
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13301
13106
  };
13302
13107
  }
13303
- function resolveEntryAnchor(entry) {
13304
- if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13305
- const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13306
- return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13108
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13109
+ const parsed = parseSchedulingInsertAfter(insertAfter);
13110
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13111
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13112
+ return { effectiveInsertAfter, insertBefore };
13113
+ }
13114
+ function getSchedulingMountPoint(insertAfter) {
13115
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
13116
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13117
+ if (!anchorEl && anchor === "scheduling") {
13118
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13119
+ anchorEl = widgets.at(-1) ?? null;
13120
+ }
13121
+ if (!anchorEl) return null;
13122
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13307
13123
  }
13308
13124
  function schedulingMountDepth(insertAfter) {
13309
13125
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13320,7 +13136,8 @@ function getPageSchedulingEntries(raw) {
13320
13136
  }
13321
13137
  }
13322
13138
  function isSchedulingWidgetMissing(entry) {
13323
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13139
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13140
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13324
13141
  }
13325
13142
  function hasMissingSchedulingWidgets(entries) {
13326
13143
  return entries.some(isSchedulingWidgetMissing);
@@ -13350,17 +13167,16 @@ function initSectionsFromContent(content, removeExisting = false) {
13350
13167
  } catch {
13351
13168
  }
13352
13169
  }
13353
- function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13354
- const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13355
- const sectionId = schedulingSectionId(widgetId);
13170
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13171
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13172
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
13356
13173
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13357
- const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13358
- if (!anchorEl) return false;
13359
- const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13174
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13175
+ if (!mountPoint) return false;
13360
13176
  const container = document.createElement("div");
13361
13177
  container.dataset.ohwSectionContainer = "scheduling";
13362
- if (beforeId) {
13363
- const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13178
+ if (insertBefore) {
13179
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13364
13180
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13365
13181
  if (!beforePoint) return false;
13366
13182
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13371,25 +13187,19 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
13371
13187
  }
13372
13188
  tail.insertAdjacentElement("afterend", container);
13373
13189
  }
13374
- try {
13375
- const root = (0, import_client2.createRoot)(container);
13376
- (0, import_react_dom3.flushSync)(() => {
13377
- root.render(
13378
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13379
- SchedulingWidget,
13380
- {
13381
- notifyOnConnect,
13382
- initialScheduleId: scheduleId,
13383
- insertAfter: widgetId
13384
- }
13385
- )
13386
- );
13387
- });
13388
- } catch (err) {
13389
- console.error("[ow:scheduling] render threw", err);
13390
- container.remove();
13391
- return false;
13392
- }
13190
+ const root = (0, import_client2.createRoot)(container);
13191
+ (0, import_react_dom3.flushSync)(() => {
13192
+ root.render(
13193
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13194
+ SchedulingWidget,
13195
+ {
13196
+ notifyOnConnect,
13197
+ initialScheduleId: scheduleId,
13198
+ insertAfter: effectiveInsertAfter
13199
+ }
13200
+ )
13201
+ );
13202
+ });
13393
13203
  const tracker = getSectionsTracker();
13394
13204
  let sections = [];
13395
13205
  try {
@@ -13397,12 +13207,10 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
13397
13207
  } catch {
13398
13208
  }
13399
13209
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13400
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13210
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13401
13211
  sections.push({
13402
13212
  type: "scheduling",
13403
- insertAfter: widgetId,
13404
- anchorId,
13405
- beforeId: beforeId ?? null,
13213
+ insertAfter: effectiveInsertAfter,
13406
13214
  pagePath: window.location.pathname,
13407
13215
  ...scheduleId ? { scheduleId } : {}
13408
13216
  });
@@ -13416,8 +13224,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13416
13224
  for (let i = pending.length - 1; i >= 0; i--) {
13417
13225
  const entry = pending[i];
13418
13226
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13419
- const { anchorId, beforeId } = resolveEntryAnchor(entry);
13420
- if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13227
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13421
13228
  pending.splice(i, 1);
13422
13229
  }
13423
13230
  }
@@ -13490,7 +13297,11 @@ function isIconEditable(el) {
13490
13297
  return el.dataset.ohwEditable === "icon";
13491
13298
  }
13492
13299
  var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
13493
- var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"])';
13300
+ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data-ohw-field-toolbar], [data-ohw-item-toolbar], [data-ohw-more-menu], [data-ohw-state-toggle], [data-ohw-max-badge], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]';
13301
+ function isOverEditorChrome(x, y) {
13302
+ return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
13303
+ }
13304
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"])';
13494
13305
  function getVideoEl2(el) {
13495
13306
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
13496
13307
  }
@@ -13561,13 +13372,6 @@ function isInsideLinkEditor(target) {
13561
13372
  target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
13562
13373
  );
13563
13374
  }
13564
- function isInsideFloatingPanel(target) {
13565
- return Boolean(target.closest("[data-ohw-floating-panel]"));
13566
- }
13567
- function isPointOverFloatingPanel(clientX, clientY) {
13568
- const el = document.elementFromPoint(clientX, clientY);
13569
- return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13570
- }
13571
13375
  function getHrefKeyFromElement(el) {
13572
13376
  if (!el) return null;
13573
13377
  const anchor = el.closest("[data-ohw-href-key]");
@@ -13615,7 +13419,8 @@ function isNavItemPointerTarget(el) {
13615
13419
  function getNavigationItemAnchor(el) {
13616
13420
  const anchor = el.matches("[data-ohw-href-key]") ? el : el.closest("[data-ohw-href-key]");
13617
13421
  if (!anchor) return null;
13618
- if (!anchor.querySelector('[data-ohw-editable="text"]') && !getSocialItem(anchor)) return null;
13422
+ if (!anchor.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') && !anchor.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') && !getSocialItem(anchor))
13423
+ return null;
13619
13424
  if (!isNavItemPointerTarget(anchor)) return null;
13620
13425
  return anchor;
13621
13426
  }
@@ -13805,7 +13610,7 @@ function getNavigationSelectionParent(el) {
13805
13610
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
13806
13611
  return getFooterLinksContainer();
13807
13612
  }
13808
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
13613
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13809
13614
  return getNavigationRoot(el);
13810
13615
  }
13811
13616
  return null;
@@ -14398,7 +14203,6 @@ function StateToggle({
14398
14203
  );
14399
14204
  }
14400
14205
  var contentCache = /* @__PURE__ */ new Map();
14401
- var fetchedContentPaths = /* @__PURE__ */ new Set();
14402
14206
  function resolveSubdomain(subdomainFromQuery) {
14403
14207
  if (subdomainFromQuery) return subdomainFromQuery;
14404
14208
  if (typeof window !== "undefined") {
@@ -14493,14 +14297,8 @@ function OhhwellsBridge() {
14493
14297
  });
14494
14298
  const selectFrameRef = (0, import_react16.useRef)(() => {
14495
14299
  });
14496
- const selectLogoRef = (0, import_react16.useRef)(() => {
14497
- });
14498
- const openLogoSizePanelRef = (0, import_react16.useRef)(() => {
14499
- });
14500
14300
  const deselectRef = (0, import_react16.useRef)(() => {
14501
14301
  });
14502
- const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(() => {
14503
- });
14504
14302
  const reselectNavigationItemRef = (0, import_react16.useRef)(() => {
14505
14303
  });
14506
14304
  const commitNavigationTextEditRef = (0, import_react16.useRef)(() => {
@@ -14516,6 +14314,182 @@ function OhhwellsBridge() {
14516
14314
  const sectionsLoadedRef = (0, import_react16.useRef)(false);
14517
14315
  const pendingScheduleConfigRequests = (0, import_react16.useRef)([]);
14518
14316
  const [toolbarRect, setToolbarRect] = (0, import_react16.useState)(null);
14317
+ const [formPickRect, setFormPickRect] = (0, import_react16.useState)(null);
14318
+ const formPickElRef = (0, import_react16.useRef)(null);
14319
+ const [formViewState, setFormViewStateUi] = (0, import_react16.useState)("default");
14320
+ const [formPickCount, setFormPickCount] = (0, import_react16.useState)(null);
14321
+ const [formHoverRect, setFormHoverRect] = (0, import_react16.useState)(null);
14322
+ const formHoverElRef = (0, import_react16.useRef)(null);
14323
+ const [fieldPickRect, setFieldPickRect] = (0, import_react16.useState)(null);
14324
+ const fieldPickElRef = (0, import_react16.useRef)(null);
14325
+ const [fieldPickState, setFieldPickState] = (0, import_react16.useState)(null);
14326
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = (0, import_react16.useState)(false);
14327
+ const clearFormPick = (0, import_react16.useCallback)(() => {
14328
+ const form = formPickElRef.current;
14329
+ const editing = fieldPickElRef.current;
14330
+ if (commitPlaceholderEdit(editing) && editing) {
14331
+ const owner = editing.closest('[data-ohw-editable="form"]');
14332
+ if (owner) persistFieldsRef.current(owner);
14333
+ }
14334
+ if (form) {
14335
+ const key = formKeyOf(form);
14336
+ if (key) setFormViewState(form, key, "default", successInitialFor(form, key, editContentRef.current));
14337
+ }
14338
+ setFormViewStateUi("default");
14339
+ setFormPickCount(null);
14340
+ setFieldTypePickerOpen(false);
14341
+ fieldPickElRef.current = null;
14342
+ setFieldPickRect(null);
14343
+ setFieldPickState(null);
14344
+ formPickElRef.current = null;
14345
+ setFormPickRect(null);
14346
+ }, []);
14347
+ const clearFieldPick = (0, import_react16.useCallback)(() => {
14348
+ const wrapper = fieldPickElRef.current;
14349
+ if (commitPlaceholderEdit(wrapper) && wrapper) {
14350
+ const form = wrapper.closest('[data-ohw-editable="form"]');
14351
+ if (form) persistFieldsRef.current(form);
14352
+ }
14353
+ fieldPickElRef.current = null;
14354
+ setFieldPickRect(null);
14355
+ setFieldPickState(null);
14356
+ }, []);
14357
+ const persistFieldsRef = (0, import_react16.useRef)(() => {
14358
+ });
14359
+ const persistFields = (0, import_react16.useCallback)(
14360
+ (form) => {
14361
+ const key = formKeyOf(form);
14362
+ if (!key) return;
14363
+ const json = JSON.stringify(readFieldsFromDom(form));
14364
+ editContentRef.current = { ...editContentRef.current, [fieldsKey(key)]: json };
14365
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: fieldsKey(key), text: json }] });
14366
+ },
14367
+ []
14368
+ );
14369
+ persistFieldsRef.current = persistFields;
14370
+ const selectField = (0, import_react16.useCallback)((wrapper) => {
14371
+ if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14372
+ commitPlaceholderEdit(fieldPickElRef.current);
14373
+ }
14374
+ syncRequiredMark(wrapper);
14375
+ beginPlaceholderEdit(wrapper);
14376
+ formHoverElRef.current = null;
14377
+ setFormHoverRect(null);
14378
+ fieldPickElRef.current = wrapper;
14379
+ setFieldPickRect(wrapper.getBoundingClientRect());
14380
+ setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14381
+ setFieldTypePickerOpen(false);
14382
+ }, []);
14383
+ const withSelectedField = (0, import_react16.useCallback)(
14384
+ (run) => {
14385
+ const wrapper = fieldPickElRef.current;
14386
+ const form = formPickElRef.current;
14387
+ if (!wrapper || !form) return;
14388
+ commitPlaceholderEdit(wrapper);
14389
+ run(form, wrapper);
14390
+ persistFields(form);
14391
+ beginPlaceholderEdit(wrapper);
14392
+ setFormPickRect(form.getBoundingClientRect());
14393
+ },
14394
+ [persistFields]
14395
+ );
14396
+ const handleFieldTypeChange = (0, import_react16.useCallback)(
14397
+ (type) => withSelectedField((_form, wrapper) => {
14398
+ applyFieldType(wrapper, type);
14399
+ selectField(wrapper);
14400
+ }),
14401
+ [selectField, withSelectedField]
14402
+ );
14403
+ const handleFieldRequiredToggle = (0, import_react16.useCallback)(
14404
+ () => withSelectedField((_form, wrapper) => {
14405
+ setFieldRequired(wrapper, !isFieldRequired(wrapper));
14406
+ selectField(wrapper);
14407
+ }),
14408
+ [selectField, withSelectedField]
14409
+ );
14410
+ const handleFieldDuplicate = (0, import_react16.useCallback)(
14411
+ () => withSelectedField((form, wrapper) => {
14412
+ const copy = duplicateField(form, wrapper);
14413
+ selectField(copy);
14414
+ }),
14415
+ [selectField, withSelectedField]
14416
+ );
14417
+ const handleFieldDelete = (0, import_react16.useCallback)(
14418
+ () => withSelectedField((_form, wrapper) => {
14419
+ removeField(wrapper);
14420
+ clearFieldPick();
14421
+ postToParentRef.current({ type: "ow:toast", title: "Form field deleted" });
14422
+ }),
14423
+ [clearFieldPick, withSelectedField]
14424
+ );
14425
+ const handleAddField = (0, import_react16.useCallback)(
14426
+ (type) => {
14427
+ const form = formPickElRef.current;
14428
+ if (!form) return;
14429
+ const wrapper = insertField(form, type);
14430
+ setFieldTypePickerOpen(false);
14431
+ if (!wrapper) return;
14432
+ persistFields(form);
14433
+ setFormPickRect(null);
14434
+ requestAnimationFrame(() => {
14435
+ selectField(wrapper);
14436
+ wrapper.scrollIntoView({ block: "nearest", behavior: "smooth" });
14437
+ });
14438
+ },
14439
+ [persistFields, selectField]
14440
+ );
14441
+ const fieldDragRef = (0, import_react16.useRef)(null);
14442
+ const buildFieldDropSlots = (0, import_react16.useCallback)((form, draggedKey) => {
14443
+ const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
14444
+ const slots = others.map((el) => {
14445
+ const rect = el.getBoundingClientRect();
14446
+ return { top: rect.top, left: rect.left, width: rect.width };
14447
+ });
14448
+ const last = others[others.length - 1];
14449
+ if (last) {
14450
+ const rect = last.getBoundingClientRect();
14451
+ slots.push({ top: rect.bottom, left: rect.left, width: rect.width });
14452
+ }
14453
+ return slots;
14454
+ }, []);
14455
+ const handleFieldDragStart = (0, import_react16.useCallback)(() => {
14456
+ const wrapper = fieldPickElRef.current;
14457
+ const form = formPickElRef.current;
14458
+ if (!wrapper || !form) return;
14459
+ const key = fieldKeyOf(wrapper);
14460
+ fieldDragRef.current = { key, form };
14461
+ setFieldDragging(true);
14462
+ setFieldDropSlots(buildFieldDropSlots(form, key));
14463
+ }, [buildFieldDropSlots]);
14464
+ const handleFieldDragEnd = (0, import_react16.useCallback)(() => {
14465
+ fieldDragRef.current = null;
14466
+ setFieldDropIndex(null);
14467
+ setFieldDropSlots([]);
14468
+ setFieldDragging(false);
14469
+ }, []);
14470
+ const [fieldDropIndex, setFieldDropIndex] = (0, import_react16.useState)(null);
14471
+ const [fieldDropSlots, setFieldDropSlots] = (0, import_react16.useState)([]);
14472
+ const [fieldDragging, setFieldDragging] = (0, import_react16.useState)(false);
14473
+ const clearFormPickRef = (0, import_react16.useRef)(clearFormPick);
14474
+ clearFormPickRef.current = clearFormPick;
14475
+ (0, import_react16.useEffect)(() => {
14476
+ const el = fieldPickElRef.current;
14477
+ if (!el || fieldPickRect === null) return;
14478
+ const observer = new ResizeObserver(() => {
14479
+ if (fieldPickElRef.current === el) setFieldPickRect(el.getBoundingClientRect());
14480
+ });
14481
+ observer.observe(el);
14482
+ return () => observer.disconnect();
14483
+ }, [fieldPickRect !== null, fieldPickState]);
14484
+ (0, import_react16.useEffect)(() => {
14485
+ const el = formPickElRef.current;
14486
+ if (!el || formPickRect === null) return;
14487
+ const observer = new ResizeObserver(() => {
14488
+ if (formPickElRef.current === el) setFormPickRect(el.getBoundingClientRect());
14489
+ });
14490
+ observer.observe(el);
14491
+ return () => observer.disconnect();
14492
+ }, [formPickRect !== null, formViewState]);
14519
14493
  const [toolbarVariant, setToolbarVariant] = (0, import_react16.useState)("none");
14520
14494
  const toolbarVariantRef = (0, import_react16.useRef)("none");
14521
14495
  toolbarVariantRef.current = toolbarVariant;
@@ -14537,7 +14511,7 @@ function OhhwellsBridge() {
14537
14511
  (0, import_react16.useEffect)(() => {
14538
14512
  const sync = () => {
14539
14513
  const el = document.querySelector(
14540
- "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
14514
+ '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
14541
14515
  );
14542
14516
  const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
14543
14517
  if (!target) {
@@ -14561,6 +14535,11 @@ function OhhwellsBridge() {
14561
14535
  const [isItemDragging, setIsItemDragging] = (0, import_react16.useState)(false);
14562
14536
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react16.useState)(false);
14563
14537
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
14538
+ const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14539
+ const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14540
+ floatingPanelOpenRef.current = floatingPanel !== null;
14541
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14542
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
14564
14543
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
14565
14544
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
14566
14545
  const footerDragRef = (0, import_react16.useRef)(null);
@@ -14575,16 +14554,7 @@ function OhhwellsBridge() {
14575
14554
  const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
14576
14555
  const editContentRef = (0, import_react16.useRef)({});
14577
14556
  const aiSectionsRef = (0, import_react16.useRef)("");
14578
- const brandKitRef = (0, import_react16.useRef)("");
14579
- const stylesRef = (0, import_react16.useRef)("");
14580
14557
  const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14581
- const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14582
- const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14583
- const setFloatingPanelRef = (0, import_react16.useRef)(setFloatingPanel);
14584
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14585
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react16.useState)(null);
14586
- const [editorViewport, setEditorViewport] = (0, import_react16.useState)("desktop");
14587
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
14588
14558
  const [sitePages, setSitePages] = (0, import_react16.useState)([]);
14589
14559
  const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
14590
14560
  const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
@@ -14593,18 +14563,7 @@ function OhhwellsBridge() {
14593
14563
  const linkPopoverOpenRef = (0, import_react16.useRef)(false);
14594
14564
  const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
14595
14565
  setLinkPopoverRef.current = setLinkPopover;
14596
- setFloatingPanelRef.current = setFloatingPanel;
14597
14566
  linkPopoverSessionRef.current = linkPopover;
14598
- floatingPanelOpenRef.current = Boolean(floatingPanel);
14599
- (0, import_react16.useEffect)(() => {
14600
- const syncViewport = () => {
14601
- const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14602
- setEditorViewport((prev) => prev === next ? prev : next);
14603
- };
14604
- syncViewport();
14605
- window.addEventListener("resize", syncViewport);
14606
- return () => window.removeEventListener("resize", syncViewport);
14607
- }, []);
14608
14567
  const {
14609
14568
  navDragRef,
14610
14569
  navDropSlots,
@@ -14773,7 +14732,9 @@ function OhhwellsBridge() {
14773
14732
  const deactivate = (0, import_react16.useCallback)(() => {
14774
14733
  const el = activeElRef.current;
14775
14734
  if (!el) return;
14776
- const key = el.dataset.ohwKey;
14735
+ const isFormBlock = el.dataset.ohwEditable === "form";
14736
+ const isFormControl = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;
14737
+ const key = isFormBlock || isFormControl ? void 0 : el.dataset.ohwKey;
14777
14738
  if (key) {
14778
14739
  const timer = autoSaveTimers.current.get(key);
14779
14740
  if (timer !== void 0) {
@@ -14790,6 +14751,12 @@ function OhhwellsBridge() {
14790
14751
  }
14791
14752
  el.removeAttribute("contenteditable");
14792
14753
  el.removeAttribute("data-ohw-editing");
14754
+ const labelField = getFieldWrapper(el);
14755
+ if (labelField) {
14756
+ syncRequiredMark(labelField);
14757
+ const owner = labelField.closest('[data-ohw-editable="form"]');
14758
+ if (owner) persistFieldsRef.current(owner);
14759
+ }
14793
14760
  activeElRef.current = null;
14794
14761
  setNavGroupForceOpen(null, false);
14795
14762
  setReorderHrefKey(null);
@@ -14829,8 +14796,6 @@ function OhhwellsBridge() {
14829
14796
  setHoveredNavContainerRect(null);
14830
14797
  hoveredItemElRef.current = null;
14831
14798
  setHoveredItemRect(null);
14832
- setFloatingPanel(null);
14833
- setLogoSizeDraft(null);
14834
14799
  if (!activeElRef.current) {
14835
14800
  setNavGroupForceOpen(null, false);
14836
14801
  setToolbarRect(null);
@@ -14945,6 +14910,12 @@ function OhhwellsBridge() {
14945
14910
  }
14946
14911
  el.removeAttribute("contenteditable");
14947
14912
  el.removeAttribute("data-ohw-editing");
14913
+ const labelField = getFieldWrapper(el);
14914
+ if (labelField) {
14915
+ syncRequiredMark(labelField);
14916
+ const owner = labelField.closest('[data-ohw-editable="form"]');
14917
+ if (owner) persistFieldsRef.current(owner);
14918
+ }
14948
14919
  activeElRef.current = null;
14949
14920
  setMaxBadge(null);
14950
14921
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -15536,8 +15507,6 @@ function OhhwellsBridge() {
15536
15507
  setToolbarRect(anchor.getBoundingClientRect());
15537
15508
  setToolbarShowEditLink(false);
15538
15509
  setActiveCommands(/* @__PURE__ */ new Set());
15539
- setFloatingPanel(null);
15540
- setLogoSizeDraft(null);
15541
15510
  }, [deactivate, markSelected]);
15542
15511
  const selectFrame = (0, import_react16.useCallback)((el) => {
15543
15512
  if (!isNavigationContainer(el)) return;
@@ -15587,51 +15556,7 @@ function OhhwellsBridge() {
15587
15556
  setToolbarRect(el.getBoundingClientRect());
15588
15557
  setToolbarShowEditLink(false);
15589
15558
  setActiveCommands(/* @__PURE__ */ new Set());
15590
- setFloatingPanel(null);
15591
- setLogoSizeDraft(null);
15592
15559
  }, [deactivate, markSelected, postToParent2]);
15593
- const selectLogo = (0, import_react16.useCallback)(
15594
- (logoEl) => {
15595
- if (activeElRef.current) deactivate();
15596
- selectedElRef.current = logoEl;
15597
- selectedHrefKeyRef.current = null;
15598
- selectedFooterColAttrRef.current = null;
15599
- markSelected(logoEl);
15600
- setSelectedIsCta(false);
15601
- setSelectedIsSocial(false);
15602
- setSelectedIsSocialsRow(false);
15603
- clearHrefKeyHover(logoEl);
15604
- hoveredNavContainerRef.current = null;
15605
- setHoveredNavContainerRect(null);
15606
- setHoveredItemRect(null);
15607
- hoveredItemElRef.current = null;
15608
- siblingHintElRef.current = null;
15609
- setSiblingHintRect(null);
15610
- setSiblingHintRects([]);
15611
- setIsItemDragging(false);
15612
- setReorderHrefKey(null);
15613
- setReorderDragDisabled(false);
15614
- setIsFooterFrameSelection(false);
15615
- setToolbarVariant("logo");
15616
- setToolbarRect(getLogoInteractionRect(logoEl));
15617
- setToolbarShowEditLink(false);
15618
- setActiveCommands(/* @__PURE__ */ new Set());
15619
- },
15620
- [deactivate, markSelected]
15621
- );
15622
- const openLogoSizePanel = (0, import_react16.useCallback)((logoEl) => {
15623
- const placement = getLogoPlacement(logoEl);
15624
- const draft = readLogoSizeState(editContentRef.current, placement);
15625
- setLogoSizeDraft(draft);
15626
- setParentScrollSnap(parentScrollRef.current);
15627
- setFloatingPanel({
15628
- key: `logo-size:${placement}`,
15629
- title: "Logo",
15630
- context: placement === "navbar" ? "Navbar" : "Footer",
15631
- kind: "logo-size",
15632
- placement
15633
- });
15634
- }, []);
15635
15560
  const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
15636
15561
  setParentScrollSnap(parentScrollRef.current);
15637
15562
  setFloatingPanel({
@@ -15667,53 +15592,13 @@ function OhhwellsBridge() {
15667
15592
  );
15668
15593
  const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
15669
15594
  setFloatingPanel(null);
15670
- setLogoSizeDraft(null);
15671
15595
  }, []);
15596
+ const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(closeFloatingPanelOnly);
15597
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15672
15598
  const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
15673
15599
  setFloatingPanel(null);
15674
- setLogoSizeDraft(null);
15675
15600
  deselectRef.current();
15676
15601
  }, []);
15677
- const persistLogoSizeDraft = (0, import_react16.useCallback)(
15678
- (placement, draft) => {
15679
- const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15680
- const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15681
- const nodes = [
15682
- { key: desktopKey, text: String(draft.desktopPx) }
15683
- ];
15684
- if (draft.mobileFollowing) {
15685
- nodes.push({ key: mobileKey, text: "" });
15686
- } else {
15687
- nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15688
- }
15689
- editContentRef.current = {
15690
- ...editContentRef.current,
15691
- [desktopKey]: String(draft.desktopPx),
15692
- [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15693
- };
15694
- applyLogoSizeToPlacement(
15695
- placement,
15696
- draft.desktopPx,
15697
- draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15698
- draft.mobileFollowing
15699
- );
15700
- postToParent2({ type: "ow:change", nodes });
15701
- requestAnimationFrame(() => {
15702
- const selected = selectedElRef.current;
15703
- if (!selected || toolbarVariantRef.current !== "logo") return;
15704
- const rect = getLogoInteractionRect(selected);
15705
- setToolbarRect(rect);
15706
- if (glowElRef.current) {
15707
- const GAP = SELECTION_CHROME_GAP2;
15708
- glowElRef.current.style.top = `${rect.top - GAP}px`;
15709
- glowElRef.current.style.left = `${rect.left - GAP}px`;
15710
- glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15711
- glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15712
- }
15713
- });
15714
- },
15715
- [postToParent2]
15716
- );
15717
15602
  const activate = (0, import_react16.useCallback)((el, options) => {
15718
15603
  if (activeElRef.current === el) return;
15719
15604
  if (isIconEditable(el)) return;
@@ -15794,10 +15679,7 @@ function OhhwellsBridge() {
15794
15679
  deactivateRef.current = deactivate;
15795
15680
  selectRef.current = select;
15796
15681
  selectFrameRef.current = selectFrame;
15797
- selectLogoRef.current = selectLogo;
15798
- openLogoSizePanelRef.current = openLogoSizePanel;
15799
15682
  deselectRef.current = deselect;
15800
- closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15801
15683
  const lastSiteWideScopeRef = (0, import_react16.useRef)(null);
15802
15684
  (0, import_react16.useEffect)(() => {
15803
15685
  if (!isEditMode) {
@@ -15836,23 +15718,9 @@ function OhhwellsBridge() {
15836
15718
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
15837
15719
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
15838
15720
  }
15839
- if (typeof content[BRAND_KIT_KEY] === "string") {
15840
- brandKitRef.current = content[BRAND_KIT_KEY];
15841
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15842
- }
15843
- if (typeof content[STYLE_STORE_KEY] === "string") {
15844
- stylesRef.current = content[STYLE_STORE_KEY];
15845
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15846
- }
15847
- applyBrandChrome(content);
15848
15721
  for (const [key, val] of Object.entries(content)) {
15849
15722
  if (key === "__ohw_sections") continue;
15850
15723
  if (key === AI_SECTIONS_KEY) continue;
15851
- if (key === LOGO_PLACEHOLDER_KEY) continue;
15852
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
15853
- if (key === BRAND_KIT_KEY) continue;
15854
- if (key === STYLE_STORE_KEY) continue;
15855
- if (BRAND_CHROME_KEYS.has(key)) continue;
15856
15724
  if (applyVideoSettingNode(key, val)) continue;
15857
15725
  if (applyCarouselNode(key, val)) continue;
15858
15726
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -15881,14 +15749,13 @@ function OhhwellsBridge() {
15881
15749
  applyLinkHref(el, val);
15882
15750
  } else if (el.dataset.ohwEditable === "icon") {
15883
15751
  applyIconMarkup(el, val);
15752
+ } else if (el.dataset.ohwEditable === "form") {
15884
15753
  } else if (el.innerHTML !== val) {
15885
15754
  el.innerHTML = val;
15886
15755
  }
15887
15756
  });
15888
15757
  applyLinkByKey(key, val);
15889
15758
  }
15890
- applyLogoFromContent(content);
15891
- applyLogoSizes(content);
15892
15759
  reconcileNavbarItemsFromContent(content);
15893
15760
  reconcileFooterOrderFromContent(content);
15894
15761
  reconcileSocialsFromContent(content);
@@ -15909,9 +15776,7 @@ function OhhwellsBridge() {
15909
15776
  let cancelled = false;
15910
15777
  setFetchState("loading");
15911
15778
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15912
- const initialPath = pathname;
15913
- fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15914
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15779
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15915
15780
  if (cancelled) return;
15916
15781
  const content = data?.content ?? {};
15917
15782
  contentCache.set(subdomain, content);
@@ -15924,6 +15789,102 @@ function OhhwellsBridge() {
15924
15789
  cancelled = true;
15925
15790
  };
15926
15791
  }, [subdomain, isEditMode]);
15792
+ (0, import_react16.useEffect)(() => {
15793
+ if (!isEditMode) return;
15794
+ const resolveIndex = (form, clientY) => {
15795
+ const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
15796
+ for (let i = 0; i < wrappers.length; i += 1) {
15797
+ const rect = wrappers[i].getBoundingClientRect();
15798
+ if (clientY < rect.top + rect.height / 2) return i;
15799
+ }
15800
+ return wrappers.length;
15801
+ };
15802
+ const onDragOver = (e) => {
15803
+ const session = fieldDragRef.current;
15804
+ if (!session) return;
15805
+ e.preventDefault();
15806
+ e.stopPropagation();
15807
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
15808
+ setFieldDropSlots(buildFieldDropSlots(session.form, session.key));
15809
+ setFieldDropIndex(resolveIndex(session.form, e.clientY));
15810
+ };
15811
+ const onDrop = (e) => {
15812
+ const session = fieldDragRef.current;
15813
+ if (!session) return;
15814
+ e.preventDefault();
15815
+ e.stopPropagation();
15816
+ moveField(session.form, session.key, resolveIndex(session.form, e.clientY));
15817
+ persistFields(session.form);
15818
+ const moved = listFieldWrappers(session.form).find((el) => fieldKeyOf(el) === session.key);
15819
+ if (moved) selectField(moved);
15820
+ setFormPickRect(session.form.getBoundingClientRect());
15821
+ fieldDragRef.current = null;
15822
+ setFieldDropIndex(null);
15823
+ setFieldDropSlots([]);
15824
+ setFieldDragging(false);
15825
+ };
15826
+ window.addEventListener("dragover", onDragOver, true);
15827
+ window.addEventListener("drop", onDrop, true);
15828
+ return () => {
15829
+ window.removeEventListener("dragover", onDragOver, true);
15830
+ window.removeEventListener("drop", onDrop, true);
15831
+ };
15832
+ }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
15833
+ (0, import_react16.useEffect)(() => {
15834
+ if (!isEditMode) return;
15835
+ const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15836
+ markFormFields(form);
15837
+ });
15838
+ mark();
15839
+ const observer = new MutationObserver(() => mark());
15840
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15841
+ observer.observe(form, { childList: true, subtree: true, characterData: true });
15842
+ });
15843
+ return () => observer.disconnect();
15844
+ }, [isEditMode, fetchState, pathname]);
15845
+ (0, import_react16.useEffect)(() => {
15846
+ if (!isEditMode) return;
15847
+ let saveTimer = null;
15848
+ const onInput = (e) => {
15849
+ const input = e.target;
15850
+ if (!input || !("value" in input)) return;
15851
+ const wrapper = getFieldWrapper(input);
15852
+ if (!wrapper || wrapper !== fieldPickElRef.current) return;
15853
+ if (saveTimer) clearTimeout(saveTimer);
15854
+ const owner = wrapper.closest('[data-ohw-editable="form"]');
15855
+ saveTimer = setTimeout(() => {
15856
+ const form = owner ?? formPickElRef.current;
15857
+ if (!form) return;
15858
+ const previous = input.getAttribute("placeholder");
15859
+ input.setAttribute("placeholder", input.value);
15860
+ persistFields(form);
15861
+ input.setAttribute("placeholder", previous ?? "");
15862
+ }, 400);
15863
+ };
15864
+ document.addEventListener("input", onInput, true);
15865
+ return () => document.removeEventListener("input", onInput, true);
15866
+ }, [isEditMode, persistFields]);
15867
+ (0, import_react16.useEffect)(() => {
15868
+ if (isEditMode || fetchState !== "done") return;
15869
+ const content = contentCache.get(subdomain) ?? {};
15870
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15871
+ reconcileFieldsFromContent(form, content);
15872
+ });
15873
+ }, [isEditMode, fetchState, subdomain]);
15874
+ (0, import_react16.useEffect)(() => {
15875
+ if (!isEditMode) return;
15876
+ const swallow = (e) => {
15877
+ const target = e.target;
15878
+ if (target && getFormElement(target)) e.preventDefault();
15879
+ };
15880
+ document.addEventListener("submit", swallow, true);
15881
+ return () => document.removeEventListener("submit", swallow, true);
15882
+ }, [isEditMode]);
15883
+ (0, import_react16.useEffect)(() => {
15884
+ if (isEditMode || fetchState !== "done") return;
15885
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15886
+ bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
15887
+ }, [isEditMode, fetchState, subdomain]);
15927
15888
  (0, import_react16.useEffect)(() => {
15928
15889
  if (!subdomain || isEditMode) return;
15929
15890
  let debounceTimer = null;
@@ -15935,21 +15896,8 @@ function OhhwellsBridge() {
15935
15896
  initSectionInstancesFromContent(content, window.location.pathname);
15936
15897
  observer?.disconnect();
15937
15898
  try {
15938
- applyBrandChrome(content);
15939
- if (typeof content[BRAND_KIT_KEY] === "string") {
15940
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15941
- }
15942
- if (typeof content[STYLE_STORE_KEY] === "string") {
15943
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15944
- }
15945
15899
  for (const [key, val] of Object.entries(content)) {
15946
15900
  if (key === "__ohw_sections") continue;
15947
- if (key === LOGO_PLACEHOLDER_KEY) continue;
15948
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
15949
- if (key === BRAND_KIT_KEY) continue;
15950
- if (key === STYLE_STORE_KEY) continue;
15951
- if (key === STYLE_STORE_KEY) continue;
15952
- if (BRAND_CHROME_KEYS.has(key)) continue;
15953
15901
  if (applyVideoSettingNode(key, val)) continue;
15954
15902
  if (applyCarouselNode(key, val)) continue;
15955
15903
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -15964,17 +15912,23 @@ function OhhwellsBridge() {
15964
15912
  if (video && video.src !== val) applyVideoSrc(video, val);
15965
15913
  } else if (el.dataset.ohwEditable === "link") {
15966
15914
  applyLinkHref(el, val);
15915
+ } else if (el.dataset.ohwEditable === "form") {
15967
15916
  } else if (el.innerHTML !== val) {
15968
15917
  el.innerHTML = val;
15969
15918
  }
15970
15919
  });
15971
15920
  applyLinkByKey(key, val);
15972
15921
  }
15973
- applyLogoFromContent(content);
15974
15922
  reconcileNavbarItemsFromContent(content);
15975
15923
  reconcileFooterOrderFromContent(content);
15976
15924
  reconcileSocialsFromContent(content);
15977
15925
  applySocialsDisplayFromContent(content);
15926
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15927
+ if (!form.querySelector("[data-ohw-form-success]")) {
15928
+ reconcileFieldsFromContent(form, content);
15929
+ }
15930
+ listFieldWrappers(form).forEach(syncRequiredMark);
15931
+ });
15978
15932
  } finally {
15979
15933
  observer?.observe(document.body, { childList: true, subtree: true });
15980
15934
  }
@@ -15985,17 +15939,6 @@ function OhhwellsBridge() {
15985
15939
  debounceTimer = setTimeout(applyFromCache, 150);
15986
15940
  };
15987
15941
  applyFromCache();
15988
- const pathCacheKey = `${subdomain}::${pathname}`;
15989
- if (!fetchedContentPaths.has(pathCacheKey)) {
15990
- fetchedContentPaths.add(pathCacheKey);
15991
- const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15992
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15993
- if (!data?.content) return;
15994
- contentCache.set(subdomain, data.content);
15995
- applyFromCache();
15996
- }).catch(() => {
15997
- });
15998
- }
15999
15942
  observer = new MutationObserver(scheduleApply);
16000
15943
  observer.observe(document.body, { childList: true, subtree: true });
16001
15944
  return () => {
@@ -16089,31 +16032,26 @@ function OhhwellsBridge() {
16089
16032
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16090
16033
  (0, import_react16.useEffect)(() => {
16091
16034
  if (!isEditMode) return;
16092
- let lastPosted = 0;
16093
16035
  const measure = () => {
16094
16036
  const h = document.body.scrollHeight;
16095
- if (h > 50 && Math.abs(h - lastPosted) > 1) {
16096
- lastPosted = h;
16097
- postToParent2({ type: "ow:height", height: h });
16098
- }
16099
- };
16100
- let raf = null;
16101
- const schedule = () => {
16102
- if (raf != null) return;
16103
- raf = requestAnimationFrame(() => {
16104
- raf = null;
16105
- measure();
16106
- });
16037
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
16107
16038
  };
16108
16039
  const t1 = setTimeout(measure, 50);
16109
16040
  const t2 = setTimeout(measure, 500);
16110
- const ro = new ResizeObserver(schedule);
16111
- ro.observe(document.body);
16041
+ let lastWidth = window.innerWidth;
16042
+ let resizeTimer = null;
16043
+ const handleResize = () => {
16044
+ if (window.innerWidth === lastWidth) return;
16045
+ lastWidth = window.innerWidth;
16046
+ if (resizeTimer) clearTimeout(resizeTimer);
16047
+ resizeTimer = setTimeout(measure, 150);
16048
+ };
16049
+ window.addEventListener("resize", handleResize);
16112
16050
  return () => {
16113
16051
  clearTimeout(t1);
16114
16052
  clearTimeout(t2);
16115
- if (raf != null) cancelAnimationFrame(raf);
16116
- ro.disconnect();
16053
+ if (resizeTimer) clearTimeout(resizeTimer);
16054
+ window.removeEventListener("resize", handleResize);
16117
16055
  };
16118
16056
  }, [pathname, isEditMode, postToParent2]);
16119
16057
  (0, import_react16.useEffect)(() => {
@@ -16157,7 +16095,9 @@ function OhhwellsBridge() {
16157
16095
  [style*="100vh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16158
16096
  [style*="100svh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16159
16097
  [style*="100dvh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16160
- [data-ohw-editable] {
16098
+ /* Not the form: it is a layout container (flex/grid with gaps), and forcing block
16099
+ crushed its fields together (OHH-490). */
16100
+ [data-ohw-editable]:not([data-ohw-editable="form"]) {
16161
16101
  display: block;
16162
16102
  }
16163
16103
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
@@ -16183,6 +16123,35 @@ function OhhwellsBridge() {
16183
16123
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
16184
16124
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
16185
16125
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
16126
+ /* A form field is a design surface in the editor: its input takes the pointer so the
16127
+ field can be picked and hovered from anywhere inside it (OHH-642). */
16128
+ [data-ohw-editable="form"] [data-ohw-form-field] input,
16129
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea,
16130
+ [data-ohw-editable="form"] [data-ohw-form-field] label {
16131
+ pointer-events: auto !important;
16132
+ cursor: pointer !important;
16133
+ }
16134
+ /* While a field is selected its placeholder is being written in the value, so the
16135
+ text reads as a placeholder rather than as an answer (OHH-642). */
16136
+ /* A field is a design surface here: dragging its corner resized it past the form and
16137
+ left the chrome behind. Height stays adjustable, width does not (OHH-642). */
16138
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea {
16139
+ resize: vertical !important;
16140
+ max-width: 100% !important;
16141
+ }
16142
+ [data-ohw-editable="form"] [data-ohw-form-field] input,
16143
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea {
16144
+ box-sizing: border-box !important;
16145
+ width: 100% !important;
16146
+ }
16147
+ /* Somewhere to click the block itself, on every side (OHH-642) \u2014 editor only. */
16148
+ [data-ohw-editable="form"] {
16149
+ padding: 18px !important;
16150
+ }
16151
+ [data-ohw-placeholder-edit] {
16152
+ color: color-mix(in srgb, currentColor 55%, transparent) !important;
16153
+ cursor: text !important;
16154
+ }
16186
16155
  /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
16187
16156
  that used to draw it dashes denser than the overlay border, so identical specs
16188
16157
  still read as two different frames (OHH-695). The attribute stays: hover paths
@@ -16263,12 +16232,53 @@ function OhhwellsBridge() {
16263
16232
  return;
16264
16233
  }
16265
16234
  const target = e.target;
16266
- if (target.closest("[data-ohw-ai-review]")) return;
16267
16235
  if (target.closest("[data-ohw-toolbar]")) return;
16268
16236
  if (target.closest("[data-ohw-state-toggle]")) return;
16269
16237
  if (target.closest("[data-ohw-max-badge]")) return;
16270
16238
  if (isInsideLinkEditor(target)) return;
16271
- if (isInsideFloatingPanel(target)) return;
16239
+ if (target.closest("[data-ohw-form-toolbar]")) return;
16240
+ if (target.closest(
16241
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
16242
+ )) {
16243
+ return;
16244
+ }
16245
+ {
16246
+ const formEl = getFormElement(target);
16247
+ const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
16248
+ if (formEl && formKeyOf(formEl) && !onSuccessText) {
16249
+ const fieldEl = getFieldWrapper(target);
16250
+ if (fieldEl) {
16251
+ const label = target.closest("label");
16252
+ if (label && fieldPickElRef.current === fieldEl) return;
16253
+ e.preventDefault();
16254
+ e.stopPropagation();
16255
+ deactivateRef.current();
16256
+ deselectRef.current();
16257
+ formPickElRef.current = formEl;
16258
+ setFormPickRect(null);
16259
+ selectField(fieldEl);
16260
+ if (!label) fieldEl.querySelector("input, textarea")?.focus();
16261
+ return;
16262
+ }
16263
+ clearFieldPick();
16264
+ e.preventDefault();
16265
+ e.stopPropagation();
16266
+ deactivateRef.current();
16267
+ deselectRef.current();
16268
+ markFormFields(formEl);
16269
+ formHoverElRef.current = null;
16270
+ setFormHoverRect(null);
16271
+ formPickElRef.current = formEl;
16272
+ setFormPickRect(formEl.getBoundingClientRect());
16273
+ postToParentRef.current({
16274
+ type: "ow:form-selected",
16275
+ formKey: formKeyOf(formEl),
16276
+ hasLongText: formHasLongText(formEl)
16277
+ });
16278
+ return;
16279
+ }
16280
+ if (!formEl && formPickElRef.current) clearFormPick();
16281
+ }
16272
16282
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16273
16283
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
16274
16284
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -16336,15 +16346,9 @@ function OhhwellsBridge() {
16336
16346
  if (logoEl) {
16337
16347
  e.preventDefault();
16338
16348
  e.stopPropagation();
16339
- if (!logoHasUploadedImage(logoEl)) {
16340
- deselectRef.current();
16341
- deactivateRef.current();
16342
- const identity = readLogoIdentityFromDom();
16343
- postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16344
- return;
16345
- }
16346
- selectLogoRef.current(logoEl);
16347
- openLogoSizePanelRef.current(logoEl);
16349
+ deselectRef.current();
16350
+ deactivateRef.current();
16351
+ postToParentRef.current({ type: "ow:open-logo-settings" });
16348
16352
  return;
16349
16353
  }
16350
16354
  const editable = target.closest("[data-ohw-editable]");
@@ -16499,12 +16503,10 @@ function OhhwellsBridge() {
16499
16503
  };
16500
16504
  const handleDblClick = (e) => {
16501
16505
  const target = e.target;
16502
- if (target.closest("[data-ohw-ai-review]")) return;
16503
16506
  if (target.closest("[data-ohw-toolbar]")) return;
16504
16507
  if (target.closest("[data-ohw-state-toggle]")) return;
16505
16508
  if (target.closest("[data-ohw-max-badge]")) return;
16506
16509
  if (isInsideLinkEditor(target)) return;
16507
- if (isInsideFloatingPanel(target)) return;
16508
16510
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16509
16511
  return;
16510
16512
  }
@@ -16532,16 +16534,26 @@ function OhhwellsBridge() {
16532
16534
  setHoveredNavContainerRect(null);
16533
16535
  return;
16534
16536
  }
16535
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
16537
+ if (target.closest(EDITOR_CHROME_SELECTOR) || isOverEditorChrome(e.clientX, e.clientY) || isInsideLinkEditor(target)) {
16538
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
16536
16539
  hoveredItemElRef.current = null;
16537
16540
  setHoveredItemRect(null);
16538
16541
  hoveredNavContainerRef.current = null;
16539
16542
  setHoveredNavContainerRect(null);
16543
+ formHoverElRef.current = null;
16544
+ setFormHoverRect(null);
16540
16545
  siblingHintElRef.current = null;
16541
16546
  setSiblingHintRect(null);
16542
16547
  setSiblingHintRects([]);
16543
16548
  return;
16544
16549
  }
16550
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
16551
+ hoveredItemElRef.current = null;
16552
+ setHoveredItemRect(null);
16553
+ hoveredNavContainerRef.current = null;
16554
+ setHoveredNavContainerRect(null);
16555
+ return;
16556
+ }
16545
16557
  {
16546
16558
  const selected2 = selectedElRef.current;
16547
16559
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -16584,7 +16596,7 @@ function OhhwellsBridge() {
16584
16596
  setHoveredNavContainerRect(null);
16585
16597
  if (selectedElRef.current === logoEl) return;
16586
16598
  hoveredItemElRef.current = logoEl;
16587
- setHoveredItemRect(getLogoInteractionRect(logoEl));
16599
+ setHoveredItemRect(logoEl.getBoundingClientRect());
16588
16600
  return;
16589
16601
  }
16590
16602
  const navAnchor = getNavigationItemAnchor(target);
@@ -16608,6 +16620,26 @@ function OhhwellsBridge() {
16608
16620
  return;
16609
16621
  }
16610
16622
  const editable = target.closest("[data-ohw-editable]");
16623
+ const hoverForm = getFormElement(target);
16624
+ if (hoverForm) {
16625
+ const hoverField = getFieldWrapper(target);
16626
+ const hoverTarget = hoverField ?? hoverForm;
16627
+ const selectedHere = hoverTarget === fieldPickElRef.current || hoverTarget === formPickElRef.current;
16628
+ hoveredItemElRef.current = null;
16629
+ setHoveredItemRect(null);
16630
+ if (selectedHere) {
16631
+ formHoverElRef.current = null;
16632
+ setFormHoverRect(null);
16633
+ } else {
16634
+ formHoverElRef.current = hoverTarget;
16635
+ setFormHoverRect(hoverTarget.getBoundingClientRect());
16636
+ }
16637
+ return;
16638
+ }
16639
+ if (formHoverElRef.current) {
16640
+ formHoverElRef.current = null;
16641
+ setFormHoverRect(null);
16642
+ }
16611
16643
  if (!editable) return;
16612
16644
  const selected = selectedElRef.current;
16613
16645
  if (selected && (selected === editable || selected.contains(editable))) return;
@@ -16628,6 +16660,7 @@ function OhhwellsBridge() {
16628
16660
  hoveredNavContainerRef.current = null;
16629
16661
  setHoveredNavContainerRect(null);
16630
16662
  hoveredItemElRef.current = editable;
16663
+ setHoveredItemRect(editable.getBoundingClientRect());
16631
16664
  }
16632
16665
  }
16633
16666
  }
@@ -16835,7 +16868,7 @@ function OhhwellsBridge() {
16835
16868
  setHoveredNavContainerRect(null);
16836
16869
  if (selectedElRef.current !== logo) {
16837
16870
  hoveredItemElRef.current = logo;
16838
- setHoveredItemRect(getLogoInteractionRect(logo));
16871
+ setHoveredItemRect(logo.getBoundingClientRect());
16839
16872
  }
16840
16873
  return;
16841
16874
  }
@@ -16924,7 +16957,7 @@ function OhhwellsBridge() {
16924
16957
  }
16925
16958
  };
16926
16959
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
16927
- if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
16960
+ if (linkPopoverOpenRef.current) {
16928
16961
  if (hoveredImageRef.current) {
16929
16962
  hoveredImageRef.current = null;
16930
16963
  hoveredImageHasTextOverlapRef.current = false;
@@ -17178,7 +17211,7 @@ function OhhwellsBridge() {
17178
17211
  }
17179
17212
  };
17180
17213
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
17181
- if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17214
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17182
17215
  if (activeStateElRef.current) {
17183
17216
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
17184
17217
  activeStateElRef.current = null;
@@ -17244,19 +17277,24 @@ function OhhwellsBridge() {
17244
17277
  setSectionGap(null);
17245
17278
  }
17246
17279
  };
17280
+ const pointOwnedByFloatingPanel = (clientX, clientY) => {
17281
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return true;
17282
+ const panel = document.querySelector("[data-ohw-floating-panel]");
17283
+ if (!panel) return false;
17284
+ const rect = panel.getBoundingClientRect();
17285
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
17286
+ };
17247
17287
  const handleMouseMove = (e) => {
17248
17288
  const { clientX, clientY } = e;
17249
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17289
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17290
+ if (isOverEditorChrome(clientX, clientY)) {
17291
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
17292
+ formHoverElRef.current = null;
17293
+ setFormHoverRect(null);
17250
17294
  hoveredItemElRef.current = null;
17251
17295
  setHoveredItemRect(null);
17252
17296
  hoveredNavContainerRef.current = null;
17253
17297
  setHoveredNavContainerRect(null);
17254
- siblingHintElRef.current = null;
17255
- setSiblingHintRect(null);
17256
- setSiblingHintRects([]);
17257
- dismissImageHover();
17258
- clearImageHover();
17259
- setSectionGap(null);
17260
17298
  return;
17261
17299
  }
17262
17300
  probeSectionGapAt(clientX, clientY);
@@ -17267,11 +17305,7 @@ function OhhwellsBridge() {
17267
17305
  if (e.data?.type !== "ow:pointer-sync") return;
17268
17306
  const { clientX, clientY } = e.data;
17269
17307
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
17270
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17271
- dismissImageHover();
17272
- clearImageHover();
17273
- return;
17274
- }
17308
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17275
17309
  probeSectionGapAt(clientX, clientY);
17276
17310
  probeImageAt(clientX, clientY);
17277
17311
  probeHoverCardsAt(clientX, clientY);
@@ -17521,15 +17555,6 @@ function OhhwellsBridge() {
17521
17555
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17522
17556
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17523
17557
  }
17524
- if (typeof content[BRAND_KIT_KEY] === "string") {
17525
- brandKitRef.current = content[BRAND_KIT_KEY];
17526
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17527
- }
17528
- if (typeof content[STYLE_STORE_KEY] === "string") {
17529
- stylesRef.current = content[STYLE_STORE_KEY];
17530
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17531
- }
17532
- applyBrandChrome(content);
17533
17558
  let sectionsJson = null;
17534
17559
  for (const [key, val] of Object.entries(content)) {
17535
17560
  if (key === "__ohw_sections") {
@@ -17537,11 +17562,6 @@ function OhhwellsBridge() {
17537
17562
  continue;
17538
17563
  }
17539
17564
  if (key === AI_SECTIONS_KEY) continue;
17540
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17541
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17542
- if (key === BRAND_KIT_KEY) continue;
17543
- if (key === STYLE_STORE_KEY) continue;
17544
- if (BRAND_CHROME_KEYS.has(key)) continue;
17545
17565
  if (applyVideoSettingNode(key, val)) continue;
17546
17566
  if (applyCarouselNode(key, val)) continue;
17547
17567
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17561,8 +17581,6 @@ function OhhwellsBridge() {
17561
17581
  });
17562
17582
  applyLinkByKey(key, val);
17563
17583
  }
17564
- applyLogoFromContent(content);
17565
- applyLogoSizes(content);
17566
17584
  if (sectionsJson) {
17567
17585
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
17568
17586
  sectionsLoadedRef.current = true;
@@ -17578,58 +17596,6 @@ function OhhwellsBridge() {
17578
17596
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
17579
17597
  postToParentRef.current({ type: "ow:hydrate-done" });
17580
17598
  };
17581
- const handleUpdateLogoIdentity = (e) => {
17582
- if (e.data?.type !== "ow:update-logo-identity") return;
17583
- const rawText = typeof e.data.text === "string" ? e.data.text : "";
17584
- const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17585
- const href = typeof e.data.href === "string" ? e.data.href : void 0;
17586
- const imageProvided = "image" in e.data;
17587
- const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17588
- let isPlaceholder = e.data.isPlaceholder !== false;
17589
- if (imageUrl) isPlaceholder = false;
17590
- else if (imageProvided && imageUrl === null) {
17591
- isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17592
- }
17593
- const display = applyLogoIdentity(rawText, isPlaceholder);
17594
- const displayAlt = resolveLogoDisplayText(alt || display);
17595
- if (imageUrl !== void 0) {
17596
- applyLogoImage(imageUrl, displayAlt);
17597
- } else {
17598
- for (const key of LOGO_IMAGE_KEYS) {
17599
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17600
- const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17601
- if (img) img.alt = displayAlt;
17602
- });
17603
- }
17604
- }
17605
- if (href !== void 0) {
17606
- applyLogoHref(href);
17607
- applyLinkByKey("nav-logo-href", href);
17608
- applyLinkByKey("footer-logo-href", href);
17609
- applyLinkByKey("logo-href", href);
17610
- }
17611
- const nodes = [
17612
- ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17613
- { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17614
- { key: LOGO_ALT_KEY, text: displayAlt }
17615
- ];
17616
- if (imageUrl !== void 0) {
17617
- if (imageUrl) {
17618
- for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17619
- } else {
17620
- for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17621
- }
17622
- }
17623
- if (href !== void 0) {
17624
- for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17625
- }
17626
- editContentRef.current = {
17627
- ...editContentRef.current,
17628
- ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17629
- };
17630
- applyLogoSizes(editContentRef.current);
17631
- postToParentRef.current({ type: "ow:change", nodes });
17632
- };
17633
17599
  window.addEventListener("message", handleHydrate);
17634
17600
  const postAiSectionsChanged = () => {
17635
17601
  postToParentRef.current({
@@ -17643,10 +17609,7 @@ function OhhwellsBridge() {
17643
17609
  const payload = e.data.payload;
17644
17610
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
17645
17611
  const previous = aiSectionsRef.current;
17646
- const nextState = applyTreeToState(parseAiSectionsState(previous), {
17647
- ...payload,
17648
- path: payload.path ?? window.location.pathname
17649
- });
17612
+ const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
17650
17613
  const nextValue = serializeAiSectionsState(nextState);
17651
17614
  aiSectionsRef.current = nextValue;
17652
17615
  applyAiSectionsToDom(nextState);
@@ -17683,42 +17646,12 @@ function OhhwellsBridge() {
17683
17646
  const value = typeof e.data.value === "string" ? e.data.value : "";
17684
17647
  aiSectionsRef.current = value;
17685
17648
  applyAiSectionsToDom(parseAiSectionsState(value));
17686
- applyStylesToDom(parseStyleStore(stylesRef.current));
17687
17649
  const restoredHeight = document.documentElement.scrollHeight;
17688
17650
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
17689
17651
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
17690
17652
  postAiSectionsChanged();
17691
17653
  };
17692
17654
  window.addEventListener("message", handleAiSetSections);
17693
- const handleAiSetBrand = (e) => {
17694
- if (e.data?.type !== "ow:ai-set-brand") return;
17695
- const value = typeof e.data.value === "string" ? e.data.value : "";
17696
- const previous = brandKitRef.current;
17697
- brandKitRef.current = value;
17698
- applyBrandToDom(parseBrandKit(value));
17699
- if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17700
- applyStylesToDom(parseStyleStore(stylesRef.current));
17701
- postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17702
- postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17703
- };
17704
- window.addEventListener("message", handleAiSetBrand);
17705
- const handleAiSetStyles = (e) => {
17706
- if (e.data?.type !== "ow:ai-set-styles") return;
17707
- const value = typeof e.data.value === "string" ? e.data.value : "";
17708
- const previous = stylesRef.current;
17709
- stylesRef.current = value;
17710
- applyStylesToDom(parseStyleStore(value));
17711
- postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
17712
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
17713
- };
17714
- window.addEventListener("message", handleAiSetStyles);
17715
- const handleGetBrand = (e) => {
17716
- if (e.data?.type !== "ow:get-brand") return;
17717
- const template = deriveTemplateBrand();
17718
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
17719
- postToParentRef.current({ type: "ow:brand-value", value });
17720
- };
17721
- window.addEventListener("message", handleGetBrand);
17722
17655
  const handleDeactivate = (e) => {
17723
17656
  if (e.data?.type !== "ow:deactivate") return;
17724
17657
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -17727,12 +17660,6 @@ function OhhwellsBridge() {
17727
17660
  closeLinkPopoverRef.current();
17728
17661
  return;
17729
17662
  }
17730
- if (floatingPanelOpenRef.current) {
17731
- setFloatingPanelRef.current(null);
17732
- deselectRef.current();
17733
- deactivateRef.current();
17734
- return;
17735
- }
17736
17663
  deselectRef.current();
17737
17664
  deactivateRef.current();
17738
17665
  };
@@ -17786,10 +17713,6 @@ function OhhwellsBridge() {
17786
17713
  return;
17787
17714
  }
17788
17715
  if (selectedElRef.current) {
17789
- if (toolbarVariantRef.current === "logo") {
17790
- deselectRef.current();
17791
- return;
17792
- }
17793
17716
  if (toolbarVariantRef.current === "select-frame") {
17794
17717
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
17795
17718
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -17809,6 +17732,13 @@ function OhhwellsBridge() {
17809
17732
  }
17810
17733
  }
17811
17734
  };
17735
+ const handleFormCount = (e) => {
17736
+ if (e.data?.type !== "ow:form-count") return;
17737
+ const form = formPickElRef.current;
17738
+ if (!form || formKeyOf(form) !== e.data.formKey) return;
17739
+ setFormPickCount(typeof e.data.count === "number" ? e.data.count : null);
17740
+ };
17741
+ window.addEventListener("message", handleFormCount);
17812
17742
  window.addEventListener("message", handleUiEscape);
17813
17743
  const handleKeyDown = (e) => {
17814
17744
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
@@ -17828,11 +17758,12 @@ function OhhwellsBridge() {
17828
17758
  closeFloatingPanelOnlyRef.current();
17829
17759
  return;
17830
17760
  }
17761
+ if (e.key === "Escape" && formPickElRef.current) {
17762
+ e.preventDefault();
17763
+ clearFormPickRef.current();
17764
+ return;
17765
+ }
17831
17766
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17832
- if (toolbarVariantRef.current === "logo") {
17833
- deselectRef.current();
17834
- return;
17835
- }
17836
17767
  if (toolbarVariantRef.current === "select-frame") {
17837
17768
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
17838
17769
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -17910,8 +17841,7 @@ function OhhwellsBridge() {
17910
17841
  const handleScroll = () => {
17911
17842
  const focusEl = activeElRef.current ?? selectedElRef.current;
17912
17843
  if (focusEl) {
17913
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17914
- const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
17844
+ const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17915
17845
  applyToolbarPos(r2);
17916
17846
  setToolbarRect(r2);
17917
17847
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -17921,9 +17851,7 @@ function OhhwellsBridge() {
17921
17851
  setToggleState((prev) => prev ? { ...prev, rect } : null);
17922
17852
  }
17923
17853
  if (hoveredItemElRef.current) {
17924
- const hoverEl = hoveredItemElRef.current;
17925
- const logo = getLogoElement(hoverEl);
17926
- setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
17854
+ setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17927
17855
  }
17928
17856
  if (hoveredNavContainerRef.current) {
17929
17857
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -17967,12 +17895,13 @@ function OhhwellsBridge() {
17967
17895
  if (aiSectionsRef.current) {
17968
17896
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
17969
17897
  }
17970
- if (stylesRef.current) {
17971
- nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
17972
- }
17973
- if (brandKitRef.current) {
17974
- nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17975
- }
17898
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
17899
+ const formKey = formKeyOf(form);
17900
+ if (!formKey) return;
17901
+ const specs = readFieldsFromDom(form);
17902
+ const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
17903
+ if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
17904
+ });
17976
17905
  postToParentRef.current({ type: "ow:save-result", nodes });
17977
17906
  };
17978
17907
  const handleInsertSection = (e) => {
@@ -17983,12 +17912,8 @@ function OhhwellsBridge() {
17983
17912
  if (inserted) {
17984
17913
  const tracker = getSectionsTracker();
17985
17914
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
17986
- const reportHeight = () => {
17987
- const h = document.body.scrollHeight;
17988
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17989
- };
17990
- reportHeight();
17991
- setTimeout(reportHeight, 500);
17915
+ const h = document.documentElement.scrollHeight;
17916
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17992
17917
  }
17993
17918
  };
17994
17919
  const handleSwitchSchedule = (e) => {
@@ -18181,17 +18106,13 @@ function OhhwellsBridge() {
18181
18106
  if (e.data?.type !== "ow:parent-scroll") return;
18182
18107
  const { iframeOffsetTop, headerH, canvasH } = e.data;
18183
18108
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
18184
- if (floatingPanelOpenRef.current) {
18185
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
18186
- }
18187
18109
  if (visibleViewportRef.current) {
18188
18110
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
18189
18111
  }
18190
18112
  const focusEl = activeElRef.current ?? selectedElRef.current;
18191
18113
  if (focusEl) {
18192
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
18193
- const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
18194
- applyToolbarPos(r2);
18114
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
18115
+ applyToolbarPos(measureEl.getBoundingClientRect());
18195
18116
  }
18196
18117
  };
18197
18118
  const handleClickAt = (e) => {
@@ -18224,15 +18145,9 @@ function OhhwellsBridge() {
18224
18145
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
18225
18146
  });
18226
18147
  if (logoAtPoint) {
18227
- if (!logoHasUploadedImage(logoAtPoint)) {
18228
- deselectRef.current();
18229
- deactivateRef.current();
18230
- const identity = readLogoIdentityFromDom();
18231
- postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
18232
- return;
18233
- }
18234
- selectLogoRef.current(logoAtPoint);
18235
- openLogoSizePanelRef.current(logoAtPoint);
18148
+ deselectRef.current();
18149
+ deactivateRef.current();
18150
+ postToParentRef.current({ type: "ow:open-logo-settings" });
18236
18151
  return;
18237
18152
  }
18238
18153
  const textEditable = Array.from(
@@ -18306,14 +18221,6 @@ function OhhwellsBridge() {
18306
18221
  window.addEventListener("message", handleParentScroll);
18307
18222
  window.addEventListener("message", handlePointerSync);
18308
18223
  window.addEventListener("message", handleClickAt);
18309
- window.addEventListener("message", handleUpdateLogoIdentity);
18310
- const handleViewMode = (e) => {
18311
- if (e.data?.type !== "ow:view-mode") return;
18312
- const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
18313
- setEditorViewport(mode);
18314
- applyLogoSizes(editContentRef.current);
18315
- };
18316
- window.addEventListener("message", handleViewMode);
18317
18224
  const handleViewportResize = () => {
18318
18225
  if (visibleViewportRef.current) {
18319
18226
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -18369,17 +18276,13 @@ function OhhwellsBridge() {
18369
18276
  window.removeEventListener("resize", handleViewportResize);
18370
18277
  window.removeEventListener("message", handlePointerSync);
18371
18278
  window.removeEventListener("message", handleClickAt);
18372
- window.removeEventListener("message", handleUpdateLogoIdentity);
18373
- window.removeEventListener("message", handleViewMode);
18374
18279
  window.removeEventListener("message", handleHydrate);
18375
18280
  window.removeEventListener("message", handleAiApplyTree);
18376
18281
  window.removeEventListener("message", handleAiDeleteSection);
18377
18282
  window.removeEventListener("message", handleAiSetSections);
18378
- window.removeEventListener("message", handleAiSetBrand);
18379
- window.removeEventListener("message", handleAiSetStyles);
18380
- window.removeEventListener("message", handleGetBrand);
18381
18283
  window.removeEventListener("message", handleDeactivate);
18382
18284
  window.removeEventListener("message", handleToastAction);
18285
+ window.removeEventListener("message", handleFormCount);
18383
18286
  window.removeEventListener("message", handleUiEscape);
18384
18287
  autoSaveTimers.current.forEach(clearTimeout);
18385
18288
  autoSaveTimers.current.clear();
@@ -18403,7 +18306,9 @@ function OhhwellsBridge() {
18403
18306
  if (footerDragRef.current) return;
18404
18307
  const target = e.target;
18405
18308
  if (!target) return;
18406
- if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel]')) {
18309
+ if (target.closest(
18310
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel], [data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"]'
18311
+ )) {
18407
18312
  return;
18408
18313
  }
18409
18314
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -18581,7 +18486,7 @@ function OhhwellsBridge() {
18581
18486
  postToParent2({
18582
18487
  type: "ow:ready",
18583
18488
  version: "1",
18584
- bridgeVersion: "0.1.64",
18489
+ bridgeVersion: "0.1.63",
18585
18490
  path: pathname,
18586
18491
  nodes: collectEditableNodes(editContentRef.current),
18587
18492
  sections
@@ -19048,6 +18953,151 @@ function OhhwellsBridge() {
19048
18953
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
19049
18954
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
19050
18955
  hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
18956
+ formPickRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
18957
+ ItemInteractionLayer,
18958
+ {
18959
+ rect: formPickRect,
18960
+ state: "active-top",
18961
+ itemDragSurface: false,
18962
+ toolbarAlign: "left",
18963
+ chromeGap: 24,
18964
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
18965
+ "div",
18966
+ {
18967
+ "data-ohw-form-toolbar": "",
18968
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
18969
+ children: [
18970
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
18971
+ "button",
18972
+ {
18973
+ type: "button",
18974
+ "aria-label": "Add field",
18975
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
18976
+ onClick: () => setFieldTypePickerOpen((open) => !open),
18977
+ "data-ohw-add-field": "",
18978
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Plus, { size: 15, "aria-hidden": true })
18979
+ }
18980
+ ),
18981
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
18982
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
18983
+ "button",
18984
+ {
18985
+ type: "button",
18986
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-semibold text-foreground transition-colors hover:bg-muted/80",
18987
+ onClick: () => {
18988
+ const form = formPickElRef.current;
18989
+ if (!form) return;
18990
+ postToParent2({
18991
+ type: "ow:form-pick",
18992
+ formKey: formKeyOf(form),
18993
+ hasLongText: formHasLongText(form)
18994
+ });
18995
+ },
18996
+ children: [
18997
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Settings, { size: 14, "aria-hidden": true }),
18998
+ "Form settings",
18999
+ formPickCount ? (
19000
+ // Counter pill, per the design — not a text suffix.
19001
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19002
+ "span",
19003
+ {
19004
+ "data-ohw-form-count": "",
19005
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
19006
+ children: formPickCount
19007
+ }
19008
+ )
19009
+ ) : null
19010
+ ]
19011
+ }
19012
+ ),
19013
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
19014
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19015
+ "button",
19016
+ {
19017
+ type: "button",
19018
+ "aria-pressed": formViewState === state,
19019
+ className: "rounded-md px-2.5 py-1 text-[13px] font-semibold capitalize transition-colors " + (formViewState === state ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
19020
+ onClick: () => {
19021
+ const form = formPickElRef.current;
19022
+ const key = form ? formKeyOf(form) : null;
19023
+ if (!form || !key) return;
19024
+ const initial = successInitialFor(form, key, editContentRef.current);
19025
+ setFormViewState(form, key, state, initial);
19026
+ setFormViewStateUi(state);
19027
+ setFormPickRect(form.getBoundingClientRect());
19028
+ if (state === "success") {
19029
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
19030
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
19031
+ } else {
19032
+ deactivateRef.current();
19033
+ }
19034
+ },
19035
+ children: state
19036
+ },
19037
+ state
19038
+ )) })
19039
+ ]
19040
+ }
19041
+ )
19042
+ }
19043
+ ),
19044
+ formHoverRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19045
+ ItemInteractionLayer,
19046
+ {
19047
+ rect: formHoverRect,
19048
+ state: "hover",
19049
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
19050
+ }
19051
+ ),
19052
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19053
+ ItemInteractionLayer,
19054
+ {
19055
+ rect: fieldPickRect,
19056
+ state: fieldDragging ? "dragging" : "active-top",
19057
+ itemDragSurface: false,
19058
+ toolbarAlign: "left",
19059
+ chromeGap: 10,
19060
+ showHandle: true,
19061
+ dragHandleLabel: "Reorder field",
19062
+ onDragHandleDragStart: handleFieldDragStart,
19063
+ onDragHandleDragEnd: handleFieldDragEnd,
19064
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19065
+ FormFieldToolbar,
19066
+ {
19067
+ type: fieldPickState.type,
19068
+ required: fieldPickState.required,
19069
+ onTypeChange: handleFieldTypeChange,
19070
+ onRequiredToggle: handleFieldRequiredToggle,
19071
+ onDuplicate: handleFieldDuplicate,
19072
+ onDelete: handleFieldDelete
19073
+ }
19074
+ )
19075
+ }
19076
+ ),
19077
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19078
+ "div",
19079
+ {
19080
+ className: "pointer-events-none fixed z-[2147483644]",
19081
+ style: { top: slot.top, left: slot.left, width: slot.width },
19082
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19083
+ DropIndicator,
19084
+ {
19085
+ direction: "horizontal",
19086
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
19087
+ className: "!w-full"
19088
+ }
19089
+ )
19090
+ },
19091
+ `field-drop-${i}`
19092
+ )) : null,
19093
+ fieldTypePickerOpen && formPickRect && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19094
+ "div",
19095
+ {
19096
+ className: "pointer-events-none fixed z-[2147483645]",
19097
+ style: { top: formPickRect.top + 16, left: formPickRect.left + 24 },
19098
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(FieldTypePicker, { onPick: handleAddField })
19099
+ }
19100
+ ),
19051
19101
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
19052
19102
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19053
19103
  FooterContainerChrome,
@@ -19057,7 +19107,7 @@ function OhhwellsBridge() {
19057
19107
  addDisabled: !canAddFooterColumn()
19058
19108
  }
19059
19109
  ),
19060
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19110
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19061
19111
  ItemInteractionLayer,
19062
19112
  {
19063
19113
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -19069,9 +19119,9 @@ function OhhwellsBridge() {
19069
19119
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
19070
19120
  onDragHandleDragStart: handleItemDragStart,
19071
19121
  onDragHandleDragEnd: handleItemDragEnd,
19072
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19073
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19074
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19122
+ onItemPointerDown: handleItemChromePointerDown,
19123
+ onItemClick: handleItemChromeClick,
19124
+ itemDragSurface: !isFooterFrameSelection,
19075
19125
  toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19076
19126
  ItemActionToolbar,
19077
19127
  {
@@ -19234,115 +19284,11 @@ function OhhwellsBridge() {
19234
19284
  }
19235
19285
  )
19236
19286
  }
19237
- ) : null,
19238
- floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19239
- FloatingPanel,
19240
- {
19241
- open: true,
19242
- title: floatingPanel.title,
19243
- context: floatingPanel.context,
19244
- position: floatingPanelPos,
19245
- onPositionChange: setFloatingPanelPos,
19246
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
19247
- onClose: closeFloatingPanelAndDeselect,
19248
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19249
- LogoSizePanel,
19250
- {
19251
- viewport: editorViewport,
19252
- sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
19253
- mobileFollowing: logoSizeDraft.mobileFollowing,
19254
- onSizeChange: (px) => {
19255
- const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
19256
- ...logoSizeDraft,
19257
- desktopPx: px,
19258
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
19259
- };
19260
- setLogoSizeDraft(next);
19261
- persistLogoSizeDraft(floatingPanel.placement, next);
19262
- },
19263
- onCustomizeMobile: () => {
19264
- const next = {
19265
- ...logoSizeDraft,
19266
- mobileFollowing: false,
19267
- mobilePx: logoSizeDraft.desktopPx
19268
- };
19269
- setLogoSizeDraft(next);
19270
- persistLogoSizeDraft(floatingPanel.placement, next);
19271
- },
19272
- onResetMobile: () => {
19273
- const next = {
19274
- ...logoSizeDraft,
19275
- mobileFollowing: true,
19276
- mobilePx: logoSizeDraft.desktopPx
19277
- };
19278
- setLogoSizeDraft(next);
19279
- persistLogoSizeDraft(floatingPanel.placement, next);
19280
- },
19281
- onUpdateEverywhere: () => {
19282
- const identity = readLogoIdentityFromDom();
19283
- postToParent2({ type: "ow:open-logo-settings", ...identity });
19284
- }
19285
- }
19286
- )
19287
- }
19288
19287
  ) : null
19289
19288
  ] }),
19290
19289
  bridgeRoot
19291
19290
  ) : null;
19292
19291
  }
19293
-
19294
- // src/ui/EmptySection.tsx
19295
- var import_link = __toESM(require("next/link"), 1);
19296
- var import_jsx_runtime34 = require("react/jsx-runtime");
19297
- function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
19298
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
19299
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19300
- "p",
19301
- {
19302
- style: {
19303
- fontFamily: "var(--brand-font-body)",
19304
- fontSize: "0.75rem",
19305
- fontWeight: 500,
19306
- letterSpacing: "0.15em",
19307
- textTransform: "uppercase",
19308
- color: "var(--brand-accent)",
19309
- marginBottom: "1.5rem"
19310
- },
19311
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
19312
- }
19313
- ),
19314
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19315
- "h1",
19316
- {
19317
- style: {
19318
- fontFamily: "var(--brand-font-heading)",
19319
- fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
19320
- lineHeight: 1.1,
19321
- letterSpacing: "-0.025em",
19322
- color: "var(--brand-text)",
19323
- marginBottom: "1rem"
19324
- },
19325
- ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
19326
- children: title
19327
- }
19328
- ),
19329
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19330
- "p",
19331
- {
19332
- style: {
19333
- fontFamily: "var(--brand-font-body)",
19334
- fontSize: "1rem",
19335
- lineHeight: 1.7,
19336
- fontWeight: 300,
19337
- color: "var(--brand-text-muted)",
19338
- maxWidth: "340px"
19339
- },
19340
- ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19341
- children: "This page doesn't have any content yet."
19342
- }
19343
- )
19344
- ] });
19345
- }
19346
19292
  // Annotate the CommonJS export names for ESM import in node:
19347
19293
  0 && (module.exports = {
19348
19294
  AI_DEFAULT_BRAND,
@@ -19360,7 +19306,6 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
19360
19306
  DropdownMenuItem,
19361
19307
  DropdownMenuSeparator,
19362
19308
  DropdownMenuTrigger,
19363
- EmptySection,
19364
19309
  ItemActionToolbar,
19365
19310
  ItemInteractionLayer,
19366
19311
  LinkEditorPanel,