@use-kona/editor 0.1.16 → 0.1.18

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 (30) hide show
  1. package/dist/plugins/CommandsPlugin/CommandsPlugin.d.ts +2 -3
  2. package/dist/plugins/CommandsPlugin/CommandsPlugin.js +5 -15
  3. package/dist/plugins/CommandsPlugin/Menu.d.ts +3 -3
  4. package/dist/plugins/CommandsPlugin/Menu.js +227 -69
  5. package/dist/plugins/CommandsPlugin/index.d.ts +1 -1
  6. package/dist/plugins/CommandsPlugin/resolveCommands.d.ts +33 -0
  7. package/dist/plugins/CommandsPlugin/resolveCommands.js +141 -0
  8. package/dist/plugins/CommandsPlugin/resolveCommands.spec.d.ts +1 -0
  9. package/dist/plugins/CommandsPlugin/resolveCommands.spec.js +323 -0
  10. package/dist/plugins/CommandsPlugin/styles.module.js +3 -0
  11. package/dist/plugins/CommandsPlugin/styles_module.css +32 -8
  12. package/dist/plugins/CommandsPlugin/types.d.ts +14 -2
  13. package/dist/plugins/CommandsPlugin/useResolvedCommands.d.ts +12 -0
  14. package/dist/plugins/CommandsPlugin/useResolvedCommands.js +66 -0
  15. package/dist/plugins/DnDPlugin/DnDPlugin.d.ts +7 -0
  16. package/dist/plugins/DnDPlugin/DnDPlugin.js +34 -7
  17. package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +6 -1
  18. package/dist/plugins/index.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/plugins/CommandsPlugin/CommandsPlugin.tsx +5 -25
  21. package/src/plugins/CommandsPlugin/Menu.tsx +252 -86
  22. package/src/plugins/CommandsPlugin/index.ts +1 -1
  23. package/src/plugins/CommandsPlugin/resolveCommands.spec.ts +278 -0
  24. package/src/plugins/CommandsPlugin/resolveCommands.ts +277 -0
  25. package/src/plugins/CommandsPlugin/styles.module.css +34 -8
  26. package/src/plugins/CommandsPlugin/types.ts +17 -3
  27. package/src/plugins/CommandsPlugin/useResolvedCommands.ts +88 -0
  28. package/src/plugins/DnDPlugin/DnDPlugin.tsx +64 -9
  29. package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +10 -2
  30. package/src/plugins/index.ts +6 -1
@@ -1,11 +1,10 @@
1
- import { type MapStore, type ReadableAtom } from 'nanostores';
1
+ import { type MapStore } from 'nanostores';
2
2
  import type { IPlugin, UiParams } from '../../types';
3
3
  import type { CommandsStore, Options } from './types';
4
4
  export declare class CommandsPlugin implements IPlugin {
5
5
  options: Options;
6
6
  $store: MapStore<CommandsStore>;
7
- $commands: ReadableAtom<CommandsStore['commands']>;
8
7
  constructor(options: Options);
9
8
  init(editor: any): any;
10
- ui(params: UiParams): import("react/jsx-runtime").JSX.Element | null;
9
+ ui(_params: UiParams): import("react/jsx-runtime").JSX.Element | null;
11
10
  }
@@ -1,34 +1,25 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useStore } from "@nanostores/react";
3
- import { computed, map } from "nanostores";
3
+ import { map } from "nanostores";
4
4
  import { Editor, Node } from "slate";
5
5
  import { Menu } from "./Menu.js";
