@ohhwells/bridge 0.1.65-next.181 → 0.1.65-next.183

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
@@ -6907,7 +6907,7 @@ function showSuccess(form, message) {
6907
6907
  });
6908
6908
  return;
6909
6909
  }
6910
- const text = message ?? form.getAttribute("data-ohw-success-text") ?? DEFAULT_SUCCESS_TEXT;
6910
+ const text = message || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6911
6911
  const note = document.createElement("p");
6912
6912
  note.setAttribute("data-ohw-form-success", "");
6913
6913
  note.setAttribute("role", "status");
@@ -6949,6 +6949,9 @@ function ensureSuccessTextEl(form, formKey, initialText) {
6949
6949
  form.appendChild(el);
6950
6950
  return el;
6951
6951
  }
6952
+ function successInitialFor(form, formKey, content) {
6953
+ return content[formSuccessKey(formKey)] || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6954
+ }
6952
6955
  function setFormViewState(form, formKey, state, initialText) {
6953
6956
  const success = ensureSuccessTextEl(form, formKey, initialText);
6954
6957
  Array.from(form.children).forEach((child) => {
@@ -7015,6 +7018,7 @@ var FIELD_TYPES = [
7015
7018
  ];
7016
7019
  var FIELD_ATTR = "data-ohw-form-field";
7017
7020
  var FIELD_TYPE_ATTR = "data-ohw-field-type";
7021
+ var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
7018
7022
  function fieldsKey(formKey) {
7019
7023
  return `${formKey}-fields`;
7020
7024
  }
@@ -7052,7 +7056,7 @@ function ensureFieldLabel(wrapper, key) {
7052
7056
  label.style.lineHeight = "1.3";
7053
7057
  const fromPlaceholder = input?.getAttribute("placeholder")?.trim();
7054
7058
  const text = fromPlaceholder && fromPlaceholder.length < 40 ? fromPlaceholder : key.replace(/[-_]/g, " ");
7055
- label.textContent = input?.hasAttribute("required") ? `${text} *` : text;
7059
+ label.textContent = text;
7056
7060
  if (input && input.parentElement === wrapper) wrapper.insertBefore(label, input);
7057
7061
  else wrapper.insertBefore(label, wrapper.firstChild);
7058
7062
  return label;
@@ -7108,8 +7112,11 @@ function readFieldsFromDom(form) {
7108
7112
  return {
7109
7113
  key: fieldKeyOf(wrapper),
7110
7114
  type: fieldTypeOf(wrapper),
7111
- label: fieldLabelOf(wrapper)?.textContent?.trim() ?? "",
7112
- placeholder: input?.getAttribute("placeholder") ?? "",
7115
+ label: fieldLabelText(fieldLabelOf(wrapper)).trim(),
7116
+ // While a field is selected its placeholder text lives in the value (see
7117
+ // beginPlaceholderEdit), so reading the attribute would store an empty one — whoever
7118
+ // happens to save at that moment must still record what the owner typed.
7119
+ placeholder: input ? input.hasAttribute(PLACEHOLDER_EDIT_ATTR) ? input.value : input.getAttribute("placeholder") ?? "" : "",
7113
7120
  required: Boolean(input?.hasAttribute("required"))
7114
7121
  };
7115
7122
  });
@@ -7137,7 +7144,7 @@ function applyFieldType(wrapper, type) {
7137
7144
  const followsDefaults = {
7138
7145
  // A label the owner wrote stays; one that still reads as a type name (or as the old
7139
7146
  // placeholder the template shipped) follows the new type.
7140
- label: isDefaultText(label?.textContent ?? "", (d) => d.label) || (label?.textContent ?? "").trim().replace(/\s*\*$/, "") === placeholder.trim()
7147
+ label: isDefaultText(label?.textContent ?? "", (d) => d.label) || fieldLabelText(label).trim() === placeholder.trim()
7141
7148
  };
7142
7149
  const { tag, inputType } = inputTagFor(type);
7143
7150
  wrapper.setAttribute(FIELD_TYPE_ATTR, type);
@@ -7151,8 +7158,7 @@ function applyFieldType(wrapper, type) {
7151
7158
  const applyDefaults = (el) => {
7152
7159
  el.setAttribute("placeholder", defaults.placeholder);
7153
7160
  if (label && followsDefaults.label) {
7154
- const required = (label.textContent ?? "").trim().endsWith("*");
7155
- label.textContent = required ? `${defaults.label} *` : defaults.label;
7161
+ label.textContent = defaults.label;
7156
7162
  }
7157
7163
  };
7158
7164
  if (input.tagName.toLowerCase() === tag) {
@@ -7172,13 +7178,19 @@ function applyFieldType(wrapper, type) {
7172
7178
  input.replaceWith(next);
7173
7179
  applyDefaults(next);
7174
7180
  }
7181
+ var REQUIRED_MARK_ATTR = "data-ohw-field-required";
7182
+ function fieldLabelText(label) {
7183
+ if (!label) return "";
7184
+ return (label.textContent ?? "").replace(/\s*\*\s*$/, "").trimEnd();
7185
+ }
7175
7186
  function syncRequiredMark(wrapper) {
7176
7187
  const label = fieldLabelOf(wrapper);
7177
7188
  if (!label) return;
7178
- const required = isFieldRequired(wrapper);
7179
- const base = (label.textContent ?? "").replace(/\s*\*\s*$/, "").trimEnd();
7180
- const next = required ? `${base} *` : base;
7181
- if (label.textContent !== next) label.textContent = next;
7189
+ label.querySelectorAll("[data-ohw-required-mark]").forEach((el) => el.remove());
7190
+ const words = fieldLabelText(label);
7191
+ if ((label.textContent ?? "") !== words) label.textContent = words;
7192
+ if (isFieldRequired(wrapper)) label.setAttribute(REQUIRED_MARK_ATTR, "");
7193
+ else label.removeAttribute(REQUIRED_MARK_ATTR);
7182
7194
  }
7183
7195
  function setFieldRequired(wrapper, required) {
7184
7196
  const input = fieldInputOf(wrapper);
@@ -7188,7 +7200,6 @@ function setFieldRequired(wrapper, required) {
7188
7200
  else input.removeAttribute("required");
7189
7201
  if (label) syncRequiredMark(wrapper);
7190
7202
  }
7191
- var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
7192
7203
  function beginPlaceholderEdit(wrapper) {
7193
7204
  const input = fieldInputOf(wrapper);
7194
7205
  if (!input || input.hasAttribute(PLACEHOLDER_EDIT_ATTR)) return;
@@ -7267,14 +7278,25 @@ function duplicateField(form, wrapper) {
7267
7278
  function removeField(wrapper) {
7268
7279
  wrapper.remove();
7269
7280
  }
7270
- function moveField(form, key, toIndex) {
7281
+ function applyFieldOrder(form, keys) {
7271
7282
  const wrappers = listFieldWrappers(form);
7272
- const moving = wrappers.find((wrapper) => fieldKeyOf(wrapper) === key);
7273
- if (!moving) return;
7274
- const rest = wrappers.filter((wrapper) => wrapper !== moving);
7275
- const target = rest[Math.max(0, Math.min(rest.length, toIndex))];
7276
- if (target) target.before(moving);
7277
- else rest[rest.length - 1]?.after(moving);
7283
+ const byKey = new Map(wrappers.map((wrapper) => [fieldKeyOf(wrapper), wrapper]));
7284
+ const ordered = keys.map((key) => byKey.get(key)).filter((w) => Boolean(w));
7285
+ if (ordered.length !== wrappers.length) return;
7286
+ const slots = wrappers.map((wrapper) => {
7287
+ const slot = document.createComment("ohw-field-slot");
7288
+ wrapper.replaceWith(slot);
7289
+ return slot;
7290
+ });
7291
+ ordered.forEach((wrapper, index) => slots[index].replaceWith(wrapper));
7292
+ }
7293
+ function moveField(form, key, toIndex) {
7294
+ const keys = listFieldWrappers(form).map(fieldKeyOf);
7295
+ const from = keys.indexOf(key);
7296
+ if (from === -1) return;
7297
+ const rest = keys.filter((k) => k !== key);
7298
+ rest.splice(Math.max(0, Math.min(rest.length, toIndex)), 0, key);
7299
+ applyFieldOrder(form, rest);
7278
7300
  }
7279
7301
  function reconcileFieldsFromContent(form, content) {
7280
7302
  const formKey = form.getAttribute("data-ohw-key");
@@ -7300,13 +7322,18 @@ function reconcileFieldsFromContent(form, content) {
7300
7322
  setFieldRequired(wrapper, spec.required);
7301
7323
  setFieldPlaceholder(wrapper, spec.placeholder);
7302
7324
  const label = fieldLabelOf(wrapper);
7303
- if (label && spec.label) label.textContent = spec.label;
7325
+ if (label && spec.label) label.textContent = spec.label.replace(/\s*\*\s*$/, "").trimEnd();
7326
+ syncRequiredMark(wrapper);
7304
7327
  });
7305
7328
  const wanted = new Set(stored.map((spec) => spec.key));
7306
7329
  listFieldWrappers(form).forEach((wrapper) => {
7307
7330
  if (!wanted.has(fieldKeyOf(wrapper))) wrapper.remove();
7308
7331
  });
7309
- stored.forEach((spec, index) => moveField(form, spec.key, index));
7332
+ const currentOrder = listFieldWrappers(form).map(fieldKeyOf).join("\0");
7333
+ const storedOrder = stored.map((spec) => spec.key).join("\0");
7334
+ if (currentOrder !== storedOrder) {
7335
+ applyFieldOrder(form, stored.map((spec) => spec.key));
7336
+ }
7310
7337
  }
7311
7338
 
7312
7339
  // src/ui/form-field-toolbar.tsx
@@ -14129,6 +14156,10 @@ function isIconEditable(el) {
14129
14156
  return el.dataset.ohwEditable === "icon";
14130
14157
  }
14131
14158
  var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
14159
+ 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"]';
14160
+ function isOverEditorChrome(x, y) {
14161
+ return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
14162
+ }
14132
14163
  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"])';
14133
14164
  function getVideoEl2(el) {
14134
14165
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
@@ -15167,9 +15198,14 @@ function OhhwellsBridge() {
15167
15198
  const [fieldTypePickerOpen, setFieldTypePickerOpen] = (0, import_react16.useState)(false);
15168
15199
  const clearFormPick = (0, import_react16.useCallback)(() => {
15169
15200
  const form = formPickElRef.current;
15201
+ const editing = fieldPickElRef.current;
15202
+ if (commitPlaceholderEdit(editing) && editing) {
15203
+ const owner = editing.closest('[data-ohw-editable="form"]');
15204
+ if (owner) persistFieldsRef.current(owner);
15205
+ }
15170
15206
  if (form) {
15171
15207
  const key = formKeyOf(form);
15172
- if (key) setFormViewState(form, key, "default", DEFAULT_SUCCESS_TEXT);
15208
+ if (key) setFormViewState(form, key, "default", successInitialFor(form, key, editContentRef.current));
15173
15209
  }
15174
15210
  setFormViewStateUi("default");
15175
15211
  setFormPickCount(null);
@@ -15223,8 +15259,8 @@ function OhhwellsBridge() {
15223
15259
  if (!wrapper || !form) return;
15224
15260
  commitPlaceholderEdit(wrapper);
15225
15261
  run(form, wrapper);
15226
- beginPlaceholderEdit(wrapper);
15227
15262
  persistFields(form);
15263
+ beginPlaceholderEdit(wrapper);
15228
15264
  setFormPickRect(form.getBoundingClientRect());
15229
15265
  },
15230
15266
  [persistFields]
@@ -15563,7 +15599,9 @@ function OhhwellsBridge() {
15563
15599
  const deactivate = (0, import_react16.useCallback)(() => {
15564
15600
  const el = activeElRef.current;
15565
15601
  if (!el) return;
15566
- const key = el.dataset.ohwKey;
15602
+ const isFormBlock = el.dataset.ohwEditable === "form";
15603
+ const isFormControl = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;
15604
+ const key = isFormBlock || isFormControl ? void 0 : el.dataset.ohwKey;
15567
15605
  if (key) {
15568
15606
  const timer = autoSaveTimers.current.get(key);
15569
15607
  if (timer !== void 0) {
@@ -15580,6 +15618,12 @@ function OhhwellsBridge() {
15580
15618
  }
15581
15619
  el.removeAttribute("contenteditable");
15582
15620
  el.removeAttribute("data-ohw-editing");
15621
+ const labelField = getFieldWrapper(el);
15622
+ if (labelField) {
15623
+ syncRequiredMark(labelField);
15624
+ const owner = labelField.closest('[data-ohw-editable="form"]');
15625
+ if (owner) persistFieldsRef.current(owner);
15626
+ }
15583
15627
  activeElRef.current = null;
15584
15628
  setNavGroupForceOpen(null, false);
15585
15629
  setReorderHrefKey(null);
@@ -15735,6 +15779,12 @@ function OhhwellsBridge() {
15735
15779
  }
15736
15780
  el.removeAttribute("contenteditable");
15737
15781
  el.removeAttribute("data-ohw-editing");
15782
+ const labelField = getFieldWrapper(el);
15783
+ if (labelField) {
15784
+ syncRequiredMark(labelField);
15785
+ const owner = labelField.closest('[data-ohw-editable="form"]');
15786
+ if (owner) persistFieldsRef.current(owner);
15787
+ }
15738
15788
  activeElRef.current = null;
15739
15789
  setMaxBadge(null);
15740
15790
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -16672,6 +16722,7 @@ function OhhwellsBridge() {
16672
16722
  applyLinkHref(el, val);
16673
16723
  } else if (el.dataset.ohwEditable === "icon") {
16674
16724
  applyIconMarkup(el, val);
16725
+ } else if (el.dataset.ohwEditable === "form") {
16675
16726
  } else if (el.innerHTML !== val) {
16676
16727
  el.innerHTML = val;
16677
16728
  }
@@ -16774,8 +16825,9 @@ function OhhwellsBridge() {
16774
16825
  const wrapper = getFieldWrapper(input);
16775
16826
  if (!wrapper || wrapper !== fieldPickElRef.current) return;
16776
16827
  if (saveTimer) clearTimeout(saveTimer);
16828
+ const owner = wrapper.closest('[data-ohw-editable="form"]');
16777
16829
  saveTimer = setTimeout(() => {
16778
- const form = formPickElRef.current;
16830
+ const form = owner ?? formPickElRef.current;
16779
16831
  if (!form) return;
16780
16832
  const previous = input.getAttribute("placeholder");
16781
16833
  input.setAttribute("placeholder", input.value);
@@ -16847,6 +16899,7 @@ function OhhwellsBridge() {
16847
16899
  if (video && video.src !== val) applyVideoSrc(video, val);
16848
16900
  } else if (el.dataset.ohwEditable === "link") {
16849
16901
  applyLinkHref(el, val);
16902
+ } else if (el.dataset.ohwEditable === "form") {
16850
16903
  } else if (el.innerHTML !== val) {
16851
16904
  el.innerHTML = val;
16852
16905
  }
@@ -16858,6 +16911,9 @@ function OhhwellsBridge() {
16858
16911
  reconcileFooterOrderFromContent(content);
16859
16912
  reconcileSocialsFromContent(content);
16860
16913
  applySocialsDisplayFromContent(content);
16914
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
16915
+ listFieldWrappers(form).forEach(syncRequiredMark);
16916
+ });
16861
16917
  } finally {
16862
16918
  observer?.observe(document.body, { childList: true, subtree: true });
16863
16919
  }
@@ -17489,6 +17545,19 @@ function OhhwellsBridge() {
17489
17545
  setHoveredNavContainerRect(null);
17490
17546
  return;
17491
17547
  }
17548
+ if (target.closest(EDITOR_CHROME_SELECTOR) || isOverEditorChrome(e.clientX, e.clientY) || isInsideLinkEditor(target)) {
17549
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
17550
+ hoveredItemElRef.current = null;
17551
+ setHoveredItemRect(null);
17552
+ hoveredNavContainerRef.current = null;
17553
+ setHoveredNavContainerRect(null);
17554
+ formHoverElRef.current = null;
17555
+ setFormHoverRect(null);
17556
+ siblingHintElRef.current = null;
17557
+ setSiblingHintRect(null);
17558
+ setSiblingHintRects([]);
17559
+ return;
17560
+ }
17492
17561
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
17493
17562
  hoveredItemElRef.current = null;
17494
17563
  setHoveredItemRect(null);
@@ -18230,7 +18299,10 @@ function OhhwellsBridge() {
18230
18299
  };
18231
18300
  const handleMouseMove = (e) => {
18232
18301
  const { clientX, clientY } = e;
18233
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
18302
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
18303
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
18304
+ formHoverElRef.current = null;
18305
+ setFormHoverRect(null);
18234
18306
  hoveredItemElRef.current = null;
18235
18307
  setHoveredItemRect(null);
18236
18308
  hoveredNavContainerRef.current = null;
@@ -18969,6 +19041,13 @@ function OhhwellsBridge() {
18969
19041
  if (brandKitRef.current) {
18970
19042
  nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
18971
19043
  }
19044
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
19045
+ const formKey = formKeyOf(form);
19046
+ if (!formKey) return;
19047
+ const specs = readFieldsFromDom(form);
19048
+ const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
19049
+ if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
19050
+ });
18972
19051
  postToParentRef.current({ type: "ow:save-result", nodes });
18973
19052
  };
18974
19053
  const handleInsertSection = (e) => {
@@ -20105,28 +20184,6 @@ function OhhwellsBridge() {
20105
20184
  }
20106
20185
  ),
20107
20186
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20108
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
20109
- "button",
20110
- {
20111
- type: "button",
20112
- 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",
20113
- onClick: () => {
20114
- const form = formPickElRef.current;
20115
- if (!form) return;
20116
- postToParent2({
20117
- type: "ow:form-submissions",
20118
- formKey: formKeyOf(form),
20119
- hasLongText: formHasLongText(form)
20120
- });
20121
- },
20122
- "data-ohw-view-submissions": "",
20123
- children: [
20124
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.Table, { size: 14, "aria-hidden": true }),
20125
- "View submissions"
20126
- ]
20127
- }
20128
- ),
20129
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "mx-0.5 h-5 w-px bg-border" }),
20130
20187
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
20131
20188
  "button",
20132
20189
  {
@@ -20137,7 +20194,7 @@ function OhhwellsBridge() {
20137
20194
  const form = formPickElRef.current;
20138
20195
  const key = form ? formKeyOf(form) : null;
20139
20196
  if (!form || !key) return;
20140
- const initial = editContentRef.current[formSuccessKey(key)] ?? DEFAULT_SUCCESS_TEXT;
20197
+ const initial = successInitialFor(form, key, editContentRef.current);
20141
20198
  setFormViewState(form, key, state, initial);
20142
20199
  setFormViewStateUi(state);
20143
20200
  setFormPickRect(form.getBoundingClientRect());
@@ -20165,7 +20222,7 @@ function OhhwellsBridge() {
20165
20222
  chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
20166
20223
  }
20167
20224
  ),
20168
- fieldPickRect && fieldPickState && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
20225
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
20169
20226
  ItemInteractionLayer,
20170
20227
  {
20171
20228
  rect: fieldPickRect,