@thangph2146/lexical-editor 0.0.3 → 0.0.5
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/editor-x/editor.cjs +724 -435
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +1391 -1091
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.js +728 -439
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +760 -472
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1391 -1091
- package/dist/index.css.map +1 -1
- package/dist/index.js +763 -475
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/lexical-editor.tsx +138 -123
- package/src/editor-ui/content-editable.tsx +3 -3
- package/src/editor-x/editor.tsx +16 -3
- package/src/editor-x/plugins.tsx +385 -380
- package/src/nodes/list-with-color-node.tsx +160 -160
- package/src/plugins/autocomplete-plugin.tsx +2574 -2574
- package/src/plugins/context-menu-plugin.tsx +239 -9
- package/src/plugins/floating-text-format-plugin.tsx +84 -92
- package/src/plugins/images-plugin.tsx +4 -4
- package/src/plugins/list-color-plugin.tsx +178 -178
- package/src/plugins/tab-focus-plugin.tsx +66 -66
- package/src/plugins/table-column-resizer-plugin.tsx +329 -190
- package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
- package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
- package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
- package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
- package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
- package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
- package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
- package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
- package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
- package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
- package/src/themes/_mixins.scss +158 -10
- package/src/themes/_variables.scss +168 -0
- package/src/themes/core/_code.scss +59 -0
- package/src/themes/core/_images.scss +80 -0
- package/src/themes/core/_lists.scss +214 -0
- package/src/themes/core/_misc.scss +46 -0
- package/src/themes/core/_reset.scss +119 -0
- package/src/themes/core/_tables.scss +145 -0
- package/src/themes/core/_text.scss +35 -0
- package/src/themes/core/_typography.scss +73 -0
- package/src/themes/editor-theme copy.scss +763 -0
- package/src/themes/editor-theme.scss +9 -621
- package/src/themes/editor-theme.ts +118 -118
- package/src/themes/plugins/_auto-embed.scss +11 -0
- package/src/themes/plugins/_color-picker.scss +103 -0
- package/src/themes/plugins/_draggable-block.scss +32 -0
- package/src/themes/plugins/_floating-link-editor.scss +47 -0
- package/src/themes/plugins/_floating-toolbars.scss +61 -0
- package/src/themes/plugins/_image-resizer.scss +38 -0
- package/src/themes/plugins/_image.scss +57 -0
- package/src/themes/plugins/_layout.scss +39 -0
- package/src/themes/plugins/_list-color.scss +23 -0
- package/src/themes/plugins/_mentions.scss +21 -0
- package/src/themes/plugins/_menus-and-pickers.scss +153 -0
- package/src/themes/plugins/_table.scss +20 -0
- package/src/themes/plugins/_toolbar.scss +36 -0
- package/src/themes/plugins/_tree-view.scss +11 -0
- package/src/themes/plugins copy.scss +656 -0
- package/src/themes/plugins.scss +20 -1165
- package/src/themes/ui-components/_animations.scss +31 -0
- package/src/themes/ui-components/_backgrounds.scss +27 -0
- package/src/themes/ui-components/_borders.scss +20 -0
- package/src/themes/ui-components/_button.scss +176 -0
- package/src/themes/ui-components/_checkbox.scss +14 -0
- package/src/themes/ui-components/_cursors.scss +31 -0
- package/src/themes/ui-components/_dialog.scss +86 -0
- package/src/themes/ui-components/_display-sizing.scss +100 -0
- package/src/themes/ui-components/_flex.scss +124 -0
- package/src/themes/ui-components/_form-layout.scss +15 -0
- package/src/themes/ui-components/_icons.scss +23 -0
- package/src/themes/ui-components/_input.scss +86 -0
- package/src/themes/ui-components/_label.scss +19 -0
- package/src/themes/ui-components/_loader.scss +9 -0
- package/src/themes/ui-components/_margins-paddings.scss +45 -0
- package/src/themes/ui-components/_popover.scss +16 -0
- package/src/themes/ui-components/_positioning.scss +73 -0
- package/src/themes/ui-components/_rounded.scss +19 -0
- package/src/themes/ui-components/_scroll-area.scss +11 -0
- package/src/themes/ui-components/_select.scss +110 -0
- package/src/themes/ui-components/_separator.scss +19 -0
- package/src/themes/ui-components/_shadow.scss +15 -0
- package/src/themes/ui-components/_tabs.scss +46 -0
- package/src/themes/ui-components/_text-utilities.scss +48 -0
- package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
- package/src/themes/ui-components/_toggle.scss +80 -0
- package/src/themes/ui-components/_typography.scss +22 -0
- package/src/themes/ui-components copy.scss +1335 -0
- package/src/themes/ui-components.scss +27 -937
- package/src/transformers/markdown-list-transformer.ts +51 -51
- package/src/ui/button.tsx +11 -2
- package/src/ui/collapsible.tsx +1 -1
- package/src/ui/dialog.tsx +2 -2
- package/src/ui/flex.tsx +4 -4
- package/src/ui/popover.tsx +1 -1
- package/src/ui/tooltip.tsx +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef } from "react"
|
|
3
|
+
import { useEffect, useRef, useState, useMemo, useLayoutEffect, useCallback } from "react"
|
|
4
|
+
import { createPortal } from "react-dom"
|
|
4
5
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
|
|
5
6
|
import { useLexicalEditable } from "@lexical/react/useLexicalEditable"
|
|
6
7
|
import {
|
|
@@ -18,10 +19,16 @@ type DragState = {
|
|
|
18
19
|
columnIndex: number
|
|
19
20
|
startX: number
|
|
20
21
|
startWidth: number
|
|
21
|
-
initialColWidths: number[]
|
|
22
|
+
initialColWidths: readonly number[]
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
type HoverState = {
|
|
26
|
+
cellKey: NodeKey
|
|
27
|
+
tableKey: NodeKey
|
|
28
|
+
edge: ResizeEdge
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EDGE_HITBOX_PX = 8
|
|
25
32
|
|
|
26
33
|
/** Độ rộng tối thiểu khi kéo cột (px) — nhỏ hơn để có thể thu hẹp cột như "TT" */
|
|
27
34
|
const MIN_COLUMN_WIDTH_PX = 15
|
|
@@ -46,7 +53,7 @@ function getResizeEdge(clientX: number, rect: DOMRect): ResizeEdge | null {
|
|
|
46
53
|
return nearLeft ? "left" : "right"
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
function getColumnIndexFromTableMap(
|
|
56
|
+
function $getColumnIndexFromTableMap(
|
|
50
57
|
tableNode: TableNode,
|
|
51
58
|
tableCellNode: ReturnType<typeof $getNearestNodeFromDOMNode>
|
|
52
59
|
): number | null {
|
|
@@ -68,242 +75,374 @@ function getColumnIndexFromTableMap(
|
|
|
68
75
|
return null
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
export function TableColumnResizerPlugin(
|
|
78
|
+
export function TableColumnResizerPlugin({
|
|
79
|
+
anchorElem = document.body,
|
|
80
|
+
}: {
|
|
81
|
+
anchorElem?: HTMLElement
|
|
82
|
+
}) {
|
|
72
83
|
const [editor] = useLexicalComposerContext()
|
|
73
84
|
const isEditable = useLexicalEditable()
|
|
74
85
|
const dragRef = useRef<DragState | null>(null)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
const [hoverState, setHoverState] = useState<HoverState | null>(null)
|
|
87
|
+
const resizerRef = useRef<HTMLDivElement | null>(null)
|
|
88
|
+
|
|
89
|
+
const onPointerDownImpl = useCallback((
|
|
90
|
+
event: PointerEvent,
|
|
91
|
+
cell: HTMLTableCellElement,
|
|
92
|
+
edge: ResizeEdge
|
|
93
|
+
) => {
|
|
94
|
+
if (event.button !== 0) return
|
|
95
|
+
|
|
96
|
+
event.preventDefault()
|
|
97
|
+
|
|
98
|
+
// Capture pointer to handle dragging outside the resizer handle
|
|
99
|
+
const target = event.target as HTMLElement
|
|
100
|
+
if (target) {
|
|
101
|
+
target.setPointerCapture(event.pointerId)
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
if (dragRef.current) return
|
|
92
|
-
|
|
93
|
-
const cell = getCellTarget(event.target)
|
|
94
|
-
if (!cell) {
|
|
95
|
-
clearCursor()
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const edge = getResizeEdge(event.clientX, cell.getBoundingClientRect())
|
|
100
|
-
|
|
101
|
-
// Disable dragging left edge of the first column (visual check)
|
|
102
|
-
if (edge === "left") {
|
|
103
|
-
const table = cell.closest("table")
|
|
104
|
-
if (table) {
|
|
105
|
-
const tableRect = table.getBoundingClientRect()
|
|
106
|
-
const cellRect = cell.getBoundingClientRect()
|
|
107
|
-
// If cell left edge is close to table left edge (within tolerance)
|
|
108
|
-
if (Math.abs(cellRect.left - tableRect.left) < 5) {
|
|
109
|
-
clearCursor()
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
setCursor(edge ? "col-resize" : "")
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const onPointerDown = (event: PointerEvent) => {
|
|
119
|
-
if (event.button !== 0) return
|
|
120
|
-
|
|
121
|
-
const cell = getCellTarget(event.target)
|
|
122
|
-
if (!cell) return
|
|
104
|
+
let nextDragState: DragState | null = null
|
|
123
105
|
|
|
124
|
-
|
|
125
|
-
|
|
106
|
+
editor.update(() => {
|
|
107
|
+
const tableCellNode = $getNearestNodeFromDOMNode(cell)
|
|
108
|
+
if (!$isTableCellNode(tableCellNode)) return
|
|
126
109
|
|
|
127
|
-
|
|
110
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode)
|
|
111
|
+
const tableElement = editor.getElementByKey(tableNode.getKey())
|
|
112
|
+
if (!tableElement) return
|
|
128
113
|
|
|
129
|
-
|
|
114
|
+
const tableMap = $computeTableMapSkipCellCheck(tableNode, null, null)[0]
|
|
115
|
+
const colCount = tableMap[0]?.length ?? 0
|
|
116
|
+
const columnIndexFromMap = $getColumnIndexFromTableMap(tableNode, tableCellNode)
|
|
130
117
|
|
|
131
|
-
|
|
132
|
-
const tableCellNode = $getNearestNodeFromDOMNode(cell)
|
|
133
|
-
if (!$isTableCellNode(tableCellNode)) return
|
|
118
|
+
if (columnIndexFromMap === null) return
|
|
134
119
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
const colSpan = tableCellNode.getColSpan()
|
|
121
|
+
const resizeColumnIndex =
|
|
122
|
+
edge === "left" ? columnIndexFromMap - 1 : columnIndexFromMap + colSpan - 1
|
|
138
123
|
|
|
139
|
-
|
|
140
|
-
if (edge === "left" && columnIndexFromMap === 0) return
|
|
124
|
+
if (resizeColumnIndex < 0 || resizeColumnIndex >= colCount - 1) return
|
|
141
125
|
|
|
142
|
-
|
|
143
|
-
const resizeColumnIndex =
|
|
144
|
-
edge === "left" ? columnIndexFromMap - 1 : columnIndexFromMap + colSpan - 1
|
|
126
|
+
let currentWidths = tableNode.getColWidths()
|
|
145
127
|
|
|
146
|
-
|
|
128
|
+
if (!currentWidths) {
|
|
129
|
+
// If no widths are stored in the node, measure the actual table
|
|
130
|
+
const columns = tableElement.querySelectorAll("col")
|
|
131
|
+
const widths: number[] = []
|
|
147
132
|
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
for (let r = 0; r < tableMap.length; r++) {
|
|
157
|
-
const row = tableMap[r]
|
|
158
|
-
if (!row) continue
|
|
159
|
-
for (let c = 0; c < row.length; c++) {
|
|
160
|
-
if (widths[c] !== undefined) continue
|
|
161
|
-
|
|
162
|
-
const tableMapCell = row[c]
|
|
163
|
-
if (!tableMapCell) continue
|
|
164
|
-
|
|
165
|
-
const { cell } = tableMapCell
|
|
166
|
-
// Only measure if this cell spans 1 column (accurate width)
|
|
167
|
-
if (cell.getColSpan() === 1) {
|
|
168
|
-
const cellElem = editor.getElementByKey(cell.getKey())
|
|
169
|
-
if (cellElem) {
|
|
170
|
-
widths[c] = Math.round(cellElem.getBoundingClientRect().width)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// If all widths found, break early
|
|
175
|
-
if (widths.every(w => w !== undefined)) break
|
|
133
|
+
if (columns.length === colCount) {
|
|
134
|
+
columns.forEach((col) => {
|
|
135
|
+
const styleWidth = (col as HTMLElement).style.width
|
|
136
|
+
if (styleWidth && styleWidth.endsWith("px")) {
|
|
137
|
+
widths.push(parseFloat(styleWidth))
|
|
138
|
+
} else {
|
|
139
|
+
const rect = col.getBoundingClientRect()
|
|
140
|
+
widths.push(rect.width || 0)
|
|
176
141
|
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
177
144
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Final fallback: distribute evenly for any remaining undefined
|
|
194
|
-
if (widths.some(w => w === undefined)) {
|
|
195
|
-
const tableWidth = tableElement.getBoundingClientRect().width
|
|
196
|
-
const defaultWidth = tableWidth / colCount
|
|
197
|
-
for (let i = 0; i < colCount; i++) {
|
|
198
|
-
if (widths[i] === undefined) widths[i] = defaultWidth
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
currentWidths = widths as number[]
|
|
145
|
+
// If columns don't match or are missing, try to measure cells in the first row
|
|
146
|
+
if (widths.length !== colCount) {
|
|
147
|
+
const firstRow = tableElement.querySelector("tr")
|
|
148
|
+
if (firstRow) {
|
|
149
|
+
const cells = firstRow.querySelectorAll("th, td")
|
|
150
|
+
// This is only accurate if there are no colSpans in the first row
|
|
151
|
+
if (cells.length === colCount) {
|
|
152
|
+
cells.forEach((cell) => {
|
|
153
|
+
widths.push(cell.getBoundingClientRect().width)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
203
156
|
}
|
|
204
157
|
}
|
|
205
158
|
|
|
206
|
-
if
|
|
207
|
-
|
|
159
|
+
// Fallback to average width if still missing
|
|
160
|
+
if (widths.length !== colCount) {
|
|
161
|
+
const tableRect = tableElement.getBoundingClientRect()
|
|
162
|
+
const avgWidth = tableRect.width / (colCount || 1)
|
|
163
|
+
for (let i = 0; i < colCount; i++) {
|
|
164
|
+
widths.push(avgWidth)
|
|
165
|
+
}
|
|
208
166
|
}
|
|
167
|
+
|
|
168
|
+
currentWidths = widths
|
|
169
|
+
}
|
|
209
170
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
171
|
+
if (currentWidths[resizeColumnIndex] === undefined || currentWidths[resizeColumnIndex + 1] === undefined) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
213
174
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
175
|
+
nextDragState = {
|
|
176
|
+
columnIndex: resizeColumnIndex,
|
|
177
|
+
initialColWidths: currentWidths,
|
|
178
|
+
startX: event.clientX,
|
|
179
|
+
startWidth: currentWidths[resizeColumnIndex],
|
|
180
|
+
tableKey: tableNode.getKey(),
|
|
181
|
+
}
|
|
182
|
+
})
|
|
222
183
|
|
|
223
|
-
|
|
184
|
+
if (!nextDragState) return
|
|
224
185
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
186
|
+
dragRef.current = nextDragState
|
|
187
|
+
|
|
188
|
+
// Prevent text selection while dragging
|
|
189
|
+
const rootElement = editor.getRootElement()
|
|
190
|
+
if (rootElement) {
|
|
191
|
+
rootElement.style.userSelect = "none"
|
|
192
|
+
rootElement.style.cursor = "col-resize"
|
|
229
193
|
}
|
|
230
194
|
|
|
231
195
|
const onPointerMoveDocument = (event: PointerEvent) => {
|
|
232
|
-
|
|
233
|
-
|
|
196
|
+
const drag = dragRef.current
|
|
197
|
+
if (!drag) return
|
|
234
198
|
|
|
235
|
-
|
|
236
|
-
|
|
199
|
+
event.preventDefault()
|
|
200
|
+
const deltaX = event.clientX - drag.startX
|
|
201
|
+
const { tableKey, columnIndex, initialColWidths } = drag
|
|
237
202
|
|
|
238
203
|
editor.update(() => {
|
|
239
|
-
const tableNode = $getNodeByKey(
|
|
204
|
+
const tableNode = $getNodeByKey(tableKey)
|
|
240
205
|
if (!(tableNode instanceof TableNode)) return
|
|
241
206
|
|
|
242
|
-
const
|
|
207
|
+
const currentWidths = tableNode.getColWidths() || initialColWidths
|
|
208
|
+
const colCount = tableNode.getColumnCount()
|
|
243
209
|
const nextColumnIndex = columnIndex + 1
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (hasNeighbor) {
|
|
247
|
-
// Strict "Borrow" Logic:
|
|
248
|
-
// Adjust column width by borrowing from the neighbor.
|
|
249
|
-
// The total width of the two columns remains constant, ensuring the table width doesn't change.
|
|
250
|
-
const currentLeftWidth = initialColWidths[columnIndex]
|
|
251
|
-
const currentRightWidth = initialColWidths[nextColumnIndex]
|
|
210
|
+
|
|
211
|
+
if (nextColumnIndex >= colCount) return
|
|
252
212
|
|
|
253
|
-
|
|
213
|
+
const currentLeftWidth = initialColWidths[columnIndex]
|
|
214
|
+
const currentRightWidth = initialColWidths[nextColumnIndex]
|
|
254
215
|
|
|
255
|
-
|
|
256
|
-
const maxGrowLeft = currentRightWidth - MIN_COLUMN_WIDTH_PX
|
|
216
|
+
if (currentLeftWidth === undefined || currentRightWidth === undefined) return
|
|
257
217
|
|
|
258
|
-
|
|
259
|
-
|
|
218
|
+
const maxShrinkLeft = currentLeftWidth - MIN_COLUMN_WIDTH_PX
|
|
219
|
+
const maxGrowLeft = currentRightWidth - MIN_COLUMN_WIDTH_PX
|
|
260
220
|
|
|
261
|
-
|
|
262
|
-
|
|
221
|
+
// Clamp deltaX to ensure neither column shrinks below MIN_COLUMN_WIDTH_PX
|
|
222
|
+
const constrainedDelta = Math.min(Math.max(deltaX, -maxShrinkLeft), maxGrowLeft)
|
|
263
223
|
|
|
264
|
-
|
|
265
|
-
|
|
224
|
+
const newLeftWidth = currentLeftWidth + constrainedDelta
|
|
225
|
+
const newRightWidth = currentRightWidth - constrainedDelta
|
|
266
226
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
227
|
+
// Create a clean copy of widths, filling with measured values if needed
|
|
228
|
+
const nextColWidths = Array.from({ length: colCount }, (_, i) => {
|
|
229
|
+
if (i === columnIndex) return newLeftWidth
|
|
230
|
+
if (i === nextColumnIndex) return newRightWidth
|
|
231
|
+
return currentWidths[i] ?? initialColWidths[i] ?? 0
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// Use the correct method to update widths if setColWidths is not standard
|
|
235
|
+
if (typeof (tableNode as unknown as Record<string, unknown>).setColWidths === "function") {
|
|
236
|
+
;((tableNode as unknown as Record<string, unknown>).setColWidths as (widths: number[]) => void)(nextColWidths)
|
|
271
237
|
} else {
|
|
272
|
-
// Fallback for
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (colWidths[columnIndex] === newWidth) return
|
|
279
|
-
|
|
280
|
-
const nextColWidths = [...colWidths]
|
|
281
|
-
nextColWidths[columnIndex] = newWidth
|
|
282
|
-
tableNode.setColWidths(nextColWidths)
|
|
238
|
+
// Fallback for different Lexical versions if needed
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
+
;(tableNode as any).__colWidths = nextColWidths
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
+
;(tableNode as any).__widths = nextColWidths
|
|
243
|
+
tableNode.markDirty()
|
|
283
244
|
}
|
|
284
245
|
})
|
|
285
|
-
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const onPointerUpDocument = (event: PointerEvent) => {
|
|
249
|
+
const drag = dragRef.current
|
|
250
|
+
if (!drag) return
|
|
251
|
+
|
|
252
|
+
const target = event.target as HTMLElement
|
|
253
|
+
if (target && target.hasPointerCapture(event.pointerId)) {
|
|
254
|
+
target.releasePointerCapture(event.pointerId)
|
|
255
|
+
}
|
|
286
256
|
|
|
287
|
-
const onPointerUpDocument = () => {
|
|
288
|
-
if (!dragRef.current) return
|
|
289
257
|
dragRef.current = null
|
|
290
|
-
|
|
258
|
+
const rootElement = editor.getRootElement()
|
|
259
|
+
if (rootElement) {
|
|
260
|
+
rootElement.style.userSelect = ""
|
|
261
|
+
rootElement.style.cursor = ""
|
|
262
|
+
}
|
|
263
|
+
document.removeEventListener("pointermove", onPointerMoveDocument)
|
|
264
|
+
document.removeEventListener("pointerup", onPointerUpDocument)
|
|
291
265
|
}
|
|
292
266
|
|
|
293
|
-
rootElement.addEventListener("pointermove", onPointerMove)
|
|
294
|
-
rootElement.addEventListener("pointerdown", onPointerDown)
|
|
295
267
|
document.addEventListener("pointermove", onPointerMoveDocument)
|
|
296
268
|
document.addEventListener("pointerup", onPointerUpDocument)
|
|
269
|
+
}, [editor])
|
|
270
|
+
|
|
271
|
+
const updateResizerPosition = useCallback(() => {
|
|
272
|
+
const state = hoverState as HoverState | null
|
|
273
|
+
if (state === null || !resizerRef.current) return
|
|
274
|
+
|
|
275
|
+
const { cellKey, edge } = state
|
|
276
|
+
const cell = editor.getElementByKey(cellKey)
|
|
277
|
+
if (!cell) return
|
|
278
|
+
|
|
279
|
+
const cellRect = cell.getBoundingClientRect()
|
|
280
|
+
const anchorRect = anchorElem.getBoundingClientRect()
|
|
281
|
+
|
|
282
|
+
const resizerElem = resizerRef.current
|
|
283
|
+
|
|
284
|
+
// Find the table element to make the resizer span the entire table height
|
|
285
|
+
const tableElement = cell.closest("table")
|
|
286
|
+
let top = cellRect.top - anchorRect.top
|
|
287
|
+
let height = cellRect.height
|
|
288
|
+
|
|
289
|
+
if (tableElement) {
|
|
290
|
+
const tableRect = tableElement.getBoundingClientRect()
|
|
291
|
+
top = tableRect.top - anchorRect.top
|
|
292
|
+
height = tableRect.height
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Position at the edge, centered (width is 16px, so center is edge - 8)
|
|
296
|
+
const left = (edge === "left" ? cellRect.left : cellRect.right) - anchorRect.left - 8
|
|
297
|
+
|
|
298
|
+
resizerElem.style.transform = `translate(${left}px, ${top}px)`
|
|
299
|
+
resizerElem.style.height = `${height}px`
|
|
300
|
+
resizerElem.style.opacity = "1"
|
|
301
|
+
}, [editor, hoverState, anchorElem])
|
|
302
|
+
|
|
303
|
+
useLayoutEffect(() => {
|
|
304
|
+
updateResizerPosition()
|
|
305
|
+
|
|
306
|
+
// Update on window resize and scroll
|
|
307
|
+
window.addEventListener("resize", updateResizerPosition)
|
|
308
|
+
window.addEventListener("scroll", updateResizerPosition, true)
|
|
309
|
+
|
|
310
|
+
const updateListener = editor.registerUpdateListener(() => {
|
|
311
|
+
editor.read(() => {
|
|
312
|
+
const state = hoverState as HoverState | null
|
|
313
|
+
if (state !== null) {
|
|
314
|
+
const cell = editor.getElementByKey(state.cellKey)
|
|
315
|
+
if (!cell) {
|
|
316
|
+
setHoverState(null)
|
|
317
|
+
} else {
|
|
318
|
+
updateResizerPosition()
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
})
|
|
297
323
|
|
|
298
324
|
return () => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
document.removeEventListener("pointerup", onPointerUpDocument)
|
|
303
|
-
clearCursor()
|
|
304
|
-
dragRef.current = null
|
|
325
|
+
window.removeEventListener("resize", updateResizerPosition)
|
|
326
|
+
window.removeEventListener("scroll", updateResizerPosition, true)
|
|
327
|
+
updateListener()
|
|
305
328
|
}
|
|
306
|
-
}, [editor,
|
|
329
|
+
}, [editor, hoverState, updateResizerPosition])
|
|
307
330
|
|
|
308
|
-
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (!isEditable) return
|
|
333
|
+
|
|
334
|
+
const rootElement = editor.getRootElement()
|
|
335
|
+
if (!rootElement) return
|
|
336
|
+
|
|
337
|
+
const setCursor = (cursor: string) => {
|
|
338
|
+
if (rootElement) {
|
|
339
|
+
rootElement.style.cursor = cursor
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const clearCursor = () => {
|
|
344
|
+
setCursor("")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const onPointerMove = (event: PointerEvent) => {
|
|
348
|
+
if (dragRef.current) return
|
|
349
|
+
|
|
350
|
+
// Do not show resizer while dragging/selecting (any button is pressed)
|
|
351
|
+
if (event.buttons !== 0) return
|
|
352
|
+
|
|
353
|
+
// If we are over the resizer, don't clear hover state
|
|
354
|
+
if (resizerRef.current && (event.target === resizerRef.current || resizerRef.current.contains(event.target as Node))) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const cell = getCellTarget(event.target)
|
|
359
|
+
|
|
360
|
+
let nextHoverState: HoverState | null = null
|
|
361
|
+
|
|
362
|
+
if (cell) {
|
|
363
|
+
editor.read(() => {
|
|
364
|
+
const cellNode = $getNearestNodeFromDOMNode(cell)
|
|
365
|
+
if ($isTableCellNode(cellNode)) {
|
|
366
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(cellNode)
|
|
367
|
+
const cellKey = cellNode.getKey()
|
|
368
|
+
const tableKey = tableNode.getKey()
|
|
369
|
+
|
|
370
|
+
const edge = getResizeEdge(event.clientX, cell.getBoundingClientRect())
|
|
371
|
+
if (edge) {
|
|
372
|
+
const colCount = tableNode.getColumnCount()
|
|
373
|
+
const colSpan = cellNode.getColSpan()
|
|
374
|
+
const columnIndexFromMap = $getColumnIndexFromTableMap(tableNode, cellNode)
|
|
375
|
+
|
|
376
|
+
if (
|
|
377
|
+
columnIndexFromMap !== null &&
|
|
378
|
+
!((edge === "left" && columnIndexFromMap === 0) ||
|
|
379
|
+
(edge === "right" && columnIndexFromMap + colSpan === colCount))
|
|
380
|
+
) {
|
|
381
|
+
nextHoverState = { cellKey, tableKey, edge }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!nextHoverState) {
|
|
389
|
+
if (hoverState !== null) {
|
|
390
|
+
clearCursor()
|
|
391
|
+
setHoverState(null)
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
setCursor("col-resize")
|
|
395
|
+
// @ts-expect-error hoverState might be null initially
|
|
396
|
+
if (!hoverState || hoverState.cellKey !== nextHoverState.cellKey || hoverState.edge !== nextHoverState.edge) {
|
|
397
|
+
setHoverState(nextHoverState)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
anchorElem.addEventListener("pointermove", onPointerMove)
|
|
403
|
+
|
|
404
|
+
return () => {
|
|
405
|
+
anchorElem.removeEventListener("pointermove", onPointerMove)
|
|
406
|
+
clearCursor()
|
|
407
|
+
}
|
|
408
|
+
}, [editor, isEditable, hoverState, anchorElem])
|
|
409
|
+
|
|
410
|
+
const resizer = useMemo(() => {
|
|
411
|
+
const state = hoverState as HoverState | null
|
|
412
|
+
if (state === null) return null
|
|
413
|
+
|
|
414
|
+
const { cellKey, edge } = state
|
|
415
|
+
|
|
416
|
+
return createPortal(
|
|
417
|
+
<div
|
|
418
|
+
ref={resizerRef}
|
|
419
|
+
className="editor-table-cell-resizer"
|
|
420
|
+
style={{
|
|
421
|
+
position: "absolute",
|
|
422
|
+
top: 0,
|
|
423
|
+
left: 0,
|
|
424
|
+
width: "16px",
|
|
425
|
+
opacity: 1,
|
|
426
|
+
willChange: "transform",
|
|
427
|
+
userSelect: "none",
|
|
428
|
+
}}
|
|
429
|
+
onPointerDown={(event) => {
|
|
430
|
+
event.stopPropagation()
|
|
431
|
+
event.preventDefault()
|
|
432
|
+
const cell = editor.getElementByKey(cellKey)
|
|
433
|
+
if (cell instanceof HTMLTableCellElement) {
|
|
434
|
+
onPointerDownImpl(event.nativeEvent, cell, edge)
|
|
435
|
+
}
|
|
436
|
+
}}
|
|
437
|
+
>
|
|
438
|
+
<div
|
|
439
|
+
className="editor-table-cell-resize-ruler"
|
|
440
|
+
style={{ pointerEvents: "none" }}
|
|
441
|
+
/>
|
|
442
|
+
</div>,
|
|
443
|
+
anchorElem
|
|
444
|
+
)
|
|
445
|
+
}, [hoverState, anchorElem, editor, onPointerDownImpl])
|
|
446
|
+
|
|
447
|
+
return resizer
|
|
309
448
|
}
|
|
@@ -42,6 +42,10 @@ export const blockTypeToBlockName: Record<
|
|
|
42
42
|
label: "Alpha List (a, b, c)",
|
|
43
43
|
icon: <IconSize size="sm"><CaseSensitiveIcon /></IconSize>,
|
|
44
44
|
},
|
|
45
|
+
"number-multi-level": {
|
|
46
|
+
label: "Multi-level List (1.1 / 1.1.1)",
|
|
47
|
+
icon: <IconSize size="sm"><ListOrderedIcon /></IconSize>,
|
|
48
|
+
},
|
|
45
49
|
bullet: {
|
|
46
50
|
label: "Bulleted List",
|
|
47
51
|
icon: <IconSize size="sm"><ListIcon /></IconSize>,
|