6
6
  class CommandsPlugin {
7
7
  options;
8
8
  $store;
9
- $commands;
10
9
  constructor(options){
11
10
  this.options = options;
12
11
  this.$store = map({
13
12
  isOpen: false,
14
13
  filter: false,
15
- commands: []
16
- });
17
- this.$commands = computed(this.$store, (store)=>{
18
- if (false === store.filter) return [];
19
- return store.commands.filter((command)=>{
20
- const isCommandMatches = command.commandName.toLocaleLowerCase().includes(store.filter.toLocaleLowerCase());
21
- const isTitleMatches = command.title.toLocaleLowerCase().includes(store.filter.toLocaleLowerCase());
22
- return isCommandMatches || isTitleMatches;
23
- });
14
+ openId: 0
24
15
  });
25
16
  }
26
17
  init(editor) {
27
18
  const { onChange, insertText } = editor;
28
19
  editor.insertText = (text)=>{
29
20
  if ('/' === text) {
21
+ this.$store.setKey('openId', this.$store.get().openId + 1);
30
22
  this.$store.setKey('isOpen', true);
31
- this.$store.setKey('commands', this.options.commands);
32
23
  this.$store.setKey('filter', '');
33
24
  }
34
25
  insertText(text);
@@ -43,14 +34,13 @@ class CommandsPlugin {
43
34
  };
44
35
  return editor;
45
36
  }
46
- ui(params) {
47
- const commands = useStore(this.$commands);
37
+ ui(_params) {
48
38
  const { isOpen } = useStore(this.$store);
49
39
  if (!isOpen) return null;
50
40
  return /*#__PURE__*/ jsx(Menu, {
51
41
  renderMenu: this.options.renderMenu,
52
42
  $store: this.$store,
53
- commands: commands,
43
+ rootCommands: this.options.commands,
54
44
  ignoreNodes: this.options.ignoreNodes
55
45
  });
56
46
  }
@@ -1,11 +1,11 @@
1
1
  import type { MapStore } from 'nanostores';
2
- import React, { type ReactNode } from 'react';
2
+ import { type ReactNode } from 'react';
3
3
  import type { Command, CommandsStore } from './types';
4
4
  type Props = {
5
5
  $store: MapStore<CommandsStore>;
6
- commands: Command[];
6
+ rootCommands: Command[];
7
7
  ignoreNodes?: string[];
8
8
  renderMenu: (children: ReactNode) => ReactNode;
9
9
  };
10
- export declare const Menu: (props: Props) => React.ReactPortal | null;
10
+ export declare const Menu: (props: Props) => import("react").ReactPortal | null;
11
11
  export {};
@@ -7,11 +7,13 @@ import { Editor } from "slate";
7
7
  import { useFocused, useSlate, useSlateSelection } from "slate-react";
8
8
  import { insert, insertText, removeCommand, set, wrap } from "./actions.js";
9
9
  import styles_module from "./styles.module.js";
10
+ import { useResolvedCommands } from "./useResolvedCommands.js";
10
11
  const Menu = (props)=>{
11
- const { commands, $store, renderMenu, ignoreNodes = [] } = props;
12
+ const { rootCommands, $store, renderMenu, ignoreNodes = [] } = props;
12
13
  const store = useStore($store);
13
14
  const [style, setStyle] = useState({});
14
15
  const [active, setActive] = useState(0);
16
+ const [path, setPath] = useState([]);
15
17
  const refs = useRef({});
16
18
  const selection = useSlateSelection();
17
19
  const editor = useSlate();
@@ -20,6 +22,31 @@ const Menu = (props)=>{
20
22
  const entry = Editor.above(editor, {
21
23
  match: (n)=>Editor.isBlock(editor, n)
22
24
  });
25
+ const isBrowseMode = 'string' == typeof store.filter && '' === store.filter;
26
+ const { commands, isLoading, isError } = useResolvedCommands({
27
+ rootCommands,
28
+ filter: store.filter,
29
+ path,
30
+ editor,
31
+ isOpen: store.isOpen
32
+ });
33
+ const entries = useMemo(()=>{
34
+ const commandEntries = commands.map((command)=>({
35
+ type: 'command',
36
+ command
37
+ }));
38
+ if (isBrowseMode && path.length > 0) return [
39
+ {
40
+ type: 'back'
41
+ },
42
+ ...commandEntries
43
+ ];
44
+ return commandEntries;
45
+ }, [
46
+ commands,
47
+ isBrowseMode,
48
+ path.length
49
+ ]);
23
50
  const actions = useMemo(()=>({
24
51
  removeCommand: removeCommand(editor, selection, store.filter),
25
52
  set: set(editor, selection, store.filter),
@@ -27,89 +54,148 @@ const Menu = (props)=>{
27
54
  wrap: wrap(editor, selection, store.filter),
28
55
  insertText: insertText(editor)
29
56
  }), [
30
- commands,
57
+ selection,
31
58
  store.filter
32
59
  ]);
60
+ useEffect(()=>{
61
+ if (!store.isOpen) return;
62
+ setPath([]);
63
+ setActive(0);
64
+ }, [
65
+ store.isOpen,
66
+ store.openId
67
+ ]);
68
+ useEffect(()=>{
69
+ if (false === store.filter || 'string' == typeof store.filter && '' !== store.filter) setActive(0);
70
+ }, [
71
+ store.filter
72
+ ]);
73
+ useEffect(()=>{
74
+ if (!entries.length) return void setActive(0);
75
+ if (active > entries.length - 1) setActive(entries.length - 1);
76
+ }, [
77
+ active,
78
+ entries
79
+ ]);
33
80
  useLayoutEffect(()=>{
34
81
  const { selection } = editor;
35
82
  if (!selection || !isFocused) return void setStyle(void 0);
36
- setTimeout(()=>{
83
+ const frame = window.requestAnimationFrame(()=>{
37
84
  const domSelection = window.getSelection();
38
- const domRange = domSelection?.getRangeAt(0);
85
+ const domRange = domSelection && domSelection.rangeCount > 0 ? domSelection.getRangeAt(0) : null;
39
86
  const rect = domRange?.getBoundingClientRect();
40
- store.isOpen ? setStyle({
41
- opacity: 1,
42
- transform: 'scale(1)',
43
- top: `${(rect?.top || 0) + window.scrollY + (rect?.height || 0) + 2}px`,
44
- left: `${(rect?.left || 0) + window.scrollX + (rect?.width || 0) / 2}px`
45
- }) : setStyle({
46
- opacity: 0,
47
- transform: 'scale(0.9)'
87
+ const x = (rect?.left || 0) + (rect?.width || 0) / 2;
88
+ const menuHeight = ref.current?.offsetHeight || 0;
89
+ let y = (rect?.top || 0) + (rect?.height || 0) + 2;
90
+ if (menuHeight > 0 && y + menuHeight >= window.innerHeight) y = Math.max(8, (rect?.top || 0) - menuHeight - 2);
91
+ const transform = `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0) ${store.isOpen ? 'scale(1)' : 'scale(0.95)'}`;
92
+ setStyle({
93
+ opacity: store.isOpen ? 1 : 0,
94
+ transform
48
95
  });
49
- }, 0);
96
+ });
97
+ return ()=>{
98
+ window.cancelAnimationFrame(frame);
99
+ };
100
+ }, [
101
+ selection,
102
+ commands.length,
103
+ isLoading,
104
+ isError,
105
+ store.isOpen,
106
+ isFocused
107
+ ]);
108
+ useEffect(()=>{
50
109
  const handleKeyDown = (event)=>{
110
+ if (!store.isOpen) return;
51
111
  switch(event.key){
52
112
  case 'ArrowDown':
113
+ if (!entries.length) return;
53
114
  event.preventDefault();
115
+ event.stopPropagation();
54
116
  setActive((active)=>{
55
- const newActive = active >= commands.length - 1 ? 0 : active + 1;
56
- refs.current[newActive]?.scrollIntoView({
117
+ const nextActive = active >= entries.length - 1 ? 0 : active + 1;
118
+ refs.current[nextActive]?.scrollIntoView({
57
119
  block: 'nearest'
58
120
  });
59
- return newActive;
121
+ return nextActive;
60
122
  });
61
123
  break;
62
124
  case 'ArrowUp':
125
+ if (!entries.length) return;
63
126
  event.preventDefault();
127
+ event.stopPropagation();
64
128
  setActive((active)=>{
65
- const newActive = active <= 0 ? commands.length - 1 : active - 1;
66
- refs.current[newActive]?.scrollIntoView({
129
+ const nextActive = active <= 0 ? entries.length - 1 : active - 1;
130
+ refs.current[nextActive]?.scrollIntoView({
67
131
  block: 'nearest'
68
132
  });
69
- return newActive;
133
+ return nextActive;
70
134
  });
71
135
  break;
72
- case 'Enter':
136
+ case 'ArrowRight':
137
+ {
138
+ const entry = entries[active];
139
+ if (!entry || 'command' !== entry.type || !entry.command.isSubmenu) return;
140
+ event.preventDefault();
141
+ event.stopPropagation();
142
+ setPath(entry.command.path);
143
+ if (!isBrowseMode) $store.setKey('filter', '');
144
+ setActive(0);
145
+ break;
146
+ }
147
+ case 'ArrowLeft':
148
+ if (!isBrowseMode || !path.length) return;
73
149
  event.preventDefault();
74
- commands[active]?.action(actions, editor);
75
- $store.setKey('isOpen', false);
150
+ event.stopPropagation();
151
+ setPath((path)=>path.slice(0, path.length - 1));
152
+ setActive(0);
76
153
  break;
154
+ case 'Enter':
155
+ {
156
+ const entry = entries[active];
157
+ if (!entry) return;
158
+ event.preventDefault();
159
+ event.stopPropagation();
160
+ if ('back' === entry.type && isBrowseMode) {
161
+ setPath((path)=>path.slice(0, path.length - 1));
162
+ setActive(0);
163
+ break;
164
+ }
165
+ if ('command' !== entry.type) break;
166
+ if (entry.command.isSubmenu) {
167
+ setPath(entry.command.path);
168
+ if (!isBrowseMode) $store.setKey('filter', '');
169
+ setActive(0);
170
+ break;
171
+ }
172
+ entry.command.command.action?.(actions, editor);
173
+ $store.setKey('isOpen', false);
174
+ break;
175
+ }
77
176
  case 'Escape':
177
+ event.preventDefault();
78
178
  event.stopPropagation();
79
179
  $store.setKey('isOpen', false);
80
180
  break;
81
181
  }
82
182
  };
83
- if (store.isOpen && commands.length > 0) document.addEventListener('keydown', handleKeyDown);
183
+ document.addEventListener('keydown', handleKeyDown);
84
184
  return ()=>{
85
185
  document.removeEventListener('keydown', handleKeyDown);
86
186
  };
87
187
  }, [
88
- selection,
89
- active,
90
188
  actions,
91
- store.isOpen
92
- ]);
93
- useEffect(()=>{
94
- if (active < 0) return void setActive(0);
95
- if (active > commands.length - 1) setActive(commands.length - 1);
96
- }, [
97
- store.filter
189
+ active,
190
+ entries,
191
+ editor,
192
+ isBrowseMode,
193
+ path.length,
194
+ store.isOpen,
195
+ $store
98
196
  ]);
99
- useLayoutEffect(()=>{
100
- const element = ref.current;
101
- if (element) {
102
- const { height, top } = element.getBoundingClientRect();
103
- const domSelection = window.getSelection();
104
- const domRange = domSelection?.getRangeAt(0);
105
- const rect = domRange?.getBoundingClientRect();
106
- if (top + height >= window.innerHeight) setStyle((style)=>({
107
- ...style,
108
- top: `${top - height - (rect?.height ?? 22)}px`
109
- }));
110
- }
111
- }, []);
112
- if (!commands.length) return null;
197
+ const hasRows = entries.length > 0 || isLoading || isError;
198
+ if (false === store.filter || !hasRows) return null;
113
199
  if (entry && ignoreNodes.includes(entry[0].type)) return null;
114
200
  return /*#__PURE__*/ createPortal(renderMenu(/*#__PURE__*/ jsxs(Fragment, {
115
201
  children: [
@@ -119,36 +205,108 @@ const Menu = (props)=>{
119
205
  $store.setKey('isOpen', false);
120
206
  }
121
207
  }),
122
- /*#__PURE__*/ jsx("div", {
208
+ /*#__PURE__*/ jsxs("div", {
123
209
  ref: ref,
124
210
  style: style,
125
211
  className: styles_module.menu,
126
212
  onMouseDown: (event)=>{
127
213
  event.preventDefault();
128
214
  },
129
- children: commands.map((command, index)=>/*#__PURE__*/ jsxs("button", {
130
- type: "button",
131
- ref: (element)=>{
132
- if (element) refs.current[index] = element;
133
- },
134
- className: clsx(styles_module.button, {
135
- [styles_module.active]: index === active
136
- }),
137
- onMouseDown: (event)=>{
138
- event.preventDefault();
139
- command.action(actions, editor);
140
- $store.setKey('isOpen', false);
141
- },
142
- children: [
143
- /*#__PURE__*/ jsx("span", {
144
- className: styles_module.icon,
145
- children: command.icon
215
+ children: [
216
+ entries.map((entry, index)=>{
217
+ if ('back' === entry.type) return /*#__PURE__*/ jsxs("button", {
218
+ type: "button",
219
+ ref: (element)=>{
220
+ refs.current[index] = element;
221
+ },
222
+ className: clsx(styles_module.button, {
223
+ [styles_module.active]: index === active
224
+ }),
225
+ onMouseDown: (event)=>{
226
+ event.preventDefault();
227
+ setPath((path)=>path.slice(0, path.length - 1));
228
+ setActive(0);
229
+ },
230
+ children: [
231
+ /*#__PURE__*/ jsx("span", {
232
+ className: styles_module.icon,
233
+ children: "..."
234
+ }),
235
+ /*#__PURE__*/ jsx("span", {
236
+ className: styles_module.content,
237
+ children: /*#__PURE__*/ jsx("span", {
238
+ children: "..."
239
+ })
240
+ })
241
+ ]
242
+ }, "back");
243
+ return /*#__PURE__*/ jsxs("button", {
244
+ type: "button",
245
+ ref: (element)=>{
246
+ refs.current[index] = element;
247
+ },
248
+ className: clsx(styles_module.button, {
249
+ [styles_module.active]: index === active
146
250
  }),
147
- /*#__PURE__*/ jsx("span", {
148
- children: command.title
149
- })
150
- ]
151
- }, index))
251
+ onMouseDown: (event)=>{
252
+ event.preventDefault();
253
+ if (entry.command.isSubmenu) {
254
+ setPath(entry.command.path);
255
+ if (!isBrowseMode) $store.setKey('filter', '');
256
+ setActive(0);
257
+ return;
258
+ }
259
+ entry.command.command.action?.(actions, editor);
260
+ $store.setKey('isOpen', false);
261
+ },
262
+ children: [
263
+ /*#__PURE__*/ jsx("span", {
264
+ className: styles_module.icon,
265
+ children: entry.command.command.icon
266
+ }),
267
+ /*#__PURE__*/ jsx("span", {
268
+ className: styles_module.content,
269
+ children: /*#__PURE__*/ jsx("span", {
270
+ children: entry.command.command.title
271
+ })
272
+ }),
273
+ entry.command.isSubmenu && isBrowseMode && /*#__PURE__*/ jsx("span", {
274
+ className: styles_module.submenu,
275
+ "aria-hidden": "true",
276
+ children: /*#__PURE__*/ jsxs("svg", {
277
+ xmlns: "http://www.w3.org/2000/svg",
278
+ width: "12",
279
+ height: "12",
280
+ viewBox: "0 0 24 24",
281
+ fill: "none",
282
+ stroke: "currentColor",
283
+ strokeWidth: "2",
284
+ strokeLinecap: "round",
285
+ strokeLinejoin: "round",
286
+ children: [
287
+ /*#__PURE__*/ jsx("path", {
288
+ stroke: "none",
289
+ d: "M0 0h24v24H0z",
290
+ fill: "none"
291
+ }),
292
+ /*#__PURE__*/ jsx("path", {
293
+ d: "M9 6l6 6l-6 6"
294
+ })
295
+ ]
296
+ })
297
+ })
298
+ ]
299
+ }, entry.command.key);
300
+ }),
301
+ isLoading && /*#__PURE__*/ jsx("div", {
302
+ className: styles_module.systemRow,
303
+ children: "Loading..."
304
+ }),
305
+ isError && /*#__PURE__*/ jsx("div", {
306
+ className: styles_module.systemRow,
307
+ children: "Could not load commands"
308
+ })
309
+ ]
152
310
  })
153
311
  ]
154
312
  })), document.body);
@@ -1,2 +1,2 @@
1
1
  export { CommandsPlugin } from './CommandsPlugin';
2
- export type { Command } from './types';
2
+ export type { Command, CommandPathEntry, GetCommandsContext } from './types';
@@ -0,0 +1,33 @@
1
+ import type { Editor } from 'slate';
2
+ import type { Command, CommandPathEntry } from './types';
3
+ export type ResolvedCommand = {
4
+ command: Command;
5
+ key: string;
6
+ path: CommandPathEntry[];
7
+ isSubmenu: boolean;
8
+ };
9
+ export type ResolveCommandsParams = {
10
+ rootCommands: Command[];
11
+ filter: string;
12
+ path: CommandPathEntry[];
13
+ editor: Editor;
14
+ };
15
+ export type ResolvedCommandsState = {
16
+ commands: ResolvedCommand[];
17
+ isLoading: boolean;
18
+ isError: boolean;
19
+ };
20
+ type ResolveRequest = {
21
+ state: ResolvedCommandsState;
22
+ promise: Promise<ResolvedCommandsState>;
23
+ };
24
+ export declare class CommandsResolver {
25
+ private cache;
26
+ private requestId;
27
+ private state;
28
+ getState(): ResolvedCommandsState;
29
+ resolve(params: ResolveCommandsParams): ResolveRequest;
30
+ private resolveChildCommands;
31
+ private resolveInternal;
32
+ }
33
+ export {};
@@ -0,0 +1,141 @@
1
+ const toPathEntry = (command)=>({
2
+ name: command.name,
3
+ title: command.title,
4
+ commandName: command.commandName
5
+ });
6
+ const pathToKey = (path)=>path.map((item)=>item.name).join('/');
7
+ const normalizeCommands = (commands)=>Array.isArray(commands) ? commands : [];
8
+ const isCommandMatchesQuery = (command, query)=>{
9
+ const normalized = query.toLocaleLowerCase();
10
+ return command.commandName.toLocaleLowerCase().includes(normalized) || command.title.toLocaleLowerCase().includes(normalized);
11
+ };
12
+ const toResolvedCommand = (command, parentPath)=>{
13
+ const path = [
14
+ ...parentPath,
15
+ toPathEntry(command)
16
+ ];
17
+ return {
18
+ command,
19
+ path,
20
+ key: pathToKey(path),
21
+ isSubmenu: Boolean(command.getCommands)
22
+ };
23
+ };
24
+ const resolveCurrentLevelCommands = async (params, resolveChildCommands, queryForCurrentLevel)=>{
25
+ const { rootCommands, path, editor } = params;
26
+ let currentCommands = rootCommands;
27
+ let currentPath = [];
28
+ for(let index = 0; index < path.length; index++){
29
+ const item = path[index];
30
+ const isLastPathItem = index === path.length - 1;
31
+ const command = currentCommands.find((entry)=>entry.name === item.name);
32
+ if (!command?.getCommands) return {
33
+ commands: [],
34
+ path: currentPath
35
+ };
36
+ currentPath = [
37
+ ...currentPath,
38
+ toPathEntry(command)
39
+ ];
40
+ currentCommands = await resolveChildCommands({
41
+ command,
42
+ path: currentPath,
43
+ query: isLastPathItem ? queryForCurrentLevel : '',
44
+ editor
45
+ });
46
+ }
47
+ return {
48
+ commands: currentCommands,
49
+ path: currentPath
50
+ };
51
+ };
52
+ const resolveBrowseCommands = async (params, resolveChildCommands)=>{
53
+ const currentLevel = await resolveCurrentLevelCommands(params, resolveChildCommands, '');
54
+ return currentLevel.commands.map((command)=>toResolvedCommand(command, currentLevel.path));
55
+ };
56
+ const resolveSearchCommands = async (params, resolveChildCommands)=>{
57
+ const { filter } = params;
58
+ const currentLevel = await resolveCurrentLevelCommands(params, resolveChildCommands, filter);
59
+ const matchedCommands = currentLevel.commands.filter((command)=>isCommandMatchesQuery(command, filter));
60
+ return matchedCommands.map((command)=>toResolvedCommand(command, currentLevel.path));
61
+ };
62
+ class CommandsResolver {
63
+ cache = new Map();
64
+ requestId = 0;
65
+ state = {
66
+ commands: [],
67
+ isLoading: false,
68
+ isError: false
69
+ };
70
+ getState() {
71
+ return this.state;
72
+ }
73
+ resolve(params) {
74
+ const currentRequestId = ++this.requestId;
75
+ this.state = {
76
+ commands: [],
77
+ isLoading: true,
78
+ isError: false
79
+ };
80
+ const promise = this.resolveInternal(params).then((commands)=>{
81
+ if (currentRequestId !== this.requestId) return this.state;
82
+ this.state = {
83
+ commands,
84
+ isLoading: false,
85
+ isError: false
86
+ };
87
+ return this.state;
88
+ }).catch(()=>{
89
+ if (currentRequestId !== this.requestId) return this.state;
90
+ this.state = {
91
+ ...this.state,
92
+ isLoading: false,
93
+ isError: true
94
+ };
95
+ return this.state;
96
+ });
97
+ return {
98
+ state: this.state,
99
+ promise
100
+ };
101
+ }
102
+ resolveChildCommands = async (params)=>{
103
+ const { command, path, query, editor } = params;
104
+ if (!command.getCommands) return [];
105
+ const cacheKey = `${query}::${pathToKey(path)}`;
106
+ const cached = this.cache.get(cacheKey);
107
+ if (cached?.status === 'resolved') return cached.value;
108
+ if (cached?.status === 'rejected') throw cached.error;
109
+ if (cached?.status === 'pending') return cached.promise;
110
+ const promise = Promise.resolve(command.getCommands({
111
+ query,
112
+ editor,
113
+ path,
114
+ parent: command
115
+ })).then((commands)=>normalizeCommands(commands));
116
+ this.cache.set(cacheKey, {
117
+ status: 'pending',
118
+ promise
119
+ });
120
+ try {
121
+ const commands = await promise;
122
+ this.cache.set(cacheKey, {
123
+ status: 'resolved',
124
+ value: commands
125
+ });
126
+ return commands;
127
+ } catch (error) {
128
+ this.cache.set(cacheKey, {
129
+ status: 'rejected',
130
+ error
131
+ });
132
+ throw error;
133
+ }
134
+ };
135
+ async resolveInternal(params) {
136
+ const { filter } = params;
137
+ if (!filter) return resolveBrowseCommands(params, this.resolveChildCommands);
138
+ return resolveSearchCommands(params, this.resolveChildCommands);
139
+ }
140
+ }
141
+ export { CommandsResolver };