@blocknote/xl-multi-column 0.47.0 → 0.47.2
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.
- package/dist/blocknote-xl-multi-column.cjs +1 -1
- package/dist/blocknote-xl-multi-column.cjs.map +1 -1
- package/dist/blocknote-xl-multi-column.js +381 -485
- package/dist/blocknote-xl-multi-column.js.map +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +3 -3
- package/src/blocks/Columns/index.ts +2 -0
- package/src/extensions/DropCursor/multiColumnDropCursor.ts +112 -0
- package/src/extensions/DropCursor/multiColumnHandleDropPlugin.ts +162 -0
- package/src/index.ts +1 -1
- package/types/src/extensions/DropCursor/multiColumnDropCursor.d.ts +21 -0
- package/types/src/extensions/DropCursor/multiColumnHandleDropPlugin.d.ts +16 -0
- package/types/src/index.d.ts +1 -1
- package/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +0 -508
- package/types/src/extensions/DropCursor/MultiColumnDropCursorPlugin.d.ts +0 -11
package/dist/webpack-stats.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"builtAt":
|
|
1
|
+
{"builtAt":1774004019713,"assets":[{"name":"blocknote-xl-multi-column.cjs","size":17011},{"name":"blocknote-xl-multi-column.cjs.map","size":3780581}],"chunks":[{"id":"a1ee98a","entry":true,"initial":true,"files":["blocknote-xl-multi-column.cjs"],"names":["blocknote-xl-multi-column"]}],"modules":[{"name":"./src/i18n/locales/ar.ts","size":364,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/de.ts","size":400,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/en.ts","size":378,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/es.ts","size":392,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/fr.ts","size":398,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/hr.ts","size":396,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/is.ts","size":376,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ja.ts","size":284,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ko.ts","size":288,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/nl.ts","size":390,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/pl.ts","size":394,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/pt.ts","size":388,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ru.ts","size":370,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/vi.ts","size":340,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/zh.ts","size":274,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/index.ts","size":0,"chunks":["a1ee98a"]},{"name":"./src/i18n/dictionary.ts","size":189,"chunks":["a1ee98a"]},{"name":"./src/extensions/DropCursor/multiColumnDropCursor.ts","size":1778,"chunks":["a1ee98a"]},{"name":"./src/extensions/DropCursor/multiColumnHandleDropPlugin.ts","size":3253,"chunks":["a1ee98a"]},{"name":"./src/extensions/ColumnResize/ColumnResizeExtension.ts","size":8412,"chunks":["a1ee98a"]},{"name":"./src/pm-nodes/Column.ts","size":2593,"chunks":["a1ee98a"]},{"name":"./src/pm-nodes/ColumnList.ts","size":1146,"chunks":["a1ee98a"]},{"name":"./src/blocks/Columns/index.ts","size":351,"chunks":["a1ee98a"]},{"name":"./src/blocks/schema.ts","size":293,"chunks":["a1ee98a"]},{"name":"../../node_modules/.pnpm/react-icons@5.5.0_react@19.2.3/node_modules/react-icons/lib/iconContext.mjs","size":251,"chunks":["a1ee98a"]},{"name":"../../node_modules/.pnpm/react-icons@5.5.0_react@19.2.3/node_modules/react-icons/lib/iconBase.mjs","size":4003,"chunks":["a1ee98a"]},{"name":"../../node_modules/.pnpm/react-icons@5.5.0_react@19.2.3/node_modules/react-icons/tb/index.mjs","size":708,"chunks":["a1ee98a"]},{"name":"./src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx","size":2105,"chunks":["a1ee98a"]},{"name":"./src/index.ts","size":0,"chunks":["a1ee98a"]}]}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"directory": "packages/xl-multi-column"
|
|
10
10
|
},
|
|
11
11
|
"license": "GPL-3.0 OR PROPRIETARY",
|
|
12
|
-
"version": "0.47.
|
|
12
|
+
"version": "0.47.2",
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"types",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@blocknote/core": "0.47.
|
|
46
|
-
"@blocknote/react": "0.47.
|
|
45
|
+
"@blocknote/core": "0.47.2",
|
|
46
|
+
"@blocknote/react": "0.47.2",
|
|
47
47
|
"@tiptap/core": "^3.13.0",
|
|
48
48
|
"prosemirror-model": "^1.25.4",
|
|
49
49
|
"prosemirror-state": "^1.4.4",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MultiColumnDropHandlerExtension } from "../../extensions/DropCursor/multiColumnHandleDropPlugin.js";
|
|
1
2
|
import { Column } from "../../pm-nodes/Column.js";
|
|
2
3
|
import { ColumnList } from "../../pm-nodes/ColumnList.js";
|
|
3
4
|
|
|
@@ -14,6 +15,7 @@ export const ColumnBlock = createBlockSpecFromTiptapNode(
|
|
|
14
15
|
default: 1,
|
|
15
16
|
},
|
|
16
17
|
},
|
|
18
|
+
[MultiColumnDropHandlerExtension()],
|
|
17
19
|
);
|
|
18
20
|
|
|
19
21
|
export const ColumnListBlock = createBlockSpecFromTiptapNode(
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { type DropCursorHooks, getNearestBlockPos } from "@blocknote/core";
|
|
2
|
+
import type { EditorState } from "prosemirror-state";
|
|
3
|
+
import type { EditorView } from "prosemirror-view";
|
|
4
|
+
|
|
5
|
+
const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1;
|
|
6
|
+
|
|
7
|
+
export interface EdgeDropPosition {
|
|
8
|
+
position: "left" | "right" | "regular";
|
|
9
|
+
posBeforeNode: number;
|
|
10
|
+
node: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detects if the drop event is near the left or right edge of a block.
|
|
15
|
+
* Shared utility used by both the drop cursor visualization and the drop handler.
|
|
16
|
+
* Returns null when the event position cannot be resolved (e.g. drop outside editor bounds).
|
|
17
|
+
*/
|
|
18
|
+
export function detectEdgePosition(
|
|
19
|
+
event: DragEvent,
|
|
20
|
+
view: EditorView,
|
|
21
|
+
state: EditorState,
|
|
22
|
+
): EdgeDropPosition | null {
|
|
23
|
+
const eventPos = view.posAtCoords({
|
|
24
|
+
left: event.clientX,
|
|
25
|
+
top: event.clientY,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!eventPos) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const blockPos = getNearestBlockPos(state.doc, eventPos.pos);
|
|
33
|
+
|
|
34
|
+
// If we're at a block that's in a column, we want to compare the mouse position to the column, not the block inside it
|
|
35
|
+
// Why? Because we want to insert a new column in the columnList, instead of a new columnList inside of the column
|
|
36
|
+
let resolved = state.doc.resolve(blockPos.posBeforeNode);
|
|
37
|
+
if (resolved.parent.type.name === "column") {
|
|
38
|
+
resolved = state.doc.resolve(resolved.before());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const posInfo = {
|
|
42
|
+
posBeforeNode: resolved.pos,
|
|
43
|
+
node: resolved.nodeAfter!,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const blockElement = view.nodeDOM(posInfo.posBeforeNode);
|
|
47
|
+
if (blockElement === null) {
|
|
48
|
+
return {
|
|
49
|
+
position: "regular",
|
|
50
|
+
posBeforeNode: posInfo.posBeforeNode,
|
|
51
|
+
node: posInfo.node,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const blockRect = (blockElement as HTMLElement).getBoundingClientRect();
|
|
55
|
+
|
|
56
|
+
let position: "regular" | "left" | "right" = "regular";
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
event.clientX <=
|
|
60
|
+
blockRect.left +
|
|
61
|
+
blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
|
|
62
|
+
) {
|
|
63
|
+
position = "left";
|
|
64
|
+
} else if (
|
|
65
|
+
event.clientX >=
|
|
66
|
+
blockRect.right -
|
|
67
|
+
blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
|
|
68
|
+
) {
|
|
69
|
+
position = "right";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
position,
|
|
74
|
+
posBeforeNode: posInfo.posBeforeNode,
|
|
75
|
+
node: posInfo.node,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates the computeDropPosition hook for multi-column support.
|
|
81
|
+
* This hook detects edge drops and returns vertical cursor orientations.
|
|
82
|
+
*/
|
|
83
|
+
export const multiColumnDropCursor: { hooks: DropCursorHooks } = {
|
|
84
|
+
hooks: {
|
|
85
|
+
computeDropPosition: (context) => {
|
|
86
|
+
const edgePos = detectEdgePosition(
|
|
87
|
+
context.event,
|
|
88
|
+
context.view,
|
|
89
|
+
context.view.state,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Fall back to default when position cannot be resolved
|
|
93
|
+
if (edgePos === null) {
|
|
94
|
+
return context.defaultPosition;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If it's a regular (non-edge) drop, use the default position
|
|
98
|
+
if (edgePos.position === "regular") {
|
|
99
|
+
return context.defaultPosition;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Edge drop - show vertical cursor
|
|
103
|
+
return {
|
|
104
|
+
pos: edgePos.posBeforeNode,
|
|
105
|
+
orientation:
|
|
106
|
+
edgePos.position === "left"
|
|
107
|
+
? "block-vertical-left"
|
|
108
|
+
: "block-vertical-right",
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { BlockNoteEditor } from "@blocknote/core";
|
|
2
|
+
import {
|
|
3
|
+
UniqueID,
|
|
4
|
+
createExtension,
|
|
5
|
+
getBlockInfo,
|
|
6
|
+
nodeToBlock,
|
|
7
|
+
} from "@blocknote/core";
|
|
8
|
+
import { Plugin } from "prosemirror-state";
|
|
9
|
+
import type { EditorView } from "prosemirror-view";
|
|
10
|
+
import { detectEdgePosition } from "./multiColumnDropCursor.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a ProseMirror plugin that handles drop events for multi-column layouts.
|
|
14
|
+
* When a block is dropped near the left or right edge of another block, it creates
|
|
15
|
+
* or modifies column layouts.
|
|
16
|
+
*/
|
|
17
|
+
export function createMultiColumnHandleDropPlugin(
|
|
18
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
19
|
+
): Plugin {
|
|
20
|
+
return new Plugin({
|
|
21
|
+
props: {
|
|
22
|
+
handleDrop(view: EditorView, event: DragEvent, slice, _moved) {
|
|
23
|
+
const edgePos = detectEdgePosition(event, view, view.state);
|
|
24
|
+
if (edgePos === null) {
|
|
25
|
+
return false; // Let ProseMirror handle the drop (e.g. outside editor bounds)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const blockInfo = getBlockInfo(edgePos);
|
|
29
|
+
|
|
30
|
+
// Only handle edge drops (left/right)
|
|
31
|
+
if (edgePos.position === "regular") {
|
|
32
|
+
return false; // Let ProseMirror handle regular drops
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (slice.content.childCount === 0) {
|
|
36
|
+
return false; // Let ProseMirror handle empty slice drops
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const draggedBlock = nodeToBlock(
|
|
40
|
+
slice.content.child(0),
|
|
41
|
+
editor.pmSchema,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (blockInfo.blockNoteType === "column") {
|
|
45
|
+
// Insert new column in existing columnList
|
|
46
|
+
const parentBlock = view.state.doc
|
|
47
|
+
.resolve(blockInfo.bnBlock.beforePos)
|
|
48
|
+
.node();
|
|
49
|
+
|
|
50
|
+
const columnList = nodeToBlock<any, any, any>(
|
|
51
|
+
parentBlock,
|
|
52
|
+
editor.pmSchema,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Normalize column widths to average of 1
|
|
56
|
+
// In a `columnList`, we expect that the average width of each column
|
|
57
|
+
// is 1. However, there are cases in which this stops being true. For
|
|
58
|
+
// example, having one wider column and then removing it will cause
|
|
59
|
+
// the average width to go down. This isn't really an issue until the
|
|
60
|
+
// user tries to add a new column, which will, in this case, be wider
|
|
61
|
+
// than expected. Therefore, we normalize the column widths to an
|
|
62
|
+
// average of 1 here to avoid this issue.
|
|
63
|
+
let sumColumnWidthPercent = 0;
|
|
64
|
+
columnList.children.forEach((column) => {
|
|
65
|
+
sumColumnWidthPercent += column.props.width as number;
|
|
66
|
+
});
|
|
67
|
+
const avgColumnWidthPercent =
|
|
68
|
+
sumColumnWidthPercent / columnList.children.length;
|
|
69
|
+
|
|
70
|
+
// If the average column width is not 1, normalize it. We're dealing
|
|
71
|
+
// with floats so we need a small margin to account for precision
|
|
72
|
+
// errors.
|
|
73
|
+
if (avgColumnWidthPercent < 0.99 || avgColumnWidthPercent > 1.01) {
|
|
74
|
+
const scalingFactor = 1 / avgColumnWidthPercent;
|
|
75
|
+
|
|
76
|
+
columnList.children.forEach((column) => {
|
|
77
|
+
column.props.width =
|
|
78
|
+
(column.props.width as number) * scalingFactor;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const index = columnList.children.findIndex(
|
|
83
|
+
(b) => b.id === blockInfo.bnBlock.node.attrs.id,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const newChildren = columnList.children
|
|
87
|
+
// If the dragged block is in one of the columns, remove it.
|
|
88
|
+
.map((column) => ({
|
|
89
|
+
...column,
|
|
90
|
+
children: column.children.filter(
|
|
91
|
+
(block) => block.id !== draggedBlock.id,
|
|
92
|
+
),
|
|
93
|
+
}))
|
|
94
|
+
// Remove empty columns (can happen when dragged block is removed).
|
|
95
|
+
.filter((column) => column.children.length > 0)
|
|
96
|
+
// Insert the dragged block in the correct position.
|
|
97
|
+
.toSpliced(edgePos.position === "left" ? index : index + 1, 0, {
|
|
98
|
+
type: "column",
|
|
99
|
+
children: [draggedBlock],
|
|
100
|
+
props: {},
|
|
101
|
+
content: undefined,
|
|
102
|
+
id: UniqueID.options.generateID(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (editor.getBlock(draggedBlock.id)) {
|
|
106
|
+
editor.removeBlocks([draggedBlock]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
editor.updateBlock(columnList, {
|
|
110
|
+
children: newChildren,
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
// Create new columnList with blocks as columns
|
|
114
|
+
const block = nodeToBlock(blockInfo.bnBlock.node, editor.pmSchema);
|
|
115
|
+
|
|
116
|
+
// The user is dropping next to the original block being dragged - do
|
|
117
|
+
// nothing.
|
|
118
|
+
if (block.id === draggedBlock.id) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const blocks =
|
|
123
|
+
edgePos.position === "left"
|
|
124
|
+
? [draggedBlock, block]
|
|
125
|
+
: [block, draggedBlock];
|
|
126
|
+
|
|
127
|
+
if (editor.getBlock(draggedBlock.id)) {
|
|
128
|
+
editor.removeBlocks([draggedBlock]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
editor.replaceBlocks(
|
|
132
|
+
[block],
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
type: "columnList",
|
|
136
|
+
children: blocks.map((b) => {
|
|
137
|
+
return {
|
|
138
|
+
type: "column",
|
|
139
|
+
children: [b],
|
|
140
|
+
};
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return true; // Prevent default ProseMirror drop behavior
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* BlockNote extension that adds the multi-column drop handler plugin.
|
|
155
|
+
* This should be added to the editor's extensions to enable column creation via drag-and-drop.
|
|
156
|
+
*/
|
|
157
|
+
export const MultiColumnDropHandlerExtension = createExtension(
|
|
158
|
+
({ editor }) => ({
|
|
159
|
+
key: "multiColumnDropHandler",
|
|
160
|
+
prosemirrorPlugins: [createMultiColumnHandleDropPlugin(editor)],
|
|
161
|
+
}),
|
|
162
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { locales };
|
|
|
3
3
|
export * from "./i18n/dictionary.js";
|
|
4
4
|
export * from "./blocks/Columns/index.js";
|
|
5
5
|
export * from "./blocks/schema.js";
|
|
6
|
-
export * from "./extensions/DropCursor/
|
|
6
|
+
export * from "./extensions/DropCursor/multiColumnDropCursor.js";
|
|
7
7
|
export * from "./extensions/SuggestionMenu/getMultiColumnSlashMenuItems.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DropCursorHooks } from "@blocknote/core";
|
|
2
|
+
import type { EditorState } from "prosemirror-state";
|
|
3
|
+
import type { EditorView } from "prosemirror-view";
|
|
4
|
+
export interface EdgeDropPosition {
|
|
5
|
+
position: "left" | "right" | "regular";
|
|
6
|
+
posBeforeNode: number;
|
|
7
|
+
node: any;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Detects if the drop event is near the left or right edge of a block.
|
|
11
|
+
* Shared utility used by both the drop cursor visualization and the drop handler.
|
|
12
|
+
* Returns null when the event position cannot be resolved (e.g. drop outside editor bounds).
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectEdgePosition(event: DragEvent, view: EditorView, state: EditorState): EdgeDropPosition | null;
|
|
15
|
+
/**
|
|
16
|
+
* Creates the computeDropPosition hook for multi-column support.
|
|
17
|
+
* This hook detects edge drops and returns vertical cursor orientations.
|
|
18
|
+
*/
|
|
19
|
+
export declare const multiColumnDropCursor: {
|
|
20
|
+
hooks: DropCursorHooks;
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BlockNoteEditor } from "@blocknote/core";
|
|
2
|
+
import { Plugin } from "prosemirror-state";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a ProseMirror plugin that handles drop events for multi-column layouts.
|
|
5
|
+
* When a block is dropped near the left or right edge of another block, it creates
|
|
6
|
+
* or modifies column layouts.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createMultiColumnHandleDropPlugin(editor: BlockNoteEditor<any, any, any>): Plugin;
|
|
9
|
+
/**
|
|
10
|
+
* BlockNote extension that adds the multi-column drop handler plugin.
|
|
11
|
+
* This should be added to the editor's extensions to enable column creation via drag-and-drop.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MultiColumnDropHandlerExtension: (options?: any) => import("@blocknote/core").ExtensionFactoryInstance<{
|
|
14
|
+
key: string;
|
|
15
|
+
prosemirrorPlugins: Plugin<any>[];
|
|
16
|
+
}>;
|
package/types/src/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { locales };
|
|
|
3
3
|
export * from "./i18n/dictionary.js";
|
|
4
4
|
export * from "./blocks/Columns/index.js";
|
|
5
5
|
export * from "./blocks/schema.js";
|
|
6
|
-
export * from "./extensions/DropCursor/
|
|
6
|
+
export * from "./extensions/DropCursor/multiColumnDropCursor.js";
|
|
7
7
|
export * from "./extensions/SuggestionMenu/getMultiColumnSlashMenuItems.js";
|