@antscorp/antsomi-ui 1.3.7-beta.3 → 1.3.7-beta.31

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 (122) hide show
  1. package/es/components/atoms/InputDynamic/InputDynamic.js +3 -3
  2. package/es/components/atoms/index.d.ts +0 -2
  3. package/es/components/atoms/index.js +0 -2
  4. package/es/components/molecules/AddDynamicContent/AddDynamicContent.js +28 -2
  5. package/es/components/molecules/AddDynamicContent/constants.d.ts +2 -0
  6. package/es/components/molecules/AddDynamicContent/constants.js +2 -0
  7. package/es/components/molecules/EmojiPopover/EmojiPopover.d.ts +1 -0
  8. package/es/components/molecules/EmojiPopover/EmojiPopover.js +2 -2
  9. package/es/components/molecules/EmojiPopover/styled.d.ts +2 -2
  10. package/es/components/molecules/EmojiPopover/styled.js +1 -1
  11. package/es/components/molecules/SearchPopover/SearchPopover.js +2 -2
  12. package/es/components/molecules/SearchPopover/styled.d.ts +12 -1
  13. package/es/components/molecules/SearchPopover/styled.js +1 -2
  14. package/es/components/molecules/SearchPopover/types.d.ts +4 -3
  15. package/es/components/molecules/TagifyInput/TagifyInput.js +55 -19
  16. package/es/components/molecules/TagifyInput/types.d.ts +20 -1
  17. package/es/components/molecules/TagifyInput/utils.d.ts +6 -1
  18. package/es/components/molecules/TagifyInput/utils.js +19 -0
  19. package/es/components/molecules/VirtualizedMenu/VirtualizedMenu.d.ts +1 -0
  20. package/es/components/molecules/VirtualizedMenu/components/Item/Item.js +3 -7
  21. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.d.ts +10 -10
  22. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.js +46 -327
  23. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/index.d.ts +9 -0
  24. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/index.js +5 -0
  25. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useFocusManagement.d.ts +23 -0
  26. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useFocusManagement.js +81 -0
  27. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useItemInteraction.d.ts +24 -0
  28. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useItemInteraction.js +32 -0
  29. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useKeyboardNavigation.d.ts +26 -0
  30. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useKeyboardNavigation.js +93 -0
  31. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useTreeState.d.ts +24 -0
  32. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useTreeState.js +94 -0
  33. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useVisibleItems.d.ts +7 -0
  34. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useVisibleItems.js +132 -0
  35. package/es/components/molecules/VirtualizedMenu/styled.js +14 -3
  36. package/es/components/molecules/VirtualizedMenu/types.d.ts +1 -0
  37. package/es/components/molecules/VirtualizedMenu/utils.d.ts +1 -0
  38. package/es/components/molecules/VirtualizedMenu/utils.js +1 -0
  39. package/es/components/organism/ActivityTimeline/ActivityTimeline.js +3 -3
  40. package/es/components/organism/ActivityTimeline/constants.d.ts +9 -9
  41. package/es/components/organism/ActivityTimeline/constants.js +3 -3
  42. package/es/components/organism/ActivityTimeline/index.d.ts +530 -1
  43. package/es/components/organism/ActivityTimeline/index.js +9 -1
  44. package/es/components/organism/ActivityTimeline/utils.js +7 -6
  45. package/es/components/organism/TextEditor/TextEditor.d.ts +9 -2
  46. package/es/components/organism/TextEditor/TextEditor.js +181 -40
  47. package/es/components/organism/TextEditor/constants.d.ts +9 -0
  48. package/es/components/organism/TextEditor/constants.js +66 -0
  49. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.d.ts +6 -128
  50. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.js +7 -292
  51. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.d.ts +1 -1
  52. package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.js +4 -0
  53. package/es/components/organism/TextEditor/extensions/Link.js +3 -3
  54. package/es/components/organism/TextEditor/extensions/ListItem.d.ts +10 -0
  55. package/es/components/organism/TextEditor/extensions/ListItem.js +93 -0
  56. package/es/components/organism/TextEditor/extensions/SmartTag.d.ts +5 -6
  57. package/es/components/organism/TextEditor/extensions/SmartTag.js +94 -2
  58. package/es/components/organism/TextEditor/hooks/useColorSet.js +7 -0
  59. package/es/components/organism/TextEditor/hooks/useMarkTracking.js +2 -1
  60. package/es/components/organism/TextEditor/index.d.ts +7 -5
  61. package/es/components/organism/TextEditor/index.scss +4 -7
  62. package/es/components/organism/TextEditor/provider.d.ts +1 -0
  63. package/es/components/organism/TextEditor/provider.js +6 -3
  64. package/es/components/organism/TextEditor/store.d.ts +11 -4
  65. package/es/components/organism/TextEditor/store.js +22 -2
  66. package/es/components/organism/TextEditor/stories/WithOldDynAndLink/froala-legacy-format.settings.json +95 -0
  67. package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.d.ts +111 -0
  68. package/es/components/organism/TextEditor/stories/WithOldDynAndLink/shared.js +82 -0
  69. package/es/components/organism/TextEditor/stories/shared.d.ts +64 -0
  70. package/es/components/organism/TextEditor/stories/shared.js +57 -0
  71. package/es/components/organism/TextEditor/styled.d.ts +1 -1
  72. package/es/components/organism/TextEditor/styled.js +1 -0
  73. package/es/components/organism/TextEditor/types.d.ts +153 -9
  74. package/es/components/organism/TextEditor/types.js +1 -0
  75. package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.d.ts +2 -1
  76. package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.js +80 -51
  77. package/es/components/organism/TextEditor/ui/Emoji/EmojiList.js +1 -1
  78. package/es/components/organism/TextEditor/ui/Emoji/suggestion.d.ts +1 -1
  79. package/es/components/organism/TextEditor/ui/Emoji/suggestion.js +2 -2
  80. package/es/components/organism/TextEditor/ui/FontPopover/FontPopover.js +12 -2
  81. package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.d.ts +16 -0
  82. package/es/components/organism/TextEditor/ui/LinkInsertForm/LinkInsertForm.js +61 -0
  83. package/es/components/organism/TextEditor/ui/LinkInsertForm/index.d.ts +2 -0
  84. package/es/components/organism/TextEditor/ui/LinkInsertForm/index.js +1 -0
  85. package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.d.ts +9 -0
  86. package/es/components/organism/TextEditor/ui/LinkPopover/LinkPopover.js +126 -0
  87. package/es/components/organism/TextEditor/ui/LinkPopover/index.d.ts +2 -0
  88. package/es/components/organism/TextEditor/ui/LinkPopover/index.js +1 -0
  89. package/es/components/organism/TextEditor/ui/Popover/Popover.js +1 -1
  90. package/es/components/organism/TextEditor/ui/TextAlignSelect/TextAlignSelect.js +1 -3
  91. package/es/components/organism/TextEditor/ui/Toolbar/{Toolbar.d.ts → FormattingToolbar.d.ts} +3 -4
  92. package/es/components/organism/TextEditor/ui/Toolbar/FormattingToolbar.js +85 -0
  93. package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.d.ts +10 -0
  94. package/es/components/organism/TextEditor/ui/Toolbar/LinkPreviewToolbar.js +39 -0
  95. package/es/components/organism/TextEditor/ui/Toolbar/actions/EmojiAction.js +4 -3
  96. package/es/components/organism/TextEditor/ui/Toolbar/actions/FontFamilyAction.d.ts +1 -0
  97. package/es/components/organism/TextEditor/ui/Toolbar/actions/FontFamilyAction.js +2 -2
  98. package/es/components/organism/TextEditor/ui/Toolbar/actions/FontSizeAction.js +6 -1
  99. package/es/components/organism/TextEditor/ui/Toolbar/index.d.ts +2 -1
  100. package/es/components/organism/TextEditor/ui/Toolbar/index.js +2 -1
  101. package/es/components/organism/TextEditor/utils/documentState.d.ts +14 -0
  102. package/es/components/organism/TextEditor/utils/documentState.js +25 -0
  103. package/es/components/organism/TextEditor/utils/htmlProcessing.js +60 -0
  104. package/es/components/organism/TextEditor/utils/link.d.ts +10 -1
  105. package/es/components/organism/TextEditor/utils/link.js +161 -7
  106. package/es/components/organism/TextEditor/utils/menu.js +2 -1
  107. package/es/components/organism/TextEditor/utils/selection.js +3 -2
  108. package/es/components/organism/TextEditor/utils/smartTag.js +2 -1
  109. package/es/components/organism/index.d.ts +1 -1
  110. package/es/hooks/index.d.ts +1 -1
  111. package/es/hooks/index.js +1 -1
  112. package/es/services/MediaTemplateDesign/UploadFile/index.js +4 -4
  113. package/es/types/index.d.ts +1 -1
  114. package/es/types/index.js +1 -1
  115. package/es/utils/common.d.ts +1 -1
  116. package/es/utils/common.js +3 -3
  117. package/package.json +23 -35
  118. package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.d.ts +0 -1
  119. package/es/components/organism/TextEditor/ui/BubbleToolbar/BubbleToolbar.js +0 -1
  120. package/es/components/organism/TextEditor/ui/BubbleToolbar/index.d.ts +0 -0
  121. package/es/components/organism/TextEditor/ui/BubbleToolbar/index.js +0 -1
  122. package/es/components/organism/TextEditor/ui/Toolbar/Toolbar.js +0 -39
