@use-kona/editor 0.1.21 → 0.1.23

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 (86) hide show
  1. package/dist/plugins/DnDPlugin/DnDPlugin.js +29 -15
  2. package/dist/plugins/ListsPlugin/ListsPlugin.js +15 -39
  3. package/dist/plugins/ListsPlugin/styles_module.css +1 -0
  4. package/dist/plugins/TableOfContentsPlugin/TableOfContentsPlugin.js +2 -2
  5. package/dist/{plugins → src/plugins}/CodeBlockPlugin/CodeBlockPlugin.d.ts +1 -2
  6. package/dist/{plugins → src/plugins}/HeadingsPlugin/HeadingsPlugin.d.ts +1 -1
  7. package/dist/{plugins → src/plugins}/LinksPlugin/LinksPlugin.d.ts +1 -1
  8. package/dist/{plugins → src/plugins}/ListsPlugin/ListsPlugin.d.ts +1 -3
  9. package/dist/src/plugins/MenuPlugin/MenuPlugin.d.ts +7 -0
  10. package/dist/src/types.d.ts +60 -0
  11. package/dist/types.d.ts +5 -58
  12. package/dist/utils.js +1 -1
  13. package/package.json +11 -11
  14. package/src/plugins/DnDPlugin/DnDPlugin.tsx +43 -26
  15. package/src/plugins/ListsPlugin/ListsPlugin.spec.tsx +166 -6
  16. package/src/plugins/ListsPlugin/ListsPlugin.tsx +27 -63
  17. package/src/plugins/ListsPlugin/styles.module.css +1 -0
  18. package/src/plugins/TableOfContentsPlugin/TableOfContentsPlugin.tsx +2 -2
  19. package/src/utils.ts +4 -2
  20. package/LICENSE +0 -21
  21. package/dist/plugins/CommandsPlugin/resolveCommands.spec.d.ts +0 -1
  22. package/dist/plugins/CommandsPlugin/resolveCommands.spec.js +0 -323
  23. package/dist/plugins/ListsPlugin/ListsPlugin.spec.d.ts +0 -3
  24. package/dist/plugins/ListsPlugin/ListsPlugin.spec.js +0 -47
  25. package/dist/plugins/MenuPlugin/MenuPlugin.d.ts +0 -7
  26. /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
  27. /package/dist/{core → src/core}/createEditable.d.ts +0 -0
  28. /package/dist/{core → src/core}/createEditor.d.ts +0 -0
  29. /package/dist/{core → src/core}/deserialize.d.ts +0 -0
  30. /package/dist/{core → src/core}/queries.d.ts +0 -0
  31. /package/dist/{core → src/core}/serialize.d.ts +0 -0
  32. /package/dist/{defaultValue.d.ts → src/defaultValue.d.ts} +0 -0
  33. /package/dist/{editor.d.ts → src/editor.d.ts} +0 -0
  34. /package/dist/{elements → src/elements}/BaseElement.d.ts +0 -0
  35. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  36. /package/dist/{plugins → src/plugins}/AttachmentsPlugin/AttachmentsPlugin.d.ts +0 -0
  37. /package/dist/{plugins → src/plugins}/AttachmentsPlugin/index.d.ts +0 -0
  38. /package/dist/{plugins → src/plugins}/AttachmentsPlugin/types.d.ts +0 -0
  39. /package/dist/{plugins → src/plugins}/BasicFormattingPlugin/BasicFormattingPlugin.d.ts +0 -0
  40. /package/dist/{plugins → src/plugins}/BasicFormattingPlugin/index.d.ts +0 -0
  41. /package/dist/{plugins → src/plugins}/BreaksPlugin/BreaksPlugin.d.ts +0 -0
  42. /package/dist/{plugins → src/plugins}/BreaksPlugin/index.d.ts +0 -0
  43. /package/dist/{plugins → src/plugins}/CodeBlockPlugin/CodeBlock.d.ts +0 -0
  44. /package/dist/{plugins → src/plugins}/CodeBlockPlugin/CodeBlockLine.d.ts +0 -0
  45. /package/dist/{plugins → src/plugins}/CodeBlockPlugin/index.d.ts +0 -0
  46. /package/dist/{plugins → src/plugins}/CodeBlockPlugin/types.d.ts +0 -0
  47. /package/dist/{plugins → src/plugins}/CodeBlockPlugin/utils.d.ts +0 -0
  48. /package/dist/{plugins → src/plugins}/CommandsPlugin/CommandsPlugin.d.ts +0 -0
  49. /package/dist/{plugins → src/plugins}/CommandsPlugin/Menu.d.ts +0 -0
  50. /package/dist/{plugins → src/plugins}/CommandsPlugin/actions.d.ts +0 -0
  51. /package/dist/{plugins → src/plugins}/CommandsPlugin/index.d.ts +0 -0
  52. /package/dist/{plugins → src/plugins}/CommandsPlugin/resolveCommands.d.ts +0 -0
  53. /package/dist/{plugins → src/plugins}/CommandsPlugin/types.d.ts +0 -0
  54. /package/dist/{plugins → src/plugins}/CommandsPlugin/useResolvedCommands.d.ts +0 -0
  55. /package/dist/{plugins → src/plugins}/DnDPlugin/DnDPlugin.d.ts +0 -0
  56. /package/dist/{plugins → src/plugins}/DnDPlugin/index.d.ts +0 -0
  57. /package/dist/{plugins → src/plugins}/EmojiPlugin/EmojiPlugin.d.ts +0 -0
  58. /package/dist/{plugins → src/plugins}/EmojiPlugin/Menu.d.ts +0 -0
  59. /package/dist/{plugins → src/plugins}/EmojiPlugin/index.d.ts +0 -0
  60. /package/dist/{plugins → src/plugins}/EmojiPlugin/types.d.ts +0 -0
  61. /package/dist/{plugins → src/plugins}/FloatingMenuPlugin/FloatingMenu.d.ts +0 -0
  62. /package/dist/{plugins → src/plugins}/FloatingMenuPlugin/FloatingMenuPlugin.d.ts +0 -0
  63. /package/dist/{plugins → src/plugins}/FloatingMenuPlugin/index.d.ts +0 -0
  64. /package/dist/{plugins → src/plugins}/FloatingMenuPlugin/types.d.ts +0 -0
  65. /package/dist/{plugins → src/plugins}/HeadingsPlugin/index.d.ts +0 -0
  66. /package/dist/{plugins → src/plugins}/HighlightsPlugin/HighlightsPlugin.d.ts +0 -0
  67. /package/dist/{plugins → src/plugins}/HighlightsPlugin/index.d.ts +0 -0
  68. /package/dist/{plugins → src/plugins}/LinksPlugin/Link.d.ts +0 -0
  69. /package/dist/{plugins → src/plugins}/LinksPlugin/constants.d.ts +0 -0
  70. /package/dist/{plugins → src/plugins}/LinksPlugin/index.d.ts +0 -0
  71. /package/dist/{plugins → src/plugins}/LinksPlugin/types.d.ts +0 -0
  72. /package/dist/{plugins → src/plugins}/ListsPlugin/index.d.ts +0 -0
  73. /package/dist/{plugins → src/plugins}/MenuPlugin/Menu.d.ts +0 -0
  74. /package/dist/{plugins → src/plugins}/MenuPlugin/index.d.ts +0 -0
  75. /package/dist/{plugins → src/plugins}/MenuPlugin/types.d.ts +0 -0
  76. /package/dist/{plugins → src/plugins}/NodeIdPlugin/NodeIdPlugin.d.ts +0 -0
  77. /package/dist/{plugins → src/plugins}/NodeIdPlugin/index.d.ts +0 -0
  78. /package/dist/{plugins → src/plugins}/PlaceholderPlugin/PlaceholderPlugin.d.ts +0 -0
  79. /package/dist/{plugins → src/plugins}/PlaceholderPlugin/index.d.ts +0 -0
  80. /package/dist/{plugins → src/plugins}/ShortcutsPlugin/ShortcutsPlugin.d.ts +0 -0
  81. /package/dist/{plugins → src/plugins}/ShortcutsPlugin/index.d.ts +0 -0
  82. /package/dist/{plugins → src/plugins}/TableOfContentsPlugin/TableOfContentsPlugin.d.ts +0 -0
  83. /package/dist/{plugins → src/plugins}/TableOfContentsPlugin/index.d.ts +0 -0
  84. /package/dist/{plugins → src/plugins}/index.d.ts +0 -0
  85. /package/dist/{queries → src/queries}/getMatchedGroups.d.ts +0 -0
  86. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { useStore } from "@nanostores/react";
