@sofereditor/react 0.3.2 → 0.5.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,17 @@ 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
+ block.attrs.borderColor
2253
+ )
2254
+ ),
2237
2255
  className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
2238
2256
  children: renderInline(delta, `t${index}-c${flat}`)
2239
2257
  },
@@ -3016,6 +3034,26 @@ function useEditor(opts = {}) {
3016
3034
  if (sel.focus.cellIndex == null || !doc.isTable(sel.focus.blockIndex)) return void 0;
3017
3035
  return doc.getCellAttrs(sel.focus.blockIndex, sel.focus.cellIndex).bgColor;
3018
3036
  }, [doc]);
3037
+ const setTableBorderPreset = (0, import_react13.useCallback)((preset) => {
3038
+ const sel = selectionRef.current;
3039
+ if (!doc.isTable(sel.focus.blockIndex)) return;
3040
+ (0, import_core5.setBlockAttrAtIndex)(ctxRef.current, sel.focus.blockIndex, "borderPreset", preset);
3041
+ }, [doc]);
3042
+ const getTableBorderPreset = (0, import_react13.useCallback)(() => {
3043
+ const sel = selectionRef.current;
3044
+ if (!doc.isTable(sel.focus.blockIndex)) return "all";
3045
+ return doc.getBlockAttrs(sel.focus.blockIndex).borderPreset ?? "all";
3046
+ }, [doc]);
3047
+ const setTableBorderColor = (0, import_react13.useCallback)((color) => {
3048
+ const sel = selectionRef.current;
3049
+ if (!doc.isTable(sel.focus.blockIndex)) return;
3050
+ (0, import_core5.setBlockAttrAtIndex)(ctxRef.current, sel.focus.blockIndex, "borderColor", color);
3051
+ }, [doc]);
3052
+ const getTableBorderColor = (0, import_react13.useCallback)(() => {
3053
+ const sel = selectionRef.current;
3054
+ if (!doc.isTable(sel.focus.blockIndex)) return void 0;
3055
+ return doc.getBlockAttrs(sel.focus.blockIndex).borderColor;
3056
+ }, [doc]);
3019
3057
  const isListActive = (0, import_react13.useCallback)(
3020
3058
  (kind) => {
3021
3059
  const sel = selectionRef.current;
@@ -3049,6 +3087,9 @@ function useEditor(opts = {}) {
3049
3087
  const insertTable = (0, import_react13.useCallback)((rows, cols) => {
3050
3088
  (0, import_core5.insertTable)(ctxRef.current, rows, cols);
3051
3089
  }, []);
3090
+ const insertAnswerLines = (0, import_react13.useCallback)((count, spacing) => {
3091
+ (0, import_core5.insertAnswerLines)(ctxRef.current, count, spacing);
3092
+ }, []);
3052
3093
  const deleteTable = (0, import_react13.useCallback)((blockIndex) => {
3053
3094
  const idx = blockIndex ?? selectionRef.current.focus.blockIndex;
3054
3095
  (0, import_core5.deleteTable)(ctxRef.current, idx);
@@ -3257,6 +3298,10 @@ function useEditor(opts = {}) {
3257
3298
  getAlign,
3258
3299
  setCellBackground,
3259
3300
  getCellBackground,
3301
+ setTableBorderPreset,
3302
+ getTableBorderPreset,
3303
+ setTableBorderColor,
3304
+ getTableBorderColor,
3260
3305
  isListActive,
3261
3306
  toggleList,
3262
3307
  indentList,
@@ -3267,6 +3312,7 @@ function useEditor(opts = {}) {
3267
3312
  resolveLinkRequest,
3268
3313
  isInTable,
3269
3314
  getTableLocation,
3315
+ insertAnswerLines,
3270
3316
  insertTable,
3271
3317
  deleteTable,
3272
3318
  insertRowAbove,
@@ -4221,6 +4267,7 @@ function caretRangeFromPoint(x, y) {
4221
4267
 
4222
4268
  // src/Toolbar.tsx
4223
4269
  var import_react15 = require("react");
4270
+ var import_core7 = require("@sofereditor/core");
4224
4271
  var import_jsx_runtime13 = require("react/jsx-runtime");
4225
4272
  var FONT_FAMILIES = ["Arial"];
4226
4273
  var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
@@ -4242,6 +4289,15 @@ var ALIGN_OPTIONS = [
4242
4289
  { value: "justify", label: "\u2630", title: "Justificar" }
4243
4290
  ];
4244
4291
  var IMAGE_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right"]);
4292
+ var NATIVE_CONTROL_TAGS = /* @__PURE__ */ new Set(["SELECT", "INPUT", "OPTION"]);
4293
+ function isNativeControl(t) {
4294
+ const el = t;
4295
+ return !!el && NATIVE_CONTROL_TAGS.has(el.tagName);
4296
+ }
4297
+ function keepsOwnMouseDown(t) {
4298
+ const el = t;
4299
+ return !!el && (NATIVE_CONTROL_TAGS.has(el.tagName) || el.tagName === "BUTTON");
4300
+ }
4245
4301
  var IMAGE_LAYOUT_OPTIONS = [
4246
4302
  { value: "inline", label: "\u2194", title: "Inline (no fluxo do texto)" },
4247
4303
  { value: "wrap-left", label: "\u25E7", title: "Texto contorna \xE0 direita" },
@@ -4274,10 +4330,7 @@ function Toolbar({ className }) {
4274
4330
  const blockAlign = getAlign();
4275
4331
  const blockDir = getBlockAttr("dir");
4276
4332
  const stop = (e) => {
4277
- const t = e.target;
4278
- if (t && (t.tagName === "SELECT" || t.tagName === "INPUT" || t.tagName === "OPTION")) {
4279
- return;
4280
- }
4333
+ if (isNativeControl(e.target)) return;
4281
4334
  e.preventDefault();
4282
4335
  };
4283
4336
  const onToggle = (name) => (e) => {
@@ -4287,6 +4340,9 @@ function Toolbar({ className }) {
4287
4340
  const onColorChange = (e) => {
4288
4341
  setMark("color", e.target.value);
4289
4342
  };
4343
+ const onHighlightChange = (e) => {
4344
+ setMark("highlight", e.target.value);
4345
+ };
4290
4346
  const onFontFamilyChange = (e) => {
4291
4347
  const v = e.target.value;
4292
4348
  if (v === "") removeMark("fontFamily");
@@ -4351,6 +4407,7 @@ function Toolbar({ className }) {
4351
4407
  [active.link, editor, removeMark, setMark]
4352
4408
  );
4353
4409
  const colorValue = typeof active.color === "string" ? active.color : "#000000";
4410
+ const highlightValue = typeof active.highlight === "string" ? active.highlight : "#fff176";
4354
4411
  const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
4355
4412
  const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
4356
4413
  const blockValue = blockSelectValue(blockType, blockLevel);
@@ -4414,6 +4471,40 @@ function Toolbar({ className }) {
4414
4471
  },
4415
4472
  children: "\u232B"
4416
4473
  }
4474
+ ),
4475
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", title: "Marca-texto", children: [
4476
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4477
+ "span",
4478
+ {
4479
+ className: "ed-toolbar-swatch ed-toolbar-swatch--highlight",
4480
+ "aria-hidden": true,
4481
+ style: { background: highlightValue }
4482
+ }
4483
+ ),
4484
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4485
+ "input",
4486
+ {
4487
+ type: "color",
4488
+ value: highlightValue,
4489
+ onChange: onHighlightChange,
4490
+ onMouseDown: stop,
4491
+ "aria-label": "Marca-texto"
4492
+ }
4493
+ )
4494
+ ] }),
4495
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4496
+ "button",
4497
+ {
4498
+ type: "button",
4499
+ className: "ed-toolbar-btn",
4500
+ title: "Remover marca-texto",
4501
+ onMouseDown: stop,
4502
+ onClick: (e) => {
4503
+ e.preventDefault();
4504
+ removeMark("highlight");
4505
+ },
4506
+ children: "\u232B"
4507
+ }
4417
4508
  )
4418
4509
  ] }),
4419
4510
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
@@ -4543,7 +4634,10 @@ function Toolbar({ className }) {
4543
4634
  children: "\u{1F517}"
4544
4635
  }
4545
4636
  ) }),