@@ -1,14 +1,13 @@
1
1
  import type React from 'react';
2
2
  import type { JSONContent, Editor, MarkRange } from '@tiptap/core';
3
- import type { Mark } from '@tiptap/pm/model';
4
- import type { SmartTagAttrs } from './extensions/SmartTag';
3
+ import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
4
+ import type { Mark, Node } from '@tiptap/pm/model';
5
5
  import { ORDERED_LIST_STYLE_TYPE, UNORDERED_LIST_STYLE_TYPE } from './constants';
6
6
  import type { OverrideProperties } from 'type-fest';
7
- import { BubbleMenuPluginProps } from './extensions/BubbleMenu';
8
7
  export type HandleSmartTagRef = {
9
8
  setSmartTag: (attrs: SmartTagAttrs) => void;
10
9
  deleteSmartTag: (id: string) => void;
11
- updateSmartTagAttrs: (id: string, updatedAttrs: Omit<SmartTagAttrs, 'id'>) => void;
10
+ updateSmartTagAttrs: (id: string, updatedAttrs: Partial<SmartTagAttrs>) => void;
12
11
  };
13
12
  export type HandleLinkRef = {
14
13
  setLink: (params: {
@@ -22,6 +21,11 @@ export type HandleLinkRef = {
22
21
  updated: (currentText: string) => string;
23
22
  mergeAdjacentEqualFn?: (currentAttrs: LinkAttrs, candidateAttrs: LinkAttrs) => boolean;
24
23
  }) => void;
24
+ eachLinkGroup: (callback: (params: {
25
+ attrs: LinkAttrs;
26
+ content: string;
27
+ }) => void) => void;
28
+ eachSmartag: (callback: (attrs: SmartTagAttrs) => void) => void;
25
29
  };
26
30
  export type TextEditorRef = HandleSmartTagRef & HandleLinkRef & {
27
31
  blur: (perserveSelection?: boolean) => void;
@@ -43,6 +47,28 @@ export type LinkHandler = Partial<{
43
47
  selectionText: string;
44
48
  }) => void;
45
49
  }>;
