@ohhwells/bridge 0.1.42-next.98 → 0.1.42-next.99

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
@@ -6717,6 +6717,8 @@ function applyNavOrderToContainer(container, order) {
6717
6717
  if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6718
6718
  return;
6719
6719
  }
6720
+ const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
6721
+ const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
6720
6722
  const seen = /* @__PURE__ */ new Set();
6721
6723
  for (const hrefKey of desired) {
6722
6724
  if (seen.has(hrefKey)) continue;
@@ -6731,7 +6733,9 @@ function applyNavOrderToContainer(container, order) {
6731
6733
  orderedEls.push(el);
6732
6734
  }
6733
6735
  for (const el of orderedEls) {
6734
- if (container.lastElementChild !== el) {
6736
+ if (anchor) {
6737
+ if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
6738
+ } else if (container.lastElementChild !== el) {
6735
6739
  container.appendChild(el);
6736
6740
  }
6737
6741
  }
@@ -7073,6 +7077,11 @@ function reconcileFooterOrderFromContent(content) {
7073
7077
  }
7074
7078
  applyFooterOrder(order);
7075
7079
  }
7080
+ function isRowLayoutColumn(links) {
7081
+ if (links.length < 2) return false;
7082
+ const firstTop = links[0].getBoundingClientRect().top;
7083
+ return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
7084
+ }
7076
7085
  function buildLinkDropSlots(column, columnIndex) {
7077
7086
  const links = listFooterLinksInColumn(column);
7078
7087
  const colRect = column.getBoundingClientRect();
@@ -7090,6 +7099,32 @@ function buildLinkDropSlots(column, columnIndex) {
7090
7099
  });
7091
7100
  return slots;
7092
7101
  }
7102
+ if (isRowLayoutColumn(links)) {
7103
+ for (let i = 0; i <= links.length; i++) {
7104
+ let left;
7105
+ if (i === 0) {
7106
+ left = links[0].getBoundingClientRect().left - barThickness / 2;
7107
+ } else if (i === links.length) {
7108
+ const last = links[links.length - 1].getBoundingClientRect();
7109
+ left = last.right - barThickness / 2;
7110
+ } else {
7111
+ const prev = links[i - 1].getBoundingClientRect();
7112
+ const next = links[i].getBoundingClientRect();
7113
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7114
+ }
7115
+ const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7116
+ slots.push({
7117
+ insertIndex: i,
7118
+ columnIndex,
7119
+ left,
7120
+ top: heightRef.top,
7121
+ width: barThickness,
7122
+ height: Math.max(heightRef.height, colRect.height * 0.8),
7123
+ direction: "vertical"
7124
+ });
7125
+ }
7126
+ return slots;
7127
+ }
7093
7128
  for (let i = 0; i <= links.length; i++) {
7094
7129
  let top;
7095
7130
  if (i === 0) {
@@ -7173,8 +7208,7 @@ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7173
7208
  return true;
7174
7209
  });
7175
7210
  for (const slot of slots) {
7176
- const cy = slot.top + slot.height / 2;
7177
- const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
7211
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
7178
7212
  if (!best || dist < best.dist) best = { slot, dist };
7179
7213
  }
7180
7214
  }
