@lexical/table 0.44.1-nightly.20260519.0 → 0.45.1-dev.0
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/{LexicalTable.dev.js → dist/LexicalTable.dev.js} +268 -33
- package/{LexicalTable.dev.mjs → dist/LexicalTable.dev.mjs} +264 -33
- package/{LexicalTable.mjs → dist/LexicalTable.mjs} +4 -0
- package/{LexicalTable.node.mjs → dist/LexicalTable.node.mjs} +4 -0
- package/dist/LexicalTable.prod.js +9 -0
- package/dist/LexicalTable.prod.mjs +9 -0
- package/dist/TableImportExtension.d.ts +41 -0
- package/{index.d.ts → dist/index.d.ts} +1 -0
- package/package.json +34 -18
- package/src/LexicalTableCellNode.ts +479 -0
- package/src/LexicalTableCommands.ts +27 -0
- package/src/LexicalTableExtension.ts +104 -0
- package/src/LexicalTableNode.ts +678 -0
- package/src/LexicalTableObserver.ts +575 -0
- package/src/LexicalTablePluginHelpers.ts +694 -0
- package/src/LexicalTableRowNode.ts +154 -0
- package/src/LexicalTableSelection.ts +460 -0
- package/src/LexicalTableSelectionHelpers.ts +2409 -0
- package/src/LexicalTableUtils.ts +1386 -0
- package/src/TableImportExtension.ts +324 -0
- package/src/constants.ts +13 -0
- package/src/index.ts +97 -0
- package/LexicalTable.prod.js +0 -9
- package/LexicalTable.prod.mjs +0 -9
- /package/{LexicalTable.js → dist/LexicalTable.js} +0 -0
- /package/{LexicalTable.js.flow → dist/LexicalTable.js.flow} +0 -0
- /package/{LexicalTableCellNode.d.ts → dist/LexicalTableCellNode.d.ts} +0 -0
- /package/{LexicalTableCommands.d.ts → dist/LexicalTableCommands.d.ts} +0 -0
- /package/{LexicalTableExtension.d.ts → dist/LexicalTableExtension.d.ts} +0 -0
- /package/{LexicalTableNode.d.ts → dist/LexicalTableNode.d.ts} +0 -0
- /package/{LexicalTableObserver.d.ts → dist/LexicalTableObserver.d.ts} +0 -0
- /package/{LexicalTablePluginHelpers.d.ts → dist/LexicalTablePluginHelpers.d.ts} +0 -0
- /package/{LexicalTableRowNode.d.ts → dist/LexicalTableRowNode.d.ts} +0 -0
- /package/{LexicalTableSelection.d.ts → dist/LexicalTableSelection.d.ts} +0 -0
- /package/{LexicalTableSelectionHelpers.d.ts → dist/LexicalTableSelectionHelpers.d.ts} +0 -0
- /package/{LexicalTableUtils.d.ts → dist/LexicalTableUtils.d.ts} +0 -0
- /package/{constants.d.ts → dist/constants.d.ts} +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
DOMConversionMap,
|
|
11
|
+
DOMConversionOutput,
|
|
12
|
+
DOMExportOutput,
|
|
13
|
+
EditorConfig,
|
|
14
|
+
LexicalEditor,
|
|
15
|
+
LexicalNode,
|
|
16
|
+
LexicalUpdateJSON,
|
|
17
|
+
NodeKey,
|
|
18
|
+
ParagraphNode,
|
|
19
|
+
SerializedElementNode,
|
|
20
|
+
Spread,
|
|
21
|
+
} from 'lexical';
|
|
22
|
+
|
|
23
|
+
import {addClassNamesToElement} from '@lexical/utils';
|
|
24
|
+
import {
|
|
25
|
+
$applyNodeReplacement,
|
|
26
|
+
$createParagraphNode,
|
|
27
|
+
$isInlineElementOrDecoratorNode,
|
|
28
|
+
$isLineBreakNode,
|
|
29
|
+
$isTextNode,
|
|
30
|
+
ElementNode,
|
|
31
|
+
isHTMLElement,
|
|
32
|
+
} from 'lexical';
|
|
33
|
+
|
|
34
|
+
import {COLUMN_WIDTH, PIXEL_VALUE_REG_EXP} from './constants';
|
|
35
|
+
|
|
36
|
+
export const TableCellHeaderStates = {
|
|
37
|
+
BOTH: 3,
|
|
38
|
+
COLUMN: 2,
|
|
39
|
+
NO_STATUS: 0,
|
|
40
|
+
ROW: 1,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type TableCellHeaderState =
|
|
44
|
+
(typeof TableCellHeaderStates)[keyof typeof TableCellHeaderStates];
|
|
45
|
+
|
|
46
|
+
export type SerializedTableCellNode = Spread<
|
|
47
|
+
{
|
|
48
|
+
colSpan?: number;
|
|
49
|
+
rowSpan?: number;
|
|
50
|
+
headerState: TableCellHeaderState;
|
|
51
|
+
width?: number;
|
|
52
|
+
backgroundColor?: null | string;
|
|
53
|
+
verticalAlign?: string;
|
|
54
|
+
},
|
|
55
|
+
SerializedElementNode
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
/** @noInheritDoc */
|
|
59
|
+
export class TableCellNode extends ElementNode {
|
|
60
|
+
/** @internal */
|
|
61
|
+
__colSpan: number;
|
|
62
|
+
/** @internal */
|
|
63
|
+
__rowSpan: number;
|
|
64
|
+
/** @internal */
|
|
65
|
+
__headerState: TableCellHeaderState;
|
|
66
|
+
/** @internal */
|
|
67
|
+
__width?: number | undefined;
|
|
68
|
+
/** @internal */
|
|
69
|
+
__backgroundColor: null | string;
|
|
70
|
+
/** @internal */
|
|
71
|
+
__verticalAlign?: undefined | string;
|
|
72
|
+
|
|
73
|
+
static getType(): string {
|
|
74
|
+
return 'tablecell';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static clone(node: TableCellNode): TableCellNode {
|
|
78
|
+
return new TableCellNode(
|
|
79
|
+
node.__headerState,
|
|
80
|
+
node.__colSpan,
|
|
81
|
+
node.__width,
|
|
82
|
+
node.__key,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
afterCloneFrom(node: this): void {
|
|
87
|
+
super.afterCloneFrom(node);
|
|
88
|
+
this.__rowSpan = node.__rowSpan;
|
|
89
|
+
this.__backgroundColor = node.__backgroundColor;
|
|
90
|
+
this.__verticalAlign = node.__verticalAlign;
|
|
91
|
+
this.__colSpan = node.__colSpan;
|
|
92
|
+
this.__headerState = node.__headerState;
|
|
93
|
+
this.__width = node.__width;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static importDOM(): DOMConversionMap | null {
|
|
97
|
+
return {
|
|
98
|
+
td: (node: Node) => ({
|
|
99
|
+
conversion: $convertTableCellNodeElement,
|
|
100
|
+
priority: 0,
|
|
101
|
+
}),
|
|
102
|
+
th: (node: Node) => ({
|
|
103
|
+
conversion: $convertTableCellNodeElement,
|
|
104
|
+
priority: 0,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static importJSON(serializedNode: SerializedTableCellNode): TableCellNode {
|
|
110
|
+
return $createTableCellNode().updateFromJSON(serializedNode);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
updateFromJSON(
|
|
114
|
+
serializedNode: LexicalUpdateJSON<SerializedTableCellNode>,
|
|
115
|
+
): this {
|
|
116
|
+
return super
|
|
117
|
+
.updateFromJSON(serializedNode)
|
|
118
|
+
.setHeaderStyles(serializedNode.headerState)
|
|
119
|
+
.setColSpan(serializedNode.colSpan || 1)
|
|
120
|
+
.setRowSpan(serializedNode.rowSpan || 1)
|
|
121
|
+
.setWidth(serializedNode.width || undefined)
|
|
122
|
+
.setBackgroundColor(serializedNode.backgroundColor || null)
|
|
123
|
+
.setVerticalAlign(serializedNode.verticalAlign || undefined);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
constructor(
|
|
127
|
+
headerState = TableCellHeaderStates.NO_STATUS,
|
|
128
|
+
colSpan = 1,
|
|
129
|
+
width?: number,
|
|
130
|
+
key?: NodeKey,
|
|
131
|
+
) {
|
|
132
|
+
super(key);
|
|
133
|
+
this.__colSpan = colSpan;
|
|
134
|
+
this.__rowSpan = 1;
|
|
135
|
+
this.__headerState = headerState;
|
|
136
|
+
this.__width = width;
|
|
137
|
+
this.__backgroundColor = null;
|
|
138
|
+
this.__verticalAlign = undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
createDOM(config: EditorConfig): HTMLTableCellElement {
|
|
142
|
+
const element = document.createElement(this.getTag());
|
|
143
|
+
|
|
144
|
+
if (this.__width) {
|
|
145
|
+
element.style.width = `${this.__width}px`;
|
|
146
|
+
}
|
|
147
|
+
if (this.__colSpan > 1) {
|
|
148
|
+
element.colSpan = this.__colSpan;
|
|
149
|
+
}
|
|
150
|
+
if (this.__rowSpan > 1) {
|
|
151
|
+
element.rowSpan = this.__rowSpan;
|
|
152
|
+
}
|
|
153
|
+
if (this.__backgroundColor !== null) {
|
|
154
|
+
element.style.backgroundColor = this.__backgroundColor;
|
|
155
|
+
}
|
|
156
|
+
if (isValidVerticalAlign(this.__verticalAlign)) {
|
|
157
|
+
element.style.verticalAlign = this.__verticalAlign;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
addClassNamesToElement(
|
|
161
|
+
element,
|
|
162
|
+
config.theme.tableCell,
|
|
163
|
+
this.hasHeader() && config.theme.tableCellHeader,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return element;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
|
170
|
+
const output = super.exportDOM(editor);
|
|
171
|
+
|
|
172
|
+
if (isHTMLElement(output.element)) {
|
|
173
|
+
const element = output.element as HTMLTableCellElement;
|
|
174
|
+
element.setAttribute(
|
|
175
|
+
'data-temporary-table-cell-lexical-key',
|
|
176
|
+
this.getKey(),
|
|
177
|
+
);
|
|
178
|
+
element.style.border = '1px solid black';
|
|
179
|
+
if (this.__colSpan > 1) {
|
|
180
|
+
element.colSpan = this.__colSpan;
|
|
181
|
+
}
|
|
182
|
+
if (this.__rowSpan > 1) {
|
|
183
|
+
element.rowSpan = this.__rowSpan;
|
|
184
|
+
}
|
|
185
|
+
element.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
|
|
186
|
+
|
|
187
|
+
element.style.verticalAlign = this.getVerticalAlign() || 'top';
|
|
188
|
+
element.style.textAlign = 'start';
|
|
189
|
+
if (this.__backgroundColor === null && this.hasHeader()) {
|
|
190
|
+
element.style.backgroundColor = '#f2f3f5';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
exportJSON(): SerializedTableCellNode {
|
|
198
|
+
return {
|
|
199
|
+
...super.exportJSON(),
|
|
200
|
+
...(isValidVerticalAlign(this.__verticalAlign) && {
|
|
201
|
+
verticalAlign: this.__verticalAlign,
|
|
202
|
+
}),
|
|
203
|
+
backgroundColor: this.getBackgroundColor(),
|
|
204
|
+
colSpan: this.__colSpan,
|
|
205
|
+
headerState: this.__headerState,
|
|
206
|
+
rowSpan: this.__rowSpan,
|
|
207
|
+
width: this.getWidth(),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getColSpan(): number {
|
|
212
|
+
return this.getLatest().__colSpan;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setColSpan(colSpan: number): this {
|
|
216
|
+
const self = this.getWritable();
|
|
217
|
+
self.__colSpan = colSpan;
|
|
218
|
+
return self;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getRowSpan(): number {
|
|
222
|
+
return this.getLatest().__rowSpan;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
setRowSpan(rowSpan: number): this {
|
|
226
|
+
const self = this.getWritable();
|
|
227
|
+
self.__rowSpan = rowSpan;
|
|
228
|
+
return self;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
getTag(): 'th' | 'td' {
|
|
232
|
+
return this.hasHeader() ? 'th' : 'td';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
setHeaderStyles(
|
|
236
|
+
headerState: TableCellHeaderState,
|
|
237
|
+
mask: TableCellHeaderState = TableCellHeaderStates.BOTH,
|
|
238
|
+
): this {
|
|
239
|
+
const self = this.getWritable();
|
|
240
|
+
self.__headerState = (headerState & mask) | (self.__headerState & ~mask);
|
|
241
|
+
return self;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getHeaderStyles(): TableCellHeaderState {
|
|
245
|
+
return this.getLatest().__headerState;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
setWidth(width: number | undefined): this {
|
|
249
|
+
const self = this.getWritable();
|
|
250
|
+
self.__width = width;
|
|
251
|
+
return self;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
getWidth(): number | undefined {
|
|
255
|
+
return this.getLatest().__width;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
getBackgroundColor(): null | string {
|
|
259
|
+
return this.getLatest().__backgroundColor;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
setBackgroundColor(newBackgroundColor: null | string): this {
|
|
263
|
+
const self = this.getWritable();
|
|
264
|
+
self.__backgroundColor = newBackgroundColor;
|
|
265
|
+
return self;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getVerticalAlign(): undefined | string {
|
|
269
|
+
return this.getLatest().__verticalAlign;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
setVerticalAlign(newVerticalAlign: null | undefined | string): this {
|
|
273
|
+
const self = this.getWritable();
|
|
274
|
+
self.__verticalAlign = newVerticalAlign || undefined;
|
|
275
|
+
return self;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
toggleHeaderStyle(headerStateToToggle: TableCellHeaderState): this {
|
|
279
|
+
const self = this.getWritable();
|
|
280
|
+
|
|
281
|
+
if ((self.__headerState & headerStateToToggle) === headerStateToToggle) {
|
|
282
|
+
self.__headerState -= headerStateToToggle;
|
|
283
|
+
} else {
|
|
284
|
+
self.__headerState += headerStateToToggle;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return self;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
hasHeaderState(headerState: TableCellHeaderState): boolean {
|
|
291
|
+
return (this.getHeaderStyles() & headerState) === headerState;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
hasHeader(): boolean {
|
|
295
|
+
return this.getLatest().__headerState !== TableCellHeaderStates.NO_STATUS;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
updateDOM(prevNode: this): boolean {
|
|
299
|
+
return (
|
|
300
|
+
prevNode.__headerState !== this.__headerState ||
|
|
301
|
+
prevNode.__width !== this.__width ||
|
|
302
|
+
prevNode.__colSpan !== this.__colSpan ||
|
|
303
|
+
prevNode.__rowSpan !== this.__rowSpan ||
|
|
304
|
+
prevNode.__backgroundColor !== this.__backgroundColor ||
|
|
305
|
+
prevNode.__verticalAlign !== this.__verticalAlign
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
isShadowRoot(): boolean {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
collapseAtStart(): true {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
canBeEmpty(): false {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
canIndent(): false {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isValidVerticalAlign(
|
|
327
|
+
verticalAlign?: null | string,
|
|
328
|
+
): verticalAlign is 'middle' | 'bottom' {
|
|
329
|
+
return verticalAlign === 'middle' || verticalAlign === 'bottom';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function $convertTableCellNodeElement(
|
|
333
|
+
domNode: Node,
|
|
334
|
+
): DOMConversionOutput {
|
|
335
|
+
const domNode_ = domNode as HTMLTableCellElement;
|
|
336
|
+
const nodeName = domNode.nodeName.toLowerCase();
|
|
337
|
+
|
|
338
|
+
let width: number | undefined = undefined;
|
|
339
|
+
|
|
340
|
+
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
|
|
341
|
+
width = parseFloat(domNode_.style.width);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Determine header state based on the 'scope' attribute
|
|
345
|
+
let headerState = TableCellHeaderStates.NO_STATUS;
|
|
346
|
+
|
|
347
|
+
if (nodeName === 'th') {
|
|
348
|
+
const scope = domNode_.getAttribute('scope');
|
|
349
|
+
if (scope === 'col') {
|
|
350
|
+
headerState = TableCellHeaderStates.COLUMN;
|
|
351
|
+
} else if (scope === 'row') {
|
|
352
|
+
headerState = TableCellHeaderStates.ROW;
|
|
353
|
+
} else {
|
|
354
|
+
const parentRow = domNode_.parentElement;
|
|
355
|
+
const isInHeaderRow =
|
|
356
|
+
isHTMLElement(parentRow) &&
|
|
357
|
+
parentRow.nodeName.toLowerCase() === 'tr' &&
|
|
358
|
+
isHTMLElement(parentRow.parentElement) &&
|
|
359
|
+
(parentRow.parentElement.nodeName.toLowerCase() === 'thead' ||
|
|
360
|
+
(parentRow as HTMLTableRowElement).rowIndex === 0);
|
|
361
|
+
const isFirstColumn = domNode_.cellIndex === 0;
|
|
362
|
+
|
|
363
|
+
if (isInHeaderRow) {
|
|
364
|
+
headerState |= TableCellHeaderStates.ROW;
|
|
365
|
+
}
|
|
366
|
+
if (isFirstColumn) {
|
|
367
|
+
headerState |= TableCellHeaderStates.COLUMN;
|
|
368
|
+
}
|
|
369
|
+
if (headerState === TableCellHeaderStates.NO_STATUS) {
|
|
370
|
+
headerState = TableCellHeaderStates.ROW;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const tableCellNode = $createTableCellNode(
|
|
376
|
+
headerState,
|
|
377
|
+
domNode_.colSpan,
|
|
378
|
+
width,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
tableCellNode.__rowSpan = domNode_.rowSpan;
|
|
382
|
+
const backgroundColor = domNode_.style.backgroundColor;
|
|
383
|
+
if (backgroundColor !== '') {
|
|
384
|
+
tableCellNode.__backgroundColor = backgroundColor;
|
|
385
|
+
}
|
|
386
|
+
const verticalAlign = domNode_.style.verticalAlign;
|
|
387
|
+
if (isValidVerticalAlign(verticalAlign)) {
|
|
388
|
+
tableCellNode.__verticalAlign = verticalAlign;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const style = domNode_.style;
|
|
392
|
+
const textDecoration = ((style && style.textDecoration) || '').split(' ');
|
|
393
|
+
const hasBoldFontWeight =
|
|
394
|
+
style.fontWeight === '700' || style.fontWeight === 'bold';
|
|
395
|
+
const hasLinethroughTextDecoration = textDecoration.includes('line-through');
|
|
396
|
+
const hasItalicFontStyle = style.fontStyle === 'italic';
|
|
397
|
+
const hasUnderlineTextDecoration = textDecoration.includes('underline');
|
|
398
|
+
const color = style.color;
|
|
399
|
+
return {
|
|
400
|
+
after: childLexicalNodes => {
|
|
401
|
+
const result: LexicalNode[] = [];
|
|
402
|
+
let paragraphNode: ParagraphNode | null = null;
|
|
403
|
+
|
|
404
|
+
const removeSingleLineBreakNode = () => {
|
|
405
|
+
if (paragraphNode) {
|
|
406
|
+
const firstChild = paragraphNode.getFirstChild();
|
|
407
|
+
if (
|
|
408
|
+
$isLineBreakNode(firstChild) &&
|
|
409
|
+
paragraphNode.getChildrenSize() === 1
|
|
410
|
+
) {
|
|
411
|
+
firstChild.remove();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
for (const child of childLexicalNodes) {
|
|
417
|
+
if (
|
|
418
|
+
$isInlineElementOrDecoratorNode(child) ||
|
|
419
|
+
$isTextNode(child) ||
|
|
420
|
+
$isLineBreakNode(child)
|
|
421
|
+
) {
|
|
422
|
+
if ($isTextNode(child)) {
|
|
423
|
+
if (hasBoldFontWeight) {
|
|
424
|
+
child.toggleFormat('bold');
|
|
425
|
+
}
|
|
426
|
+
if (hasLinethroughTextDecoration) {
|
|
427
|
+
child.toggleFormat('strikethrough');
|
|
428
|
+
}
|
|
429
|
+
if (hasItalicFontStyle) {
|
|
430
|
+
child.toggleFormat('italic');
|
|
431
|
+
}
|
|
432
|
+
if (hasUnderlineTextDecoration) {
|
|
433
|
+
child.toggleFormat('underline');
|
|
434
|
+
}
|
|
435
|
+
if (color) {
|
|
436
|
+
const existingStyle = child.getStyle();
|
|
437
|
+
if (!existingStyle.includes('color:')) {
|
|
438
|
+
child.setStyle(existingStyle + `color: ${color};`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (paragraphNode) {
|
|
444
|
+
paragraphNode.append(child);
|
|
445
|
+
} else {
|
|
446
|
+
paragraphNode = $createParagraphNode().append(child);
|
|
447
|
+
result.push(paragraphNode);
|
|
448
|
+
}
|
|
449
|
+
} else {
|
|
450
|
+
result.push(child);
|
|
451
|
+
removeSingleLineBreakNode();
|
|
452
|
+
paragraphNode = null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
removeSingleLineBreakNode();
|
|
457
|
+
|
|
458
|
+
if (result.length === 0) {
|
|
459
|
+
result.push($createParagraphNode());
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
},
|
|
463
|
+
node: tableCellNode,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function $createTableCellNode(
|
|
468
|
+
headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
|
|
469
|
+
colSpan = 1,
|
|
470
|
+
width?: number,
|
|
471
|
+
): TableCellNode {
|
|
472
|
+
return $applyNodeReplacement(new TableCellNode(headerState, colSpan, width));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function $isTableCellNode(
|
|
476
|
+
node: LexicalNode | null | undefined,
|
|
477
|
+
): node is TableCellNode {
|
|
478
|
+
return node instanceof TableCellNode;
|
|
479
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {LexicalCommand} from 'lexical';
|
|
10
|
+
|
|
11
|
+
import {createCommand} from 'lexical';
|
|
12
|
+
|
|
13
|
+
export type InsertTableCommandPayloadHeaders =
|
|
14
|
+
| Readonly<{
|
|
15
|
+
rows: boolean;
|
|
16
|
+
columns: boolean;
|
|
17
|
+
}>
|
|
18
|
+
| boolean;
|
|
19
|
+
|
|
20
|
+
export type InsertTableCommandPayload = Readonly<{
|
|
21
|
+
columns: string;
|
|
22
|
+
rows: string;
|
|
23
|
+
includeHeaders?: InsertTableCommandPayloadHeaders;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
export const INSERT_TABLE_COMMAND: LexicalCommand<InsertTableCommandPayload> =
|
|
27
|
+
createCommand('INSERT_TABLE_COMMAND');
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {effect, namedSignals} from '@lexical/extension';
|
|
10
|
+
import {mergeRegister} from '@lexical/utils';
|
|
11
|
+
import {$fullReconcile, defineExtension, safeCast} from 'lexical';
|
|
12
|
+
|
|
13
|
+
import {TableCellNode} from './LexicalTableCellNode';
|
|
14
|
+
import {
|
|
15
|
+
$isScrollableTablesActive,
|
|
16
|
+
setScrollableTablesActive,
|
|
17
|
+
TableNode,
|
|
18
|
+
} from './LexicalTableNode';
|
|
19
|
+
import {
|
|
20
|
+
registerTableCellUnmergeTransform,
|
|
21
|
+
registerTablePlugin,
|
|
22
|
+
registerTableSelectionObserver,
|
|
23
|
+
} from './LexicalTablePluginHelpers';
|
|
24
|
+
import {TableRowNode} from './LexicalTableRowNode';
|
|
25
|
+
|
|
26
|
+
export interface TableConfig {
|
|
27
|
+
/**
|
|
28
|
+
* When `false` (default `true`), merged cell support (colspan and rowspan) will be disabled and all
|
|
29
|
+
* tables will be forced into a regular grid with 1x1 table cells.
|
|
30
|
+
*/
|
|
31
|
+
hasCellMerge: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* When `false` (default `true`), the background color of TableCellNode will always be removed.
|
|
34
|
+
*/
|
|
35
|
+
hasCellBackgroundColor: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When `true` (default `true`), the tab key can be used to navigate table cells.
|
|
38
|
+
*/
|
|
39
|
+
hasTabHandler: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* When `true` (default `true`), tables will be wrapped in a `<div>` to enable horizontal scrolling
|
|
42
|
+
*/
|
|
43
|
+
hasHorizontalScroll: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* When `true` (default `false`), nested tables will be allowed.
|
|
46
|
+
*
|
|
47
|
+
* @experimental Nested tables are not officially supported.
|
|
48
|
+
*/
|
|
49
|
+
hasNestedTables: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Configures {@link TableNode}, {@link TableRowNode}, {@link TableCellNode} and
|
|
54
|
+
* registers table behaviors (see {@link TableConfig})
|
|
55
|
+
*/
|
|
56
|
+
export const TableExtension = defineExtension({
|
|
57
|
+
build(editor, config, state) {
|
|
58
|
+
return namedSignals(config);
|
|
59
|
+
},
|
|
60
|
+
config: safeCast<TableConfig>({
|
|
61
|
+
hasCellBackgroundColor: true,
|
|
62
|
+
hasCellMerge: true,
|
|
63
|
+
hasHorizontalScroll: true,
|
|
64
|
+
hasNestedTables: false,
|
|
65
|
+
hasTabHandler: true,
|
|
66
|
+
}),
|
|
67
|
+
name: '@lexical/table/Table',
|
|
68
|
+
nodes: () => [TableNode, TableRowNode, TableCellNode],
|
|
69
|
+
register(editor, config, state) {
|
|
70
|
+
const stores = state.getOutput();
|
|
71
|
+
return mergeRegister(
|
|
72
|
+
effect(() => {
|
|
73
|
+
const hasHorizontalScroll = stores.hasHorizontalScroll.value;
|
|
74
|
+
const hadHorizontalScroll = $isScrollableTablesActive(editor);
|
|
75
|
+
if (hadHorizontalScroll !== hasHorizontalScroll) {
|
|
76
|
+
setScrollableTablesActive(editor, hasHorizontalScroll);
|
|
77
|
+
// Re-render existing tables through the new scroll-wrapper config
|
|
78
|
+
// without cloning every TableNode the way marking them dirty would. A
|
|
79
|
+
// full reconcile marks no nodes dirty, so it's deferred (no
|
|
80
|
+
// synchronous render from this effect) and produces no history entry.
|
|
81
|
+
editor.update($fullReconcile);
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
registerTablePlugin(editor, stores),
|
|
85
|
+
effect(() =>
|
|
86
|
+
registerTableSelectionObserver(editor, stores.hasTabHandler.value),
|
|
87
|
+
),
|
|
88
|
+
effect(() =>
|
|
89
|
+
stores.hasCellMerge.value
|
|
90
|
+
? undefined
|
|
91
|
+
: registerTableCellUnmergeTransform(editor),
|
|
92
|
+
),
|
|
93
|
+
effect(() =>
|
|
94
|
+
stores.hasCellBackgroundColor.value
|
|
95
|
+
? undefined
|
|
96
|
+
: editor.registerNodeTransform(TableCellNode, node => {
|
|
97
|
+
if (node.getBackgroundColor() !== null) {
|
|
98
|
+
node.setBackgroundColor(null);
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
});
|