@use-kona/editor 0.1.28 → 0.1.30

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/core/createEditable.js +4 -1
  2. package/dist/plugins/CodeBlockPlugin/styles_module.css +1 -0
  3. package/dist/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.js +121 -0
  4. package/dist/plugins/CollapsibleBlocksPlugin/index.js +2 -0
  5. package/dist/plugins/CollapsibleBlocksPlugin/styles.module.js +8 -0
  6. package/dist/plugins/CollapsibleBlocksPlugin/styles_module.css +38 -0
  7. package/dist/plugins/CommandsPlugin/Menu.js +2 -2
  8. package/dist/plugins/DnDPlugin/DnDPlugin.js +60 -19
  9. package/dist/plugins/index.js +2 -1
  10. package/dist/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.d.ts +13 -0
  11. package/dist/src/plugins/CollapsibleBlocksPlugin/index.d.ts +1 -0
  12. package/dist/src/plugins/DnDPlugin/DnDPlugin.d.ts +2 -1
  13. package/dist/src/plugins/DnDPlugin/types.d.ts +1 -0
  14. package/dist/src/plugins/index.d.ts +1 -0
  15. package/dist/src/types.d.ts +5 -0
  16. package/dist/types.d.ts +1 -0
  17. package/package.json +1 -1
  18. package/src/core/createEditable.tsx +5 -1
  19. package/src/plugins/CodeBlockPlugin/styles.module.css +1 -0
  20. package/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.spec.tsx +216 -0
  21. package/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.tsx +191 -0
  22. package/src/plugins/CollapsibleBlocksPlugin/index.ts +1 -0
  23. package/src/plugins/CollapsibleBlocksPlugin/styles.module.css +37 -0
  24. package/src/plugins/CommandsPlugin/Menu.spec.tsx +87 -0
  25. package/src/plugins/CommandsPlugin/Menu.tsx +2 -2
  26. package/src/plugins/DnDPlugin/DnDPlugin.spec.ts +346 -0
  27. package/src/plugins/DnDPlugin/DnDPlugin.tsx +117 -31
  28. package/src/plugins/DnDPlugin/types.ts +1 -0
  29. package/src/plugins/index.ts +1 -0
  30. package/src/types.ts +5 -0