4546
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}) }),
4637
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4638
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}),
4639
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AnswerLinesMenu, {})
4640
+ ] }),
4547
4641
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4548
4642
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4549
4643
  "button",
@@ -4629,6 +4723,99 @@ function Toolbar({ className }) {
4629
4723
  }
4630
4724
  );
4631
4725
  }
4726
+ function AnswerLinesMenu() {
4727
+ const editor = useEditorContext();
4728
+ const [open, setOpen] = (0, import_react15.useState)(false);
4729
+ const [count, setCount] = (0, import_react15.useState)(5);
4730
+ const [spacing, setSpacing] = (0, import_react15.useState)(1.5);
4731
+ const rootRef = (0, import_react15.useRef)(null);
4732
+ (0, import_react15.useEffect)(() => {
4733
+ if (!open) return;
4734
+ const onDown = (e) => {
4735
+ if (!rootRef.current) return;
4736
+ if (rootRef.current.contains(e.target)) return;
4737
+ setOpen(false);
4738
+ };
4739
+ document.addEventListener("mousedown", onDown);
4740
+ return () => document.removeEventListener("mousedown", onDown);
4741
+ }, [open]);
4742
+ const clamped = Math.max(1, Math.min(import_core7.ANSWER_LINE_MAX, Math.trunc(count) || 1));
4743
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ref: rootRef, className: "ed-toolbar-tablemenu", children: [
4744
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4745
+ "button",
4746
+ {
4747
+ type: "button",
4748
+ className: "ed-toolbar-btn",
4749
+ title: "Linhas de resposta",
4750
+ "aria-pressed": open,
4751
+ onMouseDown: (e) => e.preventDefault(),
4752
+ onClick: (e) => {
4753
+ e.preventDefault();
4754
+ setOpen((v) => !v);
4755
+ },
4756
+ children: "\u2630"
4757
+ }
4758
+ ),
4759
+ open && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4760
+ "div",
4761
+ {
4762
+ className: "ed-table-popover ed-answerlines-popover",
4763
+ role: "menu",
4764
+ onMouseDown: (e) => {
4765
+ if (!keepsOwnMouseDown(e.target)) e.preventDefault();
4766
+ },
4767
+ children: [
4768
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4769
+ "Quantidade",
4770
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4771
+ "input",
4772
+ {
4773
+ type: "number",
4774
+ min: 1,
4775
+ max: import_core7.ANSWER_LINE_MAX,
4776
+ value: count,
4777
+ onChange: (e) => setCount(Number(e.target.value)),
4778
+ "aria-label": "Quantidade de linhas"
4779
+ }
4780
+ )
4781
+ ] }),
4782
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4783
+ "Entrelinha",
4784
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4785
+ "select",
4786
+ {
4787
+ value: String(spacing),
4788
+ onChange: (e) => setSpacing(Number(e.target.value)),
4789
+ "aria-label": "Entrelinha",
4790
+ children: [
4791
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1", children: "Simples" }),
4792
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1.5", children: "1,5" }),
4793
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "2", children: "Duplo" })
4794
+ ]
4795
+ }
4796
+ )
4797
+ ] }),
4798
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4799
+ "button",
4800
+ {
4801
+ type: "button",
4802
+ onClick: () => {
4803
+ setOpen(false);
4804
+ editor.insertAnswerLines(clamped, spacing);
4805
+ },
4806
+ children: [
4807
+ "Inserir ",
4808
+ clamped,
4809
+ " linha",
4810
+ clamped > 1 ? "s" : ""
4811
+ ]
4812
+ }
4813
+ )
4814
+ ]
4815
+ }
4816
+ )
4817
+ ] });
4818
+ }
4632
4819
  function TableMenu() {
4633
4820
  const editor = useEditorContext();
4634
4821
  const inTable = editor.isInTable();
@@ -4666,10 +4853,7 @@ function TableMenu() {
4666
4853
  className: "ed-table-popover",
4667
4854
  role: "menu",
4668
4855
  onMouseDown: (e) => {
4669
- const t = e.target;
4670
- if (!t || t.tagName !== "INPUT" && t.tagName !== "BUTTON") {
4671
- e.preventDefault();
4672
- }
4856
+ if (!keepsOwnMouseDown(e.target)) e.preventDefault();
4673
4857
  },
4674
4858
  children: [
4675
4859
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
@@ -4757,6 +4941,56 @@ function TableMenu() {
4757
4941
  editor.setCellBackground(null);
4758
4942
  }, children: "Remover cor de fundo" }),
4759
4943
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4944
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4945
+ "Bordas",
4946
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4947
+ "select",
4948
+ {
4949
+ value: editor.getTableBorderPreset(),
4950
+ onChange: (e) => editor.setTableBorderPreset(e.target.value),
4951
+ "aria-label": "Bordas da tabela",
4952
+ children: [
4953
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "all", children: "Todas" }),
4954
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "outer", children: "S\xF3 externas" }),
4955
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "horizontal", children: "S\xF3 horizontais" }),
4956
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "vertical", children: "S\xF3 verticais" }),
4957
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "none", children: "Nenhuma" })
4958
+ ]
4959
+ }
4960
+ )
4961
+ ] }),
4962
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label ed-table-bgcolor", children: [
4963
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4964
+ "span",
4965
+ {
4966
+ className: "ed-toolbar-swatch",
4967
+ "aria-hidden": true,
4968
+ style: { background: editor.getTableBorderColor() ?? import_core7.TABLE_BORDER_COLOR }
4969
+ }
4970
+ ),
4971
+ "Cor da borda",
4972
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4973
+ "input",
4974
+ {
4975
+ type: "color",
4976
+ value: editor.getTableBorderColor() ?? import_core7.TABLE_BORDER_COLOR,
4977
+ onChange: (e) => editor.setTableBorderColor(e.target.value),
4978
+ "aria-label": "Cor da borda"
4979
+ }
4980
+ )
4981
+ ] }),
4982
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4983
+ "button",
4984
+ {
4985
+ type: "button",
4986
+ onClick: () => {
4987
+ setOpen(false);
4988
+ editor.setTableBorderColor(null);
4989
+ },
4990
+ children: "Restaurar cor padr\xE3o"
4991
+ }
4992
+ ),
4993
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4760
4994
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4761
4995
  "button",
4762
4996
  {