@trafica/editor 1.0.19 → 1.0.21

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.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var reactDom = require('react-dom');
5
6
 
6
7
  // src/editor/core/DocumentModel.ts
7
8
  function isTextNode(node) {
@@ -5532,9 +5533,11 @@ function SpecialCharactersButton({
5532
5533
  null
5533
5534
  );
5534
5535
  const [focusedIdx, setFocusedIdx] = react.useState(-1);
5535
- const containerRef = react.useRef(null);
5536
+ const buttonRef = react.useRef(null);
5537
+ const popupRef = react.useRef(null);
5536
5538
  const searchRef = react.useRef(null);
5537
5539
  const gridRef = react.useRef(null);
5540
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
5538
5541
  const filtered = react.useMemo(() => {
5539
5542
  const q = search.toLowerCase();
5540
5543
  return SPECIAL_CHARACTERS.filter(
@@ -5542,6 +5545,12 @@ function SpecialCharactersButton({
5542
5545
  );
5543
5546
  }, [category, search]);
5544
5547
  const openModal = () => {
5548
+ if (buttonRef.current) {
5549
+ const rect = buttonRef.current.getBoundingClientRect();
5550
+ const popupWidth = 340;
5551
+ const left = Math.min(rect.left, window.innerWidth - popupWidth - 8);
5552
+ setPopupPos({ top: rect.bottom + 4, left: Math.max(8, left) });
5553
+ }
5545
5554
  setOpen(true);
5546
5555
  setSearch("");
5547
5556
  setFocusedIdx(-1);
@@ -5554,9 +5563,11 @@ function SpecialCharactersButton({
5554
5563
  react.useEffect(() => {
5555
5564
  if (!open) return;
5556
5565
  const handler = (e) => {
5557
- if (containerRef.current && !containerRef.current.contains(e.target)) {
5558
- closeModal();
5559
- }
5566
+ var _a, _b;
5567
+ const target = e.target;
5568
+ const insidePopup = (_a = popupRef.current) == null ? void 0 : _a.contains(target);
5569
+ const insideButton = (_b = buttonRef.current) == null ? void 0 : _b.contains(target);
5570
+ if (!insidePopup && !insideButton) closeModal();
5560
5571
  };
5561
5572
  document.addEventListener("mousedown", handler);
5562
5573
  return () => document.removeEventListener("mousedown", handler);
@@ -5604,10 +5615,11 @@ function SpecialCharactersButton({
5604
5615
  const cell = gridRef.current.children[focusedIdx];
5605
5616
  cell == null ? void 0 : cell.focus();
5606
5617
  }, [focusedIdx]);
5607
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
5618
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5608
5619
  /* @__PURE__ */ jsxRuntime.jsx(
5609
5620
  "button",
5610
5621
  {
5622
+ ref: buttonRef,
5611
5623
  type: "button",
5612
5624
  title: "Special Characters",
5613
5625
  "aria-label": "Insert special character",
@@ -5615,7 +5627,11 @@ function SpecialCharactersButton({
5615
5627
  "aria-expanded": open,
5616
5628
  onMouseDown: (e) => {
5617
5629
  e.preventDefault();
5618
- open ? closeModal() : openModal();
5630
+ if (open) {
5631
+ closeModal();
5632
+ } else {
5633
+ openModal();
5634
+ }
5619
5635
  },
5620
5636
  className: [
5621
5637
  "flex items-center justify-center w-8 h-8 rounded text-sm font-medium transition-colors",
@@ -5624,127 +5640,131 @@ function SpecialCharactersButton({
5624
5640
  children: /* @__PURE__ */ jsxRuntime.jsx(OmegaIcon, {})
5625
5641
  }
5626
5642
  ),
5627
- open && /* @__PURE__ */ jsxRuntime.jsxs(
5628
- "div",
5629
- {
5630
- role: "dialog",
5631
- "aria-label": "Special characters",
5632
- "aria-modal": "true",
5633
- className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-2xl flex flex-col",
5634
- style: { width: 340, maxHeight: 420 },
5635
- onKeyDown: (e) => {
5636
- if (e.key === "Escape") closeModal();
5637
- },
5638
- children: [
5639
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pt-3 pb-2 border-b border-gray-100 ", children: [
5640
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
5641
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide", children: "Special Characters" }),
5642
- /* @__PURE__ */ jsxRuntime.jsx(
5643
- "button",
5644
- {
5645
- type: "button",
5646
- onClick: closeModal,
5647
- "aria-label": "Close",
5648
- className: "text-gray-400 hover:text-gray-600 p-0.5 rounded",
5649
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
5650
- }
5651
- )
5652
- ] }),
5653
- /* @__PURE__ */ jsxRuntime.jsx(
5654
- "input",
5655
- {
5656
- ref: searchRef,
5657
- type: "search",
5658
- placeholder: "Search characters\u2026",
5659
- value: search,
5660
- onChange: (e) => {
5661
- setSearch(e.target.value);
5662
- setFocusedIdx(-1);
5663
- },
5664
- onKeyDown: (e) => {
5665
- var _a, _b;
5666
- if (e.key === "Escape") closeModal();
5667
- if (e.key === "ArrowDown") {
5668
- e.preventDefault();
5669
- setFocusedIdx(0);
5670
- (_b = (_a = gridRef.current) == null ? void 0 : _a.children[0]) == null ? void 0 : _b.dispatchEvent(
5671
- new Event("focus")
5672
- );
5643
+ open && reactDom.createPortal(
5644
+ /* @__PURE__ */ jsxRuntime.jsxs(
5645
+ "div",
5646
+ {
5647
+ ref: popupRef,
5648
+ role: "dialog",
5649
+ "aria-label": "Special characters",
5650
+ "aria-modal": "true",
5651
+ className: "bg-white border border-gray-200 rounded-lg shadow-2xl flex flex-col",
5652
+ style: { position: "fixed", top: popupPos.top, left: popupPos.left, width: 340, maxHeight: 420, zIndex: 9999 },
5653
+ onKeyDown: (e) => {
5654
+ if (e.key === "Escape") closeModal();
5655
+ },
5656
+ children: [
5657
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pt-3 pb-2 border-b border-gray-100 ", children: [
5658
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
5659
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide", children: "Special Characters" }),
5660
+ /* @__PURE__ */ jsxRuntime.jsx(
5661
+ "button",
5662
+ {
5663
+ type: "button",
5664
+ onClick: closeModal,
5665
+ "aria-label": "Close",
5666
+ className: "text-gray-400 hover:text-gray-600 p-0.5 rounded",
5667
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
5673
5668
  }
5674
- },
5675
- className: "w-full border border-gray-200 rounded px-2.5 py-1.5 text-sm bg-white text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
5676
- }
5677
- ),
5678
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-2", children: CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(
5679
- "button",
5680
- {
5681
- type: "button",
5682
- onMouseDown: (e) => {
5683
- e.preventDefault();
5684
- setCategory(cat);
5685
- setFocusedIdx(-1);
5686
- },
5687
- className: [
5688
- "px-2 py-0.5 rounded text-xs font-medium transition-colors",
5689
- category === cat ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200 "
5690
- ].join(" "),
5691
- children: cat
5692
- },
5693
- cat
5694
- )) })
5695
- ] }),
5696
- /* @__PURE__ */ jsxRuntime.jsx(
5697
- "div",
5698
- {
5699
- ref: gridRef,
5700
- role: "grid",
5701
- "aria-label": "Character grid",
5702
- className: "flex-1 overflow-y-auto p-2",
5703
- style: {
5704
- display: "grid",
5705
- gridTemplateColumns: "repeat(8, 1fr)",
5706
- gap: 2
5707
- },
5708
- onKeyDown: handleGridKeyDown,
5709
- children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
5710
- "div",
5669
+ )
5670
+ ] }),
5671
+ /* @__PURE__ */ jsxRuntime.jsx(
5672
+ "input",
5711
5673
  {
5712
- className: "col-span-8 text-center py-8 text-sm text-gray-400 ",
5713
- role: "status",
5714
- children: "No characters found"
5674
+ ref: searchRef,
5675
+ type: "search",
5676
+ placeholder: "Search characters\u2026",
5677
+ value: search,
5678
+ onChange: (e) => {
5679
+ setSearch(e.target.value);
5680
+ setFocusedIdx(-1);
5681
+ },
5682
+ onKeyDown: (e) => {
5683
+ var _a, _b;
5684
+ if (e.key === "Escape") closeModal();
5685
+ if (e.key === "ArrowDown") {
5686
+ e.preventDefault();
5687
+ setFocusedIdx(0);
5688
+ (_b = (_a = gridRef.current) == null ? void 0 : _a.children[0]) == null ? void 0 : _b.dispatchEvent(
5689
+ new Event("focus")
5690
+ );
5691
+ }
5692
+ },
5693
+ className: "w-full border border-gray-200 rounded px-2.5 py-1.5 text-sm bg-white text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
5715
5694
  }
5716
- ) : filtered.map((c, idx) => /* @__PURE__ */ jsxRuntime.jsx(
5695
+ ),
5696
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-2", children: CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(
5717
5697
  "button",
5718
5698
  {
5719
5699
  type: "button",
5720
- role: "gridcell",
5721
- tabIndex: focusedIdx === idx ? 0 : -1,
5722
- "aria-label": c.name,
5723
- title: c.name,
5724
5700
  onMouseDown: (e) => {
5725
5701
  e.preventDefault();
5726
- insertChar(c.char);
5702
+ setCategory(cat);
5703
+ setFocusedIdx(-1);
5727
5704
  },
5728
- onMouseEnter: () => setHovered({ char: c.char, name: c.name }),
5729
- onMouseLeave: () => setHovered(null),
5730
- onFocus: () => setHovered({ char: c.char, name: c.name }),
5731
- onBlur: () => setHovered(null),
5732
5705
  className: [
5733
- "flex items-center justify-center rounded text-base h-8 w-full transition-colors cursor-pointer select-none",
5734
- focusedIdx === idx ? "bg-blue-100 text-blue-700 ring-2 ring-blue-500" : "text-gray-800 hover:bg-blue-50 hover:text-blue-700 "
5706
+ "px-2 py-0.5 rounded text-xs font-medium transition-colors",
5707
+ category === cat ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200 "
5735
5708
  ].join(" "),
5736
- children: c.char
5709
+ children: cat
5737
5710
  },
5738
- c.char + c.name
5739
- ))
5740
- }
5741
- ),
5742
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 border-t border-gray-100 flex items-center gap-3 min-h-[40px]", children: hovered ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5743
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl leading-none text-gray-800 w-8 text-center", children: hovered.char }),
5744
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 truncate", children: hovered.name })
5745
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 ", children: "Hover or use arrow keys to preview" }) })
5746
- ]
5747
- }
5711
+ cat
5712
+ )) })
5713
+ ] }),
5714
+ /* @__PURE__ */ jsxRuntime.jsx(
5715
+ "div",
5716
+ {
5717
+ ref: gridRef,
5718
+ role: "grid",
5719
+ "aria-label": "Character grid",
5720
+ className: "flex-1 overflow-y-auto p-2",
5721
+ style: {
5722
+ display: "grid",
5723
+ gridTemplateColumns: "repeat(8, 1fr)",
5724
+ gap: 2
5725
+ },
5726
+ onKeyDown: handleGridKeyDown,
5727
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
5728
+ "div",
5729
+ {
5730
+ className: "col-span-8 text-center py-8 text-sm text-gray-400 ",
5731
+ role: "status",
5732
+ children: "No characters found"
5733
+ }
5734
+ ) : filtered.map((c, idx) => /* @__PURE__ */ jsxRuntime.jsx(
5735
+ "button",
5736
+ {
5737
+ type: "button",
5738
+ role: "gridcell",
5739
+ tabIndex: focusedIdx === idx ? 0 : -1,
5740
+ "aria-label": c.name,
5741
+ title: c.name,
5742
+ onMouseDown: (e) => {
5743
+ e.preventDefault();
5744
+ insertChar(c.char);
5745
+ },
5746
+ onMouseEnter: () => setHovered({ char: c.char, name: c.name }),
5747
+ onMouseLeave: () => setHovered(null),
5748
+ onFocus: () => setHovered({ char: c.char, name: c.name }),
5749
+ onBlur: () => setHovered(null),
5750
+ className: [
5751
+ "flex items-center justify-center rounded text-base h-8 w-full transition-colors cursor-pointer select-none",
5752
+ focusedIdx === idx ? "bg-blue-100 text-blue-700 ring-2 ring-blue-500" : "text-gray-800 hover:bg-blue-50 hover:text-blue-700 "
5753
+ ].join(" "),
5754
+ children: c.char
5755
+ },
5756
+ c.char + c.name
5757
+ ))
5758
+ }
5759
+ ),
5760
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 border-t border-gray-100 flex items-center gap-3 min-h-[40px]", children: hovered ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5761
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl leading-none text-gray-800 w-8 text-center", children: hovered.char }),
5762
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 truncate", children: hovered.name })
5763
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 ", children: "Hover or use arrow keys to preview" }) })
5764
+ ]
5765
+ }
5766
+ ),
5767
+ document.body
5748
5768
  )
5749
5769
  ] });
5750
5770
  }
@@ -7919,7 +7939,18 @@ function EditorCore({
7919
7939
  container.querySelectorAll(".editor-cell-active").forEach((el) => {
7920
7940
  el.classList.remove("editor-cell-active");
7921
7941
  });
7922
- if (!inTableCellPos) return;
7942
+ if (!inTableCellPos) {
7943
+ setTableSelection(null);
7944
+ return;
7945
+ }
7946
+ if (tableSelection) {
7947
+ const { anchorCell, focusCell, tablePath } = tableSelection;
7948
+ const sameTable = JSON.stringify(tablePath) === JSON.stringify(inTableCellPos.tablePath);
7949
+ const singleCell = anchorCell[0] === focusCell[0] && anchorCell[1] === focusCell[1];
7950
+ if (!sameTable || singleCell) {
7951
+ setTableSelection(null);
7952
+ }
7953
+ }
7923
7954
  const cellPath = [...inTableCellPos.tablePath, inTableCellPos.row, inTableCellPos.col];
7924
7955
  const td = container.querySelector(
7925
7956
  `[data-block-path="${JSON.stringify(cellPath)}"]`