@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
- setPath(entry.command.path);
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
- setPath(entry.command.path);
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 || isLoading || isError;
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__*/ jsxs("button", {
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
- /*#__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
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
- 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
- ]
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-kona/editor",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -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
- setPath(entry.command.path);
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
- setPath(entry.command.path);
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 || isLoading || isError;
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
- setPath(entry.command.path);
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
- <span className={styles.icon}>
344
- {entry.command.command.icon}
345
- </span>
346
- <span className={styles.content}>
347
- <span>{entry.command.command.title}</span>
348
- </span>
349
- {entry.command.isSubmenu && isBrowseMode && (
350
- <span className={styles.submenu} aria-hidden="true">
351
- <svg
352
- xmlns="http://www.w3.org/2000/svg"
353
- width="12"
354
- height="12"
355
- viewBox="0 0 24 24"
356
- fill="none"
357
- stroke="currentColor"
358
- strokeWidth="2"
359
- strokeLinecap="round"
360
- strokeLinejoin="round"
361
- >
362
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
363
- <path d="M9 6l6 6l-6 6" />
364
- </svg>
365
- </span>
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 = {