@antscorp/antsomi-ui 1.3.7-beta.21 → 1.3.7-beta.23

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.
Files changed (58) hide show
  1. package/es/components/molecules/EmojiPopover/styled.d.ts +2 -2
  2. package/es/components/molecules/EmojiPopover/styled.js +1 -1
  3. package/es/components/molecules/VirtualizedMenu/VirtualizedMenu.d.ts +1 -0
  4. package/es/components/molecules/VirtualizedMenu/components/Item/Item.js +3 -7
  5. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.d.ts +1 -0
  6. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.js +7 -1
  7. package/es/components/molecules/VirtualizedMenu/styled.js +14 -3
  8. package/es/components/molecules/VirtualizedMenu/types.d.ts +1 -0
  9. package/es/components/molecules/VirtualizedMenu/utils.d.ts +1 -0
  10. package/es/components/molecules/VirtualizedMenu/utils.js +1 -0
  11. package/es/components/organism/TextEditor/TextEditor.d.ts +2 -1
  12. package/es/components/organism/TextEditor/TextEditor.js +91 -32
  13. package/es/components/organism/TextEditor/constants.d.ts +9 -0
  14. package/es/components/organism/TextEditor/constants.js +66 -0
  15. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.d.ts +6 -128
  16. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.js +7 -293
  17. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.d.ts +1 -1
  18. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.js +4 -0
  19. package/es/components/organism/TextEditor/hooks/useColorSet.js +7 -0
  20. package/es/components/organism/TextEditor/index.d.ts +1 -0
  21. package/es/components/organism/TextEditor/index.scss +4 -7
  22. package/es/components/organism/TextEditor/provider.d.ts +1 -0
  23. package/es/components/organism/TextEditor/provider.js +6 -3
  24. package/es/components/organism/TextEditor/store.d.ts +11 -4
  25. package/es/components/organism/TextEditor/store.js +22 -2
  26. package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.d.ts +111 -0
  27. package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.js +82 -0
  28. package/es/components/organism/TextEditor/stories/shared.d.ts +64 -0
  29. package/es/components/organism/TextEditor/stories/shared.js +57 -0
  30. package/es/components/organism/TextEditor/styled.d.ts +1 -1
  31. package/es/components/organism/TextEditor/types.d.ts +66 -6
  32. package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.d.ts +2 -1
  33. package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.js +34 -50
  34. package/es/components/organism/TextEditor/ui/Emoji/EmojiList.js +1 -1
  35. package/es/components/organism/TextEditor/ui/Emoji/suggestion.d.ts +1 -1
  36. package/es/components/organism/TextEditor/ui/Emoji/suggestion.js +2 -2
  37. package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.d.ts +16 -0
  38. package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.js +61 -0
  39. package/es/components/organism/TextEditor/ui/LinkInsertForm/index.d.ts +2 -0
  40. package/es/components/organism/TextEditor/ui/LinkInsertForm/index.js +1 -0
  41. package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.d.ts +9 -0
  42. package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.js +126 -0
  43. package/es/components/organism/TextEditor/ui/LinkPopover/index.d.ts +2 -0
  44. package/es/components/organism/TextEditor/ui/LinkPopover/index.js +1 -0
  45. package/es/components/organism/TextEditor/ui/Toolbar/{Toolbar.d.ts → FormattingToolbar.d.ts} +3 -4
  46. package/es/components/organism/TextEditor/ui/Toolbar/FormattingToolbar.js +85 -0
  47. package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.d.ts +10 -0
  48. package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.js +39 -0
  49. package/es/components/organism/TextEditor/ui/Toolbar/actions/EmojiAction.js +3 -2
  50. package/es/components/organism/TextEditor/ui/Toolbar/index.d.ts +2 -1
  51. package/es/components/organism/TextEditor/ui/Toolbar/index.js +2 -1
  52. package/package.json +13 -13
  53. package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.d.ts +0 -1
  54. package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.js +0 -1
  55. package/es/components/organism/TextEditor/ui/BubbleToolbar/index.d.ts +0 -0
  56. package/es/components/organism/TextEditor/ui/BubbleToolbar/index.js +0 -1
  57. package/es/components/organism/TextEditor/ui/Toolbar/Toolbar.js +0 -39
  58. /package/es/components/organism/TextEditor/stories/WithOldDynAndLink/{settings.json → froala-legacy-format.settings.json} +0 -0
@@ -1,9 +1,9 @@
1
1
  /// <reference types="react" />
