@use-kona/editor 0.1.29 → 0.1.31

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 (27) 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 +123 -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/DnDPlugin/DnDPlugin.js +62 -19
  8. package/dist/plugins/index.js +2 -1
  9. package/dist/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.d.ts +14 -0
  10. package/dist/src/plugins/CollapsibleBlocksPlugin/index.d.ts +1 -0
  11. package/dist/src/plugins/DnDPlugin/DnDPlugin.d.ts +2 -1
  12. package/dist/src/plugins/DnDPlugin/types.d.ts +1 -0
  13. package/dist/src/plugins/index.d.ts +1 -0
  14. package/dist/src/types.d.ts +5 -0
  15. package/dist/types.d.ts +1 -0
  16. package/package.json +1 -1
  17. package/src/core/createEditable.tsx +5 -1
  18. package/src/plugins/CodeBlockPlugin/styles.module.css +1 -0
  19. package/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.spec.tsx +216 -0
  20. package/src/plugins/CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.tsx +190 -0
  21. package/src/plugins/CollapsibleBlocksPlugin/index.ts +1 -0
  22. package/src/plugins/CollapsibleBlocksPlugin/styles.module.css +37 -0
  23. package/src/plugins/DnDPlugin/DnDPlugin.spec.ts +408 -0
  24. package/src/plugins/DnDPlugin/DnDPlugin.tsx +122 -32
  25. package/src/plugins/DnDPlugin/types.ts +1 -0
  26. package/src/plugins/index.ts +1 -0
  27. 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,123 @@
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 = isHiddenByCollapsedSection(editor, path);
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 isHiddenByCollapsedSection = (editor, path)=>{
98
+ const types = sectionTypesByEditor.get(editor);
99
+ if (!types) return false;
100
+ for(let index = 0; index < path[0]; index++){
101
+ const node = editor.children[index];
102
+ if (Element.isElement(node) && true === node.collapsed && types.includes(node.type) && path[0] < getSectionEnd(editor, index, types)) return true;
103
+ }
104
+ return false;
105
+ };
106
+ const isSelectionInsideSection = (editor, boundaryPath, types)=>{
107
+ const { selection } = editor;
108
+ if (!selection || !Range.isCollapsed(selection)) return false;
109
+ const selectionIndex = selection.focus.path[0];
110
+ if (selectionIndex <= boundaryPath[0]) return false;
111
+ return selectionIndex < getSectionEnd(editor, boundaryPath[0], types);
112
+ };
113
+ const getSectionEnd = (editor, boundaryIndex, types)=>{
114
+ const boundary = editor.children[boundaryIndex];
115
+ const boundaryLevel = types.indexOf(boundary.type);
116
+ for(let index = boundaryIndex + 1; index < editor.children.length; index++){
117
+ const node = editor.children[index];
118
+ const nodeLevel = Element.isElement(node) ? types.indexOf(node.type) : -1;
119
+ if (-1 !== nodeLevel && nodeLevel <= boundaryLevel) return index;
120
+ }
121
+ return editor.children.length;
122
+ };
123
+ export { CollapsibleBlocksPlugin, getCollapsibleSectionPaths, isHiddenByCollapsedSection };
@@ -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
+
@@ -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, isHiddenByCollapsedSection } from "../CollapsibleBlocksPlugin/CollapsibleBlocksPlugin.js";
9
10
  import { getNodesByNodeIds } from "./utils.js";
