@bitrix24/b24ui-nuxt 2.1.10 → 2.1.12

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 (65) hide show
  1. package/README-AI.md +1 -1
  2. package/dist/meta.d.mts +98220 -780
  3. package/dist/meta.mjs +98220 -780
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +1 -1
  6. package/dist/runtime/components/ContextMenu.d.vue.ts +6 -2
  7. package/dist/runtime/components/ContextMenu.vue.d.ts +6 -2
  8. package/dist/runtime/components/ContextMenuContent.vue +2 -2
  9. package/dist/runtime/components/DropdownMenu.d.vue.ts +6 -2
  10. package/dist/runtime/components/DropdownMenu.vue.d.ts +6 -2
  11. package/dist/runtime/components/DropdownMenuContent.vue +2 -2
  12. package/dist/runtime/components/Editor.d.vue.ts +87 -0
  13. package/dist/runtime/components/Editor.vue +185 -0
  14. package/dist/runtime/components/Editor.vue.d.ts +87 -0
  15. package/dist/runtime/components/EditorDragHandle.d.vue.ts +60 -0
  16. package/dist/runtime/components/EditorDragHandle.vue +128 -0
  17. package/dist/runtime/components/EditorDragHandle.vue.d.ts +60 -0
  18. package/dist/runtime/components/EditorEmojiMenu.d.vue.ts +35 -0
  19. package/dist/runtime/components/EditorEmojiMenu.vue +70 -0
  20. package/dist/runtime/components/EditorEmojiMenu.vue.d.ts +35 -0
  21. package/dist/runtime/components/EditorMentionMenu.d.vue.ts +39 -0
  22. package/dist/runtime/components/EditorMentionMenu.vue +74 -0
  23. package/dist/runtime/components/EditorMentionMenu.vue.d.ts +39 -0
  24. package/dist/runtime/components/EditorSuggestionMenu.d.vue.ts +52 -0
  25. package/dist/runtime/components/EditorSuggestionMenu.vue +79 -0
  26. package/dist/runtime/components/EditorSuggestionMenu.vue.d.ts +52 -0
  27. package/dist/runtime/components/EditorToolbar.d.vue.ts +80 -0
  28. package/dist/runtime/components/EditorToolbar.vue +239 -0
  29. package/dist/runtime/components/EditorToolbar.vue.d.ts +80 -0
  30. package/dist/runtime/components/FormField.vue +2 -2
  31. package/dist/runtime/components/InputMenu.d.vue.ts +2 -0
  32. package/dist/runtime/components/InputMenu.vue +14 -1
  33. package/dist/runtime/components/InputMenu.vue.d.ts +2 -0
  34. package/dist/runtime/components/Pagination.d.vue.ts +0 -1
  35. package/dist/runtime/components/Pagination.vue.d.ts +0 -1
  36. package/dist/runtime/components/Select.d.vue.ts +2 -0
  37. package/dist/runtime/components/Select.vue +14 -1
  38. package/dist/runtime/components/Select.vue.d.ts +2 -0
  39. package/dist/runtime/components/SelectMenu.d.vue.ts +2 -0
  40. package/dist/runtime/components/SelectMenu.vue +14 -1
  41. package/dist/runtime/components/SelectMenu.vue.d.ts +2 -0
  42. package/dist/runtime/components/color-mode/ColorModeSelect.vue +1 -0
  43. package/dist/runtime/components/locale/LocaleSelect.vue +1 -0
  44. package/dist/runtime/components/prose/Accordion.vue +1 -1
  45. package/dist/runtime/composables/defineShortcuts.d.ts +1 -0
  46. package/dist/runtime/composables/defineShortcuts.js +60 -13
  47. package/dist/runtime/composables/index.d.ts +1 -0
  48. package/dist/runtime/composables/index.js +1 -0
  49. package/dist/runtime/composables/useEditorMenu.d.ts +64 -0
  50. package/dist/runtime/composables/useEditorMenu.js +448 -0
  51. package/dist/runtime/composables/useSpeechRecognition.d.ts +122 -0
  52. package/dist/runtime/composables/useSpeechRecognition.js +166 -0
  53. package/dist/runtime/dictionary/icons.d.ts +1 -0
  54. package/dist/runtime/dictionary/icons.js +5 -3
  55. package/dist/runtime/types/editor.d.ts +69 -0
  56. package/dist/runtime/types/editor.js +0 -0
  57. package/dist/runtime/types/index.d.ts +7 -0
  58. package/dist/runtime/types/index.js +7 -0
  59. package/dist/runtime/utils/editor.d.ts +69 -0
  60. package/dist/runtime/utils/editor.js +364 -0
  61. package/dist/runtime/vue/components/color-mode/ColorModeSelect.vue +1 -0
  62. package/dist/shared/{b24ui-nuxt.BCphUjPy.mjs → b24ui-nuxt.C8MyFqPm.mjs} +185 -1
  63. package/dist/unplugin.mjs +1 -1
  64. package/dist/vite.mjs +1 -1
  65. package/package.json +16 -3
