@use-kona/editor 0.1.11 → 0.1.13

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 (42) hide show
  1. package/dist/core/createEditable.d.ts +1 -1
  2. package/dist/core/createEditor.d.ts +3 -1
  3. package/dist/core/createEditor.js +21 -11
  4. package/dist/elements/BaseElement.d.ts +1 -1
  5. package/dist/plugins/AttachmentsPlugin/types.d.ts +3 -3
  6. package/dist/plugins/BreaksPlugin/BreaksPlugin.d.ts +3 -1
  7. package/dist/plugins/CodeBlockPlugin/utils.d.ts +1 -1
  8. package/dist/plugins/HeadingsPlugin/HeadingsPlugin.d.ts +1 -1
  9. package/dist/plugins/LinksPlugin/LinksPlugin.d.ts +3 -1
  10. package/dist/plugins/LinksPlugin/index.d.ts +1 -1
  11. package/dist/plugins/LinksPlugin/types.d.ts +3 -3
  12. package/dist/plugins/ListsPlugin/ListsPlugin.d.ts +7 -1
  13. package/dist/plugins/ListsPlugin/ListsPlugin.js +4 -0
  14. package/dist/plugins/ListsPlugin/ListsPlugin.spec.d.ts +3 -0
  15. package/dist/plugins/ListsPlugin/ListsPlugin.spec.js +47 -0
  16. package/dist/plugins/MenuPlugin/Menu.d.ts +1 -1
  17. package/dist/plugins/MenuPlugin/MenuPlugin.d.ts +1 -1
  18. package/dist/plugins/MenuPlugin/types.d.ts +1 -1
  19. package/dist/plugins/NodeIdPlugin/NodeIdPlugin.d.ts +3 -1
  20. package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +4 -1
  21. package/dist/plugins/ShortcutsPlugin/ShortcutsPlugin.d.ts +3 -1
  22. package/dist/plugins/TableOfContentsPlugin/TableOfContentsPlugin.d.ts +3 -1
  23. package/dist/plugins/TableOfContentsPlugin/TableOfContentsPlugin.js +1 -1
  24. package/dist/plugins/TableOfContentsPlugin/styles_module.css +8 -0
  25. package/dist/types.d.ts +3 -0
  26. package/package.json +6 -7
  27. package/src/core/createEditable.tsx +4 -4
  28. package/src/core/createEditor.ts +35 -13
  29. package/src/elements/BaseElement.tsx +1 -1
  30. package/src/plugins/AttachmentsPlugin/types.ts +3 -3
  31. package/src/plugins/CodeBlockPlugin/utils.ts +1 -1
  32. package/src/plugins/HeadingsPlugin/HeadingsPlugin.tsx +1 -1
  33. package/src/plugins/LinksPlugin/index.ts +1 -1
  34. package/src/plugins/LinksPlugin/types.ts +3 -3
  35. package/src/plugins/ListsPlugin/ListsPlugin.spec.tsx +85 -0
  36. package/src/plugins/ListsPlugin/ListsPlugin.tsx +9 -0
  37. package/src/plugins/MenuPlugin/Menu.tsx +1 -1
  38. package/src/plugins/MenuPlugin/types.ts +1 -1
  39. package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +7 -1
  40. package/src/plugins/TableOfContentsPlugin/TableOfContentsPlugin.tsx +1 -1
  41. package/src/plugins/TableOfContentsPlugin/styles.module.css +11 -0
  42. package/src/types.ts +11 -1
@@ -1,4 +1,4 @@
1
- import { Editor } from 'slate';
1
+ import { type Editor } from 'slate';
2
2
  import type { IPlugin } from '../types';
