@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.
- package/README-AI.md +1 -1
- package/dist/meta.d.mts +98220 -780
- package/dist/meta.mjs +98220 -780
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/ContextMenu.d.vue.ts +6 -2
- package/dist/runtime/components/ContextMenu.vue.d.ts +6 -2
- package/dist/runtime/components/ContextMenuContent.vue +2 -2
- package/dist/runtime/components/DropdownMenu.d.vue.ts +6 -2
- package/dist/runtime/components/DropdownMenu.vue.d.ts +6 -2
- package/dist/runtime/components/DropdownMenuContent.vue +2 -2
- package/dist/runtime/components/Editor.d.vue.ts +87 -0
- package/dist/runtime/components/Editor.vue +185 -0
- package/dist/runtime/components/Editor.vue.d.ts +87 -0
- package/dist/runtime/components/EditorDragHandle.d.vue.ts +60 -0
- package/dist/runtime/components/EditorDragHandle.vue +128 -0
- package/dist/runtime/components/EditorDragHandle.vue.d.ts +60 -0
- package/dist/runtime/components/EditorEmojiMenu.d.vue.ts +35 -0
- package/dist/runtime/components/EditorEmojiMenu.vue +70 -0
- package/dist/runtime/components/EditorEmojiMenu.vue.d.ts +35 -0
- package/dist/runtime/components/EditorMentionMenu.d.vue.ts +39 -0
- package/dist/runtime/components/EditorMentionMenu.vue +74 -0
- package/dist/runtime/components/EditorMentionMenu.vue.d.ts +39 -0
- package/dist/runtime/components/EditorSuggestionMenu.d.vue.ts +52 -0
- package/dist/runtime/components/EditorSuggestionMenu.vue +79 -0
- package/dist/runtime/components/EditorSuggestionMenu.vue.d.ts +52 -0
- package/dist/runtime/components/EditorToolbar.d.vue.ts +80 -0
- package/dist/runtime/components/EditorToolbar.vue +239 -0
- package/dist/runtime/components/EditorToolbar.vue.d.ts +80 -0
- package/dist/runtime/components/FormField.vue +2 -2
- package/dist/runtime/components/InputMenu.d.vue.ts +2 -0
- package/dist/runtime/components/InputMenu.vue +14 -1
- package/dist/runtime/components/InputMenu.vue.d.ts +2 -0
- package/dist/runtime/components/Pagination.d.vue.ts +0 -1
- package/dist/runtime/components/Pagination.vue.d.ts +0 -1
- package/dist/runtime/components/Select.d.vue.ts +2 -0
- package/dist/runtime/components/Select.vue +14 -1
- package/dist/runtime/components/Select.vue.d.ts +2 -0
- package/dist/runtime/components/SelectMenu.d.vue.ts +2 -0
- package/dist/runtime/components/SelectMenu.vue +14 -1
- package/dist/runtime/components/SelectMenu.vue.d.ts +2 -0
- package/dist/runtime/components/color-mode/ColorModeSelect.vue +1 -0
- package/dist/runtime/components/locale/LocaleSelect.vue +1 -0
- package/dist/runtime/components/prose/Accordion.vue +1 -1
- package/dist/runtime/composables/defineShortcuts.d.ts +1 -0
- package/dist/runtime/composables/defineShortcuts.js +60 -13
- package/dist/runtime/composables/index.d.ts +1 -0
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/useEditorMenu.d.ts +64 -0
- package/dist/runtime/composables/useEditorMenu.js +448 -0
- package/dist/runtime/composables/useSpeechRecognition.d.ts +122 -0
- package/dist/runtime/composables/useSpeechRecognition.js +166 -0
- package/dist/runtime/dictionary/icons.d.ts +1 -0
- package/dist/runtime/dictionary/icons.js +5 -3
- package/dist/runtime/types/editor.d.ts +69 -0
- package/dist/runtime/types/editor.js +0 -0
- package/dist/runtime/types/index.d.ts +7 -0
- package/dist/runtime/types/index.js +7 -0
- package/dist/runtime/utils/editor.d.ts +69 -0
- package/dist/runtime/utils/editor.js +364 -0
- package/dist/runtime/vue/components/color-mode/ColorModeSelect.vue +1 -0
- package/dist/shared/{b24ui-nuxt.BCphUjPy.mjs → b24ui-nuxt.C8MyFqPm.mjs} +185 -1
- package/dist/unplugin.mjs +1 -1
- package/dist/vite.mjs +1 -1
- 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 (
|
|
58
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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:
|
|
158
|
+
key: baseKey,
|
|
112
159
|
metaKey: keySplit.includes("meta") || keySplit.includes("command"),
|
|
113
160
|
ctrlKey: keySplit.includes("ctrl"),
|
|
114
161
|
shiftKey: keySplit.includes("shift"),
|
|
@@ -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
|
+
}
|