2
- export declare const EmojiTabs: import("styled-components").StyledComponent<import("react").FC<import("antd").TabsProps> & {
2
+ export declare const EmojiTabs: import("styled-components").StyledComponent<import("react").FC<import("antd/es/tabs").TabsProps> & {
3
3
  TabPane: import("react").FC<import("antd").TabPaneProps>;
4
4
  }, any, {
5
5
  shadow?: boolean | undefined;
6
6
  }, never>;
7
- export declare const EmojiPopoverStyled: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<import("antd").PopoverProps & import("react").RefAttributes<unknown>> & {
7
+ export declare const EmojiPopoverStyled: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<import("antd/es/popover").PopoverProps & import("react").RefAttributes<unknown>> & {
8
8
  _InternalPanelDoNotUseOrYouWillBeFired: import("react").FC<import("antd/es/popover/PurePanel").PurePanelProps>;
9
9
  }, any, {}, never>;
@@ -1,8 +1,8 @@
1
1
  // Libraries
2
- import { Popover } from 'antd';
3
2
  import styled from 'styled-components';
4
3
  // Components
5
4
  import { Tabs as TabsUI } from '@antscorp/antsomi-ui/es/components/molecules';
5
+ import { Popover } from '../../atoms/Popover';
6
6
  export const EmojiTabs = styled(TabsUI) `
7
7
  .antsomi-tabs-tab {
8
8
  padding: 5.5px 20px !important;
@@ -8,6 +8,7 @@ export declare const VirtualizedMenu: React.ForwardRefExoticComponent<Partial<{
8
8
  className: string;
9
9
  itemSize: number;
10
10
  selected: string | string[];
11
+ focusedKey: string;
11
12
  expanded: string[];
12
13
  onClick: (args: {
13
14
  item: import("./types").ItemType;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { memo, useMemo, useRef, useEffect, useCallback } from 'react';
2
+ import { memo, useMemo, useRef, useCallback } from 'react';
3
3
  import { Typography } from '../../../../atoms/Typography';
4
4
  import { MenuItemRoot } from '../../styled';
5
5
  import clsx from 'clsx';
@@ -69,11 +69,7 @@ export const Item = memo((props) => {
69
69
  }, [item.id, normalizeTreeItems, selectedKeys]);
70
70
  const className = clsx(itemClassName, {
71
71
  [CLS.Item.selectedDescendant]: isSelectedDescendant,
72
+ [CLS.Item.focused]: isFocused,
72
73
  });
73
- useEffect(() => {
74
- if (isFocused) {
75
- itemRef.current?.focus({ preventScroll: true });
76
- }
77
- }, [isFocused]);
78
- return (_jsxs(MenuItemRoot, { ref: handleItemRef, role: displayOnly ? 'none' : 'treeitem', tabIndex: displayOnly ? -1 : isFocused ? 0 : -1, "aria-selected": displayOnly ? undefined : selected, "aria-disabled": displayOnly ? undefined : disabled, "aria-expanded": displayOnly ? undefined : expanded, "aria-level": displayOnly ? undefined : item.level, "aria-posinset": displayOnly ? undefined : index + 1, "aria-setsize": displayOnly ? undefined : items.length, className: className, style: itemStyle, onClick: e => handleOnClick(e, 'item'), children: [_jsx("div", { className: CLS.ItemLabel.default, children: labelContent }), actionContent] }));
74
+ return (_jsxs(MenuItemRoot, { "data-selected": !!selected, "data-focused": isFocused, ref: handleItemRef, role: displayOnly ? 'none' : 'treeitem', "aria-selected": displayOnly ? undefined : selected, "aria-disabled": displayOnly ? undefined : disabled, "aria-expanded": displayOnly ? undefined : expanded, "aria-level": displayOnly ? undefined : item.level, "aria-posinset": displayOnly ? undefined : index + 1, "aria-setsize": displayOnly ? undefined : items.length, className: className, style: itemStyle, onClick: e => handleOnClick(e, 'item'), children: [_jsx("div", { className: CLS.ItemLabel.default, children: labelContent }), actionContent] }));
79
75
  }, areEqual);
@@ -9,6 +9,7 @@ export declare const MenuInline: React.MemoExoticComponent<React.ForwardRefExoti
9
9
  className: string;
10
10
  itemSize: number;
11
11
  selected: string | string[];
12
+ focusedKey: string;
12
13
  expanded: string[];
13
14
  onClick: (args: {
14
15
  item: ItemType;
@@ -103,7 +103,7 @@ const computeVisibleItemsByLevel = (normalizeTreeItems, rootItemIds, expandedKey
103
103
  return visibleItems;
104
104
  };
105
105
  const MenuInlineInner = forwardRef((props, ref) => {
106
- const { items: itemsProp = [], expanded: expandedProp = [], selected: selectedProp = [], inlineIndent = INLINE_INDENT, inlinePadding = INLINE_PADDING, itemSize = ITEM_SIZE, itemSpacing = ITEM_SPACING, selectable = false, action, className, onClick, } = props;
106
+ const { items: itemsProp = [], expanded: expandedProp = [], selected: selectedProp = [], focusedKey, inlineIndent = INLINE_INDENT, inlinePadding = INLINE_PADDING, itemSize = ITEM_SIZE, itemSpacing = ITEM_SPACING, selectable = false, action, className, onClick, } = props;
107
107
  const [focusId, setFocusId] = useState(null);
108
108
  const [expandedKeys, setExpandedKeys] = useState(new Set());
109
109
  const [selectedKeys, setSelectedKeys] = useState(new Set());
@@ -122,6 +122,12 @@ const MenuInlineInner = forwardRef((props, ref) => {
122
122
  useDeepCompareEffect(() => {
123
123
  setExpandedKeys(new Set(expandedProp));
124
124
  }, [expandedProp]);
125
+ // Sync external focusedKey prop with internal focusId state for auto-scrolling
126
+ useEffect(() => {
127
+ if (focusedKey !== undefined) {
128
+ setFocusId(focusedKey);
129
+ }
130
+ }, [focusedKey]);
125
131
  useEffect(() => {
126
132
  setTreeState(serializeItems({
127
133
  items: itemsProp,
@@ -32,7 +32,7 @@ export const MenuItemRoot = styled.li `
32
32
  align-items: center;
33
33
  overflow: hidden;
34
34
 
35
- &:focus {
35
+ &.${CLS.Item.focused} {
36
36
  outline: none;
37
37
  }
38
38
 
@@ -84,8 +84,18 @@ export const MenuItemRoot = styled.li `
84
84
  }
85
85
  }
86
86
 
87
- &:focus:not(.${CLS.Item.disabled}, .${CLS.Item.selected}, .${CLS.Item.selectedDescendant}, .${CLS.Item.displayOnly}),
88
- &:hover:not(.${CLS.Item.disabled}, .${CLS.Item.selected}, .${CLS.Item.selectedDescendant}, .${CLS.Item.displayOnly}) {
87
+ &.${CLS.Item.focused}:not(
88
+ .${CLS.Item.disabled},
89
+ .${CLS.Item.selected},
90
+ .${CLS.Item.selectedDescendant},
91
+ .${CLS.Item.displayOnly}
92
+ ),
93
+ &:hover:not(
94
+ .${CLS.Item.disabled},
95
+ .${CLS.Item.selected},
96
+ .${CLS.Item.selectedDescendant},
97
+ .${CLS.Item.displayOnly}
98
+ ) {
89
99
  background-color: ${THEME.components?.Menu?.itemHoverBg};
90
100
  }
91
101
  }
@@ -101,4 +111,5 @@ export const VirtualizedMenuContainer = styled((VariableSizeList)) `
101
111
  `;
102
112
  export const StyledAutoSizer = styled(AutoSizer) `
103
113
  flex: 1;
114
+ outline: none;
104
115
  `;
@@ -22,6 +22,7 @@ type VirtualizedMenuBaseProps = Partial<{
22
22
  className: string;
23
23
  itemSize: number;
24
24
  selected: string | string[];
25
+ focusedKey: string;
25
26
  expanded: string[];
26
27
  onClick: (args: {
27
28
  item: ItemType;
@@ -29,6 +29,7 @@ export declare const CLS: {
29
29
  readonly disabled: string;
30
30
  readonly selected: string;
31
31
  readonly expanded: string;
32
+ readonly focused: string;
32
33
  readonly selectedDescendant: string;
33
34
  readonly displayOnly: string;
34
35
  };
@@ -37,6 +37,7 @@ export const CLS = {
37
37
  disabled: componentCls('item-disabled'),
38
38
  selected: componentCls('item-selected'),
39
39
  expanded: componentCls('item-expanded'),
40
+ focused: componentCls('item-focused'),
40
41
  selectedDescendant: componentCls('item-selected-descendant'),
41
42
  displayOnly: componentCls('item-display-only'),
42
43
  },
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
- import type { TextEditorProviderRefHandler } from './provider';
2
+ import { type TextEditorProviderRefHandler } from './provider';
3
3
  import { type TextEditorProps, type TextEditorRef, type TextEditorWithProviderRef } from './types';
4
4
  export declare const TextEditor: React.ForwardRefExoticComponent<TextEditorProps & {
5
+ colors?: string[] | undefined;
5
6
  onChangeColors?: ((colors: string[]) => void) | undefined;
6
7
  } & React.RefAttributes<TextEditorWithProviderRef>> & {
7
8
  Provider: React.ForwardRefExoticComponent<import("./provider").TextEditorProviderProps & React.RefAttributes<TextEditorProviderRefHandler>>;
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ANTSOMI_COMPONENT_PREFIX_CLS } from '@antscorp/antsomi-ui/es/constants';
3
- import { useDeepCompareMemo } from '@antscorp/antsomi-ui/es/hooks';
3
+ import { useDeepCompareEffect, useDeepCompareMemo } from '@antscorp/antsomi-ui/es/hooks';
4
4
  import SubScript from '@tiptap/extension-subscript';
5
5
  import Superscript from '@tiptap/extension-superscript';
6
6
  import TextAlign from '@tiptap/extension-text-align';
7
7
  import { TextStyleKit } from '@tiptap/extension-text-style';
8
- import { Selection } from '@tiptap/extensions';
8
+ import { Selection, Focus } from '@tiptap/extensions';
9
9
  import { useEditor } from '@tiptap/react';
10
10
  import StarterKit from '@tiptap/starter-kit';
11
11
  import clsx from 'clsx';
12
12
  import { isBoolean, omit } from 'lodash';
13
- import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, } from 'react';
13
+ import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, } from 'react';
14
14
  import { useDebouncedCallback } from 'use-debounce';
15
15
  import { DEFAULT_TEXT_STYLE } from './constants';
16
16
  import { BackgroundColor } from './extensions/BackgroundColor';
@@ -28,25 +28,47 @@ import { SmartTag } from './extensions/SmartTag';
28
28
  import { TextTransform } from './extensions/TextTransform';
29
29
  import { CustomUnorderedList } from './extensions/UnorderedList';
30
30
  import { ClearFormatting } from './extensions/ClearFormatting';
31
- import { TextEditorProvider, useTextEditorStore } from './provider';
31
+ import { TextEditorProvider, useTextEditorStore, } from './provider';
32
32
  import { StyledEditorContent } from './styled';
33
33
  import { isSmartTagNode, } from './types';
34
34
  import { emojiSuggestion } from './ui/Emoji';
35
- import { Toolbar } from './ui/Toolbar';
35
+ import { LinkPreviewToolbar, FormattingToolbar } from './ui/Toolbar';
36
36
  import { analyzeFont, defaultShouldShowBubbleMenu, getAllFullLinkGroups, handleLinkAction, handleSmartTagAction, isShowLinkToolbar, safeParseHTMLContent, htmlSerializerForOutput, } from './utils';
37
37
  import { BubbleMenu } from './ui/BubbleMenu';
38
+ import { LinkPopover } from './ui/LinkPopover';
38
39
  const TextEditorInternal = memo(forwardRef((props, ref) => {
39
- const { id, className, config, editable = true, initialContent = '', dataAttributes, onUpdateDebounced = 400, defaultTextStyle: defaultTextStyleProp, linkHandler: outerLinkHandler, smartTagHandler: outerSmartTagHandler, bubbleMenuProps, style, render, onUpdate, onFocus, onChangeFont, } = props;
40
- const isShowBubbleMenu = useTextEditorStore(state => state.isShowBubbleMenu);
40
+ const { id, className, config, editable = true, initialContent = '', dataAttributes, onUpdateDebounced = 300, defaultTextStyle: defaultTextStyleProp, linkHandler: outerLinkHandler, smartTagHandler: outerSmartTagHandler, bubbleMenuProps, style, render, onUpdate, onFocus, onChangeFont, } = props;
41
+ // Initialize bubbleMenuContainer with default value (document.body)
42
+ // This ensures BubbleMenu receives a valid appendTo prop on first render
43
+ const bubbleMenuContainer = useRef((() => {
44
+ const appendTo = bubbleMenuProps?.appendTo;
45
+ if (!appendTo)
46
+ return document.body;
47
+ return typeof appendTo === 'function' ? appendTo() : appendTo;
48
+ })());
49
+ const linkFormVisible = useTextEditorStore(state => state.linkFormState?.isVisible);
41
50
  const setBubbleMenuContainer = useTextEditorStore(state => state.setBubbleMenuContainer);
51
+ const showLinkForm = useTextEditorStore(state => state.showLinkForm);
42
52
  const defaultTextStyle = useDeepCompareMemo(() => ({
43
53
  ...DEFAULT_TEXT_STYLE,
44
54
  ...defaultTextStyleProp,
45
55
  }), [defaultTextStyleProp]);
56
+ useEffect(() => {
57
+ const bubbleMenuAppendTo = bubbleMenuProps?.appendTo;
58
+ // Default to document.body when no appendTo is provided
59
+ // This prevents bubble menu from being clipped by overflow containers
60
+ const el = bubbleMenuAppendTo
61
+ ? typeof bubbleMenuAppendTo === 'function'
62
+ ? bubbleMenuAppendTo()
63
+ : bubbleMenuAppendTo
64
+ : document.body;
65
+ setBubbleMenuContainer(el);
66
+ bubbleMenuContainer.current = el;
67
+ }, [bubbleMenuProps?.appendTo, setBubbleMenuContainer]);
46
68
  const handleOnUpdateDebounce = useDebouncedCallback((editor) => {
47
- const html = editor.getHTML();
48
69
  const text = editor.getText();
49
70
  const json = editor.getJSON();
71
+ const html = editor.getHTML();
50
72
  onUpdate?.({
51
73
  html: htmlSerializerForOutput(html),
52
74
  text,
@@ -54,6 +76,28 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
54
76
  });
55
77
  }, onUpdateDebounced);
56
78
  const contentRef = useRef(null);
79
+ // Compose linkHandler: merge outerLinkHandler with default handlers
80
+ // Allows partial override - user can provide only setNew or only edit
81
+ const linkHandler = useMemo(() => {
82
+ const defaultSetNew = ({ selectionText }) => {
83
+ showLinkForm('insert', {
84
+ url: '',
85
+ text: selectionText,
86
+ openInNewTab: false,
87
+ });
88
+ };
89
+ const defaultEdit = ({ attrs, selectionText, }) => {
90
+ showLinkForm('edit', {
91
+ url: attrs.href || '',
92
+ text: selectionText,
93
+ openInNewTab: attrs.target === '_blank',
94
+ });
95
+ };
96
+ return {
97
+ setNew: outerLinkHandler?.setNew || defaultSetNew,
98
+ edit: outerLinkHandler?.edit || defaultEdit,
99
+ };
100
+ }, [outerLinkHandler, showLinkForm]);
57
101
  const editor = useEditor({
58
102
  extensions: [
59
103
  StarterKit.configure({
@@ -108,6 +152,9 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
108
152
  Selection.configure({
109
153
  className: 'selection',
110
154
  }),
155
+ Focus.configure({
156
+ className: 'has-focused',
157
+ }),
111
158
  SmartTag,
112
159
  TextAlign.configure({
113
160
  types: ['heading', 'paragraph'],
@@ -122,7 +169,7 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
122
169
  Indent,
123
170
  Emoji.configure({
124
171
  suggestion: emojiSuggestion({
125
- container: bubbleMenuProps?.container,
172
+ container: bubbleMenuContainer.current,
126
173
  }),
127
174
  }),
128
175
  ClearFormatting.configure({
@@ -155,7 +202,7 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
155
202
  if (event.ctrlKey && event.key === 'k') {
156
203
  // Add this line to prevent browser's default behavior.
157
204
  event.preventDefault();
158
- handleLinkAction(view, outerLinkHandler);
205
+ handleLinkAction(view, linkHandler);
159
206
  }
160
207
  },
161
208
  },
@@ -184,10 +231,15 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
184
231
  return updatedAttrs;
185
232
  })
186
233
  .run(), [editor]);
187
- const shouldShowBubbleMenu = useCallback((params) => {
234
+ const shouldShowLinkPreview = useCallback((params) => {
235
+ const { state } = params;
236
+ return isShowLinkToolbar(state);
237
+ }, []);
238
+ const shouldShowFormattingToolbar = useCallback((params) => {
188
239
  const { state, view, element } = params;
240
+ // Don't show if link preview is visible
189
241
  if (isShowLinkToolbar(state)) {
190
- return true;
242
+ return false;
191
243
  }
192
244
  return defaultShouldShowBubbleMenu({
193
245
  state,
@@ -221,9 +273,6 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
221
273
  }
222
274
  });
223
275
  }, [editor]);
224
- const handleBubbleMenuRef = useCallback((htmlElement) => {
225
- setBubbleMenuContainer(htmlElement);
226
- }, [setBubbleMenuContainer]);
227
276
  useImperativeHandle(ref, () => ({
228
277
  setLink: params => {
229
278
  editor?.chain().setCustomLink(params).run();
@@ -260,27 +309,37 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
260
309
  // Inline styles apply to inner html elements (support email client)
261
310
  ...omit(defaultTextStyle, ['fontSize']),
262
311
  ...style,
263
- }, id: id, ref: contentRef, editor: editor, ...dataAttributes }), _jsx(BubbleMenu, { ref: handleBubbleMenuRef, editor: editor, shouldShow: shouldShowBubbleMenu, resizeDelay: 700, ...bubbleMenuProps, children: isShowBubbleMenu && (_jsx(Toolbar, { config: config, editor: editor, defaultTextStyle: defaultTextStyle, linkHanlder: {
264
- onUpsert: () => {
265
- handleLinkAction(editor.view, outerLinkHandler);
266
- },
267
- }, smartTagHandler: {
268
- onUpsert: (event) => {
269
- handleSmartTagAction(event, editor.view, outerSmartTagHandler);
270
- },
271
- }, render: render, onChangeFont: ({ font, weight }) => {
272
- onChangeFont?.({
273
- font,
274
- weight,
275
- analysis: analyzeFont(font),
276
- });
277
- } })) })] }));
312
+ }, id: id, ref: contentRef, editor: editor, ...dataAttributes }), _jsx(LinkPopover, { editor: editor }), !linkFormVisible && (_jsxs(_Fragment, { children: [_jsx(BubbleMenu, { pluginKey: "linkPreviewBubbleMenu", resizeDelay: 400, ...bubbleMenuProps, appendTo: bubbleMenuContainer.current, editor: editor, shouldShow: shouldShowLinkPreview, children: _jsx(LinkPreviewToolbar, { editor: editor, linkHanlder: {
313
+ onUpsert: () => {
314
+ handleLinkAction(editor.view, linkHandler);
315
+ },
316
+ }, render: render }) }), _jsx(BubbleMenu, { pluginKey: "formattingBubbleMenu", resizeDelay: 400, ...bubbleMenuProps, appendTo: bubbleMenuContainer.current, editor: editor, shouldShow: shouldShowFormattingToolbar, children: _jsx(FormattingToolbar, { config: config, editor: editor, defaultTextStyle: defaultTextStyle, linkHanlder: {
317
+ onUpsert: () => {
318
+ handleLinkAction(editor.view, linkHandler);
319
+ },
320
+ }, smartTagHandler: {
321
+ onUpsert: (event) => {
322
+ handleSmartTagAction(event, editor.view, outerSmartTagHandler);
323
+ },
324
+ }, onChangeFont: ({ font, weight }) => {
325
+ onChangeFont?.({
326
+ font,
327
+ weight,
328
+ analysis: analyzeFont(font),
329
+ });
330
+ } }) })] }))] }));
278
331
  }));
279
332
  TextEditorInternal.displayName = 'TextEditorInternal';
280
333
  const TextEditorWithProvider = forwardRef((props, ref) => {
281
- const { onChangeColors, ...editorProps } = props;
334
+ const { colors, onChangeColors, ...editorProps } = props;
282
335
  const providerRef = useRef(null);
283
336
  const editorRef = useRef(null);
337
+ // Sync external colors to internal store
338
+ useDeepCompareEffect(() => {
339
+ if (colors !== undefined) {
340
+ providerRef.current?.updateColors?.(colors);
341
+ }
342
+ }, [colors]);
284
343
  useImperativeHandle(ref, () => ({
285
344
  // Forward TextEditorRef methods - accessed at call time
286
345
  get style() {
@@ -322,7 +381,7 @@ const TextEditorWithProvider = forwardRef((props, ref) => {
322
381
  // Forward TextEditorProviderRefHandler methods
323
382
  updateColors: colors => providerRef.current?.updateColors?.(colors),
324
383
  }), []);
325
- return (_jsx(TextEditorProvider, { ref: providerRef, onChangeColors: onChangeColors, children: _jsx(TextEditorInternal, { ...editorProps, ref: editorRef }) }));
384
+ return (_jsx(TextEditorProvider, { ref: providerRef, colors: colors, onChangeColors: onChangeColors, children: _jsx(TextEditorInternal, { ...editorProps, ref: editorRef }) }));
326
385
  });
327
386
  TextEditorWithProvider.displayName = 'TextEditorWithProvider';
328
387
  export const TextEditor = Object.assign(TextEditorWithProvider, {
@@ -185,3 +185,12 @@ export declare const DEFAULT_FONT_GROUPING: {
185
185
  readonly label: "Non-standard";
186
186
  };
187
187
  };
188
+ /**
189
+ * All available toolbar actions in default order
190
+ */
191
+ export declare const ALL_TOOLBAR_ACTIONS: readonly ["fontFamily", "fontSize", "bold", "italic", "underline", "link", "strike", "superscript", "subscript", "textTransform", "textColor", "backgroundColor", "emoji", "smartTag", "bulletList", "orderedList", "textAlign", "lineSpacing", "letterSpacing", "indent", "outdent", "history", "clearFormatting"];
192
+ /**
193
+ * Default grouping of toolbar actions into rows
194
+ */
195
+ export declare const DEFAULT_TOOLBAR_GROUPING: readonly [readonly ["fontFamily", "fontSize", "bold", "italic", "underline", "link", "strike", "superscript", "subscript", "textTransform", "textColor", "backgroundColor", "emoji", "smartTag"], readonly ["bulletList", "orderedList", "textAlign", "lineSpacing", "letterSpacing", "indent", "outdent", "history", "clearFormatting"]];
196
+ export declare const ATTR_WHITESPACE: RegExp;
@@ -330,3 +330,69 @@ export const DEFAULT_FONT_GROUPING = {
330
330
  label: 'Non-standard',
331
331
  },
332
332
  };
333
+ /**
334
+ * All available toolbar actions in default order
335
+ */
336
+ export const ALL_TOOLBAR_ACTIONS = [
337
+ // Row 1: Text formatting and styling
338
+ 'fontFamily',
339
+ 'fontSize',
340
+ 'bold',
341
+ 'italic',
342
+ 'underline',
343
+ 'link',
344
+ 'strike',
345
+ 'superscript',
346
+ 'subscript',
347
+ 'textTransform',
348
+ 'textColor',
349
+ 'backgroundColor',
350
+ 'emoji',
351
+ 'smartTag',
352
+ // Row 2: Lists, alignment, and utilities
353
+ 'bulletList',
354
+ 'orderedList',
355
+ 'textAlign',
356
+ 'lineSpacing',
357
+ 'letterSpacing',
358
+ 'indent',
359
+ 'outdent',
360
+ 'history',
361
+ 'clearFormatting',
362
+ ];
363
+ /**
364
+ * Default grouping of toolbar actions into rows
365
+ */
366
+ export const DEFAULT_TOOLBAR_GROUPING = [
367
+ // Row 1
368
+ [
369
+ 'fontFamily',
370
+ 'fontSize',
371
+ 'bold',
372
+ 'italic',
373
+ 'underline',
374
+ 'link',
375
+ 'strike',
376
+ 'superscript',
377
+ 'subscript',
378
+ 'textTransform',
379
+ 'textColor',
380
+ 'backgroundColor',
381
+ 'emoji',
382
+ 'smartTag',
383
+ ],
384
+ // Row 2
385
+ [
386
+ 'bulletList',
387
+ 'orderedList',
388
+ 'textAlign',
389
+ 'lineSpacing',
390
+ 'letterSpacing',
391
+ 'indent',
392
+ 'outdent',
393
+ 'history',
394
+ 'clearFormatting',
395
+ ],
396
+ ];
397
+ // eslint-disable-next-line no-control-regex
398
+ export const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
@@ -1,140 +1,18 @@
1
- import { arrow, autoPlacement, flip, hide, inline, offset, shift, size } from '@floating-ui/dom';
2
- import type { Editor } from '@tiptap/core';
3
- import type { EditorState, PluginView } from '@tiptap/pm/state';
4
- import { Plugin, PluginKey } from '@tiptap/pm/state';
1
+ import type { EditorState } from '@tiptap/pm/state';
2
+ import { BubbleMenuView as TiptapBubbleMenuView } from '@tiptap/extension-bubble-menu';
3
+ import { Plugin } from '@tiptap/pm/state';
5
4
  import type { EditorView } from '@tiptap/pm/view';
6
- export interface BubbleMenuPluginProps {
7
- /**
8
- * The plugin key.
9
- * @type {PluginKey | string}
10
- * @default 'bubbleMenu'
11
- */
12
- pluginKey: PluginKey | string;
13
- /**
14
- * The editor instance.
15
- */
16
- editor: Editor;
17
- /**
18
- * The DOM element that contains your menu.
19
- * @type {HTMLElement}
20
- * @default null
21
- */
22
- element: HTMLElement;
23
- container?: HTMLElement | (() => HTMLElement) | null;
24
- /**
25
- * The delay in milliseconds before the menu should be updated.
26
- * This can be useful to prevent performance issues.
27
- * @type {number}
28
- * @default 250
29
- */
30
- updateDelay?: number;
31
- /**
32
- * The delay in milliseconds before the menu position should be updated on window resize.
33
- * This can be useful to prevent performance issues.
34
- * @type {number}
35
- * @default 60
36
- */
37
- resizeDelay?: number;
38
- /**
39
- * A function that determines whether the menu should be shown or not.
40
- * If this function returns `false`, the menu will be hidden, otherwise it will be shown.
41
- */
42
- shouldShow?: ((props: {
43
- editor: Editor;
44
- element: HTMLElement;
45
- view: EditorView;
46
- state: EditorState;
47
- oldState?: EditorState;
48
- from: number;
49
- to: number;
50
- }) => boolean) | null;
51
- /**
52
- * The options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,
53
- * hide, and inline middlewares.
54
- * @default {}
55
- * @see https://floating-ui.com/docs/computePosition#options
56
- */
57
- options?: {
58
- strategy?: 'absolute' | 'fixed';
59
- placement?: 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
60
- offset?: Parameters<typeof offset>[0] | boolean;
61
- flip?: Parameters<typeof flip>[0] | boolean;
62
- shift?: Parameters<typeof shift>[0] | boolean;
63
- arrow?: Parameters<typeof arrow>[0] | false;
64
- size?: Parameters<typeof size>[0] | boolean;
65
- autoPlacement?: Parameters<typeof autoPlacement>[0] | boolean;
66
- hide?: Parameters<typeof hide>[0] | boolean;
67
- inline?: Parameters<typeof inline>[0] | boolean;
68
- onShow?: () => void;
69
- onHide?: () => void;
70
- onUpdate?: () => void;
71
- onDestroy?: () => void;
72
- };
73
- }
74
- export type BubbleMenuViewProps = BubbleMenuPluginProps & {
75
- view: EditorView;
76
- };
77
- export declare class BubbleMenuView implements PluginView {
78
- editor: Editor;
79
- element: HTMLElement;
80
- container?: HTMLElement | (() => HTMLElement) | null;
81
- view: EditorView;
82
- preventHide: boolean;
83
- updateDelay: number;
84
- resizeDelay: number;
85
- private updateDebounceTimer;
86
- private resizeDebounceTimer;
5
+ import type { BubbleMenuPluginProps, BubbleMenuViewProps } from '@tiptap/extension-bubble-menu';
6
+ export declare class BubbleMenuView extends TiptapBubbleMenuView {
87
7
  private resizeObserver;
88
- private isVisible;
89
- private floatingUIOptions;
90
- shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null>;
91
- get middlewares(): {
92
- name: string;
93
- options?: any;
94
- fn: (state: {
95
- x: number;
96
- y: number;
97
- platform: import("@floating-ui/core").Platform;
98
- placement: import("@floating-ui/utils").Placement;
99
- initialPlacement: import("@floating-ui/utils").Placement;
100
- strategy: import("@floating-ui/utils").Strategy;
101
- middlewareData: import("@floating-ui/core").MiddlewareData;
102
- rects: import("@floating-ui/utils").ElementRects;
103
- elements: import("@floating-ui/dom").Elements;
104
- }) => import("@floating-ui/core").MiddlewareReturn | Promise<import("@floating-ui/core").MiddlewareReturn>;
105
- }[];
106
- constructor({ editor, element, view, updateDelay, resizeDelay, container, shouldShow, options, }: BubbleMenuViewProps);
107
- mousedownHandler: () => void;
108
- dragstartHandler: () => void;
109
- /**
110
- * Handles the window resize event and element resize event to update the position of the bubble menu.
111
- * It uses a debounce mechanism to prevent excessive updates.
112
- * The delay is defined by the `resizeDelay` property.
113
- */
114
- resizeHandler: (delay?: number) => void;
115
- /**
116
- * Window resize handler that uses the default resize delay.
117
- * This is a separate function to maintain the same reference for addEventListener/removeEventListener.
118
- */
119
- windowResizeHandler: () => void;
8
+ constructor(props: BubbleMenuViewProps);
120
9
  /**
121
10
  * Sets up ResizeObserver to watch for bubble menu element size changes.
122
11
  * This ensures the bubble menu position is updated when the element resizes
123
12
  * due to React component rendering different children, not just when the window resizes.
124
13
  */
125
14
  setupResizeObserver: () => void;
126
- focusHandler: () => void;
127
- blurHandler: ({ event }: {
128
- event: FocusEvent;
129
- }) => void;
130
- updatePosition(): void;
131
- update(view: EditorView, oldState?: EditorState): void;
132
- handleDebouncedUpdate: (view: EditorView, oldState?: EditorState) => void;
133
- getShouldShow(oldState?: EditorState): boolean | undefined;
134
15
  updateHandler: (view: EditorView, selectionChanged: boolean, docChanged: boolean, oldState?: EditorState) => void;
135
- private getContainer;
136
- show(): void;
137
- hide(): void;
138
16
  destroy(): void;
139
17
  }
140
18
  export declare const BubbleMenuPlugin: (options: BubbleMenuPluginProps) => Plugin<any>;