2
2
  import { map } from "nanostores";
3
- import { useEffect, useState } from "react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
  import { useDrag, useDrop } from "react-dnd";
5
5
  import { NativeTypes } from "react-dnd-html5-backend";
6
6
  import { Editor, Path, Transforms } from "slate";
@@ -34,14 +34,18 @@ class DnDPlugin {
34
34
  const options = this.options;
35
35
  const isReadOnly = useReadOnly();
36
36
  const [dropPosition, setDropPosition] = useState(null);
37
+ const dropTargetRef = useRef(null);
37
38
  const customType = options.customTypes?.[props.element.type];
38
39
  const currentElement = props.element;
40
+ const selected = Array.from($store.selected.values());
39
41
  const [, drag, preview] = useDrag({
40
42
  type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
41
43
  item: {
42
44
  ...customType?.getData?.(props.element) || {},
43
45
  element: props.element,
44
- nodeIds: Array.from($store.selected.values())
46
+ nodeIds: currentElement?.nodeId && selected.includes(currentElement?.nodeId) ? selected : [
47
+ currentElement?.nodeId
48
+ ]
45
49
  },
46
50
  ...customType?.getDndItem?.(props.element) || {},
47
51
  canDrag: !isReadOnly
@@ -61,7 +65,7 @@ class DnDPlugin {
61
65
  };
62
66
  },
63
67
  hover (_, monitor) {
64
- const element = ReactEditor.toDOMNode(editor, props.element);
68
+ const element = dropTargetRef.current;
65
69
  if (!element) return;
66
70
  const hoverBoundingRect = element.getBoundingClientRect();
67
71
  const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
@@ -74,7 +78,7 @@ class DnDPlugin {
74
78
  const itemType = monitor.getItemType();
75
79
  const sourceTo = ReactEditor.findPath(editor, props.element);
76
80
  if (!sourceTo) return;
77
- const dropPath = getDropPath(editor, sourceTo, dropPosition);
81
+ const dropPath = getDropPath(editor, sourceTo, dropPosition || 'bottom');
78
82
  if (!dropPath) return;
79
83
  switch(itemType){
80
84
  case NativeTypes.FILE:
@@ -85,12 +89,17 @@ class DnDPlugin {
85
89
  const dragItem = item;
86
90
  if (!dropPosition || !dragItem.nodeIds?.length) return;
87
91
  moveNode(editor, sourceTo, dragItem.nodeIds, dropPosition);
92
+ $store.selected.clear();
88
93
  break;
89
94
  }
90
95
  }
91
96
  setDropPosition(null);
92
97
  }
93
98
  });
99
+ const connectDropRef = (el)=>{
100
+ dropTargetRef.current = el;
101
+ return drop(el);
102
+ };
94
103
  useEffect(()=>{
95
104
  setDropPosition(null);
96
105
  }, [
@@ -101,7 +110,7 @@ class DnDPlugin {
101
110
  return options.renderBlock?.({
102
111
  props,
103
112
  dragRef: drag,
104
- dropRef: drop,
113
+ dropRef: connectDropRef,
105
114
  previewRef: preview,
106
115
  position: dropPosition,
107
116
  selected: 'string' == typeof currentElement.nodeId ? $store.selected.has(currentElement.nodeId) : false,
@@ -123,16 +132,21 @@ const getDropPath = (editor, targetNodePath, position)=>{
123
132
  const moveNode = (editor, targetNodePath, nodeIds, position)=>{
124
133
  const dropPath = getDropPath(editor, targetNodePath, position);
125
134
  if (!dropPath) return;
126
- Transforms.moveNodes(editor, {
127
- at: [],
128
- match: (n)=>{
129
- if (!Editor.isBlock(editor, n)) return false;
130
- const node = n;
131
- const shouldMove = 'string' == typeof node.nodeId && nodeIds.includes(node.nodeId);
132
- return shouldMove;
133
- },
134
- to: dropPath,
135
- mode: 'highest'
135
+ if (!Editor.hasPath(editor, dropPath)) return;
136
+ Editor.withoutNormalizing(editor, ()=>{
137
+ Transforms.moveNodes(editor, {
138
+ at: [],
139
+ match: (n)=>{
140
+ if (!Editor.isBlock(editor, n)) return false;
141
+ if (Editor.isEditor(n)) return false;
142
+ const node = n;
143
+ const shouldMove = 'string' == typeof node.nodeId && nodeIds.includes(node.nodeId);
144
+ return shouldMove;
145
+ },
146
+ to: dropPath,
147
+ mode: 'highest'
148
+ });
136
149
  });
150
+ Editor.normalize(editor);
137
151
  };
138
152
  export { DnDPlugin };
@@ -26,6 +26,9 @@ class ListsPlugin {
26
26
  editor.normalizeNode = (entry)=>{
27
27
  const [node, path] = entry;
28
28
  if (this.isList(editor, node)) {
29
+ if (0 === node.children.length) return void Transforms.removeNodes(editor, {
30
+ at: path
31
+ });
29
32
  for (const [child, childPath] of Array.from(Node.children(editor, path)))if (!this.isListItem(editor, child) && !this.isList(editor, child)) return void Transforms.unwrapNodes(editor, {
30
33
  at: childPath,
31
34
  match: (n)=>this.isList(editor, n),
@@ -47,25 +50,12 @@ class ListsPlugin {
47
50
  split: true
48
51
  });
49
52
  }
50
- if (this.isList(editor, node)) {
51
- if (!Path.hasPrevious(path)) return void normalizeNode(entry);
52
- const prevListItemPath = Editor.before(editor, path, {
53
- unit: 'block'
54
- });
55
- const prevList = prevListItemPath && Editor.above(editor, {
56
- at: prevListItemPath,
57
- match: (n)=>this.isList(editor, n)
53
+ if (this.isList(editor, node) && Path.hasPrevious(path)) {
54
+ const prevPath = Path.previous(path);
55
+ const prevNode = Node.get(editor, prevPath);
56
+ if (this.isList(editor, prevNode) && prevNode.type === node.type) return void Transforms.mergeNodes(editor, {
57
+ at: path
58
58
  });
59
- if (prevList) {
60
- const currentDepth = this.getListDepth(editor, path);
61
- const prevDepth = this.getListDepth(editor, prevList[1]);
62
- if (this.isList(editor, prevList[0]) && currentDepth === prevDepth && prevList[0].type === node.type) try {
63
- Transforms.mergeNodes(editor, {
64
- at: path,
65
- match: (n)=>this.isList(editor, n)
66
- });
67
- } catch (e) {}
68
- }
69
59
  }
70
60
  normalizeNode(entry);
71
61
  };
@@ -238,19 +228,19 @@ class ListsPlugin {
238
228
  mode: 'highest'
239
229
  }));
240
230
  if (!nodes.length) return;
241
- Transforms.wrapNodes(editor, {
242
- type: listType,
243
- children: []
244
- }, {
245
- mode: 'highest',
246
- match: (n)=>Element.isElement(n) && n.type === listItemType
247
- });
248
231
  Transforms.setNodes(editor, {
249
232
  type: listItemType
250
233
  }, {
251
234
  at: nodes[0][1],
252
235
  split: true
253
236
  });
237
+ Transforms.wrapNodes(editor, {
238
+ type: listType,
239
+ children: []
240
+ }, {
241
+ at: nodes[0][1],
242
+ match: (n)=>n.type === listItemType
243
+ });
254
244
  }
255
245
  });
256
246
  };
@@ -267,20 +257,6 @@ class ListsPlugin {
267
257
  }
268
258
  return 0;
269
259
  };
270
- getListDepth = (editor, path)=>{
271
- if (path) {
272
- const node = Node.get(editor, path);
273
- if (!this.isList(editor, node)) return 0;
274
- let count = 0;
275
- let parent = Editor.parent(editor, path);
276
- while(parent && this.isList(editor, parent[0])){
277
- count++;
278
- parent = Editor.parent(editor, parent[1]);
279
- }
280
- return count;
281
- }
282
- return 0;
283
- };
284
260
  getListItem = (editor, path)=>{
285
261
  if (path) {
286
262
  const node = Node.get(editor, path);
@@ -1,4 +1,5 @@
1
1
  .list-YEmg4D {
2
+ margin: 0;
2
3
  padding: 0;
3
4
 
4
5
  & & {
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useStore } from "@nanostores/react";
3
3
  import { map } from "nanostores";
4
4
  import { useEffect } from "react";
5
- import { Editor, Operation } from "slate";
5
+ import { Editor, Operation, Text } from "slate";
6
6
  import { ReactEditor } from "slate-react";
7
7
  import styles_module from "./styles.module.js";
8
8
  class TableOfContentsPlugin {
@@ -56,7 +56,7 @@ class TableOfContentsPlugin {
56
56
  return /*#__PURE__*/ jsx("div", {
57
57
  className: styles_module[`h${level}`],
58
58
  onMouseDown: handleMouseDown,
59
- children: node.children[0].text
59
+ children: Text.isText(node.children[0]) ? node.children[0].text : ''
60
60
  }, path.join('-'));
61
61
  });
62
62
  return /*#__PURE__*/ jsxs("div", {
@@ -4,7 +4,6 @@ import { type RenderElementProps, type RenderLeafProps } from 'slate-react';
4
4
  import type { IPlugin } from '../../types';
5
5
  import type { CodeElement } from './types';
6
6
  import 'prismjs/themes/prism.css';
7
- import type { CustomElement } from '../../../types';
8
7
  import 'prismjs/components/prism-javascript';
9
8
  import 'prismjs/components/prism-typescript';
10
9
  import 'prismjs/components/prism-json';
@@ -47,7 +46,7 @@ export declare class CodeBlockPlugin implements IPlugin {
47
46
  blocks: ({
48
47
  type: string;
49
48
  render: (props: RenderElementProps, editor: Editor) => import("react/jsx-runtime").JSX.Element;
50
- deserialize: (el: HTMLElement, children: any) => CustomElement | undefined;
49
+ deserialize: (el: HTMLElement, children: any) => import("../../../types").CustomElement | undefined;
51
50
  } | {
52
51
  type: string;
53
52
  render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
@@ -8,7 +8,7 @@ export declare class HeadingsPlugin implements IPlugin {
8
8
  blocks: {
9
9
  type: string;
10
10
  render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
11
- deserialize: (element: HTMLElement, children: any) => import("../..").CustomElement | undefined;
11
+ deserialize: (element: HTMLElement, children: any) => import("../../../types").CustomElement | undefined;
12
12
  serialize: (node: Descendant, children: any) => string | undefined;
13
13
  }[];
14
14
  static isHeading1Active(editor: Editor): boolean;
@@ -14,7 +14,7 @@ export declare class LinksPlugin implements IPlugin {
14
14
  type: string;
15
15
  render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
16
16
  serialize: (element: any, children: any) => string | undefined;
17
- deserialize: (element: HTMLElement, children: any) => import("../..").CustomElement | undefined;
17
+ deserialize: (element: HTMLElement, children: any) => import("../../../types").CustomElement | undefined;
18
18
  }[];
19
19
  static addLink: (editor: Editor, url: string) => void;
20
20
  static removeLink: (editor: Editor) => void;
@@ -1,7 +1,6 @@
1
1
  import type { KeyboardEvent } from 'react';
2
2
  import { Editor } from 'slate';
3
3
  import type { RenderElementProps } from 'slate-react';
4
- import type { CustomElement } from '../../../types';
5
4
  import type { IPlugin } from '../../types';
6
5
  type Options = {
7
6
  listTypes?: string[];
@@ -20,7 +19,7 @@ export declare class ListsPlugin implements IPlugin {
20
19
  type: string;
21
20
  render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
22
21
  serialize: (element: any, children: any) => string | undefined;
23
- deserialize: (element: HTMLElement, children: any) => CustomElement | undefined;
22
+ deserialize: (element: HTMLElement, children: any) => import("../../../types").CustomElement | undefined;
24
23
  }[];
25
24
  handlers: {
26
25
  onKeyDown: (event: KeyboardEvent, editor: Editor) => true | undefined;
@@ -36,7 +35,6 @@ export declare class ListsPlugin implements IPlugin {
36
35
  private isListItem;
37
36
  static toggleList: (editor: Editor, listType: string, listItemType?: string) => void;
38
37
  private getListItemDepth;
39
- private getListDepth;
40
38
  private getListItem;
41
39
  }
42
40
  export {};
@@ -0,0 +1,7 @@
1
+ import type { IPlugin, UiParams } from '../../types';
2
+ import type { Options } from './types';
3
+ export declare class MenuPlugin implements IPlugin {
4
+ private options;
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;
7
+ }
@@ -0,0 +1,60 @@
1
+ import type { KeyboardEvent, ReactElement, ReactNode } from 'react';
2
+ import type { DecoratedRange, Descendant, Editor, Location, NodeEntry, NodeMatch } from 'slate';
3
+ import type { RenderElementProps, RenderLeafProps } from 'slate-react';
4
+ import type { CustomElement, CustomText } from '../types';
5
+ export interface IPlugin<TEditor extends Editor = Editor, TBlock extends CustomElement = CustomElement, TLeaf extends CustomText = CustomText> {
6
+ init?: (editor: TEditor) => TEditor;
7
+ blocks?: Block<TEditor, TBlock>[];
8
+ leafs?: Leaf<TEditor, TLeaf>[];
9
+ hotkeys?: Hotkey[];
10
+ renderBlock?: (props: RenderElementProps) => ReactElement | null;
11
+ renderLeaf?: (props: RenderLeafProps) => ReactElement | null;
12
+ handlers?: {
13
+ onDrop?: (event: DragEvent, editor: Editor) => void;
14
+ onDragOver?: (event: DragEvent, editor: Editor) => void;
15
+ onKeyDown?: (event: KeyboardEvent, editor: Editor) => void;
16
+ onPaste?: (event: ClipboardEvent, editor: Editor) => void;
17
+ };
18
+ decorate?: (entry: NodeEntry) => DecoratedRange[];
19
+ commands?: {
20
+ [key: string]: (editor: TEditor, ...args: any[]) => void;
21
+ };
22
+ ui?: (params: UiParams) => ReactNode;
23
+ }
24
+ export type Block<TEditor extends Editor = Editor, TBlock extends CustomElement = CustomElement> = {
25
+ type: string;
26
+ render: (props: RenderElementProps & {
27
+ element: CustomElement & TBlock;
28
+ }, editor: TEditor) => ReactElement | null;
29
+ serialize?: Serialize;
30
+ deserialize?: Deserialize;
31
+ isVoid?: boolean;
32
+ isInline?: boolean;
33
+ onBeforeDelete?: (blocks: TBlock[]) => Promise<boolean>;
34
+ onDelete?: (blocks: TBlock[]) => void;
35
+ };
36
+ export type Leaf<T extends Editor, TLeaf extends CustomText = CustomText> = {
37
+ render: (props: RenderLeafProps & {
38
+ leaf: CustomText & TLeaf;
39
+ }, editor: T) => ReactElement | null;
40
+ isVoid?: boolean;
41
+ serialize?: Serialize;
42
+ deserialize?: Deserialize;
43
+ };
44
+ type Hotkey = readonly [string, (event: KeyboardEvent, editor: Editor) => void];
45
+ export type UiParams = {
46
+ editor: Editor;
47
+ readOnly: boolean;
48
+ children: ReactNode;
49
+ };
50
+ export type EditorRef = {
51
+ serialize: (node: CustomElement | CustomText) => string;
52
+ deserialize: (element: HTMLElement) => (Descendant | string)[] | string | Descendant | null;
53
+ insertNodes: (nodes: Descendant[], at?: Location) => boolean;
54
+ isEmpty: () => boolean;
55
+ deleteNode: (match: NodeMatch<CustomElement>) => void;
56
+ focus: (mode?: 'end') => void;
57
+ };
58
+ export type Serialize = (node: CustomElement | CustomText, children?: string) => string | undefined;
59
+ export type Deserialize = (element: HTMLElement, children?: (string | Descendant)[]) => CustomElement | CustomText[] | undefined;
60
+ export {};
package/dist/types.d.ts CHANGED
@@ -1,60 +1,7 @@
1
- import type { KeyboardEvent, ReactElement, ReactNode } from 'react';
2
- import type { DecoratedRange, Descendant, Editor, Location, NodeEntry, NodeMatch } from 'slate';
3
- import type { RenderElementProps, RenderLeafProps } from 'slate-react';
4
- import type { CustomElement, CustomText } from '../types';
5
- export interface IPlugin<TEditor extends Editor = Editor, TBlock extends CustomElement = CustomElement, TLeaf extends CustomText = CustomText> {
6
- init?: (editor: TEditor) => TEditor;
7
- blocks?: Block<TEditor, TBlock>[];
8
- leafs?: Leaf<TEditor, TLeaf>[];
9
- hotkeys?: Hotkey[];
10
- renderBlock?: (props: RenderElementProps) => ReactElement | null;
11
- renderLeaf?: (props: RenderLeafProps) => ReactElement | null;
12
- handlers?: {
13
- onDrop?: (event: DragEvent, editor: Editor) => void;
14
- onDragOver?: (event: DragEvent, editor: Editor) => void;
15
- onKeyDown?: (event: KeyboardEvent, editor: Editor) => void;
16
- onPaste?: (event: ClipboardEvent, editor: Editor) => void;
17
- };
18
- decorate?: (entry: NodeEntry) => DecoratedRange[];
19
- commands?: {
20
- [key: string]: (editor: TEditor, ...args: any[]) => void;
21
- };
22
- ui?: (params: UiParams) => ReactNode;
23
- }
24
- export type Block<TEditor extends Editor = Editor, TBlock extends CustomElement = CustomElement> = {
25
- type: string;
26
- render: (props: RenderElementProps & {
27
- element: CustomElement & TBlock;
28
- }, editor: TEditor) => ReactElement | null;
29
- serialize?: Serialize;
30
- deserialize?: Deserialize;
31
- isVoid?: boolean;
32
- isInline?: boolean;
33
- onBeforeDelete?: (blocks: TBlock[]) => Promise<boolean>;
34
- onDelete?: (blocks: TBlock[]) => void;
35
- };
36
- export type Leaf<T extends Editor, TLeaf extends CustomText = CustomText> = {
37
- render: (props: RenderLeafProps & {
38
- leaf: CustomText & TLeaf;
39
- }, editor: T) => ReactElement | null;
40
- isVoid?: boolean;
41
- serialize?: Serialize;
42
- deserialize?: Deserialize;
1
+ export type CustomText = {
2
+ text: string;
43
3
  };
44
- type Hotkey = readonly [string, (event: KeyboardEvent, editor: Editor) => void];
45
- export type UiParams = {
46
- editor: Editor;
47
- readOnly: boolean;
48
- children: ReactNode;
49
- };
50
- export type EditorRef = {
51
- serialize: (node: CustomElement | CustomText) => string;
52
- deserialize: (element: HTMLElement) => (Descendant | string)[] | string | Descendant | null;
53
- insertNodes: (nodes: Descendant[], at?: Location) => boolean;
54
- isEmpty: () => boolean;
55
- deleteNode: (match: NodeMatch<CustomElement>) => void;
56
- focus: (mode?: 'end') => void;
4
+ export type CustomElement = {
5
+ type: string;
6
+ children: (CustomElement | CustomText)[];
57
7
  };
58
- export type Serialize = (node: CustomElement | CustomText, children?: string) => string | undefined;
59
- export type Deserialize = (element: HTMLElement, children?: (string | Descendant)[]) => CustomElement | CustomText[] | undefined;
60
- export {};
package/dist/utils.js CHANGED
@@ -2,7 +2,7 @@ import { Text } from "slate";
2
2
  const isEmpty = (children)=>{
3
3
  const [first] = children || [];
4
4
  if (Text.isText(first)) return '' === first.text.trim();
5
- if (children && children.length <= 1 && Text.isText(first?.children?.[0])) return first.children.every((child)=>child.text?.trim() === '');
5
+ if (children && children.length <= 1 && first && 'children' in first) return first.children.every((child)=>Text.isText(child) && child.text?.trim() === '');
6
6
  return false;
7
7
  };
8
8
  export { isEmpty };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-kona/editor",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -11,6 +11,15 @@
11
11
  "dist",
12
12
  "src"
13
13
  ],
14
+ "scripts": {
15
+ "build": "rslib build && find dist -type f \\( -name '*.spec.*' -o -name '*.test.*' \\) -delete || true",
16
+ "build:storybook": "storybook build",
17
+ "check": "biome check --write",
18
+ "dev": "rslib build --watch",
19
+ "format": "biome format --write",
20
+ "storybook": "NODE_OPTIONS=\"--inspect=9229\" storybook dev",
21
+ "test": "vitest run"
22
+ },
14
23
  "devDependencies": {
15
24
  "@rsbuild/core": "~1.4.0",
16
25
  "@rsbuild/plugin-react": "^1.3.2",
@@ -62,14 +71,5 @@
62
71
  "is-url": "^1.2.4",
63
72
  "nanostores": "^1.0.1",
64
73
  "prismjs": "^1.30.0"
65
- },
66
- "scripts": {
67
- "build": "rslib build",
68
- "build:storybook": "storybook build",
69
- "check": "biome check --write",
70
- "dev": "rslib build --watch",
71
- "format": "biome format --write",
72
- "storybook": "storybook dev",
73
- "test": "vitest run"
74
74
  }
75
- }
75
+ }
@@ -1,7 +1,7 @@
1
1
  import { useStore } from '@nanostores/react';
2
2
  import { type MapStore, map } from 'nanostores';
3
3
  import type React from 'react';
4
- import { useEffect, useState } from 'react';
4
+ import { useEffect, useRef, useState } from 'react';
5
5
  import {
6
6
  type ConnectDragPreview,
7
7
  type ConnectDragSource,
@@ -92,15 +92,21 @@ export class DnDPlugin implements IPlugin {
92
92
  null,
93
93
  );
94
94
 
95
+ const dropTargetRef = useRef<HTMLElement | null>(null);
96
+
95
97
  const customType = options.customTypes?.[props.element.type];
96
98
  const currentElement = props.element as DnDNode;
97
99
 
100
+ const selected = Array.from($store.selected.values());
101
+
98
102
  const [, drag, preview] = useDrag({
99
103
  type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
100
104
  item: {
101
105
  ...(customType?.getData?.(props.element) || {}),
102
106
  element: props.element,
103
- nodeIds: Array.from($store.selected.values()),
107
+ nodeIds: currentElement?.nodeId && selected.includes(currentElement?.nodeId)
108
+ ? selected
109
+ : [currentElement?.nodeId],
104
110
  },
105
111
  ...(customType?.getDndItem?.(props.element) || {}),
106
112
  canDrag: !isReadOnly,
@@ -124,10 +130,7 @@ export class DnDPlugin implements IPlugin {
124
130
  };
125
131
  },
126
132
  hover(_, monitor) {
127
- const element = ReactEditor.toDOMNode(
128
- editor,
129
- props.element,
130
- ) as HTMLElement;
133
+ const element = dropTargetRef.current;
131
134
  if (!element) return;
132
135
 
133
136
  const hoverBoundingRect = element.getBoundingClientRect();
@@ -151,7 +154,7 @@ export class DnDPlugin implements IPlugin {
151
154
 
152
155
  if (!sourceTo) return;
153
156
 
154
- const dropPath = getDropPath(editor, sourceTo, dropPosition);
157
+ const dropPath = getDropPath(editor, sourceTo, dropPosition || 'bottom');
155
158
 
156
159
  if (!dropPath) return;
157
160
 
@@ -171,6 +174,7 @@ export class DnDPlugin implements IPlugin {
171
174
  }
172
175
 
173
176
  moveNode(editor, sourceTo, dragItem.nodeIds, dropPosition);
177
+ $store.selected.clear();
174
178
  break;
175
179
  }
176
180
  }
@@ -179,6 +183,11 @@ export class DnDPlugin implements IPlugin {
179
183
  },
180
184
  });
181
185
 
186
+ const connectDropRef: ConnectDropTarget = (el) => {
187
+ dropTargetRef.current = el as HTMLElement | null;
188
+ return drop(el);
189
+ };
190
+
182
191
  // biome-ignore lint/correctness/useExhaustiveDependencies: we care only about this dep
183
192
  useEffect(() => {
184
193
  setDropPosition(null);
@@ -195,7 +204,7 @@ export class DnDPlugin implements IPlugin {
195
204
  return options.renderBlock?.({
196
205
  props,
197
206
  dragRef: drag,
198
- dropRef: drop,
207
+ dropRef: connectDropRef,
199
208
  previewRef: preview,
200
209
  position: dropPosition,
201
210
  selected:
@@ -214,7 +223,7 @@ export class DnDPlugin implements IPlugin {
214
223
  const getDropPath = (
215
224
  editor: Editor,
216
225
  targetNodePath: Path,
217
- position: 'top' | 'bottom' | null,
226
+ position: 'top' | 'bottom',
218
227
  ) => {
219
228
  const target = Editor.node(editor, targetNodePath);
220
229
 
@@ -235,23 +244,31 @@ const moveNode = (
235
244
  const dropPath = getDropPath(editor, targetNodePath, position);
236
245
 
237
246
  if (!dropPath) return;
247
+ if (!Editor.hasPath(editor, dropPath)) {
248
+ return
249
+ }
238
250
 
239
- // nodeIds.reverse().forEach((nodeId) => {
240
- Transforms.moveNodes(editor, {
241
- // at: sourceNodePath,
242
- at: [],
243
- match: (n) => {
244
- if (!Editor.isBlock(editor, n as CustomElement)) {
245
- return false;
246
- }
247
-
248
- const node = n as DnDNode;
249
- const shouldMove =
250
- typeof node.nodeId === 'string' && nodeIds.includes(node.nodeId);
251
- return shouldMove;
252
- },
253
- to: dropPath,
254
- mode: 'highest',
251
+ Editor.withoutNormalizing(editor, () => {
252
+ Transforms.moveNodes(editor, {
253
+ at: [],
254
+ match: (n) => {
255
+ if (!Editor.isBlock(editor, n as CustomElement)) {
256
+ return false;
257
+ }
258
+
259
+ if (Editor.isEditor(n)) {
260
+ return false;
261
+ }
262
+
263
+ const node = n as DnDNode;
264
+ const shouldMove =
265
+ typeof node.nodeId === 'string' && nodeIds.includes(node.nodeId);
266
+ return shouldMove;
267
+ },
268
+ to: dropPath,
269
+ mode: 'highest',
270
+ });
255
271
  });
256
- // });
272
+ Editor.normalize(editor);
273
+
257
274
  };