@@ -7584,7 +7618,6 @@ function useNavItemDrag({
7584
7618
  const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7585
7619
  if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7586
7620
  if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7587
- armItemPressDrag();
7588
7621
  navPointerDragRef.current = {
7589
7622
  el: anchor,
7590
7623
  startX: e.clientX,
@@ -7611,6 +7644,7 @@ function useNavItemDrag({
7611
7644
  if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7612
7645
  e.preventDefault();
7613
7646
  pending.started = true;
7647
+ armItemPressDrag();
7614
7648
  clearTextSelection();
7615
7649
  try {
7616
7650
  document.body.setPointerCapture(pending.pointerId);
@@ -7638,7 +7672,8 @@ function useNavItemDrag({
7638
7672
  }
7639
7673
  } catch {
7640
7674
  }
7641
- if (!pending?.started) {
7675
+ if (!pending) return;
7676
+ if (!pending.started) {
7642
7677
  unlockItemDragInteraction();
7643
7678
  return;
7644
7679
  }
@@ -7690,7 +7725,6 @@ function useNavItemDrag({
7690
7725
  const hrefKey = selected.getAttribute("data-ohw-href-key");
7691
7726
  if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7692
7727
  if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7693
- armItemPressDrag();
7694
7728
  navPointerDragRef.current = {
7695
7729
  el: selected,
7696
7730
  startX: clientX,
@@ -8917,6 +8951,7 @@ function OhhwellsBridge() {
8917
8951
  const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
8918
8952
  const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
8919
8953
  const activeElRef = (0, import_react9.useRef)(null);
8954
+ const pointerHeldRef = (0, import_react9.useRef)(false);
8920
8955
  const selectedElRef = (0, import_react9.useRef)(null);
8921
8956
  const selectedHrefKeyRef = (0, import_react9.useRef)(null);
8922
8957
  const selectedFooterColAttrRef = (0, import_react9.useRef)(null);
@@ -9614,7 +9649,6 @@ function OhhwellsBridge() {
9614
9649
  if (e.button !== 0) return;
9615
9650
  const selected = selectedElRef.current;
9616
9651
  if (!selected) return;
9617
- armFooterPressDrag();
9618
9652
  const hrefKey = selected.getAttribute("data-ohw-href-key");
9619
9653
  if (hrefKey && isFooterHrefKey(hrefKey)) {
9620
9654
  footerPointerDragRef.current = {
@@ -9725,6 +9759,8 @@ function OhhwellsBridge() {
9725
9759
  siblingHintElRef.current = null;
9726
9760
  setSiblingHintRect(null);
9727
9761
  setIsItemDragging(false);
9762
+ const preActivationSelection = window.getSelection();
9763
+ const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
9728
9764
  el.setAttribute("contenteditable", "true");
9729
9765
  el.setAttribute("data-ohw-editing", "");
9730
9766
  el.removeAttribute("data-ohw-hovered");
@@ -9732,7 +9768,11 @@ function OhhwellsBridge() {
9732
9768
  activeElRef.current = el;
9733
9769
  originalContentRef.current = el.innerHTML;
9734
9770
  el.focus({ preventScroll: true });
9735
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
9771
+ if (preservedRange) {
9772
+ const selection = window.getSelection();
9773
+ selection?.removeAllRanges();
9774
+ selection?.addRange(preservedRange);
9775
+ } else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
9736
9776
  const { caretX, caretY } = options;
9737
9777
  placeCaretAtPoint(el, caretX, caretY);
9738
9778
  requestAnimationFrame(() => {
@@ -10188,7 +10228,18 @@ function OhhwellsBridge() {
10188
10228
  if (inActive || inActiveNav) {
10189
10229
  e.preventDefault();
10190
10230
  e.stopPropagation();
10191
- if (e.detail < 2) {
10231
+ const selection = window.getSelection();
10232
+ const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
10233
+ console.log(
10234
+ "[OHW DEBUG handleClick] already-active branch",
10235
+ "detail=",
10236
+ e.detail,
10237
+ "hasRangeSelection=",
10238
+ hasRangeSelection,
10239
+ "willPlaceCaret=",
10240
+ e.detail < 2 && !hasRangeSelection
10241
+ );
10242
+ if (e.detail < 2 && !hasRangeSelection) {
10192
10243
  placeCaretAtPoint(active, e.clientX, e.clientY);
10193
10244
  refreshActiveCommandsRef.current();
10194
10245
  }
@@ -10231,6 +10282,15 @@ function OhhwellsBridge() {
10231
10282
  }
10232
10283
  e.preventDefault();
10233
10284
  e.stopPropagation();
10285
+ console.log(
10286
+ "[OHW DEBUG handleClick] first-activation branch",
10287
+ "detail=",
10288
+ e.detail,
10289
+ "selectionAtClick=",
10290
+ JSON.stringify(window.getSelection()?.toString() ?? ""),
10291
+ "collapsedAtClick=",
10292
+ window.getSelection()?.isCollapsed
10293
+ );
10234
10294
  activateRef.current(editable);
10235
10295
  return;
10236
10296
  }
@@ -11341,30 +11401,83 @@ function OhhwellsBridge() {
11341
11401
  const h = document.documentElement.scrollHeight;
11342
11402
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
11343
11403
  };
11344
- const handleSelectionChange = () => {
11345
- if (!activeElRef.current) return;
11404
+ let selectionChangeRaf = null;
11405
+ const runSelectionChange = () => {
11406
+ selectionChangeRaf = null;
11407
+ const activeRoot = activeElRef.current;
11408
+ if (!activeRoot) return;
11346
11409
  const next = /* @__PURE__ */ new Set();
11347
- for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
11348
- try {
11349
- if (document.queryCommandState(cmd)) next.add(cmd);
11350
- } catch {
11351
- }
11352
- }
11353
11410
  const sel = window.getSelection();
11354
- const anchor = sel?.anchorNode;
11355
- if (anchor) {
11356
- const el = anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : anchor;
11357
- const block = el?.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? el;
11358
- if (block) {
11359
- const align = getComputedStyle(block).textAlign;
11360
- if (align === "center") next.add("justifyCenter");
11361
- else if (align === "right" || align === "end") next.add("justifyRight");
11362
- else next.add("justifyLeft");
11411
+ let startNode = null;
11412
+ if (sel && sel.rangeCount > 0) {
11413
+ const range = sel.getRangeAt(0);
11414
+ startNode = range.startContainer;
11415
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
11416
+ const el = startNode;
11417
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
11363
11418
  }
11364
11419
  }
11365
- setActiveCommands(next);
11420
+ const startEl = startNode ? startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode : null;
11421
+ if (startEl) {
11422
+ const cs = getComputedStyle(startEl);
11423
+ const weight = parseInt(cs.fontWeight, 10);
11424
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
11425
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
11426
+ let hasUnderline = false;
11427
+ let hasStrike = false;
11428
+ let hasUl = false;
11429
+ let hasOl = false;
11430
+ for (let el = startEl; el; el = el.parentElement) {
11431
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
11432
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
11433
+ const decoration = getComputedStyle(el).textDecorationLine;
11434
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
11435
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
11436
+ if (el.tagName === "UL") hasUl = true;
11437
+ if (el.tagName === "OL") hasOl = true;
11438
+ if (el === activeRoot) break;
11439
+ }
11440
+ if (hasBold) next.add("bold");
11441
+ if (hasItalic) next.add("italic");
11442
+ if (hasUnderline) next.add("underline");
11443
+ if (hasStrike) next.add("strikeThrough");
11444
+ if (hasUl) next.add("insertUnorderedList");
11445
+ if (hasOl) next.add("insertOrderedList");
11446
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
11447
+ const align = getComputedStyle(block).textAlign;
11448
+ if (align === "center") next.add("justifyCenter");
11449
+ else if (align === "right" || align === "end") next.add("justifyRight");
11450
+ else next.add("justifyLeft");
11451
+ }
11452
+ setActiveCommands((prev) => {
11453
+ if (prev.size === next.size) {
11454
+ let same = true;
11455
+ for (const cmd of prev) {
11456
+ if (!next.has(cmd)) {
11457
+ same = false;
11458
+ break;
11459
+ }
11460
+ }
11461
+ if (same) return prev;
11462
+ }
11463
+ return next;
11464
+ });
11465
+ };
11466
+ const handleSelectionChange = () => {
11467
+ if (pointerHeldRef.current) return;
11468
+ if (selectionChangeRaf !== null) return;
11469
+ selectionChangeRaf = requestAnimationFrame(runSelectionChange);
11470
+ };
11471
+ const markPointerHeld = (e) => {
11472
+ if (e.button !== 0) return;
11473
+ pointerHeldRef.current = true;
11474
+ };
11475
+ const markPointerReleased = () => {
11476
+ if (!pointerHeldRef.current) return;
11477
+ pointerHeldRef.current = false;
11478
+ handleSelectionChange();
11366
11479
  };
11367
- refreshActiveCommandsRef.current = handleSelectionChange;
11480
+ refreshActiveCommandsRef.current = runSelectionChange;
11368
11481
  const handleDocMouseLeave = () => {
11369
11482
  hoveredImageRef.current = null;
11370
11483
  setMediaHover(null);
@@ -11604,7 +11717,6 @@ function OhhwellsBridge() {
11604
11717
  const anchor = getNavigationItemAnchor(target);
11605
11718
  const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
11606
11719
  if (anchor && isFooterHrefKey(hrefKey)) {
11607
- armFooterPressDrag();
11608
11720
  footerPointerDragRef.current = {
11609
11721
  el: anchor,
11610
11722
  kind: "link",
@@ -11618,7 +11730,6 @@ function OhhwellsBridge() {
11618
11730
  }
11619
11731
  const col = target.closest("[data-ohw-footer-col]");
11620
11732
  if (col && !getNavigationItemAnchor(target)) {
11621
- armFooterPressDrag();
11622
11733
  footerPointerDragRef.current = {
11623
11734
  el: col,
11624
11735
  kind: "column",
@@ -11647,6 +11758,7 @@ function OhhwellsBridge() {
11647
11758
  if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
11648
11759
  e.preventDefault();
11649
11760
  pending.started = true;
11761
+ armFooterPressDrag();
11650
11762
  clearTextSelection();
11651
11763
  try {
11652
11764
  document.body.setPointerCapture(pending.pointerId);
@@ -11700,7 +11812,8 @@ function OhhwellsBridge() {
11700
11812
  }
11701
11813
  } catch {
11702
11814
  }
11703
- if (!pending?.started) {
11815
+ if (!pending) return;
11816
+ if (!pending.started) {
11704
11817
  unlockFooterDragInteraction();
11705
11818
  return;
11706
11819
  }
@@ -11735,6 +11848,22 @@ function OhhwellsBridge() {
11735
11848
  unlockFooterDragInteraction();
11736
11849
  };
11737
11850
  }, [isEditMode]);
11851
+ (0, import_react9.useEffect)(() => {
11852
+ if (!isEditMode) return;
11853
+ const debugSelectionChange = () => {
11854
+ const sel = window.getSelection();
11855
+ console.log(
11856
+ "[OHW DEBUG selectionchange]",
11857
+ JSON.stringify(sel?.toString() ?? ""),
11858
+ "collapsed=",
11859
+ sel?.isCollapsed,
11860
+ "anchorNode=",
11861
+ sel?.anchorNode,
11862
+ "activeEl=",
11863
+ activeElRef.current
11864
+ );
11865
+ };
11866
+ }, [isEditMode]);
11738
11867
  (0, import_react9.useEffect)(() => {
11739
11868
  const handler = (e) => {
11740
11869
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -11793,6 +11922,7 @@ function OhhwellsBridge() {
11793
11922
  return () => window.removeEventListener("hashchange", onHashChange);
11794
11923
  }, [pathname]);
11795
11924
  const handleCommand = (0, import_react9.useCallback)((cmd) => {
11925
+ document.execCommand("styleWithCSS", false, true);
11796
11926
  document.execCommand(cmd, false);
11797
11927
  activeElRef.current?.focus();
11798
11928
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());