@sofereditor/react 0.3.2 → 0.4.0

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
@@ -1899,8 +1899,15 @@ function ImageEmbedViewImpl({
1899
1899
  }
1900
1900
  );
1901
1901
  }
1902
+ function decorateBlanks(text) {
1903
+ const segs = (0, import_core3.splitUnderscoreRuns)(text);
1904
+ if (segs.length === 1 && !segs[0].blank) return text;
1905
+ return segs.map(
1906
+ (s, i) => s.blank ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-blank": "true", style: import_core3.BLANK_STYLE, children: s.text }, i) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react8.Fragment, { children: s.text }, i)
1907
+ );
1908
+ }
1902
1909
  function wrap(text, attrs) {
1903
- let node = text;
1910
+ let node = decorateBlanks(text);
1904
1911
  if (!attrs) return node;
1905
1912
  if (attrs.bold) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: node });
1906
1913
  if (attrs.italic) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("em", { children: node });
@@ -1908,6 +1915,7 @@ function wrap(text, attrs) {
1908
1915
  if (attrs.strike) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("s", { children: node });
1909
1916
  const styleParts = {};
1910
1917
  if (attrs.color) styleParts.color = attrs.color;
1918
+ if (attrs.highlight) styleParts.backgroundColor = attrs.highlight;
1911
1919
  if (attrs.fontFamily) styleParts.fontFamily = attrs.fontFamily;
1912
1920
  if (attrs.fontSize) styleParts.fontSize = attrs.fontSize;
1913
1921
  if (Object.keys(styleParts).length > 0) {
@@ -2154,6 +2162,7 @@ function NodeView({ block, index, children, fragment, tableFragment }) {
2154
2162
  function commonBlockProps(attrs, index, type, fragment) {
2155
2163
  const style = {};
2156
2164
  if (attrs.align) style.textAlign = attrs.align;
2165
+ Object.assign(style, (0, import_core4.answerLineStyle)(attrs) ?? {});
2157
2166
  const base = {
2158
2167
  "data-block-index": index,
2159
2168
  "data-block-type": type,
@@ -2173,11 +2182,10 @@ function fragmentClass(f) {
2173
2182
  function alignClass(a) {
2174
2183
  return a ? ` ed-align-${a}` : "";
2175
2184
  }
2176
- function cellStyle(attrs) {
2177
- if (!attrs) return void 0;
2178
- const style = {};
2179
- if (attrs.align) style.textAlign = attrs.align;
2180
- if (attrs.bgColor) style.backgroundColor = attrs.bgColor;
2185
+ function cellStyle(attrs, border) {
2186
+ const style = { ...border };
2187
+ if (attrs?.align) style.textAlign = attrs.align;
2188
+ if (attrs?.bgColor) style.backgroundColor = attrs.bgColor;
2181
2189
  return Object.keys(style).length > 0 ? style : void 0;
2182
2190
  }
2183
2191
  function clampLevel(l) {
@@ -2233,7 +2241,16 @@ function TableView({ block, index, tableFragment }) {
2233
2241
  "data-cell-colspan": colspan,
2234
2242
  rowSpan: rowspan > 1 ? rowspan : void 0,
2235
2243
  colSpan: colspan > 1 ? colspan : void 0,
2236
- style: cellStyle(cell?.attrs),
2244
+ style: cellStyle(
2245
+ cell?.attrs,
2246
+ // rowStart/rowEnd são os limites do FRAGMENTO: numa tabela
2247
+ // quebrada entre páginas, cada página fecha a própria caixa.
2248
+ (0, import_core4.cellBorderStyle)(
2249
+ block.attrs.borderPreset,
2250
+ { row: r, col: c, rowspan, colspan, cols, rowStart, rowEnd },
2251
+ "screen"
2252
+ )
2253
+ ),
2237
2254
  className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
2238
2255
  children: renderInline(delta, `t${index}-c${flat}`)
2239
2256
  },
@@ -3016,6 +3033,16 @@ function useEditor(opts = {}) {
3016
3033
  if (sel.focus.cellIndex == null || !doc.isTable(sel.focus.blockIndex)) return void 0;
3017
3034
  return doc.getCellAttrs(sel.focus.blockIndex, sel.focus.cellIndex).bgColor;
3018
3035
  }, [doc]);
3036
+ const setTableBorderPreset = (0, import_react13.useCallback)((preset) => {
3037
+ const sel = selectionRef.current;
3038
+ if (!doc.isTable(sel.focus.blockIndex)) return;
3039
+ (0, import_core5.setBlockAttrAtIndex)(ctxRef.current, sel.focus.blockIndex, "borderPreset", preset);
3040
+ }, [doc]);
3041
+ const getTableBorderPreset = (0, import_react13.useCallback)(() => {
3042
+ const sel = selectionRef.current;
3043
+ if (!doc.isTable(sel.focus.blockIndex)) return "all";
3044
+ return doc.getBlockAttrs(sel.focus.blockIndex).borderPreset ?? "all";
3045
+ }, [doc]);
3019
3046
  const isListActive = (0, import_react13.useCallback)(
3020
3047
  (kind) => {
3021
3048
  const sel = selectionRef.current;
@@ -3049,6 +3076,9 @@ function useEditor(opts = {}) {
3049
3076
  const insertTable = (0, import_react13.useCallback)((rows, cols) => {
3050
3077
  (0, import_core5.insertTable)(ctxRef.current, rows, cols);
3051
3078
  }, []);
3079
+ const insertAnswerLines = (0, import_react13.useCallback)((count, spacing) => {
3080
+ (0, import_core5.insertAnswerLines)(ctxRef.current, count, spacing);
3081
+ }, []);
3052
3082
  const deleteTable = (0, import_react13.useCallback)((blockIndex) => {
3053
3083
  const idx = blockIndex ?? selectionRef.current.focus.blockIndex;
3054
3084
  (0, import_core5.deleteTable)(ctxRef.current, idx);
@@ -3257,6 +3287,8 @@ function useEditor(opts = {}) {
3257
3287
  getAlign,
3258
3288
  setCellBackground,
3259
3289
  getCellBackground,
3290
+ setTableBorderPreset,
3291
+ getTableBorderPreset,
3260
3292
  isListActive,
3261
3293
  toggleList,
3262
3294
  indentList,
@@ -3267,6 +3299,7 @@ function useEditor(opts = {}) {
3267
3299
  resolveLinkRequest,
3268
3300
  isInTable,
3269
3301
  getTableLocation,
3302
+ insertAnswerLines,
3270
3303
  insertTable,
3271
3304
  deleteTable,
3272
3305
  insertRowAbove,
@@ -4221,6 +4254,7 @@ function caretRangeFromPoint(x, y) {
4221
4254
 
4222
4255
  // src/Toolbar.tsx
4223
4256
  var import_react15 = require("react");
4257
+ var import_core7 = require("@sofereditor/core");
4224
4258
  var import_jsx_runtime13 = require("react/jsx-runtime");
4225
4259
  var FONT_FAMILIES = ["Arial"];
4226
4260
  var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
@@ -4287,6 +4321,9 @@ function Toolbar({ className }) {
4287
4321
  const onColorChange = (e) => {
4288
4322
  setMark("color", e.target.value);
4289
4323
  };
4324
+ const onHighlightChange = (e) => {
4325
+ setMark("highlight", e.target.value);
4326
+ };
4290
4327
  const onFontFamilyChange = (e) => {
4291
4328
  const v = e.target.value;
4292
4329
  if (v === "") removeMark("fontFamily");
@@ -4351,6 +4388,7 @@ function Toolbar({ className }) {
4351
4388
  [active.link, editor, removeMark, setMark]
4352
4389
  );
4353
4390
  const colorValue = typeof active.color === "string" ? active.color : "#000000";
4391
+ const highlightValue = typeof active.highlight === "string" ? active.highlight : "#fff176";
4354
4392
  const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
4355
4393
  const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
4356
4394
  const blockValue = blockSelectValue(blockType, blockLevel);
@@ -4414,6 +4452,40 @@ function Toolbar({ className }) {
4414
4452
  },
4415
4453
  children: "\u232B"
4416
4454
  }
4455
+ ),
4456
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", title: "Marca-texto", children: [
4457
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4458
+ "span",
4459
+ {
4460
+ className: "ed-toolbar-swatch ed-toolbar-swatch--highlight",
4461
+ "aria-hidden": true,
4462
+ style: { background: highlightValue }
4463
+ }
4464
+ ),
4465
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4466
+ "input",
4467
+ {
4468
+ type: "color",
4469
+ value: highlightValue,
4470
+ onChange: onHighlightChange,
4471
+ onMouseDown: stop,
4472
+ "aria-label": "Marca-texto"
4473
+ }
4474
+ )
4475
+ ] }),
4476
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4477
+ "button",
4478
+ {
4479
+ type: "button",
4480
+ className: "ed-toolbar-btn",
4481
+ title: "Remover marca-texto",
4482
+ onMouseDown: stop,
4483
+ onClick: (e) => {
4484
+ e.preventDefault();
4485
+ removeMark("highlight");
4486
+ },
4487
+ children: "\u232B"
4488
+ }
4417
4489
  )
4418
4490
  ] }),
4419
4491
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
@@ -4543,7 +4615,10 @@ function Toolbar({ className }) {
4543
4615
  children: "\u{1F517}"
4544
4616
  }
4545
4617
  ) }),
4546
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}) }),
4618
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4619
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}),
4620
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AnswerLinesMenu, {})
4621
+ ] }),
4547
4622
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4548
4623
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4549
4624
  "button",
