diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5f5074869c1..431c342226a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -9,9 +9,7 @@ on:
- 'packages/lexical-website/**'
pull_request:
types: [opened, synchronize, reopened]
- paths-ignore:
- - 'examples/**'
- - 'packages/lexical-website/**'
+ merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
diff --git a/package-lock.json b/package-lock.json
index e5387c0ffdc..4488fab50c5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30584,6 +30584,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
@@ -39153,7 +39154,7 @@
"katex": "^0.16.10",
"lexical": "0.21.0",
"lodash-es": "^4.17.21",
- "prettier": "^2.3.2",
+ "prettier": "^3.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
@@ -39173,6 +39174,21 @@
"vite-plugin-static-copy": "^2.1.0"
}
},
+ "packages/lexical-playground/node_modules/prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"packages/lexical-react": {
"name": "@lexical/react",
"version": "0.21.0",
@@ -56265,7 +56281,7 @@
"katex": "^0.16.10",
"lexical": "0.21.0",
"lodash-es": "^4.17.21",
- "prettier": "^2.3.2",
+ "prettier": "^3.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
@@ -56275,6 +56291,13 @@
"vite-plugin-static-copy": "^2.1.0",
"y-websocket": "^1.5.4",
"yjs": ">=13.5.42"
+ },
+ "dependencies": {
+ "prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="
+ }
}
},
"lib0": {
@@ -60208,7 +60231,8 @@
"prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
- "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g=="
+ "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "dev": true
},
"prettier-plugin-hermes-parser": {
"version": "0.20.1",
diff --git a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts
index 94582207ffc..a4c49cb6dfd 100644
--- a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts
+++ b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts
@@ -15,14 +15,19 @@ import {
} from '@lexical/link';
import {$createMarkNode, $isMarkNode} from '@lexical/mark';
import {
+ $createLineBreakNode,
$createParagraphNode,
$createTextNode,
$getRoot,
+ $getSelection,
+ $isLineBreakNode,
+ $isRangeSelection,
+ $isTextNode,
$selectAll,
ParagraphNode,
+ RangeSelection,
SerializedParagraphNode,
- TextNode,
-} from 'lexical/src';
+} from 'lexical';
import {initializeUnitTest} from 'lexical/src/__tests__/utils';
const editorConfig = Object.freeze({
@@ -47,20 +52,20 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('/');
+ const linkNode = $createLinkNode('/');
expect(linkNode.__type).toBe('link');
expect(linkNode.__url).toBe('/');
});
- expect(() => new LinkNode('')).toThrow();
+ expect(() => $createLinkNode('')).toThrow();
});
test('LineBreakNode.clone()', async () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('/');
+ const linkNode = $createLinkNode('/');
const linkNodeClone = LinkNode.clone(linkNode);
@@ -73,7 +78,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
expect(linkNode.getURL()).toBe('https://example.com/foo');
});
@@ -83,7 +88,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
expect(linkNode.getURL()).toBe('https://example.com/foo');
@@ -97,7 +102,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
target: '_blank',
});
@@ -109,7 +114,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
target: '_blank',
});
@@ -125,7 +130,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener noreferrer',
target: '_blank',
});
@@ -138,7 +143,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener',
target: '_blank',
});
@@ -155,7 +160,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
title: 'Hello world',
});
@@ -167,7 +172,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
title: 'Hello world',
});
@@ -183,7 +188,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
expect(linkNode.createDOM(editorConfig).outerHTML).toBe(
'',
@@ -201,7 +206,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener noreferrer',
target: '_blank',
title: 'Hello world',
@@ -226,7 +231,7 @@ describe('LexicalLinkNode tests', () => {
await editor.update(() => {
// eslint-disable-next-line no-script-url
- const linkNode = new LinkNode('javascript:alert(0)');
+ const linkNode = $createLinkNode('javascript:alert(0)');
expect(linkNode.createDOM(editorConfig).outerHTML).toBe(
'',
);
@@ -237,7 +242,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
const domElement = linkNode.createDOM(editorConfig);
@@ -245,7 +250,7 @@ describe('LexicalLinkNode tests', () => {
'',
);
- const newLinkNode = new LinkNode('https://example.com/bar');
+ const newLinkNode = $createLinkNode('https://example.com/bar');
const result = newLinkNode.updateDOM(
linkNode,
domElement,
@@ -263,7 +268,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener noreferrer',
target: '_blank',
title: 'Hello world',
@@ -275,7 +280,7 @@ describe('LexicalLinkNode tests', () => {
'',
);
- const newLinkNode = new LinkNode('https://example.com/bar', {
+ const newLinkNode = $createLinkNode('https://example.com/bar', {
rel: 'noopener',
target: '_self',
title: 'World hello',
@@ -297,7 +302,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener noreferrer',
target: '_blank',
title: 'Hello world',
@@ -309,7 +314,7 @@ describe('LexicalLinkNode tests', () => {
'',
);
- const newLinkNode = new LinkNode('https://example.com/bar');
+ const newLinkNode = $createLinkNode('https://example.com/bar');
const result = newLinkNode.updateDOM(
linkNode,
domElement,
@@ -327,7 +332,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
expect(linkNode.canInsertTextBefore()).toBe(false);
});
@@ -337,7 +342,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
expect(linkNode.canInsertTextAfter()).toBe(false);
});
@@ -347,7 +352,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo');
+ const linkNode = $createLinkNode('https://example.com/foo');
const createdLinkNode = $createLinkNode('https://example.com/foo');
@@ -362,7 +367,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('https://example.com/foo', {
+ const linkNode = $createLinkNode('https://example.com/foo', {
rel: 'noopener noreferrer',
target: '_blank',
title: 'Hello world',
@@ -388,7 +393,7 @@ describe('LexicalLinkNode tests', () => {
const {editor} = testEnv;
await editor.update(() => {
- const linkNode = new LinkNode('');
+ const linkNode = $createLinkNode('');
expect($isLinkNode(linkNode)).toBe(true);
});
@@ -397,14 +402,27 @@ describe('LexicalLinkNode tests', () => {
test('$toggleLink applies the title attribute when creating', async () => {
const {editor} = testEnv;
await editor.update(() => {
- const p = new ParagraphNode();
- p.append(new TextNode('Some text'));
+ const p = $createParagraphNode();
+ const textNode = $createTextNode('Some text');
+ p.append(textNode);
$getRoot().append(p);
- });
-
- await editor.update(() => {
$selectAll();
$toggleLink('https://lexical.dev/', {title: 'Lexical Website'});
+ const linkNode = p.getFirstChild() as LinkNode;
+ expect($isLinkNode(linkNode)).toBe(true);
+ expect(linkNode.getTitle()).toBe('Lexical Website');
+ const selection = $getSelection() as RangeSelection;
+ expect($isRangeSelection(selection)).toBe(true);
+ expect(selection.anchor).toMatchObject({
+ key: textNode.getKey(),
+ offset: 0,
+ type: 'text',
+ });
+ expect(selection.focus).toMatchObject({
+ key: textNode.getKey(),
+ offset: textNode.getTextContentSize(),
+ type: 'text',
+ });
});
const paragraph = editor!.getEditorState().toJSON().root
@@ -442,6 +460,7 @@ describe('LexicalLinkNode tests', () => {
expect(textNode.getTextContent()).toBe('some ');
// Check link node and its nested structure
+ expect($isLinkNode(linkNode)).toBe(true);
if ($isLinkNode(linkNode)) {
expect(linkNode.getURL()).toBe('https://example.com/foo');
expect(linkNode.getRel()).toBe('noreferrer');
@@ -470,6 +489,7 @@ describe('LexicalLinkNode tests', () => {
expect(textNode.getTextContent()).toBe('some ');
// Check mark node is preserved and moved up to paragraph level
+ expect($isMarkNode(markNode)).toBe(true);
if ($isMarkNode(markNode)) {
expect(markNode.getType()).toBe('mark');
expect(markNode.getIDs()).toEqual(['knetk']);
@@ -477,5 +497,64 @@ describe('LexicalLinkNode tests', () => {
}
});
});
+
+ test('$toggleLink adds link with embedded LineBreakNode', async () => {
+ const {editor} = testEnv;
+ await editor.update(() => {
+ const paragraph = $createParagraphNode();
+ const precedingText = $createTextNode('some '); // space after
+ const textNode = $createTextNode('text');
+ paragraph.append(precedingText, textNode, $createLineBreakNode());
+ $getRoot().clear().append(paragraph);
+ paragraph.select(1);
+ $toggleLink('https://example.com/foo', {
+ rel: 'noreferrer',
+ });
+ });
+
+ editor.read(() => {
+ const paragraph = $getRoot().getFirstChild() as ParagraphNode;
+ const [precedingText, linkNode] = paragraph.getChildren();
+
+ // Check first text node
+ expect(precedingText.getTextContent()).toBe('some ');
+
+ // Check link node and its nested structure
+ expect($isLinkNode(linkNode)).toBe(true);
+ if ($isLinkNode(linkNode)) {
+ expect(linkNode.getURL()).toBe('https://example.com/foo');
+ expect(linkNode.getRel()).toBe('noreferrer');
+ expect(
+ linkNode.getChildren().map((node) => node.getTextContent()),
+ ).toEqual(['text', '\n']);
+ expect($getSelection()).toMatchObject({
+ anchor: {
+ key: linkNode.getFirstChildOrThrow().getKey(),
+ offset: 0,
+ type: 'text',
+ },
+ focus: {key: linkNode.getKey(), offset: 2, type: 'element'},
+ });
+ }
+ });
+
+ await editor.update(() => {
+ $selectAll();
+ $toggleLink(null);
+ });
+
+ // Verify structure after link removal
+ editor.read(() => {
+ const paragraph = $getRoot().getFirstChild() as ParagraphNode;
+ const children = paragraph.getChildren();
+ expect(children.map((node) => node.getTextContent())).toEqual([
+ 'some text',
+ '\n',
+ ]);
+ const [textNode, lineBreakNode] = children;
+ expect($isTextNode(textNode)).toBe(true);
+ expect($isLineBreakNode(lineBreakNode)).toBe(true);
+ });
+ });
});
});
diff --git a/packages/lexical-link/src/index.ts b/packages/lexical-link/src/index.ts
index 1ddb4dcec47..b2cdaefc89c 100644
--- a/packages/lexical-link/src/index.ts
+++ b/packages/lexical-link/src/index.ts
@@ -14,6 +14,7 @@ import type {
LexicalCommand,
LexicalNode,
NodeKey,
+ Point,
RangeSelection,
SerializedElementNode,
} from 'lexical';
@@ -28,10 +29,13 @@ import {
$getSelection,
$isElementNode,
$isRangeSelection,
+ $normalizeSelection__EXPERIMENTAL,
+ $setSelection,
createCommand,
ElementNode,
Spread,
} from 'lexical';
+import invariant from 'shared/invariant';
export type LinkAttributes = {
rel?: null | string;
@@ -477,6 +481,66 @@ export const TOGGLE_LINK_COMMAND: LexicalCommand<
string | ({url: string} & LinkAttributes) | null
> = createCommand('TOGGLE_LINK_COMMAND');
+function $getPointNode(point: Point, offset: number): LexicalNode | null {
+ if (point.type === 'element') {
+ const node = point.getNode();
+ invariant(
+ $isElementNode(node),
+ '$getPointNode: element point is not an ElementNode',
+ );
+ const childNode = node.getChildren()[point.offset + offset];
+ return childNode || null;
+ }
+ return null;
+}
+
+/**
+ * Preserve the logical start/end of a RangeSelection in situations where
+ * the point is an element that may be reparented in the callback.
+ *
+ * @param $fn The function to run
+ * @returns The result of the callback
+ */
+function $withSelectedNodes($fn: () => T): T {
+ const initialSelection = $getSelection();
+ if (!$isRangeSelection(initialSelection)) {
+ return $fn();
+ }
+ const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection);
+ const isBackwards = normalized.isBackward();
+ const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0);
+ const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1);
+ const rval = $fn();
+ if (anchorNode || focusNode) {
+ const updatedSelection = $getSelection();
+ if ($isRangeSelection(updatedSelection)) {
+ const finalSelection = updatedSelection.clone();
+ if (anchorNode) {
+ const anchorParent = anchorNode.getParent();
+ if (anchorParent) {
+ finalSelection.anchor.set(
+ anchorParent.getKey(),
+ anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0),
+ 'element',
+ );
+ }
+ }
+ if (focusNode) {
+ const focusParent = focusNode.getParent();
+ if (focusParent) {
+ finalSelection.focus.set(
+ focusParent.getKey(),
+ focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1),
+ 'element',
+ );
+ }
+ }
+ $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection));
+ }
+ }
+ return rval;
+}
+
/**
* Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null,
* but saves any children and brings them up to the parent node.
@@ -515,93 +579,82 @@ export function $toggleLink(
parentLink.remove();
}
});
- } else {
- // Add or merge LinkNodes
- if (nodes.length === 1) {
- const firstNode = nodes[0];
- // if the first node is a LinkNode or if its
- // parent is a LinkNode, we update the URL, target and rel.
- const linkNode = $getAncestor(firstNode, $isLinkNode);
- if (linkNode !== null) {
- linkNode.setURL(url);
- if (target !== undefined) {
- linkNode.setTarget(target);
- }
- if (rel !== null) {
- linkNode.setRel(rel);
- }
- if (title !== undefined) {
- linkNode.setTitle(title);
- }
- return;
- }
+ return;
+ }
+ const updatedNodes = new Set();
+ const updateLinkNode = (linkNode: LinkNode) => {
+ if (updatedNodes.has(linkNode.getKey())) {
+ return;
+ }
+ updatedNodes.add(linkNode.getKey());
+ linkNode.setURL(url);
+ if (target !== undefined) {
+ linkNode.setTarget(target);
+ }
+ if (rel !== undefined) {
+ linkNode.setRel(rel);
}
+ if (title !== undefined) {
+ linkNode.setTitle(title);
+ }
+ };
+ // Add or merge LinkNodes
+ if (nodes.length === 1) {
+ const firstNode = nodes[0];
+ // if the first node is a LinkNode or if its
+ // parent is a LinkNode, we update the URL, target and rel.
+ const linkNode = $getAncestor(firstNode, $isLinkNode);
+ if (linkNode !== null) {
+ return updateLinkNode(linkNode);
+ }
+ }
- let prevParent: ElementNode | LinkNode | null = null;
+ $withSelectedNodes(() => {
let linkNode: LinkNode | null = null;
-
- nodes.forEach((node) => {
- const parent = node.getParent();
-
- if (
- parent === linkNode ||
- parent === null ||
- ($isElementNode(node) && !node.isInline())
- ) {
- return;
+ for (const node of nodes) {
+ if (!node.isAttached()) {
+ continue;
}
-
- if ($isLinkNode(parent)) {
- linkNode = parent;
- parent.setURL(url);
- if (target !== undefined) {
- parent.setTarget(target);
- }
- if (rel !== null) {
- linkNode.setRel(rel);
- }
- if (title !== undefined) {
- linkNode.setTitle(title);
- }
- return;
+ const parentLinkNode = $getAncestor(node, $isLinkNode);
+ if (parentLinkNode) {
+ updateLinkNode(parentLinkNode);
+ continue;
}
-
- if (!parent.is(prevParent)) {
- prevParent = parent;
- linkNode = $createLinkNode(url, {rel, target, title});
-
- if ($isLinkNode(parent)) {
- if (node.getPreviousSibling() === null) {
- parent.insertBefore(linkNode);
- } else {
- parent.insertAfter(linkNode);
- }
- } else {
- node.insertBefore(linkNode);
- }
- }
-
- if ($isLinkNode(node)) {
- if (node.is(linkNode)) {
- return;
+ if ($isElementNode(node)) {
+ if (!node.isInline()) {
+ // Ignore block nodes, if there are any children we will see them
+ // later and wrap in a new LinkNode
+ continue;
}
- if (linkNode !== null) {
- const children = node.getChildren();
-
- for (let i = 0; i < children.length; i++) {
- linkNode.append(children[i]);
+ if ($isLinkNode(node)) {
+ // If it's not an autolink node and we don't already have a LinkNode
+ // in this block then we can update it and re-use it
+ if (
+ !$isAutoLinkNode(node) &&
+ (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))
+ ) {
+ updateLinkNode(node);
+ linkNode = node;
+ continue;
+ }
+ // Unwrap LinkNode, we already have one or it's an AutoLinkNode
+ for (const child of node.getChildren()) {
+ node.insertBefore(child);
}
+ node.remove();
+ continue;
}
-
- node.remove();
- return;
}
-
- if (linkNode !== null) {
- linkNode.append(node);
+ const prevLinkNode = node.getPreviousSibling();
+ if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) {
+ prevLinkNode.append(node);
+ continue;
}
- });
- }
+ linkNode = $createLinkNode(url, {rel, target, title});
+ node.insertAfter(linkNode);
+ linkNode.append(node);
+ }
+ });
}
/** @deprecated renamed to {@link $toggleLink} by @lexical/eslint-plugin rules-of-lexical */
export const toggleLink = $toggleLink;
diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts
index f4fafcba71a..9dade3ae0a3 100644
--- a/packages/lexical-list/src/LexicalListItemNode.ts
+++ b/packages/lexical-list/src/LexicalListItemNode.ts
@@ -385,7 +385,7 @@ export class ListItemNode extends ElementNode {
}
canMergeWith(node: LexicalNode): boolean {
- return $isParagraphNode(node) || $isListItemNode(node);
+ return $isListItemNode(node) || $isParagraphNode(node);
}
extractWithChild(child: LexicalNode, selection: BaseSelection): boolean {
diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts
index 3dc4a22ea20..46694253ebb 100644
--- a/packages/lexical-list/src/formatList.ts
+++ b/packages/lexical-list/src/formatList.ts
@@ -12,7 +12,6 @@ import {
$getSelection,
$isElementNode,
$isLeafNode,
- $isParagraphNode,
$isRangeSelection,
$isRootOrShadowRoot,
ElementNode,
@@ -494,10 +493,12 @@ export function $handleListInsertParagraph(): boolean {
const grandparent = parent.getParent();
- let replacementNode;
+ let replacementNode: ParagraphNode | ListItemNode;
if ($isRootOrShadowRoot(grandparent)) {
replacementNode = $createParagraphNode();
+ replacementNode.setTextStyle(selection.style);
+ replacementNode.setTextFormat(selection.format);
topListNode.insertAfter(replacementNode);
} else if ($isListItemNode(grandparent)) {
replacementNode = $createListItemNode();
@@ -511,18 +512,14 @@ export function $handleListInsertParagraph(): boolean {
if (nextSiblings.length > 0) {
const newList = $createListNode(parent.getListType());
-
- if ($isParagraphNode(replacementNode)) {
- replacementNode.insertAfter(newList);
- } else {
+ if ($isListItemNode(replacementNode)) {
const newListItem = $createListItemNode();
newListItem.append(newList);
replacementNode.insertAfter(newListItem);
+ } else {
+ replacementNode.insertAfter(newList);
}
- nextSiblings.forEach((sibling) => {
- sibling.remove();
- newList.append(sibling);
- });
+ newList.append(...nextSiblings);
}
// Don't leave hanging nested empty lists
diff --git a/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs b/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs
index 34dd9972778..dab99d16c5d 100644
--- a/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs
@@ -366,7 +366,9 @@ test.describe('CodeBlock', () => {
;
-
+
@@ -393,7 +395,9 @@ test.describe('CodeBlock', () => {
;
-
+
@@ -453,7 +457,9 @@ test.describe('CodeBlock', () => {
{
-
+
@@ -501,8 +507,12 @@ test.describe('CodeBlock', () => {
data-gutter="123"
data-highlight-language="javascript"
data-language="javascript">
-
-
+
+
@@ -527,9 +537,15 @@ test.describe('CodeBlock', () => {
{
-
-
-
+
+
+
@@ -551,8 +567,12 @@ test.describe('CodeBlock', () => {
;
-
-
+
+
@@ -575,7 +595,9 @@ test.describe('CodeBlock', () => {
data-gutter="123"
data-highlight-language="javascript"
data-language="javascript">
-
+
@@ -600,8 +622,12 @@ test.describe('CodeBlock', () => {
{
-
-
+
+
@@ -623,7 +649,9 @@ test.describe('CodeBlock', () => {
;
-
+
@@ -913,10 +941,10 @@ test.describe('CodeBlock', () => {
data-gutter="12"
data-language="javascript"
data-highlight-language="javascript">
-
+
a b
-
+
c d
`,
diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs
index 8c1703c15c9..df81baee7a2 100644
--- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs
@@ -17,6 +17,7 @@ import {
redo,
selectAll,
selectCharacters,
+ toggleBold,
undo,
} from '../keyboardShortcuts/index.mjs';
import {
@@ -28,10 +29,10 @@ import {
focusEditor,
html,
initialize,
- IS_LINUX,
pasteFromClipboard,
repeat,
selectFromAlignDropdown,
+ selectFromColorPicker,
selectFromFormatDropdown,
test,
waitForSelector,
@@ -72,60 +73,62 @@ test.beforeEach(({isPlainText}) => {
test.describe.parallel('Nested List', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
- test(`Can create a list and partially copy some content out of it`, async ({
- page,
- isCollab,
- }) => {
- test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab');
- await focusEditor(page);
- await page.keyboard.type(
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam venenatis risus ac cursus efficitur. Cras efficitur magna odio, lacinia posuere mauris placerat in. Etiam eu congue nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla vulputate justo id eros convallis, vel pellentesque orci hendrerit. Pellentesque accumsan molestie eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, et bibendum quam feugiat eget. Praesent vestibulum libero sed enim ornare, in consequat dui posuere. Maecenas ornare vestibulum felis, non elementum urna imperdiet sit amet.',
- );
- await toggleBulletList(page);
- await moveToEditorBeginning(page);
- await moveRight(page, 6);
- await selectCharacters(page, 'right', 11);
+ test(
+ `Can create a list and partially copy some content out of it`,
+ {
+ tag: '@flaky',
+ },
+ async ({page, isCollab}) => {
+ await focusEditor(page);
+ await page.keyboard.type(
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam venenatis risus ac cursus efficitur. Cras efficitur magna odio, lacinia posuere mauris placerat in. Etiam eu congue nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla vulputate justo id eros convallis, vel pellentesque orci hendrerit. Pellentesque accumsan molestie eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, et bibendum quam feugiat eget. Praesent vestibulum libero sed enim ornare, in consequat dui posuere. Maecenas ornare vestibulum felis, non elementum urna imperdiet sit amet.',
+ );
+ await toggleBulletList(page);
+ await moveToEditorBeginning(page);
+ await moveRight(page, 6);
+ await selectCharacters(page, 'right', 11);
- await withExclusiveClipboardAccess(async () => {
- const clipboard = await copyToClipboard(page);
+ await withExclusiveClipboardAccess(async () => {
+ const clipboard = await copyToClipboard(page);
- await moveToEditorEnd(page);
- await page.keyboard.press('Enter');
- await page.keyboard.press('Enter');
+ await moveToEditorEnd(page);
+ await page.keyboard.press('Enter');
+ await page.keyboard.press('Enter');
- await pasteFromClipboard(page, clipboard);
- });
+ await pasteFromClipboard(page, clipboard);
+ });
- await assertHTML(
- page,
- html`
-
- -
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam
- venenatis risus ac cursus efficitur. Cras efficitur magna odio,
- lacinia posuere mauris placerat in. Etiam eu congue nisl.
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
- posuere cubilia curae; Nulla vulputate justo id eros convallis,
- vel pellentesque orci hendrerit. Pellentesque accumsan molestie
- eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor,
- et bibendum quam feugiat eget. Praesent vestibulum libero sed enim
- ornare, in consequat dui posuere. Maecenas ornare vestibulum
- felis, non elementum urna imperdiet sit amet.
-
-
-
-
- ipsum dolor
-
- `,
- );
- });
+ await assertHTML(
+ page,
+ html`
+
+ -
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam
+ venenatis risus ac cursus efficitur. Cras efficitur magna odio,
+ lacinia posuere mauris placerat in. Etiam eu congue nisl.
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
+ posuere cubilia curae; Nulla vulputate justo id eros convallis,
+ vel pellentesque orci hendrerit. Pellentesque accumsan molestie
+ eros, vitae tempor nisl semper sit amet. Sed vulputate leo
+ dolor, et bibendum quam feugiat eget. Praesent vestibulum libero
+ sed enim ornare, in consequat dui posuere. Maecenas ornare
+ vestibulum felis, non elementum urna imperdiet sit amet.
+
+
+
+
+ ipsum dolor
+
+ `,
+ );
+ },
+ );
test('Should outdent if indented when the backspace key is pressed', async ({
page,
@@ -158,6 +161,24 @@ test.describe.parallel('Nested List', () => {
);
});
+ test('Should retain selection style when exiting list', async ({page}) => {
+ await focusEditor(page);
+ await toggleBulletList(page);
+
+ await selectFromColorPicker(page);
+ await toggleBold(page);
+ await page.keyboard.type('Hello');
+ //Double-enter to exit list
+ await page.keyboard.press('Enter');
+ await page.keyboard.press('Enter');
+ await page.keyboard.type('World');
+
+ await assertHTML(
+ page,
+ 'World
',
+ );
+ });
+
test(`Can indent/outdent mutliple list nodes in a list with multiple levels of indentation`, async ({
page,
}) => {
diff --git a/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs
index 118e39536d4..b9198e193de 100644
--- a/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs
@@ -81,7 +81,9 @@ test.describe('Tab', () => {
dir="ltr"
style="padding-inline-start: calc(40px)">
すし
-
+
すし
`,
@@ -106,7 +108,9 @@ test.describe('Tab', () => {
data-gutter="1"
data-highlight-language="javascript"
data-language="javascript">
-
-
|
-
|
+
+
+ |
+
+
+ |
{
`,
);
});
+
+ test('Can delete table row when previous cell is a merged cell', async ({
+ page,
+ isCollab,
+ isPlainText,
+ }) => {
+ await initialize({isCollab, page});
+ test.skip(isPlainText);
+
+ await focusEditor(page);
+
+ await insertTable(page, 5, 5);
+
+ await selectCellsFromTableCords(
+ page,
+ {x: 1, y: 1},
+ {x: 1, y: 3},
+ false,
+ false,
+ );
+ await mergeTableCells(page);
+ await selectCellsFromTableCords(
+ page,
+ {x: 1, y: 2},
+ {x: 2, y: 4},
+ false,
+ false,
+ );
+ await mergeTableCells(page);
+ await assertHTML(
+ page,
+ html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+
+ await selectCellsFromTableCords(
+ page,
+ {x: 0, y: 2},
+ {x: 0, y: 2},
+ true,
+ true,
+ );
+
+ await deleteTableRows(page);
+
+ await assertHTML(
+ page,
+ html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+ });
+
+ test('Can delete table row when siblings are merged cell', async ({
+ page,
+ isCollab,
+ isPlainText,
+ }) => {
+ await initialize({isCollab, page});
+ test.skip(isPlainText);
+
+ await focusEditor(page);
+
+ await insertTable(page, 5, 5);
+
+ await selectCellsFromTableCords(
+ page,
+ {x: 0, y: 0},
+ {x: 0, y: 3},
+ true,
+ true,
+ );
+ await mergeTableCells(page);
+ await selectCellsFromTableCords(
+ page,
+ {x: 2, y: 0},
+ {x: 1, y: 2},
+ true,
+ false,
+ );
+ await mergeTableCells(page);
+ await assertHTML(
+ page,
+ html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+
+ await selectCellsFromTableCords(
+ page,
+ {x: 0, y: 2},
+ {x: 0, y: 2},
+ false,
+ false,
+ );
+
+ await deleteTableRows(page);
+
+ await assertHTML(
+ page,
+ html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+ });
});
diff --git a/packages/lexical-playground/__tests__/utils/index.mjs b/packages/lexical-playground/__tests__/utils/index.mjs
index ed81a581866..a9e13d587ec 100644
--- a/packages/lexical-playground/__tests__/utils/index.mjs
+++ b/packages/lexical-playground/__tests__/utils/index.mjs
@@ -225,7 +225,7 @@ async function assertHTMLOnPageOrFrame(
frameName,
actualHtmlModificationsCallback = (actualHtml) => actualHtml,
) {
- const expected = prettifyHTML(expectedHtml.replace(/\n/gm, ''), {
+ const expected = await prettifyHTML(expectedHtml.replace(/\n/gm, ''), {
ignoreClasses,
ignoreInlineStyles,
});
@@ -236,7 +236,7 @@ async function assertHTMLOnPageOrFrame(
.first()
.innerHTML(),
);
- let actual = prettifyHTML(actualHtml.replace(/\n/gm, ''), {
+ let actual = await prettifyHTML(actualHtml.replace(/\n/gm, ''), {
ignoreClasses,
ignoreInlineStyles,
});
@@ -780,7 +780,10 @@ export async function dragImage(
);
}
-export function prettifyHTML(string, {ignoreClasses, ignoreInlineStyles} = {}) {
+export async function prettifyHTML(
+ string,
+ {ignoreClasses, ignoreInlineStyles} = {},
+) {
let output = string;
if (ignoreClasses) {
@@ -793,15 +796,14 @@ export function prettifyHTML(string, {ignoreClasses, ignoreInlineStyles} = {}) {
output = output.replace(/\s__playwright_target__="[^"]+"/, '');
- return prettier
- .format(output, {
- attributeGroups: ['$DEFAULT', '^data-'],
- attributeSort: 'ASC',
- bracketSameLine: true,
- htmlWhitespaceSensitivity: 'ignore',
- parser: 'html',
- })
- .trim();
+ return await prettier.format(output, {
+ attributeGroups: ['$DEFAULT', '^data-'],
+ attributeSort: 'asc',
+ bracketSameLine: true,
+ htmlWhitespaceSensitivity: 'ignore',
+ parser: 'html',
+ plugins: ['prettier-plugin-organize-attributes'],
+ });
}
// This function does not suppose to do anything, it's only used as a trigger
diff --git a/packages/lexical-playground/package.json b/packages/lexical-playground/package.json
index cd7ced6303f..5a2751577cf 100644
--- a/packages/lexical-playground/package.json
+++ b/packages/lexical-playground/package.json
@@ -29,7 +29,7 @@
"katex": "^0.16.10",
"lexical": "0.21.0",
"lodash-es": "^4.17.21",
- "prettier": "^2.3.2",
+ "prettier": "^3.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
diff --git a/packages/lexical-playground/src/index.css b/packages/lexical-playground/src/index.css
index b57f34b85d5..d62dd871509 100644
--- a/packages/lexical-playground/src/index.css
+++ b/packages/lexical-playground/src/index.css
@@ -1778,28 +1778,6 @@ button.item.dropdown-item-active i {
z-index: 3;
}
-.PlaygroundEditorTheme__blockCursor {
- display: block;
- pointer-events: none;
- position: absolute;
-}
-
-.PlaygroundEditorTheme__blockCursor:after {
- content: '';
- display: block;
- position: absolute;
- top: -2px;
- width: 20px;
- border-top: 1px solid black;
- animation: CursorBlink 1.1s steps(2, start) infinite;
-}
-
-@keyframes CursorBlink {
- to {
- visibility: hidden;
- }
-}
-
.dialog-dropdown {
background-color: #eee !important;
margin-bottom: 10px;
diff --git a/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx b/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx
index 203e63702b9..c1e47f23ec4 100644
--- a/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx
+++ b/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx
@@ -10,7 +10,6 @@ import './index.css';
import {$isCodeNode} from '@lexical/code';
import {$getNearestNodeFromDOMNode, LexicalEditor} from 'lexical';
import {Options} from 'prettier';
-import * as React from 'react';
import {useState} from 'react';
interface Props {
@@ -20,17 +19,27 @@ interface Props {
}
const PRETTIER_PARSER_MODULES = {
- css: () => import('prettier/parser-postcss'),
- html: () => import('prettier/parser-html'),
- js: () => import('prettier/parser-babel'),
- markdown: () => import('prettier/parser-markdown'),
+ css: [() => import('prettier/parser-postcss')],
+ html: [() => import('prettier/parser-html')],
+ js: [
+ () => import('prettier/parser-babel'),
+ () => import('prettier/plugins/estree'),
+ ],
+ markdown: [() => import('prettier/parser-markdown')],
+ typescript: [
+ () => import('prettier/parser-typescript'),
+ () => import('prettier/plugins/estree'),
+ ],
} as const;
type LanguagesType = keyof typeof PRETTIER_PARSER_MODULES;
async function loadPrettierParserByLang(lang: string) {
- const dynamicImport = PRETTIER_PARSER_MODULES[lang as LanguagesType];
- return await dynamicImport();
+ const dynamicImports = PRETTIER_PARSER_MODULES[lang as LanguagesType];
+ const modules = await Promise.all(
+ dynamicImports.map((dynamicImport) => dynamicImport()),
+ );
+ return modules;
}
async function loadPrettierFormat() {
@@ -39,18 +48,11 @@ async function loadPrettierFormat() {
}
const PRETTIER_OPTIONS_BY_LANG: Record = {
- css: {
- parser: 'css',
- },
- html: {
- parser: 'html',
- },
- js: {
- parser: 'babel',
- },
- markdown: {
- parser: 'markdown',
- },
+ css: {parser: 'css'},
+ html: {parser: 'html'},
+ js: {parser: 'babel'},
+ markdown: {parser: 'markdown'},
+ typescript: {parser: 'typescript'},
};
const LANG_CAN_BE_PRETTIER = Object.keys(PRETTIER_OPTIONS_BY_LANG);
@@ -76,36 +78,37 @@ export function PrettierButton({lang, editor, getCodeDOMNode}: Props) {
async function handleClick(): Promise {
const codeDOMNode = getCodeDOMNode();
+ if (!codeDOMNode) {
+ return;
+ }
+
+ let content = '';
+ editor.update(() => {
+ const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);
+ if ($isCodeNode(codeNode)) {
+ content = codeNode.getTextContent();
+ }
+ });
+ if (content === '') {
+ return;
+ }
try {
const format = await loadPrettierFormat();
const options = getPrettierOptions(lang);
- options.plugins = [await loadPrettierParserByLang(lang)];
-
- if (!codeDOMNode) {
- return;
- }
+ const prettierParsers = await loadPrettierParserByLang(lang);
+ options.plugins = prettierParsers.map(
+ (parser) => parser.default || parser,
+ );
+ const formattedCode = await format(content, options);
editor.update(() => {
const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);
-
if ($isCodeNode(codeNode)) {
- const content = codeNode.getTextContent();
-
- let parsed = '';
-
- try {
- parsed = format(content, options);
- } catch (error: unknown) {
- setError(error);
- }
-
- if (parsed !== '') {
- const selection = codeNode.select(0);
- selection.insertText(parsed);
- setSyntaxError('');
- setTipsVisible(false);
- }
+ const selection = codeNode.select(0);
+ selection.insertText(formattedCode);
+ setSyntaxError('');
+ setTipsVisible(false);
}
});
} catch (error: unknown) {
diff --git a/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx b/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx
index cbdeee1fe23..dd226849e62 100644
--- a/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx
+++ b/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx
@@ -97,6 +97,25 @@ export function LayoutPlugin(): null {
return false;
};
+ const $fillLayoutItemIfEmpty = (node: LayoutItemNode) => {
+ if (node.isEmpty()) {
+ node.append($createParagraphNode());
+ }
+ };
+
+ const $removeIsolatedLayoutItem = (node: LayoutItemNode): boolean => {
+ const parent = node.getParent();
+ if (!$isLayoutContainerNode(parent)) {
+ const children = node.getChildren();
+ for (const child of children) {
+ node.insertBefore(child);
+ }
+ node.remove();
+ return true;
+ }
+ return false;
+ };
+
return mergeRegister(
// When layout is the last child pressing down/right arrow will insert paragraph
// below it to allow adding more content. It's similar what $insertBlockNode
@@ -186,17 +205,17 @@ export function LayoutPlugin(): null {
},
COMMAND_PRIORITY_EDITOR,
),
- // Structure enforcing transformers for each node type. In case nesting structure is not
- // "Container > Item" it'll unwrap nodes and convert it back
- // to regular content.
+
editor.registerNodeTransform(LayoutItemNode, (node) => {
- const parent = node.getParent();
- if (!$isLayoutContainerNode(parent)) {
- const children = node.getChildren();
- for (const child of children) {
- node.insertBefore(child);
- }
- node.remove();
+ // Structure enforcing transformers for each node type. In case nesting structure is not
+ // "Container > Item" it'll unwrap nodes and convert it back
+ // to regular content.
+ const isRemoved = $removeIsolatedLayoutItem(node);
+
+ if (!isRemoved) {
+ // Layout item should always have a child. this function will listen
+ // for any empty layout item and fill it with a paragraph node
+ $fillLayoutItemIfEmpty(node);
}
}),
editor.registerNodeTransform(LayoutContainerNode, (node) => {
diff --git a/packages/lexical-playground/src/plugins/TablePlugin.tsx b/packages/lexical-playground/src/plugins/TablePlugin.tsx
index e53ef957546..795c784335e 100644
--- a/packages/lexical-playground/src/plugins/TablePlugin.tsx
+++ b/packages/lexical-playground/src/plugins/TablePlugin.tsx
@@ -8,22 +8,13 @@
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {
- $createTableNodeWithDimensions,
INSERT_TABLE_COMMAND,
+ TableCellNode,
TableNode,
+ TableRowNode,
} from '@lexical/table';
-import {
- $insertNodes,
- COMMAND_PRIORITY_EDITOR,
- createCommand,
- EditorThemeClasses,
- Klass,
- LexicalCommand,
- LexicalEditor,
- LexicalNode,
-} from 'lexical';
+import {EditorThemeClasses, Klass, LexicalEditor, LexicalNode} from 'lexical';
import {createContext, useContext, useEffect, useMemo, useState} from 'react';
-import * as React from 'react';
import invariant from 'shared/invariant';
import Button from '../ui/Button';
@@ -53,9 +44,6 @@ export type CellEditorConfig = Readonly<{
theme?: EditorThemeClasses;
}>;
-export const INSERT_NEW_TABLE_COMMAND: LexicalCommand =
- createCommand('INSERT_NEW_TABLE_COMMAND');
-
export const CellContext = createContext({
cellEditorConfig: null,
cellEditorPlugins: null,
@@ -155,28 +143,16 @@ export function TablePlugin({
}): JSX.Element | null {
const [editor] = useLexicalComposerContext();
const cellContext = useContext(CellContext);
-
useEffect(() => {
- if (!editor.hasNodes([TableNode])) {
- invariant(false, 'TablePlugin: TableNode is not registered on editor');
+ if (!editor.hasNodes([TableNode, TableRowNode, TableCellNode])) {
+ invariant(
+ false,
+ 'TablePlugin: TableNode, TableRowNode, or TableCellNode is not registered on editor',
+ );
}
-
+ }, [editor]);
+ useEffect(() => {
cellContext.set(cellEditorConfig, children);
-
- return editor.registerCommand(
- INSERT_NEW_TABLE_COMMAND,
- ({columns, rows, includeHeaders}) => {
- const tableNode = $createTableNodeWithDimensions(
- Number(rows),
- Number(columns),
- includeHeaders,
- );
- $insertNodes([tableNode]);
- return true;
- },
- COMMAND_PRIORITY_EDITOR,
- );
- }, [cellContext, cellEditorConfig, children, editor]);
-
+ }, [cellContext, cellEditorConfig, children]);
return null;
}
diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
index 60fc2a96675..d5e48ae6d25 100644
--- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
+++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
@@ -57,12 +57,56 @@
.PlaygroundEditorTheme__textUnderline {
text-decoration: underline;
}
+
.PlaygroundEditorTheme__textStrikethrough {
text-decoration: line-through;
}
+
.PlaygroundEditorTheme__textUnderlineStrikethrough {
text-decoration: underline line-through;
}
+
+.PlaygroundEditorTheme__tabNode {
+ position: relative;
+ text-decoration: none;
+}
+
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderline::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0.15em;
+ border-bottom: 0.1em solid currentColor;
+}
+
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textStrikethrough::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0.69em;
+ border-top: 0.1em solid currentColor;
+}
+
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::before,
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+}
+
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::before {
+ top: 0.69em;
+ border-top: 0.1em solid currentColor;
+}
+
+.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::after {
+ bottom: 0.05em;
+ border-bottom: 0.1em solid currentColor;
+}
+
.PlaygroundEditorTheme__textSubscript {
font-size: 0.8em;
vertical-align: sub !important;
@@ -98,6 +142,25 @@
text-decoration: underline;
cursor: pointer;
}
+.PlaygroundEditorTheme__blockCursor {
+ display: block;
+ pointer-events: none;
+ position: absolute;
+}
+.PlaygroundEditorTheme__blockCursor:after {
+ content: '';
+ display: block;
+ position: absolute;
+ top: -2px;
+ width: 20px;
+ border-top: 1px solid black;
+ animation: CursorBlink 1.1s steps(2, start) infinite;
+}
+@keyframes CursorBlink {
+ to {
+ visibility: hidden;
+ }
+}
.PlaygroundEditorTheme__code {
background-color: rgb(240, 242, 245);
font-family: Menlo, Consolas, Monaco, monospace;
diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts
index 9dfd9e95c29..e7c6a4aab7e 100644
--- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts
+++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts
@@ -91,6 +91,7 @@ const theme: EditorThemeClasses = {
quote: 'PlaygroundEditorTheme__quote',
rtl: 'PlaygroundEditorTheme__rtl',
specialText: 'PlaygroundEditorTheme__specialText',
+ tab: 'PlaygroundEditorTheme__tabNode',
table: 'PlaygroundEditorTheme__table',
tableCell: 'PlaygroundEditorTheme__tableCell',
tableCellActionButton: 'PlaygroundEditorTheme__tableCellActionButton',
diff --git a/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx b/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx
index d37bd69f3c9..ef7303ee281 100644
--- a/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx
+++ b/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx
@@ -191,9 +191,15 @@ function setMenuPosition(
const floatingElemRect = floatingElem.getBoundingClientRect();
const anchorElementRect = anchorElem.getBoundingClientRect();
+ // top left
+ let targetCalculateHeight: number = parseInt(targetStyle.lineHeight, 10);
+ if (isNaN(targetCalculateHeight)) {
+ // middle
+ targetCalculateHeight = targetRect.bottom - targetRect.top;
+ }
const top =
targetRect.top +
- (parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
+ (targetCalculateHeight - floatingElemRect.height) / 2 -
anchorElementRect.top;
const left = SPACE;
diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts
index a5c43d17c65..7d91b0b576c 100644
--- a/packages/lexical-react/src/LexicalTablePlugin.ts
+++ b/packages/lexical-react/src/LexicalTablePlugin.ts
@@ -6,43 +6,15 @@
*
*/
-import type {
- HTMLTableElementWithWithTableSelectionState,
- InsertTableCommandPayload,
- TableObserver,
-} from '@lexical/table';
-import type {NodeKey} from 'lexical';
-
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {
- $computeTableMap,
- $computeTableMapSkipCellCheck,
- $createTableCellNode,
- $createTableNodeWithDimensions,
- $getNodeTriplet,
- $getTableAndElementByKey,
- $isTableCellNode,
- $isTableRowNode,
- applyTableHandlers,
- getTableElement,
- INSERT_TABLE_COMMAND,
+ registerTableCellUnmergeTransform,
+ registerTablePlugin,
+ registerTableSelectionObserver,
setScrollableTablesActive,
TableCellNode,
- TableNode,
- TableRowNode,
} from '@lexical/table';
-import {
- $insertFirst,
- $insertNodeToNearestRoot,
- mergeRegister,
-} from '@lexical/utils';
-import {
- $createParagraphNode,
- $isTextNode,
- COMMAND_PRIORITY_EDITOR,
-} from 'lexical';
import {useEffect} from 'react';
-import invariant from 'shared/invariant';
export interface TablePluginProps {
/**
@@ -82,181 +54,18 @@ export function TablePlugin({
setScrollableTablesActive(editor, hasHorizontalScroll);
}, [editor, hasHorizontalScroll]);
- useEffect(() => {
- if (!editor.hasNodes([TableNode, TableCellNode, TableRowNode])) {
- invariant(
- false,
- 'TablePlugin: TableNode, TableCellNode or TableRowNode not registered on editor',
- );
- }
-
- return mergeRegister(
- editor.registerCommand(
- INSERT_TABLE_COMMAND,
- ({columns, rows, includeHeaders}) => {
- const tableNode = $createTableNodeWithDimensions(
- Number(rows),
- Number(columns),
- includeHeaders,
- );
- $insertNodeToNearestRoot(tableNode);
-
- const firstDescendant = tableNode.getFirstDescendant();
- if ($isTextNode(firstDescendant)) {
- firstDescendant.select();
- }
-
- return true;
- },
- COMMAND_PRIORITY_EDITOR,
- ),
- editor.registerNodeTransform(TableNode, (node) => {
- const [gridMap] = $computeTableMapSkipCellCheck(node, null, null);
- const maxRowLength = gridMap.reduce((curLength, row) => {
- return Math.max(curLength, row.length);
- }, 0);
- const rowNodes = node.getChildren();
- for (let i = 0; i < gridMap.length; ++i) {
- const rowNode = rowNodes[i];
- if (!rowNode) {
- continue;
- }
- const rowLength = gridMap[i].reduce(
- (acc, cell) => (cell ? 1 + acc : acc),
- 0,
- );
- if (rowLength === maxRowLength) {
- continue;
- }
- for (let j = rowLength; j < maxRowLength; ++j) {
- // TODO: inherit header state from another header or body
- const newCell = $createTableCellNode(0);
- newCell.append($createParagraphNode());
- (rowNode as TableRowNode).append(newCell);
- }
- }
- }),
- );
- }, [editor]);
+ useEffect(() => registerTablePlugin(editor), [editor]);
- useEffect(() => {
- const tableSelections = new Map<
- NodeKey,
- [TableObserver, HTMLTableElementWithWithTableSelectionState]
- >();
-
- const initializeTableNode = (
- tableNode: TableNode,
- nodeKey: NodeKey,
- dom: HTMLElement,
- ) => {
- const tableElement = getTableElement(tableNode, dom);
- const tableSelection = applyTableHandlers(
- tableNode,
- tableElement,
- editor,
- hasTabHandler,
- );
- tableSelections.set(nodeKey, [tableSelection, tableElement]);
- };
-
- const unregisterMutationListener = editor.registerMutationListener(
- TableNode,
- (nodeMutations) => {
- editor.getEditorState().read(
- () => {
- for (const [nodeKey, mutation] of nodeMutations) {
- const tableSelection = tableSelections.get(nodeKey);
- if (mutation === 'created' || mutation === 'updated') {
- const {tableNode, tableElement} =
- $getTableAndElementByKey(nodeKey);
- if (tableSelection === undefined) {
- initializeTableNode(tableNode, nodeKey, tableElement);
- } else if (tableElement !== tableSelection[1]) {
- // The update created a new DOM node, destroy the existing TableObserver
- tableSelection[0].removeListeners();
- tableSelections.delete(nodeKey);
- initializeTableNode(tableNode, nodeKey, tableElement);
- }
- } else if (mutation === 'destroyed') {
- if (tableSelection !== undefined) {
- tableSelection[0].removeListeners();
- tableSelections.delete(nodeKey);
- }
- }
- }
- },
- {editor},
- );
- },
- {skipInitialization: false},
- );
-
- return () => {
- unregisterMutationListener();
- // Hook might be called multiple times so cleaning up tables listeners as well,
- // as it'll be reinitialized during recurring call
- for (const [, [tableSelection]] of tableSelections) {
- tableSelection.removeListeners();
- }
- };
- }, [editor, hasTabHandler]);
+ useEffect(
+ () => registerTableSelectionObserver(editor, hasTabHandler),
+ [editor, hasTabHandler],
+ );
// Unmerge cells when the feature isn't enabled
useEffect(() => {
- if (hasCellMerge) {
- return;
+ if (!hasCellMerge) {
+ return registerTableCellUnmergeTransform(editor);
}
- return editor.registerNodeTransform(TableCellNode, (node) => {
- if (node.getColSpan() > 1 || node.getRowSpan() > 1) {
- // When we have rowSpan we have to map the entire Table to understand where the new Cells
- // fit best; let's analyze all Cells at once to save us from further transform iterations
- const [, , gridNode] = $getNodeTriplet(node);
- const [gridMap] = $computeTableMap(gridNode, node, node);
- // TODO this function expects Tables to be normalized. Look into this once it exists
- const rowsCount = gridMap.length;
- const columnsCount = gridMap[0].length;
- let row = gridNode.getFirstChild();
- invariant(
- $isTableRowNode(row),
- 'Expected TableNode first child to be a RowNode',
- );
- const unmerged = [];
- for (let i = 0; i < rowsCount; i++) {
- if (i !== 0) {
- row = row.getNextSibling();
- invariant(
- $isTableRowNode(row),
- 'Expected TableNode first child to be a RowNode',
- );
- }
- let lastRowCell: null | TableCellNode = null;
- for (let j = 0; j < columnsCount; j++) {
- const cellMap = gridMap[i][j];
- const cell = cellMap.cell;
- if (cellMap.startRow === i && cellMap.startColumn === j) {
- lastRowCell = cell;
- unmerged.push(cell);
- } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) {
- invariant(
- $isTableCellNode(cell),
- 'Expected TableNode cell to be a TableCellNode',
- );
- const newCell = $createTableCellNode(cell.__headerState);
- if (lastRowCell !== null) {
- lastRowCell.insertAfter(newCell);
- } else {
- $insertFirst(row, newCell);
- }
- }
- }
- }
- for (const cell of unmerged) {
- cell.setColSpan(1);
- cell.setRowSpan(1);
- }
- }
- });
}, [editor, hasCellMerge]);
// Remove cell background color when feature is disabled
diff --git a/packages/lexical-react/src/LexicalTreeView.tsx b/packages/lexical-react/src/LexicalTreeView.tsx
index db536de589b..1b7057b27ef 100644
--- a/packages/lexical-react/src/LexicalTreeView.tsx
+++ b/packages/lexical-react/src/LexicalTreeView.tsx
@@ -18,13 +18,31 @@ import {mergeRegister} from '@lexical/utils';
import * as React from 'react';
import {useEffect, useState} from 'react';
+/**
+ * TreeView is a React component that provides a visual representation of
+ * the Lexical editor's state and enables debugging features like time travel
+ * and custom tree node rendering.
+ *
+ * @param {Object} props - The properties passed to the TreeView component.
+ * @param {LexicalEditor} props.editor - The Lexical editor instance to be visualized and debugged.
+ * @param {string} [props.treeTypeButtonClassName] - Custom class name for the tree type toggle button.
+ * @param {string} [props.timeTravelButtonClassName] - Custom class name for the time travel toggle button.
+ * @param {string} [props.timeTravelPanelButtonClassName] - Custom class name for buttons inside the time travel panel.
+ * @param {string} [props.timeTravelPanelClassName] - Custom class name for the overall time travel panel container.
+ * @param {string} [props.timeTravelPanelSliderClassName] - Custom class name for the time travel slider in the panel.
+ * @param {string} [props.viewClassName] - Custom class name for the tree view container.
+ * @param {CustomPrintNodeFn} [props.customPrintNode] - A function for customizing the display of nodes in the tree.
+ *
+ * @returns {JSX.Element} - A React element that visualizes the editor's state and supports debugging interactions.
+ */
+
export function TreeView({
treeTypeButtonClassName,
timeTravelButtonClassName,
timeTravelPanelSliderClassName,
timeTravelPanelButtonClassName,
- viewClassName,
timeTravelPanelClassName,
+ viewClassName,
editor,
customPrintNode,
}: {
@@ -38,6 +56,7 @@ export function TreeView({
customPrintNode?: CustomPrintNodeFn;
}): JSX.Element {
const treeElementRef = React.createRef();
+
const [editorCurrentState, setEditorCurrentState] = useState(
editor.getEditorState(),
);
@@ -45,6 +64,7 @@ export function TreeView({
const commandsLog = useLexicalCommandsLog(editor);
useEffect(() => {
+ // Registers listeners to update the tree view when the editor state changes
return mergeRegister(
editor.registerUpdateListener(({editorState}) => {
setEditorCurrentState(editorState);
@@ -59,16 +79,23 @@ export function TreeView({
const element = treeElementRef.current;
if (element !== null) {
- // @ts-ignore Internal field
+ // Assigns the editor instance to the tree view DOM element for internal tracking
+ // @ts-ignore Internal field used by Lexical
element.__lexicalEditor = editor;
return () => {
- // @ts-ignore Internal field
+ // Cleans up the reference when the component is unmounted
+ // @ts-ignore Internal field used by Lexical
element.__lexicalEditor = null;
};
}
}, [editor, treeElementRef]);
+ /**
+ * Handles toggling the readonly state of the editor.
+ *
+ * @param {boolean} isReadonly - Whether the editor should be set to readonly.
+ */
const handleEditorReadOnly = (isReadonly: boolean) => {
const rootElement = editor.getRootElement();
if (rootElement == null) {
@@ -90,6 +117,7 @@ export function TreeView({
editorState={editorCurrentState}
setEditorState={(state) => editor.setEditorState(state)}
generateContent={async function (exportDOM) {
+ // Generates the content for the tree view, allowing customization with exportDOM and customPrintNode
return generateContent(editor, commandsLog, exportDOM, customPrintNode);
}}
ref={treeElementRef}
diff --git a/packages/lexical-react/src/shared/useCharacterLimit.ts b/packages/lexical-react/src/shared/useCharacterLimit.ts
index 8e1e4f813c0..75d8e58040b 100644
--- a/packages/lexical-react/src/shared/useCharacterLimit.ts
+++ b/packages/lexical-react/src/shared/useCharacterLimit.ts
@@ -14,7 +14,7 @@ import {
OverflowNode,
} from '@lexical/overflow';
import {$rootTextContent} from '@lexical/text';
-import {$dfs, mergeRegister} from '@lexical/utils';
+import {$dfs, $unwrapNode, mergeRegister} from '@lexical/utils';
import {
$getSelection,
$isElementNode,
@@ -254,18 +254,6 @@ function $wrapNode(node: LexicalNode): OverflowNode {
return overflowNode;
}
-function $unwrapNode(node: OverflowNode): LexicalNode | null {
- const children = node.getChildren();
- const childrenLength = children.length;
-
- for (let i = 0; i < childrenLength; i++) {
- node.insertBefore(children[i]);
- }
-
- node.remove();
- return childrenLength > 0 ? children[childrenLength - 1] : null;
-}
-
export function $mergePrevious(overflowNode: OverflowNode): void {
const previousNode = overflowNode.getPreviousSibling();
diff --git a/packages/lexical-selection/src/index.ts b/packages/lexical-selection/src/index.ts
index d901ab4d4d9..8d9d47ce635 100644
--- a/packages/lexical-selection/src/index.ts
+++ b/packages/lexical-selection/src/index.ts
@@ -18,7 +18,6 @@ import {
$isParentElementRTL,
$moveCaretSelection,
$moveCharacter,
- $selectAll,
$setBlocksType,
$shouldOverrideDefaultCharacterSelection,
$wrapNodes,
@@ -32,7 +31,9 @@ import {
export {
/** @deprecated moved to the lexical package */ $cloneWithProperties,
+ /** @deprecated moved to the lexical package */ $selectAll,
} from 'lexical';
+
export {
$addNodeStyle,
$isAtNodeEnd,
@@ -48,7 +49,6 @@ export {
$isParentElementRTL,
$moveCaretSelection,
$moveCharacter,
- $selectAll,
$setBlocksType,
$shouldOverrideDefaultCharacterSelection,
$wrapNodes,
diff --git a/packages/lexical-selection/src/range-selection.ts b/packages/lexical-selection/src/range-selection.ts
index e92a81b6188..15b4e66c5c7 100644
--- a/packages/lexical-selection/src/range-selection.ts
+++ b/packages/lexical-selection/src/range-selection.ts
@@ -452,41 +452,6 @@ export function $moveCharacter(
);
}
-/**
- * Expands the current Selection to cover all of the content in the editor.
- * @param selection - The current selection.
- */
-export function $selectAll(selection: RangeSelection): void {
- const anchor = selection.anchor;
- const focus = selection.focus;
- const anchorNode = anchor.getNode();
- const topParent = anchorNode.getTopLevelElementOrThrow();
- const root = topParent.getParentOrThrow();
- let firstNode = root.getFirstDescendant();
- let lastNode = root.getLastDescendant();
- let firstType: 'element' | 'text' = 'element';
- let lastType: 'element' | 'text' = 'element';
- let lastOffset = 0;
-
- if ($isTextNode(firstNode)) {
- firstType = 'text';
- } else if (!$isElementNode(firstNode) && firstNode !== null) {
- firstNode = firstNode.getParentOrThrow();
- }
-
- if ($isTextNode(lastNode)) {
- lastType = 'text';
- lastOffset = lastNode.getTextContentSize();
- } else if (!$isElementNode(lastNode) && lastNode !== null) {
- lastNode = lastNode.getParentOrThrow();
- }
-
- if (firstNode && lastNode) {
- anchor.set(firstNode.getKey(), 0, firstType);
- focus.set(lastNode.getKey(), lastOffset, lastType);
- }
-}
-
/**
* Returns the current value of a CSS property for Nodes, if set. If not set, it returns the defaultValue.
* @param node - The node whose style value to get.
diff --git a/packages/lexical-table/flow/LexicalTable.js.flow b/packages/lexical-table/flow/LexicalTable.js.flow
index 2674a125f50..0d3af559ed3 100644
--- a/packages/lexical-table/flow/LexicalTable.js.flow
+++ b/packages/lexical-table/flow/LexicalTable.js.flow
@@ -75,7 +75,7 @@ declare export class TableCellNode extends ElementNode {
canBeEmpty(): false;
}
declare export function $createTableCellNode(
- headerState: TableCellHeaderState,
+ headerState?: TableCellHeaderState,
colSpan?: number,
width?: ?number,
): TableCellNode;
@@ -350,4 +350,14 @@ export type InsertTableCommandPayload = $ReadOnly<{
includeHeaders?: InsertTableCommandPayloadHeaders;
}>;
-declare export var INSERT_TABLE_COMMAND: LexicalCommand;
\ No newline at end of file
+declare export var INSERT_TABLE_COMMAND: LexicalCommand;
+
+/**
+ * LexicalTablePluginHelpers
+ */
+
+declare export function registerTableCellUnmergeTransform(editor: LexicalEditor): () => void;
+
+declare export function registerTablePlugin(editor: LexicalEditor): () => void;
+
+declare export function registerTableSelectionObserver(editor: LexicalEditor, hasTabHandler?: boolean): () => void;
diff --git a/packages/lexical-table/src/LexicalTableCellNode.ts b/packages/lexical-table/src/LexicalTableCellNode.ts
index 2b00b8dfb6e..795779c4990 100644
--- a/packages/lexical-table/src/LexicalTableCellNode.ts
+++ b/packages/lexical-table/src/LexicalTableCellNode.ts
@@ -366,7 +366,7 @@ export function $convertTableCellNodeElement(
}
export function $createTableCellNode(
- headerState: TableCellHeaderState,
+ headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
colSpan = 1,
width?: number,
): TableCellNode {
diff --git a/packages/lexical-table/src/LexicalTableNode.ts b/packages/lexical-table/src/LexicalTableNode.ts
index 4a4a2c970fa..636613346b3 100644
--- a/packages/lexical-table/src/LexicalTableNode.ts
+++ b/packages/lexical-table/src/LexicalTableNode.ts
@@ -6,9 +6,8 @@
*
*/
-import type {TableRowNode} from './LexicalTableRowNode';
-
import {
+ $descendantsMatching,
addClassNamesToElement,
isHTMLElement,
removeClassNamesFromElement,
@@ -36,6 +35,7 @@ import invariant from 'shared/invariant';
import {PIXEL_VALUE_REG_EXP} from './constants';
import {$isTableCellNode, type TableCellNode} from './LexicalTableCellNode';
import {TableDOMCell, TableDOMTable} from './LexicalTableObserver';
+import {$isTableRowNode, type TableRowNode} from './LexicalTableRowNode';
import {
$getNearestTableCellInTableFromDOMNode,
getTable,
@@ -498,7 +498,10 @@ export function $convertTableElement(
tableNode.setColWidths(columns);
}
}
- return {node: tableNode};
+ return {
+ after: (children) => $descendantsMatching(children, $isTableRowNode),
+ node: tableNode,
+ };
}
export function $createTableNode(): TableNode {
diff --git a/packages/lexical-table/src/LexicalTablePluginHelpers.ts b/packages/lexical-table/src/LexicalTablePluginHelpers.ts
new file mode 100644
index 00000000000..ae7ee4547e2
--- /dev/null
+++ b/packages/lexical-table/src/LexicalTablePluginHelpers.ts
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import {
+ $insertFirst,
+ $insertNodeToNearestRoot,
+ $unwrapAndFilterDescendants,
+ mergeRegister,
+} from '@lexical/utils';
+import {
+ $createParagraphNode,
+ $isTextNode,
+ COMMAND_PRIORITY_EDITOR,
+ LexicalEditor,
+ NodeKey,
+} from 'lexical';
+import invariant from 'shared/invariant';
+
+import {
+ $createTableCellNode,
+ $isTableCellNode,
+ TableCellNode,
+} from './LexicalTableCellNode';
+import {
+ INSERT_TABLE_COMMAND,
+ InsertTableCommandPayload,
+} from './LexicalTableCommands';
+import {$isTableNode, TableNode} from './LexicalTableNode';
+import {$getTableAndElementByKey, TableObserver} from './LexicalTableObserver';
+import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode';
+import {
+ applyTableHandlers,
+ getTableElement,
+ HTMLTableElementWithWithTableSelectionState,
+} from './LexicalTableSelectionHelpers';
+import {
+ $computeTableMap,
+ $computeTableMapSkipCellCheck,
+ $createTableNodeWithDimensions,
+ $getNodeTriplet,
+} from './LexicalTableUtils';
+
+function $insertTableCommandListener({
+ rows,
+ columns,
+ includeHeaders,
+}: InsertTableCommandPayload): boolean {
+ const tableNode = $createTableNodeWithDimensions(
+ Number(rows),
+ Number(columns),
+ includeHeaders,
+ );
+ $insertNodeToNearestRoot(tableNode);
+
+ const firstDescendant = tableNode.getFirstDescendant();
+ if ($isTextNode(firstDescendant)) {
+ firstDescendant.select();
+ }
+
+ return true;
+}
+
+function $tableCellTransform(node: TableCellNode) {
+ if (!$isTableRowNode(node.getParent())) {
+ // TableCellNode must be a child of TableRowNode.
+ node.remove();
+ } else if (node.isEmpty()) {
+ // TableCellNode should never be empty
+ node.append($createParagraphNode());
+ }
+}
+
+function $tableRowTransform(node: TableRowNode) {
+ if (!$isTableNode(node.getParent())) {
+ // TableRowNode must be a child of TableNode.
+ // TODO: Future support of tbody/thead/tfoot may change this
+ node.remove();
+ } else {
+ $unwrapAndFilterDescendants(node, $isTableCellNode);
+ }
+}
+
+function $tableTransform(node: TableNode) {
+ // TableRowNode is the only valid child for TableNode
+ // TODO: Future support of tbody/thead/tfoot/caption may change this
+ $unwrapAndFilterDescendants(node, $isTableRowNode);
+
+ const [gridMap] = $computeTableMapSkipCellCheck(node, null, null);
+ const maxRowLength = gridMap.reduce((curLength, row) => {
+ return Math.max(curLength, row.length);
+ }, 0);
+ const rowNodes = node.getChildren();
+ for (let i = 0; i < gridMap.length; ++i) {
+ const rowNode = rowNodes[i];
+ if (!rowNode) {
+ continue;
+ }
+ invariant(
+ $isTableRowNode(rowNode),
+ 'TablePlugin: Expecting all children of TableNode to be TableRowNode, found %s (type %s)',
+ rowNode.constructor.name,
+ rowNode.getType(),
+ );
+ const rowLength = gridMap[i].reduce(
+ (acc, cell) => (cell ? 1 + acc : acc),
+ 0,
+ );
+ if (rowLength === maxRowLength) {
+ continue;
+ }
+ for (let j = rowLength; j < maxRowLength; ++j) {
+ // TODO: inherit header state from another header or body
+ const newCell = $createTableCellNode();
+ newCell.append($createParagraphNode());
+ rowNode.append(newCell);
+ }
+ }
+}
+
+/**
+ * Register a transform to ensure that all TableCellNode have a colSpan and rowSpan of 1.
+ * This should only be registered when you do not want to support merged cells.
+ *
+ * @param editor The editor
+ * @returns An unregister callback
+ */
+export function registerTableCellUnmergeTransform(
+ editor: LexicalEditor,
+): () => void {
+ return editor.registerNodeTransform(TableCellNode, (node) => {
+ if (node.getColSpan() > 1 || node.getRowSpan() > 1) {
+ // When we have rowSpan we have to map the entire Table to understand where the new Cells
+ // fit best; let's analyze all Cells at once to save us from further transform iterations
+ const [, , gridNode] = $getNodeTriplet(node);
+ const [gridMap] = $computeTableMap(gridNode, node, node);
+ // TODO this function expects Tables to be normalized. Look into this once it exists
+ const rowsCount = gridMap.length;
+ const columnsCount = gridMap[0].length;
+ let row = gridNode.getFirstChild();
+ invariant(
+ $isTableRowNode(row),
+ 'Expected TableNode first child to be a RowNode',
+ );
+ const unmerged = [];
+ for (let i = 0; i < rowsCount; i++) {
+ if (i !== 0) {
+ row = row.getNextSibling();
+ invariant(
+ $isTableRowNode(row),
+ 'Expected TableNode first child to be a RowNode',
+ );
+ }
+ let lastRowCell: null | TableCellNode = null;
+ for (let j = 0; j < columnsCount; j++) {
+ const cellMap = gridMap[i][j];
+ const cell = cellMap.cell;
+ if (cellMap.startRow === i && cellMap.startColumn === j) {
+ lastRowCell = cell;
+ unmerged.push(cell);
+ } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) {
+ invariant(
+ $isTableCellNode(cell),
+ 'Expected TableNode cell to be a TableCellNode',
+ );
+ const newCell = $createTableCellNode(cell.__headerState);
+ if (lastRowCell !== null) {
+ lastRowCell.insertAfter(newCell);
+ } else {
+ $insertFirst(row, newCell);
+ }
+ }
+ }
+ }
+ for (const cell of unmerged) {
+ cell.setColSpan(1);
+ cell.setRowSpan(1);
+ }
+ }
+ });
+}
+
+export function registerTableSelectionObserver(
+ editor: LexicalEditor,
+ hasTabHandler: boolean = true,
+): () => void {
+ const tableSelections = new Map<
+ NodeKey,
+ [TableObserver, HTMLTableElementWithWithTableSelectionState]
+ >();
+
+ const initializeTableNode = (
+ tableNode: TableNode,
+ nodeKey: NodeKey,
+ dom: HTMLElement,
+ ) => {
+ const tableElement = getTableElement(tableNode, dom);
+ const tableSelection = applyTableHandlers(
+ tableNode,
+ tableElement,
+ editor,
+ hasTabHandler,
+ );
+ tableSelections.set(nodeKey, [tableSelection, tableElement]);
+ };
+
+ const unregisterMutationListener = editor.registerMutationListener(
+ TableNode,
+ (nodeMutations) => {
+ editor.getEditorState().read(
+ () => {
+ for (const [nodeKey, mutation] of nodeMutations) {
+ const tableSelection = tableSelections.get(nodeKey);
+ if (mutation === 'created' || mutation === 'updated') {
+ const {tableNode, tableElement} =
+ $getTableAndElementByKey(nodeKey);
+ if (tableSelection === undefined) {
+ initializeTableNode(tableNode, nodeKey, tableElement);
+ } else if (tableElement !== tableSelection[1]) {
+ // The update created a new DOM node, destroy the existing TableObserver
+ tableSelection[0].removeListeners();
+ tableSelections.delete(nodeKey);
+ initializeTableNode(tableNode, nodeKey, tableElement);
+ }
+ } else if (mutation === 'destroyed') {
+ if (tableSelection !== undefined) {
+ tableSelection[0].removeListeners();
+ tableSelections.delete(nodeKey);
+ }
+ }
+ }
+ },
+ {editor},
+ );
+ },
+ {skipInitialization: false},
+ );
+
+ return () => {
+ unregisterMutationListener();
+ // Hook might be called multiple times so cleaning up tables listeners as well,
+ // as it'll be reinitialized during recurring call
+ for (const [, [tableSelection]] of tableSelections) {
+ tableSelection.removeListeners();
+ }
+ };
+}
+
+/**
+ * Register the INSERT_TABLE_COMMAND listener and the table integrity transforms. The
+ * table selection observer should be registered separately after this with
+ * {@link registerTableSelectionObserver}.
+ *
+ * @param editor The editor
+ * @returns An unregister callback
+ */
+export function registerTablePlugin(editor: LexicalEditor): () => void {
+ if (!editor.hasNodes([TableNode])) {
+ invariant(false, 'TablePlugin: TableNode is not registered on editor');
+ }
+ return mergeRegister(
+ editor.registerCommand(
+ INSERT_TABLE_COMMAND,
+ $insertTableCommandListener,
+ COMMAND_PRIORITY_EDITOR,
+ ),
+ editor.registerNodeTransform(TableNode, $tableTransform),
+ editor.registerNodeTransform(TableRowNode, $tableRowTransform),
+ editor.registerNodeTransform(TableCellNode, $tableCellTransform),
+ );
+}
diff --git a/packages/lexical-table/src/LexicalTableRowNode.ts b/packages/lexical-table/src/LexicalTableRowNode.ts
index fd8bcb8fa0a..9a7d5c99c88 100644
--- a/packages/lexical-table/src/LexicalTableRowNode.ts
+++ b/packages/lexical-table/src/LexicalTableRowNode.ts
@@ -8,7 +8,7 @@
import type {BaseSelection, Spread} from 'lexical';
-import {addClassNamesToElement} from '@lexical/utils';
+import {$descendantsMatching, addClassNamesToElement} from '@lexical/utils';
import {
$applyNodeReplacement,
DOMConversionMap,
@@ -21,6 +21,7 @@ import {
} from 'lexical';
import {PIXEL_VALUE_REG_EXP} from './constants';
+import {$isTableCellNode} from './LexicalTableCellNode';
export type SerializedTableRowNode = Spread<
{
@@ -124,7 +125,10 @@ export function $convertTableRowElement(domNode: Node): DOMConversionOutput {
height = parseFloat(domNode_.style.height);
}
- return {node: $createTableRowNode(height)};
+ return {
+ after: (children) => $descendantsMatching(children, $isTableCellNode),
+ node: $createTableRowNode(height),
+ };
}
export function $createTableRowNode(height?: number): TableRowNode {
diff --git a/packages/lexical-table/src/LexicalTableUtils.ts b/packages/lexical-table/src/LexicalTableUtils.ts
index e1c0c0884cd..6c354285ec4 100644
--- a/packages/lexical-table/src/LexicalTableUtils.ts
+++ b/packages/lexical-table/src/LexicalTableUtils.ts
@@ -548,6 +548,7 @@ export function $deleteTableRow__EXPERIMENTAL(): void {
return;
}
const columnCount = gridMap[0].length;
+ const selectedRowCount = anchorCell.__rowSpan;
const nextRow = gridMap[focusEndRow + 1];
const nextRowNode: null | TableRowNode = grid.getChildAtIndex(
focusEndRow + 1,
@@ -565,7 +566,11 @@ export function $deleteTableRow__EXPERIMENTAL(): void {
}
// Rows overflowing top have to be trimmed
if (row === anchorStartRow && cellStartRow < anchorStartRow) {
- cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow));
+ const overflowTop = anchorStartRow - cellStartRow;
+ cell.setRowSpan(
+ cell.__rowSpan -
+ Math.min(selectedRowCount, cell.__rowSpan - overflowTop),
+ );
}
// Rows overflowing bottom have to be trimmed and moved to the next row
if (
@@ -574,11 +579,22 @@ export function $deleteTableRow__EXPERIMENTAL(): void {
) {
cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1));
invariant(nextRowNode !== null, 'Expected nextRowNode not to be null');
- if (column === 0) {
+ let insertAfterCell: null | TableCellNode = null;
+ for (let columnIndex = 0; columnIndex < column; columnIndex++) {
+ const currentCellMap = nextRow[columnIndex];
+ const currentCell = currentCellMap.cell;
+ // Checking the cell having startRow as same as nextRow
+ if (currentCellMap.startRow === row + 1) {
+ insertAfterCell = currentCell;
+ }
+ if (currentCell.__colSpan > 1) {
+ columnIndex += currentCell.__colSpan - 1;
+ }
+ }
+ if (insertAfterCell === null) {
$insertFirst(nextRowNode, cell);
} else {
- const {cell: previousCell} = nextRow[column - 1];
- previousCell.insertAfter(cell);
+ insertAfterCell.insertAfter(cell);
}
}
}
diff --git a/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx b/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx
index 96ca3c7e426..38755a841e2 100644
--- a/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx
+++ b/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx
@@ -317,6 +317,318 @@ describe('LexicalTableNode tests', () => {
);
});
+ test('Copy table with caption/tbody/thead/tfoot from an external source', async () => {
+ const {editor} = testEnv;
+
+ const dataTransfer = new DataTransferMock();
+ dataTransfer.setData(
+ 'text/html',
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead
+ html`
+
+
+
+ Council budget (in £) 2018
+
+
+
+
+ Items
+ |
+
+ Expenditure
+ |
+
+
+
+
+
+ Donuts
+ |
+
+ 3,000
+ |
+
+
+
+ Stationery
+ |
+
+ 18,000
+ |
+
+
+
+
+
+ Totals
+ |
+
+ 21,000
+ |
+
+
+
+ `,
+ );
+ await editor.update(() => {
+ const selection = $getSelection();
+ invariant(
+ $isRangeSelection(selection),
+ 'isRangeSelection(selection)',
+ );
+ $insertDataTransferForRichText(dataTransfer, selection, editor);
+ });
+ // Here we are testing the createDOM, not the exportDOM, so the tbody is not there
+ expectTableHtmlToBeEqual(
+ testEnv.innerHTML,
+ html`
+
+
+
+
+
+
+
+
+ Items
+
+ |
+
+
+ Expenditure
+
+ |
+
+
+
+
+ Donuts
+
+ |
+
+
+ 3,000
+
+ |
+
+
+
+
+ Stationery
+
+ |
+
+
+ 18,000
+
+ |
+
+
+
+
+ Totals
+
+ |
+
+
+ 21,000
+
+ |
+
+
+ `,
+ );
+ });
+
+ test('Copy table with caption from an external source', async () => {
+ const {editor} = testEnv;
+
+ const dataTransfer = new DataTransferMock();
+ dataTransfer.setData(
+ 'text/html',
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption
+ html`
+
+
+
+ He-Man and Skeletor facts
+
+
+
+ |
+
+ He-Man
+ |
+
+ Skeletor
+ |
+
+
+
+ Role
+ |
+
+ Hero
+ |
+
+ Villain
+ |
+
+
+
+ Weapon
+ |
+
+ Power Sword
+ |
+
+ Havoc Staff
+ |
+
+
+
+ Dark secret
+ |
+
+ Expert florist
+ |
+
+ Cries at romcoms
+ |
+
+
+
+ `,
+ );
+ await editor.update(() => {
+ const selection = $getSelection();
+ invariant(
+ $isRangeSelection(selection),
+ 'isRangeSelection(selection)',
+ );
+ $insertDataTransferForRichText(dataTransfer, selection, editor);
+ });
+ // Here we are testing the createDOM, not the exportDOM, so the tbody is not there
+ expectTableHtmlToBeEqual(
+ testEnv.innerHTML,
+ html`
+
+
+
+
+
+
+
+
+
+ |
+
+
+ He-Man
+
+ |
+
+
+ Skeletor
+
+ |
+
+
+
+
+ Role
+
+ |
+
+
+ Hero
+
+ |
+
+
+ Villain
+
+ |
+
+
+
+
+ Weapon
+
+ |
+
+
+ Power Sword
+
+ |
+
+
+ Havoc Staff
+
+ |
+
+
+
+
+ Dark secret
+
+ |
+
+
+ Expert florist
+
+ |
+
+
+ Cries at romcoms
+
+ |
+
+
+ `,
+ );
+ });
+
test('Copy table from an external source like gdoc with formatting', async () => {
const {editor} = testEnv;
diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts
index be452681b98..c4fe6ace096 100644
--- a/packages/lexical-table/src/index.ts
+++ b/packages/lexical-table/src/index.ts
@@ -29,6 +29,11 @@ export {
} from './LexicalTableNode';
export type {TableDOMCell} from './LexicalTableObserver';
export {$getTableAndElementByKey, TableObserver} from './LexicalTableObserver';
+export {
+ registerTableCellUnmergeTransform,
+ registerTablePlugin,
+ registerTableSelectionObserver,
+} from './LexicalTablePluginHelpers';
export type {SerializedTableRowNode} from './LexicalTableRowNode';
export {
$createTableRowNode,
diff --git a/packages/lexical-utils/flow/LexicalUtils.js.flow b/packages/lexical-utils/flow/LexicalUtils.js.flow
index 11524eee950..958dd8acfa7 100644
--- a/packages/lexical-utils/flow/LexicalUtils.js.flow
+++ b/packages/lexical-utils/flow/LexicalUtils.js.flow
@@ -125,3 +125,14 @@ declare export function $splitNode(
declare export function calculateZoomLevel(element: Element | null): number;
declare export function $isEditorIsNestedEditor(editor: LexicalEditor): boolean;
+
+declare export function $unwrapAndFilterDescendants(
+ root: ElementNode,
+ $predicate: (node: LexicalNode) => boolean,
+): boolean;
+
+declare export function $firstToLastIterator(node: ElementNode): Iterable;
+
+declare export function $lastToFirstIterator(node: ElementNode): Iterable;
+
+declare export function $unwrapNode(node: ElementNode): void;
diff --git a/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx b/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx
new file mode 100644
index 00000000000..3a4e596626f
--- /dev/null
+++ b/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import type {Klass, LexicalEditor, LexicalNode} from 'lexical';
+
+import {$descendantsMatching} from '@lexical/utils';
+import {
+ $createParagraphNode,
+ $createTextNode,
+ $getRoot,
+ $isTextNode,
+ ParagraphNode,
+} from 'lexical';
+import {createTestEditor} from 'lexical/src/__tests__/utils';
+
+function assertClass(v: unknown, klass: Klass): T {
+ if (v instanceof klass) {
+ return v as T;
+ }
+ throw new Error(`Value does not extend ${klass.name}`);
+}
+
+function $createTextAndParagraphWithDepth(depth: number): LexicalNode[] {
+ if (depth <= 0) {
+ return [$createTextNode(`<${depth} />`)];
+ }
+ return [
+ $createTextNode(`<${depth}>`),
+ $createParagraphNode().append(
+ ...$createTextAndParagraphWithDepth(depth - 1),
+ ),
+ $createTextNode(`${depth}>`),
+ ];
+}
+
+function textContentForDepth(i: number): string {
+ return i > 0 ? `<${i}>${textContentForDepth(i - 1)}${i}>` : `<${i} />`;
+}
+
+describe('$descendantsMatching', () => {
+ let editor: LexicalEditor;
+
+ beforeEach(async () => {
+ editor = createTestEditor();
+ editor._headless = true;
+ });
+
+ [0, 1, 2].forEach((depth) =>
+ it(`Can un-nest children at depth ${depth}`, () => {
+ editor.update(
+ () => {
+ const firstNode = $createParagraphNode();
+ $getRoot()
+ .clear()
+ .append(
+ firstNode.append(...$createTextAndParagraphWithDepth(depth)),
+ );
+ },
+ {discrete: true},
+ );
+ editor.update(
+ () => {
+ const firstNode = assertClass(
+ $getRoot().getFirstChildOrThrow(),
+ ParagraphNode,
+ );
+ expect(firstNode.getChildren().every($isTextNode)).toBe(depth === 0);
+ firstNode.splice(
+ 0,
+ firstNode.getChildrenSize(),
+ $descendantsMatching(firstNode.getChildren(), $isTextNode),
+ );
+ expect(firstNode.getChildren().every($isTextNode)).toBe(true);
+ expect(firstNode.getTextContent()).toBe(textContentForDepth(depth));
+ },
+ {discrete: true},
+ );
+ }),
+ );
+});
diff --git a/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx b/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx
new file mode 100644
index 00000000000..f7042e14e0e
--- /dev/null
+++ b/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import type {Klass, LexicalEditor, LexicalNode} from 'lexical';
+
+import {$firstToLastIterator, $lastToFirstIterator} from '@lexical/utils';
+import {$createParagraphNode, $createTextNode, TextNode} from 'lexical';
+import {createTestEditor} from 'lexical/src/__tests__/utils';
+
+function assertClass(v: unknown, klass: Klass): T {
+ if (v instanceof klass) {
+ return v as T;
+ }
+ throw new Error(`Value does not extend ${klass.name}`);
+}
+
+describe('$firstToLastIterator', () => {
+ let editor: LexicalEditor;
+
+ beforeEach(async () => {
+ editor = createTestEditor();
+ editor._headless = true;
+ });
+
+ it(`Iterates from first to last`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($firstToLastIterator(parent), (node) => {
+ return assertClass(node, TextNode).getTextContent();
+ }),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ // Parent was not affected
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ },
+ {discrete: true},
+ );
+ });
+ it(`Can handle node removal`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($firstToLastIterator(parent), (node) => {
+ const rval = assertClass(node, TextNode).getTextContent();
+ node.remove();
+ return rval;
+ }),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(parent.getChildren()).toEqual([]);
+ },
+ {discrete: true},
+ );
+ });
+ it(`Detects cycles when nodes move incorrectly`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(() =>
+ Array.from($firstToLastIterator(parent), (node) => {
+ const rval = assertClass(node, TextNode).getTextContent();
+ parent.append(node);
+ return rval;
+ }),
+ ).toThrow(/\$childIterator: Cycle detected/);
+ },
+ {discrete: true},
+ );
+ });
+ it(`Can handle nodes moving in the other direction`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($firstToLastIterator(parent), (node) => {
+ const rval = assertClass(node, TextNode).getTextContent();
+ if (node.getPreviousSibling() !== null) {
+ parent.splice(0, 0, [node]);
+ }
+ return rval;
+ }),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ // This mutation reversed the nodes while traversing
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['4', '3', '2', '1', '0']);
+ },
+ {discrete: true},
+ );
+ });
+});
+
+describe('$lastToFirstIterator', () => {
+ let editor: LexicalEditor;
+
+ beforeEach(async () => {
+ editor = createTestEditor();
+ editor._headless = true;
+ });
+
+ it(`Iterates from last to first`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($lastToFirstIterator(parent), (node) => {
+ return assertClass(node, TextNode).getTextContent();
+ }),
+ ).toEqual(['4', '3', '2', '1', '0']);
+ // Parent was not affected
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ },
+ {discrete: true},
+ );
+ });
+ it(`Can handle node removal`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($lastToFirstIterator(parent), (node) => {
+ const rval = assertClass(node, TextNode).getTextContent();
+ node.remove();
+ return rval;
+ }),
+ ).toEqual(['4', '3', '2', '1', '0']);
+ expect(parent.getChildren()).toEqual([]);
+ },
+ {discrete: true},
+ );
+ });
+ it(`Can handle nodes moving in the other direction`, () => {
+ editor.update(
+ () => {
+ const parent = $createParagraphNode().splice(
+ 0,
+ 0,
+ Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)),
+ );
+ // Check initial state
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['0', '1', '2', '3', '4']);
+ expect(
+ Array.from($lastToFirstIterator(parent), (node) => {
+ const rval = assertClass(node, TextNode).getTextContent();
+ parent.append(node);
+ return rval;
+ }),
+ ).toEqual(['4', '3', '2', '1', '0']);
+ // This mutation reversed the nodes while traversing
+ expect(
+ parent.getAllTextNodes().map((node) => node.getTextContent()),
+ ).toEqual(['4', '3', '2', '1', '0']);
+ },
+ {discrete: true},
+ );
+ });
+});
diff --git a/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx b/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx
new file mode 100644
index 00000000000..cb42d0fe643
--- /dev/null
+++ b/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import type {Klass, LexicalEditor, LexicalNode} from 'lexical';
+
+import {$unwrapAndFilterDescendants} from '@lexical/utils';
+import {
+ $createParagraphNode,
+ $createTextNode,
+ $getRoot,
+ $isParagraphNode,
+ $isTextNode,
+ ParagraphNode,
+} from 'lexical';
+import {createTestEditor} from 'lexical/src/__tests__/utils';
+
+function assertClass(v: unknown, klass: Klass): T {
+ if (v instanceof klass) {
+ return v as T;
+ }
+ throw new Error(`Value does not extend ${klass.name}`);
+}
+
+function $createTextAndParagraphWithDepth(depth: number): LexicalNode[] {
+ if (depth <= 0) {
+ return [$createTextNode(`<${depth} />`)];
+ }
+ return [
+ $createTextNode(`<${depth}>`),
+ $createParagraphNode().append(
+ ...$createTextAndParagraphWithDepth(depth - 1),
+ ),
+ $createTextNode(`${depth}>`),
+ ];
+}
+
+function textContentForDepth(i: number): string {
+ return i > 0 ? `<${i}>${textContentForDepth(i - 1)}${i}>` : `<${i} />`;
+}
+
+describe('$unwrapAndFilterDescendants', () => {
+ let editor: LexicalEditor;
+
+ beforeEach(async () => {
+ editor = createTestEditor();
+ editor._headless = true;
+ });
+
+ it('Is a no-op with valid children', () => {
+ editor.update(
+ () => {
+ $getRoot().clear().append($createParagraphNode());
+ },
+ {discrete: true},
+ );
+ editor.update(
+ () => {
+ expect($unwrapAndFilterDescendants($getRoot(), $isParagraphNode)).toBe(
+ false,
+ );
+ expect($getRoot().getChildrenSize()).toBe(1);
+ expect($isParagraphNode($getRoot().getFirstChild())).toBe(true);
+ },
+ {discrete: true},
+ );
+ });
+ [0, 1, 2].forEach((depth) =>
+ it(`Can un-nest children at depth ${depth}`, () => {
+ editor.update(
+ () => {
+ const firstNode = $createParagraphNode();
+ $getRoot()
+ .clear()
+ .append(
+ firstNode.append(...$createTextAndParagraphWithDepth(depth)),
+ );
+ },
+ {discrete: true},
+ );
+ editor.update(
+ () => {
+ const firstNode = assertClass(
+ $getRoot().getFirstChildOrThrow(),
+ ParagraphNode,
+ );
+ expect(firstNode.getChildren().every($isTextNode)).toBe(depth === 0);
+ expect($unwrapAndFilterDescendants(firstNode, $isTextNode)).toBe(
+ depth > 0,
+ );
+ expect(firstNode.getChildren().every($isTextNode)).toBe(true);
+ expect(firstNode.getTextContent()).toBe(textContentForDepth(depth));
+ },
+ {discrete: true},
+ );
+ }),
+ );
+});
diff --git a/packages/lexical-utils/src/index.ts b/packages/lexical-utils/src/index.ts
index 8994e3dad65..0a758f40f2f 100644
--- a/packages/lexical-utils/src/index.ts
+++ b/packages/lexical-utils/src/index.ts
@@ -23,6 +23,7 @@ import {
Klass,
LexicalEditor,
LexicalNode,
+ NodeKey,
} from 'lexical';
// This underscore postfixing is used as a hotfix so we do not
// export shared types from this module #5918
@@ -690,3 +691,157 @@ export function calculateZoomLevel(element: Element | null): number {
export function $isEditorIsNestedEditor(editor: LexicalEditor): boolean {
return editor._parentEditor !== null;
}
+
+/**
+ * A depth first last-to-first traversal of root that stops at each node that matches
+ * $predicate and ensures that its parent is root. This is typically used to discard
+ * invalid or unsupported wrapping nodes. For example, a TableNode must only have
+ * TableRowNode as children, but an importer might add invalid nodes based on
+ * caption, tbody, thead, etc. and this will unwrap and discard those.
+ *
+ * @param root The root to start the traversal
+ * @param $predicate Should return true for nodes that are permitted to be children of root
+ * @returns true if this unwrapped or removed any nodes
+ */
+export function $unwrapAndFilterDescendants(
+ root: ElementNode,
+ $predicate: (node: LexicalNode) => boolean,
+): boolean {
+ return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
+}
+
+function $unwrapAndFilterDescendantsImpl(
+ root: ElementNode,
+ $predicate: (node: LexicalNode) => boolean,
+ $onSuccess: null | ((node: LexicalNode) => void),
+): boolean {
+ let didMutate = false;
+ for (const node of $lastToFirstIterator(root)) {
+ if ($predicate(node)) {
+ if ($onSuccess !== null) {
+ $onSuccess(node);
+ }
+ continue;
+ }
+ didMutate = true;
+ if ($isElementNode(node)) {
+ $unwrapAndFilterDescendantsImpl(
+ node,
+ $predicate,
+ $onSuccess ? $onSuccess : (child) => node.insertAfter(child),
+ );
+ }
+ node.remove();
+ }
+ return didMutate;
+}
+
+/**
+ * A depth first traversal of the children array that stops at and collects
+ * each node that `$predicate` matches. This is typically used to discard
+ * invalid or unsupported wrapping nodes on a children array in the `after`
+ * of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
+ * TableRowNode as children, but an importer might add invalid nodes based on
+ * caption, tbody, thead, etc. and this will unwrap and discard those.
+ *
+ * This function is read-only and performs no mutation operations, which makes
+ * it suitable for import and export purposes but likely not for any in-place
+ * mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
+ * mutations such as node transforms.
+ *
+ * @param children The children to traverse
+ * @param $predicate Should return true for nodes that are permitted to be children of root
+ * @returns The children or their descendants that match $predicate
+ */
+export function $descendantsMatching(
+ children: LexicalNode[],
+ $predicate: (node: LexicalNode) => node is T,
+): T[];
+export function $descendantsMatching(
+ children: LexicalNode[],
+ $predicate: (node: LexicalNode) => boolean,
+): LexicalNode[] {
+ const result: LexicalNode[] = [];
+ const stack = [...children].reverse();
+ for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
+ if ($predicate(child)) {
+ result.push(child);
+ } else if ($isElementNode(child)) {
+ for (const grandchild of $lastToFirstIterator(child)) {
+ stack.push(grandchild);
+ }
+ }
+ }
+ return result;
+}
+
+/**
+ * Return an iterator that yields each child of node from first to last, taking
+ * care to preserve the next sibling before yielding the value in case the caller
+ * removes the yielded node.
+ *
+ * @param node The node whose children to iterate
+ * @returns An iterator of the node's children
+ */
+export function $firstToLastIterator(node: ElementNode): Iterable {
+ return {
+ [Symbol.iterator]: () =>
+ $childIterator(node.getFirstChild(), (child) => child.getNextSibling()),
+ };
+}
+
+/**
+ * Return an iterator that yields each child of node from last to first, taking
+ * care to preserve the previous sibling before yielding the value in case the caller
+ * removes the yielded node.
+ *
+ * @param node The node whose children to iterate
+ * @returns An iterator of the node's children
+ */
+export function $lastToFirstIterator(node: ElementNode): Iterable {
+ return {
+ [Symbol.iterator]: () =>
+ $childIterator(node.getLastChild(), (child) =>
+ child.getPreviousSibling(),
+ ),
+ };
+}
+
+function $childIterator(
+ initialNode: LexicalNode | null,
+ nextNode: (node: LexicalNode) => LexicalNode | null,
+): Iterator {
+ let state = initialNode;
+ const seen = __DEV__ ? new Set() : null;
+ return {
+ next() {
+ if (state === null) {
+ return iteratorDone;
+ }
+ const rval = iteratorNotDone(state);
+ if (__DEV__ && seen !== null) {
+ const key = state.getKey();
+ invariant(
+ !seen.has(key),
+ '$childIterator: Cycle detected, node with key %s has already been traversed',
+ String(key),
+ );
+ seen.add(key);
+ }
+ state = nextNode(state);
+ return rval;
+ },
+ };
+}
+
+/**
+ * Insert all children before this node, and then remove it.
+ *
+ * @param node The ElementNode to unwrap and remove
+ */
+export function $unwrapNode(node: ElementNode): void {
+ for (const child of $firstToLastIterator(node)) {
+ node.insertBefore(child);
+ }
+ node.remove();
+}
diff --git a/packages/lexical-website/docs/react/create_plugin.md b/packages/lexical-website/docs/react/create_plugin.md
index 310d964672c..6ebf19263fe 100644
--- a/packages/lexical-website/docs/react/create_plugin.md
+++ b/packages/lexical-website/docs/react/create_plugin.md
@@ -1,7 +1,3 @@
----
-sidebar_position: 2
----
-
# Creating a React Plugin
In addition to using the Lexical React plugins offered by the core library, you can make your own plugins to extend or alter Lexical's functionality to suit your own use cases.
@@ -18,7 +14,7 @@ If the Plugin introduces new nodes, they have to be registered in `initialConfig
```js
const initialConfig = {
- namespace: "MyEditor",
+ namespace: 'MyEditor',
nodes: [MyLexicalNode],
};
```
diff --git a/packages/lexical-website/docs/react/faq.md b/packages/lexical-website/docs/react/faq.md
index c98dce9be91..b63a2295e5a 100644
--- a/packages/lexical-website/docs/react/faq.md
+++ b/packages/lexical-website/docs/react/faq.md
@@ -1,6 +1,3 @@
----
----
-
# React FAQ
## My app does not work in dev when using StrictMode, help!?
@@ -14,18 +11,18 @@ conventions and guidelines. This is a great place to start:
Some Lexical-specific concerns (which are consequences of React's
concurrent and StrictMode semantics, not due to anything unusual in Lexical):
-* In React 19, `useMemo` calls are cached across StrictMode re-renders, so
+- In React 19, `useMemo` calls are cached across StrictMode re-renders, so
only one editor will be used for both renders. If you have a `useEffect`
call with side-effects (such as updating the document when a plug-in
initializes), then you should first check to make sure that this effect
has not already occurred (e.g. by checking the state of the document or
undoing the change as a cleanup function returned by the effect)
-* `LexicalComposer`'s initialConfig prop is only considered once during
+- `LexicalComposer`'s initialConfig prop is only considered once during
the first render (`useMemo` is used to create the `LexicalComposerContext`
which includes the editor and theme)
-* If you are using an `editorState` argument in the config when creating the
+- If you are using an `editorState` argument in the config when creating the
editor, it will only be called once when the editor is created.
-* You should generally prefer to use hooks that return state such as
+- You should generally prefer to use hooks that return state such as
`useLexicalEditable` (`useLexicalSubscription` is a generalization of this
style) rather than manually registering the listeners and expecting a
particular sequence of triggers to be called, especially
@@ -45,10 +42,10 @@ build of Lexical that the hook was imported from.
The most common root causes of this issue are:
-* You are trying to use `useLexicalComposerContext()` in a component that is
+- You are trying to use `useLexicalComposerContext()` in a component that is
not a child of the `LexicalComposer`. If you need to do that, you need to
pass the context or editor up the tree with something like `EditorRefPlugin`.
-* You have multiple builds of Lexical in your project. This could be because
+- You have multiple builds of Lexical in your project. This could be because
you have a dependency that has a direct dependency on some other version
of Lexical (these packages should have Lexical as `peerDependencies`, but
not all do), or because your project mixes import and require statements
diff --git a/packages/lexical-website/docs/react/index.md b/packages/lexical-website/docs/react/index.md
index b0150b10116..1b811e5b517 100644
--- a/packages/lexical-website/docs/react/index.md
+++ b/packages/lexical-website/docs/react/index.md
@@ -1,9 +1,5 @@
---
-id: "index"
-title: "Lexical API"
-sidebar_label: "Introduction"
-sidebar_position: 0
-custom_edit_url: null
+sidebar_label: 'Introduction'
---
# Lexical + React
@@ -13,7 +9,7 @@ To make it easier for React users to implement rich-text editors, Lexical expose
- {`Getting Started Guide`}
+{`Getting Started Guide`}
## Supported Versions
diff --git a/packages/lexical-website/docs/react/plugins.md b/packages/lexical-website/docs/react/plugins.md
index c40f8c22251..ca97f7935ca 100644
--- a/packages/lexical-website/docs/react/plugins.md
+++ b/packages/lexical-website/docs/react/plugins.md
@@ -1,7 +1,3 @@
----
-sidebar_position: 1
----
-
# Lexical Plugins
React-based plugins are using Lexical editor instance from `` context:
@@ -29,7 +25,7 @@ const initialConfig = {
...
-
+;
```
> Note: Many plugins might require you to register the one or many Lexical nodes in order for the plugin to work. You can do this by passing a reference to the node to the `nodes` array in your initial editor configuration.
@@ -45,7 +41,7 @@ const initialConfig = {
### `LexicalPlainTextPlugin`
-React wrapper for `@lexical/plain-text` that adds major features for plain text editing, including typing, deletion and copy/pasting
+React wrapper for `@lexical/plain-text` that adds major features for plain text editing, including typing, deletion and copy/pasting.
```jsx
@@ -77,7 +73,7 @@ Plugin that calls `onChange` whenever Lexical state is updated. Using `ignoreHis
### `LexicalHistoryPlugin`
-React wrapper for `@lexical/history` that adds support for history stack management and `undo` / `redo` commands
+React wrapper for `@lexical/history` that adds support for history stack management and `undo` / `redo` commands.
```jsx
@@ -85,7 +81,7 @@ React wrapper for `@lexical/history` that adds support for history stack managem
### `LexicalLinkPlugin`
-React wrapper for `@lexical/link` that adds support for links, including `$toggleLink` command support that toggles link for selected text
+React wrapper for `@lexical/link` that adds support for links, including `$toggleLink` command support that toggles link for selected text.
```jsx
@@ -111,7 +107,7 @@ React wrapper for `@lexical/list` that adds support for check lists. Note that i
[![See API Documentation](/img/see-api-documentation.svg)](/docs/api/modules/lexical_react_LexicalTablePlugin)
-React wrapper for `@lexical/table` that adds support for tables
+React wrapper for `@lexical/table` that adds support for tables.
```jsx
@@ -157,7 +153,7 @@ const MATCHERS = [
### `LexicalClearEditorPlugin`
-Adds `clearEditor` command support to clear editor's content
+Adds `clearEditor` command support to clear editor's content.
```jsx
@@ -165,7 +161,7 @@ Adds `clearEditor` command support to clear editor's content
### `LexicalMarkdownShortcutPlugin`
-Adds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough)
+Adds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough).
```jsx
@@ -184,7 +180,9 @@ In order to use `TableOfContentsPlugin`, you need to pass a callback function in
```jsx
{(tableOfContentsArray) => {
- return ;
+ return (
+
+ );
}}
```
@@ -195,8 +193,8 @@ Allows you to get a ref to the underlying editor instance outside of LexicalComp
from a separate part of your application.
```jsx
- const editorRef = useRef(null);
-
+const editorRef = useRef(null);
+;
```
### `LexicalSelectionAlwaysOnDisplay`
@@ -204,5 +202,5 @@ from a separate part of your application.
By default, browser text selection becomes invisible when clicking away from the editor. This plugin ensures the selection remains visible.
```jsx
-
-```
\ No newline at end of file
+
+```
diff --git a/packages/lexical-website/sidebars.js b/packages/lexical-website/sidebars.js
index d1f61b1374a..523933de4c2 100644
--- a/packages/lexical-website/sidebars.js
+++ b/packages/lexical-website/sidebars.js
@@ -71,12 +71,13 @@ const sidebars = {
type: 'category',
},
{
- items: [{dirName: 'react', type: 'autogenerated'}],
+ items: [
+ 'react/index',
+ 'react/plugins',
+ 'react/create_plugin',
+ 'react/faq',
+ ],
label: 'React',
- link: {
- id: 'react/index',
- type: 'doc',
- },
type: 'category',
},
{
diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts
index 76de3af0278..9acecb88ce3 100644
--- a/packages/lexical/src/LexicalEditor.ts
+++ b/packages/lexical/src/LexicalEditor.ts
@@ -134,6 +134,7 @@ export type EditorThemeClasses = {
quote?: EditorThemeClassName;
root?: EditorThemeClassName;
rtl?: EditorThemeClassName;
+ tab?: EditorThemeClassName;
table?: EditorThemeClassName;
tableAddColumns?: EditorThemeClassName;
tableAddRows?: EditorThemeClassName;
@@ -1073,6 +1074,19 @@ export class LexicalEditor {
if (classNames != null) {
nextRootElement.classList.add(...classNames);
}
+ if (__DEV__) {
+ const nextRootElementParent = nextRootElement.parentElement;
+ if (
+ nextRootElementParent != null &&
+ ['flex', 'inline-flex'].includes(
+ getComputedStyle(nextRootElementParent).display,
+ )
+ ) {
+ console.warn(
+ `When using "display: flex" or "display: inline-flex" on an element containing content editable, Chrome may have unwanted focusing behavior when clicking outside of it. Consider wrapping the content editable within a non-flex element.`,
+ );
+ }
+ }
} else {
// If content editable is unmounted we'll reset editor state back to original
// (or pending) editor state since there will be no reconciliation
diff --git a/packages/lexical/src/LexicalUtils.ts b/packages/lexical/src/LexicalUtils.ts
index d6af132f690..66f7cdea11a 100644
--- a/packages/lexical/src/LexicalUtils.ts
+++ b/packages/lexical/src/LexicalUtils.ts
@@ -1093,10 +1093,25 @@ export function isSelectAll(
return key.toLowerCase() === 'a' && controlOrMeta(metaKey, ctrlKey);
}
-export function $selectAll(): void {
+export function $selectAll(selection?: RangeSelection | null): RangeSelection {
const root = $getRoot();
- const selection = root.select(0, root.getChildrenSize());
- $setSelection($normalizeSelection(selection));
+
+ if ($isRangeSelection(selection)) {
+ const anchor = selection.anchor;
+ const focus = selection.focus;
+ const anchorNode = anchor.getNode();
+ const topParent = anchorNode.getTopLevelElementOrThrow();
+ const rootNode = topParent.getParentOrThrow();
+ anchor.set(rootNode.getKey(), 0, 'element');
+ focus.set(rootNode.getKey(), rootNode.getChildrenSize(), 'element');
+ $normalizeSelection(selection);
+ return selection;
+ } else {
+ // Create a new RangeSelection
+ const newSelection = root.select(0, root.getChildrenSize());
+ $setSelection($normalizeSelection(newSelection));
+ return newSelection;
+ }
}
export function getCachedClassNameArray(
diff --git a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx
index cf33a568d3f..3986f27806f 100644
--- a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx
+++ b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx
@@ -2210,7 +2210,7 @@ describe('LexicalEditor tests', () => {
await editor.update(() => {
const root = $getRoot();
- const tableCell = $createTableCellNode(0);
+ const tableCell = $createTableCellNode();
const tableRow = $createTableRowNode();
const table = $createTableNode();
@@ -2225,7 +2225,7 @@ describe('LexicalEditor tests', () => {
await editor.update(() => {
const tableRow = $getNodeByKey(tableRowKey) as TableRowNode;
- const tableCell = $createTableCellNode(0);
+ const tableCell = $createTableCellNode();
tableRow.append(tableCell);
});
diff --git a/packages/lexical/src/nodes/LexicalTabNode.ts b/packages/lexical/src/nodes/LexicalTabNode.ts
index d3182e40df0..8c5999b33c2 100644
--- a/packages/lexical/src/nodes/LexicalTabNode.ts
+++ b/packages/lexical/src/nodes/LexicalTabNode.ts
@@ -11,8 +11,9 @@ import type {DOMConversionMap, NodeKey} from '../LexicalNode';
import invariant from 'shared/invariant';
import {IS_UNMERGEABLE} from '../LexicalConstants';
+import {EditorConfig} from '../LexicalEditor';
import {LexicalNode} from '../LexicalNode';
-import {$applyNodeReplacement} from '../LexicalUtils';
+import {$applyNodeReplacement, getCachedClassNameArray} from '../LexicalUtils';
import {
SerializedTextNode,
TextDetailType,
@@ -47,6 +48,17 @@ export class TabNode extends TextNode {
return null;
}
+ createDOM(config: EditorConfig): HTMLElement {
+ const dom = super.createDOM(config);
+ const classNames = getCachedClassNameArray(config.theme, 'tab');
+
+ if (classNames !== undefined) {
+ const domClassList = dom.classList;
+ domClassList.add(...classNames);
+ }
+ return dom;
+ }
+
static importJSON(serializedTabNode: SerializedTabNode): TabNode {
const node = $createTabNode();
node.setFormat(serializedTabNode.format);
diff --git a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json
index 113ad945203..c0bf25d4f21 100644
--- a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json
+++ b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json
@@ -1,24 +1,24 @@
{
"name": "lexical-sveltekit-vanilla-js",
- "version": "0.17.1",
+ "version": "0.21.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lexical-sveltekit-vanilla-js",
- "version": "0.17.1",
+ "version": "0.21.0",
"devDependencies": {
- "@lexical/dragon": "0.17.1",
- "@lexical/history": "0.17.1",
- "@lexical/rich-text": "0.17.1",
- "@lexical/utils": "0.17.1",
+ "@lexical/dragon": "0.21.0",
+ "@lexical/history": "0.21.0",
+ "@lexical/rich-text": "0.21.0",
+ "@lexical/utils": "0.21.0",
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/adapter-static": "^3.0.1",
- "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/kit": "^2.10.1",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
- "lexical": "0.17.1",
+ "lexical": "0.21.0",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.19",
@@ -41,9 +41,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
- "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
@@ -57,9 +57,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
- "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
@@ -73,9 +73,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
- "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
@@ -89,9 +89,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
- "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
@@ -105,9 +105,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
- "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
@@ -121,9 +121,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
- "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
@@ -137,9 +137,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
- "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
@@ -153,9 +153,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
- "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
@@ -169,9 +169,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
- "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
@@ -185,9 +185,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
- "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
@@ -201,9 +201,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
- "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
@@ -217,9 +217,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
- "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
@@ -233,9 +233,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
- "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
@@ -249,9 +249,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
- "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
@@ -265,9 +265,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
- "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
@@ -281,9 +281,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
- "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
@@ -297,9 +297,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
- "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
@@ -313,9 +313,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
- "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
@@ -329,9 +329,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
- "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
@@ -345,9 +345,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
- "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
@@ -361,9 +361,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
- "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
@@ -377,9 +377,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
- "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
@@ -393,9 +393,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
- "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
@@ -457,108 +457,100 @@
}
},
"node_modules/@lexical/clipboard": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.17.1.tgz",
- "integrity": "sha512-OVqnEfWX8XN5xxuMPo6BfgGKHREbz++D5V5ISOiml0Z8fV/TQkdgwqbBJcUdJHGRHWSUwdK7CWGs/VALvVvZyw==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.21.0.tgz",
+ "integrity": "sha512-3lNMlMeUob9fcnRXGVieV/lmPbmet/SVWckNTOwzfKrZ/YW5HiiyJrWviLRVf50dGXTbmBGt7K/2pfPYvWCHFA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/html": "0.17.1",
- "@lexical/list": "0.17.1",
- "@lexical/selection": "0.17.1",
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/html": "0.21.0",
+ "@lexical/list": "0.21.0",
+ "@lexical/selection": "0.21.0",
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/dragon": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.17.1.tgz",
- "integrity": "sha512-lhBRKP7RlhiVCLtF0qiNqmMhEO6cQB43sMe7d4bvuY1G2++oKY/XAJPg6QJZdXRrCGRQ6vZ26QRNhRPmCxL5Ng==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.21.0.tgz",
+ "integrity": "sha512-ahTCaOtRFNauEzplN1qVuPjyGAlDd+XcVM5FQCdxVh/1DvqmBxEJRVuCBqatzUUVb89jRBekYUcEdnY9iNjvEQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "lexical": "0.17.1"
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/history": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.17.1.tgz",
- "integrity": "sha512-OU/ohajz4FXchUhghsWC7xeBPypFe50FCm5OePwo767G7P233IztgRKIng2pTT4zhCPW7S6Mfl53JoFHKehpWA==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.21.0.tgz",
+ "integrity": "sha512-Sv2sici2NnAfHYHYRSjjS139MDT8fHP6PlYM2hVr+17dOg7/fJl22VBLRgQ7/+jLtAPxQjID69jvaMlOvt4Oog==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/html": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.17.1.tgz",
- "integrity": "sha512-yGG+K2DXl7Wn2DpNuZ0Y3uCHJgfHkJN3/MmnFb4jLnH1FoJJiuy7WJb/BRRh9H+6xBJ9v70iv+kttDJ0u1xp5w==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.21.0.tgz",
+ "integrity": "sha512-UGahVsGz8OD7Ya39qwquE+JPStTxCw/uaQrnUNorCM7owtPidO2H+tsilAB3A1GK3ksFGdHeEjBjG0Gf7gOg+Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/selection": "0.17.1",
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/selection": "0.21.0",
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/list": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.17.1.tgz",
- "integrity": "sha512-k9ZnmQuBvW+xVUtWJZwoGtiVG2cy+hxzkLGU4jTq1sqxRIoSeGcjvhFAK8JSEj4i21SgkB1FmkWXoYK5kbwtRA==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.21.0.tgz",
+ "integrity": "sha512-WItGlwwNJCS8b6SO1QPKzArShmD+OXQkLbhBcAh+EfpnkvmCW5T5LqY+OfIRmEN1dhDOnwqCY7mXkivWO8o5tw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/rich-text": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.17.1.tgz",
- "integrity": "sha512-T3kvj4P1OpedX9jvxN3WN8NP1Khol6mCW2ScFIRNRz2dsXgyN00thH1Q1J/uyu7aKyGS7rzcY0rb1Pz1qFufqQ==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.21.0.tgz",
+ "integrity": "sha512-+pvEKUneEkGfWOSTl9jU58N9knePilMLxxOtppCAcgnaCdilOh3n5YyRppXhvmprUe0JaTseCMoik2LP51G/JA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/clipboard": "0.17.1",
- "@lexical/selection": "0.17.1",
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/clipboard": "0.21.0",
+ "@lexical/selection": "0.21.0",
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/selection": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.17.1.tgz",
- "integrity": "sha512-qBKVn+lMV2YIoyRELNr1/QssXx/4c0id9NCB/BOuYlG8du5IjviVJquEF56NEv2t0GedDv4BpUwkhXT2QbNAxA==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.21.0.tgz",
+ "integrity": "sha512-4u53bc8zlPPF0rnHjsGQExQ1St8NafsDd70/t1FMw7yvoMtUsKdH7+ap00esLkJOMv45unJD7UOzKRqU1X0sEA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "lexical": "0.17.1"
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/table": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.17.1.tgz",
- "integrity": "sha512-2fUYPmxhyuMQX3MRvSsNaxbgvwGNJpHaKx1Ldc+PT2MvDZ6ALZkfsxbi0do54Q3i7dOon8/avRp4TuVaCnqvoA==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.21.0.tgz",
+ "integrity": "sha512-JhylAWcf4qKD4FmxMUt3YzH5zg2+baBr4+/haLZL7178hMvUzJwGIiWk+3hD3phzmW3WrP49uFXzM7DMSCkE8w==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/utils": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/clipboard": "0.21.0",
+ "@lexical/utils": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@lexical/utils": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.17.1.tgz",
- "integrity": "sha512-jCQER5EsvhLNxKH3qgcpdWj/necUb82Xjp8qWQ3c0tyL07hIRm2tDRA/s9mQmvcP855HEZSmGVmR5SKtkcEAVg==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.21.0.tgz",
+ "integrity": "sha512-YzsNOAiLkCy6R3DuP18gtseDrzgx+30lFyqRvp5M7mckeYgQElwdfG5biNFDLv7BM9GjSzgU5Cunjycsx6Sjqg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@lexical/list": "0.17.1",
- "@lexical/selection": "0.17.1",
- "@lexical/table": "0.17.1",
- "lexical": "0.17.1"
+ "@lexical/list": "0.21.0",
+ "@lexical/selection": "0.21.0",
+ "@lexical/table": "0.21.0",
+ "lexical": "0.21.0"
}
},
"node_modules/@playwright/test": {
@@ -577,26 +569,27 @@
}
},
"node_modules/@polka/url": {
- "version": "1.0.0-next.25",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
- "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==",
+ "version": "1.0.0-next.28",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
+ "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
"dev": true
},
"node_modules/@rollup/plugin-commonjs": {
- "version": "25.0.7",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
- "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==",
+ "version": "28.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
+ "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"commondir": "^1.0.1",
"estree-walker": "^2.0.2",
- "glob": "^8.0.3",
+ "fdir": "^6.2.0",
"is-reference": "1.2.1",
- "magic-string": "^0.30.3"
+ "magic-string": "^0.30.3",
+ "picomatch": "^4.0.2"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16.0.0 || 14 >= 14.17"
},
"peerDependencies": {
"rollup": "^2.68.0||^3.0.0||^4.0.0"
@@ -607,6 +600,32 @@
}
}
},
+ "node_modules/@rollup/plugin-commonjs/node_modules/fdir": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
+ "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
+ "dev": true,
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@rollup/plugin-json": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
@@ -628,15 +647,14 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
- "version": "15.2.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
- "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
+ "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
- "is-builtin-module": "^3.2.1",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
@@ -675,9 +693,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
- "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
+ "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
"cpu": [
"arm"
],
@@ -688,9 +706,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
- "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
+ "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
"cpu": [
"arm64"
],
@@ -701,9 +719,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
- "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
+ "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
"cpu": [
"arm64"
],
@@ -714,9 +732,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
- "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
+ "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
"cpu": [
"x64"
],
@@ -726,10 +744,49 @@
"darwin"
]
},
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
+ "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
+ "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
- "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
+ "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
+ "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
"cpu": [
"arm"
],
@@ -740,9 +797,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
- "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
+ "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
"cpu": [
"arm64"
],
@@ -753,9 +810,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
- "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
+ "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
"cpu": [
"arm64"
],
@@ -765,10 +822,36 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
+ "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
- "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
"cpu": [
"riscv64"
],
@@ -778,10 +861,23 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
+ "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
- "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
+ "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
"cpu": [
"x64"
],
@@ -792,9 +888,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
- "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
+ "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
"cpu": [
"x64"
],
@@ -805,9 +901,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
- "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
+ "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
"cpu": [
"arm64"
],
@@ -818,9 +914,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
- "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
"cpu": [
"ia32"
],
@@ -831,9 +927,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
- "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
"cpu": [
"x64"
],
@@ -844,26 +940,26 @@
]
},
"node_modules/@sveltejs/adapter-auto": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.1.tgz",
- "integrity": "sha512-6LeZft2Fo/4HfmLBi5CucMYmgRxgcETweQl/yQoZo/895K3S9YWYN4Sfm/IhwlIpbJp3QNvhKmwCHbsqQNYQpw==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz",
+ "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==",
"dev": true,
"dependencies": {
- "import-meta-resolve": "^4.0.0"
+ "import-meta-resolve": "^4.1.0"
},
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/adapter-node": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.0.1.tgz",
- "integrity": "sha512-eYdmxdUWMW+dad1JfMsWBPY2vjXz9eE+52A2AQnXPScPJlIxIVk5mmbaEEzrZivLfO2wEcLTZ5vdC03W69x+iA==",
+ "version": "5.2.9",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.9.tgz",
+ "integrity": "sha512-51euNrx0AcaTu8//wDfVh7xmqQSVgU52rfinE/MwvGkJa4nHPJMHmzv6+OIpmxg7gZaF6+5NVlxnieCzxLD59g==",
"dev": true,
"dependencies": {
- "@rollup/plugin-commonjs": "^25.0.7",
+ "@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
- "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-node-resolve": "^15.3.0",
"rollup": "^4.9.5"
},
"peerDependencies": {
@@ -871,32 +967,32 @@
}
},
"node_modules/@sveltejs/adapter-static": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
- "integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==",
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz",
+ "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==",
"dev": true,
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.4.tgz",
- "integrity": "sha512-eDxK2d4EGzk99QsZNoPXe7jlzA5EGqfcCpUwZ912bhnalsZ2ZsG5wGRthkydupVjYyqdmzEanVKFhLxU2vkPSQ==",
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.10.1.tgz",
+ "integrity": "sha512-2aormKTn94aU8Lfxj4gcbRGh1Dyw0hCFlNo51+njdRDn9P2ERuWC4bOtTuoy5HJpPYR3AH8oaaEjKDWUHbi1OA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
- "devalue": "^4.3.2",
- "esm-env": "^1.0.0",
- "import-meta-resolve": "^4.0.0",
+ "devalue": "^5.1.0",
+ "esm-env": "^1.2.1",
+ "import-meta-resolve": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
- "sirv": "^2.0.4",
+ "sirv": "^3.0.0",
"tiny-glob": "^0.2.9"
},
"bin": {
@@ -906,9 +1002,9 @@
"node": ">=18.13"
},
"peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.3"
+ "vite": "^5.0.3 || ^6.0.0"
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
@@ -957,9 +1053,9 @@
"dev": true
},
"node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true
},
"node_modules/@types/resolve": {
@@ -998,33 +1094,6 @@
"dequal": "^2.0.3"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/builtin-modules": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
- "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
- "dev": true,
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/code-red": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
@@ -1111,15 +1180,15 @@
}
},
"node_modules/devalue": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz",
- "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
+ "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
"dev": true
},
"node_modules/esbuild": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
- "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -1129,35 +1198,35 @@
"node": ">=12"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.20.2",
- "@esbuild/android-arm": "0.20.2",
- "@esbuild/android-arm64": "0.20.2",
- "@esbuild/android-x64": "0.20.2",
- "@esbuild/darwin-arm64": "0.20.2",
- "@esbuild/darwin-x64": "0.20.2",
- "@esbuild/freebsd-arm64": "0.20.2",
- "@esbuild/freebsd-x64": "0.20.2",
- "@esbuild/linux-arm": "0.20.2",
- "@esbuild/linux-arm64": "0.20.2",
- "@esbuild/linux-ia32": "0.20.2",
- "@esbuild/linux-loong64": "0.20.2",
- "@esbuild/linux-mips64el": "0.20.2",
- "@esbuild/linux-ppc64": "0.20.2",
- "@esbuild/linux-riscv64": "0.20.2",
- "@esbuild/linux-s390x": "0.20.2",
- "@esbuild/linux-x64": "0.20.2",
- "@esbuild/netbsd-x64": "0.20.2",
- "@esbuild/openbsd-x64": "0.20.2",
- "@esbuild/sunos-x64": "0.20.2",
- "@esbuild/win32-arm64": "0.20.2",
- "@esbuild/win32-ia32": "0.20.2",
- "@esbuild/win32-x64": "0.20.2"
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/esm-env": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
- "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
+ "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
"dev": true
},
"node_modules/estree-walker": {
@@ -1166,12 +1235,6 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
- },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -1195,37 +1258,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/globalyzer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
@@ -1251,53 +1283,25 @@
}
},
"node_modules/import-meta-resolve": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
- "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
+ "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
"dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/is-builtin-module": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
- "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"dependencies": {
- "builtin-modules": "^3.3.0"
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
- "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
- "dev": true,
- "dependencies": {
- "hasown": "^2.0.0"
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1328,11 +1332,10 @@
}
},
"node_modules/lexical": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.17.1.tgz",
- "integrity": "sha512-72/MhR7jqmyqD10bmJw8gztlCm4KDDT+TPtU4elqXrEvHoO5XENi34YAEUD9gIkPfqSwyLa9mwAX1nKzIr5xEA==",
- "dev": true,
- "license": "MIT"
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.21.0.tgz",
+ "integrity": "sha512-Dxc5SCG4kB+wF+Rh55ism3SuecOKeOtCtGHFGKd6pj2QKVojtjkxGTQPMt7//2z5rMSue4R+hmRM0pCEZflupA==",
+ "dev": true
},
"node_modules/locate-character": {
"version": "3.0.0",
@@ -1383,9 +1386,9 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
@@ -1400,15 +1403,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "dependencies": {
- "wrappy": "1"
- }
- },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -1445,9 +1439,9 @@
}
},
"node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/picomatch": {
@@ -1493,9 +1487,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
@@ -1513,8 +1507,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -1563,12 +1557,12 @@
}
},
"node_modules/rollup": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
- "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
+ "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
"dev": true,
"dependencies": {
- "@types/estree": "1.0.5"
+ "@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -1578,19 +1572,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.13.0",
- "@rollup/rollup-android-arm64": "4.13.0",
- "@rollup/rollup-darwin-arm64": "4.13.0",
- "@rollup/rollup-darwin-x64": "4.13.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
- "@rollup/rollup-linux-arm64-gnu": "4.13.0",
- "@rollup/rollup-linux-arm64-musl": "4.13.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
- "@rollup/rollup-linux-x64-gnu": "4.13.0",
- "@rollup/rollup-linux-x64-musl": "4.13.0",
- "@rollup/rollup-win32-arm64-msvc": "4.13.0",
- "@rollup/rollup-win32-ia32-msvc": "4.13.0",
- "@rollup/rollup-win32-x64-msvc": "4.13.0",
+ "@rollup/rollup-android-arm-eabi": "4.28.1",
+ "@rollup/rollup-android-arm64": "4.28.1",
+ "@rollup/rollup-darwin-arm64": "4.28.1",
+ "@rollup/rollup-darwin-x64": "4.28.1",
+ "@rollup/rollup-freebsd-arm64": "4.28.1",
+ "@rollup/rollup-freebsd-x64": "4.28.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.28.1",
+ "@rollup/rollup-linux-arm64-musl": "4.28.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-musl": "4.28.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.28.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.28.1",
+ "@rollup/rollup-win32-x64-msvc": "4.28.1",
"fsevents": "~2.3.2"
}
},
@@ -1613,9 +1613,9 @@
"dev": true
},
"node_modules/sirv": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
- "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
+ "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
"dev": true,
"dependencies": {
"@polka/url": "^1.0.0-next.24",
@@ -1623,13 +1623,13 @@
"totalist": "^3.0.0"
},
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -1742,14 +1742,14 @@
}
},
"node_modules/vite": {
- "version": "5.2.13",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz",
- "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==",
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dev": true,
"dependencies": {
- "esbuild": "^0.20.1",
- "postcss": "^8.4.38",
- "rollup": "^4.13.0"
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -1768,6 +1768,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
+ "sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -1785,6 +1786,9 @@
"sass": {
"optional": true
},
+ "sass-embedded": {
+ "optional": true
+ },
"stylus": {
"optional": true
},
@@ -1823,12 +1827,6 @@
"optional": true
}
}
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
}
}
}
diff --git a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json
index 79ae305e4b2..acb21924aa5 100644
--- a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json
+++ b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json
@@ -17,7 +17,7 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/adapter-static": "^3.0.1",
- "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/kit": "^2.10.1",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"lexical": "0.21.0",
"prettier": "^3.1.1",
|