@use-kona/editor 0.1.18 → 0.1.19
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.
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useStore } from "@nanostores/react";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
|
-
import { Editor } from "slate";
|
|
6
|
+
import { Editor, Transforms } 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";
|
|
@@ -47,6 +47,26 @@ const Menu = (props)=>{
|
|
|
47
47
|
isBrowseMode,
|
|
48
48
|
path.length
|
|
49
49
|
]);
|
|
50
|
+
const enterSubmenu = useCallback((nextPath)=>{
|
|
51
|
+
if (!isBrowseMode && 'string' == typeof store.filter && store.filter) {
|
|
52
|
+
const focus = selection?.focus ?? editor.selection?.focus;
|
|
53
|
+
if (focus) Transforms["delete"](editor, {
|
|
54
|
+
at: focus,
|
|
55
|
+
distance: store.filter.length,
|
|
56
|
+
reverse: true,
|
|
57
|
+
unit: 'character'
|
|
58
|
+
});
|
|
59
|
+
$store.setKey('filter', '');
|
|
60
|
+
}
|
|
61
|
+
setPath(nextPath);
|
|
62
|
+
setActive(0);
|
|
63
|
+
}, [
|
|
64
|
+
editor,
|
|
65
|
+
isBrowseMode,
|
|
66
|
+
selection,
|
|
67
|
+
store.filter,
|
|
68
|
+
$store
|
|
69
|
+
]);
|
|
50
70
|
const actions = useMemo(()=>({
|
|
51
71
|
removeCommand: removeCommand(editor, selection, store.filter),
|
|
52
72
|
set: set(editor, selection, store.filter),
|
|
@@ -139,9 +159,7 @@ const Menu = (props)=>{
|
|
|
139
159
|
if (!entry || 'command' !== entry.type || !entry.command.isSubmenu) return;
|
|
140
160
|
event.preventDefault();
|
|
141
161
|
event.stopPropagation();
|
|
142
|
-
|
|
143
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
144
|
-
setActive(0);
|
|
162
|
+
enterSubmenu(entry.command.path);
|
|
145
163
|
break;
|
|
146
164
|
}
|
|
147
165
|
case 'ArrowLeft':
|
|
@@ -164,9 +182,7 @@ const Menu = (props)=>{
|
|
|
164
182
|
}
|
|
165
183
|
if ('command' !== entry.type) break;
|
|
166
184
|
if (entry.command.isSubmenu) {
|
|
167
|
-
|
|
168
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
169
|
-
setActive(0);
|
|
185
|
+
enterSubmenu(entry.command.path);
|
|
170
186
|
break;
|
|
171
187
|
}
|
|
172
188
|
entry.command.command.action?.(actions, editor);
|
|
@@ -189,12 +205,13 @@ const Menu = (props)=>{
|
|
|
189
205
|
active,
|
|
190
206
|
entries,
|
|
191
207
|
editor,
|
|
208
|
+
enterSubmenu,
|
|
192
209
|
isBrowseMode,
|
|
193
210
|
path.length,
|
|
194
211
|
store.isOpen,
|
|
195
212
|
$store
|
|
196
213
|
]);
|
|
197
|
-
const hasRows = entries.length > 0 ||
|
|
214
|
+
const hasRows = entries.length > 0 || isError;
|
|
198
215
|
if (false === store.filter || !hasRows) return null;
|
|
199
216
|
if (entry && ignoreNodes.includes(entry[0].type)) return null;
|
|
200
217
|
return /*#__PURE__*/ createPortal(renderMenu(/*#__PURE__*/ jsxs(Fragment, {
|
|
@@ -240,7 +257,7 @@ const Menu = (props)=>{
|
|
|
240
257
|
})
|
|
241
258
|
]
|
|
242
259
|
}, "back");
|
|
243
|
-
return /*#__PURE__*/
|
|
260
|
+
return /*#__PURE__*/ jsx("button", {
|
|
244
261
|
type: "button",
|
|
245
262
|
ref: (element)=>{
|
|
246
263
|
refs.current[index] = element;
|
|
@@ -250,58 +267,55 @@ const Menu = (props)=>{
|
|
|
250
267
|
}),
|
|
251
268
|
onMouseDown: (event)=>{
|
|
252
269
|
event.preventDefault();
|
|
253
|
-
if (entry.command.isSubmenu)
|
|
254
|
-
setPath(entry.command.path);
|
|
255
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
256
|
-
setActive(0);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
270
|
+
if (entry.command.isSubmenu) return void enterSubmenu(entry.command.path);
|
|
259
271
|
entry.command.command.action?.(actions, editor);
|
|
260
272
|
$store.setKey('isOpen', false);
|
|
261
273
|
},
|
|
262
|
-
children:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
children: entry.command.command.
|
|
274
|
+
children: entry.command.command.render?.({
|
|
275
|
+
command: entry.command.command,
|
|
276
|
+
isSubmenu: entry.command.isSubmenu,
|
|
277
|
+
isActive: index === active
|
|
278
|
+
}) ?? /*#__PURE__*/ jsxs(Fragment, {
|
|
279
|
+
children: [
|
|
280
|
+
/*#__PURE__*/ jsx("span", {
|
|
281
|
+
className: styles_module.icon,
|
|
282
|
+
children: entry.command.command.icon
|
|
283
|
+
}),
|
|
284
|
+
/*#__PURE__*/ jsx("span", {
|
|
285
|
+
className: styles_module.content,
|
|
286
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
287
|
+
children: entry.command.command.title
|
|
288
|
+
})
|
|
289
|
+
}),
|
|
290
|
+
entry.command.isSubmenu && /*#__PURE__*/ jsx("span", {
|
|
291
|
+
className: styles_module.submenu,
|
|
292
|
+
"aria-hidden": "true",
|
|
293
|
+
children: /*#__PURE__*/ jsxs("svg", {
|
|
294
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
295
|
+
width: "12",
|
|
296
|
+
height: "12",
|
|
297
|
+
viewBox: "0 0 24 24",
|
|
298
|
+
fill: "none",
|
|
299
|
+
stroke: "currentColor",
|
|
300
|
+
strokeWidth: "2",
|
|
301
|
+
strokeLinecap: "round",
|
|
302
|
+
strokeLinejoin: "round",
|
|
303
|
+
children: [
|
|
304
|
+
/*#__PURE__*/ jsx("path", {
|
|
305
|
+
stroke: "none",
|
|
306
|
+
d: "M0 0h24v24H0z",
|
|
307
|
+
fill: "none"
|
|
308
|
+
}),
|
|
309
|
+
/*#__PURE__*/ jsx("path", {
|
|
310
|
+
d: "M9 6l6 6l-6 6"
|
|
311
|
+
})
|
|
312
|
+
]
|
|
313
|
+
})
|
|
271
314
|
})
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
]
|
|
315
|
+
]
|
|
316
|
+
})
|
|
299
317
|
}, entry.command.key);
|
|
300
318
|
}),
|
|
301
|
-
isLoading && /*#__PURE__*/ jsx("div", {
|
|
302
|
-
className: styles_module.systemRow,
|
|
303
|
-
children: "Loading..."
|
|
304
|
-
}),
|
|
305
319
|
isError && /*#__PURE__*/ jsx("div", {
|
|
306
320
|
className: styles_module.systemRow,
|
|
307
321
|
children: "Could not load commands"
|
|
@@ -29,6 +29,11 @@ export type Command = {
|
|
|
29
29
|
icon: ReactNode;
|
|
30
30
|
action?: (actions: Actions, editor: Editor) => void;
|
|
31
31
|
getCommands?: (context: GetCommandsContext) => Command[] | Promise<Command[]>;
|
|
32
|
+
render?: (params: {
|
|
33
|
+
command: Command;
|
|
34
|
+
isSubmenu: boolean;
|
|
35
|
+
isActive: boolean;
|
|
36
|
+
}) => ReactNode;
|
|
32
37
|
};
|
|
33
38
|
export type Actions = {
|
|
34
39
|
removeCommand: () => void;
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import type { MapStore } from 'nanostores';
|
|
|
4
4
|
import {
|
|
5
5
|
type CSSProperties,
|
|
6
6
|
type ReactNode,
|
|
7
|
+
useCallback,
|
|
7
8
|
useEffect,
|
|
8
9
|
useLayoutEffect,
|
|
9
10
|
useMemo,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
useState,
|
|
12
13
|
} from 'react';
|
|
13
14
|
import { createPortal } from 'react-dom';
|
|
14
|
-
import { Editor } from 'slate';
|
|
15
|
+
import { Editor, Transforms } from 'slate';
|
|
15
16
|
import { useFocused, useSlate, useSlateSelection } from 'slate-react';
|
|
16
17
|
import type { CustomElement } from '../../../types';
|
|
17
18
|
import { insert, insertText, removeCommand, set, wrap } from './actions';
|
|
@@ -65,6 +66,29 @@ export const Menu = (props: Props) => {
|
|
|
65
66
|
return commandEntries;
|
|
66
67
|
}, [commands, isBrowseMode, path.length]);
|
|
67
68
|
|
|
69
|
+
const enterSubmenu = useCallback(
|
|
70
|
+
(nextPath: CommandPathEntry[]) => {
|
|
71
|
+
if (!isBrowseMode && typeof store.filter === 'string' && store.filter) {
|
|
72
|
+
const focus = selection?.focus ?? editor.selection?.focus;
|
|
73
|
+
|
|
74
|
+
if (focus) {
|
|
75
|
+
Transforms.delete(editor, {
|
|
76
|
+
at: focus,
|
|
77
|
+
distance: store.filter.length,
|
|
78
|
+
reverse: true,
|
|
79
|
+
unit: 'character',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
$store.setKey('filter', '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setPath(nextPath);
|
|
87
|
+
setActive(0);
|
|
88
|
+
},
|
|
89
|
+
[editor, isBrowseMode, selection, store.filter, $store],
|
|
90
|
+
);
|
|
91
|
+
|
|
68
92
|
// biome-ignore lint/correctness/useExhaustiveDependencies: we care only about those deps
|
|
69
93
|
const actions = useMemo(() => {
|
|
70
94
|
return {
|
|
@@ -185,11 +209,7 @@ export const Menu = (props: Props) => {
|
|
|
185
209
|
|
|
186
210
|
event.preventDefault();
|
|
187
211
|
event.stopPropagation();
|
|
188
|
-
|
|
189
|
-
if (!isBrowseMode) {
|
|
190
|
-
$store.setKey('filter', '');
|
|
191
|
-
}
|
|
192
|
-
setActive(0);
|
|
212
|
+
enterSubmenu(entry.command.path);
|
|
193
213
|
break;
|
|
194
214
|
}
|
|
195
215
|
case 'ArrowLeft': {
|
|
@@ -223,11 +243,7 @@ export const Menu = (props: Props) => {
|
|
|
223
243
|
}
|
|
224
244
|
|
|
225
245
|
if (entry.command.isSubmenu) {
|
|
226
|
-
|
|
227
|
-
if (!isBrowseMode) {
|
|
228
|
-
$store.setKey('filter', '');
|
|
229
|
-
}
|
|
230
|
-
setActive(0);
|
|
246
|
+
enterSubmenu(entry.command.path);
|
|
231
247
|
break;
|
|
232
248
|
}
|
|
233
249
|
|
|
@@ -254,13 +270,14 @@ export const Menu = (props: Props) => {
|
|
|
254
270
|
active,
|
|
255
271
|
entries,
|
|
256
272
|
editor,
|
|
273
|
+
enterSubmenu,
|
|
257
274
|
isBrowseMode,
|
|
258
275
|
path.length,
|
|
259
276
|
store.isOpen,
|
|
260
277
|
$store,
|
|
261
278
|
]);
|
|
262
279
|
|
|
263
|
-
const hasRows = entries.length > 0 ||
|
|
280
|
+
const hasRows = entries.length > 0 || isError;
|
|
264
281
|
|
|
265
282
|
if (store.filter === false || !hasRows) {
|
|
266
283
|
return null;
|
|
@@ -328,11 +345,7 @@ export const Menu = (props: Props) => {
|
|
|
328
345
|
onMouseDown={(event) => {
|
|
329
346
|
event.preventDefault();
|
|
330
347
|
if (entry.command.isSubmenu) {
|
|
331
|
-
|
|
332
|
-
if (!isBrowseMode) {
|
|
333
|
-
$store.setKey('filter', '');
|
|
334
|
-
}
|
|
335
|
-
setActive(0);
|
|
348
|
+
enterSubmenu(entry.command.path);
|
|
336
349
|
return;
|
|
337
350
|
}
|
|
338
351
|
|
|
@@ -340,34 +353,41 @@ export const Menu = (props: Props) => {
|
|
|
340
353
|
$store.setKey('isOpen', false);
|
|
341
354
|
}}
|
|
342
355
|
>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
356
|
+
{entry.command.command.render?.({
|
|
357
|
+
command: entry.command.command,
|
|
358
|
+
isSubmenu: entry.command.isSubmenu,
|
|
359
|
+
isActive: index === active,
|
|
360
|
+
}) ?? (
|
|
361
|
+
<>
|
|
362
|
+
<span className={styles.icon}>
|
|
363
|
+
{entry.command.command.icon}
|
|
364
|
+
</span>
|
|
365
|
+
<span className={styles.content}>
|
|
366
|
+
<span>{entry.command.command.title}</span>
|
|
367
|
+
</span>
|
|
368
|
+
{entry.command.isSubmenu && (
|
|
369
|
+
<span className={styles.submenu} aria-hidden="true">
|
|
370
|
+
<svg
|
|
371
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
372
|
+
width="12"
|
|
373
|
+
height="12"
|
|
374
|
+
viewBox="0 0 24 24"
|
|
375
|
+
fill="none"
|
|
376
|
+
stroke="currentColor"
|
|
377
|
+
strokeWidth="2"
|
|
378
|
+
strokeLinecap="round"
|
|
379
|
+
strokeLinejoin="round"
|
|
380
|
+
>
|
|
381
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
382
|
+
<path d="M9 6l6 6l-6 6" />
|
|
383
|
+
</svg>
|
|
384
|
+
</span>
|
|
385
|
+
)}
|
|
386
|
+
</>
|
|
366
387
|
)}
|
|
367
388
|
</button>
|
|
368
389
|
);
|
|
369
390
|
})}
|
|
370
|
-
{isLoading && <div className={styles.systemRow}>Loading...</div>}
|
|
371
391
|
{isError && (
|
|
372
392
|
<div className={styles.systemRow}>Could not load commands</div>
|
|
373
393
|
)}
|
|
@@ -35,6 +35,11 @@ export type Command = {
|
|
|
35
35
|
icon: ReactNode;
|
|
36
36
|
action?: (actions: Actions, editor: Editor) => void;
|
|
37
37
|
getCommands?: (context: GetCommandsContext) => Command[] | Promise<Command[]>;
|
|
38
|
+
render?: (params: {
|
|
39
|
+
command: Command;
|
|
40
|
+
isSubmenu: boolean;
|
|
41
|
+
isActive: boolean;
|
|
42
|
+
}) => ReactNode;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
export type Actions = {
|