@@ -19,6 +19,9 @@ const SUPPORTED_HANDLERS = [
19
19
  const createEditable = (editor, plugins)=>({ readOnly })=>{
20
20
  const [selectedNodes] = useState(()=>atom([]));
21
21
  const $selectedNodes = useStore(selectedNodes);
22
+ const renderBlockPlugins = [
23
+ ...plugins
24
+ ].sort((left, right)=>(left.renderBlockPriority || 0) - (right.renderBlockPriority || 0));
22
25
  const renderElement = (props)=>{
23
26
  let result = null;
24
27
  for (const plugin of plugins)for (const element of plugin.blocks || []){
@@ -26,7 +29,7 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
26
29
  const newElement = element.render(props, editor);
27
30
  result = newElement || result;
28
31
  }
29
- for (const plugin of plugins)if (plugin.renderBlock) result = plugin.renderBlock({
32
+ for (const plugin of renderBlockPlugins)if (plugin.renderBlock) result = plugin.renderBlock({
30
33
  ...props,
31
34
  children: result || props.children
32
35
  });
@@ -1,4 +1,5 @@
1
1
  .root-aY1XCj {
2
+ counter-reset: line;
2
3
  flex-direction: column;
3
4
  font-family: monospace;
4
5
  display: flex;
@@ -0,0 +1,121 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Editor, Element, Range, Transforms } from "slate";
3
+ import { ReactEditor, useSlate } from "slate-react";
4
+ import styles_module from "./styles.module.js";
5
+ const sectionTypesByEditor = new WeakMap();
6
+ class CollapsibleBlocksPlugin {
7
+ types;
8
+ renderBlockPriority = -1;
9
+ constructor(types){
10
+ this.types = types;
11
+ }
12
+ init(editor) {
13
+ sectionTypesByEditor.set(editor, this.types);
14
+ return editor;
15
+ }
16
+ renderBlock = (props)=>{
17
+ const editor = useSlate();
18
+ const path = ReactEditor.findPath(editor, props.element);
19
+ if (1 !== path.length) return props.children;
20
+ const isHidden = isHiddenByCollapsedBoundary(editor, path, this.types);
21
+ if (isHidden) return /*#__PURE__*/ jsx("div", {
22
+ className: styles_module.hidden,
23
+ "data-collapsible-block-hidden": true,
24
+ hidden: true,
25
+ children: props.children
26
+ });
27
+ if (this.types.includes(props.element.type)) return /*#__PURE__*/ jsx(CollapsibleBoundary, {
28
+ editor: editor,
29
+ element: props.element,
30
+ path: path,
31
+ types: this.types,
32
+ children: props.children
33
+ });
34
+ return props.children;
35
+ };
36
+ }
37
+ const CollapsibleBoundary = ({ children, editor, element, path, types })=>{
38
+ const isCollapsed = true === element.collapsed;
39
+ const toggle = ()=>{
40
+ const willCollapse = !isCollapsed;
41
+ Transforms.setNodes(editor, {
42
+ collapsed: willCollapse
43
+ }, {
44
+ at: path
45
+ });
46
+ if (willCollapse && isSelectionInsideSection(editor, path, types)) Transforms.select(editor, Editor.start(editor, path));
47
+ };
48
+ return /*#__PURE__*/ jsxs("div", {
49
+ className: styles_module.boundary,
50
+ children: [
51
+ /*#__PURE__*/ jsx("button", {
52
+ "aria-expanded": !isCollapsed,
53
+ "aria-label": isCollapsed ? 'Expand section' : 'Collapse section',
54
+ className: isCollapsed ? styles_module.chevron : `${styles_module.chevron} ${styles_module.expanded}`,
55
+ contentEditable: false,
56
+ onClick: toggle,
57
+ onMouseDown: (event)=>event.preventDefault(),
58
+ type: "button",
59
+ children: /*#__PURE__*/ jsxs("svg", {
60
+ "aria-hidden": "true",
61
+ fill: "none",
62
+ height: "16",
63
+ stroke: "currentColor",
64
+ strokeLinecap: "round",
65
+ strokeLinejoin: "round",
66
+ strokeWidth: "1.5",
67
+ viewBox: "0 0 24 24",
68
+ width: "16",
69
+ children: [
70
+ /*#__PURE__*/ jsx("path", {
71
+ d: "M0 0h24v24H0z",
72
+ fill: "none",
73
+ stroke: "none"
74
+ }),
75
+ /*#__PURE__*/ jsx("path", {
76
+ d: "M9 6l6 6l-6 6"
77
+ })
78
+ ]
79
+ })
80
+ }),
81
+ children
82
+ ]
83
+ });
84
+ };
85
+ const getCollapsibleSectionPaths = (editor, path)=>{
86
+ const types = sectionTypesByEditor.get(editor);
87
+ if (!types || 1 !== path.length) return null;
88
+ const boundary = editor.children[path[0]];
89
+ if (!boundary || !types.includes(boundary.type)) return null;
90
+ const end = getSectionEnd(editor, path[0], types);
91
+ return Array.from({
92
+ length: end - path[0]
93
+ }, (_, index)=>[
94
+ path[0] + index
95
+ ]);
96
+ };
97
+ const isHiddenByCollapsedBoundary = (editor, path, types)=>{
98
+ for(let index = 0; index < path[0]; index++){
99
+ const node = editor.children[index];
100
+ if (Element.isElement(node) && true === node.collapsed && types.includes(node.type) && path[0] < getSectionEnd(editor, index, types)) return true;
101
+ }
102
+ return false;
103
+ };
104
+ const isSelectionInsideSection = (editor, boundaryPath, types)=>{
105
+ const { selection } = editor;
106
+ if (!selection || !Range.isCollapsed(selection)) return false;
107
+ const selectionIndex = selection.focus.path[0];
108
+ if (selectionIndex <= boundaryPath[0]) return false;
109
+ return selectionIndex < getSectionEnd(editor, boundaryPath[0], types);
110
+ };
111
+ const getSectionEnd = (editor, boundaryIndex, types)=>{
112
+ const boundary = editor.children[boundaryIndex];
113
+ const boundaryLevel = types.indexOf(boundary.type);
114
+ for(let index = boundaryIndex + 1; index < editor.children.length; index++){
115
+ const node = editor.children[index];
116
+ const nodeLevel = Element.isElement(node) ? types.indexOf(node.type) : -1;
117
+ if (-1 !== nodeLevel && nodeLevel <= boundaryLevel) return index;
118
+ }
119
+ return editor.children.length;
120
+ };
121
+ export { CollapsibleBlocksPlugin, getCollapsibleSectionPaths };
@@ -0,0 +1,2 @@
1
+ import { CollapsibleBlocksPlugin } from "./CollapsibleBlocksPlugin.js";
2
+ export { CollapsibleBlocksPlugin };
@@ -0,0 +1,8 @@
1
+ import "./styles_module.css";
2
+ const styles_module = {
3
+ boundary: "boundary-gLj40W",
4
+ chevron: "chevron-up0Fid",
5
+ expanded: "expanded-HCZXmg",
6
+ hidden: "hidden-icZZ78"
7
+ };
8
+ export { styles_module as default };
@@ -0,0 +1,38 @@
1
+ .boundary-gLj40W {
2
+ position: relative;
3
+ }
4
+
5
+ .chevron-up0Fid {
6
+ appearance: none;
7
+ color: inherit;
8
+ cursor: pointer;
9
+ inset-inline-start: -20px;
10
+ width: 16px;
11
+ height: 16px;
12
+ font: inherit;
13
+ background: none;
14
+ border: 0;
15
+ justify-content: center;
16
+ align-items: center;
17
+ margin: 0;
18
+ padding: 0;
19
+ line-height: 1;
20
+ transition: transform .15s;
21
+ display: flex;
22
+ position: absolute;
23
+ top: 50%;
24
+ transform: translateY(-50%);
25
+ }
26
+
27
+ .expanded-HCZXmg {
28
+ transform: translateY(-50%)rotate(90deg);
29
+ }
30
+
31
+ .chevron-up0Fid svg {
32
+ display: block;
33
+ }
34
+
35
+ .hidden-icZZ78 {
36
+ display: none;
37
+ }
38
+
@@ -196,9 +196,9 @@ const Menu = (props)=>{
196
196
  break;
197
197
  }
198
198
  };
199
- document.addEventListener('keydown', handleKeyDown);
199
+ document.addEventListener('keydown', handleKeyDown, true);
200
200
  return ()=>{
201
- document.removeEventListener('keydown', handleKeyDown);
201
+ document.removeEventListener('keydown', handleKeyDown, true);
202
202
  };
203
203
  }, [
204
204
  actions,
@@ -6,6 +6,7 @@ import { NativeTypes } from "react-dnd-html5-backend";
6
6
  import { Editor, Element, Path, Transforms } from "slate";
7
7
  import { ReactEditor, useReadOnly, useSlate } from "slate-react";
8
8
  import { useEditorContext } from "../../provider.js";
9
+ import { getCollapsibleSectionPaths } from "../CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.js";
9
10
  import { getNodesByNodeIds } from "./utils.js";
10
11
  class DnDPlugin {
11
12
  options;
@@ -37,7 +38,9 @@ class DnDPlugin {
37
38
  type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
38
39
  item: ()=>{
39
40
  const selected = $selectedNodes;
40
- const nodes = selected.length > 0 && selected.find((node)=>node.nodeId === currentElement.nodeId) ? getNodesByNodeIds(editor, selected.map((node)=>node.nodeId)) : [
41
+ const path = ReactEditor.findPath(editor, props.element);
42
+ const sectionPaths = getCollapsibleSectionPaths(editor, path);
43
+ const nodes = sectionPaths ? getNodesAtPaths(editor, sectionPaths) : selected.length > 0 && selected.find((node)=>node.nodeId === currentElement.nodeId) ? getNodesByNodeIds(editor, selected.map((node)=>node.nodeId)) : [
41
44
  structuredClone(props.element)
42
45
  ];
43
46
  const item = this.options.customTypes?.[currentElement.type]?.getData?.(currentElement);
@@ -52,7 +55,9 @@ class DnDPlugin {
52
55
  items,
53
56
  source: {
54
57
  editor,
55
- documentId: options.documentId
58
+ documentId: options.documentId,
59
+ path,
60
+ sectionPaths: sectionPaths || void 0
56
61
  }
57
62
  };
58
63
  return dragItem;
@@ -77,14 +82,8 @@ class DnDPlugin {
77
82
  };
78
83
  },
79
84
  hover (_, monitor) {
80
- const element = dropTargetRef.current;
81
- if (!element) return;
82
- const hoverBoundingRect = element.getBoundingClientRect();
83
- const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
84
- const clientOffset = monitor.getClientOffset();
85
- if (!clientOffset) return;
86
- const hoverClientY = clientOffset.y - hoverBoundingRect.top;
87
- hoverClientY < hoverMiddleY ? setDropPosition('top') : setDropPosition('bottom');
85
+ const position = getDropPosition(dropTargetRef.current, monitor);
86
+ if (position) setDropPosition(position);
88
87
  },
89
88
  drop: (data, monitor)=>{
90
89
  const itemType = monitor.getItemType();
@@ -93,7 +92,8 @@ class DnDPlugin {
93
92
  const isSameEditor = 'inner' === data.kind && data.source?.editor === editor;
94
93
  const sourceTo = ReactEditor.findPath(editor, props.element);
95
94
  if (!sourceTo) return;
96
- const insertAt = getDropPath(editor, sourceTo, dropPosition || 'bottom');
95
+ const position = getDropPosition(dropTargetRef.current, monitor) || dropPosition || 'bottom';
96
+ const insertAt = getDropPath(editor, sourceTo, position);
97
97
  if (!insertAt) return;
98
98
  const customDropHandler = itemType && options.customDropHandlers?.[itemType];
99
99
  if (customDropHandler && 'outer' === data.kind) return void customDropHandler.onDrop({
@@ -109,18 +109,18 @@ class DnDPlugin {
109
109
  {
110
110
  const dragItem = data;
111
111
  if ('outer' === dragItem.kind) return;
112
- if (!dropPosition || !dragItem.nodes.length) return;
113
- const nodeIds = dragItem.nodes.map((node)=>node.nodeId);
112
+ if (!dragItem.nodes.length) return;
113
+ const nodeIds = dragItem.nodes.map((node)=>node.nodeId).filter((nodeId)=>'string' == typeof nodeId);
114
114
  if (isSameDocument || isSameEditor) if (isCopying.current) {
115
115
  if (!generateId) return;
116
116
  const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
117
117
  insertNodes(editor, insertAt, clonedNodes);
118
- } else moveNode(editor, sourceTo, nodeIds, dropPosition);
118
+ } else moveNode(editor, sourceTo, nodeIds, position, dragItem.source.path, dragItem.source.sectionPaths);
119
119
  else {
120
120
  if (!generateId) return;
121
121
  const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
122
122
  insertNodes(editor, insertAt, clonedNodes);
123
- if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds);
123
+ if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds, dragItem.source.sectionPaths);
124
124
  }
125
125
  selectedNodes.set([]);
126
126
  break;
@@ -174,11 +174,33 @@ const getDropPath = (editor, targetNodePath, position)=>{
174
174
  return Path.next(target[1]);
175
175
  }
176
176
  };
177
- const moveNode = (editor, targetNodePath, nodeIds, position)=>{
177
+ const getDropPosition = (element, monitor)=>{
178
+ if (!element) return null;
179
+ const clientOffset = monitor.getClientOffset();
180
+ if (!clientOffset) return null;
181
+ const rect = element.getBoundingClientRect();
182
+ return clientOffset.y - rect.top < (rect.bottom - rect.top) / 2 ? 'top' : 'bottom';
183
+ };
184
+ const moveNode = (editor, targetNodePath, nodeIds, position, sourcePath, sectionPaths)=>{
178
185
  const dropPath = getDropPath(editor, targetNodePath, position);
179
186
  if (!dropPath) return;
180
- if (!Editor.hasPath(editor, dropPath)) return;
187
+ if (!isValidInsertPath(editor, dropPath)) return;
188
+ if (sectionPaths?.some((path)=>Path.equals(path, targetNodePath))) return;
181
189
  Editor.withoutNormalizing(editor, ()=>{
190
+ if (sectionPaths?.length) {
191
+ const sectionNodes = new Set(sectionPaths.filter((path)=>Editor.hasPath(editor, path)).map((path)=>Editor.node(editor, path)[0]));
192
+ Transforms.moveNodes(editor, {
193
+ at: [],
194
+ match: (node)=>sectionNodes.has(node),
195
+ to: dropPath,
196
+ mode: 'highest'
197
+ });
198
+ return;
199
+ }
200
+ if (0 === nodeIds.length && sourcePath && Editor.hasPath(editor, sourcePath)) return void Transforms.moveNodes(editor, {
201
+ at: sourcePath,
202
+ to: dropPath
203
+ });
182
204
  Transforms.moveNodes(editor, {
183
205
  at: [],
184
206
  match: (n)=>{
@@ -194,6 +216,13 @@ const moveNode = (editor, targetNodePath, nodeIds, position)=>{
194
216
  });
195
217
  Editor.normalize(editor);
196
218
  };
219
+ const isValidInsertPath = (editor, path)=>{
220
+ const parentPath = Path.parent(path);
221
+ const index = path[path.length - 1];
222
+ if (void 0 === index || !Editor.hasPath(editor, parentPath)) return false;
223
+ const [parent] = Editor.node(editor, parentPath);
224
+ return 'children' in parent && index <= parent.children.length;
225
+ };
197
226
  const insertNodes = (editor, path, nodes)=>{
198
227
  Editor.withoutNormalizing(editor, ()=>{
199
228
  Transforms.insertNodes(editor, nodes, {
@@ -201,8 +230,20 @@ const insertNodes = (editor, path, nodes)=>{
201
230
  });
202
231
  });
203
232
  };
204
- const removeNodes = (editor, nodeIds)=>{
233
+ const getNodesAtPaths = (editor, paths)=>paths.filter((path)=>Editor.hasPath(editor, path)).map((path)=>structuredClone(Editor.node(editor, path)[0]));
234
+ const removeNodes = (editor, nodeIds, sectionPaths)=>{
205
235
  try {
236
+ if (sectionPaths?.length) {
237
+ const paths = sectionPaths.filter((path)=>Editor.hasPath(editor, path)).sort((left, right)=>Path.compare(right, left));
238
+ Editor.withoutNormalizing(editor, ()=>{
239
+ paths.forEach((path)=>{
240
+ Transforms.removeNodes(editor, {
241
+ at: path
242
+ });
243
+ });
244
+ });
245
+ return;
246
+ }
206
247
  Transforms.removeNodes(editor, {
207
248
  at: [],
208
249
  match: (n)=>{
@@ -228,4 +269,4 @@ const cloneWithNewIds = (nodes, generateId)=>{
228
269
  copy.forEach(assignId);
229
270
  return copy;
230
271
  };
231
- export { DnDPlugin };
272
+ export { DnDPlugin, moveNode };
@@ -2,6 +2,7 @@ import { AttachmentsPlugin } from "./AttachmentsPlugin/index.js";
2
2
  import { BasicFormattingPlugin } from "./BasicFormattingPlugin/index.js";
3
3
  import { BreaksPlugin } from "./BreaksPlugin/index.js";
4
4
  import { CodeBlockPlugin } from "./CodeBlockPlugin/index.js";
5
+ import { CollapsibleBlocksPlugin } from "./CollapsibleBlocksPlugin/index.js";
5
6
  import { CommandsPlugin } from "./CommandsPlugin/index.js";
6
7
  import { DnDPlugin } from "./DnDPlugin/index.js";
7
8
  import { EmojiPlugin } from "./EmojiPlugin/index.js";
@@ -15,4 +16,4 @@ import { NodeIdPlugin } from "./NodeIdPlugin/index.js";
15
16
  import { PlaceholderPlugin } from "./PlaceholderPlugin/index.js";
16
17
  import { ShortcutsPlugin } from "./ShortcutsPlugin/index.js";
17
18
  import { TableOfContentsPlugin } from "./TableOfContentsPlugin/index.js";
18
- export { AttachmentsPlugin, BasicFormattingPlugin, BreaksPlugin, CodeBlockPlugin, CommandsPlugin, DnDPlugin, EmojiPlugin, FloatingMenuPlugin, HeadingsPlugin, HighlightsPlugin, LinksPlugin, ListsPlugin, MenuPlugin, NodeIdPlugin, PlaceholderPlugin, ShortcutsPlugin, TableOfContentsPlugin, isSafeLinkUrl };
19
+ export { AttachmentsPlugin, BasicFormattingPlugin, BreaksPlugin, CodeBlockPlugin, CollapsibleBlocksPlugin, CommandsPlugin, DnDPlugin, EmojiPlugin, FloatingMenuPlugin, HeadingsPlugin, HighlightsPlugin, LinksPlugin, ListsPlugin, MenuPlugin, NodeIdPlugin, PlaceholderPlugin, ShortcutsPlugin, TableOfContentsPlugin, isSafeLinkUrl };
@@ -0,0 +1,13 @@
1
+ import { Editor, type Path } from 'slate';
2
+ import { ReactEditor, type RenderElementProps } from 'slate-react';
3
+ import type { IPlugin } from '../../types';
4
+ export declare class CollapsibleBlocksPlugin implements IPlugin {
5
+ private types;
6
+ renderBlockPriority: number;
7
+ constructor(types: string[]);
8
+ init(editor: Editor): import("slate").BaseEditor & ReactEditor & {
9
+ getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
10
+ };
11
+ renderBlock: (props: RenderElementProps) => any;
12
+ }
13
+ export declare const getCollapsibleSectionPaths: (editor: Editor, path: Path) => number[][] | null;
@@ -0,0 +1 @@
1
+ export { CollapsibleBlocksPlugin } from './CollapsibleBlocksPlugin';
@@ -1,4 +1,4 @@
1
- import { Editor } from 'slate';
1
+ import { Editor, Path } from 'slate';
2
2
  import { ReactEditor, type RenderElementProps } from 'slate-react';
3
3
  import type { IPlugin } from '../../types';
4
4
  import type { Options } from './types';
@@ -14,3 +14,4 @@ export declare class DnDPlugin implements IPlugin {
14
14
  };
15
15
  renderBlock: (props: RenderElementProps) => any;
16
16
  }
17
+ export declare const moveNode: (editor: Editor, targetNodePath: Path, nodeIds: string[], position: "top" | "bottom", sourcePath?: Path, sectionPaths?: Path[]) => void;
@@ -61,6 +61,7 @@ export type EditorInnerDragItem<TItem = unknown> = {
61
61
  */
62
62
  documentId?: string | null;
63
63
  path?: Path;
64
+ sectionPaths?: Path[];
64
65
  };
65
66
  data?: Record<string, unknown>;
66
67
  };
@@ -2,6 +2,7 @@ export { AttachmentsPlugin } from './AttachmentsPlugin';
2
2
  export { BasicFormattingPlugin } from './BasicFormattingPlugin';
3
3
  export { BreaksPlugin } from './BreaksPlugin';
4
4
  export { CodeBlockPlugin, type CodeElement } from './CodeBlockPlugin';
5
+ export { CollapsibleBlocksPlugin } from './CollapsibleBlocksPlugin';
5
6
  export { type Command, type CommandPathEntry, CommandsPlugin, type GetCommandsContext, } from './CommandsPlugin';
6
7
  export { DnDPlugin } from './DnDPlugin';
7
8
  export { EmojiPlugin } from './EmojiPlugin';
@@ -8,6 +8,11 @@ export interface IPlugin<TEditor extends Editor = Editor, TBlock extends CustomE
8
8
  blocks?: Block<TEditor, TBlock>[];
9
9
  leafs?: Leaf<TEditor, TLeaf>[];
10
10
  hotkeys?: Hotkey[];
11
+ /**
12
+ * Render block wrappers from lower to higher priority. Plugins with the same
13
+ * priority retain the order supplied to KonaEditor.
14
+ */
15
+ renderBlockPriority?: number;
11
16
  renderBlock?: (props: RenderElementProps) => ReactElement | null;
12
17
  renderLeaf?: (props: RenderLeafProps) => ReactElement | null;
13
18
  handlers?: {
package/dist/types.d.ts CHANGED
@@ -3,5 +3,6 @@ export type CustomText = {
3
3
  };
4
4
  export type CustomElement = {
5
5
  type: string;
6
+ collapsed?: boolean;
6
7
  children: (CustomElement | CustomText)[];
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-kona/editor",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -47,6 +47,10 @@ export const createEditable =
47
47
  const [selectedNodes] = useState(() => atom<CustomElement[]>([]));
48
48
 
49
49
  const $selectedNodes = useStore(selectedNodes);
50
+ const renderBlockPlugins = [...plugins].sort(
51
+ (left, right) =>
52
+ (left.renderBlockPriority || 0) - (right.renderBlockPriority || 0),
53
+ );
50
54
 
51
55
  const renderElement = (props: RenderElementProps): ReactElement => {
52
56
  let result: ReactElement | null = null;
@@ -60,7 +64,7 @@ export const createEditable =
60
64
  }
61
65
  }
62
66
 
63
- for (const plugin of plugins) {
67
+ for (const plugin of renderBlockPlugins) {
64
68
  if (plugin.renderBlock) {
65
69
  result = plugin.renderBlock({
66
70
  ...props,
@@ -1,4 +1,5 @@
1
1
  .root {
2
+ counter-reset: line;
2
3
  overflow: hidden;
3
4
  font-family: monospace;
4
5
  display: flex;