3
3
  type Props = {
4
4
  readOnly?: boolean;
@@ -1,2 +1,4 @@
1
1
  import type { IPlugin } from '../types';
2
- export declare const createEditor: (plugins: IPlugin[]) => () => import("slate").BaseEditor & import("slate-react").ReactEditor;
2
+ export declare const createEditor: (plugins: IPlugin[]) => () => import("slate").BaseEditor & import("slate-react").ReactEditor & {
3
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
4
+ };
@@ -3,11 +3,17 @@ import { withHistory } from "slate-history";
3
3
  import { withReact } from "slate-react";
4
4
  const createEditor_createEditor = (plugins)=>()=>{
5
5
  const baseEditor = withHistory(withReact(createEditor()));
6
+ const pluginsMap = new Map();
6
7
  const editorWithPlugins = plugins.reduce((editor, plugin)=>{
7
8
  if (plugin.init) return plugin.init(editor);
8
9
  return editor;
9
10
  }, baseEditor);
10
11
  const { isInline, isVoid, normalizeNode, deleteFragment, deleteBackward, deleteForward } = editorWithPlugins;
12
+ editorWithPlugins.getCommands = (plugin)=>{
13
+ const pluginInstance = pluginsMap.get(plugin);
14
+ if (!pluginInstance) throw new Error(`Plugin ${plugin.name} not found`);
15
+ return pluginInstance.commands;
16
+ };
11
17
  editorWithPlugins.normalizeNode = (entry)=>{
12
18
  const [node, path] = entry;
13
19
  const lastElement = editorWithPlugins.children[editorWithPlugins.children.length - 1];
@@ -150,17 +156,21 @@ const createEditor_createEditor = (plugins)=>()=>{
150
156
  const plugin = plugins.find((plugin)=>plugin.blocks?.find((b)=>b.type === node.type));
151
157
  if (plugin) {
152
158
  const match = plugin.blocks?.find((b)=>b.type === node.type);
153
- const result = match?.onBeforeDelete ? await match.onBeforeDelete([
154
- node
155
- ]) : true;
156
- if (!result) return;
157
- Transforms.removeNodes(editorWithPlugins, {
158
- at: path,
159
- match: (n)=>n.type === node.type
160
- });
161
- match?.onDelete?.([
162
- node
163
- ]);
159
+ const hasOnBeforeDelete = match?.onBeforeDelete;
160
+ if (!hasOnBeforeDelete) return void deleteFragment(options);
161
+ {
162
+ const result = await match.onBeforeDelete([
163
+ node
164
+ ]);
165
+ if (!result) return;
166
+ Transforms.removeNodes(editorWithPlugins, {
167
+ at: path,
168
+ match: (n)=>n.type === node.type
169
+ });
170
+ match?.onDelete?.([
171
+ node
172
+ ]);
173
+ }
164
174
  } else Transforms.removeNodes(editorWithPlugins, {
165
175
  at: path,
166
176
  match: (n)=>n.type === node.type
@@ -1,2 +1,2 @@
1
- import { RenderElementProps } from 'slate-react';
1
+ import type { RenderElementProps } from 'slate-react';
2
2
  export declare const BaseElement: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
- import { Editor } from 'slate';
2
- import { RenderElementProps } from 'slate-react';
3
- import { ReactNode } from 'react';
1
+ import type { Editor } from 'slate';
2
+ import type { RenderElementProps } from 'slate-react';
3
+ import type { ReactNode } from 'react';
4
4
  export type Options = {
5
5
  onDragEnter?: () => void;
6
6
  onDragLeave?: () => void;
@@ -7,7 +7,9 @@ type Options = {
7
7
  export declare class BreaksPlugin implements IPlugin {
8
8
  private options;
9
9
  constructor(options: Options);
10
- init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor;
10
+ init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor & {
11
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
12
+ };
11
13
  handlers: {
12
14
  onKeyDown: (event: KeyboardEvent, editor: Editor) => void;
13
15
  };
@@ -2,7 +2,7 @@
2
2
  * Copied from prism-react-renderer repo
3
3
  * https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/utils/normalizeTokens.js
4
4
  * */
5
- import Prism from 'prismjs';
5
+ import type Prism from 'prismjs';
6
6
  type PrismToken = Prism.Token;
7
7
  type Token = {
8
8
  types: string[];
@@ -1,4 +1,4 @@
1
- import { Descendant, Editor } from 'slate';
1
+ import { type Descendant, Editor } from 'slate';
2
2
  import type { RenderElementProps } from 'slate-react';
3
3
  import type { IPlugin } from '../../types';
4
4
  export declare class HeadingsPlugin implements IPlugin {
@@ -6,7 +6,9 @@ export declare class LinksPlugin implements IPlugin {
6
6
  private options;
7
7
  static LINK_TYPE: string;
8
8
  constructor(options: Options);
9
- init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor;
9
+ init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor & {
10
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
11
+ };
10
12
  blocks: {
11
13
  isInline: boolean;
12
14
  type: string;
@@ -1,2 +1,2 @@
1
1
  export { LinksPlugin } from './LinksPlugin';
2
- export { type OptionsMethods } from './types';
2
+ export type { OptionsMethods } from './types';
@@ -1,6 +1,6 @@
1
- import { LINK_ELEMENT } from './constants';
2
- import { Editor } from 'slate';
3
- import { ReactNode } from 'react';
1
+ import type { LINK_ELEMENT } from './constants';
2
+ import type { Editor } from 'slate';
3
+ import type { ReactNode } from 'react';
4
4
  export type LinkElement = {
5
5
  type: typeof LINK_ELEMENT;
6
6
  url: string;
@@ -13,7 +13,9 @@ export declare class ListsPlugin implements IPlugin {
13
13
  static NUMBERED_LIST_ELEMENT: string;
14
14
  static LIST_ITEM_ELEMENT: string;
15
15
  constructor(options?: Options);
16
- init: (editor: Editor) => import("slate").BaseEditor & import("slate-react").ReactEditor;
16
+ init: (editor: Editor) => import("slate").BaseEditor & import("slate-react").ReactEditor & {
17
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
18
+ };
17
19
  blocks: {
18
20
  type: string;
19
21
  render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
@@ -23,6 +25,10 @@ export declare class ListsPlugin implements IPlugin {
23
25
  handlers: {
24
26
  onKeyDown: (event: KeyboardEvent, editor: Editor) => true | undefined;
25
27
  };
28
+ commands: {
29
+ toggleBulletedList: () => (editor: Editor) => void;
30
+ toggleNumberedList: () => (editor: Editor) => void;
31
+ };
26
32
  static isListActive: (editor: Editor, type: string) => boolean;
27
33
  static isBulletedListActive: (editor: Editor) => boolean;
28
34
  static isNumberedListActive: (editor: Editor) => boolean;
@@ -196,6 +196,10 @@ class ListsPlugin {
196
196
  }
197
197
  }
198
198
  };
199
+ commands = {
200
+ toggleBulletedList: ()=>(editor)=>ListsPlugin.toggleList(editor, ListsPlugin.BULLETED_LIST_ELEMENT),
201
+ toggleNumberedList: ()=>(editor)=>ListsPlugin.toggleList(editor, ListsPlugin.NUMBERED_LIST_ELEMENT)
202
+ };
199
203
  static isListActive = (editor, type)=>{
200
204
  const { selection } = editor;
201
205
  if (!selection) return false;
@@ -0,0 +1,3 @@
1
+ /** @jsxRuntime classic */
2
+ /** @jsx jsx */
3
+ export {};
@@ -0,0 +1,47 @@
1
+ import { createHyperscript, createText } from "slate-hyperscript";
2
+ import { describe, expect, it } from "vitest";
3
+ import { createEditor } from "../../core/createEditor.js";
4
+ import { ListsPlugin } from "./ListsPlugin.js";
5
+ const ListsPlugin_spec_elements = {
6
+ paragraph: {
7
+ type: 'paragraph'
8
+ },
9
+ numberedList: {
10
+ type: ListsPlugin.NUMBERED_LIST_ELEMENT
11
+ },
12
+ bulletedList: {
13
+ type: ListsPlugin.BULLETED_LIST_ELEMENT
14
+ },
15
+ listItem: {
16
+ type: ListsPlugin.LIST_ITEM_ELEMENT
17
+ }
18
+ };
19
+ const jsx = createHyperscript({
20
+ elements: ListsPlugin_spec_elements,
21
+ creators: {
22
+ text: createText
23
+ }
24
+ });
25
+ const createEditorWithPlugin = (children)=>{
26
+ const editorState = /*#__PURE__*/ jsx("editor", null, children);
27
+ const editor = createEditor([
28
+ new ListsPlugin()
29
+ ])();
30
+ editor.children = editorState.children;
31
+ editor.selection = editorState.selection;
32
+ return editor;
33
+ };
34
+ describe('ListsPlugin', ()=>{
35
+ it('should change current block to bulleted list', ()=>{
36
+ const editor = createEditorWithPlugin(/*#__PURE__*/ jsx("paragraph", null, /*#__PURE__*/ jsx("cursor", null), /*#__PURE__*/ jsx("text", null, "Hello world")));
37
+ ListsPlugin.toggleList(editor, ListsPlugin.BULLETED_LIST_ELEMENT);
38
+ const output = /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("bulletedList", null, /*#__PURE__*/ jsx("listItem", null, /*#__PURE__*/ jsx("text", null, "Hello world"))), /*#__PURE__*/ jsx("paragraph", null, /*#__PURE__*/ jsx("text", null)));
39
+ expect(editor.children).toEqual(output.children);
40
+ });
41
+ it('should change current block to numbered list', ()=>{
42
+ const editor = createEditorWithPlugin(/*#__PURE__*/ jsx("paragraph", null, /*#__PURE__*/ jsx("cursor", null), /*#__PURE__*/ jsx("text", null, "Hello world")));
43
+ ListsPlugin.toggleList(editor, ListsPlugin.NUMBERED_LIST_ELEMENT);
44
+ const output = /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("numberedList", null, /*#__PURE__*/ jsx("listItem", null, /*#__PURE__*/ jsx("text", null, "Hello world"))), /*#__PURE__*/ jsx("paragraph", null, /*#__PURE__*/ jsx("text", null)));
45
+ expect(editor.children).toEqual(output.children);
46
+ });
47
+ });
@@ -1,2 +1,2 @@
1
- import { Options } from './types';
1
+ import type { Options } from './types';
2
2
  export declare const Menu: (props: Options) => import("react/jsx-runtime").JSX.Element;
@@ -3,5 +3,5 @@ import type { Options } from './types';
3
3
  export declare class MenuPlugin implements IPlugin {
4
4
  private options;
5
5
  constructor(options: Options);
6
- ui: (params: UiParams) => string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
6
+ ui: (params: UiParams) => string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
7
7
  }
@@ -1,4 +1,4 @@
1
- import { UiParams } from '../../types';
1
+ import type { UiParams } from '../../types';
2
2
  export type Options = {
3
3
  renderMenu: (props: UiParams) => React.ReactNode;
4
4
  };
@@ -10,6 +10,8 @@ type Options = {
10
10
  export declare class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
11
11
  private options;
12
12
  constructor(options: Options);
13
- init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor;
13
+ init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor & {
14
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
15
+ };
14
16
  }
15
17
  export {};
@@ -1,8 +1,11 @@
1
1
  import { Element, Node, Text } from "slate";
2
2
  const assignId = (node, generateId)=>{
3
3
  if (Element.isElement(node)) try {
4
+ if (node.nodeId) return;
4
5
  node.nodeId = generateId();
5
- node.children.forEach((n)=>assignId(n, generateId));
6
+ node.children.forEach((n)=>{
7
+ assignId(n, generateId);
8
+ });
6
9
  } catch {}
7
10
  };
8
11
  class NodeIdPlugin {
@@ -19,6 +19,8 @@ export type ChangeMatch = {
19
19
  export declare class ShortcutsPlugin implements IPlugin {
20
20
  private options;
21
21
  constructor(options: Options);
22
- init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor;
22
+ init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor & {
23
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
24
+ };
23
25
  }
24
26
  export {};
@@ -12,7 +12,9 @@ export declare class TableOfContentsPlugin implements IPlugin {
12
12
  headings: NodeEntry<CustomElement>[];
13
13
  }>;
14
14
  constructor(options: Options);
15
- init(editor: Editor): import("slate").BaseEditor & ReactEditor;
15
+ init(editor: Editor): import("slate").BaseEditor & ReactEditor & {
16
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
17
+ };
16
18
  ui(params: UiParams): import("react/jsx-runtime").JSX.Element;
17
19
  }
18
20
  export {};
@@ -50,7 +50,7 @@ class TableOfContentsPlugin {
50
50
  const element = ReactEditor.toDOMNode(params.editor, node);
51
51
  element?.scrollIntoView({
52
52
  behavior: 'smooth',
53
- block: 'nearest'
53
+ block: 'start'
54
54
  });
55
55
  };
56
56
  return /*#__PURE__*/ jsx("div", {
@@ -3,6 +3,12 @@
3
3
  position: absolute;
4
4
  top: 50px;
5
5
  right: 8px;
6
+
7
+ &:after {
8
+ content: "";
9
+ position: absolute;
10
+ inset: -8px 0 -8px -8px;
11
+ }
6
12
  }
7
13
 
8
14
  .toc-MJmvGk .max-NR6DEL {
@@ -19,6 +25,7 @@
19
25
 
20
26
  .mini-iQ6Zfx {
21
27
  transform-origin: 100% 0;
28
+ opacity: .65;
22
29
  flex-direction: column;
23
30
  align-items: flex-end;
24
31
  gap: 4px;
@@ -50,6 +57,7 @@
50
57
  transform-origin: 100% 0;
51
58
  border-radius: 4px;
52
59
  width: max-content;
60
+ min-width: 120px;
53
61
  max-width: 200px;
54
62
  max-height: 160px;
55
63
  padding: 4px;
package/dist/types.d.ts CHANGED
@@ -15,6 +15,9 @@ export interface IPlugin<TEditor extends Editor = Editor, TBlock extends CustomE
15
15
  onPaste?: (event: ClipboardEvent, editor: Editor) => void;
16
16
  };
17
17
  decorate?: (entry: NodeEntry) => DecoratedRange[];
18
+ commands?: {
19
+ [key: string]: (editor: TEditor, ...args: any[]) => void;
20
+ };
18
21
  ui?: (params: UiParams) => ReactNode;
19
22
  }
20
23
  export type Block<TEditor extends Editor = Editor, TBlock extends CustomElement = CustomElement> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-kona/editor",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -21,7 +21,6 @@
21
21
  "test": "vitest run"
22
22
  },
23
23
  "devDependencies": {
24
- "@biomejs/biome": "2.0.0",
25
24
  "@rsbuild/core": "~1.4.0",
26
25
  "@rsbuild/plugin-react": "^1.3.2",
27
26
  "@rslib/core": "^0.10.4",
@@ -44,15 +43,15 @@
44
43
  "react-dnd": "^16.0.1",
45
44
  "react-dnd-html5-backend": "^16.0.1",
46
45
  "react-dom": "18",
46
+ "slate": "^0.117.2",
47
+ "slate-history": "^0.113.1",
48
+ "slate-hyperscript": "^0.100.0",
49
+ "slate-react": "^0.117.3",
47
50
  "storybook": "^9.0.13",
48
51
  "storybook-addon-rslib": "^2.0.2",
49
52
  "storybook-react-rsbuild": "^2.0.2",
50
53
  "typescript": "^5.8.3",
51
- "vitest": "^3.2.4",
52
- "slate": "^0.117.2",
53
- "slate-history": "^0.113.1",
54
- "slate-hyperscript": "^0.100.0",
55
- "slate-react": "^0.117.3"
54
+ "vitest": "^3.2.4"
56
55
  },
57
56
  "peerDependencies": {
58
57
  "react": ">=16.9.0",
@@ -9,10 +9,10 @@ import React, {
9
9
  useCallback,
10
10
  } from 'react';
11
11
  import {
12
- DecoratedRange,
13
- Descendant,
14
- Editor,
15
- NodeEntry,
12
+ type DecoratedRange,
13
+ type Descendant,
14
+ type Editor,
15
+ type NodeEntry,
16
16
  Transforms,
17
17
  } from 'slate';
18
18
  import {
@@ -14,6 +14,8 @@ import type { Block, IPlugin } from '../types';
14
14
  export const createEditor = (plugins: IPlugin[]) => () => {
15
15
  const baseEditor = withHistory(withReact(createBaseEditor()));
16
16
 
17
+ const pluginsMap = new Map<new () => IPlugin, IPlugin>();
18
+
17
19
  const editorWithPlugins = plugins.reduce<Editor>((editor, plugin) => {
18
20
  if (plugin.init) {
19
21
  return plugin.init(editor);
@@ -31,6 +33,16 @@ export const createEditor = (plugins: IPlugin[]) => () => {
31
33
  deleteForward,
32
34
  } = editorWithPlugins;
33
35
 
36
+ editorWithPlugins.getCommands = (plugin: new () => IPlugin) => {
37
+ const pluginInstance = pluginsMap.get(plugin);
38
+
39
+ if (!pluginInstance) {
40
+ throw new Error(`Plugin ${plugin.name} not found`);
41
+ }
42
+
43
+ return pluginInstance.commands;
44
+ };
45
+
34
46
  editorWithPlugins.normalizeNode = (entry) => {
35
47
  const [node, path] = entry;
36
48
 
@@ -201,16 +213,18 @@ export const createEditor = (plugins: IPlugin[]) => () => {
201
213
  const nodes = Array.from(
202
214
  Editor.nodes<CustomElement>(editorWithPlugins, {
203
215
  at: selection,
204
- match: (n) =>
205
- !Editor.isEditor(n) &&
206
- Editor.isBlock(editorWithPlugins, n as CustomElement),
216
+ match: (n) => {
217
+ return (
218
+ !Editor.isEditor(n) &&
219
+ Editor.isBlock(editorWithPlugins, n as CustomElement)
220
+ );
221
+ },
207
222
  reverse: true,
208
223
  mode: 'highest',
209
224
  voids: true,
210
225
  }),
211
226
  );
212
227
 
213
-
214
228
  for (const entry of nodes) {
215
229
  const [node, path] = entry;
216
230
 
@@ -222,18 +236,26 @@ export const createEditor = (plugins: IPlugin[]) => () => {
222
236
  const plugin = plugins.find((plugin) =>
223
237
  plugin.blocks?.find((b) => b.type === node.type),
224
238
  );
239
+
225
240
  if (plugin) {
226
241
  const match = plugin.blocks?.find((b) => b.type === node.type);
227
- const result = match?.onBeforeDelete
228
- ? await match.onBeforeDelete([node])
229
- : true;
230
- if (result) {
231
- Transforms.removeNodes(editorWithPlugins, {
232
- at: path,
233
- match: (n) => (n as CustomElement).type === node.type,
234
- });
235
- match?.onDelete?.([node]);
242
+
243
+ const hasOnBeforeDelete = match?.onBeforeDelete;
244
+
245
+ if (hasOnBeforeDelete) {
246
+ const result = await match.onBeforeDelete!([node]);
247
+
248
+ if (result) {
249
+ Transforms.removeNodes(editorWithPlugins, {
250
+ at: path,
251
+ match: (n) => (n as CustomElement).type === node.type,
252
+ });
253
+ match?.onDelete?.([node]);
254
+ } else {
255
+ return;
256
+ }
236
257
  } else {
258
+ deleteFragment(options);
237
259
  return;
238
260
  }
239
261
  } else {
@@ -1,4 +1,4 @@
1
- import { RenderElementProps } from 'slate-react';
1
+ import type { RenderElementProps } from 'slate-react';
2
2
 
3
3
  export const BaseElement = (props: RenderElementProps) => {
4
4
  return <p {...props.attributes}>{props.children}</p>;
@@ -1,6 +1,6 @@
1
- import { Editor } from 'slate';
2
- import { RenderElementProps } from 'slate-react';
3
- import { ReactNode } from 'react';
1
+ import type { Editor } from 'slate';
2
+ import type { RenderElementProps } from 'slate-react';
3
+ import type { ReactNode } from 'react';
4
4
 
5
5
  export type Options = {
6
6
  onDragEnter?: () => void;
@@ -3,7 +3,7 @@
3
3
  * https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/utils/normalizeTokens.js
4
4
  * */
5
5
 
6
- import Prism from 'prismjs'
6
+ import type Prism from 'prismjs'
7
7
 
8
8
  type PrismToken = Prism.Token
9
9
  type Token = {
@@ -1,4 +1,4 @@
1
- import { Descendant, Editor, Element, Transforms } from 'slate';
1
+ import { type Descendant, Editor, Element, Transforms } from 'slate';
2
2
  import { jsx } from 'slate-hyperscript';
3
3
  import type { RenderElementProps } from 'slate-react';
4
4
  import type { IPlugin } from '../../types';
@@ -1,2 +1,2 @@
1
1
  export { LinksPlugin } from './LinksPlugin';
2
- export { type OptionsMethods } from './types';
2
+ export type { OptionsMethods } from './types';
@@ -1,6 +1,6 @@
1
- import { LINK_ELEMENT } from './constants';
2
- import { Editor } from 'slate';
3
- import { ReactNode } from 'react';
1
+ import type { LINK_ELEMENT } from './constants';
2
+ import type { Editor } from 'slate';
3
+ import type { ReactNode } from 'react';
4
4
 
5
5
  export type LinkElement = {
6
6
  type: typeof LINK_ELEMENT;
@@ -0,0 +1,85 @@
1
+ /** @jsxRuntime classic */
2
+ /** @jsx jsx */
3
+
4
+ import {
5
+ createHyperscript,
6
+ createText,
7
+ type HyperscriptShorthands,
8
+ } from 'slate-hyperscript';
9
+ import { describe, expect, it } from 'vitest';
10
+ import { createEditor } from '../../core/createEditor';
11
+ import { ListsPlugin } from './ListsPlugin';
12
+
13
+ const elements: HyperscriptShorthands = {
14
+ paragraph: { type: 'paragraph' },
15
+ numberedList: { type: ListsPlugin.NUMBERED_LIST_ELEMENT },
16
+ bulletedList: { type: ListsPlugin.BULLETED_LIST_ELEMENT },
17
+ listItem: { type: ListsPlugin.LIST_ITEM_ELEMENT },
18
+ };
19
+
20
+ const jsx = createHyperscript({ elements, creators: { text: createText } });
21
+
22
+ jsx;
23
+
24
+ const createEditorWithPlugin = (children: any) => {
25
+ const editorState = <editor>{children}</editor>;
26
+ const editor = createEditor([new ListsPlugin()])();
27
+ editor.children = editorState.children;
28
+ editor.selection = editorState.selection;
29
+
30
+ return editor;
31
+ };
32
+
33
+ describe('ListsPlugin', () => {
34
+ it('should change current block to bulleted list', () => {
35
+ const editor = createEditorWithPlugin(
36
+ <paragraph>
37
+ <cursor />
38
+ <text>Hello world</text>
39
+ </paragraph>,
40
+ );
41
+
42
+ ListsPlugin.toggleList(editor, ListsPlugin.BULLETED_LIST_ELEMENT);
43
+
44
+ const output = (
45
+ <editor>
46
+ <bulletedList>
47
+ <listItem>
48
+ <text>Hello world</text>
49
+ </listItem>
50
+ </bulletedList>
51
+ <paragraph>
52
+ <text></text>
53
+ </paragraph>
54
+ </editor>
55
+ );
56
+
57
+ expect(editor.children).toEqual(output.children);
58
+ });
59
+
60
+ it('should change current block to numbered list', () => {
61
+ const editor = createEditorWithPlugin(
62
+ <paragraph>
63
+ <cursor />
64
+ <text>Hello world</text>
65
+ </paragraph>,
66
+ );
67
+
68
+ ListsPlugin.toggleList(editor, ListsPlugin.NUMBERED_LIST_ELEMENT);
69
+
70
+ const output = (
71
+ <editor>
72
+ <numberedList>
73
+ <listItem>
74
+ <text>Hello world</text>
75
+ </listItem>
76
+ </numberedList>
77
+ <paragraph>
78
+ <text></text>
79
+ </paragraph>
80
+ </editor>
81
+ );
82
+
83
+ expect(editor.children).toEqual(output.children);
84
+ });
85
+ });
@@ -328,6 +328,15 @@ export class ListsPlugin implements IPlugin {
328
328
  },
329
329
  };
330
330
 
331
+ commands = {
332
+ toggleBulletedList: () => (editor: Editor) => {
333
+ return ListsPlugin.toggleList(editor, ListsPlugin.BULLETED_LIST_ELEMENT);
334
+ },
335
+ toggleNumberedList: () => (editor: Editor) => {
336
+ return ListsPlugin.toggleList(editor, ListsPlugin.NUMBERED_LIST_ELEMENT);
337
+ },
338
+ };
339
+
331
340
  static isListActive = (editor: Editor, type: string) => {
332
341
  const { selection } = editor;
333
342
 
@@ -1,5 +1,5 @@
1
1
  import { useReadOnly, useSlate } from 'slate-react';
2
- import { Options } from './types';
2
+ import type { Options } from './types';
3
3
 
4
4
  export const Menu = (props: Options) => {
5
5
  const editor = useSlate();
@@ -1,4 +1,4 @@
1
- import { UiParams } from '../../types';
1
+ import type { UiParams } from '../../types';
2
2
 
3
3
  export type Options = {
4
4
  renderMenu: (props: UiParams) => React.ReactNode;
@@ -9,8 +9,14 @@ type NodeIdBlock = CustomElement & {
9
9
  const assignId = (node: Node, generateId: () => string) => {
10
10
  if (Element.isElement(node)) {
11
11
  try {
12
+ if ((node as NodeIdBlock).nodeId) {
13
+ return;
14
+ }
15
+
12
16
  (node as NodeIdBlock).nodeId = generateId();
13
- node.children.forEach((n) => assignId(n, generateId));
17
+ node.children.forEach((n) => {
18
+ assignId(n, generateId);
19
+ });
14
20
  } catch {}
15
21
  }
16
22
  };
@@ -77,7 +77,7 @@ export class TableOfContentsPlugin implements IPlugin {
77
77
  ) as HTMLElement;
78
78
  element?.scrollIntoView({
79
79
  behavior: 'smooth',
80
- block: 'nearest'
80
+ block: 'start',
81
81
  });
82
82
  };
83
83
 
@@ -3,6 +3,15 @@
3
3
  top: 50px;
4
4
  right: 8px;
5
5
  z-index: 3;
6
+
7
+ &:after {
8
+ content: "";
9
+ position: absolute;
10
+ top: -8px;
11
+ left: -8px;
12
+ bottom: -8px;
13
+ right: 0;
14
+ }
6
15
  }
7
16
 
8
17
  .toc .max {
@@ -24,6 +33,7 @@
24
33
  gap: 4px;
25
34
  transition: all 0.25s;
26
35
  transform-origin: top right;
36
+ opacity: 0.65;
27
37
  }
28
38
 
29
39
  .mini .h1 {
@@ -55,6 +65,7 @@
55
65
  transition: all 0.25s;
56
66
  transform-origin: top right;
57
67
  width: max-content;
68
+ min-width: 120px;
58
69
  max-width: 200px;
59
70
  max-height: 160px;
60
71
  overflow-y: auto;
package/src/types.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { KeyboardEvent, ReactElement, ReactNode } from 'react';
2
- import type {DecoratedRange, Descendant, Editor, NodeEntry, NodeMatch} from 'slate';
2
+ import type {
3
+ DecoratedRange,
4
+ Descendant,
5
+ Editor,
6
+ NodeEntry,
7
+ NodeMatch,
8
+ } from 'slate';
3
9
  import type { RenderElementProps, RenderLeafProps } from 'slate-react';
4
10
  import type { CustomElement, CustomText } from '../types';
5
11
 
@@ -24,6 +30,10 @@ export interface IPlugin<
24
30
 
25
31
  decorate?: (entry: NodeEntry) => DecoratedRange[];
26
32
 
33
+ commands?: {
34
+ [key: string]: (editor: TEditor, ...args: any[]) => void;
35
+ };
36
+
27
37
  ui?: (params: UiParams) => ReactNode;
28
38
  }
29
39