@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.
- package/es/components/molecules/EmojiPopover/styled.d.ts +2 -2
- package/es/components/molecules/EmojiPopover/styled.js +1 -1
- package/es/components/molecules/VirtualizedMenu/VirtualizedMenu.d.ts +1 -0
- package/es/components/molecules/VirtualizedMenu/components/Item/Item.js +3 -7
- package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.d.ts +1 -0
- package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.js +7 -1
- package/es/components/molecules/VirtualizedMenu/styled.js +14 -3
- package/es/components/molecules/VirtualizedMenu/types.d.ts +1 -0
- package/es/components/molecules/VirtualizedMenu/utils.d.ts +1 -0
- package/es/components/molecules/VirtualizedMenu/utils.js +1 -0
- package/es/components/organism/TextEditor/TextEditor.d.ts +2 -1
- package/es/components/organism/TextEditor/TextEditor.js +91 -32
- package/es/components/organism/TextEditor/constants.d.ts +9 -0
- package/es/components/organism/TextEditor/constants.js +66 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.d.ts +6 -128
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.js +7 -293
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.d.ts +1 -1
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.js +4 -0
- package/es/components/organism/TextEditor/hooks/useColorSet.js +7 -0
- package/es/components/organism/TextEditor/index.d.ts +1 -0
- package/es/components/organism/TextEditor/index.scss +4 -7
- package/es/components/organism/TextEditor/provider.d.ts +1 -0
- package/es/components/organism/TextEditor/provider.js +6 -3
- package/es/components/organism/TextEditor/store.d.ts +11 -4
- package/es/components/organism/TextEditor/store.js +22 -2
- package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.d.ts +111 -0
- package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.js +82 -0
- package/es/components/organism/TextEditor/stories/shared.d.ts +64 -0
- package/es/components/organism/TextEditor/stories/shared.js +57 -0
- package/es/components/organism/TextEditor/styled.d.ts +1 -1
- package/es/components/organism/TextEditor/types.d.ts +66 -6
- package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.d.ts +2 -1
- package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.js +34 -50
- package/es/components/organism/TextEditor/ui/Emoji/EmojiList.js +1 -1
- package/es/components/organism/TextEditor/ui/Emoji/suggestion.d.ts +1 -1
- package/es/components/organism/TextEditor/ui/Emoji/suggestion.js +2 -2
- package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.d.ts +16 -0
- package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.js +61 -0
- package/es/components/organism/TextEditor/ui/LinkInsertForm/index.d.ts +2 -0
- package/es/components/organism/TextEditor/ui/LinkInsertForm/index.js +1 -0
- package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.d.ts +9 -0
- package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.js +126 -0
- package/es/components/organism/TextEditor/ui/LinkPopover/index.d.ts +2 -0
- package/es/components/organism/TextEditor/ui/LinkPopover/index.js +1 -0
- package/es/components/organism/TextEditor/ui/Toolbar/{Toolbar.d.ts → FormattingToolbar.d.ts} +3 -4
- package/es/components/organism/TextEditor/ui/Toolbar/FormattingToolbar.js +85 -0
- package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.d.ts +10 -0
- package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.js +39 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/EmojiAction.js +3 -2
- package/es/components/organism/TextEditor/ui/Toolbar/index.d.ts +2 -1
- package/es/components/organism/TextEditor/ui/Toolbar/index.js +2 -1
- package/package.json +13 -13
- package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.d.ts +0 -1
- package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.js +0 -1
- package/es/components/organism/TextEditor/ui/BubbleToolbar/index.d.ts +0 -0
- package/es/components/organism/TextEditor/ui/BubbleToolbar/index.js +0 -1
- package/es/components/organism/TextEditor/ui/Toolbar/Toolbar.js +0 -39
- /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;
|
|
@@ -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,
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
`;
|
|
@@ -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
|
|
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 {
|
|
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 =
|
|
40
|
-
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|