@ohhwells/bridge 0.1.42-next.100 → 0.1.42-next.102

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
@@ -7252,9 +7252,10 @@ function lockItemDuringDrag() {
7252
7252
  clearTextSelection();
7253
7253
  }
7254
7254
  function unlockItemDragInteraction() {
7255
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
7255
7256
  document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7256
7257
  document.documentElement.removeAttribute("data-ohw-item-dragging");
7257
- clearTextSelection();
7258
+ if (wasDragging) clearTextSelection();
7258
7259
  }
7259
7260
  var armFooterPressDrag = armItemPressDrag;
7260
7261
  var lockFooterDuringDrag = lockItemDuringDrag;
@@ -8569,9 +8570,16 @@ function sanitizeHtml(html) {
8569
8570
  if (!SAFE_TAGS.has(el.tagName)) {
8570
8571
  parent.replaceChild(document.createTextNode(el.textContent ?? ""), el);
8571
8572
  } else {
8572
- const textAlign = el.style?.textAlign || el.getAttribute("align") || "";
8573
+ const htmlEl = el;
8574
+ const textAlign = htmlEl.style?.textAlign || el.getAttribute("align") || "";
8575
+ const fontWeight = htmlEl.style?.fontWeight || "";
8576
+ const fontStyle = htmlEl.style?.fontStyle || "";
8577
+ const textDecorationLine = htmlEl.style?.textDecorationLine || htmlEl.style?.textDecoration || "";
8573
8578
  for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);
8574
- if (textAlign) el.style.textAlign = textAlign;
8579
+ if (textAlign) htmlEl.style.textAlign = textAlign;
8580
+ if (fontWeight) htmlEl.style.fontWeight = fontWeight;
8581
+ if (fontStyle) htmlEl.style.fontStyle = fontStyle;
8582
+ if (textDecorationLine) htmlEl.style.textDecorationLine = textDecorationLine;
8575
8583
  walk(el);
8576
8584
  }
8577
8585
  }
@@ -8759,6 +8767,79 @@ function resolveItemInteractionState(rect, parentScroll) {
8759
8767
  const { transform } = calcToolbarPos(rect, parentScroll, 120);
8760
8768
  return transform.includes("translateY(-100%)") ? "active-top" : "active-bottom";
8761
8769
  }
8770
+ function resolveSelectionStartElement(sel) {
8771
+ if (!sel || sel.rangeCount === 0) return null;
8772
+ const range = sel.getRangeAt(0);
8773
+ let startNode = range.startContainer;
8774
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
8775
+ const el = startNode;
8776
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
8777
+ }
8778
+ return startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;
8779
+ }
8780
+ function detectActiveFormats(startEl, activeRoot) {
8781
+ const formats = /* @__PURE__ */ new Set();
8782
+ const cs = getComputedStyle(startEl);
8783
+ const weight = parseInt(cs.fontWeight, 10);
8784
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
8785
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
8786
+ let hasUnderline = false;
8787
+ let hasStrike = false;
8788
+ let hasUl = false;
8789
+ let hasOl = false;
8790
+ for (let el = startEl; el; el = el.parentElement) {
8791
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
8792
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
8793
+ const decoration = getComputedStyle(el).textDecorationLine;
8794
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
8795
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
8796
+ if (el.tagName === "UL") hasUl = true;
8797
+ if (el.tagName === "OL") hasOl = true;
8798
+ if (el === activeRoot) break;
8799
+ }
8800
+ if (hasBold) formats.add("bold");
8801
+ if (hasItalic) formats.add("italic");
8802
+ if (hasUnderline) formats.add("underline");
8803
+ if (hasStrike) formats.add("strikeThrough");
8804
+ if (hasUl) formats.add("insertUnorderedList");
8805
+ if (hasOl) formats.add("insertOrderedList");
8806
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
8807
+ const align = getComputedStyle(block).textAlign;
8808
+ if (align === "center") formats.add("justifyCenter");
8809
+ else if (align === "right" || align === "end") formats.add("justifyRight");
8810
+ else formats.add("justifyLeft");
8811
+ return formats;
8812
+ }
8813
+ function setsEqual(a, b) {
8814
+ if (a.size !== b.size) return false;
8815
+ for (const item of a) {
8816
+ if (!b.has(item)) return false;
8817
+ }
8818
+ return true;
8819
+ }
8820
+ function textOffsetInRoot(root, node, offset) {
8821
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
8822
+ let total = 0;
8823
+ let current;
8824
+ while (current = walker.nextNode()) {
8825
+ if (current === node) return total + offset;
8826
+ total += (current.textContent ?? "").length;
8827
+ }
8828
+ return total;
8829
+ }
8830
+ function pointAtTextOffset(root, targetOffset) {
8831
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
8832
+ let total = 0;
8833
+ let current;
8834
+ let last = null;
8835
+ while (current = walker.nextNode()) {
8836
+ const len = (current.textContent ?? "").length;
8837
+ if (total + len >= targetOffset) return { node: current, offset: targetOffset - total };
8838
+ total += len;
8839
+ last = current;
8840
+ }
8841
+ return last ? { node: last, offset: (last.textContent ?? "").length } : null;
8842
+ }
8762
8843
  function FloatingToolbar({
8763
8844
  rect,
8764
8845
  parentScroll,
@@ -10230,15 +10311,6 @@ function OhhwellsBridge() {
10230
10311
  e.stopPropagation();
10231
10312
  const selection = window.getSelection();
10232
10313
  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
10314
  if (e.detail < 2 && !hasRangeSelection) {
10243
10315
  placeCaretAtPoint(active, e.clientX, e.clientY);
10244
10316
  refreshActiveCommandsRef.current();
@@ -10282,15 +10354,6 @@ function OhhwellsBridge() {
10282
10354
  }
10283
10355
  e.preventDefault();
10284
10356
  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
- );
10294
10357
  activateRef.current(editable);
10295
10358
  return;
10296
10359
  }
@@ -11406,62 +11469,9 @@ function OhhwellsBridge() {
11406
11469
  selectionChangeRaf = null;
11407
11470
  const activeRoot = activeElRef.current;
11408
11471
  if (!activeRoot) return;
11409
- const next = /* @__PURE__ */ new Set();
11410
- const sel = window.getSelection();
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;
11418
- }
11419
- }
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
- });
11472
+ const startEl = resolveSelectionStartElement(window.getSelection());
11473
+ const next = startEl ? detectActiveFormats(startEl, activeRoot) : /* @__PURE__ */ new Set();
11474
+ setActiveCommands((prev) => setsEqual(prev, next) ? prev : next);
11465
11475
  };