50
+ /**
51
+ * Link form data structure
52
+ */
53
+ export type LinkFormData = {
54
+ /** The URL/href of the link */
55
+ url: string;
56
+ /** The text content to display */
57
+ text: string;
58
+ /** Whether to open link in new tab */
59
+ openInNewTab: boolean;
60
+ };
61
+ /**
62
+ * Link form state in store
63
+ */
64
+ export type LinkFormState = {
65
+ /** Whether the link form is visible */
66
+ isVisible: boolean;
67
+ /** Mode: insert new link or edit existing link */
68
+ mode: 'insert' | 'edit';
69
+ /** Form data */
70
+ data: LinkFormData;
71
+ } | null;
46
72
  export type FontConfig = {
47
73
  fontFamily: {
48
74
  name: string;
@@ -73,6 +99,29 @@ export type FontGroupResult = {
73
99
  * @returns Array of group keys in desired display order
74
100
  */
75
101
  export type GroupOrderFunction = (groups: string[]) => string[];
102
+ /**
103
+ * Toolbar action names - union type for type safety
104
+ */
105
+ export type ToolbarActionName = 'bold' | 'italic' | 'underline' | 'strike' | 'superscript' | 'subscript' | 'fontFamily' | 'fontSize' | 'textColor' | 'backgroundColor' | 'textTransform' | 'bulletList' | 'orderedList' | 'indent' | 'outdent' | 'textAlign' | 'lineSpacing' | 'letterSpacing' | 'link' | 'smartTag' | 'emoji' | 'history' | 'clearFormatting';
106
+ /**
107
+ * Toolbar configuration options
108
+ */
109
+ export type ToolbarConfig = {
110
+ /**
111
+ * Which toolbar actions to show
112
+ * - Array: Show only specified actions in given order
113
+ * - 'all': Show all available actions (default)
114
+ * - false: Hide toolbar entirely
115
+ */
116
+ items?: ToolbarActionName[] | 'all' | false;
117
+ /**
118
+ * Which toolbar actions to hide (ignored if items is an array)
119
+ * Useful when you want to show most items but exclude a few
120
+ * @example
121
+ * config={{ Toolbar: { exclude: ['smartTag', 'emoji'] } }}
122
+ */
123
+ exclude?: ToolbarActionName[];
124
+ };
76
125
  /**
77
126
  * Configuration options for TextEditor components
78
127
  */
@@ -91,6 +140,11 @@ export type Config = Partial<{
91
140
  /** Whether to use custom bullet styles */
92
141
  useCustomBullet?: boolean;
93
142
  };
143
+ SmartTag: {
144
+ generateId?: () => string;
145
+ };
146
+ /** Toolbar configuration */
147
+ Toolbar: ToolbarConfig;
94
148
  }>;
95
149
  export type TextStyle = {
96
150
  fontFamily: string;
@@ -109,8 +163,6 @@ export type TextEditorComponentsRender = {
109
163
  unlink: React.ReactNode;
110
164
  }) => React.ReactNode;
111
165
  };
112
- type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
113
- export type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React.HTMLAttributes<HTMLDivElement>;
114
166
  export type TextEditorProps = {
115
167
  id?: string;
116
168
  className?: string;
@@ -120,9 +172,9 @@ export type TextEditorProps = {
120
172
  smartTagHandler?: SmartTagHandler;
121
173
  linkHandler?: LinkHandler;
122
174
  editable?: boolean;
123
- bubbleMenuProps?: {
124
- container?: HTMLElement | null;
125
- } & Pick<BubbleMenuProps, 'options'>;
175
+ bubbleMenuProps?: Pick<BubbleMenuPluginProps, 'options' | 'appendTo'> & {
176
+ enabled?: boolean;
177
+ };
126
178
  defaultTextStyle?: Partial<TextStyle>;
127
179
  dataAttributes?: Record<`data-${string}`, unknown>;
128
180
  onUpdateDebounced?: number;
@@ -133,6 +185,11 @@ export type TextEditorProps = {
133
185
  text: string;
134
186
  json: JSONContent;
135
187
  }) => void;
188
+ onCreate?: (args: {
189
+ html: string;
190
+ text: string;
191
+ json: JSONContent;
192
+ }) => void;
136
193
  onChangeFont?: (changeInfo: {
137
194
  font: FontConfig;
138
195
  weight: number;
@@ -142,6 +199,13 @@ export type TextEditorProps = {
142
199
  };
143
200
  }) => void;
144
201
  };
202
+ export type TextEditorAllProps = TextEditorProps & {
203
+ colors?: string[];
204
+ onChangeColors?: (colors: string[]) => void;
205
+ };
206
+ export type TextEditorWithProviderRef = TextEditorRef & {
207
+ updateColors?: (colors: string[]) => void;
208
+ };
145
209
  export type OrderedListStyleType = (typeof ORDERED_LIST_STYLE_TYPE)[keyof typeof ORDERED_LIST_STYLE_TYPE];
146
210
  export type UnorderedListStyleType = (typeof UNORDERED_LIST_STYLE_TYPE)[keyof typeof UNORDERED_LIST_STYLE_TYPE];
147
211
  export declare const isOrderedListStyleType: (value: unknown) => value is OrderedListStyleType;
@@ -155,9 +219,19 @@ export type LinkAttrs = {
155
219
  title: string | null;
156
220
  data: Record<string, string>;
157
221
  }>;
222
+ export type SmartTagAttrs = {
223
+ id: string;
224
+ sourceId?: string;
225
+ } & Partial<{
226
+ content: string;
227
+ tag: string;
228
+ }>;
158
229
  export type LinkMark = OverrideProperties<Mark, {
159
230
  attrs: LinkAttrs;
160
231
  }>;
232
+ export type SmartTagNode = OverrideProperties<Node, {
233
+ attrs: SmartTagAttrs;
234
+ }>;
161
235
  export type LinkMarkRange = OverrideProperties<MarkRange, {
162
236
  mark: LinkMark;
163
237
  }>;
@@ -170,6 +244,76 @@ export declare const isLinkMark: (mark: Mark) => mark is {
170
244
  toJSON: () => any;
171
245
  attrs: LinkAttrs;
172
246
  };
247
+ type ProtocolOptions = {
248
+ /**
249
+ * The protocol scheme to be registered.
250
+ * @default '''
251
+ * @example 'ftp'
252
+ * @example 'git'
253
+ */
254
+ scheme: string;
255
+ /**
256
+ * If enabled, it allows optional slashes after the protocol.
257
+ * @default false
258
+ * @example true
259
+ */
260
+ optionalSlashes?: boolean;
261
+ };
262
+ export type ProtocolConfig = Array<ProtocolOptions | string>;
263
+ export declare const isSmartTagNode: (node: Node) => node is {
264
+ toString: () => string;
265
+ readonly type: import("prosemirror-model").NodeType;
266
+ readonly marks: readonly Mark[];
267
+ readonly content: import("prosemirror-model").Fragment;
268
+ readonly children: readonly Node[];
269
+ readonly text: string | undefined;
270
+ readonly nodeSize: number;
271
+ readonly childCount: number;
272
+ child: (index: number) => Node;
273
+ maybeChild: (index: number) => Node | null;
274
+ forEach: (f: (node: Node, offset: number, index: number) => void) => void;
275
+ nodesBetween: (from: number, to: number, f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void, startPos?: number | undefined) => void;
276
+ descendants: (f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void) => void;
277
+ readonly textContent: string;
278
+ textBetween: (from: number, to: number, blockSeparator?: string | null | undefined, leafText?: string | ((leafNode: Node) => string) | null | undefined) => string;
279
+ readonly firstChild: Node | null;
280
+ readonly lastChild: Node | null;
281
+ eq: (other: Node) => boolean;
282
+ sameMarkup: (other: Node) => boolean;
283
+ hasMarkup: (type: import("prosemirror-model").NodeType, attrs?: import("prosemirror-model").Attrs | null | undefined, marks?: readonly Mark[] | undefined) => boolean;
284
+ copy: (content?: import("prosemirror-model").Fragment | null | undefined) => Node;
285
+ mark: (marks: readonly Mark[]) => Node;
286
+ cut: (from: number, to?: number | undefined) => Node;
287
+ slice: (from: number, to?: number | undefined, includeParents?: boolean | undefined) => import("prosemirror-model").Slice;
288
+ replace: (from: number, to: number, slice: import("prosemirror-model").Slice) => Node;
289
+ nodeAt: (pos: number) => Node | null;
290
+ childAfter: (pos: number) => {
291
+ node: Node | null;
292
+ index: number;
293
+ offset: number;
294
+ };
295
+ childBefore: (pos: number) => {
296
+ node: Node | null;
297
+ index: number;
298
+ offset: number;
299
+ };
300
+ resolve: (pos: number) => import("prosemirror-model").ResolvedPos;
301
+ rangeHasMark: (from: number, to: number, type: import("prosemirror-model").MarkType | Mark) => boolean;
302
+ readonly isBlock: boolean;
303
+ readonly isTextblock: boolean;
304
+ readonly inlineContent: boolean;
305
+ readonly isInline: boolean;
306
+ readonly isText: boolean;
307
+ readonly isLeaf: boolean;
308
+ readonly isAtom: boolean;
309
+ contentMatchAt: (index: number) => import("prosemirror-model").ContentMatch;
310
+ canReplace: (from: number, to: number, replacement?: import("prosemirror-model").Fragment | undefined, start?: number | undefined, end?: number | undefined) => boolean;
311
+ canReplaceWith: (from: number, to: number, type: import("prosemirror-model").NodeType, marks?: readonly Mark[] | undefined) => boolean;
312
+ canAppend: (other: Node) => boolean;
313
+ check: () => void;
314
+ toJSON: () => any;
315
+ attrs: SmartTagAttrs;
316
+ };
173
317
  export declare const isLinkMarkRange: (markRange: MarkRange) => markRange is {
174
318
  from: number;
175
319
  to: number;
@@ -2,4 +2,5 @@ import { CUSTOM_LINK_EXTENSION_NAME, ORDERED_LIST_STYLE_TYPE, UNORDERED_LIST_STY
2
2
  export const isOrderedListStyleType = (value) => typeof value === 'string' && Object.values(ORDERED_LIST_STYLE_TYPE).some(v => v === value);
3
3
  export const isUnorderedListStyleType = (value) => typeof value === 'string' && Object.values(UNORDERED_LIST_STYLE_TYPE).some(v => v === value);
4
4
  export const isLinkMark = (mark) => mark.type.name === CUSTOM_LINK_EXTENSION_NAME;
5
+ export const isSmartTagNode = (node) => node.type.name === 'smartTag';
5
6
  export const isLinkMarkRange = (markRange) => isLinkMark(markRange.mark);
@@ -1,2 +1,3 @@
1
1
  /// <reference types="react" />
2
- export declare const BubbleMenu: import("react").ForwardRefExoticComponent<Pick<Partial<Omit<Pick<Partial<import("../../extensions/BubbleMenu").BubbleMenuPluginProps>, "pluginKey"> & Omit<import("../../extensions/BubbleMenu").BubbleMenuPluginProps, "pluginKey">, "element">>, "editor"> & Omit<Omit<Pick<Partial<import("../../extensions/BubbleMenu").BubbleMenuPluginProps>, "pluginKey"> & Omit<import("../../extensions/BubbleMenu").BubbleMenuPluginProps, "pluginKey">, "element">, "editor"> & import("react").HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
2
+ import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
3
+ export declare const BubbleMenu: import("react").ForwardRefExoticComponent<Pick<Partial<Omit<Pick<Partial<BubbleMenuPluginProps>, "pluginKey"> & Omit<BubbleMenuPluginProps, "pluginKey">, "element">>, "editor"> & Omit<Omit<Pick<Partial<BubbleMenuPluginProps>, "pluginKey"> & Omit<BubbleMenuPluginProps, "pluginKey">, "element">, "editor"> & import("react").HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,17 +1,32 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCurrentEditor } from '@tiptap/react';
3
- import { forwardRef, useEffect, useRef } from 'react';
3
+ import { forwardRef, useEffect, useLayoutEffect, useRef, useState } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { BubbleMenuPlugin } from '../../extensions/BubbleMenu';
6
- import { useDeepCompareEffect, useIsMounted } from '@antscorp/antsomi-ui/es/hooks';
7
- import { useTextEditorStore } from '../../provider';
8
- import clsx from 'clsx';
6
+ import { useDeepCompareEffect } from '@antscorp/antsomi-ui/es/hooks';
9
7
  import { ANTSOMI_COMPONENT_PREFIX_CLS } from '@antscorp/antsomi-ui/es/constants';
10
- export const BubbleMenu = forwardRef(({ pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, options, children, container, className, ...restProps }, ref) => {
8
+ export const BubbleMenu = forwardRef(({ pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, getReferencedVirtualElement, appendTo, options, children, ...restProps }, ref) => {
11
9
  const menuEl = useRef(document.createElement('div'));
12
- const isMounted = useIsMounted();
13
- const setIsShowBubbleMenu = useTextEditorStore(state => state.setIsShowBubbleMenu);
10
+ menuEl.current.classList.add(`${ANTSOMI_COMPONENT_PREFIX_CLS}-text-editor-bubble-menu`);
14
11
  const { editor: currentEditor } = useCurrentEditor();
12
+ /**
13
+ * Tracks whether the bubble menu has been shown at least once.
14
+ * Used for lazy mounting optimization - children are only mounted after first show.
15
+ */
16
+ const [hasShown, setHasShown] = useState(false);
17
+ /**
18
+ * Ref to track hasShown synchronously (since state updates are async).
19
+ */
20
+ const hasShownRef = useRef(false);
21
+ /**
22
+ * Tracks whether we're in the first show process (to prevent empty flash).
23
+ * After first show completes, this flag is no longer used.
24
+ */
25
+ const isFirstShowRef = useRef(false);
26
+ /**
27
+ * The editor instance where the bubble menu plugin will be registered.
28
+ */
29
+ const pluginEditor = editor || currentEditor;
15
30
  useEffect(() => {
16
31
  if (typeof ref === 'function') {
17
32
  ref(menuEl.current);
@@ -20,66 +35,80 @@ export const BubbleMenu = forwardRef(({ pluginKey = 'bubbleMenu', editor, update
20
35
  ref.current = menuEl.current;
21
36
  }
22
37
  }, [ref]);
38
+ const bubbleMenuPluginProps = {
39
+ updateDelay,
40
+ resizeDelay,
41
+ pluginKey,
42
+ shouldShow,
43
+ getReferencedVirtualElement,
44
+ options: {
45
+ ...options,
46
+ strategy: options?.strategy || 'absolute',
47
+ placement: options?.placement || 'bottom',
48
+ offset: options?.offset || 10,
49
+ onHide: () => {
50
+ options?.onHide?.();
51
+ },
52
+ onShow: () => {
53
+ if (!hasShownRef.current) {
54
+ // First show: mark as first show and force hide until children mount
55
+ hasShownRef.current = true;
56
+ isFirstShowRef.current = true;
57
+ menuEl.current.style.visibility = 'hidden';
58
+ setHasShown(true);
59
+ }
60
+ options?.onShow?.();
61
+ },
62
+ },
63
+ };
64
+ const bubbleMenuPluginPropsRef = useRef(bubbleMenuPluginProps);
65
+ bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps;
66
+ useLayoutEffect(() => {
67
+ // After children mount in first show, make menuEl visible
68
+ if (isFirstShowRef.current) {
69
+ menuEl.current.style.visibility = 'visible';
70
+ isFirstShowRef.current = false;
71
+ }
72
+ });
23
73
  useDeepCompareEffect(() => {
24
- const bubbleMenuElement = menuEl.current;
25
- bubbleMenuElement.className = clsx(className, `${ANTSOMI_COMPONENT_PREFIX_CLS}-text-editor-bubble-menu`);
26
- if (editor?.isDestroyed || currentEditor?.isDestroyed) {
74
+ if (pluginEditor?.isDestroyed) {
27
75
  return;
28
76
  }
29
- const attachToEditor = editor || currentEditor;
30
- if (!attachToEditor) {
77
+ if (!pluginEditor) {
31
78
  // eslint-disable-next-line no-console
32
79
  console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.');
33
80
  return;
34
81
  }
82
+ const bubbleMenuElement = menuEl.current;
83
+ bubbleMenuElement.style.visibility = 'hidden';
84
+ bubbleMenuElement.style.position = 'absolute';
35
85
  const plugin = BubbleMenuPlugin({
36
- updateDelay,
37
- resizeDelay,
38
- editor: attachToEditor,
86
+ ...bubbleMenuPluginPropsRef.current,
87
+ appendTo,
88
+ editor: pluginEditor,
39
89
  element: bubbleMenuElement,
40
- container,
41
- pluginKey,
42
- options: {
43
- ...options,
44
- strategy: options?.strategy || 'absolute',
45
- placement: options?.placement || 'bottom',
46
- offset: options?.offset,
47
- onShow: () => {
48
- if (isMounted()) {
49
- setIsShowBubbleMenu(true);
50
- }
51
- options?.onShow?.();
52
- },
53
- onHide: () => {
54
- if (isMounted()) {
55
- setIsShowBubbleMenu(false);
56
- }
57
- options?.onHide?.();
58
- },
59
- },
60
- shouldShow,
61
90
  });
62
- attachToEditor.registerPlugin(plugin);
91
+ pluginEditor.registerPlugin(plugin);
92
+ const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey;
63
93
  return () => {
64
- attachToEditor.unregisterPlugin(pluginKey);
94
+ pluginEditor.unregisterPlugin(createdPluginKey);
65
95
  window.requestAnimationFrame(() => {
66
96
  if (bubbleMenuElement.parentNode) {
67
97
  bubbleMenuElement.parentNode.removeChild(bubbleMenuElement);
68
98
  }
69
99
  });
70
100
  };
71
- }, [
72
- editor,
73
- container,
74
- updateDelay,
75
- resizeDelay,
76
- pluginKey,
77
- options,
78
- className,
79
- currentEditor,
80
- isMounted,
81
- shouldShow,
82
- setIsShowBubbleMenu,
83
- ]);
101
+ }, [appendTo, pluginEditor]);
102
+ /**
103
+ * Lazy mounting optimization: only mount children after the bubble menu
104
+ * has been shown at least once. This significantly improves performance
105
+ * when many TextEditor instances exist (e.g., in lists).
106
+ *
107
+ * After first show, children remain mounted and the tiptap plugin
108
+ * handles visibility automatically via CSS, avoiding flash effects.
109
+ */
110
+ if (!hasShown) {
111
+ return null;
112
+ }
84
113
  return createPortal(_jsx("div", { ...restProps, children: children }), menuEl.current);
85
114
  });
@@ -44,7 +44,7 @@ export const EmojiList = forwardRef((props, ref) => {
44
44
  if (items.length <= 0) {
45
45
  return null;
46
46
  }
47
- return (_jsx(VirtualizedMenu, { className: `${COMPONENT_CLS}-emoji-list`, itemSize: ITEM_SIZE, selected: items.at(selectedIndex) ? [items[selectedIndex].slug] : [], onClick: item => {
47
+ return (_jsx(VirtualizedMenu, { className: `${COMPONENT_CLS}-emoji-list`, itemSize: ITEM_SIZE, selected: items.at(selectedIndex) ? [items[selectedIndex].slug] : [], focusedKey: items[selectedIndex]?.slug, onClick: item => {
48
48
  const idx = items.findIndex(i => i.slug === item.item.key);
49
49
  if (idx !== -1) {
50
50
  selectItem(idx);
@@ -1,4 +1,4 @@
1
1
  import { EmojiOptions } from '../../extensions/Emoji';
2
2
  export declare const suggestion: ({ container, }: {
3
- container?: HTMLElement | null;
3
+ container?: HTMLElement | (() => HTMLElement);
4
4
  }) => EmojiOptions['suggestion'];
@@ -10,7 +10,7 @@ const updatePosition = (editor, element, itemLength) => {
10
10
  getBoundingClientRect: () => posToDOMRect(editor.view, editor.state.selection.from, editor.state.selection.to),
11
11
  };
12
12
  computePosition(virtualElement, element, {
13
- placement: 'top-start',
13
+ placement: 'bottom-start',
14
14
  strategy: 'absolute',
15
15
  middleware: [shift(), flip()],
16
16
  }).then(({ x, y, strategy }) => {
@@ -45,7 +45,7 @@ export const suggestion = ({ container }) => ({
45
45
  }
46
46
  const rendererEl = safeGetRenndererEl();
47
47
  rendererEl.classList.add(`${COMPONENT_CLS}-emoji-popover`);
48
- (container || document.body).appendChild(rendererEl);
48
+ ((typeof container === 'function' ? container() : container) || document.body).appendChild(rendererEl);
49
49
  updatePosition(props.editor, rendererEl, props.items.length);
50
50
  },
51
51
  onUpdate(props) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { memo, useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Typography } from 'antd';
4
4
  import { SearchPopover, VirtualizedMenu } from '@antscorp/antsomi-ui/es/components/molecules';
5
5
  import { FontGroupTitle, PopoverTrigger } from './styled';
@@ -52,6 +52,7 @@ export const FontPopover = memo((props) => {
52
52
  const itemSize = 30;
53
53
  const itemSpacing = 4;
54
54
  const activeFontName = fontValue?.font.fontFamily.name;
55
+ const inputSearchRef = useRef(null);
55
56
  const [search, setSearch] = useState('');
56
57
  const [open, setOpen] = useState(false);
57
58
  const [fontItemsSize, setFontItemSize] = useDebounce(0, 200);
@@ -83,10 +84,19 @@ export const FontPopover = memo((props) => {
83
84
  const listSize = calInlineListSize(Math.min(items.length, 6), itemSize, itemSpacing);
84
85
  setFontItemSize(listSize);
85
86
  }, [items.length, setFontItemSize]);
87
+ useEffect(() => {
88
+ if (open) {
89
+ // Use setTimeout to ensure the popover content is fully mounted in the DOM
90
+ const timeoutId = setTimeout(() => {
91
+ inputSearchRef.current?.focus();
92
+ }, 0);
93
+ return () => clearTimeout(timeoutId);
94
+ }
95
+ }, [open]);
86
96
  return (_jsx(SearchPopover, { open: open, placement: "bottomLeft", onOpenChange: handleOpenChange, getPopupContainer: () => bubbleMenuContainer || document.body, inputSearchProps: {
87
97
  value: search,
88
98
  onAfterChange: setSearch,
89
- }, overlayClassName: "font-popover-overlay", content: _jsx("div", { className: "list-font-root", style: {
99
+ }, inputSearchRef: inputSearchRef, overlayClassName: "font-popover-overlay", content: _jsx("div", { className: "list-font-root", style: {
90
100
  height: `${fontItemsSize}px`,
91
101
  }, children: _jsx(VirtualizedMenu, { itemSize: itemSize, inlinePadding: 0, itemSpacing: itemSpacing, selectable: false, items: items }) }), children: _jsx(PopoverTrigger, { className: "font-popover-trigger", children: _jsx(Typography.Text, { ellipsis: { tooltip: true }, children: activeFontName }) }) }));
92
102
  });
@@ -0,0 +1,16 @@
1
+ import { Editor } from '@tiptap/core';
2
+ import React from 'react';
3
+ import type { LinkFormData } from '../../types';
4
+ export type LinkInsertFormProps = {
5
+ /** TipTap editor instance */
6
+ editor: Editor;
7
+ /** Form mode: insert or edit */
8
+ mode: 'insert' | 'edit';
9
+ /** Initial form data */
10
+ initialData: LinkFormData;
11
+ /** Callback when form is submitted */
12
+ onSubmit: () => void;
13
+ /** Callback when form is cancelled */
14
+ onCancel: () => void;
15
+ };
16
+ export declare const LinkInsertForm: React.FC<LinkInsertFormProps>;
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Checkbox, Flex, Input } from 'antd';
3
+ import { useState, useCallback, useRef, useEffect } from 'react';
4
+ export const LinkInsertForm = props => {
5
+ const { editor, mode, initialData, onSubmit, onCancel } = props;
6
+ const [url, setUrl] = useState(initialData.url);
7
+ const [text, setText] = useState(initialData.text);
8
+ const [openInNewTab, setOpenInNewTab] = useState(initialData.openInNewTab);
9
+ const urlInputRef = useRef(null);
10
+ // Auto-focus the URL input without scrolling
11
+ useEffect(() => {
12
+ urlInputRef.current?.focus({
13
+ preventScroll: true,
14
+ });
15
+ }, []);
16
+ const handleSubmit = useCallback(() => {
17
+ if (!url.trim()) {
18
+ // Don't submit if URL is empty
19
+ return;
20
+ }
21
+ // If text is different from initial, update the content
22
+ if (text !== initialData.text && text.trim()) {
23
+ editor
24
+ .chain()
25
+ .focus()
26
+ .setCustomLink({
27
+ attrs: {
28
+ href: url,
29
+ target: openInNewTab ? '_blank' : null,
30
+ },
31
+ content: text,
32
+ })
33
+ .run();
34
+ }
35
+ else {
36
+ // Just update the link attributes
37
+ editor
38
+ .chain()
39
+ .focus()
40
+ .setCustomLink({
41
+ attrs: {
42
+ href: url,
43
+ target: openInNewTab ? '_blank' : null,
44
+ },
45
+ })
46
+ .run();
47
+ }
48
+ onSubmit();
49
+ }, [editor, url, text, initialData.text, openInNewTab, onSubmit]);
50
+ const handleKeyDown = useCallback((e) => {
51
+ if (e.key === 'Enter' && !e.shiftKey) {
52
+ e.preventDefault();
53
+ handleSubmit();
54
+ }
55
+ else if (e.key === 'Escape') {
56
+ e.preventDefault();
57
+ onCancel();
58
+ }
59
+ }, [handleSubmit, onCancel]);
60
+ return (_jsxs(Flex, { vertical: true, gap: 8, children: [_jsxs(Flex, { gap: 8, vertical: true, children: [_jsx(Input, { ref: urlInputRef, placeholder: "URL", value: url, onChange: e => setUrl(e.target.value), onKeyDown: handleKeyDown }), _jsx(Input, { placeholder: "Text", value: text, onChange: e => setText(e.target.value), onKeyDown: handleKeyDown })] }), _jsx(Checkbox, { checked: openInNewTab, onChange: e => setOpenInNewTab(e.target.checked), children: "Open in new tab" }), _jsxs(Flex, { justify: "flex-end", gap: 8, children: [_jsx(Button, { onClick: onCancel, children: "Cancel" }), _jsx(Button, { type: "primary", onClick: handleSubmit, disabled: !url.trim(), children: mode === 'insert' ? 'Insert' : 'Update' })] })] }));
61
+ };
@@ -0,0 +1,2 @@
1
+ export { LinkInsertForm } from './LinkInsertForm';
2
+ export type { LinkInsertFormProps } from './LinkInsertForm';
@@ -0,0 +1 @@
1
+ export { LinkInsertForm } from './LinkInsertForm';
@@ -0,0 +1,9 @@
1
+ import { Editor } from '@tiptap/react';
2
+ import React from 'react';
3
+ export type LinkPopoverProps = {
4
+ /** TipTap editor instance */
5
+ editor: Editor;
6
+ /** Optional container to append the popover to. Defaults to document.body */
7
+ container?: HTMLElement | (() => HTMLElement);
8
+ };
9
+ export declare const LinkPopover: React.FC<LinkPopoverProps>;