@@ -4,6 +4,30 @@ import { useKbd } from "./useKbd.js";
4
4
  const chainedShortcutRegex = /^[^-]+.*-.*[^-]+$/;
5
5
  const combinedShortcutRegex = /^[^_]+.*_.*[^_]+$/;
6
6
  const shiftableKeys = ["arrowleft", "arrowright", "arrowup", "arrowright", "tab", "escape", "enter", "backspace"];
7
+ function convertKeyToCode(key) {
8
+ if (/^[a-z]$/i.test(key)) {
9
+ return `Key${key.toUpperCase()}`;
10
+ }
11
+ if (/^\d$/.test(key)) {
12
+ return `Digit${key}`;
13
+ }
14
+ if (/^f\d+$/i.test(key)) {
15
+ return key.toUpperCase();
16
+ }
17
+ const specialKeys = {
18
+ space: "Space",
19
+ enter: "Enter",
20
+ escape: "Escape",
21
+ tab: "Tab",
22
+ backspace: "Backspace",
23
+ delete: "Delete",
24
+ arrowup: "ArrowUp",
25
+ arrowdown: "ArrowDown",
26
+ arrowleft: "ArrowLeft",
27
+ arrowright: "ArrowRight"
28
+ };
29
+ return specialKeys[key.toLowerCase()] || key;
30
+ }
7
31
  export function extractShortcuts(items) {
8
32
  const shortcuts = {};
9
33
  function traverse(items2) {
@@ -31,14 +55,16 @@ export function defineShortcuts(config, options = {}) {
31
55
  const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800);
32
56
  const { macOS } = useKbd();
33
57
  const activeElement = useActiveElement();
58
+ const layoutIndependent = options.layoutIndependent ?? false;
59
+ const shiftableCodes = shiftableKeys.map((k) => convertKeyToCode(k));
34
60
  const onKeyDown = (e) => {
35
61
  if (!e.key) {
36
62
  return;
37
63
  }
38
- const alphabetKey = /^[a-z]{1}$/i.test(e.key);
39
- const shiftableKey = shiftableKeys.includes(e.key.toLowerCase());
64
+ const alphabetKey = layoutIndependent ? /^Key[A-Z]$/i.test(e.code) : /^[a-z]{1}$/i.test(e.key);
65
+ const shiftableKey = layoutIndependent ? shiftableCodes.includes(e.code) : shiftableKeys.includes(e.key.toLowerCase());
40
66
  let chainedKey;
41
- chainedInputs.value.push(e.key);
67
+ chainedInputs.value.push(layoutIndependent ? e.code : e.key);
42
68
  if (chainedInputs.value.length >= 2) {
43
69
  chainedKey = chainedInputs.value.slice(-2).join("-");
44
70
  for (const shortcut of shortcuts.value.filter((s) => s.chained)) {
@@ -54,8 +80,14 @@ export function defineShortcuts(config, options = {}) {
54
80
  }
55
81
  }
56
82
  for (const shortcut of shortcuts.value.filter((s) => !s.chained)) {
57
- if (e.key.toLowerCase() !== shortcut.key) {
58
- continue;
83
+ if (layoutIndependent) {
84
+ if (e.code !== shortcut.key) {
85
+ continue;
86
+ }
87
+ } else {
88
+ if (e.key.toLowerCase() !== shortcut.key) {
89
+ continue;
90
+ }
59
91
  }
60
92
  if (e.metaKey !== shortcut.metaKey) {
61
93
  continue;
@@ -98,17 +130,32 @@ export function defineShortcuts(config, options = {}) {
98
130
  }
99
131
  const chained = key.includes("-") && key !== "-" && !key.includes("_");
100
132
  if (chained) {
101
- shortcut = {
102
- key: key.toLowerCase(),
103
- metaKey: false,
104
- ctrlKey: false,
105
- shiftKey: false,
106
- altKey: false
107
- };
133
+ if (layoutIndependent) {
134
+ const parts = key.split("-").map((p) => convertKeyToCode(p));
135
+ shortcut = {
136
+ key: parts.join("-"),
137
+ metaKey: false,
138
+ ctrlKey: false,
139
+ shiftKey: false,
140
+ altKey: false
141
+ };
142
+ } else {
143
+ shortcut = {
144
+ key: key.toLowerCase(),
145
+ metaKey: false,
146
+ ctrlKey: false,
147
+ shiftKey: false,
148
+ altKey: false
149
+ };
150
+ }
108
151
  } else {
109
152
  const keySplit = key.toLowerCase().split("_").map((k) => k);
153
+ let baseKey = keySplit.filter((k) => !["meta", "command", "ctrl", "shift", "alt", "option"].includes(k)).join("_");
154
+ if (layoutIndependent) {
155
+ baseKey = convertKeyToCode(baseKey);
156
+ }
110
157
  shortcut = {
111
- key: keySplit.filter((k) => !["meta", "command", "ctrl", "shift", "alt", "option"].includes(k)).join("_"),
158
+ key: baseKey,
112
159
  metaKey: keySplit.includes("meta") || keySplit.includes("command"),
113
160
  ctrlKey: keySplit.includes("ctrl"),
114
161
  shiftKey: keySplit.includes("shift"),
@@ -6,3 +6,4 @@ export * from './useOverlay';
6
6
  export * from './useResizable';
7
7
  export * from './useScrollspy';
8
8
  export * from './useToast';
9
+ export * from './useSpeechRecognition';
@@ -6,3 +6,4 @@ export * from "./useOverlay.js";
6
6
  export * from "./useResizable.js";
7
7
  export * from "./useScrollspy.js";
8
8
  export * from "./useToast.js";
9
+ export * from "./useSpeechRecognition.js";
@@ -0,0 +1,64 @@
1
+ import type { Ref, ComputedRef, MaybeRef } from 'vue';
2
+ import type { Editor } from '@tiptap/vue-3';
3
+ import type { FloatingUIOptions } from '../types/editor';
4
+ export interface EditorMenuOptions<T = any> {
5
+ editor: Editor;
6
+ /**
7
+ * The trigger character (e.g., '/', '@', ':')
8
+ */
9
+ char: string;
10
+ /**
11
+ * Plugin key to identify this menu
12
+ */
13
+ pluginKey: string;
14
+ /**
15
+ * The items to display (can be a flat array or grouped)
16
+ */
17
+ items?: MaybeRef<T[] | T[][] | undefined>;
18
+ /**
19
+ * Fields to filter items by.
20
+ * @defaultValue ['label']
21
+ */
22
+ filterFields?: string[];
23
+ /**
24
+ * Function to filter items based on query
25
+ */
26
+ filter?: (items: T[], query: string) => T[];
27
+ /**
28
+ * Maximum number of items to display
29
+ * @defaultValue 42
30
+ */
31
+ limit?: number;
32
+ /**
33
+ * Function to execute when an item is selected
34
+ */
35
+ onSelect: (editor: Editor, range: any, item: T) => void;
36
+ /**
37
+ * Function to render each menu item
38
+ */
39
+ renderItem: (item: T, b24ui: ComputedRef<any>) => any;
40
+ /**
41
+ * The options for positioning the menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, size, autoPlacement, hide, and inline middleware.
42
+ * @defaultValue { strategy: 'absolute', placement: 'bottom-start', offset: 8, shift: { padding: 8 } }
43
+ * @see https://floating-ui.com/docs/computePosition#options
44
+ */
45
+ options?: FloatingUIOptions;
46
+ /**
47
+ * The DOM element to append the menu to. Default is the editor's parent element.
48
+ *
49
+ * Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.
50
+ *
51
+ * @type {HTMLElement}
52
+ * @default editor.view.dom.parentElement
53
+ */
54
+ appendTo?: HTMLElement | (() => HTMLElement);
55
+ /**
56
+ * UI styles computed ref
57
+ */
58
+ b24ui: ComputedRef<any>;
59
+ }
60
+ export declare function useEditorMenu<T = any>(options: EditorMenuOptions<T>): {
61
+ plugin: import("prosemirror-state").Plugin<any>;
62
+ destroy: () => void;
63
+ filteredItems: Ref<T[], T[]>;
64
+ };
@@ -0,0 +1,448 @@
1
+ import { ref, h, computed, unref } from "vue";
2
+ import { defu } from "defu";
3
+ import { useFilter } from "reka-ui";
4
+ import { computePosition } from "@floating-ui/dom";
5
+ import { VueRenderer } from "@tiptap/vue-3";
6
+ import Suggestion from "@tiptap/suggestion";
7
+ import { PluginKey } from "@tiptap/pm/state";
8
+ import { buildFloatingUIMiddleware } from "../utils/editor.js";
9
+ import { get, isArrayOfArray } from "../utils/index.js";
10
+ export function useEditorMenu(options) {
11
+ const filteredItems = ref([]);
12
+ const selectedIndex = ref(0);
13
+ const menuState = ref("closed");
14
+ let renderer = null;
15
+ let element = null;
16
+ let handleMouseDown = null;
17
+ let commandFn = null;
18
+ let keyDownHandler = null;
19
+ let globalKeyHandler = null;
20
+ let blurHandler = null;
21
+ let triggerClientRect = null;
22
+ let handleHover = null;
23
+ let scrollHandler = null;
24
+ const { contains } = useFilter({ sensitivity: "base" });
25
+ const cleanupMenu = () => {
26
+ if (menuState.value === "closed") return;
27
+ menuState.value = "closed";
28
+ if (globalKeyHandler) {
29
+ document.removeEventListener("keydown", globalKeyHandler, true);
30
+ globalKeyHandler = null;
31
+ }
32
+ if (blurHandler) {
33
+ options.editor.view.dom.removeEventListener("blur", blurHandler);
34
+ blurHandler = null;
35
+ }
36
+ if (scrollHandler) {
37
+ window.removeEventListener("scroll", scrollHandler, true);
38
+ scrollHandler = null;
39
+ }
40
+ if (element && handleMouseDown) {
41
+ element.removeEventListener("mousedown", handleMouseDown);
42
+ handleMouseDown = null;
43
+ }
44
+ if (renderer) {
45
+ renderer.destroy();
46
+ renderer = null;
47
+ }
48
+ if (element) {
49
+ element.remove();
50
+ element = null;
51
+ }
52
+ };
53
+ const filterFields = options.filterFields ?? ["label"];
54
+ const defaultFilter = (items2, query) => {
55
+ if (!query) return items2;
56
+ return items2.filter((item) => {
57
+ return filterFields.some((field) => {
58
+ const value = get(item, field);
59
+ if (value === void 0 || value === null) return false;
60
+ const stringValue = Array.isArray(value) ? value.join(" ") : String(value);
61
+ return contains(stringValue, query) || contains(stringValue.replace(/[\s_-]/g, ""), query);
62
+ });
63
+ });
64
+ };
65
+ const filter = options.filter || defaultFilter;
66
+ const limit = options.limit ?? 42;
67
+ const groups = computed(() => {
68
+ const items2 = unref(options.items);
69
+ return items2?.length ? isArrayOfArray(items2) ? items2 : [items2] : [];
70
+ });
71
+ const items = computed(() => groups.value.flatMap((group) => group));
72
+ const filteredGroups = computed(() => {
73
+ if (!filteredItems.value.length) return [];
74
+ return groups.value.map((group) => group.filter((item) => filteredItems.value.includes(item))).filter((group) => group.length > 0);
75
+ });
76
+ const selectableItems = computed(() => {
77
+ return filteredItems.value.filter((item) => {
78
+ return item.type !== "label" && item.type !== "separator";
79
+ });
80
+ });
81
+ const floatingUIOptions = defu(options.options, {
82
+ strategy: "absolute",
83
+ placement: "bottom-start",
84
+ offset: 8,
85
+ flip: {},
86
+ shift: { padding: 8 },
87
+ size: false,
88
+ autoPlacement: false,
89
+ hide: false,
90
+ inline: false
91
+ });
92
+ const middleware = buildFloatingUIMiddleware(floatingUIOptions);
93
+ const updatePosition = (element2) => {
94
+ if (!triggerClientRect) return;
95
+ const rect = triggerClientRect();
96
+ if (!rect) return;
97
+ const virtualElement = {
98
+ getBoundingClientRect: () => rect
99
+ };
100
+ computePosition(virtualElement, element2, {
101
+ placement: floatingUIOptions.placement,
102
+ strategy: floatingUIOptions.strategy,
103
+ middleware
104
+ }).then(({ x, y, strategy }) => {
105
+ element2.style.width = "max-content";
106
+ element2.style.position = strategy;
107
+ element2.style.top = "0";
108
+ element2.style.left = "0";
109
+ element2.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;
110
+ });
111
+ };
112
+ const MenuComponent = {
113
+ props: {
114
+ groups: { type: Array, required: true },
115
+ selectedIndex: { type: Number, required: true },
116
+ onSelect: { type: Function, required: true },
117
+ onHover: { type: Function, required: true },
118
+ state: { type: String, required: true }
119
+ },
120
+ setup(menuProps) {
121
+ function handleClick(e, item, selectableIndex) {
122
+ e.preventDefault();
123
+ menuProps.onSelect(item, selectableIndex);
124
+ }
125
+ function handleMouseEnter(selectableIndex) {
126
+ menuProps.onHover(selectableIndex);
127
+ }
128
+ return () => {
129
+ const groupsData = menuProps.groups;
130
+ const selectableIndexMap = /* @__PURE__ */ new Map();
131
+ let selectableCounter = 0;
132
+ for (const group of groupsData) {
133
+ for (const item of group) {
134
+ const itemData = item;
135
+ if (itemData.type !== "label" && itemData.type !== "separator") {
136
+ selectableIndexMap.set(item, selectableCounter++);
137
+ }
138
+ }
139
+ }
140
+ return h("div", {
141
+ "class": options.b24ui.value.content(),
142
+ "role": "listbox",
143
+ "data-state": menuProps.state
144
+ }, [
145
+ h("div", {
146
+ class: options.b24ui.value.viewport(),
147
+ role: "presentation"
148
+ }, groupsData.map(
149
+ (group, groupIndex) => h("div", {
150
+ key: `group-${groupIndex}`,
151
+ class: options.b24ui.value.group(),
152
+ role: "group"
153
+ }, group.map((item, itemInGroupIndex) => {
154
+ const itemData = item;
155
+ if (itemData.type === "label") {
156
+ return h("div", {
157
+ key: `label-${groupIndex}-${itemInGroupIndex}`,
158
+ class: options.b24ui.value.label({ class: itemData.class })
159
+ }, options.renderItem(item, options.b24ui));
160
+ }
161
+ if (itemData.type === "separator") {
162
+ return h("div", {
163
+ key: `separator-${groupIndex}-${itemInGroupIndex}`,
164
+ class: options.b24ui.value.separator({ class: itemData.class }),
165
+ role: "separator"
166
+ });
167
+ }
168
+ const selectableIndex = selectableIndexMap.get(item);
169
+ const isHighlighted = selectableIndex === menuProps.selectedIndex;
170
+ return h("div", {
171
+ "key": `item-${selectableIndex}`,
172
+ "class": options.b24ui.value.item({ class: itemData.class, active: false }),
173
+ "role": "option",
174
+ "aria-selected": isHighlighted,
175
+ "data-highlighted": isHighlighted ? "" : void 0,
176
+ "data-disabled": itemData.disabled ? "" : void 0,
177
+ "onMousedown": (e) => handleClick(e, item, selectableIndex),
178
+ "onMouseenter": () => handleMouseEnter(selectableIndex),
179
+ "ref": (el) => {
180
+ if (el && isHighlighted) {
181
+ el.scrollIntoView({ block: "nearest", inline: "nearest" });
182
+ }
183
+ }
184
+ }, options.renderItem(item, options.b24ui));
185
+ }))
186
+ ))
187
+ ]);
188
+ };
189
+ }
190
+ };
191
+ const pluginKeyInstance = typeof options.pluginKey === "string" ? new PluginKey(options.pluginKey) : options.pluginKey;
192
+ const plugin = Suggestion({
193
+ pluginKey: pluginKeyInstance,
194
+ editor: options.editor,
195
+ char: options.char,
196
+ items: ({ query }) => {
197
+ const filtered = filter(items.value, query);
198
+ return filtered.slice(0, limit);
199
+ },
200
+ command: ({ editor, range, props }) => {
201
+ options.onSelect(editor, range, props);
202
+ },
203
+ render: () => {
204
+ keyDownHandler = (props) => {
205
+ const { event } = props;
206
+ if (!renderer || !selectableItems.value.length) {
207
+ return false;
208
+ }
209
+ if (event.key === "Escape") {
210
+ cleanupMenu();
211
+ return true;
212
+ }
213
+ if (event.key === "ArrowUp") {
214
+ selectedIndex.value = (selectedIndex.value + selectableItems.value.length - 1) % selectableItems.value.length;
215
+ renderer?.updateProps({
216
+ groups: filteredGroups.value,
217
+ selectedIndex: selectedIndex.value,
218
+ onSelect: commandFn,
219
+ onHover: handleHover,
220
+ state: menuState.value
221
+ });
222
+ return true;
223
+ }
224
+ if (event.key === "ArrowDown") {
225
+ selectedIndex.value = (selectedIndex.value + 1) % selectableItems.value.length;
226
+ renderer?.updateProps({
227
+ groups: filteredGroups.value,
228
+ selectedIndex: selectedIndex.value,
229
+ onSelect: commandFn,
230
+ onHover: handleHover,
231
+ state: menuState.value
232
+ });
233
+ return true;
234
+ }
235
+ if (event.key === "Enter" || event.key === "Tab") {
236
+ const selectedItem = selectableItems.value[selectedIndex.value];
237
+ if (selectedItem && commandFn) {
238
+ commandFn(selectedItem);
239
+ }
240
+ return true;
241
+ }
242
+ return false;
243
+ };
244
+ const handlers = {
245
+ onStart: (suggestionProps) => {
246
+ filteredItems.value = suggestionProps.items;
247
+ selectedIndex.value = 0;
248
+ commandFn = (item) => suggestionProps.command(item);
249
+ triggerClientRect = suggestionProps.clientRect;
250
+ if (!filteredItems.value.length) {
251
+ return;
252
+ }
253
+ menuState.value = "open";
254
+ globalKeyHandler = (e) => {
255
+ if (keyDownHandler) {
256
+ const handled = keyDownHandler({ event: e });
257
+ if (handled) {
258
+ e.preventDefault();
259
+ e.stopPropagation();
260
+ }
261
+ }
262
+ };
263
+ document.addEventListener("keydown", globalKeyHandler, true);
264
+ blurHandler = () => {
265
+ setTimeout(() => {
266
+ if (menuState.value === "open") {
267
+ const tr = suggestionProps.editor.view.state.tr.setMeta(pluginKeyInstance, { exit: true });
268
+ suggestionProps.editor.view.dispatch(tr);
269
+ }
270
+ }, 0);
271
+ };
272
+ suggestionProps.editor.view.dom.addEventListener("blur", blurHandler);
273
+ scrollHandler = () => {
274
+ if (element) {
275
+ updatePosition(element);
276
+ }
277
+ };
278
+ window.addEventListener("scroll", scrollHandler, true);
279
+ handleHover = (index) => {
280
+ selectedIndex.value = index;
281
+ if (renderer) {
282
+ renderer.updateProps({
283
+ groups: filteredGroups.value,
284
+ selectedIndex: index,
285
+ onSelect: commandFn,
286
+ onHover: handleHover,
287
+ state: menuState.value
288
+ });
289
+ }
290
+ };
291
+ renderer = new VueRenderer(MenuComponent, {
292
+ props: {
293
+ groups: filteredGroups.value,
294
+ selectedIndex: selectedIndex.value,
295
+ onSelect: commandFn,
296
+ onHover: handleHover,
297
+ state: menuState.value
298
+ },
299
+ editor: suggestionProps.editor
300
+ });
301
+ element = document.createElement("div");
302
+ element.style.position = floatingUIOptions.strategy;
303
+ element.style.zIndex = "50";
304
+ handleMouseDown = (e) => {
305
+ e.preventDefault();
306
+ };
307
+ element.addEventListener("mousedown", handleMouseDown);
308
+ const appendToElement = typeof options.appendTo === "function" ? options.appendTo() : options.appendTo;
309
+ (appendToElement ?? suggestionProps.editor.view.dom.parentElement)?.appendChild(element);
310
+ if (renderer.element) {
311
+ element.appendChild(renderer.element);
312
+ }
313
+ updatePosition(element);
314
+ },
315
+ onUpdate: (suggestionProps) => {
316
+ filteredItems.value = suggestionProps.items;
317
+ commandFn = (item) => suggestionProps.command(item);
318
+ if (selectedIndex.value >= selectableItems.value.length) {
319
+ selectedIndex.value = Math.max(0, selectableItems.value.length - 1);
320
+ }
321
+ if (!filteredItems.value.length) {
322
+ cleanupMenu();
323
+ return;
324
+ }
325
+ if (!renderer) {
326
+ menuState.value = "open";
327
+ if (!globalKeyHandler) {
328
+ globalKeyHandler = (e) => {
329
+ if (keyDownHandler) {
330
+ const handled = keyDownHandler({ event: e });
331
+ if (handled) {
332
+ e.preventDefault();
333
+ e.stopPropagation();
334
+ }
335
+ }
336
+ };
337
+ document.addEventListener("keydown", globalKeyHandler, true);
338
+ }
339
+ if (!blurHandler) {
340
+ blurHandler = () => {
341
+ setTimeout(() => {
342
+ if (menuState.value === "open") {
343
+ const tr = suggestionProps.editor.view.state.tr.setMeta(pluginKeyInstance, { exit: true });
344
+ suggestionProps.editor.view.dispatch(tr);
345
+ }
346
+ }, 0);
347
+ };
348
+ suggestionProps.editor.view.dom.addEventListener("blur", blurHandler);
349
+ }
350
+ if (!scrollHandler) {
351
+ scrollHandler = () => {
352
+ if (element) {
353
+ updatePosition(element);
354
+ }
355
+ };
356
+ window.addEventListener("scroll", scrollHandler, true);
357
+ }
358
+ handleHover = (index) => {
359
+ selectedIndex.value = index;
360
+ if (renderer) {
361
+ renderer.updateProps({
362
+ groups: filteredGroups.value,
363
+ selectedIndex: index,
364
+ onSelect: commandFn,
365
+ onHover: handleHover,
366
+ state: menuState.value
367
+ });
368
+ }
369
+ };
370
+ renderer = new VueRenderer(MenuComponent, {
371
+ props: {
372
+ groups: filteredGroups.value,
373
+ selectedIndex: selectedIndex.value,
374
+ onSelect: commandFn,
375
+ onHover: handleHover,
376
+ state: menuState.value
377
+ },
378
+ editor: suggestionProps.editor
379
+ });
380
+ element = document.createElement("div");
381
+ element.style.position = floatingUIOptions.strategy;
382
+ element.style.zIndex = "50";
383
+ handleMouseDown = (e) => {
384
+ e.preventDefault();
385
+ };
386
+ element.addEventListener("mousedown", handleMouseDown);
387
+ const appendToElement = typeof options.appendTo === "function" ? options.appendTo() : options.appendTo;
388
+ (appendToElement ?? suggestionProps.editor.view.dom.parentElement)?.appendChild(element);
389
+ if (renderer.element) {
390
+ element.appendChild(renderer.element);
391
+ }
392
+ } else {
393
+ renderer.updateProps({
394
+ groups: filteredGroups.value,
395
+ selectedIndex: selectedIndex.value,
396
+ onSelect: commandFn,
397
+ onHover: (index) => {
398
+ selectedIndex.value = index;
399
+ },
400
+ state: menuState.value
401
+ });
402
+ }
403
+ if (element) {
404
+ updatePosition(element);
405
+ }
406
+ },
407
+ onKeyDown: keyDownHandler,
408
+ onExit: () => {
409
+ cleanupMenu();
410
+ triggerClientRect = null;
411
+ }
412
+ };
413
+ return handlers;
414
+ }
415
+ });
416
+ const destroy = () => {
417
+ menuState.value = "closed";
418
+ if (globalKeyHandler) {
419
+ document.removeEventListener("keydown", globalKeyHandler, true);
420
+ globalKeyHandler = null;
421
+ }
422
+ if (blurHandler) {
423
+ options.editor.view.dom.removeEventListener("blur", blurHandler);
424
+ blurHandler = null;
425
+ }
426
+ if (scrollHandler) {
427
+ window.removeEventListener("scroll", scrollHandler, true);
428
+ scrollHandler = null;
429
+ }
430
+ if (element && handleMouseDown) {
431
+ element.removeEventListener("mousedown", handleMouseDown);
432
+ handleMouseDown = null;
433
+ }
434
+ if (renderer) {
435
+ renderer.destroy();
436
+ renderer = null;
437
+ }
438
+ if (element) {
439
+ element.remove();
440
+ element = null;
441
+ }
442
+ };
443
+ return {
444
+ plugin,
445
+ destroy,
446
+ filteredItems
447
+ };
448
+ }