10
11
  class DnDPlugin {
11
12
  options;
@@ -33,11 +34,14 @@ class DnDPlugin {
33
34
  const dropTargetRef = useRef(null);
34
35
  const customType = options.customTypes?.[props.element.type];
35
36
  const currentElement = props.element;
37
+ const path = ReactEditor.findPath(editor, props.element);
38
+ const isHidden = isHiddenByCollapsedSection(editor, path);
36
39
  const [, drag, preview] = useDrag({
37
40
  type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
38
41
  item: ()=>{
39
42
  const selected = $selectedNodes;
40
- const nodes = selected.length > 0 && selected.find((node)=>node.nodeId === currentElement.nodeId) ? getNodesByNodeIds(editor, selected.map((node)=>node.nodeId)) : [
43
+ const sectionPaths = getCollapsibleSectionPaths(editor, path);
44
+ const nodes = sectionPaths ? getNodesAtPaths(editor, sectionPaths) : selected.length > 0 && selected.find((node)=>node.nodeId === currentElement.nodeId) ? getNodesByNodeIds(editor, selected.map((node)=>node.nodeId)) : [
41
45
  structuredClone(props.element)
42
46
  ];
43
47
  const item = this.options.customTypes?.[currentElement.type]?.getData?.(currentElement);
@@ -52,7 +56,9 @@ class DnDPlugin {
52
56
  items,
53
57
  source: {
54
58
  editor,
55
- documentId: options.documentId
59
+ documentId: options.documentId,
60
+ path,
61
+ sectionPaths: sectionPaths || void 0
56
62
  }
57
63
  };
58
64
  return dragItem;
@@ -77,14 +83,8 @@ class DnDPlugin {
77
83
  };
78
84
  },
79
85
  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');
86
+ const position = getDropPosition(dropTargetRef.current, monitor);
87
+ if (position) setDropPosition(position);
88
88
  },
89
89
  drop: (data, monitor)=>{
90
90
  const itemType = monitor.getItemType();
@@ -93,7 +93,8 @@ class DnDPlugin {
93
93
  const isSameEditor = 'inner' === data.kind && data.source?.editor === editor;
94
94
  const sourceTo = ReactEditor.findPath(editor, props.element);
95
95
  if (!sourceTo) return;
96
- const insertAt = getDropPath(editor, sourceTo, dropPosition || 'bottom');
96
+ const position = getDropPosition(dropTargetRef.current, monitor) || dropPosition || 'bottom';
97
+ const insertAt = getDropPath(editor, sourceTo, position);
97
98
  if (!insertAt) return;
98
99
  const customDropHandler = itemType && options.customDropHandlers?.[itemType];
99
100
  if (customDropHandler && 'outer' === data.kind) return void customDropHandler.onDrop({
@@ -109,18 +110,18 @@ class DnDPlugin {
109
110
  {
110
111
  const dragItem = data;
111
112
  if ('outer' === dragItem.kind) return;
112
- if (!dropPosition || !dragItem.nodes.length) return;
113
- const nodeIds = dragItem.nodes.map((node)=>node.nodeId);
113
+ if (!dragItem.nodes.length) return;
114
+ const nodeIds = dragItem.nodes.map((node)=>node.nodeId).filter((nodeId)=>'string' == typeof nodeId);
114
115
  if (isSameDocument || isSameEditor) if (isCopying.current) {
115
116
  if (!generateId) return;
116
117
  const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
117
118
  insertNodes(editor, insertAt, clonedNodes);
118
- } else moveNode(editor, sourceTo, nodeIds, dropPosition);
119
+ } else moveNode(editor, sourceTo, nodeIds, position, dragItem.source.path, dragItem.source.sectionPaths);
119
120
  else {
120
121
  if (!generateId) return;
121
122
  const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
122
123
  insertNodes(editor, insertAt, clonedNodes);
123
- if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds);
124
+ if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds, dragItem.source.sectionPaths);
124
125
  }
125
126
  selectedNodes.set([]);
126
127
  break;
@@ -142,6 +143,7 @@ class DnDPlugin {
142
143
  if (this.options.ignoreNodes?.includes(props.element.type)) return props.children;
143
144
  if (editor.isInline(props.element)) return props.children;
144
145
  return /*#__PURE__*/ jsx("div", {
146
+ hidden: isHidden,
145
147
  children: options.renderBlock?.({
146
148
  props,
147
149
  dragRef: drag,
@@ -174,11 +176,33 @@ const getDropPath = (editor, targetNodePath, position)=>{
174
176
  return Path.next(target[1]);
175
177
  }
176
178
  };
177
- const moveNode = (editor, targetNodePath, nodeIds, position)=>{
179
+ const getDropPosition = (element, monitor)=>{
180
+ if (!element) return null;
181
+ const clientOffset = monitor.getClientOffset();
182
+ if (!clientOffset) return null;
183
+ const rect = element.getBoundingClientRect();
184
+ return clientOffset.y - rect.top < (rect.bottom - rect.top) / 2 ? 'top' : 'bottom';
185
+ };
186
+ const moveNode = (editor, targetNodePath, nodeIds, position, sourcePath, sectionPaths)=>{
178
187
  const dropPath = getDropPath(editor, targetNodePath, position);
179
188
  if (!dropPath) return;
180
- if (!Editor.hasPath(editor, dropPath)) return;
189
+ if (!isValidInsertPath(editor, dropPath)) return;
190
+ if (sectionPaths?.some((path)=>Path.equals(path, targetNodePath))) return;
181
191
  Editor.withoutNormalizing(editor, ()=>{
192
+ if (sectionPaths?.length) {
193
+ const sectionNodes = new Set(sectionPaths.filter((path)=>Editor.hasPath(editor, path)).map((path)=>Editor.node(editor, path)[0]));
194
+ Transforms.moveNodes(editor, {
195
+ at: [],
196
+ match: (node)=>sectionNodes.has(node),
197
+ to: dropPath,
198
+ mode: 'highest'
199
+ });
200
+ return;
201
+ }
202
+ if (0 === nodeIds.length && sourcePath && Editor.hasPath(editor, sourcePath)) return void Transforms.moveNodes(editor, {
203
+ at: sourcePath,
204
+ to: dropPath
205
+ });
182
206
  Transforms.moveNodes(editor, {
183
207
  at: [],
184
208
  match: (n)=>{
@@ -194,6 +218,13 @@ const moveNode = (editor, targetNodePath, nodeIds, position)=>{
194
218
  });
195
219
  Editor.normalize(editor);
196
220
  };
221
+ const isValidInsertPath = (editor, path)=>{
222
+ const parentPath = Path.parent(path);
223
+ const index = path[path.length - 1];
224
+ if (void 0 === index || !Editor.hasPath(editor, parentPath)) return false;
225
+ const [parent] = Editor.node(editor, parentPath);
226
+ return 'children' in parent && index <= parent.children.length;
227
+ };
197
228
  const insertNodes = (editor, path, nodes)=>{
198
229
  Editor.withoutNormalizing(editor, ()=>{
199
230
  Transforms.insertNodes(editor, nodes, {
@@ -201,8 +232,20 @@ const insertNodes = (editor, path, nodes)=>{
201
232
  });
202
233
  });
203
234
  };
204
- const removeNodes = (editor, nodeIds)=>{
235
+ const getNodesAtPaths = (editor, paths)=>paths.filter((path)=>Editor.hasPath(editor, path)).map((path)=>structuredClone(Editor.node(editor, path)[0]));
236
+ const removeNodes = (editor, nodeIds, sectionPaths)=>{
205
237
  try {
238
+ if (sectionPaths?.length) {
239
+ const paths = sectionPaths.filter((path)=>Editor.hasPath(editor, path)).sort((left, right)=>Path.compare(right, left));
240
+ Editor.withoutNormalizing(editor, ()=>{
241
+ paths.forEach((path)=>{
242
+ Transforms.removeNodes(editor, {
243
+ at: path
244
+ });
245
+ });
246
+ });
247
+ return;
248
+ }
206
249
  Transforms.removeNodes(editor, {
207
250
  at: [],
208
251
  match: (n)=>{
@@ -228,4 +271,4 @@ const cloneWithNewIds = (nodes, generateId)=>{
228
271
  copy.forEach(assignId);
229
272
  return copy;
230
273
  };
231
- export { DnDPlugin };
274
+ 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,14 @@
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;
14
+ export declare const isHiddenByCollapsedSection: (editor: Editor, path: Path) => boolean;
@@ -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.29",
3
+ "version": "0.1.31",
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;