@mhamz.01/easyflow-texteditor 0.1.145 → 0.1.147

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
@@ -4449,6 +4449,56 @@ function ListDropdownMenu({
4449
4449
 
4450
4450
  // src/components/tiptap-ui/blockquote-button/blockquote-button.tsx
4451
4451
  var import_react52 = require("react");
4452
+
4453
+ // src/hooks/mark-preservers/mark-preserver.ts
4454
+ var MarkPreserver = class {
4455
+ static preservedMarks = /* @__PURE__ */ new Map();
4456
+ /**
4457
+ * Preserve marks before an action that will cause blur
4458
+ * Call this in onMouseDown or onPointerDown events
4459
+ */
4460
+ static preserve(editor) {
4461
+ if (!editor) return;
4462
+ const { state } = editor;
4463
+ const { $from } = state.selection;
4464
+ const currentMarks = state.storedMarks || $from.marks();
4465
+ if (currentMarks.length > 0) {
4466
+ this.preservedMarks.set(editor, [...currentMarks]);
4467
+ }
4468
+ }
4469
+ /**
4470
+ * Restore marks after an action completes
4471
+ * Call this after the editor action in onClick or in a setTimeout
4472
+ */
4473
+ static restore(editor, delay = 0) {
4474
+ if (!editor) return;
4475
+ const marks = this.preservedMarks.get(editor);
4476
+ if (!marks) return;
4477
+ const restoreFn = () => {
4478
+ if (editor.isDestroyed) {
4479
+ this.preservedMarks.delete(editor);
4480
+ return;
4481
+ }
4482
+ const { state } = editor;
4483
+ editor.view.dispatch(state.tr.setStoredMarks(marks));
4484
+ this.preservedMarks.delete(editor);
4485
+ editor.commands.focus();
4486
+ };
4487
+ if (delay > 0) {
4488
+ setTimeout(restoreFn, delay);
4489
+ } else {
4490
+ restoreFn();
4491
+ }
4492
+ }
4493
+ /**
4494
+ * Clear preserved marks for an editor
4495
+ */
4496
+ static clear(editor) {
4497
+ this.preservedMarks.delete(editor);
4498
+ }
4499
+ };
4500
+
4501
+ // src/components/tiptap-ui/blockquote-button/blockquote-button.tsx
4452
4502
  var import_jsx_runtime45 = require("react/jsx-runtime");
4453
4503
  function BlockquoteShortcutBadge({
4454
4504
  shortcutKeys = BLOCKQUOTE_SHORTCUT_KEY
@@ -4484,10 +4534,15 @@ var BlockquoteButton = (0, import_react52.forwardRef)(
4484
4534
  (event) => {
4485
4535
  onClick?.(event);
4486
4536
  if (event.defaultPrevented) return;
4537
+ MarkPreserver.preserve(editor);
4487
4538
  handleToggle();
4539
+ MarkPreserver.restore(editor, 10);
4488
4540
  },
4489
- [handleToggle, onClick]
4541
+ [handleToggle, onClick, editor]
4490
4542
  );
4543
+ const handlePointerDown = (0, import_react52.useCallback)(() => {
4544
+ MarkPreserver.preserve(editor);
4545
+ }, [editor]);
4491
4546
  if (!isVisible) {
4492
4547
  return null;
4493
4548
  }
@@ -4504,6 +4559,7 @@ var BlockquoteButton = (0, import_react52.forwardRef)(
4504
4559
  "aria-label": label,
4505
4560
  "aria-pressed": isActive,
4506
4561
  tooltip: `${label}${shortcutKeys ? ` (${String(parseShortcutKeys({ shortcutKeys })).replace(/Mod/i, "Ctrl").replace(/,/g, " + ")})` : ""}`,
4562
+ onPointerDown: handlePointerDown,
4507
4563
  onClick: handleClick,
4508
4564
  ...buttonProps,
4509
4565
  ref,
@@ -7091,7 +7147,15 @@ function ColorPicker({ type = "text" }) {
7091
7147
  const [tempHex, setTempHex] = (0, import_react87.useState)("#000000");
7092
7148
  const [canApply, setCanApply] = (0, import_react87.useState)(false);
7093
7149
  (0, import_react87.useEffect)(() => {
7094
- const current = type === "text" ? editor?.getAttributes("textStyle").color || "#000000" : editor?.getAttributes("highlight")?.color || "#FFFF00";
7150
+ if (!editor) return;
7151
+ let current = editor.getAttributes("textStyle").color;
7152
+ if (!current) {
7153
+ const { $from } = editor.state.selection;
7154
+ current = $from.parent.attrs.color;
7155
+ }
7156
+ if (!current) {
7157
+ current = type === "text" ? "#000000" : "#FFFF00";
7158
+ }
7095
7159
  setColor(current);
7096
7160
  setTempHex(current);
7097
7161
  }, [editor, type]);
@@ -7114,14 +7178,37 @@ function ColorPicker({ type = "text" }) {
7114
7178
  const applyColor = import_react89.default.useCallback(
7115
7179
  (value) => {
7116
7180
  if (!editor) return;
7117
- if (editor.state.storedMarks) {
7118
- const textStyleMark = editor.schema.marks.textStyle;
7119
- if (textStyleMark) {
7120
- editor.view.dispatch(editor.state.tr.removeStoredMark(textStyleMark));
7121
- }
7181
+ const { state } = editor;
7182
+ const { $from, $to, empty } = state.selection;
7183
+ editor.chain().focus().setColor(value).run();
7184
+ const depth = $from.depth;
7185
+ const nodePos = $from.before(depth);
7186
+ const node = $from.node(depth);
7187
+ const supportsColor = [
7188
+ "paragraph",
7189
+ "heading",
7190
+ "listItem",
7191
+ "taskItem",
7192
+ "blockquote"
7193
+ ].includes(node.type.name);
7194
+ if (supportsColor) {
7195
+ const tr = state.tr.setNodeMarkup(nodePos, void 0, {
7196
+ ...node.attrs,
7197
+ color: value
7198
+ });
7199
+ editor.view.dispatch(tr);
7122
7200
  }
7123
7201
  setTimeout(() => {
7124
- editor.chain().focus().setColor(value).run();
7202
+ const newState = editor.state;
7203
+ const storedMarks = newState.storedMarks || newState.selection.$from.marks();
7204
+ const filteredMarks = storedMarks.filter((mark) => mark.type.name !== "textStyle");
7205
+ const newMarks = [
7206
+ ...filteredMarks,
7207
+ newState.schema.marks.textStyle.create({ color: value })
7208
+ ];
7209
+ editor.view.dispatch(
7210
+ newState.tr.setStoredMarks(newMarks)
7211
+ );
7125
7212
  setColor(value);
7126
7213
  }, 0);
7127
7214
  },
@@ -7166,7 +7253,7 @@ function ColorPicker({ type = "text" }) {
7166
7253
  applyColor(c);
7167
7254
  setOpen(false);
7168
7255
  },
7169
- className: "w-5 h-5 rounded-lg cursor-pointer border border-black/10 hover:scale-105 transition",
7256
+ className: "w-5 h-5 rounded-lg cursor-pointer border border-black/10 hover:scale-105 transition",
7170
7257
  style: { backgroundColor: c }
7171
7258
  },
7172
7259
  c
@@ -7184,8 +7271,7 @@ function ColorPicker({ type = "text" }) {
7184
7271
  }
7185
7272
  )
7186
7273
  ] }),
7187
- showCustom && // stop mouse/pointer events from bubbling so popover doesn't treat them as outside clicks
7188
- /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(
7274
+ showCustom && /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(
7189
7275
  "div",
7190
7276
  {
7191
7277
  className: "flex flex-col gap-3",
@@ -7627,150 +7713,71 @@ var StylePersistence = import_core4.Extension.create({
7627
7713
  };
7628
7714
  },
7629
7715
  onSelectionUpdate({ editor }) {
7630
- const { state } = editor;
7631
7716
  const excludedMarks = this.options.excludedMarks;
7632
- const hasExcludedMarks = () => {
7633
- const { $from } = state.selection;
7634
- const marks = $from.marks();
7635
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7636
- };
7637
- const isInCodeBlock = () => {
7638
- const { $from } = state.selection;
7639
- for (let depth = $from.depth; depth > 0; depth--) {
7640
- const node = $from.node(depth);
7641
- if (node.type.name === "codeBlock") {
7642
- return true;
7643
- }
7644
- }
7645
- return false;
7646
- };
7647
- if (hasExcludedMarks() || isInCodeBlock()) {
7648
- return;
7649
- }
7650
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7651
- const blockColor = state.selection.$from.parent.attrs.color;
7652
- const existingTextStyleMark = state.selection.$from.marks().find(
7653
- (mark) => mark.type.name === "textStyle"
7654
- );
7655
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7656
- const markColor = existingTextStyleMark?.attrs.color;
7657
- const finalFont = markFont || blockFont;
7658
- const finalColor = markColor || blockColor;
7659
- if (!finalFont && !finalColor) return;
7660
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7661
- const filteredMarks = storedMarks.filter(
7662
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7663
- );
7664
- const textStyleAttrs = {};
7665
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7666
- if (finalColor) textStyleAttrs.color = finalColor;
7667
- const newMarks = [
7668
- ...filteredMarks,
7669
- state.schema.marks.textStyle.create(textStyleAttrs)
7670
- ];
7671
- editor.view.dispatch(
7672
- state.tr.setStoredMarks(newMarks)
7673
- );
7717
+ syncStoredMarks(editor, excludedMarks);
7674
7718
  },
7675
7719
  onFocus({ editor }) {
7676
- const { state } = editor;
7677
7720
  const excludedMarks = this.options.excludedMarks;
7678
- const hasExcludedMarks = () => {
7679
- const { $from } = state.selection;
7680
- const marks = $from.marks();
7681
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7682
- };
7683
- const isInCodeBlock = () => {
7684
- const { $from } = state.selection;
7685
- for (let depth = $from.depth; depth > 0; depth--) {
7686
- const node = $from.node(depth);
7687
- if (node.type.name === "codeBlock") {
7688
- return true;
7689
- }
7690
- }
7691
- return false;
7692
- };
7693
- if (hasExcludedMarks() || isInCodeBlock()) {
7694
- return;
7695
- }
7696
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7697
- const blockColor = state.selection.$from.parent.attrs.color;
7698
- const existingTextStyleMark = state.selection.$from.marks().find(
7699
- (mark) => mark.type.name === "textStyle"
7700
- );
7701
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7702
- const markColor = existingTextStyleMark?.attrs.color;
7703
- const finalFont = markFont || blockFont;
7704
- const finalColor = markColor || blockColor;
7705
- if (!finalFont && !finalColor) return;
7706
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7707
- const filteredMarks = storedMarks.filter(
7708
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7709
- );
7710
- const textStyleAttrs = {};
7711
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7712
- if (finalColor) textStyleAttrs.color = finalColor;
7713
- const newMarks = [
7714
- ...filteredMarks,
7715
- state.schema.marks.textStyle.create(textStyleAttrs)
7716
- ];
7717
- editor.view.dispatch(
7718
- state.tr.setStoredMarks(newMarks)
7719
- );
7721
+ syncStoredMarks(editor, excludedMarks);
7722
+ },
7723
+ onCreate({ editor }) {
7724
+ const excludedMarks = this.options.excludedMarks;
7725
+ syncStoredMarks(editor, excludedMarks);
7720
7726
  },
7721
- // CRITICAL: Handle transaction updates to catch any textStyle changes
7722
7727
  onTransaction({ editor, transaction }) {
7723
7728
  if (!transaction.selectionSet && !transaction.docChanged) return;
7724
- const { state } = editor;
7725
7729
  const excludedMarks = this.options.excludedMarks;
7726
- const hasExcludedMarks = () => {
7727
- const { $from } = state.selection;
7728
- const marks = $from.marks();
7729
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7730
- };
7731
- const isInCodeBlock = () => {
7732
- const { $from } = state.selection;
7733
- for (let depth = $from.depth; depth > 0; depth--) {
7734
- const node = $from.node(depth);
7735
- if (node.type.name === "codeBlock") {
7736
- return true;
7737
- }
7730
+ requestAnimationFrame(() => {
7731
+ if (!editor.isDestroyed) {
7732
+ syncStoredMarks(editor, excludedMarks);
7738
7733
  }
7739
- return false;
7740
- };
7741
- if (hasExcludedMarks() || isInCodeBlock()) {
7742
- return;
7743
- }
7744
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7745
- const blockColor = state.selection.$from.parent.attrs.color;
7746
- const existingTextStyleMark = state.selection.$from.marks().find(
7747
- (mark) => mark.type.name === "textStyle"
7748
- );
7749
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7750
- const markColor = existingTextStyleMark?.attrs.color;
7751
- const finalFont = markFont || blockFont;
7752
- const finalColor = markColor || blockColor;
7753
- if (!finalFont && !finalColor) return;
7754
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7755
- const existingStoredTextStyle = storedMarks.find((mark) => mark.type.name === "textStyle");
7756
- const hasCorrectFont = !finalFont || existingStoredTextStyle?.attrs.fontFamily === finalFont;
7757
- const hasCorrectColor = !finalColor || existingStoredTextStyle?.attrs.color === finalColor;
7758
- if (hasCorrectFont && hasCorrectColor && existingStoredTextStyle) return;
7759
- const filteredMarks = storedMarks.filter(
7760
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7761
- );
7762
- const textStyleAttrs = {};
7763
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7764
- if (finalColor) textStyleAttrs.color = finalColor;
7765
- const newMarks = [
7766
- ...filteredMarks,
7767
- state.schema.marks.textStyle.create(textStyleAttrs)
7768
- ];
7769
- editor.view.dispatch(
7770
- state.tr.setStoredMarks(newMarks)
7771
- );
7734
+ });
7772
7735
  }
7773
7736
  });
7737
+ function syncStoredMarks(editor, excludedMarks) {
7738
+ const { state } = editor;
7739
+ const { $from } = state.selection;
7740
+ const marks = $from.marks();
7741
+ const hasExcludedMarks = marks.some(
7742
+ (mark) => excludedMarks.includes(mark.type.name)
7743
+ );
7744
+ let isInCodeBlock = false;
7745
+ for (let depth = $from.depth; depth > 0; depth--) {
7746
+ const node = $from.node(depth);
7747
+ if (node.type.name === "codeBlock") {
7748
+ isInCodeBlock = true;
7749
+ break;
7750
+ }
7751
+ }
7752
+ if (hasExcludedMarks || isInCodeBlock) {
7753
+ return;
7754
+ }
7755
+ const parentNode = $from.parent;
7756
+ const blockFont = parentNode.attrs.fontFamily;
7757
+ const blockColor = parentNode.attrs.color;
7758
+ if (!blockFont && !blockColor) {
7759
+ return;
7760
+ }
7761
+ const storedMarks = state.storedMarks || $from.marks();
7762
+ const existingTextStyle = storedMarks.find((mark) => mark.type.name === "textStyle");
7763
+ const needsUpdate = !existingTextStyle || blockFont && existingTextStyle.attrs.fontFamily !== blockFont || blockColor && existingTextStyle.attrs.color !== blockColor;
7764
+ if (!needsUpdate) {
7765
+ return;
7766
+ }
7767
+ const filteredMarks = storedMarks.filter(
7768
+ (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7769
+ );
7770
+ const textStyleAttrs = {};
7771
+ if (blockFont) textStyleAttrs.fontFamily = blockFont;
7772
+ if (blockColor) textStyleAttrs.color = blockColor;
7773
+ const newMarks = [
7774
+ ...filteredMarks,
7775
+ state.schema.marks.textStyle.create(textStyleAttrs)
7776
+ ];
7777
+ editor.view.dispatch(
7778
+ state.tr.setStoredMarks(newMarks)
7779
+ );
7780
+ }
7774
7781
 
7775
7782
  // src/components/tiptap-templates/simple/simple-editor.tsx
7776
7783
  var import_jsx_runtime81 = require("react/jsx-runtime");