@@ -4629,6 +4704,102 @@ function Toolbar({ className }) {
4629
4704
  }
4630
4705
  );
4631
4706
  }
4707
+ function AnswerLinesMenu() {
4708
+ const editor = useEditorContext();
4709
+ const [open, setOpen] = (0, import_react15.useState)(false);
4710
+ const [count, setCount] = (0, import_react15.useState)(5);
4711
+ const [spacing, setSpacing] = (0, import_react15.useState)(1.5);
4712
+ const rootRef = (0, import_react15.useRef)(null);
4713
+ (0, import_react15.useEffect)(() => {
4714
+ if (!open) return;
4715
+ const onDown = (e) => {
4716
+ if (!rootRef.current) return;
4717
+ if (rootRef.current.contains(e.target)) return;
4718
+ setOpen(false);
4719
+ };
4720
+ document.addEventListener("mousedown", onDown);
4721
+ return () => document.removeEventListener("mousedown", onDown);
4722
+ }, [open]);
4723
+ const clamped = Math.max(1, Math.min(import_core7.ANSWER_LINE_MAX, Math.trunc(count) || 1));
4724
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ref: rootRef, className: "ed-toolbar-tablemenu", children: [
4725
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4726
+ "button",
4727
+ {
4728
+ type: "button",
4729
+ className: "ed-toolbar-btn",
4730
+ title: "Linhas de resposta",
4731
+ "aria-pressed": open,
4732
+ onMouseDown: (e) => e.preventDefault(),
4733
+ onClick: (e) => {
4734
+ e.preventDefault();
4735
+ setOpen((v) => !v);
4736
+ },
4737
+ children: "\u2630"
4738
+ }
4739
+ ),
4740
+ open && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4741
+ "div",
4742
+ {
4743
+ className: "ed-table-popover ed-answerlines-popover",
4744
+ role: "menu",
4745
+ onMouseDown: (e) => {
4746
+ const t = e.target;
4747
+ if (!t || t.tagName !== "INPUT" && t.tagName !== "BUTTON" && t.tagName !== "SELECT") {
4748
+ e.preventDefault();
4749
+ }
4750
+ },
4751
+ children: [
4752
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4753
+ "Quantidade",
4754
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4755
+ "input",
4756
+ {
4757
+ type: "number",
4758
+ min: 1,
4759
+ max: import_core7.ANSWER_LINE_MAX,
4760
+ value: count,
4761
+ onChange: (e) => setCount(Number(e.target.value)),
4762
+ "aria-label": "Quantidade de linhas"
4763
+ }
4764
+ )
4765
+ ] }),
4766
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4767
+ "Entrelinha",
4768
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4769
+ "select",
4770
+ {
4771
+ value: String(spacing),
4772
+ onChange: (e) => setSpacing(Number(e.target.value)),
4773
+ "aria-label": "Entrelinha",
4774
+ children: [
4775
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1", children: "Simples" }),
4776
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1.5", children: "1,5" }),
4777
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "2", children: "Duplo" })
4778
+ ]
4779
+ }
4780
+ )
4781
+ ] }),
4782
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4783
+ "button",
4784
+ {
4785
+ type: "button",
4786
+ onClick: () => {
4787
+ setOpen(false);
4788
+ editor.insertAnswerLines(clamped, spacing);
4789
+ },
4790
+ children: [
4791
+ "Inserir ",
4792
+ clamped,
4793
+ " linha",
4794
+ clamped > 1 ? "s" : ""
4795
+ ]
4796
+ }
4797
+ )
4798
+ ]
4799
+ }
4800
+ )
4801
+ ] });
4802
+ }
4632
4803
  function TableMenu() {
4633
4804
  const editor = useEditorContext();
4634
4805
  const inTable = editor.isInTable();
@@ -4757,6 +4928,25 @@ function TableMenu() {
4757
4928
  editor.setCellBackground(null);
4758
4929
  }, children: "Remover cor de fundo" }),
4759
4930
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4931
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4932
+ "Bordas",
4933
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4934
+ "select",
4935
+ {
4936
+ value: editor.getTableBorderPreset(),
4937
+ onChange: (e) => editor.setTableBorderPreset(e.target.value),
4938
+ "aria-label": "Bordas da tabela",
4939
+ children: [
4940
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "all", children: "Todas" }),
4941
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "outer", children: "S\xF3 externas" }),
4942
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "horizontal", children: "S\xF3 horizontais" }),
4943
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "vertical", children: "S\xF3 verticais" }),
4944
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "none", children: "Nenhuma" })
4945
+ ]
4946
+ }
4947
+ )
4948
+ ] }),
4949
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4760
4950
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4761
4951
  "button",
4762
4952
  {