@mhamz.01/easyflow-texteditor 0.1.150 → 0.1.152

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
@@ -2900,6 +2900,115 @@ function useTiptapEditor(providedEditor) {
2900
2900
  return editorState || { editor: null };
2901
2901
  }
2902
2902
 
2903
+ // src/hooks/mark-preservers/mark-preserver.ts
2904
+ var MarkPreserver = class {
2905
+ static preservedStyles = /* @__PURE__ */ new Map();
2906
+ /**
2907
+ * Preserve current font and color before blur or node change
2908
+ */
2909
+ static preserve(editor) {
2910
+ if (!editor) return;
2911
+ try {
2912
+ const { state } = editor;
2913
+ const { $from } = state.selection;
2914
+ const currentNode = $from.parent;
2915
+ let font = currentNode.attrs.fontFamily;
2916
+ let color = currentNode.attrs.color;
2917
+ const marks = state.storedMarks || $from.marks();
2918
+ const textStyleMark = marks.find((m) => m.type.name === "textStyle");
2919
+ if (!font && textStyleMark?.attrs.fontFamily) {
2920
+ font = textStyleMark.attrs.fontFamily;
2921
+ }
2922
+ if (!color && textStyleMark?.attrs.color) {
2923
+ color = textStyleMark.attrs.color;
2924
+ }
2925
+ if (font || color) {
2926
+ this.preservedStyles.set(editor, { font, color });
2927
+ }
2928
+ } catch (error) {
2929
+ console.debug("MarkPreserver.preserve error:", error);
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Restore preserved styles to current node
2934
+ */
2935
+ static restore(editor, delay = 0) {
2936
+ if (!editor) return;
2937
+ const styles = this.preservedStyles.get(editor);
2938
+ if (!styles) return;
2939
+ const restoreFn = () => {
2940
+ if (editor.isDestroyed) {
2941
+ this.preservedStyles.delete(editor);
2942
+ return;
2943
+ }
2944
+ try {
2945
+ const { state } = editor;
2946
+ const { $from } = state.selection;
2947
+ const depth = $from.depth;
2948
+ const pos = $from.before(depth);
2949
+ const node = $from.node(depth);
2950
+ const supportsAttributes = [
2951
+ "paragraph",
2952
+ "heading",
2953
+ "listItem",
2954
+ "taskItem",
2955
+ "blockquote"
2956
+ ].includes(node.type.name);
2957
+ if (!supportsAttributes) {
2958
+ this.preservedStyles.delete(editor);
2959
+ editor.commands.focus();
2960
+ return;
2961
+ }
2962
+ const updates = {};
2963
+ if (styles.font && !node.attrs.fontFamily) {
2964
+ updates.fontFamily = styles.font;
2965
+ }
2966
+ if (styles.color && !node.attrs.color) {
2967
+ updates.color = styles.color;
2968
+ }
2969
+ if (Object.keys(updates).length > 0) {
2970
+ const newAttrs = { ...node.attrs, ...updates };
2971
+ editor.view.dispatch(
2972
+ state.tr.setNodeMarkup(pos, void 0, newAttrs)
2973
+ );
2974
+ }
2975
+ const marks = state.storedMarks || $from.marks();
2976
+ const filteredMarks = marks.filter((mark) => mark.type.name !== "textStyle");
2977
+ const textStyleAttrs = {};
2978
+ if (styles.font) textStyleAttrs.fontFamily = styles.font;
2979
+ if (styles.color) textStyleAttrs.color = styles.color;
2980
+ const newMarks = [
2981
+ ...filteredMarks,
2982
+ state.schema.marks.textStyle.create(textStyleAttrs)
2983
+ ];
2984
+ setTimeout(() => {
2985
+ if (!editor.isDestroyed) {
2986
+ editor.view.dispatch(
2987
+ editor.state.tr.setStoredMarks(newMarks)
2988
+ );
2989
+ editor.commands.focus();
2990
+ }
2991
+ }, 0);
2992
+ } catch (error) {
2993
+ console.debug("MarkPreserver.restore error:", error);
2994
+ } finally {
2995
+ this.preservedStyles.delete(editor);
2996
+ }
2997
+ };
2998
+ if (delay > 0) {
2999
+ setTimeout(restoreFn, delay);
3000
+ } else {
3001
+ restoreFn();
3002
+ }
3003
+ }
3004
+ /**
3005
+ * Clear preserved styles
3006
+ */
3007
+ static clear(editor) {
3008
+ this.preservedStyles.delete(editor);
3009
+ }
3010
+ };
3011
+
2903
3012
  // src/components/tiptap-ui/heading-button/heading-button.tsx
2904
3013
  var import_react28 = require("react");
2905
3014
 
@@ -3504,9 +3613,15 @@ var HeadingDropdownMenu = (0, import_react38.forwardRef)(
3504
3613
  if (!editor || !canToggle2) return;
3505
3614
  setIsOpen(open);
3506
3615
  onOpenChange?.(open);
3616
+ if (!open) {
3617
+ MarkPreserver.restore(editor, 10);
3618
+ }
3507
3619
  },
3508
3620
  [canToggle2, editor, onOpenChange]
3509
3621
  );
3622
+ const handlePointerDown = (0, import_react38.useCallback)(() => {
3623
+ MarkPreserver.preserve(editor);
3624
+ }, [editor]);
3510
3625
  if (!isVisible) {
3511
3626
  return null;
3512
3627
  }
@@ -3522,6 +3637,7 @@ var HeadingDropdownMenu = (0, import_react38.forwardRef)(
3522
3637
  "aria-label": "Format text as heading",
3523
3638
  "aria-pressed": isActive,
3524
3639
  tooltip: "Heading",
3640
+ onPointerDown: handlePointerDown,
3525
3641
  ...buttonProps,
3526
3642
  ref,
3527
3643
  children: [
@@ -4409,9 +4525,15 @@ function ListDropdownMenu({
4409
4525
  (open) => {
4410
4526
  setIsOpen(open);
4411
4527
  onOpenChange?.(open);
4528
+ if (!open) {
4529
+ MarkPreserver.restore(editor, 10);
4530
+ }
4412
4531
  },
4413
- [onOpenChange]
4532
+ [onOpenChange, editor]
4414
4533
  );
4534
+ const handlePointerDown = (0, import_react51.useCallback)(() => {
4535
+ MarkPreserver.preserve(editor);
4536
+ }, [editor]);
4415
4537
  if (!isVisible) {
4416
4538
  return null;
4417
4539
  }
@@ -4428,6 +4550,7 @@ function ListDropdownMenu({
4428
4550
  "data-disabled": !canToggle2,
4429
4551
  "aria-label": "List options",
4430
4552
  tooltip: "List",
4553
+ onPointerDown: handlePointerDown,
4431
4554
  ...props,
4432
4555
  children: [
4433
4556
  /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Icon, { className: "tiptap-button-icon" }),
@@ -4449,56 +4572,6 @@ function ListDropdownMenu({
4449
4572
 
4450
4573
  // src/components/tiptap-ui/blockquote-button/blockquote-button.tsx
4451
4574
  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
4502
4575
  var import_jsx_runtime45 = require("react/jsx-runtime");
4503
4576
  function BlockquoteShortcutBadge({
4504
4577
  shortcutKeys = BLOCKQUOTE_SHORTCUT_KEY
@@ -7648,6 +7721,7 @@ function useCursorVisibility({
7648
7721
 
7649
7722
  // src/components/extensions/font-family-block.ts
7650
7723
  var import_core4 = require("@tiptap/core");
7724
+ var import_state5 = require("@tiptap/pm/state");
7651
7725
  var FontFamilyBlock = import_core4.Extension.create({
7652
7726
  name: "fontFamilyBlock",
7653
7727
  addGlobalAttributes() {
@@ -7704,150 +7778,174 @@ var ColorBlock = import_core4.Extension.create({
7704
7778
  ];
7705
7779
  }
7706
7780
  });
7781
+ var stylePersistenceKey = new import_state5.PluginKey("stylePersistence");
7707
7782
  var StylePersistence = import_core4.Extension.create({
7708
7783
  name: "stylePersistence",
7709
7784
  addOptions() {
7710
7785
  return {
7711
- // Marks that should NOT inherit font family or color
7712
7786
  excludedMarks: ["code", "codeBlock"]
7713
7787
  };
7714
7788
  },
7715
- addStorage() {
7716
- return {
7717
- // Store marks temporarily when editor loses focus
7718
- preservedMarks: null,
7719
- // Store marks from the previous node (for Enter key handling)
7720
- previousNodeMarks: null
7721
- };
7722
- },
7723
- addKeyboardShortcuts() {
7724
- return {
7725
- // Handle Enter key to preserve marks when creating new paragraphs
7726
- "Enter": ({ editor }) => {
7727
- const { state } = editor;
7728
- const { $from } = state.selection;
7729
- const currentNode = $from.parent;
7730
- const blockFont = currentNode.attrs.fontFamily;
7731
- const blockColor = currentNode.attrs.color;
7732
- if (blockFont || blockColor) {
7733
- this.storage.previousNodeMarks = { blockFont, blockColor };
7734
- }
7735
- return false;
7736
- },
7737
- // Handle Shift+Enter (soft break) - same logic
7738
- "Shift-Enter": ({ editor }) => {
7739
- const { state } = editor;
7740
- const { $from } = state.selection;
7741
- const currentNode = $from.parent;
7742
- const blockFont = currentNode.attrs.fontFamily;
7743
- const blockColor = currentNode.attrs.color;
7744
- if (blockFont || blockColor) {
7745
- this.storage.previousNodeMarks = { blockFont, blockColor };
7746
- }
7747
- return false;
7748
- }
7749
- };
7750
- },
7751
- onSelectionUpdate({ editor }) {
7789
+ addProseMirrorPlugins() {
7752
7790
  const excludedMarks = this.options.excludedMarks;
7753
- syncStoredMarks(editor, excludedMarks, this.storage);
7754
- },
7755
- onFocus({ editor }) {
7756
- const excludedMarks = this.options.excludedMarks;
7757
- if (this.storage.preservedMarks) {
7758
- editor.view.dispatch(
7759
- editor.state.tr.setStoredMarks(this.storage.preservedMarks)
7760
- );
7761
- this.storage.preservedMarks = null;
7762
- }
7763
- syncStoredMarks(editor, excludedMarks, this.storage);
7764
- },
7765
- onBlur({ editor }) {
7766
- const { state } = editor;
7767
- const { $from } = state.selection;
7768
- const currentMarks = state.storedMarks || $from.marks();
7769
- const hasTextStyle = currentMarks.some((mark) => mark.type.name === "textStyle");
7770
- if (hasTextStyle) {
7771
- this.storage.preservedMarks = currentMarks;
7772
- }
7773
- },
7774
- onCreate({ editor }) {
7775
- const excludedMarks = this.options.excludedMarks;
7776
- syncStoredMarks(editor, excludedMarks, this.storage);
7777
- },
7778
- onTransaction({ editor, transaction }) {
7779
- if (!transaction.selectionSet && !transaction.docChanged) return;
7780
- const excludedMarks = this.options.excludedMarks;
7781
- requestAnimationFrame(() => {
7782
- if (!editor.isDestroyed) {
7783
- syncStoredMarks(editor, excludedMarks, this.storage);
7784
- }
7785
- });
7791
+ return [
7792
+ new import_state5.Plugin({
7793
+ key: stylePersistenceKey,
7794
+ state: {
7795
+ init() {
7796
+ return {
7797
+ lastFont: null,
7798
+ lastColor: null
7799
+ };
7800
+ },
7801
+ apply(tr, value, oldState, newState) {
7802
+ const { $from } = newState.selection;
7803
+ const marks = $from.marks();
7804
+ const hasExcludedMarks = marks.some(
7805
+ (mark) => excludedMarks.includes(mark.type.name)
7806
+ );
7807
+ let isInCodeBlock = false;
7808
+ for (let depth = $from.depth; depth > 0; depth--) {
7809
+ const node = $from.node(depth);
7810
+ if (node.type.name === "codeBlock") {
7811
+ isInCodeBlock = true;
7812
+ break;
7813
+ }
7814
+ }
7815
+ if (hasExcludedMarks || isInCodeBlock) {
7816
+ return value;
7817
+ }
7818
+ const parentNode = $from.parent;
7819
+ let font = parentNode.attrs.fontFamily;
7820
+ let color = parentNode.attrs.color;
7821
+ const textStyleMark = marks.find((m) => m.type.name === "textStyle");
7822
+ if (!font && textStyleMark?.attrs.fontFamily) {
7823
+ font = textStyleMark.attrs.fontFamily;
7824
+ }
7825
+ if (!color && textStyleMark?.attrs.color) {
7826
+ color = textStyleMark.attrs.color;
7827
+ }
7828
+ return {
7829
+ lastFont: font || value.lastFont,
7830
+ lastColor: color || value.lastColor
7831
+ };
7832
+ }
7833
+ },
7834
+ appendTransaction(transactions, oldState, newState) {
7835
+ if (!transactions.some((tr2) => tr2.docChanged)) {
7836
+ return null;
7837
+ }
7838
+ const pluginState = stylePersistenceKey.getState(newState);
7839
+ if (!pluginState.lastFont && !pluginState.lastColor) {
7840
+ return null;
7841
+ }
7842
+ const { $from } = newState.selection;
7843
+ const parentNode = $from.parent;
7844
+ const marks = $from.marks();
7845
+ const hasExcludedMarks = marks.some(
7846
+ (mark) => excludedMarks.includes(mark.type.name)
7847
+ );
7848
+ let isInCodeBlock = false;
7849
+ for (let depth = $from.depth; depth > 0; depth--) {
7850
+ const node = $from.node(depth);
7851
+ if (node.type.name === "codeBlock") {
7852
+ isInCodeBlock = true;
7853
+ break;
7854
+ }
7855
+ }
7856
+ if (hasExcludedMarks || isInCodeBlock) {
7857
+ return null;
7858
+ }
7859
+ const supportsAttributes = [
7860
+ "paragraph",
7861
+ "heading",
7862
+ "listItem",
7863
+ "taskItem",
7864
+ "blockquote"
7865
+ ].includes(parentNode.type.name);
7866
+ if (!supportsAttributes) {
7867
+ return null;
7868
+ }
7869
+ const currentFont = parentNode.attrs.fontFamily;
7870
+ const currentColor = parentNode.attrs.color;
7871
+ const needsFont = !currentFont && pluginState.lastFont;
7872
+ const needsColor = !currentColor && pluginState.lastColor;
7873
+ if (!needsFont && !needsColor) {
7874
+ return null;
7875
+ }
7876
+ const tr = newState.tr;
7877
+ try {
7878
+ const depth = $from.depth;
7879
+ const pos = $from.before(depth);
7880
+ const node = $from.node(depth);
7881
+ const newAttrs = { ...node.attrs };
7882
+ if (needsFont) newAttrs.fontFamily = pluginState.lastFont;
7883
+ if (needsColor) newAttrs.color = pluginState.lastColor;
7884
+ tr.setNodeMarkup(pos, void 0, newAttrs);
7885
+ const storedMarks = newState.storedMarks || $from.marks();
7886
+ const filteredMarks = storedMarks.filter(
7887
+ (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7888
+ );
7889
+ const textStyleAttrs = {};
7890
+ if (pluginState.lastFont) textStyleAttrs.fontFamily = pluginState.lastFont;
7891
+ if (pluginState.lastColor) textStyleAttrs.color = pluginState.lastColor;
7892
+ const newMarks = [
7893
+ ...filteredMarks,
7894
+ newState.schema.marks.textStyle.create(textStyleAttrs)
7895
+ ];
7896
+ tr.setStoredMarks(newMarks);
7897
+ return tr;
7898
+ } catch (error) {
7899
+ console.error("StylePersistence error:", error);
7900
+ return null;
7901
+ }
7902
+ },
7903
+ props: {
7904
+ handleKeyDown(view, event) {
7905
+ if (event.key === "Enter") {
7906
+ const { state } = view;
7907
+ const { $from } = state.selection;
7908
+ const marks = $from.marks();
7909
+ const hasExcludedMarks = marks.some(
7910
+ (mark) => excludedMarks.includes(mark.type.name)
7911
+ );
7912
+ if (hasExcludedMarks) {
7913
+ return false;
7914
+ }
7915
+ let isInCodeBlock = false;
7916
+ for (let depth = $from.depth; depth > 0; depth--) {
7917
+ const node = $from.node(depth);
7918
+ if (node.type.name === "codeBlock") {
7919
+ isInCodeBlock = true;
7920
+ break;
7921
+ }
7922
+ }
7923
+ if (isInCodeBlock) {
7924
+ return false;
7925
+ }
7926
+ const parentNode = $from.parent;
7927
+ let font = parentNode.attrs.fontFamily;
7928
+ let color = parentNode.attrs.color;
7929
+ const textStyleMark = marks.find((m) => m.type.name === "textStyle");
7930
+ if (!font && textStyleMark?.attrs.fontFamily) {
7931
+ font = textStyleMark.attrs.fontFamily;
7932
+ }
7933
+ if (!color && textStyleMark?.attrs.color) {
7934
+ color = textStyleMark.attrs.color;
7935
+ }
7936
+ if (font || color) {
7937
+ const pluginState = stylePersistenceKey.getState(state);
7938
+ pluginState.lastFont = font || pluginState.lastFont;
7939
+ pluginState.lastColor = color || pluginState.lastColor;
7940
+ }
7941
+ }
7942
+ return false;
7943
+ }
7944
+ }
7945
+ })
7946
+ ];
7786
7947
  }
7787
7948
  });
7788
- function syncStoredMarks(editor, excludedMarks, storage) {
7789
- const { state } = editor;
7790
- const { $from } = state.selection;
7791
- const marks = $from.marks();
7792
- const hasExcludedMarks = marks.some(
7793
- (mark) => excludedMarks.includes(mark.type.name)
7794
- );
7795
- let isInCodeBlock = false;
7796
- for (let depth = $from.depth; depth > 0; depth--) {
7797
- const node = $from.node(depth);
7798
- if (node.type.name === "codeBlock") {
7799
- isInCodeBlock = true;
7800
- break;
7801
- }
7802
- }
7803
- if (hasExcludedMarks || isInCodeBlock) {
7804
- return;
7805
- }
7806
- const parentNode = $from.parent;
7807
- let blockFont = parentNode.attrs.fontFamily;
7808
- let blockColor = parentNode.attrs.color;
7809
- if ((!blockFont || !blockColor) && storage?.previousNodeMarks) {
7810
- if (!blockFont && storage.previousNodeMarks.blockFont) {
7811
- blockFont = storage.previousNodeMarks.blockFont;
7812
- }
7813
- if (!blockColor && storage.previousNodeMarks.blockColor) {
7814
- blockColor = storage.previousNodeMarks.blockColor;
7815
- }
7816
- if (blockFont || blockColor) {
7817
- const depth = $from.depth;
7818
- const nodePos = $from.before(depth);
7819
- const node = $from.node(depth);
7820
- const newAttrs = { ...node.attrs };
7821
- if (blockFont) newAttrs.fontFamily = blockFont;
7822
- if (blockColor) newAttrs.color = blockColor;
7823
- const tr = state.tr.setNodeMarkup(nodePos, void 0, newAttrs);
7824
- editor.view.dispatch(tr);
7825
- }
7826
- storage.previousNodeMarks = null;
7827
- }
7828
- if (!blockFont && !blockColor) {
7829
- return;
7830
- }
7831
- const storedMarks = state.storedMarks || $from.marks();
7832
- const existingTextStyle = storedMarks.find((mark) => mark.type.name === "textStyle");
7833
- const needsUpdate = !existingTextStyle || blockFont && existingTextStyle.attrs.fontFamily !== blockFont || blockColor && existingTextStyle.attrs.color !== blockColor;
7834
- if (!needsUpdate) {
7835
- return;
7836
- }
7837
- const filteredMarks = storedMarks.filter(
7838
- (mark) => mark.type.name !== "textStyle" && !excludedMarks.includes(mark.type.name)
7839
- );
7840
- const textStyleAttrs = {};
7841
- if (blockFont) textStyleAttrs.fontFamily = blockFont;
7842
- if (blockColor) textStyleAttrs.color = blockColor;
7843
- const newMarks = [
7844
- ...filteredMarks,
7845
- state.schema.marks.textStyle.create(textStyleAttrs)
7846
- ];
7847
- editor.view.dispatch(
7848
- state.tr.setStoredMarks(newMarks)
7849
- );
7850
- }
7851
7949
 
7852
7950
  // src/components/tiptap-templates/simple/simple-editor.tsx
7853
7951
  var import_jsx_runtime81 = require("react/jsx-runtime");