@sofereditor/react 0.9.0 → 0.10.1

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.d.cts CHANGED
@@ -417,8 +417,15 @@ declare function NodeView({ block, index, children, fragment, tableFragment }: N
417
417
  *
418
418
  * Keys are stable on `${blockIndex}-${runningOffset}` so React reconciliation
419
419
  * survives split/merge of runs under collaboration.
420
+ *
421
+ * `baseOffset` shifts every emitted `data-embed-offset` (and key) by a fixed
422
+ * amount. Needed when `delta` is only a SLICE of a larger model range — e.g.
423
+ * one line of a célula-lista, where each `<li>` gets its own line-local delta
424
+ * but `data-embed-offset` must stay in CELL coordinates (contract with
425
+ * `Editor.tsx`'s embed-selection and `ImageResizeOverlay`'s DOM query).
426
+ * Defaults to 0, which reproduces the old line-local numbering exactly.
420
427
  */
421
- declare function renderInline(delta: DeltaOp[], keyPrefix: number | string): ReactNode;
428
+ declare function renderInline(delta: DeltaOp[], keyPrefix: number | string, baseOffset?: number): ReactNode;
422
429
 
423
430
  interface EditorProviderProps {
424
431
  editor: UseEditorResult;
@@ -512,10 +519,11 @@ declare function applyDomSelection(root: HTMLElement, modelSel: Selection): void
512
519
  * com o marcador (número/símbolo) como texto literal dentro de um span
513
520
  * `mso-list:Ignore`. `parseWordListLevel` + a exclusão desse span tratam isso.
514
521
  *
515
- * fontFamily é deliberadamente NUNCA emitida: a escola padroniza Arial e cada
516
- * documento tem sua própria tipografia (mesma decisão de `import-docx`, que
517
- * descarta `w:rFonts`). fontSize É preservado (ver `parseFontSizePt`): o
518
- * modelo já suporta a mark, a barra já tem controle de tamanho, e descartar o
522
+ * fontFamily é deliberadamente NUNCA emitida: a escola padroniza uma fonte só,
523
+ * definida no CSS (`.ed-root`) e cada documento tem sua própria tipografia
524
+ * (mesma decisão de `import-docx`, que descarta `w:rFonts`). fontSize É
525
+ * preservado (ver `parseFontSizePt`): o modelo já suporta a mark, a barra já
526
+ * tem controle de tamanho, e descartar o
519
527
  * tamanho perdia informação real (título de 37.5pt colado do Google Docs saía
520
528
  * do tamanho do corpo). Unidades que dependem do contexto de herança do
521
529
  * documento de origem (em/rem/%/larger/smaller/medium) não são resolvíveis
package/dist/index.d.ts CHANGED
@@ -417,8 +417,15 @@ declare function NodeView({ block, index, children, fragment, tableFragment }: N
417
417
  *
418
418
  * Keys are stable on `${blockIndex}-${runningOffset}` so React reconciliation
419
419
  * survives split/merge of runs under collaboration.
420
+ *
421
+ * `baseOffset` shifts every emitted `data-embed-offset` (and key) by a fixed
422
+ * amount. Needed when `delta` is only a SLICE of a larger model range — e.g.
423
+ * one line of a célula-lista, where each `<li>` gets its own line-local delta
424
+ * but `data-embed-offset` must stay in CELL coordinates (contract with
425
+ * `Editor.tsx`'s embed-selection and `ImageResizeOverlay`'s DOM query).
426
+ * Defaults to 0, which reproduces the old line-local numbering exactly.
420
427
  */
421
- declare function renderInline(delta: DeltaOp[], keyPrefix: number | string): ReactNode;
428
+ declare function renderInline(delta: DeltaOp[], keyPrefix: number | string, baseOffset?: number): ReactNode;
422
429
 
423
430
  interface EditorProviderProps {
424
431
  editor: UseEditorResult;
@@ -512,10 +519,11 @@ declare function applyDomSelection(root: HTMLElement, modelSel: Selection): void
512
519
  * com o marcador (número/símbolo) como texto literal dentro de um span
513
520
  * `mso-list:Ignore`. `parseWordListLevel` + a exclusão desse span tratam isso.
514
521
  *
515
- * fontFamily é deliberadamente NUNCA emitida: a escola padroniza Arial e cada
516
- * documento tem sua própria tipografia (mesma decisão de `import-docx`, que
517
- * descarta `w:rFonts`). fontSize É preservado (ver `parseFontSizePt`): o
518
- * modelo já suporta a mark, a barra já tem controle de tamanho, e descartar o
522
+ * fontFamily é deliberadamente NUNCA emitida: a escola padroniza uma fonte só,
523
+ * definida no CSS (`.ed-root`) e cada documento tem sua própria tipografia
524
+ * (mesma decisão de `import-docx`, que descarta `w:rFonts`). fontSize É
525
+ * preservado (ver `parseFontSizePt`): o modelo já suporta a mark, a barra já
526
+ * tem controle de tamanho, e descartar o
519
527
  * tamanho perdia informação real (título de 37.5pt colado do Google Docs saía
520
528
  * do tamanho do corpo). Unidades que dependem do contexto de herança do
521
529
  * documento de origem (em/rem/%/larger/smaller/medium) não são resolvíveis
package/dist/index.js CHANGED
@@ -94,6 +94,11 @@ function isWrapAnchorImg(n) {
94
94
  function isPhantomEmbed(n) {
95
95
  return n.nodeType === Node.ELEMENT_NODE && n.dataset.embedPhantom === "true";
96
96
  }
97
+ function isCellLineBoundary(n) {
98
+ if (n.nodeType !== Node.ELEMENT_NODE) return false;
99
+ const line = n.dataset.cellLine;
100
+ return line != null && Number(line) > 0;
101
+ }
97
102
  function indexInParent(n) {
98
103
  const parent = n.parentNode;
99
104
  if (!parent) return 0;
@@ -121,6 +126,7 @@ function textOffsetWithin(container, node, offsetInNode) {
121
126
  let done = false;
122
127
  function visit(n) {
123
128
  if (done) return;
129
+ if (isCellLineBoundary(n)) offset += 1;
124
130
  if (n === target) {
125
131
  if (n.nodeType === Node.TEXT_NODE) {
126
132
  offset += targetOff;
@@ -221,6 +227,7 @@ function locatePoint(root, pos) {
221
227
  let remaining = Math.max(0, pos.offset - containerStart);
222
228
  let result = null;
223
229
  let lastImg = null;
230
+ let lastBoundary = null;
224
231
  let lastText = null;
225
232
  function visit(n) {
226
233
  if (result) return;
@@ -232,6 +239,7 @@ function locatePoint(root, pos) {
232
239
  }
233
240
  remaining -= len;
234
241
  lastImg = null;
242
+ lastBoundary = null;
235
243
  lastText = n;
236
244
  return;
237
245
  }
@@ -239,6 +247,25 @@ function locatePoint(root, pos) {
239
247
  const el = n;
240
248
  if (isRejectedSubtree(el, container)) return;
241
249
  if (isImgEmbed(el) && isWrapAnchorImg(el)) return;
250
+ if (isCellLineBoundary(el)) {
251
+ if (remaining === 0) {
252
+ if (lastImg) {
253
+ const parent = lastImg.parentNode;
254
+ if (parent) {
255
+ result = { node: parent, offset: indexInParent(lastImg) + 1 };
256
+ return;
257
+ }
258
+ }
259
+ const prevLine = lastBoundary ?? el.previousElementSibling;
260
+ if (prevLine) {
261
+ result = { node: prevLine, offset: 0 };
262
+ return;
263
+ }
264
+ }
265
+ remaining -= 1;
266
+ lastBoundary = el;
267
+ lastImg = null;
268
+ }
242
269
  if (isImgEmbed(el) || isPhantomEmbed(el)) {
243
270
  if (remaining === 0) {
244
271
  const parent = el.parentNode;
@@ -249,6 +276,7 @@ function locatePoint(root, pos) {
249
276
  }
250
277
  remaining -= 1;
251
278
  lastImg = el;
279
+ lastBoundary = null;
252
280
  return;
253
281
  }
254
282
  for (const child of Array.from(el.childNodes)) {
@@ -258,7 +286,10 @@ function locatePoint(root, pos) {
258
286
  }
259
287
  visit(container);
260
288
  if (result) return result;
261
- if (lastImg && remaining === 0) {
289
+ if (lastBoundary) {
290
+ return { node: lastBoundary, offset: 0 };
291
+ }
292
+ if (lastImg) {
262
293
  const parent = lastImg.parentNode;
263
294
  if (parent) return { node: parent, offset: indexInParent(lastImg) + 1 };
264
295
  }
@@ -2815,7 +2846,9 @@ import { useMemo as useMemo2, useRef as useRef8 } from "react";
2815
2846
  import {
2816
2847
  answerLineStyle,
2817
2848
  cellBorderStyle,
2849
+ deltaLength as deltaLength2,
2818
2850
  normalizarLarguras as normalizarLarguras2,
2851
+ splitDeltaByLines,
2819
2852
  tableRectSelection
2820
2853
  } from "@sofereditor/core";
2821
2854
 
@@ -2827,11 +2860,11 @@ import {
2827
2860
  splitUnderscoreRuns
2828
2861
  } from "@sofereditor/core";
2829
2862
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2830
- function renderInline(delta, keyPrefix) {
2863
+ function renderInline(delta, keyPrefix, baseOffset = 0) {
2831
2864
  if (delta.length === 0 || delta.length === 1 && typeof delta[0].insert === "string" && delta[0].insert === "") {
2832
2865
  return /* @__PURE__ */ jsx10("br", { "data-empty": "true" });
2833
2866
  }
2834
- let offset = 0;
2867
+ let offset = baseOffset;
2835
2868
  const wrapAnchors = [];
2836
2869
  const body = [];
2837
2870
  for (const op of delta) {
@@ -3101,6 +3134,12 @@ import {
3101
3134
  } from "react";
3102
3135
  import { MIN_LINHA_PX, normalizarLarguras } from "@sofereditor/core";
3103
3136
  import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3137
+ function posicaoVisualDaDivisa(xLogico, larguraPx, rtl) {
3138
+ return rtl ? larguraPx - xLogico : xLogico;
3139
+ }
3140
+ function sinalDoArrasto(rtl) {
3141
+ return rtl ? -1 : 1;
3142
+ }
3104
3143
  function deltaPctDoArrasto(dxPx, larguraTabelaPx) {
3105
3144
  if (!(larguraTabelaPx > 0)) return 0;
3106
3145
  return dxPx / larguraTabelaPx * 100;
@@ -3110,7 +3149,7 @@ function distribuirAltura(base, deltaPx) {
3110
3149
  const fatia = deltaPx / base.length;
3111
3150
  return base.map((h) => Math.max(MIN_LINHA_PX, Math.round(h + fatia)));
3112
3151
  }
3113
- function TableResizeOverlay({ tableRef, blockIndex, cols, rows }) {
3152
+ function TableResizeOverlay({ tableRef, blockIndex, cols, rows, rtl = false }) {
3114
3153
  const editor = useEditorContext();
3115
3154
  const [layout, setLayout] = useState8(null);
3116
3155
  const arrastoRef = useRef7(null);
@@ -3185,7 +3224,8 @@ function TableResizeOverlay({ tableRef, blockIndex, cols, rows }) {
3185
3224
  if (!d) return;
3186
3225
  switch (d.tipo) {
3187
3226
  case "coluna": {
3188
- const dPct = deltaPctDoArrasto(e.clientX - d.startX, d.tableWidthPx);
3227
+ const dx = (e.clientX - d.startX) * sinalDoArrasto(rtl);
3228
+ const dPct = deltaPctDoArrasto(dx, d.tableWidthPx);
3189
3229
  editor.setColumnBoundary(blockIndex, d.boundary, dPct, d.baseWidths);
3190
3230
  break;
3191
3231
  }
@@ -3210,7 +3250,7 @@ function TableResizeOverlay({ tableRef, blockIndex, cols, rows }) {
3210
3250
  }
3211
3251
  }
3212
3252
  },
3213
- [blockIndex, editor]
3253
+ [blockIndex, editor, rtl]
3214
3254
  );
3215
3255
  const containerWidthPxOf = useCallback5((table, tableWidthPx) => {
3216
3256
  return table.parentElement?.getBoundingClientRect().width || tableWidthPx;
@@ -3326,7 +3366,7 @@ function TableResizeOverlay({ tableRef, blockIndex, cols, rows }) {
3326
3366
  {
3327
3367
  contentEditable: false,
3328
3368
  className: "ed-col-resize-handle",
3329
- style: colHandleStyle(x),
3369
+ style: colHandleStyle(posicaoVisualDaDivisa(x, layout.width, rtl)),
3330
3370
  onPointerDown: onColumnPointerDown(c),
3331
3371
  onPointerMove,
3332
3372
  onPointerUp,
@@ -3499,6 +3539,27 @@ function clampListLevel(l) {
3499
3539
  if (typeof l !== "number" || !Number.isFinite(l)) return 0;
3500
3540
  return Math.max(0, Math.min(5, Math.trunc(l)));
3501
3541
  }
3542
+ function renderCellContent(attrs, delta, keyPrefix) {
3543
+ const kind = attrs?.listKind;
3544
+ if (!kind) return renderInline(delta, keyPrefix);
3545
+ const linhas = splitDeltaByLines(delta);
3546
+ const Tag = kind === "ordered" ? "ol" : "ul";
3547
+ let baseOffset = 0;
3548
+ return /* @__PURE__ */ jsx12(
3549
+ Tag,
3550
+ {
3551
+ className: `ed-list ed-list-${kind}`,
3552
+ "data-list-kind": kind,
3553
+ start: kind === "ordered" && typeof attrs?.listStart === "number" ? attrs.listStart : void 0,
3554
+ style: attrs?.listStyle ? { listStyleType: attrs.listStyle } : void 0,
3555
+ children: linhas.map((linha, i) => {
3556
+ const li = /* @__PURE__ */ jsx12("li", { className: "ed-listitem", "data-cell-line": i, children: renderInline(linha, `${keyPrefix}-l${i}`, baseOffset) }, i);
3557
+ baseOffset += deltaLength2(linha) + 1;
3558
+ return li;
3559
+ })
3560
+ }
3561
+ );
3562
+ }
3502
3563
  function TableView({ block, index, tableFragment }) {
3503
3564
  const rows = clampPositive(block.attrs.rows);
3504
3565
  const cols = clampPositive(block.attrs.cols);
@@ -3523,6 +3584,7 @@ function TableView({ block, index, tableFragment }) {
3523
3584
  ref: tableRef,
3524
3585
  "data-block-index": index,
3525
3586
  "data-block-type": "table",
3587
+ dir: block.attrs.dir === "rtl" || block.attrs.dir === "ltr" ? block.attrs.dir : void 0,
3526
3588
  className: "ed-block ed-table",
3527
3589
  style: typeof tableWidth === "number" ? { width: `${tableWidth}%` } : void 0,
3528
3590
  children: [
@@ -3564,7 +3626,7 @@ function TableView({ block, index, tableFragment }) {
3564
3626
  )
3565
3627
  ),
3566
3628
  className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
3567
- children: renderInline(delta, `t${index}-c${flat}`)
3629
+ children: renderCellContent(cell?.attrs, delta, `t${index}-c${flat}`)
3568
3630
  },
3569
3631
  c
3570
3632
  );
@@ -3576,7 +3638,16 @@ function TableView({ block, index, tableFragment }) {
3576
3638
  ]
3577
3639
  }
3578
3640
  ),
3579
- isFirstFragment && /* @__PURE__ */ jsx12(TableResizeOverlay, { tableRef, blockIndex: index, cols, rows })
3641
+ isFirstFragment && /* @__PURE__ */ jsx12(
3642
+ TableResizeOverlay,
3643
+ {
3644
+ tableRef,
3645
+ blockIndex: index,
3646
+ cols,
3647
+ rows,
3648
+ rtl: block.attrs.dir === "rtl"
3649
+ }
3650
+ )
3580
3651
  ] });
3581
3652
  }
3582
3653
  function spanAttr(v) {
@@ -4403,6 +4474,10 @@ function useEditor(opts = {}) {
4403
4474
  const isListActive = useCallback7(
4404
4475
  (kind) => {
4405
4476
  const sel = selectionRef.current;
4477
+ if (sel.focus.cellIndex != null && doc.isTable(sel.focus.blockIndex)) {
4478
+ const real = doc.realCellIndex(sel.focus.blockIndex, sel.focus.cellIndex);
4479
+ return real != null && doc.getCellAttrs(sel.focus.blockIndex, real).listKind === kind;
4480
+ }
4406
4481
  const { start, end } = orderedRange(sel);
4407
4482
  for (let i = start.blockIndex; i <= end.blockIndex; i++) {
4408
4483
  if (doc.getBlockType(i) !== "listItem") return false;
@@ -5844,7 +5919,6 @@ function TableBorderPanel({
5844
5919
 
5845
5920
  // src/Toolbar.tsx
5846
5921
  import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
5847
- var FONT_FAMILIES = ["Arial"];
5848
5922
  var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
5849
5923
  var BLOCK_OPTIONS = [
5850
5924
  { value: "paragraph", label: "Par\xE1grafo", type: "paragraph" },
@@ -5918,11 +5992,6 @@ function Toolbar({ className }) {
5918
5992
  const onHighlightChange = (e) => {
5919
5993
  setMark("highlight", e.target.value);
5920
5994
  };
5921
- const onFontFamilyChange = (e) => {
5922
- const v = e.target.value;
5923
- if (v === "") removeMark("fontFamily");
5924
- else setMark("fontFamily", v);
5925
- };
5926
5995
  const onFontSizeChange = (e) => {
5927
5996
  const v = e.target.value;
5928
5997
  if (v === "") removeMark("fontSize");
@@ -5983,7 +6052,6 @@ function Toolbar({ className }) {
5983
6052
  );
5984
6053
  const colorValue = typeof active.color === "string" ? active.color : "#000000";
5985
6054
  const highlightValue = typeof active.highlight === "string" ? active.highlight : "#fff176";
5986
- const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
5987
6055
  const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
5988
6056
  const blockValue = blockSelectValue(blockType, blockLevel);
5989
6057
  const selectedEmbed = editor.getSelectedEmbed();
@@ -6082,36 +6150,20 @@ function Toolbar({ className }) {
6082
6150
  }
6083
6151
  )
6084
6152
  ] }),
6085
- /* @__PURE__ */ jsxs15(Group, { children: [
6086
- /* @__PURE__ */ jsxs15(
6087
- "select",
6088
- {
6089
- className: "ed-toolbar-select",
6090
- value: fontFamilyValue,
6091
- onChange: onFontFamilyChange,
6092
- onMouseDown: stop,
6093
- "aria-label": "Fam\xEDlia da fonte",
6094
- children: [
6095
- /* @__PURE__ */ jsx15("option", { value: "", children: "Fonte\u2026" }),
6096
- FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx15("option", { value: f, children: f }, f))
6097
- ]
6098
- }
6099
- ),
6100
- /* @__PURE__ */ jsxs15(
6101
- "select",
6102
- {
6103
- className: "ed-toolbar-select",
6104
- value: fontSizeValue,
6105
- onChange: onFontSizeChange,
6106
- onMouseDown: stop,
6107
- "aria-label": "Tamanho da fonte",
6108
- children: [
6109
- /* @__PURE__ */ jsx15("option", { value: "", children: "Tamanho\u2026" }),
6110
- FONT_SIZES.map((s) => /* @__PURE__ */ jsx15("option", { value: s, children: s }, s))
6111
- ]
6112
- }
6113
- )
6114
- ] }),
6153
+ /* @__PURE__ */ jsx15(Group, { children: /* @__PURE__ */ jsxs15(
6154
+ "select",
6155
+ {
6156
+ className: "ed-toolbar-select",
6157
+ value: fontSizeValue,
6158
+ onChange: onFontSizeChange,
6159
+ onMouseDown: stop,
6160
+ "aria-label": "Tamanho da fonte",
6161
+ children: [
6162
+ /* @__PURE__ */ jsx15("option", { value: "", children: "Tamanho\u2026" }),
6163
+ FONT_SIZES.map((s) => /* @__PURE__ */ jsx15("option", { value: s, children: s }, s))
6164
+ ]
6165
+ }
6166
+ ) }),
6115
6167
  /* @__PURE__ */ jsx15(Group, { children: ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsx15(
6116
6168
  "button",
6117
6169
  {