@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.mjs CHANGED
@@ -4423,6 +4423,56 @@ function ListDropdownMenu({
4423
4423
 
4424
4424
  // src/components/tiptap-ui/blockquote-button/blockquote-button.tsx
4425
4425
  import { forwardRef as forwardRef12, useCallback as useCallback16 } from "react";
4426
+
4427
+ // src/hooks/mark-preservers/mark-preserver.ts
4428
+ var MarkPreserver = class {
4429
+ static preservedMarks = /* @__PURE__ */ new Map();
4430
+ /**
4431
+ * Preserve marks before an action that will cause blur
4432
+ * Call this in onMouseDown or onPointerDown events
4433
+ */
4434
+ static preserve(editor) {
4435
+ if (!editor) return;
4436
+ const { state } = editor;
4437
+ const { $from } = state.selection;
4438
+ const currentMarks = state.storedMarks || $from.marks();
4439
+ if (currentMarks.length > 0) {
4440
+ this.preservedMarks.set(editor, [...currentMarks]);
4441
+ }
4442
+ }
4443
+ /**
4444
+ * Restore marks after an action completes
4445
+ * Call this after the editor action in onClick or in a setTimeout
4446
+ */
4447
+ static restore(editor, delay = 0) {
4448
+ if (!editor) return;
4449
+ const marks = this.preservedMarks.get(editor);
4450
+ if (!marks) return;
4451
+ const restoreFn = () => {
4452
+ if (editor.isDestroyed) {
4453
+ this.preservedMarks.delete(editor);
4454
+ return;
4455
+ }
4456
+ const { state } = editor;
4457
+ editor.view.dispatch(state.tr.setStoredMarks(marks));
4458
+ this.preservedMarks.delete(editor);
4459
+ editor.commands.focus();
4460
+ };
4461
+ if (delay > 0) {
4462
+ setTimeout(restoreFn, delay);
4463
+ } else {
4464
+ restoreFn();
4465
+ }
4466
+ }
4467
+ /**
4468
+ * Clear preserved marks for an editor
4469
+ */
4470
+ static clear(editor) {
4471
+ this.preservedMarks.delete(editor);
4472
+ }
4473
+ };
4474
+
4475
+ // src/components/tiptap-ui/blockquote-button/blockquote-button.tsx
4426
4476
  import { Fragment as Fragment7, jsx as jsx45, jsxs as jsxs26 } from "react/jsx-runtime";
4427
4477
  function BlockquoteShortcutBadge({
4428
4478
  shortcutKeys = BLOCKQUOTE_SHORTCUT_KEY
@@ -4458,10 +4508,15 @@ var BlockquoteButton = forwardRef12(
4458
4508
  (event) => {
4459
4509
  onClick?.(event);
4460
4510
  if (event.defaultPrevented) return;
4511
+ MarkPreserver.preserve(editor);
4461
4512
  handleToggle();
4513
+ MarkPreserver.restore(editor, 10);
4462
4514
  },
4463
- [handleToggle, onClick]
4515
+ [handleToggle, onClick, editor]
4464
4516
  );
4517
+ const handlePointerDown = useCallback16(() => {
4518
+ MarkPreserver.preserve(editor);
4519
+ }, [editor]);
4465
4520
  if (!isVisible) {
4466
4521
  return null;
4467
4522
  }
@@ -4478,6 +4533,7 @@ var BlockquoteButton = forwardRef12(
4478
4533
  "aria-label": label,
4479
4534
  "aria-pressed": isActive,
4480
4535
  tooltip: `${label}${shortcutKeys ? ` (${String(parseShortcutKeys({ shortcutKeys })).replace(/Mod/i, "Ctrl").replace(/,/g, " + ")})` : ""}`,
4536
+ onPointerDown: handlePointerDown,
4481
4537
  onClick: handleClick,
4482
4538
  ...buttonProps,
4483
4539
  ref,
@@ -7065,7 +7121,15 @@ function ColorPicker({ type = "text" }) {
7065
7121
  const [tempHex, setTempHex] = useState29("#000000");
7066
7122
  const [canApply, setCanApply] = useState29(false);
7067
7123
  useEffect19(() => {
7068
- const current = type === "text" ? editor?.getAttributes("textStyle").color || "#000000" : editor?.getAttributes("highlight")?.color || "#FFFF00";
7124
+ if (!editor) return;
7125
+ let current = editor.getAttributes("textStyle").color;
7126
+ if (!current) {
7127
+ const { $from } = editor.state.selection;
7128
+ current = $from.parent.attrs.color;
7129
+ }
7130
+ if (!current) {
7131
+ current = type === "text" ? "#000000" : "#FFFF00";
7132
+ }
7069
7133
  setColor(current);
7070
7134
  setTempHex(current);
7071
7135
  }, [editor, type]);
@@ -7088,14 +7152,37 @@ function ColorPicker({ type = "text" }) {
7088
7152
  const applyColor = React4.useCallback(
7089
7153
  (value) => {
7090
7154
  if (!editor) return;
7091
- if (editor.state.storedMarks) {
7092
- const textStyleMark = editor.schema.marks.textStyle;
7093
- if (textStyleMark) {
7094
- editor.view.dispatch(editor.state.tr.removeStoredMark(textStyleMark));
7095
- }
7155
+ const { state } = editor;
7156
+ const { $from, $to, empty } = state.selection;
7157
+ editor.chain().focus().setColor(value).run();
7158
+ const depth = $from.depth;
7159
+ const nodePos = $from.before(depth);
7160
+ const node = $from.node(depth);
7161
+ const supportsColor = [
7162
+ "paragraph",
7163
+ "heading",
7164
+ "listItem",
7165
+ "taskItem",
7166
+ "blockquote"
7167
+ ].includes(node.type.name);
7168
+ if (supportsColor) {
7169
+ const tr = state.tr.setNodeMarkup(nodePos, void 0, {
7170
+ ...node.attrs,
7171
+ color: value
7172
+ });
7173
+ editor.view.dispatch(tr);
7096
7174
  }
7097
7175
  setTimeout(() => {
7098
- editor.chain().focus().setColor(value).run();
7176
+ const newState = editor.state;
7177
+ const storedMarks = newState.storedMarks || newState.selection.$from.marks();
7178
+ const filteredMarks = storedMarks.filter((mark) => mark.type.name !== "textStyle");
7179
+ const newMarks = [
7180
+ ...filteredMarks,
7181
+ newState.schema.marks.textStyle.create({ color: value })
7182
+ ];
7183
+ editor.view.dispatch(
7184
+ newState.tr.setStoredMarks(newMarks)
7185
+ );
7099
7186
  setColor(value);
7100
7187
  }, 0);
7101
7188
  },
@@ -7140,7 +7227,7 @@ function ColorPicker({ type = "text" }) {
7140
7227
  applyColor(c);
7141
7228
  setOpen(false);
7142
7229
  },
7143
- className: "w-5 h-5 rounded-lg cursor-pointer border border-black/10 hover:scale-105 transition",
7230
+ className: "w-5 h-5 rounded-lg cursor-pointer border border-black/10 hover:scale-105 transition",
7144
7231
  style: { backgroundColor: c }
7145
7232
  },
7146
7233
  c
@@ -7158,8 +7245,7 @@ function ColorPicker({ type = "text" }) {
7158
7245
  }
7159
7246
  )
7160
7247
  ] }),
7161
- showCustom && // stop mouse/pointer events from bubbling so popover doesn't treat them as outside clicks
7162
- /* @__PURE__ */ jsxs46(
7248
+ showCustom && /* @__PURE__ */ jsxs46(
7163
7249
  "div",
7164
7250
  {
7165
7251
  className: "flex flex-col gap-3",
@@ -7601,150 +7687,71 @@ var StylePersistence = Extension2.create({
7601
7687
  };
7602
7688
  },
7603
7689
  onSelectionUpdate({ editor }) {
7604
- const { state } = editor;
7605
7690
  const excludedMarks = this.options.excludedMarks;
7606
- const hasExcludedMarks = () => {
7607
- const { $from } = state.selection;
7608
- const marks = $from.marks();
7609
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7610
- };
7611
- const isInCodeBlock = () => {
7612
- const { $from } = state.selection;
7613
- for (let depth = $from.depth; depth > 0; depth--) {
7614
- const node = $from.node(depth);
7615
- if (node.type.name === "codeBlock") {
7616
- return true;
7617
- }
7618
- }
7619
- return false;
7620
- };
7621
- if (hasExcludedMarks() || isInCodeBlock()) {
7622
- return;
7623
- }
7624
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7625
- const blockColor = state.selection.$from.parent.attrs.color;
7626
- const existingTextStyleMark = state.selection.$from.marks().find(
7627
- (mark) => mark.type.name === "textStyle"
7628
- );
7629
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7630
- const markColor = existingTextStyleMark?.attrs.color;
7631
- const finalFont = markFont || blockFont;
7632
- const finalColor = markColor || blockColor;
7633
- if (!finalFont && !finalColor) return;
7634
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7635
- const filteredMarks = storedMarks.filter(
7636
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7637
- );
7638
- const textStyleAttrs = {};
7639
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7640
- if (finalColor) textStyleAttrs.color = finalColor;
7641
- const newMarks = [
7642
- ...filteredMarks,
7643
- state.schema.marks.textStyle.create(textStyleAttrs)
7644
- ];
7645
- editor.view.dispatch(
7646
- state.tr.setStoredMarks(newMarks)
7647
- );
7691
+ syncStoredMarks(editor, excludedMarks);
7648
7692
  },
7649
7693
  onFocus({ editor }) {
7650
- const { state } = editor;
7651
7694
  const excludedMarks = this.options.excludedMarks;
7652
- const hasExcludedMarks = () => {
7653
- const { $from } = state.selection;
7654
- const marks = $from.marks();
7655
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7656
- };
7657
- const isInCodeBlock = () => {
7658
- const { $from } = state.selection;
7659
- for (let depth = $from.depth; depth > 0; depth--) {
7660
- const node = $from.node(depth);
7661
- if (node.type.name === "codeBlock") {
7662
- return true;
7663
- }
7664
- }
7665
- return false;
7666
- };
7667
- if (hasExcludedMarks() || isInCodeBlock()) {
7668
- return;
7669
- }
7670
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7671
- const blockColor = state.selection.$from.parent.attrs.color;
7672
- const existingTextStyleMark = state.selection.$from.marks().find(
7673
- (mark) => mark.type.name === "textStyle"
7674
- );
7675
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7676
- const markColor = existingTextStyleMark?.attrs.color;
7677
- const finalFont = markFont || blockFont;
7678
- const finalColor = markColor || blockColor;
7679
- if (!finalFont && !finalColor) return;
7680
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7681
- const filteredMarks = storedMarks.filter(
7682
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7683
- );
7684
- const textStyleAttrs = {};
7685
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7686
- if (finalColor) textStyleAttrs.color = finalColor;
7687
- const newMarks = [
7688
- ...filteredMarks,
7689
- state.schema.marks.textStyle.create(textStyleAttrs)
7690
- ];
7691
- editor.view.dispatch(
7692
- state.tr.setStoredMarks(newMarks)
7693
- );
7695
+ syncStoredMarks(editor, excludedMarks);
7696
+ },
7697
+ onCreate({ editor }) {
7698
+ const excludedMarks = this.options.excludedMarks;
7699
+ syncStoredMarks(editor, excludedMarks);
7694
7700
  },
7695
- // CRITICAL: Handle transaction updates to catch any textStyle changes
7696
7701
  onTransaction({ editor, transaction }) {
7697
7702
  if (!transaction.selectionSet && !transaction.docChanged) return;
7698
- const { state } = editor;
7699
7703
  const excludedMarks = this.options.excludedMarks;
7700
- const hasExcludedMarks = () => {
7701
- const { $from } = state.selection;
7702
- const marks = $from.marks();
7703
- return marks.some((mark) => excludedMarks.includes(mark.type.name));
7704
- };
7705
- const isInCodeBlock = () => {
7706
- const { $from } = state.selection;
7707
- for (let depth = $from.depth; depth > 0; depth--) {
7708
- const node = $from.node(depth);
7709
- if (node.type.name === "codeBlock") {
7710
- return true;
7711
- }
7704
+ requestAnimationFrame(() => {
7705
+ if (!editor.isDestroyed) {
7706
+ syncStoredMarks(editor, excludedMarks);
7712
7707
  }
7713
- return false;
7714
- };
7715
- if (hasExcludedMarks() || isInCodeBlock()) {
7716
- return;
7717
- }
7718
- const blockFont = state.selection.$from.parent.attrs.fontFamily;
7719
- const blockColor = state.selection.$from.parent.attrs.color;
7720
- const existingTextStyleMark = state.selection.$from.marks().find(
7721
- (mark) => mark.type.name === "textStyle"
7722
- );
7723
- const markFont = existingTextStyleMark?.attrs.fontFamily;
7724
- const markColor = existingTextStyleMark?.attrs.color;
7725
- const finalFont = markFont || blockFont;
7726
- const finalColor = markColor || blockColor;
7727
- if (!finalFont && !finalColor) return;
7728
- const storedMarks = state.storedMarks || state.selection.$from.marks();
7729
- const existingStoredTextStyle = storedMarks.find((mark) => mark.type.name === "textStyle");
7730
- const hasCorrectFont = !finalFont || existingStoredTextStyle?.attrs.fontFamily === finalFont;
7731
- const hasCorrectColor = !finalColor || existingStoredTextStyle?.attrs.color === finalColor;
7732
- if (hasCorrectFont && hasCorrectColor && existingStoredTextStyle) return;
7733
- const filteredMarks = storedMarks.filter(
7734
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7735
- );
7736
- const textStyleAttrs = {};
7737
- if (finalFont) textStyleAttrs.fontFamily = finalFont;
7738
- if (finalColor) textStyleAttrs.color = finalColor;
7739
- const newMarks = [
7740
- ...filteredMarks,
7741
- state.schema.marks.textStyle.create(textStyleAttrs)
7742
- ];
7743
- editor.view.dispatch(
7744
- state.tr.setStoredMarks(newMarks)
7745
- );
7708
+ });
7746
7709
  }
7747
7710
  });
7711
+ function syncStoredMarks(editor, excludedMarks) {
7712
+ const { state } = editor;
7713
+ const { $from } = state.selection;
7714
+ const marks = $from.marks();
7715
+ const hasExcludedMarks = marks.some(
7716
+ (mark) => excludedMarks.includes(mark.type.name)
7717
+ );
7718
+ let isInCodeBlock = false;
7719
+ for (let depth = $from.depth; depth > 0; depth--) {
7720
+ const node = $from.node(depth);
7721
+ if (node.type.name === "codeBlock") {
7722
+ isInCodeBlock = true;
7723
+ break;
7724
+ }
7725
+ }
7726
+ if (hasExcludedMarks || isInCodeBlock) {
7727
+ return;
7728
+ }
7729
+ const parentNode = $from.parent;
7730
+ const blockFont = parentNode.attrs.fontFamily;
7731
+ const blockColor = parentNode.attrs.color;
7732
+ if (!blockFont && !blockColor) {
7733
+ return;
7734
+ }
7735
+ const storedMarks = state.storedMarks || $from.marks();
7736
+ const existingTextStyle = storedMarks.find((mark) => mark.type.name === "textStyle");
7737
+ const needsUpdate = !existingTextStyle || blockFont && existingTextStyle.attrs.fontFamily !== blockFont || blockColor && existingTextStyle.attrs.color !== blockColor;
7738
+ if (!needsUpdate) {
7739
+ return;
7740
+ }
7741
+ const filteredMarks = storedMarks.filter(
7742
+ (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7743
+ );
7744
+ const textStyleAttrs = {};
7745
+ if (blockFont) textStyleAttrs.fontFamily = blockFont;
7746
+ if (blockColor) textStyleAttrs.color = blockColor;
7747
+ const newMarks = [
7748
+ ...filteredMarks,
7749
+ state.schema.marks.textStyle.create(textStyleAttrs)
7750
+ ];
7751
+ editor.view.dispatch(
7752
+ state.tr.setStoredMarks(newMarks)
7753
+ );
7754
+ }
7748
7755
 
7749
7756
  // src/components/tiptap-templates/simple/simple-editor.tsx
7750
7757
  import { Fragment as Fragment12, jsx as jsx81, jsxs as jsxs48 } from "react/jsx-runtime";