@ohhwells/bridge 0.1.51-next.118 → 0.1.51-next.119

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
@@ -81,6 +81,9 @@ function setStoredLinkHref(key, href) {
81
81
  function getStoredLinkHref(key) {
82
82
  return linkHrefStore.get(key);
83
83
  }
84
+ function clearStoredLinkHref(key) {
85
+ linkHrefStore.delete(key);
86
+ }
84
87
  function enforceLinkHrefs() {
85
88
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
86
89
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -4581,13 +4584,14 @@ function ItemActionToolbar({
4581
4584
  selectParentDisabled = false,
4582
4585
  duplicateDisabled = false,
4583
4586
  deleteDisabled = false,
4587
+ showEditLink = true,
4584
4588
  showAddItem = true,
4585
4589
  showMore = true,
4586
4590
  tooltipSide = "bottom"
4587
4591
  }) {
4588
4592
  const [moreOpen, setMoreOpen] = (0, import_react4.useState)(false);
4589
4593
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(CustomToolbar, { children: [
4590
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4594
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4591
4595
  ToolbarActionTooltip,
4592
4596
  {
4593
4597
  label: "Edit link",
@@ -4607,7 +4611,7 @@ function ItemActionToolbar({
4607
4611
  },
4608
4612
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react2.Link, { className: "size-4 shrink-0", "aria-hidden": true })
4609
4613
  }
4610
- ),
4614
+ ) : null,
4611
4615
  showAddItem ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4612
4616
  ToolbarActionTooltip,
4613
4617
  {
@@ -7052,6 +7056,22 @@ function navbarIndexExistsInDom(index) {
7052
7056
  return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
7053
7057
  }
7054
7058
  function reconcileNavbarItemsFromContent(content) {
7059
+ const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
7060
+ if (orderForest?.length) {
7061
+ const allowed = new Set(flattenNavOrder(orderForest));
7062
+ for (const hrefKey of allowed) {
7063
+ const index = parseNavIndexFromKey(hrefKey);
7064
+ if (index === null) continue;
7065
+ if (navbarIndexExistsInDom(index)) continue;
7066
+ const href = content[`nav-${index}-href`];
7067
+ const label = content[`nav-${index}-label`];
7068
+ if (!href && !label) continue;
7069
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
7070
+ }
7071
+ pruneNavbarItemsNotInOrder(allowed);
7072
+ applyNavForest(orderForest);
7073
+ return;
7074
+ }
7055
7075
  const indices = collectNavbarIndicesFromContent(content);
7056
7076
  for (const index of indices) {
7057
7077
  if (navbarIndexExistsInDom(index)) continue;
@@ -7060,9 +7080,22 @@ function reconcileNavbarItemsFromContent(content) {
7060
7080
  if (!href && !label) continue;
7061
7081
  insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
7062
7082
  }
7063
- const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
7064
- if (orderForest?.length) {
7065
- applyNavForest(orderForest);
7083
+ }
7084
+ function pruneNavbarItemsNotInOrder(allowed) {
7085
+ const roots = [getNavbarDesktopContainer(), getNavbarDrawerContainer()].filter(
7086
+ (el) => Boolean(el)
7087
+ );
7088
+ const searchRoots = roots.length > 0 ? roots : [document.body];
7089
+ for (const root of searchRoots) {
7090
+ const items = Array.from(root.querySelectorAll("[data-ohw-href-key]")).filter(
7091
+ isNavbarLinkItem
7092
+ );
7093
+ for (const item of items) {
7094
+ if (!item.isConnected) continue;
7095
+ const key = item.getAttribute("data-ohw-href-key");
7096
+ if (!key || !isNavbarHrefKey(key) || allowed.has(key)) continue;
7097
+ resolveRemovableNavUnit(item).remove();
7098
+ }
7066
7099
  }
7067
7100
  }
7068
7101
  function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
@@ -7149,6 +7182,75 @@ function duplicateNavbarItem(sourceAnchor) {
7149
7182
  orderJson
7150
7183
  };
7151
7184
  }
7185
+ function collectSubtreeHrefKeys(node) {
7186
+ const keys = [node.id];
7187
+ for (const child of node.children) {
7188
+ keys.push(...collectSubtreeHrefKeys(child));
7189
+ }
7190
+ return keys;
7191
+ }
7192
+ function labelKeyFromHrefKey(hrefKey) {
7193
+ return hrefKey.replace(/-href$/, "-label");
7194
+ }
7195
+ function resolveRemovableNavUnit(anchor) {
7196
+ if (anchor.closest("[data-ohw-nav-children]")) return anchor;
7197
+ const group = anchor.closest("[data-ohw-nav-group]");
7198
+ if (group?.querySelector(":scope > [data-ohw-nav-children]")) return group;
7199
+ return anchor;
7200
+ }
7201
+ function deleteNavbarItem(sourceAnchor) {
7202
+ if (!isNavbarLinkItem(sourceAnchor)) return null;
7203
+ const sourceHrefKey = sourceAnchor.getAttribute("data-ohw-href-key");
7204
+ if (!sourceHrefKey || !isNavbarHrefKey(sourceHrefKey)) return null;
7205
+ const labelEl = sourceAnchor.querySelector('[data-ohw-editable="text"]');
7206
+ const label = (labelEl?.textContent ?? "").trim() || "Untitled";
7207
+ const previousForest = getNavForestFromDom();
7208
+ const previousOrderJson = serializeNavOrder(previousForest);
7209
+ const taken = takeNode(previousForest, sourceHrefKey);
7210
+ if (!taken) return null;
7211
+ const removedHrefKeys = collectSubtreeHrefKeys(taken.taken);
7212
+ const removedKeys = removedHrefKeys.flatMap((hrefKey) => [hrefKey, labelKeyFromHrefKey(hrefKey)]);
7213
+ const previousContent = {};
7214
+ for (const hrefKey of removedHrefKeys) {
7215
+ const el = findCounterpartByHrefKey(hrefKey, document) ?? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
7216
+ previousContent[hrefKey] = el ? getLinkHref(el) : "";
7217
+ const labEl = el?.querySelector('[data-ohw-editable="text"]');
7218
+ previousContent[labelKeyFromHrefKey(hrefKey)] = (labEl?.textContent ?? "").trim();
7219
+ }
7220
+ const roots = [getNavbarDesktopContainer(), getNavbarDrawerContainer()].filter(
7221
+ (el) => Boolean(el)
7222
+ );
7223
+ const searchRoots = roots.length > 0 ? roots : [document.body];
7224
+ const placements = [];
7225
+ for (const root of searchRoots) {
7226
+ const primary = findCounterpartByHrefKey(sourceHrefKey, root);
7227
+ if (!primary?.isConnected || !primary.parentElement) continue;
7228
+ const unit = resolveRemovableNavUnit(primary);
7229
+ if (!unit.parentElement) continue;
7230
+ placements.push({
7231
+ node: unit,
7232
+ parent: unit.parentElement,
7233
+ nextSibling: unit.nextSibling
7234
+ });
7235
+ unit.remove();
7236
+ }
7237
+ applyNavForest(taken.forest);
7238
+ const orderJson = serializeNavOrder(taken.forest);
7239
+ return {
7240
+ label,
7241
+ hrefKey: sourceHrefKey,
7242
+ removedKeys,
7243
+ previousContent,
7244
+ orderJson,
7245
+ previousOrderJson,
7246
+ undo: () => {
7247
+ for (const placement of placements) {
7248
+ placement.parent.insertBefore(placement.node, placement.nextSibling);
7249
+ }
7250
+ applyNavForest(previousForest);
7251
+ }
7252
+ };
7253
+ }
7152
7254
 
7153
7255
  // src/lib/footer-items.ts
7154
7256
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
@@ -7502,16 +7604,21 @@ function collectFooterItemsForColumn(content, colIndex) {
7502
7604
  }));
7503
7605
  }
7504
7606
  function reconcileFooterColumnsFromContent(content) {
7607
+ const order = parseFooterOrder(content);
7608
+ const allowedCols = order ? new Set(
7609
+ order.flatMap(
7610
+ (col) => col.map((k) => parseFooterHrefKey(k)?.col).filter((n) => n !== void 0 && n !== null)
7611
+ )
7612
+ ) : null;
7505
7613
  const indices = collectFooterColumnIndicesFromContent(content);
7506
7614
  for (const colIndex of indices) {
7615
+ if (allowedCols && !allowedCols.has(colIndex)) continue;
7507
7616
  if (footerColumnExistsInDom(colIndex)) continue;
7508
7617
  const heading = content[`footer-${colIndex}-heading`] ?? DEFAULT_FOOTER_COLUMN_HEADING;
7509
7618
  const items = collectFooterItemsForColumn(content, colIndex);
7510
7619
  const hasPayload = Boolean(content[`footer-${colIndex}-heading`]) || items.some(
7511
7620
  (_, i) => content[`footer-${colIndex}-${i}-href`] !== void 0 || content[`footer-${colIndex}-${i}-label`] !== void 0
7512
- ) || (parseFooterOrder(content)?.some(
7513
- (col) => col.some((k) => parseFooterHrefKey(k)?.col === colIndex)
7514
- ) ?? false);
7621
+ ) || (order?.some((col) => col.some((k) => parseFooterHrefKey(k)?.col === colIndex)) ?? false);
7515
7622
  if (!hasPayload) continue;
7516
7623
  insertFooterColumnDom(colIndex, heading, items);
7517
7624
  }
@@ -7520,13 +7627,48 @@ function reconcileFooterOrderFromContent(content) {
7520
7627
  reconcileFooterColumnsFromContent(content);
7521
7628
  const order = parseFooterOrder(content);
7522
7629
  if (!order?.length) return;
7523
- const current = getFooterOrderFromDom();
7524
- if (ordersEqual(order, current)) {
7525
- syncFooterColumnIndices(listFooterColumns());
7526
- return;
7630
+ const linkTemplate = getFooterLinkTemplate();
7631
+ for (let c = 0; c < order.length; c++) {
7632
+ const colOrder = order[c];
7633
+ for (const hrefKey of colOrder) {
7634
+ if (findFooterLinkByKey(hrefKey)) continue;
7635
+ const parsed = parseFooterHrefKey(hrefKey);
7636
+ if (!parsed) continue;
7637
+ const columns = listFooterColumns();
7638
+ const column = columns.find((col) => col.getAttribute("data-ohw-footer-col") === String(parsed.col)) ?? columns[c] ?? null;
7639
+ if (!column) continue;
7640
+ const href = content[hrefKey] ?? DEFAULT_FOOTER_COLUMN_LINK_HREF;
7641
+ const labelKey = `footer-${parsed.col}-${parsed.item}-label`;
7642
+ const label = content[labelKey] ?? DEFAULT_FOOTER_COLUMN_LINK_LABEL;
7643
+ if (!href && !label) continue;
7644
+ column.appendChild(buildFooterLink(parsed.col, parsed.item, href || "/", label, linkTemplate));
7645
+ }
7527
7646
  }
7647
+ pruneFooterToOrder(order);
7528
7648
  applyFooterOrder(order);
7529
7649
  }
7650
+ function pruneFooterToOrder(order) {
7651
+ const allowedKeys = new Set(order.flat());
7652
+ const allowedColIndices = new Set(
7653
+ [...allowedKeys].map((k) => parseFooterHrefKey(k)?.col).filter((n) => n !== void 0 && n !== null)
7654
+ );
7655
+ for (const column of [...listFooterColumns()]) {
7656
+ if (!column.isConnected) continue;
7657
+ for (const link of [...listFooterLinksInColumn(column)]) {
7658
+ const key = link.getAttribute("data-ohw-href-key");
7659
+ if (!key || allowedKeys.has(key)) continue;
7660
+ link.remove();
7661
+ }
7662
+ const remaining = listFooterLinksInColumn(column);
7663
+ const colAttr = column.getAttribute("data-ohw-footer-col");
7664
+ const colIndex = colAttr != null ? parseInt(colAttr, 10) : NaN;
7665
+ const referenced = remaining.some((link) => allowedKeys.has(link.getAttribute("data-ohw-href-key") ?? ""));
7666
+ if (!referenced && (!Number.isFinite(colIndex) || !allowedColIndices.has(colIndex))) {
7667
+ column.remove();
7668
+ }
7669
+ }
7670
+ syncFooterColumnIndices(listFooterColumns());
7671
+ }
7530
7672
  function resolveFooterLinksContainerSelectionTarget(target, clientX, clientY, isPointOverItem) {
7531
7673
  const container = getFooterLinksContainer();
7532
7674
  if (!container) return null;
@@ -7771,6 +7913,95 @@ function duplicateFooterItem(sourceAnchor) {
7771
7913
  const label = (labelEl?.textContent ?? "").trim() || "Untitled";
7772
7914
  return insertFooterItem(column, href || "/", label, sourceAnchor);
7773
7915
  }
7916
+ function footerLabelKeyFromHrefKey(hrefKey) {
7917
+ return hrefKey.replace(/-href$/, "-label");
7918
+ }
7919
+ function deleteFooterItem(sourceAnchor) {
7920
+ if (!isFooterLinkAnchor(sourceAnchor)) return null;
7921
+ const hrefKey = sourceAnchor.getAttribute("data-ohw-href-key");
7922
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return null;
7923
+ const labelEl = sourceAnchor.querySelector('[data-ohw-editable="text"]');
7924
+ const label = (labelEl?.textContent ?? "").trim() || "Untitled";
7925
+ const labelKey = footerLabelKeyFromHrefKey(hrefKey);
7926
+ const previousOrder = getFooterOrderFromDom();
7927
+ const previousOrderJson = JSON.stringify(previousOrder);
7928
+ const previousContent = {
7929
+ [hrefKey]: getLinkHref2(sourceAnchor),
7930
+ [labelKey]: label
7931
+ };
7932
+ if (!sourceAnchor.parentElement) return null;
7933
+ const placement = {
7934
+ node: sourceAnchor,
7935
+ parent: sourceAnchor.parentElement,
7936
+ nextSibling: sourceAnchor.nextSibling
7937
+ };
7938
+ sourceAnchor.remove();
7939
+ const order = getFooterOrderFromDom();
7940
+ applyFooterOrder(order);
7941
+ const orderJson = JSON.stringify(order);
7942
+ return {
7943
+ label,
7944
+ removedKeys: [hrefKey, labelKey],
7945
+ previousContent,
7946
+ orderJson,
7947
+ previousOrderJson,
7948
+ undo: () => {
7949
+ placement.parent.insertBefore(placement.node, placement.nextSibling);
7950
+ applyFooterOrder(previousOrder);
7951
+ }
7952
+ };
7953
+ }
7954
+ function deleteFooterColumn(column) {
7955
+ const columns = listFooterColumns();
7956
+ if (!columns.includes(column)) return null;
7957
+ if (!column.parentElement) return null;
7958
+ const links = listFooterLinksInColumn(column);
7959
+ const heading = column.querySelector(
7960
+ '[data-ohw-key^="footer-"][data-ohw-key$="-heading"]'
7961
+ );
7962
+ const headingKey = heading?.getAttribute("data-ohw-key") ?? null;
7963
+ const headingText = (heading?.textContent ?? "").trim();
7964
+ const label = headingText || (links[0]?.querySelector('[data-ohw-editable="text"]')?.textContent ?? "").trim() || "Untitled";
7965
+ const previousOrder = getFooterOrderFromDom();
7966
+ const previousOrderJson = JSON.stringify(previousOrder);
7967
+ const previousContent = {};
7968
+ const removedKeys = [];
7969
+ if (headingKey) {
7970
+ removedKeys.push(headingKey);
7971
+ previousContent[headingKey] = headingText;
7972
+ }
7973
+ for (const link of links) {
7974
+ const hrefKey = link.getAttribute("data-ohw-href-key");
7975
+ if (!hrefKey) continue;
7976
+ const labelKey = footerLabelKeyFromHrefKey(hrefKey);
7977
+ removedKeys.push(hrefKey, labelKey);
7978
+ previousContent[hrefKey] = getLinkHref2(link);
7979
+ previousContent[labelKey] = (link.querySelector('[data-ohw-editable="text"]')?.textContent ?? "").trim();
7980
+ }
7981
+ const placement = {
7982
+ node: column,
7983
+ parent: column.parentElement,
7984
+ nextSibling: column.nextSibling
7985
+ };
7986
+ column.remove();
7987
+ const remaining = listFooterColumns();
7988
+ syncFooterColumnIndices(remaining);
7989
+ const order = getFooterOrderFromDom();
7990
+ applyFooterOrder(order);
7991
+ const orderJson = JSON.stringify(order);
7992
+ return {
7993
+ label,
7994
+ removedKeys,
7995
+ previousContent,
7996
+ orderJson,
7997
+ previousOrderJson,
7998
+ undo: () => {
7999
+ placement.parent.insertBefore(placement.node, placement.nextSibling);
8000
+ syncFooterColumnIndices(listFooterColumns());
8001
+ applyFooterOrder(previousOrder);
8002
+ }
8003
+ };
8004
+ }
7774
8005
 
7775
8006
  // src/lib/add-footer-column.ts
7776
8007
  function buildFooterColumnEditContentPatch(result) {
@@ -8471,6 +8702,331 @@ function useOhwCarousel(key, initial) {
8471
8702
  return { images, setImages, bind };
8472
8703
  }
8473
8704
 
8705
+ // src/lib/collect-editable-nodes.ts
8706
+ var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
8707
+ var VIDEO_MUTED_SUFFIX = "__ohw_muted";
8708
+ function getVideoEl(el) {
8709
+ return el instanceof HTMLVideoElement ? el : el.querySelector("video");
8710
+ }
8711
+ function getLinkHref3(el) {
8712
+ const key = el.getAttribute("data-ohw-href-key");
8713
+ if (key) {
8714
+ const stored = getStoredLinkHref(key);
8715
+ if (stored !== void 0) return stored;
8716
+ }
8717
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
8718
+ return anchor?.getAttribute("href") ?? "";
8719
+ }
8720
+ function collectEditableNodes(extraContent) {
8721
+ const nodes = Array.from(
8722
+ document.querySelectorAll("[data-ohw-editable]")
8723
+ ).map((el) => {
8724
+ if (el.dataset.ohwEditable === "image") {
8725
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
8726
+ return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
8727
+ }
8728
+ if (el.dataset.ohwEditable === "bg-image") {
8729
+ const raw = el.style.backgroundImage;
8730
+ const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
8731
+ return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
8732
+ }
8733
+ if (el.dataset.ohwEditable === "video") {
8734
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
8735
+ }
8736
+ if (el.dataset.ohwEditable === "link") {
8737
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
8738
+ }
8739
+ return {
8740
+ key: el.dataset.ohwKey ?? "",
8741
+ type: el.dataset.ohwEditable ?? "text",
8742
+ text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
8743
+ };
8744
+ });
8745
+ document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
8746
+ const key = el.getAttribute("data-ohw-href-key") ?? "";
8747
+ if (!key) return;
8748
+ nodes.push({ key, type: "link", text: getLinkHref3(el) });
8749
+ });
8750
+ if (extraContent) {
8751
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8752
+ const text = extraContent[key];
8753
+ if (typeof text === "string" && text.length > 0) {
8754
+ nodes.push({ key, type: "meta", text });
8755
+ }
8756
+ }
8757
+ }
8758
+ document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
8759
+ const key = el.dataset.ohwKey ?? "";
8760
+ const video = getVideoEl(el);
8761
+ if (!key || !video) return;
8762
+ nodes.push({
8763
+ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`,
8764
+ type: "video-setting",
8765
+ text: String(video.autoplay)
8766
+ });
8767
+ nodes.push({
8768
+ key: `${key}${VIDEO_MUTED_SUFFIX}`,
8769
+ type: "video-setting",
8770
+ text: String(video.muted)
8771
+ });
8772
+ });
8773
+ for (const key of listCarouselKeys()) {
8774
+ nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
8775
+ }
8776
+ const byKey = /* @__PURE__ */ new Map();
8777
+ for (const node of nodes) {
8778
+ if (!node.key) continue;
8779
+ if (!byKey.has(node.key)) byKey.set(node.key, node);
8780
+ }
8781
+ if (extraContent) {
8782
+ applyNavFooterDeleteOverrides(byKey, extraContent);
8783
+ }
8784
+ return Array.from(byKey.values());
8785
+ }
8786
+ function applyNavFooterDeleteOverrides(byKey, content) {
8787
+ for (const [key, text] of Object.entries(content)) {
8788
+ if (text !== "") continue;
8789
+ if (!key.startsWith("nav-") && !key.startsWith("footer-")) continue;
8790
+ if (key === "nav-book-href" || key === "nav-book-label") continue;
8791
+ byKey.set(key, { key, type: "meta", text: "" });
8792
+ }
8793
+ const navOrder = content[NAV_ORDER_KEY];
8794
+ if (navOrder) {
8795
+ try {
8796
+ const allowed = parseAllowedNavHrefKeys(navOrder);
8797
+ const clearKey = (key) => {
8798
+ byKey.set(key, { key, type: "meta", text: "" });
8799
+ };
8800
+ for (const key of /* @__PURE__ */ new Set([...byKey.keys(), ...Object.keys(content)])) {
8801
+ if (key === "nav-book-href" || key === "nav-book-label") continue;
8802
+ if (isNavbarHrefKey(key) && !allowed.has(key)) {
8803
+ clearKey(key);
8804
+ clearKey(key.replace(/-href$/, "-label"));
8805
+ } else if (/^nav-\d+-label$/.test(key)) {
8806
+ const hrefKey = key.replace(/-label$/, "-href");
8807
+ if (!allowed.has(hrefKey)) clearKey(key);
8808
+ }
8809
+ }
8810
+ } catch {
8811
+ }
8812
+ }
8813
+ const footerOrder = content[FOOTER_ORDER_KEY];
8814
+ if (footerOrder) {
8815
+ try {
8816
+ const { allowedKeys, allowedCols } = parseAllowedFooterKeys(footerOrder);
8817
+ const clearKey = (key) => {
8818
+ byKey.set(key, { key, type: "meta", text: "" });
8819
+ };
8820
+ for (const key of /* @__PURE__ */ new Set([...byKey.keys(), ...Object.keys(content)])) {
8821
+ if (isFooterHrefKey(key) && !allowedKeys.has(key)) {
8822
+ clearKey(key);
8823
+ clearKey(key.replace(/-href$/, "-label"));
8824
+ continue;
8825
+ }
8826
+ const labelMatch = key.match(/^footer-(\d+)-(\d+)-label$/);
8827
+ if (labelMatch) {
8828
+ const hrefKey = `footer-${labelMatch[1]}-${labelMatch[2]}-href`;
8829
+ if (!allowedKeys.has(hrefKey)) clearKey(key);
8830
+ continue;
8831
+ }
8832
+ const headingMatch = key.match(/^footer-(\d+)-heading$/);
8833
+ if (headingMatch && !allowedCols.has(parseInt(headingMatch[1], 10))) {
8834
+ clearKey(key);
8835
+ }
8836
+ }
8837
+ } catch {
8838
+ }
8839
+ }
8840
+ }
8841
+ function parseAllowedNavHrefKeys(raw) {
8842
+ const parsed = JSON.parse(raw);
8843
+ const allowed = /* @__PURE__ */ new Set();
8844
+ const walk = (nodes) => {
8845
+ if (!Array.isArray(nodes)) return;
8846
+ for (const node of nodes) {
8847
+ if (typeof node === "string" && node.length > 0) {
8848
+ allowed.add(node);
8849
+ continue;
8850
+ }
8851
+ if (node && typeof node === "object" && typeof node.id === "string") {
8852
+ allowed.add(node.id);
8853
+ walk(node.children);
8854
+ }
8855
+ }
8856
+ };
8857
+ walk(parsed);
8858
+ return allowed;
8859
+ }
8860
+ function parseAllowedFooterKeys(raw) {
8861
+ const parsed = JSON.parse(raw);
8862
+ const allowedKeys = /* @__PURE__ */ new Set();
8863
+ const allowedCols = /* @__PURE__ */ new Set();
8864
+ if (Array.isArray(parsed)) {
8865
+ for (const col of parsed) {
8866
+ if (!Array.isArray(col)) continue;
8867
+ for (const key of col) {
8868
+ if (typeof key !== "string" || !key) continue;
8869
+ allowedKeys.add(key);
8870
+ const m = key.match(/^footer-(\d+)-\d+-href$/);
8871
+ if (m) allowedCols.add(parseInt(m[1], 10));
8872
+ }
8873
+ }
8874
+ }
8875
+ return { allowedKeys, allowedCols };
8876
+ }
8877
+
8878
+ // src/lib/delete-nav-footer-selection.ts
8879
+ function isNavbarLinksContainer(el) {
8880
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer");
8881
+ }
8882
+ function isNavigationItem(el) {
8883
+ const anchor = el.matches("[data-ohw-href-key]") ? el : el.closest("[data-ohw-href-key]");
8884
+ if (!anchor) return false;
8885
+ return Boolean(anchor.querySelector('[data-ohw-editable="text"]'));
8886
+ }
8887
+ function findFooterItemGroup(item) {
8888
+ const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8889
+ if (explicit) return explicit;
8890
+ const footer = item.closest("footer");
8891
+ if (!footer) return null;
8892
+ let node = item.parentElement;
8893
+ while (node && node !== footer) {
8894
+ const count = Array.from(node.querySelectorAll("[data-ohw-href-key]")).filter(
8895
+ isNavigationItem
8896
+ ).length;
8897
+ if (count >= 2) return node;
8898
+ node = node.parentElement;
8899
+ }
8900
+ return footer;
8901
+ }
8902
+ function isInferredFooterGroup(el) {
8903
+ const footer = el.closest("footer");
8904
+ if (!footer || el === footer) return false;
8905
+ if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
8906
+ const count = Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(
8907
+ isNavigationItem
8908
+ ).length;
8909
+ return count >= 2;
8910
+ }
8911
+ function newActionId() {
8912
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `delete-${Date.now()}`;
8913
+ }
8914
+ function deleteSelectedNavFooterItem(deps) {
8915
+ const {
8916
+ selected,
8917
+ isTextEditing,
8918
+ isSelectFrame,
8919
+ getEditContent,
8920
+ applyLinkByKey: applyLinkByKey2,
8921
+ postToParent: postToParent2,
8922
+ deselect,
8923
+ setEditContent,
8924
+ setPendingUndo
8925
+ } = deps;
8926
+ if (isTextEditing) return false;
8927
+ if (isNavbarLinksContainer(selected) || isFooterLinksContainer(selected)) return false;
8928
+ const finishDelete = (opts) => {
8929
+ const actionId = newActionId();
8930
+ const clearedNodes = opts.removedKeys.map((key) => ({
8931
+ key,
8932
+ text: ""
8933
+ }));
8934
+ clearedNodes.push({ key: opts.orderKey, text: opts.orderJson });
8935
+ const nextContent = { ...getEditContent() };
8936
+ for (const key of opts.removedKeys) {
8937
+ nextContent[key] = "";
8938
+ clearStoredLinkHref(key);
8939
+ }
8940
+ nextContent[opts.orderKey] = opts.orderJson;
8941
+ setEditContent(nextContent);
8942
+ postToParent2({ type: "ow:change", nodes: clearedNodes });
8943
+ setPendingUndo({
8944
+ actionId,
8945
+ restore: () => {
8946
+ opts.undoDom();
8947
+ for (const [key, text] of Object.entries(opts.previousContent)) {
8948
+ if (key.endsWith("-href")) applyLinkByKey2(key, text);
8949
+ else {
8950
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
8951
+ el.textContent = text;
8952
+ });
8953
+ }
8954
+ }
8955
+ setEditContent({
8956
+ ...getEditContent(),
8957
+ ...opts.previousContent,
8958
+ [opts.orderKey]: opts.previousOrderJson
8959
+ });
8960
+ postToParent2({
8961
+ type: "ow:change",
8962
+ nodes: [
8963
+ ...Object.entries(opts.previousContent).map(([key, text]) => ({ key, text })),
8964
+ { key: opts.orderKey, text: opts.previousOrderJson }
8965
+ ]
8966
+ });
8967
+ }
8968
+ });
8969
+ deselect();
8970
+ postToParent2({
8971
+ type: "ow:toast",
8972
+ title: opts.toastTitle,
8973
+ toastType: "success",
8974
+ actionLabel: "Undo",
8975
+ actionId,
8976
+ duration: 6e3
8977
+ });
8978
+ };
8979
+ const isFooterColumnSelection = selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") || isSelectFrame && isInferredFooterGroup(selected) && Boolean(selected.closest("footer"));
8980
+ if (isFooterColumnSelection) {
8981
+ const column = selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") ? selected : findFooterItemGroup(selected);
8982
+ if (!column) return false;
8983
+ const result = deleteFooterColumn(column);
8984
+ if (!result) return false;
8985
+ finishDelete({
8986
+ toastTitle: "Item deleted",
8987
+ removedKeys: result.removedKeys,
8988
+ previousContent: result.previousContent,
8989
+ orderKey: FOOTER_ORDER_KEY,
8990
+ orderJson: result.orderJson,
8991
+ previousOrderJson: result.previousOrderJson,
8992
+ undoDom: result.undo
8993
+ });
8994
+ return true;
8995
+ }
8996
+ if (!isNavigationItem(selected)) return false;
8997
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8998
+ if (!hrefKey) return false;
8999
+ if (isNavbarHrefKey(hrefKey)) {
9000
+ const result = deleteNavbarItem(selected);
9001
+ if (!result) return false;
9002
+ finishDelete({
9003
+ toastTitle: "Nav item deleted",
9004
+ removedKeys: result.removedKeys,
9005
+ previousContent: result.previousContent,
9006
+ orderKey: NAV_ORDER_KEY,
9007
+ orderJson: result.orderJson,
9008
+ previousOrderJson: result.previousOrderJson,
9009
+ undoDom: result.undo
9010
+ });
9011
+ return true;
9012
+ }
9013
+ if (isFooterHrefKey(hrefKey)) {
9014
+ const result = deleteFooterItem(selected);
9015
+ if (!result) return false;
9016
+ finishDelete({
9017
+ toastTitle: "Item deleted",
9018
+ removedKeys: result.removedKeys,
9019
+ previousContent: result.previousContent,
9020
+ orderKey: FOOTER_ORDER_KEY,
9021
+ orderJson: result.orderJson,
9022
+ previousOrderJson: result.previousOrderJson,
9023
+ undoDom: result.undo
9024
+ });
9025
+ return true;
9026
+ }
9027
+ return false;
9028
+ }
9029
+
8474
9030
  // src/ui/navbar-container-chrome.tsx
8475
9031
  var import_lucide_react12 = require("lucide-react");
8476
9032
  var import_jsx_runtime24 = require("react/jsx-runtime");
@@ -8800,7 +9356,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
8800
9356
  }
8801
9357
  }
8802
9358
  }
8803
- function getLinkHref3(el) {
9359
+ function getLinkHref4(el) {
8804
9360
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
8805
9361
  return anchor?.getAttribute("href") ?? "";
8806
9362
  }
@@ -8828,78 +9384,22 @@ function canDragNavigationItem(anchor) {
8828
9384
  }
8829
9385
  function syncNavigationDragCursorAttrs() {
8830
9386
  document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]").forEach((el) => {
8831
- if (!isNavigationItem(el) || !canDragNavigationItem(el)) {
9387
+ if (!isNavigationItem2(el) || !canDragNavigationItem(el)) {
8832
9388
  el.removeAttribute("data-ohw-can-drag");
8833
9389
  return;
8834
9390
  }
8835
9391
  el.setAttribute("data-ohw-can-drag", "");
8836
9392
  });
8837
9393
  }
8838
- function collectEditableNodes(extraContent) {
8839
- const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
8840
- if (el.dataset.ohwEditable === "image") {
8841
- const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
8842
- return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
8843
- }
8844
- if (el.dataset.ohwEditable === "bg-image") {
8845
- const raw = el.style.backgroundImage;
8846
- const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
8847
- return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
8848
- }
8849
- if (el.dataset.ohwEditable === "video") {
8850
- return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
8851
- }
8852
- if (el.dataset.ohwEditable === "link") {
8853
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
8854
- }
8855
- return {
8856
- key: el.dataset.ohwKey ?? "",
8857
- type: el.dataset.ohwEditable ?? "text",
8858
- text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
8859
- };
8860
- });
8861
- document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
8862
- const key = el.getAttribute("data-ohw-href-key") ?? "";
8863
- if (!key) return;
8864
- nodes.push({ key, type: "link", text: getLinkHref3(el) });
8865
- });
8866
- if (extraContent) {
8867
- for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8868
- const text = extraContent[key];
8869
- if (typeof text === "string" && text.length > 0) {
8870
- nodes.push({ key, type: "meta", text });
8871
- }
8872
- }
8873
- }
8874
- document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
8875
- const key = el.dataset.ohwKey ?? "";
8876
- const video = getVideoEl(el);
8877
- if (!key || !video) return;
8878
- nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
8879
- nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
8880
- });
8881
- for (const key of listCarouselKeys()) {
8882
- nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
8883
- }
8884
- const seen = /* @__PURE__ */ new Set();
8885
- return nodes.filter((node) => {
8886
- if (!node.key) return true;
8887
- if (seen.has(node.key)) return false;
8888
- seen.add(node.key);
8889
- return true;
8890
- });
8891
- }
8892
9394
  function isMediaEditable(el) {
8893
9395
  const t = el.dataset.ohwEditable;
8894
9396
  return t === "image" || t === "bg-image" || t === "video";
8895
9397
  }
8896
9398
  var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
8897
9399
  var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"])';
8898
- function getVideoEl(el) {
9400
+ function getVideoEl2(el) {
8899
9401
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
8900
9402
  }
8901
- var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
8902
- var VIDEO_MUTED_SUFFIX = "__ohw_muted";
8903
9403
  function isBackgroundVideo(video) {
8904
9404
  return video.autoplay && video.muted;
8905
9405
  }
@@ -8944,7 +9444,7 @@ function applyVideoSettingNode(key, val) {
8944
9444
  const baseKey = key.slice(0, key.length - suffixLen);
8945
9445
  const on = val === "true";
8946
9446
  document.querySelectorAll(`[data-ohw-key="${baseKey}"]`).forEach((el) => {
8947
- const video = getVideoEl(el);
9447
+ const video = getVideoEl2(el);
8948
9448
  if (!video) return;
8949
9449
  if (isAutoplay) video.autoplay = on;
8950
9450
  else video.muted = on;
@@ -8984,13 +9484,13 @@ function getNavigationItemAnchor(el) {
8984
9484
  if (!anchor.querySelector('[data-ohw-editable="text"]')) return null;
8985
9485
  return anchor;
8986
9486
  }
8987
- function isNavigationItem(el) {
9487
+ function isNavigationItem2(el) {
8988
9488
  return getNavigationItemAnchor(el) !== null;
8989
9489
  }
8990
9490
  function listNavigationItems() {
8991
9491
  return Array.from(
8992
9492
  document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
8993
- ).filter((el) => isNavigationItem(el));
9493
+ ).filter((el) => isNavigationItem2(el));
8994
9494
  }
8995
9495
  function getNavigationItemReorderState(anchor) {
8996
9496
  if (isNavbarButton2(anchor)) return { key: null, disabled: false };
@@ -9021,9 +9521,9 @@ function getNavigationRoot(el) {
9021
9521
  return el.closest("nav, footer, aside");
9022
9522
  }
9023
9523
  function countFooterNavItems(el) {
9024
- return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
9524
+ return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem2).length;
9025
9525
  }
9026
- function findFooterItemGroup(item) {
9526
+ function findFooterItemGroup2(item) {
9027
9527
  const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
9028
9528
  if (explicit) return explicit;
9029
9529
  const footer = item.closest("footer");
@@ -9038,16 +9538,16 @@ function findFooterItemGroup(item) {
9038
9538
  function isNavigationRoot(el) {
9039
9539
  return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
9040
9540
  }
9041
- function isInferredFooterGroup(el) {
9541
+ function isInferredFooterGroup2(el) {
9042
9542
  const footer = el.closest("footer");
9043
9543
  if (!footer || el === footer) return false;
9044
9544
  if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
9045
9545
  return countFooterNavItems(el) >= 2;
9046
9546
  }
9047
9547
  function isNavigationContainer(el) {
9048
- return el.hasAttribute("data-ohw-nav-container") || isFooterLinksContainer(el) || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
9548
+ return el.hasAttribute("data-ohw-nav-container") || isFooterLinksContainer(el) || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup2(el);
9049
9549
  }
9050
- function isNavbarLinksContainer(el) {
9550
+ function isNavbarLinksContainer2(el) {
9051
9551
  return el.hasAttribute("data-ohw-nav-container");
9052
9552
  }
9053
9553
  function getFooterColumn(el) {
@@ -9090,7 +9590,7 @@ function findHoveredNavOrFooterContainer(x, y) {
9090
9590
  }
9091
9591
  function isPointOverNavItem(container, x, y) {
9092
9592
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
9093
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9593
+ if (!isNavigationItem2(el) || isNavbarButton2(el)) return false;
9094
9594
  const itemRect = el.getBoundingClientRect();
9095
9595
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9096
9596
  });
@@ -9122,32 +9622,32 @@ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
9122
9622
  return null;
9123
9623
  }
9124
9624
  function getNavigationSelectionParent(el) {
9125
- if (isNavigationItem(el)) {
9625
+ if (isNavigationItem2(el)) {
9126
9626
  const explicit = el.closest("[data-ohw-nav-container]");
9127
9627
  if (explicit) return explicit;
9128
9628
  const footerColumn = getFooterColumn(el);
9129
9629
  if (footerColumn) return footerColumn;
9130
- const footerGroup = findFooterItemGroup(el);
9630
+ const footerGroup = findFooterItemGroup2(el);
9131
9631
  if (footerGroup) return footerGroup;
9132
9632
  return getNavigationRoot(el);
9133
9633
  }
9134
- if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el))) {
9634
+ if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
9135
9635
  return getFooterLinksContainer();
9136
9636
  }
9137
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup(el)) {
9637
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
9138
9638
  return getNavigationRoot(el);
9139
9639
  }
9140
9640
  return null;
9141
9641
  }
9142
9642
  function collectNavigationItemSiblingHintRects(selected) {
9143
- if (!isNavigationItem(selected)) return [];
9643
+ if (!isNavigationItem2(selected)) return [];
9144
9644
  const footerColumn = getFooterColumn(selected);
9145
9645
  if (footerColumn) {
9146
9646
  return listFooterLinksInColumn(footerColumn).filter((link) => link !== selected).map((link) => link.getBoundingClientRect());
9147
9647
  }
9148
9648
  const navContainer = selected.closest("[data-ohw-nav-container], [data-ohw-nav-drawer]");
9149
9649
  if (navContainer) {
9150
- return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem(el) && el !== selected).map((el) => el.getBoundingClientRect());
9650
+ return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem2(el) && el !== selected).map((el) => el.getBoundingClientRect());
9151
9651
  }
9152
9652
  return listNavigationItems().filter((el) => el !== selected).map((el) => el.getBoundingClientRect());
9153
9653
  }
@@ -9807,6 +10307,9 @@ function OhhwellsBridge() {
9807
10307
  });
9808
10308
  const commitNavigationTextEditRef = (0, import_react11.useRef)(() => {
9809
10309
  });
10310
+ const handleDeleteSelectedRef = (0, import_react11.useRef)(() => false);
10311
+ const runPendingDeleteUndoRef = (0, import_react11.useRef)(() => false);
10312
+ const isFooterFrameSelectionRef = (0, import_react11.useRef)(false);
9810
10313
  const refreshActiveCommandsRef = (0, import_react11.useRef)(() => {
9811
10314
  });
9812
10315
  const postToParentRef = (0, import_react11.useRef)(postToParent2);
@@ -9834,6 +10337,7 @@ function OhhwellsBridge() {
9834
10337
  const [siblingHintRects, setSiblingHintRects] = (0, import_react11.useState)([]);
9835
10338
  const [isItemDragging, setIsItemDragging] = (0, import_react11.useState)(false);
9836
10339
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react11.useState)(false);
10340
+ isFooterFrameSelectionRef.current = isFooterFrameSelection;
9837
10341
  const footerDragRef = (0, import_react11.useRef)(null);
9838
10342
  const [footerDropSlots, setFooterDropSlots] = (0, import_react11.useState)([]);
9839
10343
  const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react11.useState)(null);
@@ -9845,6 +10349,7 @@ function OhhwellsBridge() {
9845
10349
  const linkPopoverSessionRef = (0, import_react11.useRef)(null);
9846
10350
  const addNavAfterAnchorRef = (0, import_react11.useRef)(null);
9847
10351
  const editContentRef = (0, import_react11.useRef)({});
10352
+ const pendingDeleteUndoRef = (0, import_react11.useRef)(null);
9848
10353
  const [sitePages, setSitePages] = (0, import_react11.useState)([]);
9849
10354
  const [sectionsByPath, setSectionsByPath] = (0, import_react11.useState)({});
9850
10355
  const sectionsPrefetchGenRef = (0, import_react11.useRef)(0);
@@ -9934,31 +10439,9 @@ function OhhwellsBridge() {
9934
10439
  hoveredGapRef.current = null;
9935
10440
  setSectionGap(null);
9936
10441
  setMediaHover(null);
9937
- postToParent2({ type: "ow:link-modal-lock", locked: true });
9938
- const html = document.documentElement;
9939
- const body = document.body;
9940
- const prevHtmlOverflow = html.style.overflow;
9941
- const prevBodyOverflow = body.style.overflow;
9942
- html.style.overflow = "hidden";
9943
- body.style.overflow = "hidden";
9944
- const preventBackgroundScroll = (e) => {
9945
- const target = e.target;
9946
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
9947
- return;
9948
- }
9949
- e.preventDefault();
9950
- };
9951
- const scrollOpts = { passive: false, capture: true };
9952
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
9953
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
9954
10442
  postToParent2({ type: "ow:image-unhover" });
9955
10443
  return () => {
9956
10444
  document.documentElement.removeAttribute("data-ohw-link-popover-open");
9957
- postToParent2({ type: "ow:link-modal-lock", locked: false });
9958
- html.style.overflow = prevHtmlOverflow;
9959
- body.style.overflow = prevBodyOverflow;
9960
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
9961
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
9962
10445
  };
9963
10446
  }, [linkPopover, postToParent2]);
9964
10447
  (0, import_react11.useEffect)(() => {
@@ -10122,7 +10605,7 @@ function OhhwellsBridge() {
10122
10605
  const hrefKey = selectedHrefKeyRef.current;
10123
10606
  if (hrefKey) {
10124
10607
  const link = resolveHrefKeyElement(hrefKey);
10125
- if (!link || !isNavigationItem(link)) return;
10608
+ if (!link || !isNavigationItem2(link)) return;
10126
10609
  selectedElRef.current = link;
10127
10610
  const { key, disabled } = getNavigationItemReorderState(link);
10128
10611
  setReorderHrefKey(key);
@@ -10315,7 +10798,7 @@ function OhhwellsBridge() {
10315
10798
  selectedHrefKeyRef.current = hrefKey;
10316
10799
  selectedFooterColAttrRef.current = null;
10317
10800
  const link = resolveHrefKeyElement(hrefKey);
10318
- if (link && isNavigationItem(link)) {
10801
+ if (link && isNavigationItem2(link)) {
10319
10802
  selectRef.current(link);
10320
10803
  return;
10321
10804
  }
@@ -10339,7 +10822,7 @@ function OhhwellsBridge() {
10339
10822
  }
10340
10823
  }
10341
10824
  if (document.body.contains(draggedEl)) {
10342
- if (isNavigationItem(draggedEl)) {
10825
+ if (isNavigationItem2(draggedEl)) {
10343
10826
  selectRef.current(draggedEl);
10344
10827
  return;
10345
10828
  }
@@ -10436,7 +10919,7 @@ function OhhwellsBridge() {
10436
10919
  const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
10437
10920
  const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
10438
10921
  if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
10439
- if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
10922
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
10440
10923
  if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
10441
10924
  }
10442
10925
  if (startNavLinkDrag(selected, clientX, clientY, true)) return;
@@ -10489,7 +10972,7 @@ function OhhwellsBridge() {
10489
10972
  };
10490
10973
  return;
10491
10974
  }
10492
- if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
10975
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
10493
10976
  footerPointerDragRef.current = {
10494
10977
  el: selected,
10495
10978
  kind: "column",
@@ -10509,7 +10992,7 @@ function OhhwellsBridge() {
10509
10992
  return;
10510
10993
  }
10511
10994
  const selected = selectedElRef.current;
10512
- if (!selected || !isNavigationItem(selected)) return;
10995
+ if (!selected || !isNavigationItem2(selected)) return;
10513
10996
  const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10514
10997
  if (!editable) return;
10515
10998
  activateRef.current(editable, { caretX: clientX, caretY: clientY });
@@ -10517,7 +11000,7 @@ function OhhwellsBridge() {
10517
11000
  reselectNavigationItemRef.current = reselectNavigationItem;
10518
11001
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
10519
11002
  const select = (0, import_react11.useCallback)((anchor) => {
10520
- if (!isNavigationItem(anchor)) return;
11003
+ if (!isNavigationItem2(anchor)) return;
10521
11004
  if (activeElRef.current) deactivate();
10522
11005
  selectedElRef.current = anchor;
10523
11006
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
@@ -10545,7 +11028,7 @@ function OhhwellsBridge() {
10545
11028
  const selectFrame = (0, import_react11.useCallback)((el) => {
10546
11029
  if (!isNavigationContainer(el)) return;
10547
11030
  if (activeElRef.current) deactivate();
10548
- const isFooterColumn = !isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el)));
11031
+ const isFooterColumn = !isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup2(el)));
10549
11032
  selectedElRef.current = el;
10550
11033
  selectedHrefKeyRef.current = null;
10551
11034
  selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
@@ -10660,7 +11143,7 @@ function OhhwellsBridge() {
10660
11143
  const next = `url('${val}')`;
10661
11144
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
10662
11145
  } else if (el.dataset.ohwEditable === "video") {
10663
- const video = getVideoEl(el);
11146
+ const video = getVideoEl2(el);
10664
11147
  if (video && video.src !== val) {
10665
11148
  applyVideoSrc(video, val);
10666
11149
  imageLoads.push(new Promise((resolve) => {
@@ -10728,7 +11211,7 @@ function OhhwellsBridge() {
10728
11211
  const next = `url('${val}')`;
10729
11212
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
10730
11213
  } else if (el.dataset.ohwEditable === "video") {
10731
- const video = getVideoEl(el);
11214
+ const video = getVideoEl2(el);
10732
11215
  if (video && video.src !== val) applyVideoSrc(video, val);
10733
11216
  } else if (el.dataset.ohwEditable === "link") {
10734
11217
  applyLinkHref(el, val);
@@ -11013,7 +11496,7 @@ function OhhwellsBridge() {
11013
11496
  e.preventDefault();
11014
11497
  e.stopPropagation();
11015
11498
  const selected = selectedElRef.current;
11016
- if (selected && isNavigationItem(selected)) {
11499
+ if (selected && isNavigationItem2(selected)) {
11017
11500
  const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
11018
11501
  if (editable2) {
11019
11502
  activateRef.current(editable2, {
@@ -11078,7 +11561,7 @@ function OhhwellsBridge() {
11078
11561
  setLinkPopoverRef.current({
11079
11562
  key: editable.dataset.ohwKey ?? "",
11080
11563
  mode: "edit",
11081
- target: getLinkHref3(editable)
11564
+ target: getLinkHref4(editable)
11082
11565
  });
11083
11566
  return;
11084
11567
  }
@@ -11224,7 +11707,7 @@ function OhhwellsBridge() {
11224
11707
  }
11225
11708
  {
11226
11709
  const selected2 = selectedElRef.current;
11227
- const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup(selected2)));
11710
+ const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
11228
11711
  const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
11229
11712
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
11230
11713
  if (allowNavContainerHover) {
@@ -11379,7 +11862,7 @@ function OhhwellsBridge() {
11379
11862
  const r2 = getVisibleRect(imgEl);
11380
11863
  const hasTextOverlap = forceTextOverlap ?? false;
11381
11864
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
11382
- const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
11865
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl2(imgEl) : null;
11383
11866
  setMediaHover({
11384
11867
  key: imgEl.dataset.ohwKey ?? "",
11385
11868
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
@@ -11446,7 +11929,7 @@ function OhhwellsBridge() {
11446
11929
  };
11447
11930
  const probeNavigationHoverAt = (x, y) => {
11448
11931
  const selected = selectedElRef.current;
11449
- const selectedIsFooterColumn = Boolean(selected) && !isFooterLinksContainer(selected) && (selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)));
11932
+ const selectedIsFooterColumn = Boolean(selected) && !isFooterLinksContainer(selected) && (selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") || Boolean(selected.closest("footer") && isInferredFooterGroup2(selected)));
11450
11933
  const selectedIsFooterLinks = Boolean(selected && isFooterLinksContainer(selected));
11451
11934
  const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
11452
11935
  const allowFooterLinksHover = (toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn) && !selectedIsFooterLinks;
@@ -11465,7 +11948,7 @@ function OhhwellsBridge() {
11465
11948
  const navItemHit = Array.from(
11466
11949
  container.querySelectorAll("[data-ohw-href-key]")
11467
11950
  ).find((el) => {
11468
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
11951
+ if (!isNavigationItem2(el) || isNavbarButton2(el)) return false;
11469
11952
  const itemRect = el.getBoundingClientRect();
11470
11953
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
11471
11954
  });
@@ -11923,7 +12406,7 @@ function OhhwellsBridge() {
11923
12406
  }
11924
12407
  }
11925
12408
  } else if (el.dataset.ohwEditable === "video") {
11926
- const video = getVideoEl(el);
12409
+ const video = getVideoEl2(el);
11927
12410
  if (video) {
11928
12411
  found = true;
11929
12412
  const onReady = () => {
@@ -12010,7 +12493,7 @@ function OhhwellsBridge() {
12010
12493
  } else if (el.dataset.ohwEditable === "bg-image") {
12011
12494
  el.style.backgroundImage = `url('${val}')`;
12012
12495
  } else if (el.dataset.ohwEditable === "video") {
12013
- const video = getVideoEl(el);
12496
+ const video = getVideoEl2(el);
12014
12497
  if (video && video.src !== val) applyVideoSrc(video, val);
12015
12498
  } else if (el.dataset.ohwEditable === "link") {
12016
12499
  applyLinkHref(el, val);
@@ -12045,6 +12528,14 @@ function OhhwellsBridge() {
12045
12528
  deactivateRef.current();
12046
12529
  };
12047
12530
  window.addEventListener("message", handleDeactivate);
12531
+ const handleToastAction = (e) => {
12532
+ if (e.data?.type !== "ow:toast-action") return;
12533
+ const actionId = typeof e.data.actionId === "string" ? e.data.actionId : null;
12534
+ const pending = pendingDeleteUndoRef.current;
12535
+ if (!pending || !actionId || pending.actionId !== actionId) return;
12536
+ runPendingDeleteUndoRef.current();
12537
+ };
12538
+ window.addEventListener("message", handleToastAction);
12048
12539
  const handleUiEscape = (e) => {
12049
12540
  if (e.data?.type !== "ui:escape") return;
12050
12541
  if (document.querySelector("[data-ohw-more-menu]")) return;
@@ -12161,6 +12652,18 @@ function OhhwellsBridge() {
12161
12652
  return;
12162
12653
  }
12163
12654
  }
12655
+ if ((e.key === "Delete" || e.key === "Backspace") && selectedElRef.current && !activeElRef.current && !linkPopoverOpenRef.current) {
12656
+ if (handleDeleteSelectedRef.current()) {
12657
+ e.preventDefault();
12658
+ return;
12659
+ }
12660
+ }
12661
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z" && !activeElRef.current) {
12662
+ if (runPendingDeleteUndoRef.current()) {
12663
+ e.preventDefault();
12664
+ return;
12665
+ }
12666
+ }
12164
12667
  if (e.key !== "Escape") return;
12165
12668
  const el = activeElRef.current;
12166
12669
  if (el && originalContentRef.current !== null) {
@@ -12197,11 +12700,11 @@ function OhhwellsBridge() {
12197
12700
  }
12198
12701
  const selected = selectedElRef.current;
12199
12702
  if (selected && !footerDragRef.current && !navDragRef.current) {
12200
- if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected))) {
12703
+ if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup2(selected))) {
12201
12704
  setSiblingHintRects(
12202
12705
  listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
12203
12706
  );
12204
- } else if (isNavigationItem(selected)) {
12707
+ } else if (isNavigationItem2(selected)) {
12205
12708
  setSiblingHintRects(collectNavigationItemSiblingHintRects(selected));
12206
12709
  }
12207
12710
  }
@@ -12563,6 +13066,7 @@ function OhhwellsBridge() {
12563
13066
  window.removeEventListener("message", handleClickAt);
12564
13067
  window.removeEventListener("message", handleHydrate);
12565
13068
  window.removeEventListener("message", handleDeactivate);
13069
+ window.removeEventListener("message", handleToastAction);
12566
13070
  window.removeEventListener("message", handleUiEscape);
12567
13071
  autoSaveTimers.current.forEach(clearTimeout);
12568
13072
  autoSaveTimers.current.clear();
@@ -12841,7 +13345,7 @@ function OhhwellsBridge() {
12841
13345
  setLinkPopover({
12842
13346
  key: hrefCtx.key,
12843
13347
  mode: "edit",
12844
- target: getLinkHref3(hrefCtx.anchor)
13348
+ target: getLinkHref4(hrefCtx.anchor)
12845
13349
  });
12846
13350
  deactivate();
12847
13351
  }, [deactivate]);
@@ -12854,7 +13358,7 @@ function OhhwellsBridge() {
12854
13358
  setLinkPopover({
12855
13359
  key,
12856
13360
  mode: "edit",
12857
- target: getLinkHref3(anchor)
13361
+ target: getLinkHref4(anchor)
12858
13362
  });
12859
13363
  deselect();
12860
13364
  }, [deselect]);
@@ -12862,6 +13366,11 @@ function OhhwellsBridge() {
12862
13366
  const selected = selectedElRef.current;
12863
13367
  if (!selected) return;
12864
13368
  if (toolbarVariantRef.current === "select-frame") {
13369
+ const parent2 = getNavigationSelectionParent(selected);
13370
+ if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selected)) {
13371
+ selectFrameRef.current(parent2);
13372
+ return;
13373
+ }
12865
13374
  deselectRef.current();
12866
13375
  return;
12867
13376
  }
@@ -12874,7 +13383,7 @@ function OhhwellsBridge() {
12874
13383
  }, []);
12875
13384
  const handleDuplicateSelected = (0, import_react11.useCallback)(() => {
12876
13385
  const selected = selectedElRef.current;
12877
- if (!selected || !isNavigationItem(selected)) return;
13386
+ if (!selected || !isNavigationItem2(selected)) return;
12878
13387
  const hrefKey = selected.getAttribute("data-ohw-href-key");
12879
13388
  if (!hrefKey) return;
12880
13389
  if (isNavbarHrefKey(hrefKey)) {
@@ -12955,6 +13464,35 @@ function OhhwellsBridge() {
12955
13464
  });
12956
13465
  }
12957
13466
  }, [postToParent2]);
13467
+ const runPendingDeleteUndo = (0, import_react11.useCallback)(() => {
13468
+ const pending = pendingDeleteUndoRef.current;
13469
+ if (!pending) return false;
13470
+ pendingDeleteUndoRef.current = null;
13471
+ pending.restore();
13472
+ enforceLinkHrefs();
13473
+ return true;
13474
+ }, []);
13475
+ const handleDeleteSelected = (0, import_react11.useCallback)(() => {
13476
+ const selected = selectedElRef.current;
13477
+ if (!selected) return false;
13478
+ return deleteSelectedNavFooterItem({
13479
+ selected,
13480
+ isTextEditing: Boolean(activeElRef.current),
13481
+ isSelectFrame: toolbarVariantRef.current === "select-frame",
13482
+ getEditContent: () => editContentRef.current,
13483
+ applyLinkByKey,
13484
+ postToParent: postToParent2,
13485
+ deselect: () => deselectRef.current(),
13486
+ setEditContent: (next) => {
13487
+ editContentRef.current = next;
13488
+ },
13489
+ setPendingUndo: (undo) => {
13490
+ pendingDeleteUndoRef.current = undo;
13491
+ }
13492
+ });
13493
+ }, [postToParent2]);
13494
+ handleDeleteSelectedRef.current = handleDeleteSelected;
13495
+ runPendingDeleteUndoRef.current = runPendingDeleteUndo;
12958
13496
  const handleLinkPopoverSubmit = (0, import_react11.useCallback)(
12959
13497
  (target) => {
12960
13498
  const session = linkPopoverSessionRef.current;
@@ -13037,7 +13575,7 @@ function OhhwellsBridge() {
13037
13575
  const handleVideoSettingsChange = (0, import_react11.useCallback)(
13038
13576
  (key, settings) => {
13039
13577
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
13040
- const video = getVideoEl(el);
13578
+ const video = getVideoEl2(el);
13041
13579
  if (!video) return;
13042
13580
  if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
13043
13581
  if (typeof settings.muted === "boolean") video.muted = settings.muted;
@@ -13127,7 +13665,7 @@ function OhhwellsBridge() {
13127
13665
  )),
13128
13666
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
13129
13667
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
13130
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
13668
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
13131
13669
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
13132
13670
  FooterContainerChrome,
13133
13671
  {
@@ -13150,17 +13688,20 @@ function OhhwellsBridge() {
13150
13688
  onItemPointerDown: handleItemChromePointerDown,
13151
13689
  onItemClick: handleItemChromeClick,
13152
13690
  itemDragSurface: !isFooterFrameSelection,
13153
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
13691
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && isFooterFrameSelection && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
13154
13692
  ItemActionToolbar,
13155
13693
  {
13156
13694
  onEditLink: openLinkPopoverForSelected,
13157
13695
  onSelectParent: handleSelectParent,
13158
13696
  onDuplicate: handleDuplicateSelected,
13697
+ onDelete: handleDeleteSelected,
13159
13698
  addItemDisabled: true,
13160
13699
  editLinkDisabled: false,
13161
13700
  moreDisabled: false,
13162
- showAddItem: !selectedIsCta,
13163
- showMore: !selectedIsCta
13701
+ duplicateDisabled: isFooterFrameSelection,
13702
+ showEditLink: !isFooterFrameSelection,
13703
+ showAddItem: !selectedIsCta && !isFooterFrameSelection,
13704
+ showMore: !selectedIsCta || isFooterFrameSelection
13164
13705
  }
13165
13706
  ) : void 0
13166
13707
  }