11466
11476
  const handleSelectionChange = () => {
11467
11477
  if (pointerHeldRef.current) return;
@@ -11848,22 +11858,6 @@ function OhhwellsBridge() {
11848
11858
  unlockFooterDragInteraction();
11849
11859
  };
11850
11860
  }, [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]);
11867
11861
  (0, import_react9.useEffect)(() => {
11868
11862
  const handler = (e) => {
11869
11863
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -11922,10 +11916,39 @@ function OhhwellsBridge() {
11922
11916
  return () => window.removeEventListener("hashchange", onHashChange);
11923
11917
  }, [pathname]);
11924
11918
  const handleCommand = (0, import_react9.useCallback)((cmd) => {
11919
+ const el = activeElRef.current;
11920
+ const selBefore = window.getSelection();
11921
+ let savedOffsets = null;
11922
+ if (el && selBefore && selBefore.rangeCount > 0 && !selBefore.isCollapsed) {
11923
+ const r2 = selBefore.getRangeAt(0);
11924
+ if (el.contains(r2.commonAncestorContainer)) {
11925
+ savedOffsets = {
11926
+ start: textOffsetInRoot(el, r2.startContainer, r2.startOffset),
11927
+ end: textOffsetInRoot(el, r2.endContainer, r2.endOffset)
11928
+ };
11929
+ }
11930
+ }
11925
11931
  document.execCommand("styleWithCSS", false, true);
11926
11932
  document.execCommand(cmd, false);
11927
- activeElRef.current?.focus();
11928
- if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
11933
+ if (el && document.activeElement !== el) {
11934
+ el.focus();
11935
+ }
11936
+ const selAfter = window.getSelection();
11937
+ if (el && savedOffsets && selAfter?.isCollapsed) {
11938
+ const start = pointAtTextOffset(el, savedOffsets.start);
11939
+ const end = pointAtTextOffset(el, savedOffsets.end);
11940
+ if (start && end) {
11941
+ try {
11942
+ const range = document.createRange();
11943
+ range.setStart(start.node, start.offset);
11944
+ range.setEnd(end.node, end.offset);
11945
+ selAfter.removeAllRanges();
11946
+ selAfter.addRange(range);
11947
+ } catch {
11948
+ }
11949
+ }
11950
+ }
11951
+ if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
11929
11952
  refreshActiveCommandsRef.current();
11930
11953
  }, []);
11931
11954
  const handleStateChange = (0, import_react9.useCallback)((state) => {