[0]) {
+ leafRenderCount++;
+
+ return ;
+}
+
+export function getLeafRenderCount() {
+ return leafRenderCount;
+}
+
+export function resetLeafRenderCount() {
+ leafRenderCount = 0;
+}
+
+describe('PlateStatic Memoization', () => {
+ beforeEach(() => {
+ resetElementRenderCount();
+ resetLeafRenderCount();
+ });
+
+ it('should render elements/leaves initially', () => {
+ const editor = createEditor();
+
+ render();
+
+ // We expect at least 1 element (the ...) and 1 leaf
+ expect(getElementRenderCount()).toBe(1);
+ expect(getLeafRenderCount()).toBe(1);
+ });
+
+ it('should NOT re-render elements/leaves if the same `value` reference is passed', () => {
+ const editor = createEditor();
+
+ const { rerender } = render(
+
+ );
+
+ // Re-render with the **same** editor.children reference:
+ rerender();
+
+ // Expect no additional renders of elements/leaves
+ expect(getElementRenderCount()).toEqual(1);
+ expect(getLeafRenderCount()).toEqual(1);
+ });
+
+ it('should re-render elements/leaves if `value` changes by reference', () => {
+ const editor = createEditor();
+
+ const { rerender } = render(
+
+ );
+
+ // Create a new array reference with the same content (just to test reference changes)
+ const newValueRef = [
+ {
+ children: [{ text: 'Hello world' }], // same text, but new object
+ type: 'p',
+ },
+ ];
+
+ // Re-render with a new reference:
+ rerender(
+
+ );
+
+ // Now we expect re-renders because the array reference changed
+ expect(getElementRenderCount()).toBe(2);
+ expect(getLeafRenderCount()).toBe(1);
+ });
+
+ it('should re-render if slate mutation', () => {
+ const editor = createEditor();
+
+ render();
+
+ // This will mutate the text but also element reference
+ editor.tf.insertText('+');
+
+ // Re-render with the updated children
+ // (the reference changed as well as the text)
+ render();
+
+ expect(getElementRenderCount()).toBe(2);
+ expect(getLeafRenderCount()).toBe(2);
+ });
+
+ it('should not re-render if only text changes since element is memoized', () => {
+ const editor = createEditor();
+
+ const { rerender } = render(
+
+ );
+
+ // This will mutate the text only
+ editor.children[0].children[1].text = 'New text';
+
+ // Re-render with the updated children
+ // (the reference changed as well as the text)
+ rerender();
+
+ expect(getElementRenderCount()).toBe(1);
+ expect(getLeafRenderCount()).toBe(1);
+ });
+
+ it('should only re-render modified element and leaf when editing a single element', () => {
+ const editor = createEditorWithMultipleElements();
+
+ const { rerender } = render(
+
+ );
+
+ expect(getElementRenderCount()).toBe(2);
+ expect(getLeafRenderCount()).toBe(2);
+
+ // Modify only the second paragraph
+ editor.children[1] = {
+ ...editor.children[1],
+ children: [
+ editor.children[1].children[0],
+ editor.children[1].children[1],
+ { bold: true, text: 'Modified' },
+ ],
+ };
+
+ // Re-render with the modified editor
+ rerender();
+
+ // We expect only one element to re-render (the modified one)
+ expect(getElementRenderCount()).toBe(3);
+ // We expect only one leaf to re-render (the new bold leaf)
+ expect(getLeafRenderCount()).toBe(3);
+
+ editor.children[1] = {
+ ...editor.children[1],
+ children: [
+ editor.children[1].children[0],
+ editor.children[1].children[1],
+ // Node equals
+ { ...editor.children[1].children[2], text: 'Modified' },
+ ],
+ };
+ rerender();
+
+ expect(getElementRenderCount()).toBe(4);
+ expect(getLeafRenderCount()).toBe(3);
+ });
+
+ it('should preserve memoization when adding and removing new elements', () => {
+ const editor = createEditorWithMultipleElements();
+
+ const { rerender } = render(
+
+ );
+
+ // Add a new paragraph
+ editor.children.push({
+ children: [{ text: 'New Paragraph' }],
+ type: 'p',
+ });
+
+ rerender();
+
+ // We expect only the new element to render
+ expect(getElementRenderCount()).toBe(3);
+
+ editor.children.pop();
+
+ rerender();
+
+ expect(getElementRenderCount()).toBe(3);
+ });
+
+ it('should use _memo property for memoization when available', () => {
+ const editor = createEditor();
+
+ editor.children[0]._memo = 'memo-value';
+
+ const { rerender } = render(
+
+ );
+
+ // Modify element but keep same _memo
+ editor.children[0] = {
+ ...editor.children[0],
+ children: [
+ { text: 'different text' },
+ { bold: true, text: 'still' },
+ { text: 'same memo' },
+ ],
+ };
+
+ rerender();
+
+ // Should not re-render because _memo is the same
+ expect(getElementRenderCount()).toBe(1);
+ });
+
+ it('should re-render when _memo changes', () => {
+ const editor = createEditor();
+
+ editor.children[0]._memo = 'memo-value';
+
+ const { rerender } = render(
+
+ );
+
+ // Change _memo value
+ editor.children[0] = {
+ ...editor.children[0],
+ _memo: 'new-memo-value',
+ };
+
+ rerender();
+
+ // Should re-render because _memo changed
+ expect(getElementRenderCount()).toBe(2);
+ });
+});
diff --git a/packages/core/src/lib/static/components/PlateStatic.tsx b/packages/core/src/lib/static/components/PlateStatic.tsx
index 0d03adf0a2..c9dd9bb6dc 100644
--- a/packages/core/src/lib/static/components/PlateStatic.tsx
+++ b/packages/core/src/lib/static/components/PlateStatic.tsx
@@ -6,9 +6,12 @@ import {
type NodeEntry,
type TElement,
type TText,
+ type Value,
ElementApi,
RangeApi,
TextApi,
+ isElementDecorationsEqual,
+ isTextDecorationsEqual,
} from '@udecode/slate';
import clsx from 'clsx';
@@ -21,7 +24,7 @@ import { pipeRenderElementStatic } from '../pipeRenderElementStatic';
import { pipeRenderLeafStatic } from '../pluginRenderLeafStatic';
import { pipeDecorate } from '../utils/pipeDecorate';
-function ElementStatic({
+function BaseElementStatic({
components,
decorate,
decorations,
@@ -93,7 +96,16 @@ function ElementStatic({
);
}
-function LeafStatic({
+export const ElementStatic = React.memo(BaseElementStatic, (prev, next) => {
+ return (
+ (prev.element === next.element ||
+ (prev.element._memo !== undefined &&
+ prev.element._memo === next.element._memo)) &&
+ isElementDecorationsEqual(prev.decorations, next.decorations)
+ );
+});
+
+function BaseLeafStatic({
components,
decorations,
editor,
@@ -130,13 +142,21 @@ function LeafStatic({
);
}
+export const LeafStatic = React.memo(BaseLeafStatic, (prev, next) => {
+ return (
+ // prev.leaf === next.leaf &&
+ TextApi.equals(next.leaf, prev.leaf) &&
+ isTextDecorationsEqual(next.decorations, prev.decorations)
+ );
+});
+
const defaultDecorate: (entry: NodeEntry) => DecoratedRange[] = () => [];
function Children({
children = [],
components,
decorate = defaultDecorate,
- decorations,
+ decorations = [],
editor,
}: {
children: Descendant[];
@@ -189,13 +209,21 @@ function Children({
}
export type PlateStaticProps = {
+ /** Node components to render. */
components: NodeComponents;
+ /** Editor instance. */
editor: SlateEditor;
style?: React.CSSProperties;
+ /** Controlled value. Alias to `editor.children`. */
+ value?: Value;
} & React.HTMLAttributes;
export function PlateStatic(props: PlateStaticProps) {
- const { className, components, editor, ...rest } = props;
+ const { className, components, editor, value, ...rest } = props;
+
+ if (value) {
+ editor.children = value;
+ }
const decorate = pipeDecorate(editor);
diff --git a/packages/core/src/react/plugins/SlateReactExtensionPlugin.ts b/packages/core/src/react/plugins/SlateReactExtensionPlugin.ts
index 7c4b1bb265..fdced7539d 100644
--- a/packages/core/src/react/plugins/SlateReactExtensionPlugin.ts
+++ b/packages/core/src/react/plugins/SlateReactExtensionPlugin.ts
@@ -1,3 +1,5 @@
+import { isDefined } from '@udecode/utils';
+
import { SlateExtensionPlugin } from '../../lib';
import { toPlatePlugin } from '../plugin';
@@ -10,12 +12,26 @@ export const SlateReactExtensionPlugin = toPlatePlugin(SlateExtensionPlugin, {
editor.currentKeyboardEvent = event;
},
},
-}).extendEditorApi(({ editor }) => ({
- redecorate: () => {
- editor.api.debug.warn(
- `The method editor.api.redecorate() has not been overridden. ` +
- `This may cause unexpected behavior. Please ensure that all required editor methods are properly defined.`,
- 'OVERRIDE_MISSING'
- );
- },
-}));
+})
+ .extendEditorApi(({ editor }) => ({
+ redecorate: () => {
+ editor.api.debug.warn(
+ `The method editor.api.redecorate() has not been overridden. ` +
+ `This may cause unexpected behavior. Please ensure that all required editor methods are properly defined.`,
+ 'OVERRIDE_MISSING'
+ );
+ },
+ }))
+ .overrideEditor(({ editor, tf: { normalizeNode } }) => ({
+ transforms: {
+ normalizeNode(entry, options) {
+ if (isDefined(entry[0]._memo)) {
+ editor.tf.unsetNodes('_memo', { at: entry[1] });
+
+ return;
+ }
+
+ normalizeNode(entry, options);
+ },
+ },
+ }));
diff --git a/packages/core/src/react/plugins/react/ReactPlugin.ts b/packages/core/src/react/plugins/react/ReactPlugin.ts
index 085b418715..c28dda1ca7 100644
--- a/packages/core/src/react/plugins/react/ReactPlugin.ts
+++ b/packages/core/src/react/plugins/react/ReactPlugin.ts
@@ -9,10 +9,10 @@ export const ReactPlugin = createSlatePlugin({
const { reset } = editor.tf;
return {
- reset: () => {
+ reset(options) {
const isFocused = editor.api.isFocused();
- reset();
+ reset(options);
if (isFocused) {
editor.tf.focus({ edge: 'startEditor' });
diff --git a/packages/markdown/package.json b/packages/markdown/package.json
index f2739a0124..78d036b38e 100644
--- a/packages/markdown/package.json
+++ b/packages/markdown/package.json
@@ -48,6 +48,7 @@
},
"dependencies": {
"lodash": "^4.17.21",
+ "marked": "^15.0.6",
"remark-gfm": "4.0.0",
"remark-parse": "^11.0.0",
"unified": "^11.0.5"
diff --git a/packages/markdown/src/lib/deserializer/utils/deserializeMd.spec.tsx b/packages/markdown/src/lib/deserializer/utils/deserializeMd.spec.tsx
index 53eafe2670..3b5fb4c816 100644
--- a/packages/markdown/src/lib/deserializer/utils/deserializeMd.spec.tsx
+++ b/packages/markdown/src/lib/deserializer/utils/deserializeMd.spec.tsx
@@ -378,184 +378,11 @@ describe('deserializeMd', () => {
});
});
-describe('deserializeMdIndentList', () => {
+describe('deserializeMd table', () => {
const editor = createSlateEditor({
plugins: [MarkdownPlugin.configure({ options: { indentList: true } })],
});
- it('should deserialize unordered lists', () => {
- const input = '- List item 1\n- List item 2';
-
- const output = [
- {
- children: [
- {
- text: 'List item 1',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [
- {
- text: 'List item 2',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- ];
-
- expect(deserializeMd(editor, input)).toEqual(output);
- });
-
- it('should deserialize ordered lists', () => {
- const input = '1. List item 1\n2. List item 2';
-
- const output = [
- {
- children: [
- {
- text: 'List item 1',
- },
- ],
- indent: 1,
- listStart: 1,
- listStyleType: 'decimal',
- type: 'p',
- },
- {
- children: [
- {
- text: 'List item 2',
- },
- ],
- indent: 1,
- listStart: 2,
- listStyleType: 'decimal',
- type: 'p',
- },
- ];
-
- expect(deserializeMd(editor, input)).toEqual(output);
- });
-
- it('should deserialize mixed nested lists', () => {
- const input = '- List item 1\n 1. List item 1.1';
-
- const output = [
- {
- children: [
- {
- text: 'List item 1',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [
- {
- text: 'List item 1.1',
- },
- ],
- indent: 2,
- listStyleType: 'disc',
- type: 'p',
- },
- ];
-
- expect(deserializeMd(editor, input)).toEqual(output);
- });
-
- it('should deserialize an empty list item', () => {
- const input = '* Line 1\n*';
-
- const output = [
- {
- children: [
- {
- text: 'Line 1',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [{ text: '' }],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- ];
-
- expect(deserializeMd(editor, input)).toEqual(output);
- });
-
- it('should deserialize list with indented block element', () => {
- const input = `
-- 1
-- 2
- - 2.1
- \`\`\`
- 2.2 code
- \`\`\`
-`.trim();
- const output = [
- {
- children: [
- {
- text: '1',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [
- {
- text: '2',
- },
- ],
- indent: 1,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [
- {
- text: '2.1',
- },
- ],
- indent: 2,
- listStyleType: 'disc',
- type: 'p',
- },
- {
- children: [
- {
- children: [
- {
- text: '2.2 code',
- },
- ],
- type: 'code_line',
- },
- ],
- indent: 2,
- type: 'code_block',
- },
- ];
-
- expect(deserializeMd(editor, input)).toEqual(output);
- });
-
it('should deserialize a table', () => {
const input = `
| Left columns | Right columns |
@@ -736,3 +563,40 @@ describe('when splitLineBreaks is enabled', () => {
expect(deserializeMd(editor, input)).toEqual(output);
});
});
+
+describe('deserializeMd options', () => {
+ const editor = createSlateEditor({
+ plugins: [MarkdownPlugin],
+ });
+
+ describe('when memoize is true', () => {
+ it('should add _memo property to elements', () => {
+ const input = '# Heading\n> Quote\n```\nCode\n```';
+
+ const output = [
+ {
+ _memo: '# Heading',
+ children: [{ text: 'Heading' }],
+ type: 'h1',
+ },
+ {
+ _memo: '> Quote',
+ children: [{ text: 'Quote' }],
+ type: 'blockquote',
+ },
+ {
+ _memo: '```\nCode\n```',
+ children: [
+ {
+ children: [{ text: 'Code' }],
+ type: 'code_line',
+ },
+ ],
+ type: 'code_block',
+ },
+ ];
+
+ expect(deserializeMd(editor, input, { memoize: true })).toEqual(output);
+ });
+ });
+});
diff --git a/packages/markdown/src/lib/deserializer/utils/deserializeMd.ts b/packages/markdown/src/lib/deserializer/utils/deserializeMd.ts
index 3bc566b7b1..c1bf1b8cc4 100644
--- a/packages/markdown/src/lib/deserializer/utils/deserializeMd.ts
+++ b/packages/markdown/src/lib/deserializer/utils/deserializeMd.ts
@@ -11,16 +11,25 @@ import {
type RemarkTextRules,
remarkPlugin,
} from '../../remark-slate';
+import {
+ type ParseMarkdownBlocksOptions,
+ parseMarkdownBlocks,
+} from './parseMarkdownBlocks';
+
+export type DeserializeMdOptions = {
+ /** Whether to add _memo property to elements */
+ memoize?: boolean;
+ /** Options for the token parser */
+ parser?: ParseMarkdownBlocksOptions;
+ /** A function that allows you to modify the markdown processor. */
+ processor?: (processor: Processor) => Processor;
+};
/** Deserialize content from Markdown format to Slate format. */
export const deserializeMd = (
editor: SlateEditor,
data: string,
- {
- processor,
- }: {
- processor?: (processor: Processor) => Processor;
- } = {}
+ { memoize, parser, processor }: DeserializeMdOptions = {}
) => {
const elementRules: RemarkElementRules = {};
const textRules: RemarkTextRules = {};
@@ -36,17 +45,33 @@ export const deserializeMd = (
tree = processor(tree);
}
- tree = tree
- .use(remarkGfm)
- .use(remarkPlugin, {
- editor,
- elementRules,
- indentList: options.indentList,
- textRules,
- } as unknown as RemarkPluginOptions)
- .processSync(data);
+ tree = tree.use(remarkGfm).use(remarkPlugin, {
+ editor,
+ elementRules,
+ indentList: options.indentList,
+ textRules,
+ } as unknown as RemarkPluginOptions);
+
+ if (memoize) {
+ return parseMarkdownBlocks(data, parser).flatMap((token) => {
+ if (token.type === 'space') {
+ return {
+ ...editor.api.create.block(),
+ _memo: token.raw,
+ };
+ }
+
+ // TODO: split list items
+ return tree.processSync(token.raw).result.map((result: any) => {
+ return {
+ _memo: token.raw,
+ ...result,
+ };
+ });
+ });
+ }
- return tree.result;
+ return tree.processSync(data).result;
};
// TODO: Collect rules from plugins
diff --git a/packages/markdown/src/lib/deserializer/utils/deserializeMdList.spec.tsx b/packages/markdown/src/lib/deserializer/utils/deserializeMdList.spec.tsx
new file mode 100644
index 0000000000..a539151422
--- /dev/null
+++ b/packages/markdown/src/lib/deserializer/utils/deserializeMdList.spec.tsx
@@ -0,0 +1,224 @@
+/** @jsx jsxt */
+
+import { createSlateEditor } from '@udecode/plate';
+import { jsxt } from '@udecode/plate-test-utils';
+
+import { MarkdownPlugin } from '../../MarkdownPlugin';
+import { deserializeMd } from './deserializeMd';
+
+jsxt;
+
+describe('deserializeMdIndentList - comprehensive coverage', () => {
+ const editor = createSlateEditor({
+ plugins: [MarkdownPlugin.configure({ options: { indentList: true } })],
+ });
+
+ it('should deserialize a single Markdown string containing all list edge cases', () => {
+ /**
+ * Explanation of this Markdown:
+ *
+ * 1. Ordered list (starts at 1), 2 items
+ * 2. Blank line
+ * 3. Ordered list with custom start=3
+ * 4. Blank line
+ * 5. Mixed bullet -> sub-bullet -> sub-ordered
+ * 6. Blank line
+ * 7. A bullet item with an indented blockquote
+ * 8. Blank line
+ * 9. Star bullet item + an empty item + multiple blank lines
+ * 10. Another bullet list with code fence inside a sub-bullet
+ * 11. Deeply nested ordered list (3 levels) + bullet sibling
+ */
+ const input = `
+1. Item A
+2. Item B
+
+3. Custom start item
+4. Another item
+
+- Bullet outer
+ - Nested bullet
+ 1. Nested ordered
+
+- A bullet with a blockquote:
+ > This is inside blockquote
+ > And so on
+
+* Star bullet
+*
+
+- Some item
+
+- Another bullet
+ - Sub bullet
+ \`\`\`
+ console.info("code fence");
+ \`\`\`
+
+1. a
+ 1. b
+ 1. c
+ - sibling bullet
+`.trim();
+
+ const output = [
+ // 1) Ordered list: #1, #2
+ {
+ children: [{ text: 'Item A' }],
+ indent: 1,
+ listStart: 1,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'Item B' }],
+ indent: 1,
+ listStart: 2,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+
+ // 2) Blank line
+
+ // 3) Ordered list with custom start=3
+ {
+ children: [{ text: 'Custom start item' }],
+ indent: 1,
+ listStart: 3,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'Another item' }],
+ indent: 1,
+ listStart: 4,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+
+ // 4) Blank line
+
+ // 5) Mixed bullet -> sub-bullet -> sub-ordered
+ {
+ children: [{ text: 'Bullet outer' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'Nested bullet' }],
+ indent: 2,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'Nested ordered' }],
+ indent: 3,
+ listStart: 1,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+
+ // 6) Blank line
+
+ // 7) Bullet item with indented blockquote
+ {
+ children: [{ text: 'A bullet with a blockquote:' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ // The blockquote lines become paragraphs at indent + 1 (if your parser merges them),
+ // or in some implementations, they might remain at indent 1. Adapt if needed.
+ // If your logic doesn't treat blockquotes as separate paragraphs inside the list,
+ // you'll see them in a single paragraph. Tweak as needed.
+ {
+ children: [{ text: 'This is inside blockquote\nAnd so on' }],
+ // Might become indent: 2, or remain indent: 1, depending on how your parser merges them.
+ // We'll guess indent: 2 for demonstration.
+ indent: 2,
+ type: 'blockquote',
+ },
+
+ // 8) Blank line
+
+ // 9) Star bullet item + empty item
+ {
+ children: [{ text: 'Star bullet' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ {
+ children: [{ text: '' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ // Extra blank lines produce no tokens
+
+ {
+ children: [{ text: 'Some item' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+
+ // 10) Another bullet with sub bullet + code fence
+ {
+ children: [{ text: 'Another bullet' }],
+ indent: 1,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'Sub bullet' }],
+ indent: 2,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ {
+ children: [
+ {
+ children: [{ text: 'console.info("code fence");' }],
+ type: 'code_line',
+ },
+ ],
+ indent: 3,
+ type: 'code_block',
+ },
+
+ // 11) Deeply nested ordered list
+ {
+ children: [{ text: 'a' }],
+ indent: 1,
+ listStart: 1,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'b' }],
+ indent: 2,
+ listStart: 1,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+ {
+ children: [{ text: 'c' }],
+ indent: 3,
+ listStart: 1,
+ listStyleType: 'decimal',
+ type: 'p',
+ },
+ // followed by sibling bullet at indent 2
+ {
+ children: [{ text: 'sibling bullet' }],
+ indent: 2,
+ listStyleType: 'disc',
+ type: 'p',
+ },
+ ];
+
+ expect(deserializeMd(editor, input)).toEqual(output);
+ });
+});
diff --git a/packages/markdown/src/lib/deserializer/utils/index.ts b/packages/markdown/src/lib/deserializer/utils/index.ts
index 09aafa8135..40f7d7f481 100644
--- a/packages/markdown/src/lib/deserializer/utils/index.ts
+++ b/packages/markdown/src/lib/deserializer/utils/index.ts
@@ -5,4 +5,5 @@
export * from './deserializeInlineMd';
export * from './deserializeMd';
export * from './filterBreakLines';
+export * from './parseMarkdownBlocks';
export * from './stripMarkdown';
diff --git a/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.spec.ts b/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.spec.ts
new file mode 100644
index 0000000000..3cc1d60f11
--- /dev/null
+++ b/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.spec.ts
@@ -0,0 +1,117 @@
+import { parseMarkdownBlocks } from './parseMarkdownBlocks';
+
+describe('parseMarkdownBlocks', () => {
+ it('should parse markdown content into tokens', () => {
+ const input = '# Heading\n \nParagraph';
+
+ const output = [
+ {
+ depth: 1,
+ raw: '# Heading',
+ text: 'Heading',
+ tokens: [
+ {
+ escaped: false,
+ raw: 'Heading',
+ text: 'Heading',
+ type: 'text',
+ },
+ ],
+ type: 'heading',
+ },
+ {
+ raw: 'Paragraph',
+ text: 'Paragraph',
+ tokens: [
+ {
+ escaped: false,
+ raw: 'Paragraph',
+ text: 'Paragraph',
+ type: 'text',
+ },
+ ],
+ type: 'paragraph',
+ },
+ ];
+
+ const tokens = parseMarkdownBlocks(input);
+
+ expect(tokens).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ depth: 1,
+ raw: '# Heading',
+ text: 'Heading',
+ type: 'heading',
+ }),
+ expect.objectContaining({
+ raw: 'Paragraph',
+ text: 'Paragraph',
+ type: 'paragraph',
+ }),
+ ])
+ );
+ });
+
+ it('should not filter tokens when exclude is empty', () => {
+ const input = `# Heading\n \nParagraph`;
+
+ const tokensWithSpace = parseMarkdownBlocks(input, { exclude: [] });
+ const tokensWithoutSpace = parseMarkdownBlocks(input);
+
+ expect(tokensWithSpace.length).toBeGreaterThan(tokensWithoutSpace.length);
+ expect(tokensWithSpace).toEqual(
+ expect.arrayContaining([expect.objectContaining({ type: 'space' })])
+ );
+ });
+
+ it('should filter multiple token types', () => {
+ const input = '# Heading\n\n---\n\nParagraph';
+ const tokens = parseMarkdownBlocks(input, {
+ exclude: ['space', 'hr'],
+ });
+
+ expect(tokens).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ type: 'heading' }),
+ expect.objectContaining({ type: 'paragraph' }),
+ ])
+ );
+
+ expect(tokens).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ type: 'space' }),
+ expect.objectContaining({ type: 'hr' }),
+ ])
+ );
+ });
+
+ it('should not trim content when trim is false', () => {
+ const input = '# Heading \n \n';
+
+ const tokens = parseMarkdownBlocks(input, { exclude: [], trim: false });
+
+ expect(tokens[1]).toEqual(
+ expect.objectContaining({
+ raw: ' \n',
+ type: 'space',
+ })
+ );
+ });
+
+ it('should trim content by default', () => {
+ const input = '# Heading \n';
+
+ const tokens = parseMarkdownBlocks(input);
+
+ expect(tokens).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ raw: '# Heading',
+ text: 'Heading',
+ type: 'heading',
+ }),
+ ])
+ );
+ });
+});
diff --git a/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.ts b/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.ts
new file mode 100644
index 0000000000..c5a230faae
--- /dev/null
+++ b/packages/markdown/src/lib/deserializer/utils/parseMarkdownBlocks.ts
@@ -0,0 +1,37 @@
+import type { Token } from 'marked';
+
+import { marked } from 'marked';
+
+export type ParseMarkdownBlocksOptions = {
+ /**
+ * Token types to exclude from the output.
+ *
+ * @default ['space']
+ */
+ exclude?: string[];
+ /**
+ * Whether to trim the content.
+ *
+ * @default true
+ */
+ trim?: boolean;
+};
+
+export const parseMarkdownBlocks = (
+ content: string,
+ { exclude = ['space'], trim = true }: ParseMarkdownBlocksOptions = {}
+): Token[] => {
+ let tokens = [...marked.lexer(content)];
+
+ if (exclude.length > 0) {
+ tokens = tokens.filter((token) => !exclude.includes(token.type));
+ }
+ if (trim) {
+ tokens = tokens.map((token) => ({
+ ...token,
+ raw: token.raw.trimEnd(),
+ }));
+ }
+
+ return tokens;
+};
diff --git a/packages/markdown/src/lib/remark-slate/remarkDefaultElementRules.ts b/packages/markdown/src/lib/remark-slate/remarkDefaultElementRules.ts
index c9d5c6933c..94a45a930c 100644
--- a/packages/markdown/src/lib/remark-slate/remarkDefaultElementRules.ts
+++ b/packages/markdown/src/lib/remark-slate/remarkDefaultElementRules.ts
@@ -1,4 +1,9 @@
-import type { Descendant, TElement, TText } from '@udecode/plate';
+import {
+ type Descendant,
+ type TElement,
+ type TText,
+ BaseParagraphPlugin,
+} from '@udecode/plate';
import type { MdastNode, RemarkElementRules } from './types';
@@ -28,14 +33,21 @@ export const remarkDefaultElementRules: RemarkElementRules = {
},
},
code: {
- transform: (node, options) => ({
- children: (node.value || '').split('\n').map((line) => ({
- children: [{ text: line } as TText],
- type: options.editor.getType({ key: 'code_line' }),
- })),
- lang: node.lang ?? undefined,
- type: options.editor.getType({ key: 'code_block' }),
- }),
+ transform: (node, options) => {
+ const codeblock: TElement = {
+ children: (node.value || '').split('\n').map((line) => ({
+ children: [{ text: line } as TText],
+ type: options.editor.getType({ key: 'code_line' }),
+ })),
+ type: options.editor.getType({ key: 'code_block' }),
+ };
+
+ if (node.lang) {
+ codeblock.lang = node.lang;
+ }
+
+ return codeblock;
+ },
},
heading: {
transform: (node, options) => {
@@ -71,71 +83,81 @@ export const remarkDefaultElementRules: RemarkElementRules = {
},
list: {
transform: (node, options) => {
- if (options.indentList) {
- const listStyleType = node.ordered ? 'decimal' : 'disc';
-
- const parseListItems = (
- _node: MdastNode,
- listItems: TElement[] = [],
- indent = 1
- ) => {
- _node.children?.forEach((listItem, index) => {
- if (!listItem.children) {
- listItems.push({
- children: remarkTransformElementChildren(listItem, options),
- type: options.editor.getType({ key: 'p' }),
- });
-
- return listItems;
- }
+ if (!options.indentList) {
+ return {
+ children: remarkTransformElementChildren(node, options),
+ type: options.editor.getType({ key: node.ordered ? 'ol' : 'ul' }),
+ };
+ }
- const [paragraph, ...subLists] = listItem.children;
+ const parseListItems = (
+ listNode: MdastNode,
+ listItems: TElement[] = [],
+ indent = 1,
+ startIndex = 1
+ ) => {
+ // Is this list bullet or ordered?
+ const isOrdered = !!listNode.ordered;
+ const listStyleType = isOrdered ? 'decimal' : 'disc';
+
+ listNode.children?.forEach((listItem, index) => {
+ if (listItem.type !== 'listItem') return;
+ if (!listItem.children) {
+ listItems.push({
+ children: remarkTransformElementChildren(listItem, options),
+ type: options.editor.getType(BaseParagraphPlugin),
+ });
- const transformedListItem: TElement = {
- children: remarkTransformElementChildren(
- paragraph || '',
- options
- ),
- indent,
- listStyleType,
- type: options.editor.getType({ key: 'p' }),
- };
+ return listItems;
+ }
- if (node.ordered) {
- transformedListItem.listStart = index + 1;
- }
+ // Each list item can have a "paragraph" + sub-lists
+ const [paragraph, ...subLists] = listItem.children;
+
+ const transformedListItem: TElement = {
+ children: remarkTransformElementChildren(paragraph || '', options),
+ indent,
+ listStyleType,
+ type: options.editor.getType(BaseParagraphPlugin),
+ };
- listItems.push(transformedListItem);
+ // Only set listStart if *this* list is ordered
+ if (isOrdered) {
+ transformedListItem.listStart = startIndex + index;
+ }
- subLists.forEach((subList) => {
- if (subList.type === 'list') {
- parseListItems(subList, listItems, indent + 1);
+ listItems.push(transformedListItem);
+
+ // Process sub-lists (which may differ: bullet vs. ordered)
+ subLists.forEach((subList) => {
+ if (subList.type === 'list') {
+ // For a sub-list, we read its .ordered (could differ from parent)
+ const subListStart = (subList as any).start || 1;
+ parseListItems(subList, listItems, indent + 1, subListStart);
+ } else {
+ // If this child is not "list", transform normally
+ const result = remarkTransformNode(subList, options) as
+ | TElement
+ | TElement[];
+
+ if (Array.isArray(result)) {
+ listItems.push(
+ ...result.map((v) => ({ ...v, indent: indent + 1 }))
+ );
} else {
- const result = remarkTransformNode(subList, options) as
- | TElement
- | TElement[];
-
- if (Array.isArray(result)) {
- listItems.push(
- ...result.map((v) => ({ ...v, indent: indent + 1 }))
- );
- } else {
- listItems.push({ ...result, indent: indent + 1 });
- }
+ listItems.push({ ...result, indent: indent + 1 });
}
- });
+ }
});
+ });
- return listItems;
- };
+ return listItems;
+ };
- return parseListItems(node);
- }
+ // Use start attribute if present on the top-level list
+ const startIndex = (node as any).start || 1;
- return {
- children: remarkTransformElementChildren(node, options),
- type: options.editor.getType({ key: node.ordered ? 'ol' : 'ul' }),
- };
+ return parseListItems(node, [], 1, startIndex);
},
},
listItem: {
@@ -145,7 +167,7 @@ export const remarkDefaultElementRules: RemarkElementRules = {
({
...child,
type:
- child.type === options.editor.getType({ key: 'p' })
+ child.type === options.editor.getType(BaseParagraphPlugin)
? options.editor.getType({ key: 'lic' })
: child.type,
}) as Descendant
@@ -160,7 +182,7 @@ export const remarkDefaultElementRules: RemarkElementRules = {
const children = remarkTransformElementChildren(node, options);
- const paragraphType = options.editor.getType({ key: 'p' });
+ const paragraphType = options.editor.getType(BaseParagraphPlugin);
const splitBlockTypes = new Set([options.editor.getType({ key: 'img' })]);
const elements: TElement[] = [];
@@ -244,7 +266,7 @@ export const remarkDefaultElementRules: RemarkElementRules = {
if (!child.type) {
return {
children: [child],
- type: options.editor.getType({ key: 'p' }),
+ type: options.editor.getType(BaseParagraphPlugin),
};
}
diff --git a/packages/markdown/src/lib/serializer/__snapshots__/serializeMd.spec.tsx.snap b/packages/markdown/src/lib/serializer/__snapshots__/serializeMd.spec.tsx.snap
index a0f6ed1daa..b67ad2b55c 100644
--- a/packages/markdown/src/lib/serializer/__snapshots__/serializeMd.spec.tsx.snap
+++ b/packages/markdown/src/lib/serializer/__snapshots__/serializeMd.spec.tsx.snap
@@ -31,11 +31,11 @@ Bullet lists:
There's also support 3 levels of headers, hyperlinks, superscript, and more.
- Indent list 1
- - Indent list 1.1
- - Indent list 1.1.1
- - Indent list 1.2
+ - Indent list 1.1
+ - Indent list 1.1.1
+ - Indent list 1.2
1. Indent ol 1
- 1. Indent ol 1.1
+ 1. Indent ol 1.1
2. Indent ol 2
Custom node @mentionmark"
diff --git a/packages/markdown/src/lib/serializer/defaultSerializeMdNodesOptions.ts b/packages/markdown/src/lib/serializer/defaultSerializeMdNodesOptions.ts
index 7283e5678f..c0fc686727 100644
--- a/packages/markdown/src/lib/serializer/defaultSerializeMdNodesOptions.ts
+++ b/packages/markdown/src/lib/serializer/defaultSerializeMdNodesOptions.ts
@@ -98,7 +98,7 @@ export const defaultSerializeMdNodesOptions: SerializeMdOptions['nodes'] = {
// Decrement indent for indent lists
const listDepth = node.indent ? node.indent - 1 : 0;
- pre += ' '.repeat(listDepth);
+ pre += ' '.repeat(listDepth);
const listStart = node.listStart ?? 1;
diff --git a/packages/reset-node/src/lib/BaseResetNodePlugin.ts b/packages/reset-node/src/lib/BaseResetNodePlugin.ts
index 9883a630b6..de68d81e63 100644
--- a/packages/reset-node/src/lib/BaseResetNodePlugin.ts
+++ b/packages/reset-node/src/lib/BaseResetNodePlugin.ts
@@ -3,6 +3,7 @@ import {
type TElement,
NodeApi,
PointApi,
+ RangeApi,
createTSlatePlugin,
} from '@udecode/plate';
@@ -53,16 +54,7 @@ export const BaseResetNodePlugin = createTSlatePlugin({
const { selection } = editor;
if (!selection) return;
-
- const start = editor.api.start([])!;
- const end = editor.api.end([])!;
-
- if (
- (PointApi.equals(selection.anchor, start) &&
- PointApi.equals(selection.focus, end)) ||
- (PointApi.equals(selection.focus, start) &&
- PointApi.equals(selection.anchor, end))
- ) {
+ if (RangeApi.equals(selection, editor.api.range([])!)) {
editor.tf.reset({
children: true,
select: true,
diff --git a/packages/selection/src/lib/getAboveDomNode.ts b/packages/selection/src/lib/getAboveDomNode.ts
index c08f6e8ac9..69dfe9980b 100644
--- a/packages/selection/src/lib/getAboveDomNode.ts
+++ b/packages/selection/src/lib/getAboveDomNode.ts
@@ -1,7 +1,7 @@
-export const getSelectedDomNode = (id: string) => {
+export const querySelectorSelectable = (id: string) => {
return document.querySelector(`.slate-selectable[data-block-id="${id}"]`);
};
-export const getAllSelectableDomNode = () => {
+export const querySelectorAllSelectable = () => {
return document.querySelectorAll(`.slate-selectable`);
};
diff --git a/packages/selection/src/react/BlockSelectionPlugin.tsx b/packages/selection/src/react/BlockSelectionPlugin.tsx
index 0b0d19c8f7..c2dbc3994c 100644
--- a/packages/selection/src/react/BlockSelectionPlugin.tsx
+++ b/packages/selection/src/react/BlockSelectionPlugin.tsx
@@ -1,18 +1,20 @@
import type { CSSProperties } from 'react';
import type React from 'react';
-import type { NodeEntry, PluginConfig, TElement } from '@udecode/plate';
+import type { NodeEntry, Path, PluginConfig, TElement } from '@udecode/plate';
import { bindFirst } from '@udecode/plate';
import { createTPlatePlugin } from '@udecode/plate/react';
import type { ChangedElements, PartialSelectionOptions } from '../internal';
-import { getAllSelectableDomNode, getSelectedDomNode } from '../lib';
+import { querySelectorAllSelectable, querySelectorSelectable } from '../lib';
import { extractSelectableIds } from '../lib/extractSelectableIds';
import { BlockMenuPlugin } from './BlockMenuPlugin';
import { BlockSelectionAfterEditable } from './components/BlockSelectionAfterEditable';
import { useBlockSelectable } from './hooks/useBlockSelectable';
+import { moveSelection } from './internal/transforms/moveSelection';
+import { shiftSelection } from './internal/transforms/shiftSelection';
import { onKeyDownSelection } from './onKeyDownSelection';
import { duplicateBlockSelectionNodes } from './transforms/duplicateBlockSelectionNodes';
import { insertBlocksAndSelect } from './transforms/insertBlocksAndSelect';
@@ -27,9 +29,12 @@ import {
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{
+ anchorId?: string | null;
areaOptions?: PartialSelectionOptions;
editorPaddingRight?: CSSProperties['width'];
enableContextMenu?: boolean;
+ /** Check if a block is selectable */
+ isSelectable?: (element: TElement, path: Path) => boolean;
isSelecting?: boolean;
isSelectionAreaVisible?: boolean;
rightSelectionAreaClassName?: string;
@@ -43,22 +48,37 @@ export type BlockSelectionConfig = PluginConfig<
>;
export type BlockSelectionSelectors = {
+ /** Check if a block is selected by id */
isSelected?: (id?: string) => boolean;
+ /** Check if any blocks are selected */
isSelectingSome?: () => boolean;
};
export type BlockSelectionApi = {
+ /** Select a block by id, with optional delay and clear options */
addSelectedRow: (
id: string,
options?: { clear?: boolean; delay?: number }
) => void;
+ /** Set selected block ids */
setSelectedIds: (
options: Partial & { ids?: string[] }
) => void;
+ /** Focus block selection – that differs from the editor focus */
focus: () => void;
+ /** Get selected blocks */
getNodes: () => NodeEntry[];
+ /** Check if a block is selectable. */
+ isSelectable: (element: TElement, path: Path) => boolean;
+ /** Arrow-based move selection */
+ moveSelection: (direction: 'down' | 'up') => void;
+ /** Reset selected block ids */
resetSelectedIds: () => void;
+ /** Select all selectable blocks */
selectedAll: () => void;
+ /** Shift-based expand/shrink selection */
+ shiftSelection: (direction: 'down' | 'up') => void;
+ /** Unselect all blocks */
unselect: () => void;
};
@@ -74,6 +94,7 @@ export const BlockSelectionPlugin = createTPlatePlugin({
},
},
options: {
+ anchorId: null,
areaOptions: {
features: {
singleTap: {
@@ -82,6 +103,7 @@ export const BlockSelectionPlugin = createTPlatePlugin({
},
},
enableContextMenu: false,
+ isSelectable: () => true,
isSelecting: false,
isSelectionAreaVisible: false,
selectedIds: new Set(),
@@ -126,13 +148,16 @@ export const BlockSelectionPlugin = createTPlatePlugin({
getNodes: () => {
const selectedIds = getOption('selectedIds');
- return [
- ...editor.api.nodes({
- at: [],
- match: (n) => selectedIds?.has(n.id),
- }),
- ];
+ return editor.api.blocks({
+ at: [],
+ match: (n) => !!n.id && selectedIds?.has(n.id),
+ });
},
+ isSelectable: (element, path) =>
+ !!element.id &&
+ editor.api.isBlock(element) &&
+ getOptions().isSelectable!(element, path),
+ moveSelection: bindFirst(moveSelection, editor),
resetSelectedIds: () => {
setOption('selectedIds', new Set());
},
@@ -158,6 +183,7 @@ export const BlockSelectionPlugin = createTPlatePlugin({
setOption('isSelecting', true);
},
+ shiftSelection: bindFirst(shiftSelection, editor),
unselect: () => {
setOption('selectedIds', new Set());
setOption('isSelecting', false);
@@ -168,7 +194,7 @@ export const BlockSelectionPlugin = createTPlatePlugin({
addSelectedRow: (id, options = {}) => {
const { clear = true, delay } = options;
- const element = getSelectedDomNode(id);
+ const element = querySelectorSelectable(id);
if (!element) return;
if (!getOptions().selectedIds!.has(id) && clear) {
@@ -191,7 +217,7 @@ export const BlockSelectionPlugin = createTPlatePlugin({
},
selectedAll: () => {
- const all = getAllSelectableDomNode();
+ const all = querySelectorAllSelectable();
setOption('selectedIds', new Set());
api.blockSelection.setSelectedIds({
@@ -201,12 +227,19 @@ export const BlockSelectionPlugin = createTPlatePlugin({
},
}))
.extendTransforms(({ editor }) => ({
+ /** Duplicate selected blocks */
duplicate: bindFirst(duplicateBlockSelectionNodes, editor),
+ /** Insert blocks and select */
insertBlocksAndSelect: bindFirst(insertBlocksAndSelect, editor),
+ /** Remove selected blocks */
removeNodes: bindFirst(removeBlockSelectionNodes, editor),
+ /** Select blocks */
select: bindFirst(selectBlockSelectionNodes, editor),
+ /** Set block indent */
setIndent: bindFirst(setBlockSelectionIndent, editor),
+ /** Set nodes on selected blocks */
setNodes: bindFirst(setBlockSelectionNodes, editor),
+ /** Set texts on selected blocks */
setTexts: bindFirst(setBlockSelectionTexts, editor),
}))
.overrideEditor(({ api, editor, getOptions, tf: { setSelection } }) => ({
diff --git a/packages/selection/src/react/components/BlockSelectionAfterEditable.tsx b/packages/selection/src/react/components/BlockSelectionAfterEditable.tsx
index c5d2480d58..89463b2ec8 100644
--- a/packages/selection/src/react/components/BlockSelectionAfterEditable.tsx
+++ b/packages/selection/src/react/components/BlockSelectionAfterEditable.tsx
@@ -21,6 +21,7 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
const editor = useEditorRef();
const { api, getOption, getOptions, setOption, useOption } =
useEditorPlugin({ key: 'blockSelection' });
+
const isSelecting = useOption('isSelecting');
const selectedIds = useOption('selectedIds');
@@ -38,6 +39,12 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
};
}, [setOption]);
+ React.useEffect(() => {
+ if (!isSelecting) {
+ setOption('anchorId', null);
+ }
+ }, [isSelecting, setOption]);
+
React.useEffect(() => {
if (isSelecting && inputRef.current) {
inputRef.current.focus({ preventScroll: true });
@@ -46,77 +53,101 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
}
}, [isSelecting]);
+ /** KeyDown logic */
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent) => {
const isReadonly = editor.api.isReadOnly();
getOptions().onKeyDownSelecting?.(e.nativeEvent);
- // selecting commands
if (!getOptions().isSelecting) return;
+ if (isHotkey('shift+up')(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ api.blockSelection.shiftSelection('up');
+
+ return;
+ }
+ if (isHotkey('shift+down')(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ api.blockSelection.shiftSelection('down');
+
+ return;
+ }
+ // ESC => unselect all
if (isHotkey('escape')(e)) {
api.blockSelection.unselect();
+
+ return;
}
+ // Undo/redo
if (isHotkey('mod+z')(e)) {
editor.undo();
selectInsertedBlocks(editor);
+
+ return;
}
if (isHotkey('mod+shift+z')(e)) {
editor.redo();
selectInsertedBlocks(editor);
+
+ return;
}
- // selecting some commands
+ // Only continue if we have "some" selection
if (!getOption('isSelectingSome')) return;
+ // Enter => focus first selected block
if (isHotkey('enter')(e)) {
- // get the first block in the selection
const entry = editor.api.node({
at: [],
- match: (n) => n.id && selectedIds!.has(n.id),
+ block: true,
+ match: (n) => !!n.id && selectedIds?.has(n.id),
});
if (entry) {
const [, path] = entry;
-
- // focus the end of that block
editor.tf.focus({ at: path, edge: 'end' });
e.preventDefault();
}
+
+ return;
}
+ // Backspace/Delete => remove selected blocks
if (isHotkey(['backspace', 'delete'])(e) && !isReadonly) {
- editor.tf.removeNodes({
- at: [],
- match: (n) => !!n.id && selectedIds!.has(n.id),
+ e.preventDefault();
+ editor.tf.withoutNormalizing(() => {
+ editor.tf.removeNodes({
+ at: [],
+ block: true,
+ match: (n) => !!n.id && selectedIds?.has(n.id),
+ });
+
+ if (editor.children.length === 0) {
+ editor.tf.focus();
+ }
});
+
+ return;
}
- // TODO: skip toggle child
+ // If SHIFT not pressed => arrow up/down sets new anchor
if (isHotkey('up')(e)) {
- const firstId = [...selectedIds!][0];
- const node = editor.api.node({
- at: [],
- match: (n) => n.id && n.id === firstId,
- });
- const prev = editor.api.previous({
- at: node?.[1],
- });
+ e.preventDefault();
+ e.stopPropagation();
+ api.blockSelection.moveSelection('up');
- const prevId = prev?.[0].id;
- api.blockSelection.addSelectedRow(prevId as string);
+ return;
}
if (isHotkey('down')(e)) {
- const lastId = [...selectedIds!].pop();
- const node = editor.api.node({
- at: [],
- match: (n) => n.id && n.id === lastId,
- });
- const next = editor.api.next({
- at: node?.[1],
- });
- const nextId = next?.[0].id;
- api.blockSelection.addSelectedRow(nextId as string);
+ e.preventDefault();
+ e.stopPropagation();
+ api.blockSelection.moveSelection('down');
+
+ return;
}
},
[editor, selectedIds, api, getOptions, getOption]
);
+ /** Handle copy / cut / paste in block selection */
const handleCopy = React.useCallback(
(e: React.ClipboardEvent) => {
e.preventDefault();
@@ -138,9 +169,8 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
if (!editor.api.isReadOnly()) {
editor.tf.removeNodes({
at: [],
- match: (n) => selectedIds!.has(n.id),
+ match: (n) => selectedIds?.has(n.id),
});
-
editor.tf.focus();
}
}
diff --git a/packages/selection/src/react/hooks/useBlockSelectable.ts b/packages/selection/src/react/hooks/useBlockSelectable.ts
index f63c7062af..982a00bc51 100644
--- a/packages/selection/src/react/hooks/useBlockSelectable.ts
+++ b/packages/selection/src/react/hooks/useBlockSelectable.ts
@@ -3,54 +3,60 @@ import type React from 'react';
import { type TElement, PathApi } from '@udecode/plate';
import { useEditorPlugin, useElement, usePath } from '@udecode/plate/react';
-import { BlockSelectionPlugin } from '../BlockSelectionPlugin';
+import type { BlockSelectionConfig } from '../BlockSelectionPlugin';
export const useBlockSelectable = () => {
const element = useElement();
const path = usePath();
const { api, editor, getOption, getOptions } =
- useEditorPlugin(BlockSelectionPlugin);
+ useEditorPlugin({
+ key: 'blockSelection',
+ });
const id = element?.id as string | undefined;
return {
- props: {
- className: 'slate-selectable',
- onContextMenu: (event: React.MouseEvent) => {
- if (!element) return;
-
- const { enableContextMenu } = getOptions();
-
- if (!enableContextMenu) return;
- if (editor.selection?.focus) {
- const nodeEntry = editor.api.above();
-
- if (nodeEntry && PathApi.isCommon(path, nodeEntry[1])) {
- const id = nodeEntry[0].id as string | undefined;
- const isSelected = getOption('isSelected', id);
- const isOpenAlways =
- (event.target as HTMLElement).dataset?.plateOpenContextMenu ===
- 'true';
-
- /**
- * When "block selected or is void or has openContextMenu props",
- * right click can always open the context menu.
- */
- if (
- !isSelected &&
- !editor.api.isVoid(nodeEntry[0]) &&
- !isOpenAlways
- ) {
- return event.stopPropagation();
+ props: api.blockSelection.isSelectable(element, path)
+ ? {
+ className: 'slate-selectable',
+ onContextMenu: (
+ event: React.MouseEvent
+ ) => {
+ if (!element) return;
+
+ const { enableContextMenu } = getOptions();
+
+ if (!enableContextMenu) return;
+ if (editor.selection?.focus) {
+ const nodeEntry = editor.api.above();
+
+ if (nodeEntry && PathApi.isCommon(path, nodeEntry[1])) {
+ const id = nodeEntry[0].id as string | undefined;
+ const isSelected = getOption('isSelected', id);
+ const isOpenAlways =
+ (event.target as HTMLElement).dataset
+ ?.plateOpenContextMenu === 'true';
+
+ /**
+ * When "block selected or is void or has openContextMenu
+ * props", right click can always open the context menu.
+ */
+ if (
+ !isSelected &&
+ !editor.api.isVoid(nodeEntry[0]) &&
+ !isOpenAlways
+ ) {
+ return event.stopPropagation();
+ }
+ }
}
- }
- }
- if (id) {
- api.blockSelection.addSelectedRow(id, {
- clear: !event?.shiftKey,
- });
+ if (id) {
+ api.blockSelection.addSelectedRow(id, {
+ clear: !event?.shiftKey,
+ });
+ }
+ },
}
- },
- },
+ : {},
};
};
diff --git a/packages/selection/src/react/hooks/useBlockSelectionNodes.ts b/packages/selection/src/react/hooks/useBlockSelectionNodes.ts
index 410710b76e..71f2c699d5 100644
--- a/packages/selection/src/react/hooks/useBlockSelectionNodes.ts
+++ b/packages/selection/src/react/hooks/useBlockSelectionNodes.ts
@@ -11,12 +11,10 @@ export function useBlockSelectionNodes() {
const selectedIds = useOption('selectedIds');
return useMemo(() => {
- return [
- ...editor.api.nodes({
- at: [],
- match: (n) => selectedIds?.has(n.id),
- }),
- ];
+ return editor.api.blocks({
+ at: [],
+ match: (n) => !!n.id && selectedIds?.has(n.id),
+ });
}, [editor, selectedIds]);
}
diff --git a/packages/selection/src/react/internal/transforms/moveSelection.spec.tsx b/packages/selection/src/react/internal/transforms/moveSelection.spec.tsx
new file mode 100644
index 0000000000..39dc608d76
--- /dev/null
+++ b/packages/selection/src/react/internal/transforms/moveSelection.spec.tsx
@@ -0,0 +1,435 @@
+/** @jsx jsxt */
+
+import type { PlateEditor } from '@udecode/plate/react';
+
+import { createPlateEditor } from '@udecode/plate/react';
+import { jsxt } from '@udecode/plate-test-utils';
+
+import * as domUtils from '../../../lib';
+import { BlockSelectionPlugin } from '../../BlockSelectionPlugin';
+import { moveSelection } from './moveSelection';
+
+jsxt;
+
+jest.mock('../../../lib', () => ({
+ ...jest.requireActual('../../../lib'),
+ querySelectorSelectable: (id: string) => ({
+ id,
+ dataset: { blockId: id },
+ }),
+}));
+
+describe('moveSelection', () => {
+ let editor: PlateEditor;
+
+ beforeEach(() => {
+ jest.spyOn(domUtils, 'querySelectorSelectable');
+
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'block1',
+ children: [{ text: 'Block One' }],
+ type: 'p',
+ },
+ {
+ id: 'block2',
+ children: [{ text: 'Block Two' }],
+ type: 'p',
+ },
+ {
+ id: 'block3',
+ children: [{ text: 'Block Three' }],
+ type: 'p',
+ },
+ ],
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('when pressing arrow down without shift', () => {
+ it('should set anchor to block below the bottom-most and select it alone', () => {
+ // Suppose block1, block2 selected
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1', 'block2'])
+ );
+ // anchor = block1 (arbitrary choice)
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ // Move selection DOWN => below bottom-most (which is block2) => block3
+ moveSelection(editor, 'down');
+
+ // Should now only have block3 in selection
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block3']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('block3');
+ });
+ });
+
+ describe('when pressing arrow up without shift', () => {
+ it('should set anchor to block above the top-most and select it alone', () => {
+ // Suppose block2, block3 selected, anchor is block3
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block2', 'block3'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block3');
+
+ // Move selection UP => above top-most (which is block2) => block1
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block1']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('block1');
+ });
+ });
+
+ describe('when only one block is selected', () => {
+ it('should do nothing if there is no block above/below', () => {
+ // Only block1 selected, anchor = block1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ // Move up => block1 is the top-most => no block above
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block1']);
+
+ // Move down twice => block2 => block3
+ moveSelection(editor, 'down'); // Now block3
+ moveSelection(editor, 'down'); // No block below block3 => do nothing
+
+ const selectedIds2 = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds2!)).toEqual(['block3']);
+ });
+ });
+
+ describe('when pressing arrow up with nested blocks', () => {
+ it('should select parent block if no previous sibling exists', () => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'parent1',
+ children: [
+ {
+ id: 'child1',
+ children: [{ text: 'Child One' }],
+ type: 'p',
+ },
+ {
+ id: 'child2',
+ children: [{ text: 'Child Two' }],
+ type: 'p',
+ },
+ ],
+ type: 'p',
+ },
+ ],
+ });
+
+ // Select child1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['child1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'child1');
+
+ // Move selection UP => no previous sibling => should select parent1
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['parent1']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('parent1');
+ });
+
+ it('should do nothing if at root level with no previous sibling', () => {
+ // Using the original test value
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ // Move up from the first block at root level
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block1']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('block1');
+ });
+ });
+
+ describe('when pressing arrow down with nested blocks', () => {
+ beforeEach(() => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'table1',
+ children: [
+ {
+ id: 'tr1',
+ children: [
+ {
+ id: 'td11',
+ children: [
+ {
+ id: 'p11',
+ children: [{ text: 'Cell 1-1' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ {
+ id: 'td12',
+ children: [
+ {
+ id: 'p12',
+ children: [{ text: 'Cell 1-2' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ ],
+ type: 'tr',
+ },
+ {
+ id: 'tr2',
+ children: [
+ {
+ id: 'td21',
+ children: [
+ {
+ id: 'p21',
+ children: [{ text: 'Cell 2-1' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ {
+ id: 'td22',
+ children: [
+ {
+ id: 'p22',
+ children: [{ text: 'Cell 2-2' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ ],
+ type: 'tr',
+ },
+ ],
+ type: 'table',
+ },
+ ],
+ });
+
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ // Only table and tr are selectable
+ return node.type === 'table' || node.type === 'tr';
+ });
+ });
+
+ it('should move from first tr to second tr in table', () => {
+ // Select tr1
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['tr1']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr1');
+
+ // Move down
+ moveSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['tr2']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('tr2');
+ });
+
+ it('should do nothing when at last tr', () => {
+ // Select tr2
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['tr2']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr2');
+
+ // Move down
+ moveSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['tr2']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('tr2');
+ });
+
+ it('should skip non-selectable td cells', () => {
+ // Select td11
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['td11']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'td11');
+
+ // Move down
+ moveSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['tr2']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('tr2');
+ });
+ });
+
+ describe('when pressing arrow up with complex nested blocks', () => {
+ beforeEach(() => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'block1',
+ children: [{ text: 'Block One' }],
+ type: 'p',
+ },
+ {
+ id: 'parent1',
+ children: [
+ {
+ id: 'child1',
+ children: [{ text: 'Child One' }],
+ type: 'p',
+ },
+ {
+ id: 'child2',
+ children: [{ text: 'Child Two' }],
+ type: 'p',
+ },
+ ],
+ type: 'div',
+ },
+ {
+ id: 'column_group1',
+ children: [
+ {
+ id: 'column1',
+ children: [
+ {
+ id: 'grandchild1',
+ children: [{ text: 'Grandchild One' }],
+ type: 'p',
+ },
+ ],
+ type: 'column',
+ },
+ {
+ id: 'column2',
+ children: [
+ {
+ id: 'grandchild2',
+ children: [{ text: 'Grandchild Two' }],
+ type: 'p',
+ },
+ ],
+ type: 'column',
+ },
+ ],
+ type: 'column_group',
+ },
+ ],
+ });
+
+ // For testing, let's skip columns
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ return node.type !== 'column';
+ });
+ });
+
+ it('should move to previous sibling when not first child', () => {
+ // Select child2
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['child2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'child2');
+
+ // Move up => should select child1
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['child1']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('child1');
+ });
+
+ it('should move to parents previous block if first child and skipping columns', () => {
+ // Select grandchild2
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['grandchild2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'grandchild2');
+
+ // Move up => should select grandchild1 (since columns are not selectable)
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['grandchild1']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('grandchild1');
+ });
+
+ it('should handle deeper nesting with non-selectable parents', () => {
+ // Make column_group1 not selectable as well
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ return node.type !== 'column' && node.type !== 'column_group';
+ });
+
+ // Select grandchild1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['grandchild1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'grandchild1');
+
+ // Move up => should skip column1 and column_group1, select child2
+ moveSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['child2']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('child2');
+ });
+ });
+});
diff --git a/packages/selection/src/react/internal/transforms/moveSelection.ts b/packages/selection/src/react/internal/transforms/moveSelection.ts
new file mode 100644
index 0000000000..5456d745ab
--- /dev/null
+++ b/packages/selection/src/react/internal/transforms/moveSelection.ts
@@ -0,0 +1,45 @@
+import type { TElement } from '@udecode/plate';
+
+import { type PlateEditor, getEditorPlugin } from '@udecode/plate/react';
+
+import { BlockSelectionPlugin } from '../../BlockSelectionPlugin';
+
+export const moveSelection = (
+ editor: PlateEditor,
+ direction: 'down' | 'up'
+) => {
+ const { api, setOption } = getEditorPlugin(editor, BlockSelectionPlugin);
+ const blocks = api.blockSelection.getNodes();
+
+ if (blocks.length === 0) return;
+ if (direction === 'up') {
+ const [, topPath] = blocks[0];
+
+ const prevEntry = editor.api.previous({
+ at: topPath,
+ from: 'parent',
+ match: api.blockSelection.isSelectable,
+ });
+
+ if (prevEntry) {
+ const [prevNode] = prevEntry;
+ setOption('anchorId', prevNode.id);
+ api.blockSelection.addSelectedRow(prevNode.id, { clear: true });
+ }
+ } else {
+ // direction === 'down'
+ const [, bottomPath] = blocks.at(-1)!;
+
+ const nextEntry = editor.api.next({
+ at: bottomPath,
+ from: 'child',
+ match: api.blockSelection.isSelectable,
+ });
+
+ if (nextEntry) {
+ const [nextNode] = nextEntry;
+ setOption('anchorId', nextNode.id);
+ api.blockSelection.addSelectedRow(nextNode.id, { clear: true });
+ }
+ }
+};
diff --git a/packages/selection/src/react/internal/transforms/shiftSelection.spec.tsx b/packages/selection/src/react/internal/transforms/shiftSelection.spec.tsx
new file mode 100644
index 0000000000..bbf7ac74a3
--- /dev/null
+++ b/packages/selection/src/react/internal/transforms/shiftSelection.spec.tsx
@@ -0,0 +1,503 @@
+/** @jsx jsxt */
+import type { PlateEditor } from '@udecode/plate/react';
+
+import { createPlateEditor } from '@udecode/plate/react';
+import { jsxt } from '@udecode/plate-test-utils';
+
+import { BlockSelectionPlugin } from '../../BlockSelectionPlugin';
+import { shiftSelection } from './shiftSelection';
+
+jsxt;
+
+describe('shiftSelection', () => {
+ let editor: PlateEditor;
+
+ describe('Flat structure', () => {
+ beforeEach(() => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'block1',
+ children: [{ text: 'Block One' }],
+ type: 'p',
+ },
+ {
+ id: 'block2',
+ children: [{ text: 'Block Two' }],
+ type: 'p',
+ },
+ {
+ id: 'block3',
+ children: [{ text: 'Block Three' }],
+ type: 'p',
+ },
+ ],
+ });
+ });
+
+ describe('when anchor is top-most and SHIFT+DOWN', () => {
+ it('should expand selection downward', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['block1', 'block2'].sort()
+ );
+ });
+ });
+
+ describe('when anchor is top-most and SHIFT+DOWN again', () => {
+ it('should expand further to block3', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1', 'block2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['block1', 'block2', 'block3'].sort()
+ );
+ });
+ });
+
+ describe('when anchor is NOT top-most and SHIFT+DOWN', () => {
+ it('should shrink from the top-most block', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1', 'block2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block2');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds!).sort()).toEqual(['block2'].sort());
+ });
+ });
+
+ describe('when anchor is bottom-most and SHIFT+UP', () => {
+ it('should expand selection upward', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block2', 'block3'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block3');
+
+ shiftSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['block1', 'block2', 'block3'].sort()
+ );
+ });
+ });
+
+ describe('when anchor is NOT bottom-most and SHIFT+UP', () => {
+ it('should shrink from bottom-most block', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1', 'block2', 'block3'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block1');
+
+ shiftSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['block1', 'block2'].sort()
+ );
+ });
+ });
+ });
+
+ describe('Nested structure', () => {
+ beforeEach(() => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'parent1',
+ children: [
+ {
+ id: 'child1',
+ children: [{ text: 'Child One' }],
+ type: 'p',
+ },
+ {
+ id: 'child2',
+ children: [{ text: 'Child Two' }],
+ type: 'p',
+ },
+ ],
+ type: 'div',
+ },
+ {
+ id: 'block3',
+ children: [{ text: 'Block Three' }],
+ type: 'p',
+ },
+ {
+ id: 'block4',
+ children: [{ text: 'Block Four' }],
+ type: 'p',
+ },
+ ],
+ });
+
+ // For testing skipping, let's say child2 is not selectable or something
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ // We'll skip if node.id === 'child2'
+ return node.id !== 'child2';
+ });
+ });
+
+ it('should expand down from parent1 to block3 if anchor is parent1 (top-most)', () => {
+ // parent1 selected, anchor=parent1, SHIFT+DOWN => expand to block3
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['parent1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'parent1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['parent1', 'block3']);
+ });
+
+ it('should shrink from parent1 if anchor is block3 (not top-most) SHIFT+DOWN', () => {
+ // parent1, block3 selected; anchor=block3 => top-most=parent1 => remove parent1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block3', 'parent1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block3');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block3']);
+ });
+
+ it('should expand up from block4 to block3 if anchor is block4 (bottom-most)', () => {
+ // block3, block4 selected; anchor=block4 => SHIFT+UP => expand to parent1
+ // Actually, let's do block3, block4 => anchor=block4 => SHIFT+UP => add parent1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block3', 'block4'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'block4');
+
+ shiftSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['child1', 'block3', 'block4'].sort()
+ );
+ });
+
+ it('should shrink from block4 if anchor is parent1 SHIFT+UP', () => {
+ // parent1, block3, block4 => anchor=parent1 => SHIFT+UP => remove block4
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block3', 'block4', 'parent1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'parent1');
+
+ shiftSelection(editor, 'up');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ // block4 should be removed from selection
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['parent1', 'block3'].sort()
+ );
+ });
+
+ it('should skip non-selectable child2 when expanding down from parent1 to block3', () => {
+ // We already set child2 as not selectable
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['parent1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'parent1');
+
+ // SHIFT+DOWN => next selectable after parent1 is block3, skipping child2
+ shiftSelection(editor, 'down');
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['parent1', 'block3'].sort()
+ );
+ });
+ });
+
+ describe('Complex columns or table-like structure', () => {
+ beforeEach(() => {
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ {
+ id: 'table1',
+ children: [
+ {
+ id: 'tr1',
+ children: [
+ {
+ id: 'td11',
+ children: [
+ {
+ id: 'p11',
+ children: [{ text: 'Cell 1-1' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ {
+ id: 'td12',
+ children: [
+ {
+ id: 'p12',
+ children: [{ text: 'Cell 1-2' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ ],
+ type: 'tr',
+ },
+ {
+ id: 'tr2',
+ children: [
+ {
+ id: 'td21',
+ children: [
+ {
+ id: 'p21',
+ children: [{ text: 'Cell 2-1' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ {
+ id: 'td22',
+ children: [
+ {
+ id: 'p22',
+ children: [{ text: 'Cell 2-2' }],
+ type: 'p',
+ },
+ ],
+ type: 'td',
+ },
+ ],
+ type: 'tr',
+ },
+ ],
+ type: 'table',
+ },
+ {
+ id: 'blockZ',
+ children: [{ text: 'Below Table' }],
+ type: 'p',
+ },
+ ],
+ });
+
+ // Let’s make only 'table' and 'tr' selectable
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ return node.type === 'table' || node.type === 'tr';
+ });
+ });
+
+ it('should NOT expand down from table1 => add tr1 if anchor=table1', () => {
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['table1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'table1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ // Should now have table1 + tr1
+ expect(Array.from(selectedIds!).sort()).toEqual(['table1'].sort());
+ });
+
+ it('should shrink from table1 if anchor=tr1 SHIFT+DOWN', () => {
+ // table1, tr1 => anchor=tr1 => top-most=table1 => remove table1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['table1', 'tr1'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['tr1']);
+ });
+
+ it('should expand up from tr1 => add table1 if anchor=tr1 is bottom-most - remove', () => {
+ // Suppose table1, tr1 are selected => anchor=tr1 => SHIFT+UP => expand up => add ???
+ // But let's do an easier test: if only tr1 is selected => anchor=tr1 => SHIFT+UP =>
+ // see if there's an above block to add? Actually, tr1 is the bottom-most if there's only 1 selected.
+ // This scenario is contrived, let's just keep it simple:
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['tr1']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr1');
+
+ shiftSelection(editor, 'up');
+
+ // We expect table1 included
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!).sort()).toEqual(['table1'].sort());
+ });
+
+ it('should expand down from tr1 => add tr2 if anchor=tr1 is top-most', () => {
+ // anchor=tr1 => SHIFT+DOWN => add tr2
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['tr1']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr1');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!).sort()).toEqual(['tr1', 'tr2'].sort());
+ });
+
+ it('should shrink from tr1 if anchor=tr2 SHIFT+DOWN', () => {
+ // anchor=tr2 => top-most=tr1 => remove tr1
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['tr1', 'tr2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr2');
+
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['tr2']);
+ });
+
+ it('should skip td / p nodes that are not selectable', () => {
+ // anchor=tr2 => SHIFT+DOWN => next would be blockZ skipping over child tds
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(['tr2']));
+ editor.setOption(BlockSelectionPlugin, 'anchorId', 'tr2');
+
+ // SHIFT+DOWN => tries to find next block after tr2 => blockZ is next top-level
+ shiftSelection(editor, 'down');
+
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ // Now includes blockZ only if blockZ is selectable.
+ // Since we only made 'table' or 'tr' selectable, blockZ might be skipped.
+ // For this test let's assume blockZ is also selectable => let's set isSelectable accordingly.
+ // We'll do that quickly:
+ editor.setOption(BlockSelectionPlugin, 'isSelectable', (node) => {
+ return (
+ node.type === 'table' || node.type === 'tr' || node.id === 'blockZ'
+ );
+ });
+ // Re-run shiftSelection to see if blockZ is included
+ shiftSelection(editor, 'down');
+
+ const newSelectedIds = editor.getOption(
+ BlockSelectionPlugin,
+ 'selectedIds'
+ );
+ expect(Array.from(newSelectedIds!).sort()).toEqual(
+ ['blockZ', 'tr2'].sort()
+ );
+ });
+ });
+
+ describe('Anchor defaults to top-most/bottom-most if not set', () => {
+ it('should set anchor to top-most for SHIFT+DOWN', () => {
+ // We have block1, block2.
+ // Let's select block2 only, no anchor set => SHIFT+DOWN => anchor=top-most => block2 => expand => block3.
+ editor = createPlateEditor({
+ plugins: [BlockSelectionPlugin],
+ value: [
+ { id: 'block1', children: [{ text: 'One' }], type: 'p' },
+ { id: 'block2', children: [{ text: 'Two' }], type: 'p' },
+ { id: 'block3', children: [{ text: 'Three' }], type: 'p' },
+ ],
+ });
+
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', null);
+
+ shiftSelection(editor, 'down');
+
+ // Now block2, block3 selected
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!).sort()).toEqual(
+ ['block2', 'block3'].sort()
+ );
+ // anchor is set to block2
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('block2');
+ });
+
+ it('should set anchor to bottom-most for SHIFT+UP', () => {
+ // block1, block2 => no anchor => SHIFT+UP => anchor=bottom-most => block2 => expand up => block3 if existed
+ editor.setOption(
+ BlockSelectionPlugin,
+ 'selectedIds',
+ new Set(['block1', 'block2'])
+ );
+ editor.setOption(BlockSelectionPlugin, 'anchorId', null);
+
+ shiftSelection(editor, 'up');
+
+ // Because we only have block1, block2, we can't go "up" further
+ const selectedIds = editor.getOption(BlockSelectionPlugin, 'selectedIds');
+ expect(Array.from(selectedIds!)).toEqual(['block1', 'block2']);
+
+ const anchorId = editor.getOption(BlockSelectionPlugin, 'anchorId');
+ expect(anchorId).toBe('block2');
+ });
+ });
+});
diff --git a/packages/selection/src/react/internal/transforms/shiftSelection.ts b/packages/selection/src/react/internal/transforms/shiftSelection.ts
new file mode 100644
index 0000000000..7cbb4bea0d
--- /dev/null
+++ b/packages/selection/src/react/internal/transforms/shiftSelection.ts
@@ -0,0 +1,126 @@
+import { type TElement, PathApi } from '@udecode/plate';
+import { type PlateEditor, getEditorPlugin } from '@udecode/plate/react';
+
+import { BlockSelectionPlugin } from '../../BlockSelectionPlugin';
+
+/**
+ * SHIFT-based expand-or-shrink selection.
+ *
+ * SHIFT + DOWN:
+ *
+ * - If anchor is top-most in the selection => expand down (add block below
+ * bottom-most).
+ * - Otherwise => shrink from top-most (unless top-most is the anchor).
+ *
+ * SHIFT + UP:
+ *
+ * - If anchor is bottom-most => expand up (add block above top-most).
+ * - Otherwise => shrink from bottom-most (unless bottom-most is the anchor).
+ */
+export const shiftSelection = (
+ editor: PlateEditor,
+ direction: 'down' | 'up'
+) => {
+ const { api, getOption, getOptions, setOption } = getEditorPlugin(
+ editor,
+ BlockSelectionPlugin
+ );
+
+ const blocks = api.blockSelection.getNodes();
+
+ if (blocks.length === 0) return;
+
+ // Identify the top-most and bottom-most blocks in the current selection.
+ const [topNode, topPath] = blocks[0];
+ const [bottomNode, bottomPath] = blocks.at(-1)!;
+ let anchorId = getOptions().anchorId;
+
+ // If no anchor is set, default to bottom-most if SHIFT+UP, else top-most if SHIFT+DOWN.
+ if (!anchorId) {
+ anchorId = direction === 'up' ? bottomNode.id : topNode.id;
+ setOption('anchorId', anchorId);
+ }
+
+ // Find the anchor block within the current selection array.
+ const anchorIndex = blocks.findIndex(([node]) => node.id === anchorId);
+
+ if (anchorIndex < 0) {
+ // If anchor not found in the current selection, fallback:
+ setOption('anchorId', bottomNode.id);
+
+ return;
+ }
+
+ const anchorIsTop = anchorIndex === 0;
+ const anchorIsBottom = anchorIndex === blocks.length - 1;
+
+ const newSelected = new Set(getOption('selectedIds'));
+
+ if (direction === 'down') {
+ // SHIFT+DOWN
+ if (anchorIsTop) {
+ // Expand down => add block below the bottom-most
+ const belowEntry = editor.api.next({
+ at: bottomPath,
+ match: (n, p) =>
+ api.blockSelection.isSelectable(n as any, p) &&
+ !PathApi.isAncestor(p, bottomPath),
+ mode: 'highest',
+ });
+
+ if (!belowEntry) return;
+
+ const [belowNode] = belowEntry;
+
+ newSelected.add(belowNode.id as string);
+ } else {
+ // anchor is not top => shrink from top-most
+ // remove the top-most from selection unless it's the anchor.
+ if (topNode.id && topNode.id !== anchorId) {
+ newSelected.delete(topNode.id);
+ }
+ }
+ } else {
+ // SHIFT+UP
+ if (anchorIsBottom) {
+ // Expand up => add block above the top-most
+ const aboveEntry = editor.api.previous({
+ at: topPath,
+ from: 'parent',
+ match: api.blockSelection.isSelectable,
+ });
+
+ if (!aboveEntry) return;
+
+ const [aboveNode, abovePath] = aboveEntry;
+
+ if (PathApi.isAncestor(abovePath, topPath)) {
+ newSelected.forEach((id) => {
+ const entry = editor.api.node({ id, at: abovePath });
+
+ if (!entry) return;
+ if (PathApi.isDescendant(entry[1], abovePath)) {
+ newSelected.delete(id);
+
+ if (id === anchorId) {
+ anchorId = aboveNode.id;
+ setOption('anchorId', anchorId);
+ }
+ }
+ });
+ }
+
+ newSelected.add(aboveNode.id);
+ } else {
+ // anchor is not bottom => shrink from bottom-most
+ if (bottomNode.id && bottomNode.id !== anchorId) {
+ newSelected.delete(bottomNode.id);
+ }
+ }
+ }
+
+ // Always ensure the anchor remains selected
+ newSelected.add(anchorId!);
+
+ setOption('selectedIds', newSelected);
+};
diff --git a/packages/selection/src/react/transforms/insertBlocksAndSelect.ts b/packages/selection/src/react/transforms/insertBlocksAndSelect.ts
index 6c2f1a40d9..519daca1ff 100644
--- a/packages/selection/src/react/transforms/insertBlocksAndSelect.ts
+++ b/packages/selection/src/react/transforms/insertBlocksAndSelect.ts
@@ -1,6 +1,6 @@
import type { PlateEditor } from '@udecode/plate/react';
-import { type Path, type TElement, nanoid } from '@udecode/plate';
+import { type Path, type TElement, NodeApi, PathApi } from '@udecode/plate';
import { BlockSelectionPlugin } from '../BlockSelectionPlugin';
@@ -9,19 +9,22 @@ export const insertBlocksAndSelect = (
nodes: TElement[],
{ at }: { at: Path }
) => {
- const ids: string[] = [];
+ editor.tf.insertNodes(nodes, { at });
- nodes.forEach((node) => {
- const id = nanoid();
- ids.push(id);
- node.id = id;
- });
+ const insertedNodes = [NodeApi.get(editor, at)!];
- editor.tf.insertNodes(nodes, { at: at });
+ let count = 1;
+
+ while (count < nodes.length) {
+ at = PathApi.next(at);
+ const nextNode = NodeApi.get(editor, at)!;
+ insertedNodes.push(nextNode);
+ count++;
+ }
setTimeout(() => {
- editor
- .getApi(BlockSelectionPlugin)
- .blockSelection.setSelectedIds({ ids } as any);
+ editor.getApi(BlockSelectionPlugin).blockSelection.setSelectedIds({
+ ids: insertedNodes.map((n) => n.id),
+ } as any);
}, 0);
};
diff --git a/packages/selection/src/react/transforms/removeBlockSelectionNodes.ts b/packages/selection/src/react/transforms/removeBlockSelectionNodes.ts
index acf543d498..296d810f87 100644
--- a/packages/selection/src/react/transforms/removeBlockSelectionNodes.ts
+++ b/packages/selection/src/react/transforms/removeBlockSelectionNodes.ts
@@ -9,6 +9,7 @@ export const removeBlockSelectionNodes = (editor: SlateEditor) => {
editor.tf.removeNodes({
at: [],
- match: (n: any) => n.id && selectedIds.has((n as any).id),
+ block: true,
+ match: (n: any) => !!n.id && selectedIds.has((n as any).id),
});
};
diff --git a/packages/slate/src/create-editor.ts b/packages/slate/src/create-editor.ts
index 5b5845f06b..500032e92a 100644
--- a/packages/slate/src/create-editor.ts
+++ b/packages/slate/src/create-editor.ts
@@ -60,7 +60,6 @@ import { getFragment } from './internal/editor/getFragment';
import { getLeafNode } from './internal/editor/getLeafNode';
import { getLevels } from './internal/editor/getLevels';
import { getMarks } from './internal/editor/getMarks';
-import { getNextNode } from './internal/editor/getNextNode';
import { getPathRefs } from './internal/editor/getPathRefs';
import { getPoint } from './internal/editor/getPoint';
import { getPointAfter } from './internal/editor/getPointAfter';
@@ -83,6 +82,7 @@ import { isEmpty } from './internal/editor/isEmpty';
import { isEndPoint } from './internal/editor/isEndPoint';
import { isStartPoint } from './internal/editor/isStartPoint';
import { last } from './internal/editor/last';
+import { next } from './internal/editor/next';
import { nodes } from './internal/editor/nodes';
import { normalizeEditor } from './internal/editor/normalizeEditor';
import { parent } from './internal/editor/parent';
@@ -97,6 +97,7 @@ import { blocks } from './internal/editor-extension/editor-blocks';
import { descendant } from './internal/editor-extension/editor-descendant';
import { mark } from './internal/editor-extension/editor-mark';
import { hasMark } from './internal/editor-extension/hasMark';
+import { isSelected } from './internal/editor-extension/is-selected';
import { isAt } from './internal/editor-extension/isAt';
import { isEditorEnd } from './internal/editor-extension/isEditorEnd';
import { isText } from './internal/editor-extension/isText';
@@ -221,7 +222,7 @@ export const createEditor = ({
mergeNodes: bindFirst(mergeNodes, editor),
move: bindFirst(moveSelection, editor),
moveNodes: bindFirst(moveNodes, editor),
- next: bindFirst(getNextNode, editor),
+ next: bindFirst(next, editor),
node: bindFirst(node, editor),
nodes: bindFirst(nodes, editor),
normalize: bindFirst(normalizeEditor, editor),
@@ -301,6 +302,7 @@ export const createEditor = ({
isMerging: bindFirst(HistoryApi.isMerging, editor as any) as any,
isReadOnly: bindFirst(isReadOnly, editor),
isSaving: bindFirst(HistoryApi.isSaving, editor as any) as any,
+ isSelected: bindFirst(isSelected, editor),
isSplittingOnce: bindFirst(HistoryApi.isSplittingOnce, editor as any),
isTargetInsideNonReadonlyVoid: bindFirst(
isTargetInsideNonReadonlyVoid,
diff --git a/packages/slate/src/interfaces/editor/editor-api.ts b/packages/slate/src/interfaces/editor/editor-api.ts
index ef68199666..48a8d06d83 100644
--- a/packages/slate/src/interfaces/editor/editor-api.ts
+++ b/packages/slate/src/interfaces/editor/editor-api.ts
@@ -75,6 +75,12 @@ export type EditorApi = {
options?: EditorFragmentOptions
) => N[];
+ /** Check if a path is selected by the current selection. */
+ isSelected: (
+ target: Path | TRange,
+ options?: EditorIsSelectedOptions
+ ) => boolean;
+
/**
* Call a function, Determine whether or not remove the previous node when
* merge.
@@ -547,8 +553,25 @@ export type EditorLevelsOptions = {
QueryVoids;
export type EditorNextOptions = QueryOptions &
- QueryMode &
- QueryVoids;
+ QueryVoids & {
+ /**
+ * Determines where to start traversing from:
+ *
+ * - `'after'` (default): Start from the point after the current location
+ * - `'child'`: Start from the first child of the current path. `at` must be a
+ * path.
+ */
+ from?: 'after' | 'child';
+
+ /**
+ * - `'all'` (default if `from` is `child`): Return all matching nodes.
+ * - `'highest'`: in a hierarchy of nodes, only return the highest level
+ * matching nodes
+ * - `'lowest'` (default if `from` is `after`): in a hierarchy of nodes, only
+ * return the lowest level matching nodes
+ */
+ mode?: 'all' | 'highest' | 'lowest';
+ };
export type EditorNodeOptions = {
depth?: number;
@@ -621,8 +644,24 @@ export type EditorPositionsOptions = {
QueryTextUnit;
export type EditorPreviousOptions = QueryOptions &
- QueryMode &
QueryVoids & {
+ /**
+ * Determines where to start traversing from:
+ *
+ * - `'before'` (default): Start from the point before the current location
+ * - `'parent'`: Start from the parent of the current location
+ */
+ from?: 'before' | 'parent';
+
+ /**
+ * - `'all'`: Return all matching nodes
+ * - `'highest'`: in a hierarchy of nodes, only return the highest level
+ * matching nodes
+ * - `'lowest'` (default): in a hierarchy of nodes, only return the lowest
+ * level matching nodes
+ */
+ mode?: 'all' | 'highest' | 'lowest';
+
/** Get the previous sibling node */
sibling?: boolean;
};
@@ -770,3 +809,8 @@ export type EditorLastOptions = {
/** Get last node at this level (0-based). */
level?: number;
};
+
+export type EditorIsSelectedOptions = {
+ /** Check if selection contains the entire path range */
+ contains?: boolean;
+};
diff --git a/packages/slate/src/interfaces/editor/editor-transforms.ts b/packages/slate/src/interfaces/editor/editor-transforms.ts
index 4dbbd729a4..614608322f 100644
--- a/packages/slate/src/interfaces/editor/editor-transforms.ts
+++ b/packages/slate/src/interfaces/editor/editor-transforms.ts
@@ -26,7 +26,6 @@ import type { HistoryApi } from '../../slate-history/index';
import type { At, TextUnit } from '../../types';
import type { QueryNodeOptions } from '../../utils';
import type { ElementIn, ElementOrTextIn } from '../element';
-import type { TLocation } from '../location';
import type { Descendant, DescendantIn, NodeIn, NodeProps } from '../node';
import type { NodeEntry } from '../node-entry';
import type { Operation } from '../operation';
@@ -514,9 +513,10 @@ export type WrapNodesOptions = {
QueryVoids;
export type InsertTextOptions = {
- at?: TLocation;
- voids?: boolean;
-};
+ /** @default true */
+ marks?: boolean;
+} & QueryAt &
+ QueryVoids;
export type DeleteTextOptions = {
distance?: number;
diff --git a/packages/slate/src/interfaces/editor/legacy-editor.ts b/packages/slate/src/interfaces/editor/legacy-editor.ts
index 00e3490754..22ec25c675 100644
--- a/packages/slate/src/interfaces/editor/legacy-editor.ts
+++ b/packages/slate/src/interfaces/editor/legacy-editor.ts
@@ -4,9 +4,9 @@ import type {
deleteForward as deleteForwardBase,
} from 'slate';
-import type { Value } from './editor-type';
import type { EditorApi } from './editor-api';
import type { EditorTransforms } from './editor-transforms';
+import type { Value } from './editor-type';
export type LegacyEditorApi = Pick<
EditorApi,
diff --git a/packages/slate/src/interfaces/node.ts b/packages/slate/src/interfaces/node.ts
index ed5f137054..869b6619e4 100644
--- a/packages/slate/src/interfaces/node.ts
+++ b/packages/slate/src/interfaces/node.ts
@@ -206,6 +206,9 @@ export const NodeApi: {
/** Check if a value implements the 'Descendant' interface. */
isDescendant: (value: any) => value is N;
+ /** Check if a value implements the `Editor` interface. */
+ isEditor: (value: any) => value is Editor;
+
/** Check if a node is the last child of its parent. */
isLastChild: (root: TNode, path: Path) => boolean;
diff --git a/packages/slate/src/interfaces/range.ts b/packages/slate/src/interfaces/range.ts
index f483f3538e..10e41163fc 100644
--- a/packages/slate/src/interfaces/range.ts
+++ b/packages/slate/src/interfaces/range.ts
@@ -25,6 +25,12 @@ export const RangeApi: {
options?: RangeTransformOptions
) => TRange | null;
+ /**
+ * Check if a range fully contains another range, meaning that both the start
+ * and end points of the target range are included in the range.
+ */
+ contains: (range: TRange, target: TRange) => boolean;
+
/**
* Get the start and end points of a range, in the order in which they appear
* in the document.
@@ -84,6 +90,14 @@ export const RangeApi: {
surrounds: (range: TRange, target: TRange) => boolean;
} = {
...SlateRange,
+ contains: (range: TRange, target: TRange) => {
+ const [targetStart, targetEnd] = RangeApi.edges(target);
+
+ return (
+ RangeApi.includes(range, targetStart) &&
+ RangeApi.includes(range, targetEnd)
+ );
+ },
isCollapsed: (range?: TRange | null) =>
!!range && SlateRange.isCollapsed(range),
isExpanded: (range?: TRange | null) =>
diff --git a/packages/slate/src/internal/editor-extension/is-selected.spec.ts b/packages/slate/src/internal/editor-extension/is-selected.spec.ts
new file mode 100644
index 0000000000..f80d92d1ec
--- /dev/null
+++ b/packages/slate/src/internal/editor-extension/is-selected.spec.ts
@@ -0,0 +1,155 @@
+import type { Range, Value } from '../../interfaces';
+
+import { createEditor } from '../../create-editor';
+import { isSelected } from './is-selected';
+
+describe('isSelected', () => {
+ it('should return false when no selection', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ });
+ editor.selection = null;
+
+ expect(isSelected(editor, [0])).toBe(false);
+ });
+
+ it('should return true when selection intersects with path', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 2, path: [0, 0] },
+ },
+ });
+
+ expect(isSelected(editor, [0])).toBe(true);
+ });
+
+ it('should return false when selection does not intersect with path', () => {
+ const editor = createEditor({
+ children: [
+ { children: [{ text: 'one' }], type: 'p' },
+ { children: [{ text: 'two' }], type: 'p' },
+ ],
+ selection: {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 1, path: [0, 0] },
+ },
+ });
+
+ expect(isSelected(editor, [1])).toBe(false);
+ });
+
+ describe('contains option', () => {
+ it('should return true when selection fully contains path', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 4, path: [0, 0] },
+ },
+ });
+
+ expect(isSelected(editor, [0], { contains: true })).toBe(true);
+ });
+
+ it('should return false when selection partially contains path', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 2, path: [0, 0] },
+ },
+ });
+
+ expect(isSelected(editor, [0], { contains: true })).toBe(false);
+ });
+
+ it('should return false when selection is outside path', () => {
+ const editor = createEditor({
+ children: [
+ { children: [{ text: 'one' }], type: 'p' },
+ { children: [{ text: 'two' }], type: 'p' },
+ ],
+ selection: {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 3, path: [0, 0] },
+ },
+ });
+
+ expect(isSelected(editor, [1], { contains: true })).toBe(false);
+ });
+ });
+
+ describe('range input', () => {
+ it('should return true when selection intersects with range', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 2, path: [0, 0] },
+ },
+ });
+
+ const range: Range = {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 3, path: [0, 0] },
+ };
+
+ expect(isSelected(editor, range)).toBe(true);
+ });
+
+ it('should return false when selection does not intersect with range', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 1, path: [0, 0] },
+ },
+ });
+
+ const range: Range = {
+ anchor: { offset: 2, path: [0, 0] },
+ focus: { offset: 4, path: [0, 0] },
+ };
+
+ expect(isSelected(editor, range)).toBe(false);
+ });
+
+ describe('contains option', () => {
+ it('should return true when selection fully contains range', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 4, path: [0, 0] },
+ },
+ });
+
+ const range: Range = {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 3, path: [0, 0] },
+ };
+
+ expect(isSelected(editor, range, { contains: true })).toBe(true);
+ });
+
+ it('should return false when selection partially contains range', () => {
+ const editor = createEditor({
+ children: [{ children: [{ text: 'test' }], type: 'p' }],
+ selection: {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 3, path: [0, 0] },
+ },
+ });
+
+ const range: Range = {
+ anchor: { offset: 0, path: [0, 0] },
+ focus: { offset: 2, path: [0, 0] },
+ };
+
+ expect(isSelected(editor, range, { contains: true })).toBe(false);
+ });
+ });
+ });
+});
diff --git a/packages/slate/src/internal/editor-extension/is-selected.ts b/packages/slate/src/internal/editor-extension/is-selected.ts
new file mode 100644
index 0000000000..49ddb72c52
--- /dev/null
+++ b/packages/slate/src/internal/editor-extension/is-selected.ts
@@ -0,0 +1,27 @@
+import {
+ type Editor,
+ type EditorIsSelectedOptions,
+ type Path,
+ type TRange,
+ RangeApi,
+} from '../../interfaces';
+
+export const isSelected = (
+ editor: Editor,
+ target: Path | TRange,
+ options: EditorIsSelectedOptions = {}
+) => {
+ const { contains = false } = options;
+
+ if (!editor.selection) return false;
+
+ const range = RangeApi.isRange(target) ? target : editor.api.range(target);
+
+ if (!range) return false;
+ if (contains) {
+ return RangeApi.contains(editor.selection, range);
+ }
+
+ // Check if selection intersects with path range
+ return !!RangeApi.intersection(editor.selection, range);
+};
diff --git a/packages/slate/src/internal/editor-extension/node-extension.ts b/packages/slate/src/internal/editor-extension/node-extension.ts
index 985043dc1a..9b9f7f8a44 100644
--- a/packages/slate/src/internal/editor-extension/node-extension.ts
+++ b/packages/slate/src/internal/editor-extension/node-extension.ts
@@ -1,3 +1,5 @@
+import { isEditor } from 'slate';
+
import { NodeApi } from '../../interfaces';
export const NodeExtension = {
@@ -34,6 +36,7 @@ export const NodeExtension = {
return firstText as any;
},
+ isEditor: (value: any) => isEditor(value),
isLastChild(root, path) {
if (path.length === 0) return false;
diff --git a/packages/slate/src/internal/editor/getNextNode.ts b/packages/slate/src/internal/editor/getNextNode.ts
deleted file mode 100644
index 71bdf9d294..0000000000
--- a/packages/slate/src/internal/editor/getNextNode.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { next } from 'slate';
-
-import type { DescendantOf, EditorNextOptions } from '../../interfaces';
-import type { Editor, ValueOf } from '../../interfaces/editor/editor-type';
-import type { NodeEntry } from '../../interfaces/node-entry';
-
-import { getQueryOptions } from '../../utils';
-
-export const getNextNode = <
- N extends DescendantOf,
- E extends Editor = Editor,
->(
- editor: E,
- options?: EditorNextOptions>
-): NodeEntry | undefined => {
- return next(editor as any, getQueryOptions(editor, options)) as any;
-};
diff --git a/packages/slate/src/internal/editor/getPointBefore.ts b/packages/slate/src/internal/editor/getPointBefore.ts
index 3a99d027b8..93036137fe 100644
--- a/packages/slate/src/internal/editor/getPointBefore.ts
+++ b/packages/slate/src/internal/editor/getPointBefore.ts
@@ -4,7 +4,7 @@ import map from 'lodash/map.js';
import { before as beforeBase } from 'slate';
import type { Editor } from '../../interfaces/editor/editor-type';
-import type { EditorBeforeOptions } from '../../interfaces/index';
+import type { EditorBeforeOptions, Point } from '../../interfaces/index';
import type { At } from '../../types';
import { getAt } from '../../utils';
@@ -13,7 +13,7 @@ export const getPointBefore = (
editor: Editor,
at: At,
options?: EditorBeforeOptions
-) => {
+): Point | undefined => {
if (!options || (!options.match && !options.matchString)) {
try {
return beforeBase(editor as any, getAt(editor, at)!, options as any);
diff --git a/packages/slate/src/internal/editor/isEditor.ts b/packages/slate/src/internal/editor/isEditor.ts
deleted file mode 100644
index 6e0cc11235..0000000000
--- a/packages/slate/src/internal/editor/isEditor.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { isEditor as isEditorBase } from 'slate';
-
-import type { Editor } from '../../interfaces/editor/editor-type';
-
-export const isEditor = (value: any): value is Editor => isEditorBase(value);
diff --git a/packages/slate/src/internal/editor/isEmpty.ts b/packages/slate/src/internal/editor/isEmpty.ts
index b1936845a7..96d760b671 100644
--- a/packages/slate/src/internal/editor/isEmpty.ts
+++ b/packages/slate/src/internal/editor/isEmpty.ts
@@ -9,7 +9,6 @@ import {
PathApi,
TextApi,
} from '../../interfaces';
-import { isEditor } from './isEditor';
export const isEmpty = (
editor: E,
@@ -17,7 +16,10 @@ export const isEmpty = (
options?: EditorEmptyOptions
) => {
if (target === null) return true;
- if ((PathApi.isPath(target) && target.length === 0) || isEditor(target)) {
+ if (
+ (PathApi.isPath(target) && target.length === 0) ||
+ NodeApi.isEditor(target)
+ ) {
return (
editor.children.length === 1 &&
isEmptyBase(editor as any, editor.children[0] as any)
diff --git a/packages/slate/src/internal/editor/last.spec.tsx b/packages/slate/src/internal/editor/last.spec.tsx
index 819d9042fb..e60ce02a52 100644
--- a/packages/slate/src/internal/editor/last.spec.tsx
+++ b/packages/slate/src/internal/editor/last.spec.tsx
@@ -108,4 +108,30 @@ describe('last', () => {
expect(res).toBeUndefined();
});
});
+
+ describe('when editor has no children', () => {
+ const input = createEditor(() as any);
+
+ it('should return undefined when level is 0', () => {
+ const res = input.api.last([], { level: 0 });
+
+ expect(res).toBeUndefined();
+ });
+ });
+
+ describe('when element has no children', () => {
+ const input = createEditor(
+ (
+
+
+
+ ) as any
+ );
+
+ it('should return undefined when getting last node in empty element', () => {
+ const res = input.api.last([1]);
+
+ expect(res).toBeUndefined();
+ });
+ });
});
diff --git a/packages/slate/src/internal/editor/last.ts b/packages/slate/src/internal/editor/last.ts
index 076b2ec400..6abf3eafa4 100644
--- a/packages/slate/src/internal/editor/last.ts
+++ b/packages/slate/src/internal/editor/last.ts
@@ -41,6 +41,10 @@ export const last = , E extends Editor>(
// If level is specified, get the node at that level
if (lastNodeEntry && typeof level === 'number') {
+ if (editor.children.length === 0) {
+ return;
+ }
+
return getNodeAtLevel(editor, lastNodeEntry, level) as any;
}
diff --git a/packages/slate/src/internal/editor/next.spec.tsx b/packages/slate/src/internal/editor/next.spec.tsx
new file mode 100644
index 0000000000..260dd15c12
--- /dev/null
+++ b/packages/slate/src/internal/editor/next.spec.tsx
@@ -0,0 +1,115 @@
+/** @jsx jsx */
+import { jsx } from '@udecode/plate-test-utils';
+
+import { createEditor } from '../../create-editor';
+
+jsx;
+
+describe('next', () => {
+ describe('when using from option', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+ Child One
+
+
+ Child Two
+
+
+
+ Block Three
+
+
+ ) as any
+ );
+
+ it('should traverse from point after when from="after"', () => {
+ const next = editor.api.next({ at: [0] });
+ expect(next![0].id).toBe('2');
+ });
+
+ it('should traverse from first child when from="child"', () => {
+ const next = editor.api.next({
+ at: [1],
+ from: 'child',
+ });
+ expect(next![0].id).toBe('2-1');
+ });
+ });
+
+ describe('when using match option', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+ Child One
+
+
+
+ ) as any
+ );
+
+ it('should find next node matching criteria', () => {
+ const next = editor.api.next({
+ at: [0],
+ match: (n) => 'type' in n && n.type === 'p',
+ });
+ expect(next![0].id).toBe('2-1');
+ });
+ });
+
+ describe('when using nested blocks', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+
+ Cell 1-1
+
+
+ Cell 1-2
+
+
+
+
+ Cell 2-1
+
+
+
+
+ Block Three
+
+
+ ) as any
+ );
+
+ it('should traverse from table to first cell when from="child"', () => {
+ const next = editor.api.next({
+ at: [1], // table path
+ from: 'child',
+ });
+ expect(next![0].id).toBe('row1');
+ });
+
+ it('should traverse from table to next block when from="after"', () => {
+ const next = editor.api.next({
+ at: [1], // table path
+ from: 'after',
+ });
+ expect(next![0].id).toBe('3');
+ });
+ });
+});
diff --git a/packages/slate/src/internal/editor/next.ts b/packages/slate/src/internal/editor/next.ts
new file mode 100644
index 0000000000..82be365892
--- /dev/null
+++ b/packages/slate/src/internal/editor/next.ts
@@ -0,0 +1,73 @@
+import type { Editor, ValueOf } from '../../interfaces/editor/editor-type';
+import type { NodeEntry } from '../../interfaces/node-entry';
+
+import {
+ type DescendantOf,
+ type EditorNextOptions,
+ type Path,
+ type Span,
+ PathApi,
+} from '../../interfaces';
+import { combineMatch, getAt, getMatch } from '../../utils';
+
+export const next = , E extends Editor = Editor>(
+ editor: E,
+ options: EditorNextOptions> = {}
+): NodeEntry | undefined => {
+ const {
+ from = 'after',
+ mode = from === 'child' ? 'all' : 'lowest',
+ voids = false,
+ } = options;
+ let match = getMatch(editor, options);
+
+ const at = getAt(editor, options.at) ?? editor.selection;
+
+ if (!at) {
+ return;
+ }
+
+ let start: Path | undefined;
+
+ // FORK: from
+ if (from === 'child' && PathApi.isPath(at)) {
+ const path = PathApi.firstChild(at);
+ const fromNode = editor.api.node(path);
+
+ if (fromNode) {
+ start = path;
+ match = combineMatch((n, p) => {
+ return !PathApi.isAncestor(p, at) && !PathApi.equals(p, at);
+ }, match);
+ }
+ }
+ if (!start) {
+ const pointAfterLocation = editor.api.after(at, { voids })!;
+
+ if (!pointAfterLocation) return;
+
+ start = pointAfterLocation.path;
+ }
+
+ const [, to] = editor.api.last([])!;
+
+ // FORK: from
+ const span: Span = [start, to];
+
+ if (PathApi.isPath(at) && at.length === 0) {
+ // throw new Error(`Cannot get the next node from the root node!`);
+ return;
+ }
+ if (match == null) {
+ if (PathApi.isPath(at)) {
+ const [parent] = editor.api.parent(at)!;
+ match = (n) => parent.children.includes(n as any);
+ } else {
+ match = () => true;
+ }
+ }
+
+ const [next] = editor.api.nodes({ at: span, match, mode, voids });
+
+ return next as any;
+};
diff --git a/packages/slate/src/internal/editor/previous.spec.ts b/packages/slate/src/internal/editor/previous.spec.tsx
similarity index 53%
rename from packages/slate/src/internal/editor/previous.spec.ts
rename to packages/slate/src/internal/editor/previous.spec.tsx
index f53fc521ad..dde62ebc88 100644
--- a/packages/slate/src/internal/editor/previous.spec.ts
+++ b/packages/slate/src/internal/editor/previous.spec.tsx
@@ -1,5 +1,133 @@
+/** @jsx jsx */
+import { jsx } from '@udecode/plate-test-utils';
+
import { createEditor } from '../../create-editor';
+jsx;
+
+describe('previous', () => {
+ describe('when using from option', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+ Child One
+
+
+ Child Two
+
+
+
+ Block Three
+
+
+ ) as any
+ );
+
+ it('should traverse from point before when from="before"', () => {
+ const prev = editor.api.previous({ at: [2] });
+ expect(prev![0].id).toBe('2');
+ });
+
+ it('should get previous when from="parent"', () => {
+ const prev = editor.api.previous({
+ at: [1, 1], // at 2-2
+ block: true,
+ from: 'parent',
+ });
+ expect(prev![0].id).toBe('2-1');
+ });
+
+ it('should traverse from parent when from="parent"', () => {
+ const prev = editor.api.previous({
+ at: [1, 0], // at 2-1
+ block: true,
+ from: 'parent',
+ });
+ expect(prev![0].id).toBe('2');
+ });
+ });
+
+ describe('when using match option', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+ Child One
+
+
+
+ Block Three
+
+
+ ) as any
+ );
+
+ it('should find previous node matching criteria', () => {
+ const prev = editor.api.previous({
+ at: [2],
+ match: (n) => 'type' in n && n.type === 'p',
+ });
+ expect(prev![0].id).toBe('2-1');
+ });
+ });
+
+ describe('when using nested blocks', () => {
+ const editor = createEditor(
+ (
+
+
+ Block One
+
+
+
+
+ Cell 1-1
+
+
+ Cell 1-2
+
+
+
+
+ Cell 2-1
+
+
+
+
+ Block Three
+
+
+ ) as any
+ );
+
+ it('should traverse from cell to parent row when from="parent"', () => {
+ const prev = editor.api.previous({
+ at: [1, 1, 0], // at cell2-1
+ block: true,
+ from: 'parent',
+ });
+ expect(prev![0].id).toBe('row2');
+ });
+
+ it('should traverse from table to previous block when from="before"', () => {
+ const prev = editor.api.previous({
+ at: [1], // table path
+ from: 'before',
+ });
+ expect(prev![0].id).toBe('1');
+ });
+ });
+});
+
const nodesFixture5 = [
{
id: '1',
@@ -64,10 +192,10 @@ describe('when getting previous node by id', () => {
});
describe('when first block', () => {
- it('should return [null, [-1]]', () => {
+ it('should return undefined', () => {
const e = createEditor();
e.children = nodesFixture5;
- expect(e.api.previous({ id: '1', block: true })).toEqual([null, [-1]]);
+ expect(e.api.previous({ id: '1', block: true })).toBeUndefined();
});
});
diff --git a/packages/slate/src/internal/editor/previous.ts b/packages/slate/src/internal/editor/previous.ts
index 3d13767062..91862c2b03 100644
--- a/packages/slate/src/internal/editor/previous.ts
+++ b/packages/slate/src/internal/editor/previous.ts
@@ -1,14 +1,79 @@
-import { previous as previousBase } from 'slate';
-
import type { Editor, ValueOf } from '../../interfaces/editor/editor-type';
import type { NodeEntry } from '../../interfaces/node-entry';
import {
type DescendantOf,
type EditorPreviousOptions,
+ type Path,
+ type Span,
PathApi,
} from '../../interfaces';
-import { getQueryOptions } from '../../utils';
+import { combineMatch, getAt, getMatch, getQueryOptions } from '../../utils';
+
+// Slate fork
+const previousBase = (
+ editor: Editor,
+ options: EditorPreviousOptions>
+) => {
+ const { from = 'after', mode = 'lowest', voids = false } = options;
+ let match = getMatch(editor, options);
+
+ const at = getAt(editor, options.at) ?? editor.selection;
+
+ if (!at) {
+ return;
+ }
+
+ let start: Path | undefined;
+
+ // FORK: from
+ if (from === 'parent' && PathApi.isPath(at) && at.length > 1) {
+ start = at;
+
+ match = combineMatch((n, p) => {
+ // We want nodes that:
+ // 1. Are not after our target path
+ // 2. Are not the same as our target path
+ return !PathApi.isAfter(p, at) && !PathApi.equals(p, at);
+ }, match);
+ }
+ if (!start) {
+ const pointBeforeLocation = editor.api.before(at, { voids })!;
+
+ if (!pointBeforeLocation) return;
+
+ start = pointBeforeLocation.path;
+ }
+
+ const [, to] = editor.api.first([])!;
+
+ // The search location is from the start of the document to the path of
+ // the point before the location passed in
+ const span: Span = [start, to];
+
+ if (PathApi.isPath(at) && at.length === 0) {
+ // throw new Error(`Cannot get the previous node from the root node!`);
+ return;
+ }
+ if (match == null) {
+ if (PathApi.isPath(at)) {
+ const [parent] = editor.api.parent(at)!;
+ match = (n) => parent.children.includes(n as any);
+ } else {
+ match = () => true;
+ }
+ }
+
+ const [previous] = editor.api.nodes({
+ at: span,
+ match,
+ mode,
+ reverse: true,
+ voids,
+ });
+
+ return previous;
+};
export const previous = , E extends Editor = Editor>(
editor: E,
@@ -16,7 +81,7 @@ export const previous = , E extends Editor = Editor>(
): NodeEntry | undefined => {
const getPrevious = (o: EditorPreviousOptions>) => {
try {
- return previousBase(editor as any, getQueryOptions(editor, o)) as any;
+ return previousBase(editor as any, o) as any;
} catch {}
};
@@ -45,7 +110,5 @@ export const previous = , E extends Editor = Editor>(
if (!block) return;
// both id and block are defined
- const prevEntry = getPrevious({ at: block[1], block: true });
-
- return prevEntry ? (prevEntry as any) : ([null, [-1]] as any);
+ return getPrevious({ at: block[1], block: true });
};
diff --git a/packages/slate/src/internal/editor/range.ts b/packages/slate/src/internal/editor/range.ts
index 9e949c60fe..184f8d9e88 100644
--- a/packages/slate/src/internal/editor/range.ts
+++ b/packages/slate/src/internal/editor/range.ts
@@ -40,7 +40,7 @@ export const range = (
if (to && from === 'before') {
const anchor = editor.api.before(to, options?.before);
- from = anchor ?? to;
+ from = anchor ?? getAt(editor, to);
}
return rangeBase(editor as any, from as any, getAt(editor, to));
diff --git a/packages/slate/src/internal/transforms-extension/removeMarks.ts b/packages/slate/src/internal/transforms-extension/removeMarks.ts
index ef5051962a..8199fd58fe 100644
--- a/packages/slate/src/internal/transforms-extension/removeMarks.ts
+++ b/packages/slate/src/internal/transforms-extension/removeMarks.ts
@@ -2,6 +2,7 @@ import castArray from 'lodash/castArray.js';
import {
type Editor,
+ type Path,
type RemoveMarksOptions,
type TElement,
type TNode,
@@ -18,12 +19,12 @@ export const removeMarks = (
if (!selection) return;
- const match = (node: TNode) => {
+ const match = (node: TNode, path: Path) => {
if (!TextApi.isText(node)) {
return false; // marks can only be applied to text
}
- const [parentNode] = editor.api.parent(editor.api.path(node)!)!;
+ const [parentNode] = editor.api.parent(path)!;
return (
!editor.api.isVoid(parentNode) || editor.api.markableVoid(parentNode)
@@ -36,7 +37,7 @@ export const removeMarks = (
if (!expandedSelection) {
const [selectedNode, selectedPath] = editor.api.node(selection)!;
- if (selectedNode && match(selectedNode)) {
+ if (selectedNode && match(selectedNode, selectedPath)) {
const [parentNode] = editor.api.parent(selectedPath)!;
markAcceptingVoidSelected =
parentNode && editor.api.markableVoid(parentNode);
diff --git a/packages/slate/src/internal/transforms/insertText.spec.tsx b/packages/slate/src/internal/transforms/insertText.spec.tsx
new file mode 100644
index 0000000000..734c6881a0
--- /dev/null
+++ b/packages/slate/src/internal/transforms/insertText.spec.tsx
@@ -0,0 +1,168 @@
+/** @jsx jsxt */
+import { jsxt } from '@udecode/plate-test-utils';
+
+import { createEditor } from '../../create-editor';
+
+jsxt;
+
+describe('insertText', () => {
+ describe('when marks is false', () => {
+ it('should insert text without marks', () => {
+ const input = createEditor(
+ (
+
+
+ existing
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ existing text
+
+
+ ) as any;
+
+ input.selection = {
+ anchor: { offset: 9, path: [0, 0] },
+ focus: { offset: 9, path: [0, 0] },
+ };
+ input.marks = { bold: true };
+ input.tf.insertText('text', { marks: false });
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when marks are present', () => {
+ it('should insert text with marks', () => {
+ const input = createEditor(
+ (
+
+
+ existing
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ existing
+ bold text
+
+
+ ) as any;
+
+ input.selection = {
+ anchor: { offset: 9, path: [0, 0] },
+ focus: { offset: 9, path: [0, 0] },
+ };
+ input.marks = { bold: true };
+ input.tf.insertText('bold text');
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when no marks are present', () => {
+ it('should insert plain text', () => {
+ const input = createEditor(
+ (
+
+
+ existing
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ existing plain text
+
+
+ ) as any;
+
+ input.selection = {
+ anchor: { offset: 9, path: [0, 0] },
+ focus: { offset: 9, path: [0, 0] },
+ };
+ input.marks = null;
+ input.tf.insertText('plain text');
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when selection is null', () => {
+ it('should not insert text', () => {
+ const input = createEditor(
+ (
+
+
+ existing text
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ existing text
+
+
+ ) as any;
+
+ input.selection = null;
+ input.marks = { bold: true };
+ input.tf.insertText('new text');
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when inserting at specific path', () => {
+ it('should insert text at given path without marks', () => {
+ const input = createEditor(
+ (
+
+
+ first
+
+
+ second
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ first
+
+
+ second inserted
+
+
+ ) as any;
+
+ input.marks = { bold: true };
+ input.tf.insertText(' inserted', {
+ at: {
+ offset: 6,
+ path: [1, 0],
+ },
+ });
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+});
diff --git a/packages/slate/src/internal/transforms/insertText.ts b/packages/slate/src/internal/transforms/insertText.ts
index d45eafbcde..36b168b388 100644
--- a/packages/slate/src/internal/transforms/insertText.ts
+++ b/packages/slate/src/internal/transforms/insertText.ts
@@ -1,16 +1,34 @@
-import { insertText as insertTextBase } from 'slate';
+import { Transforms } from 'slate';
-import type { Editor, QueryAt, QueryVoids } from '../../interfaces';
+import type { Editor, InsertTextOptions } from '../../interfaces';
-import { getAt } from '../../utils/getAt';
+import { getAt } from '../../utils';
export const insertText = (
editor: Editor,
text: string,
- options?: QueryAt & QueryVoids
+ { marks = true, ...options }: InsertTextOptions = {}
) => {
- insertTextBase(editor as any, text, {
- ...options,
- at: getAt(editor, options?.at),
- });
+ const at = getAt(editor, options.at);
+
+ // Case 1: Insert at options.at if specified, regardless of selection
+ if (at) {
+ Transforms.insertText(editor as any, text, { ...options, at });
+
+ return;
+ }
+ // Case 2: Default Slate behavior - only proceed if there's a selection
+ if (editor.selection) {
+ if (marks && editor.marks) {
+ // Case 2.1: Insert with marks if any
+ const node = { text, ...editor.marks };
+ editor.tf.insertNodes(node, {
+ voids: options.voids,
+ });
+ editor.marks = null;
+ } else {
+ // Case 2.2: Insert plain text
+ Transforms.insertText(editor as any, text, options as any);
+ }
+ }
};
diff --git a/packages/slate/src/internal/transforms/removeNodes.spec.tsx b/packages/slate/src/internal/transforms/removeNodes.spec.tsx
new file mode 100644
index 0000000000..e8b1c55173
--- /dev/null
+++ b/packages/slate/src/internal/transforms/removeNodes.spec.tsx
@@ -0,0 +1,267 @@
+/** @jsx jsxt */
+import { jsxt } from '@udecode/plate-test-utils';
+
+import { createEditor } from '../../create-editor';
+
+jsxt;
+
+describe('removeNodes', () => {
+ describe('when previousEmptyBlock is true', () => {
+ it('should remove the previous empty block', () => {
+ const input = createEditor(
+ (
+
+
+
+
+
+ text
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [1], previousEmptyBlock: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+
+ it('should do nothing if previous block is not empty', () => {
+ const input = createEditor(
+ (
+
+
+ not empty
+
+
+ text
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ not empty
+
+
+ text
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [1], previousEmptyBlock: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when previousEmptyBlock is false', () => {
+ it('should remove nodes at specified path', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+ remove me
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [1] });
+
+ expect(input.children).toEqual(output.children);
+ });
+
+ it('should do nothing if no path is specified', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+ ) as any;
+
+ input.tf.removeNodes({ previousEmptyBlock: false });
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+
+ describe('when children option is true', () => {
+ it('should remove all children at specified path', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+
+ item 1
+
+
+ item 2
+
+
+ item 3
+
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [1], children: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+
+ it('should do nothing if path has no children', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+ another
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+ another
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [0, 0], children: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+
+ it('should remove nested children in reverse order', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+
+ item 1
+
+
+ nested 1
+
+
+ nested 2
+
+
+
+
+ item 2
+
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+
+
+
+ ) as any;
+
+ input.tf.removeNodes({ at: [1], children: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+
+ it('should do nothing if no path is specified with children option', () => {
+ const input = createEditor(
+ (
+
+
+ text
+
+
+
+ item 1
+
+
+
+ ) as any
+ );
+
+ const output = (
+
+
+ text
+
+
+
+ item 1
+
+
+
+ ) as any;
+
+ input.tf.removeNodes({ children: true });
+
+ expect(input.children).toEqual(output.children);
+ });
+ });
+});
diff --git a/packages/slate/src/internal/transforms/removeNodes.ts b/packages/slate/src/internal/transforms/removeNodes.ts
index e989ac6732..a0bbc42222 100644
--- a/packages/slate/src/internal/transforms/removeNodes.ts
+++ b/packages/slate/src/internal/transforms/removeNodes.ts
@@ -12,20 +12,25 @@ export const removeNodes = (
const options = getQueryOptions(editor, opt);
editor.tf.withoutNormalizing(() => {
- // Handle previousEmptyBlock option
- if (previousEmptyBlock && options.at) {
+ if (previousEmptyBlock) {
const entry = editor.api.block({ at: options.at });
- if (entry) {
- const prevEntry = editor.api.previous({
- at: entry[1],
- sibling: true,
- });
+ if (!entry) return;
- if (prevEntry && editor.api.isEmpty(prevEntry[0])) {
- editor.tf.removeNodes({ at: prevEntry[1] });
- }
+ const prevEntry = editor.api.previous({
+ at: entry[1],
+ sibling: true,
+ });
+
+ if (!prevEntry) return;
+
+ const [prevNode, prevPath] = prevEntry;
+
+ if (editor.api.isEmpty(prevNode)) {
+ editor.tf.removeNodes({ at: prevPath });
}
+
+ return;
}
// Handle children option
if (children && options.at) {
diff --git a/packages/trailing-block/src/lib/withTrailingBlock.ts b/packages/trailing-block/src/lib/withTrailingBlock.ts
index 43c71401c3..23ea8d0b17 100644
--- a/packages/trailing-block/src/lib/withTrailingBlock.ts
+++ b/packages/trailing-block/src/lib/withTrailingBlock.ts
@@ -17,7 +17,6 @@ export const withTrailingBlock: OverrideEditor = ({
if (currentPath.length === 0) {
const lastChild = editor.api.last([], { level });
-
const lastChildNode = lastChild?.[0];
if (
diff --git a/yarn.lock b/yarn.lock
index cc7638eacb..06cfd7319c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1852,33 +1852,13 @@ __metadata:
languageName: node
linkType: hard
-"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.6.1, @eslint-community/regexpp@npm:^4.8.0":
+"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.6.1, @eslint-community/regexpp@npm:^4.8.0":
version: 4.12.1
resolution: "@eslint-community/regexpp@npm:4.12.1"
checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6
languageName: node
linkType: hard
-"@eslint/config-array@npm:^0.19.0":
- version: 0.19.1
- resolution: "@eslint/config-array@npm:0.19.1"
- dependencies:
- "@eslint/object-schema": "npm:^2.1.5"
- debug: "npm:^4.3.1"
- minimatch: "npm:^3.1.2"
- checksum: 10c0/43b01f596ddad404473beae5cf95c013d29301c72778d0f5bf8a6699939c8a9a5663dbd723b53c5f476b88b0c694f76ea145d1aa9652230d140fe1161e4a4b49
- languageName: node
- linkType: hard
-
-"@eslint/core@npm:^0.9.0":
- version: 0.9.1
- resolution: "@eslint/core@npm:0.9.1"
- dependencies:
- "@types/json-schema": "npm:^7.0.15"
- checksum: 10c0/638104b1b5833a9bbf2329f0c0ddf322e4d6c0410b149477e02cd2b78c04722be90c14b91b8ccdef0d63a2404dff72a17b6b412ce489ea429ae6a8fcb8abff28
- languageName: node
- linkType: hard
-
"@eslint/eslintrc@npm:^2.1.4":
version: 2.1.4
resolution: "@eslint/eslintrc@npm:2.1.4"
@@ -1896,23 +1876,6 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/eslintrc@npm:^3.2.0":
- version: 3.2.0
- resolution: "@eslint/eslintrc@npm:3.2.0"
- dependencies:
- ajv: "npm:^6.12.4"
- debug: "npm:^4.3.2"
- espree: "npm:^10.0.1"
- globals: "npm:^14.0.0"
- ignore: "npm:^5.2.0"
- import-fresh: "npm:^3.2.1"
- js-yaml: "npm:^4.1.0"
- minimatch: "npm:^3.1.2"
- strip-json-comments: "npm:^3.1.1"
- checksum: 10c0/43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b
- languageName: node
- linkType: hard
-
"@eslint/js@npm:8.57.1":
version: 8.57.1
resolution: "@eslint/js@npm:8.57.1"
@@ -1920,29 +1883,6 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/js@npm:9.17.0":
- version: 9.17.0
- resolution: "@eslint/js@npm:9.17.0"
- checksum: 10c0/a0fda8657a01c60aa540f95397754267ba640ffb126e011b97fd65c322a94969d161beeaef57c1441c495da2f31167c34bd38209f7c146c7225072378c3a933d
- languageName: node
- linkType: hard
-
-"@eslint/object-schema@npm:^2.1.5":
- version: 2.1.5
- resolution: "@eslint/object-schema@npm:2.1.5"
- checksum: 10c0/5320691ed41ecd09a55aff40ce8e56596b4eb81f3d4d6fe530c50fdd6552d88102d1c1a29d970ae798ce30849752a708772de38ded07a6f25b3da32ebea081d8
- languageName: node
- linkType: hard
-
-"@eslint/plugin-kit@npm:^0.2.3":
- version: 0.2.4
- resolution: "@eslint/plugin-kit@npm:0.2.4"
- dependencies:
- levn: "npm:^0.4.1"
- checksum: 10c0/1bcfc0a30b1df891047c1d8b3707833bded12a057ba01757a2a8591fdc8d8fe0dbb8d51d4b0b61b2af4ca1d363057abd7d2fb4799f1706b105734f4d3fa0dbf1
- languageName: node
- linkType: hard
-
"@excalidraw/excalidraw@npm:0.16.4":
version: 0.16.4
resolution: "@excalidraw/excalidraw@npm:0.16.4"
@@ -1968,21 +1908,21 @@ __metadata:
linkType: hard
"@floating-ui/core@npm:^1.6.0, @floating-ui/core@npm:^1.6.7":
- version: 1.6.8
- resolution: "@floating-ui/core@npm:1.6.8"
+ version: 1.6.9
+ resolution: "@floating-ui/core@npm:1.6.9"
dependencies:
- "@floating-ui/utils": "npm:^0.2.8"
- checksum: 10c0/d6985462aeccae7b55a2d3f40571551c8c42bf820ae0a477fc40ef462e33edc4f3f5b7f11b100de77c9b58ecb581670c5c3f46d0af82b5e30aa185c735257eb9
+ "@floating-ui/utils": "npm:^0.2.9"
+ checksum: 10c0/77debdfc26bc36c6f5ae1f26ab3c15468215738b3f5682af4e1915602fa21ba33ad210273f31c9d2da1c531409929e1afb1138b1608c6b54a0f5853ee84c340d
languageName: node
linkType: hard
"@floating-ui/dom@npm:^1.0.0":
- version: 1.6.12
- resolution: "@floating-ui/dom@npm:1.6.12"
+ version: 1.6.13
+ resolution: "@floating-ui/dom@npm:1.6.13"
dependencies:
"@floating-ui/core": "npm:^1.6.0"
- "@floating-ui/utils": "npm:^0.2.8"
- checksum: 10c0/c67b39862175b175c6ac299ea970f17a22c7482cfdf3b1bc79313407bf0880188b022b878953fa69d3ce166ff2bd9ae57c86043e5dd800c262b470d877591b7d
+ "@floating-ui/utils": "npm:^0.2.9"
+ checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9
languageName: node
linkType: hard
@@ -2012,10 +1952,10 @@ __metadata:
languageName: node
linkType: hard
-"@floating-ui/utils@npm:^0.2.8":
- version: 0.2.8
- resolution: "@floating-ui/utils@npm:0.2.8"
- checksum: 10c0/a8cee5f17406c900e1c3ef63e3ca89b35e7a2ed645418459a73627b93b7377477fc888081011c6cd177cac45ec2b92a6cab018c14ea140519465498dddd2d3f9
+"@floating-ui/utils@npm:^0.2.8, @floating-ui/utils@npm:^0.2.9":
+ version: 0.2.9
+ resolution: "@floating-ui/utils@npm:0.2.9"
+ checksum: 10c0/48bbed10f91cb7863a796cc0d0e917c78d11aeb89f98d03fc38d79e7eb792224a79f538ed8a2d5d5584511d4ca6354ef35f1712659fd569868e342df4398ad6f
languageName: node
linkType: hard
@@ -2076,23 +2016,6 @@ __metadata:
languageName: node
linkType: hard
-"@humanfs/core@npm:^0.19.1":
- version: 0.19.1
- resolution: "@humanfs/core@npm:0.19.1"
- checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67
- languageName: node
- linkType: hard
-
-"@humanfs/node@npm:^0.16.6":
- version: 0.16.6
- resolution: "@humanfs/node@npm:0.16.6"
- dependencies:
- "@humanfs/core": "npm:^0.19.1"
- "@humanwhocodes/retry": "npm:^0.3.0"
- checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1
- languageName: node
- linkType: hard
-
"@humanwhocodes/config-array@npm:^0.13.0":
version: 0.13.0
resolution: "@humanwhocodes/config-array@npm:0.13.0"
@@ -2118,20 +2041,6 @@ __metadata:
languageName: node
linkType: hard
-"@humanwhocodes/retry@npm:^0.3.0":
- version: 0.3.1
- resolution: "@humanwhocodes/retry@npm:0.3.1"
- checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b
- languageName: node
- linkType: hard
-
-"@humanwhocodes/retry@npm:^0.4.1":
- version: 0.4.1
- resolution: "@humanwhocodes/retry@npm:0.4.1"
- checksum: 10c0/be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b
- languageName: node
- linkType: hard
-
"@ianvs/prettier-plugin-sort-imports@npm:^4.3.1":
version: 4.4.0
resolution: "@ianvs/prettier-plugin-sort-imports@npm:4.4.0"
@@ -4770,143 +4679,143 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-android-arm-eabi@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-android-arm-eabi@npm:4.29.2"
+"@rollup/rollup-android-arm-eabi@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-android-arm-eabi@npm:4.30.1"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@rollup/rollup-android-arm64@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-android-arm64@npm:4.29.2"
+"@rollup/rollup-android-arm64@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-android-arm64@npm:4.30.1"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-darwin-arm64@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-darwin-arm64@npm:4.29.2"
+"@rollup/rollup-darwin-arm64@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-darwin-arm64@npm:4.30.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-darwin-x64@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-darwin-x64@npm:4.29.2"
+"@rollup/rollup-darwin-x64@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-darwin-x64@npm:4.30.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-arm64@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-freebsd-arm64@npm:4.29.2"
+"@rollup/rollup-freebsd-arm64@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-freebsd-arm64@npm:4.30.1"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-x64@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-freebsd-x64@npm:4.29.2"
+"@rollup/rollup-freebsd-x64@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-freebsd-x64@npm:4.30.1"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-gnueabihf@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.29.2"
+"@rollup/rollup-linux-arm-gnueabihf@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.30.1"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-musleabihf@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.29.2"
+"@rollup/rollup-linux-arm-musleabihf@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.30.1"
conditions: os=linux & cpu=arm & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.29.2"
+"@rollup/rollup-linux-arm64-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.30.1"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-musl@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-arm64-musl@npm:4.29.2"
+"@rollup/rollup-linux-arm64-musl@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-arm64-musl@npm:4.30.1"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-loongarch64-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.29.2"
+"@rollup/rollup-linux-loongarch64-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.30.1"
conditions: os=linux & cpu=loong64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-powerpc64le-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.29.2"
+"@rollup/rollup-linux-powerpc64le-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.30.1"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-riscv64-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.29.2"
+"@rollup/rollup-linux-riscv64-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.30.1"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-s390x-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.29.2"
+"@rollup/rollup-linux-s390x-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.30.1"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-gnu@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-x64-gnu@npm:4.29.2"
+"@rollup/rollup-linux-x64-gnu@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-x64-gnu@npm:4.30.1"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-musl@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-linux-x64-musl@npm:4.29.2"
+"@rollup/rollup-linux-x64-musl@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-linux-x64-musl@npm:4.30.1"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-win32-arm64-msvc@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.29.2"
+"@rollup/rollup-win32-arm64-msvc@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.30.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-win32-ia32-msvc@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.29.2"
+"@rollup/rollup-win32-ia32-msvc@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.30.1"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@rollup/rollup-win32-x64-msvc@npm:4.29.2":
- version: 4.29.2
- resolution: "@rollup/rollup-win32-x64-msvc@npm:4.29.2"
+"@rollup/rollup-win32-x64-msvc@npm:4.30.1":
+ version: 4.30.1
+ resolution: "@rollup/rollup-win32-x64-msvc@npm:4.30.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@rushstack/eslint-patch@npm:^1.10.3":
- version: 1.10.4
- resolution: "@rushstack/eslint-patch@npm:1.10.4"
- checksum: 10c0/de312bd7a3cb0f313c9720029eb719d8762fe54946cce2d33ac142b1cbb5817c4a5a92518dfa476c26311602d37f5a8f7caa90a0c73e3d6a56f9a05d2799c172
+ version: 1.10.5
+ resolution: "@rushstack/eslint-patch@npm:1.10.5"
+ checksum: 10c0/ea66e8be3a78a48d06e8ff33221cef5749386589236bbcd24013577d2b4e1035e3dc919740c6e0f16d44c1e0b62d06d00898508fc74cc2adb0ac6b667aa5a8e4
languageName: node
linkType: hard
@@ -5247,8 +5156,8 @@ __metadata:
linkType: hard
"@tailwindcss/typography@npm:^0.5.15":
- version: 0.5.15
- resolution: "@tailwindcss/typography@npm:0.5.15"
+ version: 0.5.16
+ resolution: "@tailwindcss/typography@npm:0.5.16"
dependencies:
lodash.castarray: "npm:^4.4.0"
lodash.isplainobject: "npm:^4.0.6"
@@ -5256,7 +5165,7 @@ __metadata:
postcss-selector-parser: "npm:6.0.10"
peerDependencies:
tailwindcss: "*"
- checksum: 10c0/bd1a1d0ab06816afe129a49cb8a693b4f6ffe77748f5279f07ea29ea9fcb44ef24d1f8b1cfcffaf41dd9cb60065745897cbfc9dcabc57c8a60ceb89d594c97c6
+ checksum: 10c0/35a7387876810c23c270a23848b920517229b707c8ead6a63c8bb7d6720a62f23728c3117f0a93b422a66d1e5ee9c7ad8a6c3f0fbf5255b535c0e4971ffa0158
languageName: node
linkType: hard
@@ -5516,7 +5425,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/estree@npm:*, @types/estree@npm:1.0.6, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6":
+"@types/estree@npm:*, @types/estree@npm:1.0.6, @types/estree@npm:^1.0.0":
version: 1.0.6
resolution: "@types/estree@npm:1.0.6"
checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a
@@ -5620,7 +5529,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9":
+"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.9":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
@@ -5722,11 +5631,11 @@ __metadata:
linkType: hard
"@types/node@npm:^18.16.3":
- version: 18.19.69
- resolution: "@types/node@npm:18.19.69"
+ version: 18.19.70
+ resolution: "@types/node@npm:18.19.70"
dependencies:
undici-types: "npm:~5.26.4"
- checksum: 10c0/f10ae0d07bde6b80c02702ecd470c0f710cc2fde48d8cea4140cb7d7715c0b944ca5d9b730e4b56ba9d96f1eb459739da71c54e88586fc6b911600ea1e70af4e
+ checksum: 10c0/68866e53b92be60d8840f5c93232d3ae39c71663101decc1d4f1870d9236c3c89e74177b616c2a2cabce116b1356f3e89c57df3e969c9f9b0e0b5c59b5f790f7
languageName: node
linkType: hard
@@ -5821,11 +5730,11 @@ __metadata:
linkType: hard
"@types/react@npm:*":
- version: 19.0.2
- resolution: "@types/react@npm:19.0.2"
+ version: 19.0.4
+ resolution: "@types/react@npm:19.0.4"
dependencies:
csstype: "npm:^3.0.2"
- checksum: 10c0/8992f39701fcf1bf893ef8f94a56196445667baf08fe4f6050a14e229a17aad3265ad3efc01595ff3b4d5d5c69da885f9aa4ff80f164a613018734efcff1eb8f
+ checksum: 10c0/96ecd1a73af57fd7b7facf5b36ec069b131c7608a98a0f1098183023bfb21c60a26a0dc09004fbe0ac70c436ef887bbec5690882cfb77c6e0c679f7e45987722
languageName: node
linkType: hard
@@ -5958,39 +5867,39 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/eslint-plugin@npm:^8.5.0":
- version: 8.19.0
- resolution: "@typescript-eslint/eslint-plugin@npm:8.19.0"
+ version: 8.19.1
+ resolution: "@typescript-eslint/eslint-plugin@npm:8.19.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
- "@typescript-eslint/scope-manager": "npm:8.19.0"
- "@typescript-eslint/type-utils": "npm:8.19.0"
- "@typescript-eslint/utils": "npm:8.19.0"
- "@typescript-eslint/visitor-keys": "npm:8.19.0"
+ "@typescript-eslint/scope-manager": "npm:8.19.1"
+ "@typescript-eslint/type-utils": "npm:8.19.1"
+ "@typescript-eslint/utils": "npm:8.19.1"
+ "@typescript-eslint/visitor-keys": "npm:8.19.1"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0"
- ts-api-utils: "npm:^1.3.0"
+ ts-api-utils: "npm:^2.0.0"
peerDependencies:
"@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
- checksum: 10c0/ceaa5063b68684b5608950b5e69f0caf1eadfc356cba82625240d6aae55f769faff599c38d35252dcb77a40d92e6fbf6d6264bc0c577d5c549da25061c3bd796
+ checksum: 10c0/993784b04533b13c3f3919c793cfc3a369fa61692e1a2d72de6fba27df247c275d852cdcbc4e393c310b73fce8d34d210a9b632b66f4d761a1a3b4781f8fa93f
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/parser@npm:^8.5.0":
- version: 8.19.0
- resolution: "@typescript-eslint/parser@npm:8.19.0"
+ version: 8.19.1
+ resolution: "@typescript-eslint/parser@npm:8.19.1"
dependencies:
- "@typescript-eslint/scope-manager": "npm:8.19.0"
- "@typescript-eslint/types": "npm:8.19.0"
- "@typescript-eslint/typescript-estree": "npm:8.19.0"
- "@typescript-eslint/visitor-keys": "npm:8.19.0"
+ "@typescript-eslint/scope-manager": "npm:8.19.1"
+ "@typescript-eslint/types": "npm:8.19.1"
+ "@typescript-eslint/typescript-estree": "npm:8.19.1"
+ "@typescript-eslint/visitor-keys": "npm:8.19.1"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
- checksum: 10c0/064b0997963060490fc3f92c90cebc7c694f47a7657f7882ce9eb314786e0cf3e917bfccfad614d23038439d84e69a978bdc7054515b23201001dd427e524e64
+ checksum: 10c0/1afbd2d0a25f439943bdc94637417429574eb3889a2a1ce24bd425721713aca213808a975bb518a6616171783bc04fa973167f05fc6a96cfd88c1d1666077ad4
languageName: node
linkType: hard
@@ -6004,28 +5913,28 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/scope-manager@npm:8.19.0":
- version: 8.19.0
- resolution: "@typescript-eslint/scope-manager@npm:8.19.0"
+"@typescript-eslint/scope-manager@npm:8.19.1":
+ version: 8.19.1
+ resolution: "@typescript-eslint/scope-manager@npm:8.19.1"
dependencies:
- "@typescript-eslint/types": "npm:8.19.0"
- "@typescript-eslint/visitor-keys": "npm:8.19.0"
- checksum: 10c0/5052863d00db7ae939de27e91dc6c92df3c37a877e1ff44015ae9aa754d419b44d97d98b25fbb30a80dc58cf92606dad599e27f32b86d20c13b77ac12b4f2abc
+ "@typescript-eslint/types": "npm:8.19.1"
+ "@typescript-eslint/visitor-keys": "npm:8.19.1"
+ checksum: 10c0/7dca0c28ad27a0c7e26499e0f584f98efdcf34087f46aadc661b36c310484b90655e83818bafd249b5a28c7094a69c54d553f6cd403869bf134f95a9148733f5
languageName: node
linkType: hard
-"@typescript-eslint/type-utils@npm:8.19.0":
- version: 8.19.0
- resolution: "@typescript-eslint/type-utils@npm:8.19.0"
+"@typescript-eslint/type-utils@npm:8.19.1":
+ version: 8.19.1
+ resolution: "@typescript-eslint/type-utils@npm:8.19.1"
dependencies:
- "@typescript-eslint/typescript-estree": "npm:8.19.0"
- "@typescript-eslint/utils": "npm:8.19.0"
+ "@typescript-eslint/typescript-estree": "npm:8.19.1"
+ "@typescript-eslint/utils": "npm:8.19.1"
debug: "npm:^4.3.4"
- ts-api-utils: "npm:^1.3.0"
+ ts-api-utils: "npm:^2.0.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
- checksum: 10c0/5a460b4d26fd68ded3567390cbac310500e94e9c69583fda3fb9930877663719e6831699bb6d85de6b940bcb7951a51ab1ef67c5fea8b76a13ea3a3783bbae28
+ checksum: 10c0/757592b515beec58c079c605aa648ba94d985ae48ba40460034e849c7bc2b603b1da6113e59688e284608c9d5ccaa27adf0a14fb032cb1782200c6acae51ddd2
languageName: node
linkType: hard
@@ -6036,10 +5945,10 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/types@npm:8.19.0, @typescript-eslint/types@npm:^8.9.0":
- version: 8.19.0
- resolution: "@typescript-eslint/types@npm:8.19.0"
- checksum: 10c0/0062e7dce5f374e293c97f1f50fe450187f6b0eaf4971c818e18ef2f6baf4e9aa4e8605fba8d8fc464a504ee1130527b71ecb39d31687c31825942b9f569d902
+"@typescript-eslint/types@npm:8.19.1, @typescript-eslint/types@npm:^8.9.0":
+ version: 8.19.1
+ resolution: "@typescript-eslint/types@npm:8.19.1"
+ checksum: 10c0/e907bf096d5ed7a812a1e537a98dd881ab5d2d47e072225bfffaa218c1433115a148b27a15744db8374b46dac721617c6d13a1da255fdeb369cf193416533f6e
languageName: node
linkType: hard
@@ -6061,36 +5970,36 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/typescript-estree@npm:8.19.0":
- version: 8.19.0
- resolution: "@typescript-eslint/typescript-estree@npm:8.19.0"
+"@typescript-eslint/typescript-estree@npm:8.19.1":
+ version: 8.19.1
+ resolution: "@typescript-eslint/typescript-estree@npm:8.19.1"
dependencies:
- "@typescript-eslint/types": "npm:8.19.0"
- "@typescript-eslint/visitor-keys": "npm:8.19.0"
+ "@typescript-eslint/types": "npm:8.19.1"
+ "@typescript-eslint/visitor-keys": "npm:8.19.1"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
minimatch: "npm:^9.0.4"
semver: "npm:^7.6.0"
- ts-api-utils: "npm:^1.3.0"
+ ts-api-utils: "npm:^2.0.0"
peerDependencies:
typescript: ">=4.8.4 <5.8.0"
- checksum: 10c0/ff47004588e8ff585af740b3e0bda07dc52310dbfeb2317eb4a723935740cf0c1953fc9ba57f14cf192bcfe373c46be833ba29d3303df8b501181bb852c7b822
+ checksum: 10c0/549d9d565a58a25fc8397a555506f2e8d29a740f5b6ed9105479e22de5aab89d9d535959034a8e9d4115adb435de09ee6987d28e8922052eea577842ddce1a7a
languageName: node
linkType: hard
-"@typescript-eslint/utils@npm:8.19.0, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.9.0":
- version: 8.19.0
- resolution: "@typescript-eslint/utils@npm:8.19.0"
+"@typescript-eslint/utils@npm:8.19.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.9.0":
+ version: 8.19.1
+ resolution: "@typescript-eslint/utils@npm:8.19.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
- "@typescript-eslint/scope-manager": "npm:8.19.0"
- "@typescript-eslint/types": "npm:8.19.0"
- "@typescript-eslint/typescript-estree": "npm:8.19.0"
+ "@typescript-eslint/scope-manager": "npm:8.19.1"
+ "@typescript-eslint/types": "npm:8.19.1"
+ "@typescript-eslint/typescript-estree": "npm:8.19.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
- checksum: 10c0/7731f7fb66d54491769ca68fd04728138ceb6b785778ad491f8e9b2147802fa0ff480e520f6ea5fb73c8484d13a2ed3e35d44635f5bf4cfbdb04c313154097a9
+ checksum: 10c0/f7d2fe9a2bd8cb3ae6fafe5e465882a6784b2acf81d43d194c579381b92651c2ffc0fca69d2a35eee119f539622752a0e9ec063aaec7576d5d2bfe68b441980d
languageName: node
linkType: hard
@@ -6122,13 +6031,13 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/visitor-keys@npm:8.19.0":
- version: 8.19.0
- resolution: "@typescript-eslint/visitor-keys@npm:8.19.0"
+"@typescript-eslint/visitor-keys@npm:8.19.1":
+ version: 8.19.1
+ resolution: "@typescript-eslint/visitor-keys@npm:8.19.1"
dependencies:
- "@typescript-eslint/types": "npm:8.19.0"
+ "@typescript-eslint/types": "npm:8.19.1"
eslint-visitor-keys: "npm:^4.2.0"
- checksum: 10c0/a293def05018bb2259506e23cd8f14349f4386d0e51231893fbddf96ef73c219d5f9fe17b82e3c104f5c23956dbd9b87af3cff5e84b887af243139a3b4bbbe0d
+ checksum: 10c0/117537450a099f51f3f0d39186f248ae370bdc1b7f6975dbdbffcfc89e6e1aa47c1870db790d4f778a48f2c1f6cd9c269b63867c12afaa424367c63dabee8fd0
languageName: node
linkType: hard
@@ -6182,7 +6091,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6195,7 +6104,7 @@ __metadata:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6210,7 +6119,7 @@ __metadata:
"@udecode/plate-code-block": "npm:42.0.0"
"@udecode/plate-heading": "npm:42.0.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6222,7 +6131,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6234,7 +6143,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6246,7 +6155,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6258,7 +6167,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6271,7 +6180,7 @@ __metadata:
"@udecode/plate": "workspace:^"
react-textarea-autosize: "npm:^8.5.3"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6284,7 +6193,7 @@ __metadata:
"@udecode/plate": "workspace:^"
prismjs: "npm:^1.29.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6296,7 +6205,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6309,19 +6218,19 @@ __metadata:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
linkType: soft
-"@udecode/plate-core@npm:42.0.0, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core":
+"@udecode/plate-core@npm:42.0.1, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core":
version: 0.0.0-use.local
resolution: "@udecode/plate-core@workspace:packages/core"
dependencies:
"@udecode/react-hotkeys": "npm:37.0.0"
"@udecode/react-utils": "npm:42.0.0"
- "@udecode/slate": "npm:42.0.0"
+ "@udecode/slate": "npm:42.0.1"
"@udecode/utils": "npm:42.0.0"
clsx: "npm:^2.1.1"
html-entities: "npm:^2.5.2"
@@ -6352,7 +6261,7 @@ __metadata:
"@udecode/plate-table": "npm:42.0.0"
papaparse: "npm:^5.4.1"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6364,7 +6273,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6376,7 +6285,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6390,7 +6299,7 @@ __metadata:
diff-match-patch-ts: "npm:^0.6.0"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6404,7 +6313,7 @@ __metadata:
lodash: "npm:^4.17.21"
raf: "npm:^3.4.1"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dnd: ">=14.0.0"
react-dnd-html5-backend: ">=14.0.0"
@@ -6419,12 +6328,12 @@ __metadata:
"@udecode/plate": "workspace:^"
"@udecode/plate-heading": "npm:42.0.0"
"@udecode/plate-indent": "npm:42.0.0"
- "@udecode/plate-indent-list": "npm:42.0.0"
+ "@udecode/plate-indent-list": "npm:42.0.1"
"@udecode/plate-media": "npm:42.0.0"
"@udecode/plate-table": "npm:42.0.0"
validator: "npm:^13.12.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6438,7 +6347,7 @@ __metadata:
"@udecode/plate": "workspace:^"
"@udecode/plate-combobox": "npm:42.0.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6451,7 +6360,7 @@ __metadata:
"@excalidraw/excalidraw": "npm:0.16.4"
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6463,7 +6372,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6477,7 +6386,7 @@ __metadata:
"@floating-ui/react": "npm:^0.26.23"
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6490,7 +6399,7 @@ __metadata:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6502,7 +6411,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6514,7 +6423,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6526,13 +6435,13 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
linkType: soft
-"@udecode/plate-indent-list@npm:42.0.0, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list":
+"@udecode/plate-indent-list@npm:42.0.1, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list":
version: 0.0.0-use.local
resolution: "@udecode/plate-indent-list@workspace:packages/indent-list"
dependencies:
@@ -6541,7 +6450,7 @@ __metadata:
"@udecode/plate-list": "npm:42.0.0"
clsx: "npm:^2.1.1"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6553,7 +6462,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6566,7 +6475,7 @@ __metadata:
"@udecode/plate": "workspace:^"
juice: "npm:^8.1.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6578,7 +6487,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6590,7 +6499,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6602,7 +6511,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6616,7 +6525,7 @@ __metadata:
"@udecode/plate-floating": "npm:42.0.0"
"@udecode/plate-normalizers": "npm:42.0.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6630,7 +6539,7 @@ __metadata:
"@udecode/plate-reset-node": "npm:42.0.0"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6642,11 +6551,12 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
+ marked: "npm:^15.0.6"
remark-gfm: "npm:4.0.0"
remark-parse: "npm:^11.0.0"
unified: "npm:^11.0.5"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6660,7 +6570,7 @@ __metadata:
"@udecode/plate": "workspace:^"
katex: "npm:0.16.11"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6673,7 +6583,7 @@ __metadata:
"@udecode/plate": "workspace:^"
js-video-url-parser: "npm:^0.5.1"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6686,7 +6596,7 @@ __metadata:
"@udecode/plate": "workspace:^"
"@udecode/plate-combobox": "npm:42.0.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6699,7 +6609,7 @@ __metadata:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6712,7 +6622,7 @@ __metadata:
"@udecode/plate": "workspace:^"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6725,7 +6635,7 @@ __metadata:
"@udecode/plate": "workspace:^"
peerDependencies:
"@playwright/test": ">=1.42.1"
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6737,7 +6647,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6749,7 +6659,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6761,7 +6671,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6774,7 +6684,7 @@ __metadata:
"@udecode/plate": "workspace:^"
copy-to-clipboard: "npm:^3.3.3"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6787,7 +6697,7 @@ __metadata:
"@udecode/plate": "workspace:^"
"@udecode/plate-combobox": "npm:42.0.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6801,7 +6711,7 @@ __metadata:
"@udecode/plate-diff": "npm:42.0.0"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6814,7 +6724,7 @@ __metadata:
"@udecode/plate": "workspace:^"
tabbable: "npm:^6.2.0"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6829,7 +6739,7 @@ __metadata:
"@udecode/plate-resizable": "npm:42.0.0"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6841,7 +6751,7 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6851,7 +6761,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@udecode/plate-test-utils@workspace:packages/test-utils"
dependencies:
- "@udecode/slate": "npm:42.0.0"
+ "@udecode/slate": "npm:42.0.1"
slate-hyperscript: "npm:0.100.0"
languageName: unknown
linkType: soft
@@ -6865,7 +6775,7 @@ __metadata:
"@udecode/plate-node-id": "npm:42.0.0"
lodash: "npm:^4.17.21"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6877,19 +6787,19 @@ __metadata:
dependencies:
"@udecode/plate": "workspace:^"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
linkType: soft
-"@udecode/plate-utils@npm:42.0.0, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils":
+"@udecode/plate-utils@npm:42.0.1, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils":
version: 0.0.0-use.local
resolution: "@udecode/plate-utils@workspace:packages/plate-utils"
dependencies:
- "@udecode/plate-core": "npm:42.0.0"
+ "@udecode/plate-core": "npm:42.0.1"
"@udecode/react-utils": "npm:42.0.0"
- "@udecode/slate": "npm:42.0.0"
+ "@udecode/slate": "npm:42.0.1"
"@udecode/utils": "npm:42.0.0"
clsx: "npm:^2.1.1"
lodash: "npm:^4.17.21"
@@ -6908,7 +6818,7 @@ __metadata:
"@udecode/plate": "workspace:^"
yjs: "npm:^13.6.19"
peerDependencies:
- "@udecode/plate": ">=42.0.0"
+ "@udecode/plate": ">=42.0.1"
react: ">=18.0.0"
react-dom: ">=18.0.0"
languageName: unknown
@@ -6918,11 +6828,11 @@ __metadata:
version: 0.0.0-use.local
resolution: "@udecode/plate@workspace:packages/plate"
dependencies:
- "@udecode/plate-core": "npm:42.0.0"
- "@udecode/plate-utils": "npm:42.0.0"
+ "@udecode/plate-core": "npm:42.0.1"
+ "@udecode/plate-utils": "npm:42.0.1"
"@udecode/react-hotkeys": "npm:37.0.0"
"@udecode/react-utils": "npm:42.0.0"
- "@udecode/slate": "npm:42.0.0"
+ "@udecode/slate": "npm:42.0.1"
"@udecode/utils": "npm:42.0.0"
peerDependencies:
react: ">=18.0.0"
@@ -6952,7 +6862,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@udecode/slate@npm:42.0.0, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate":
+"@udecode/slate@npm:42.0.1, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate":
version: 0.0.0-use.local
resolution: "@udecode/slate@workspace:packages/slate"
dependencies:
@@ -7081,7 +6991,7 @@ __metadata:
languageName: node
linkType: hard
-"acorn@npm:^8.0.0, acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.9.0":
+"acorn@npm:^8.0.0, acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.9.0":
version: 8.14.0
resolution: "acorn@npm:8.14.0"
bin:
@@ -7618,9 +7528,9 @@ __metadata:
linkType: hard
"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0":
- version: 2.5.1
- resolution: "bare-events@npm:2.5.1"
- checksum: 10c0/7ea9c491ff3a51cc2c7ea2ac8d459c7db2408bc31bc82cac73ba45a466f0e223194771782fa410cbe00b6553b67e7947f2d830c355b53922972ade20397e7abf
+ version: 2.5.3
+ resolution: "bare-events@npm:2.5.3"
+ checksum: 10c0/fc78e068cd1c7e75ab027121b69f104e315af122f10263734a1f3a7c5a8e2e5934d9a46638f5c9eafadf84d64c01fd87cd3169da4f7f8046df29a17fb1c532f5
languageName: node
linkType: hard
@@ -7770,9 +7680,9 @@ __metadata:
languageName: node
linkType: hard
-"browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.2":
- version: 4.24.3
- resolution: "browserslist@npm:4.24.3"
+"browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.3":
+ version: 4.24.4
+ resolution: "browserslist@npm:4.24.4"
dependencies:
caniuse-lite: "npm:^1.0.30001688"
electron-to-chromium: "npm:^1.5.73"
@@ -7780,7 +7690,7 @@ __metadata:
update-browserslist-db: "npm:^1.1.1"
bin:
browserslist: cli.js
- checksum: 10c0/bab261ef7b6e1656a719a9fa31240ae7ce4d5ba68e479f6b11e348d819346ab4c0ff6f4821f43adcc9c193a734b186775a83b37979e70a69d182965909fe569a
+ checksum: 10c0/db7ebc1733cf471e0b490b4f47e3e2ea2947ce417192c9246644e92c667dd56a71406cc58f62ca7587caf828364892e9952904a02b7aead752bc65b62a37cfe9
languageName: node
linkType: hard
@@ -7979,9 +7889,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688":
- version: 1.0.30001690
- resolution: "caniuse-lite@npm:1.0.30001690"
- checksum: 10c0/646bd469032afa90400a84dec30a2b00a6eda62c03ead358117e3f884cda8aacec02ec058a6dbee5eaf9714f83e483b9b0eb4fb42febb8076569f5ca40f1d347
+ version: 1.0.30001692
+ resolution: "caniuse-lite@npm:1.0.30001692"
+ checksum: 10c0/fca5105561ea12f3de593f3b0f062af82f7d07519e8dbcb97f34e7fd23349bcef1b1622a9a6cd2164d98e3d2f20059ef7e271edae46567aef88caf4c16c7708a
languageName: node
linkType: hard
@@ -8544,11 +8454,11 @@ __metadata:
linkType: hard
"core-js-compat@npm:^3.37.0":
- version: 3.39.0
- resolution: "core-js-compat@npm:3.39.0"
+ version: 3.40.0
+ resolution: "core-js-compat@npm:3.40.0"
dependencies:
- browserslist: "npm:^4.24.2"
- checksum: 10c0/880579a3dab235e3b6350f1e324269c600753b48e891ea859331618d5051e68b7a95db6a03ad2f3cc7df4397318c25a5bc7740562ad39e94f56568638d09d414
+ browserslist: "npm:^4.24.3"
+ checksum: 10c0/44f6e88726fe266a5be9581a79766800478a8d5c492885f2d4c2a4e2babd9b06bc1689d5340d3a61ae7332f990aff2e83b6203ff8773137a627cfedfbeefabeb
languageName: node
linkType: hard
@@ -8629,7 +8539,7 @@ __metadata:
languageName: node
linkType: hard
-"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6":
+"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
dependencies:
@@ -9280,9 +9190,9 @@ __metadata:
linkType: hard
"electron-to-chromium@npm:^1.5.73":
- version: 1.5.76
- resolution: "electron-to-chromium@npm:1.5.76"
- checksum: 10c0/5a977be9fd5810769a7b4eae0e4b41b6beca65f2b3f3b7442819f6c93366d767d183cfbf408714f944a9bf3aa304f8c9ab9d0cdfd8e878ab8f2cbb61f8b22acd
+ version: 1.5.80
+ resolution: "electron-to-chromium@npm:1.5.80"
+ checksum: 10c0/6aaf1891e1b05251efac6f4a63c0ddccf567f0f76506cf0cb284f11413762423fddd7786558066f74c3a95e2a533dad7a97bebe38779b46b7a799d8dd20cea53
languageName: node
linkType: hard
@@ -10507,16 +10417,6 @@ __metadata:
languageName: node
linkType: hard
-"eslint-scope@npm:^8.2.0":
- version: 8.2.0
- resolution: "eslint-scope@npm:8.2.0"
- dependencies:
- esrecurse: "npm:^4.3.0"
- estraverse: "npm:^5.2.0"
- checksum: 10c0/8d2d58e2136d548ac7e0099b1a90d9fab56f990d86eb518de1247a7066d38c908be2f3df477a79cf60d70b30ba18735d6c6e70e9914dca2ee515a729975d70d6
- languageName: node
- linkType: hard
-
"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
version: 3.4.3
resolution: "eslint-visitor-keys@npm:3.4.3"
@@ -10579,66 +10479,6 @@ __metadata:
languageName: node
linkType: hard
-"eslint@npm:^9.17.0":
- version: 9.17.0
- resolution: "eslint@npm:9.17.0"
- dependencies:
- "@eslint-community/eslint-utils": "npm:^4.2.0"
- "@eslint-community/regexpp": "npm:^4.12.1"
- "@eslint/config-array": "npm:^0.19.0"
- "@eslint/core": "npm:^0.9.0"
- "@eslint/eslintrc": "npm:^3.2.0"
- "@eslint/js": "npm:9.17.0"
- "@eslint/plugin-kit": "npm:^0.2.3"
- "@humanfs/node": "npm:^0.16.6"
- "@humanwhocodes/module-importer": "npm:^1.0.1"
- "@humanwhocodes/retry": "npm:^0.4.1"
- "@types/estree": "npm:^1.0.6"
- "@types/json-schema": "npm:^7.0.15"
- ajv: "npm:^6.12.4"
- chalk: "npm:^4.0.0"
- cross-spawn: "npm:^7.0.6"
- debug: "npm:^4.3.2"
- escape-string-regexp: "npm:^4.0.0"
- eslint-scope: "npm:^8.2.0"
- eslint-visitor-keys: "npm:^4.2.0"
- espree: "npm:^10.3.0"
- esquery: "npm:^1.5.0"
- esutils: "npm:^2.0.2"
- fast-deep-equal: "npm:^3.1.3"
- file-entry-cache: "npm:^8.0.0"
- find-up: "npm:^5.0.0"
- glob-parent: "npm:^6.0.2"
- ignore: "npm:^5.2.0"
- imurmurhash: "npm:^0.1.4"
- is-glob: "npm:^4.0.0"
- json-stable-stringify-without-jsonify: "npm:^1.0.1"
- lodash.merge: "npm:^4.6.2"
- minimatch: "npm:^3.1.2"
- natural-compare: "npm:^1.4.0"
- optionator: "npm:^0.9.3"
- peerDependencies:
- jiti: "*"
- peerDependenciesMeta:
- jiti:
- optional: true
- bin:
- eslint: bin/eslint.js
- checksum: 10c0/9edd8dd782b4ae2eb00a158ed4708194835d4494d75545fa63a51f020ed17f865c49b4ae1914a2ecbc7fdb262bd8059e811aeef9f0bae63dced9d3293be1bbdd
- languageName: node
- linkType: hard
-
-"espree@npm:^10.0.1, espree@npm:^10.3.0":
- version: 10.3.0
- resolution: "espree@npm:10.3.0"
- dependencies:
- acorn: "npm:^8.14.0"
- acorn-jsx: "npm:^5.3.2"
- eslint-visitor-keys: "npm:^4.2.0"
- checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462
- languageName: node
- linkType: hard
-
"espree@npm:^9.6.0, espree@npm:^9.6.1":
version: 9.6.1
resolution: "espree@npm:9.6.1"
@@ -11093,15 +10933,6 @@ __metadata:
languageName: node
linkType: hard
-"file-entry-cache@npm:^8.0.0":
- version: 8.0.0
- resolution: "file-entry-cache@npm:8.0.0"
- dependencies:
- flat-cache: "npm:^4.0.0"
- checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638
- languageName: node
- linkType: hard
-
"file-selector@npm:0.2.4":
version: 0.2.4
resolution: "file-selector@npm:0.2.4"
@@ -11155,19 +10986,15 @@ __metadata:
linkType: hard
"find-process@npm:^1.4.7":
- version: 1.4.9
- resolution: "find-process@npm:1.4.9"
+ version: 1.4.10
+ resolution: "find-process@npm:1.4.10"
dependencies:
chalk: "npm:~4.1.2"
commander: "npm:^12.1.0"
- debug: "npm:^4.4.0"
- eslint: "npm:^9.17.0"
- glob: "npm:^11.0.0"
loglevel: "npm:^1.9.2"
- rimraf: "npm:^6.0.1"
bin:
find-process: bin/find-process.js
- checksum: 10c0/82156097f1ae442850ca701a66a8f5129eb39dc01dca24639ff316439bc79e03a54bfb87c1f7c4337cab167ca3ca2b2f6a88a2c73451563536271733e27761ff
+ checksum: 10c0/5f570ba55dca0659d7c063e2296b44fd3329eabff3660399c9db39a59a5f47a5925e0520dd6ea5eae6a36e28592c27701ca75d595c7a6ed12bae421995f5e59f
languageName: node
linkType: hard
@@ -11229,16 +11056,6 @@ __metadata:
languageName: node
linkType: hard
-"flat-cache@npm:^4.0.0":
- version: 4.0.1
- resolution: "flat-cache@npm:4.0.1"
- dependencies:
- flatted: "npm:^3.2.9"
- keyv: "npm:^4.5.4"
- checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc
- languageName: node
- linkType: hard
-
"flatted@npm:^3.2.9":
version: 3.3.2
resolution: "flatted@npm:3.3.2"
@@ -11300,11 +11117,11 @@ __metadata:
linkType: hard
"framer-motion@npm:^11.5.4":
- version: 11.15.0
- resolution: "framer-motion@npm:11.15.0"
+ version: 11.16.4
+ resolution: "framer-motion@npm:11.16.4"
dependencies:
- motion-dom: "npm:^11.14.3"
- motion-utils: "npm:^11.14.3"
+ motion-dom: "npm:^11.16.4"
+ motion-utils: "npm:^11.16.0"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
@@ -11317,7 +11134,7 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: 10c0/59f1c1eea09a5cbda346624a7d700bdb1ccff8a8528ed145009db974283064c3a4e55ca9eaaf4950494f254f6233c37634735b9bd8463b25ffeef624030894d6
+ checksum: 10c0/f0063206098e9ac76da03a5fd47ca23165021c102bd2b89b8bdf81307bc86641d7110194bcedaba2dbdc28ee8d97ce0c6f9136d867aaa21fa2a50a8fd3ec9797
languageName: node
linkType: hard
@@ -11694,13 +11511,6 @@ __metadata:
languageName: node
linkType: hard
-"globals@npm:^14.0.0":
- version: 14.0.0
- resolution: "globals@npm:14.0.0"
- checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d
- languageName: node
- linkType: hard
-
"globals@npm:^15.7.0":
version: 15.14.0
resolution: "globals@npm:15.14.0"
@@ -12414,9 +12224,9 @@ __metadata:
linkType: hard
"inflection@npm:^3.0.0":
- version: 3.0.1
- resolution: "inflection@npm:3.0.1"
- checksum: 10c0/80fe70db2b55579a15fcd595660c30a7f355577a120bc6e914747ed962b8f89b48a44496bb689b79b5bb4e7b035e21793753a95065beb46536e5ebe25118b4fe
+ version: 3.0.2
+ resolution: "inflection@npm:3.0.2"
+ checksum: 10c0/ac6b635f029b27834313ce30188d74607fe9751c729bf91698675b2fd82489e0195e884d8a9455676064a74b2db77b407d35b56ada0978d0e8194e72202bf7af
languageName: node
linkType: hard
@@ -13906,7 +13716,7 @@ __metadata:
languageName: node
linkType: hard
-"keyv@npm:^4.5.3, keyv@npm:^4.5.4":
+"keyv@npm:^4.5.3":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
dependencies:
@@ -14227,9 +14037,9 @@ __metadata:
linkType: hard
"long@npm:^5.0.0":
- version: 5.2.3
- resolution: "long@npm:5.2.3"
- checksum: 10c0/6a0da658f5ef683b90330b1af76f06790c623e148222da9d75b60e266bbf88f803232dd21464575681638894a84091616e7f89557aa087fd14116c0f4e0e43d9
+ version: 5.2.4
+ resolution: "long@npm:5.2.4"
+ checksum: 10c0/0cf819ce2a7bbe48663e79233917552c7667b11e68d4d9ea4ebb99173042509d9af461e5211c22939b913332c264d9a1135937ea533cbd05bc4f8cf46f6d2e07
languageName: node
linkType: hard
@@ -14383,6 +14193,15 @@ __metadata:
languageName: node
linkType: hard
+"marked@npm:^15.0.6":
+ version: 15.0.6
+ resolution: "marked@npm:15.0.6"
+ bin:
+ marked: bin/marked.js
+ checksum: 10c0/8f30972ac5fdf879353484bdd7717409c241d15031a58bbc483070dedb58e4b314c41c0b59b78e536658907c02ee149eaf4b9be221f198df97beae703f529d40
+ languageName: node
+ linkType: hard
+
"match-sorter@npm:6.3.4":
version: 6.3.4
resolution: "match-sorter@npm:6.3.4"
@@ -14747,14 +14566,14 @@ __metadata:
linkType: hard
"memfs@npm:^4.8.2":
- version: 4.15.3
- resolution: "memfs@npm:4.15.3"
+ version: 4.17.0
+ resolution: "memfs@npm:4.17.0"
dependencies:
"@jsonjoy.com/json-pack": "npm:^1.0.3"
"@jsonjoy.com/util": "npm:^1.3.0"
tree-dump: "npm:^1.0.1"
tslib: "npm:^2.0.0"
- checksum: 10c0/95bab707edcc86ac5acdb3ca53b420be34aa04167d0f03398b702bfadb44b72a6a27198b93593e7d9094cf911d70a0d679301fbc0e309f7ac43fa04a676b1b60
+ checksum: 10c0/2901f69e80e1fbefa8aafe994a253fff6f34eb176d8b80d57476311611e516a11ab4dd93f852c8739fe04f2b57d6a4ca7a1828fa0bd401ce631bcac214b3d58b
languageName: node
linkType: hard
@@ -15726,17 +15545,19 @@ __metadata:
languageName: node
linkType: hard
-"motion-dom@npm:^11.14.3":
- version: 11.14.3
- resolution: "motion-dom@npm:11.14.3"
- checksum: 10c0/14989aba2981dcf618dc77d202ac35325366e645fd9e57c6942d88d0696263bbe7d0680da2e5f84e93339a67255bdbfebb8a4994a46584a661dd9a1e136fa7a1
+"motion-dom@npm:^11.16.4":
+ version: 11.16.4
+ resolution: "motion-dom@npm:11.16.4"
+ dependencies:
+ motion-utils: "npm:^11.16.0"
+ checksum: 10c0/66de6d40aef59d3004aaf17e39c7c2d2c679306207fdb28c73917d6a05e8c962747fea53d2e516c25d994109d38e127943a759fed18a0fadfc2572a3335fc0d2
languageName: node
linkType: hard
-"motion-utils@npm:^11.14.3":
- version: 11.14.3
- resolution: "motion-utils@npm:11.14.3"
- checksum: 10c0/7459bcb27311b72b416b2618cbfd56bad7d0fbec27736529e3f45a561fa78c43bf82e05338d9d9b765649b57d1c693821e83b30c6ba449d6f7f66c5245f072fb
+"motion-utils@npm:^11.16.0":
+ version: 11.16.0
+ resolution: "motion-utils@npm:11.16.0"
+ checksum: 10c0/e68efa08b9546a2fb065537cedcbab1a416d43cdc5773e02ea01408c276e56bbea9ef76d330e80d8536a6ac585b0bbb6f4f2b9d97637d8d36418483e4492ddff
languageName: node
linkType: hard
@@ -16559,9 +16380,9 @@ __metadata:
linkType: hard
"papaparse@npm:^5.4.1":
- version: 5.4.1
- resolution: "papaparse@npm:5.4.1"
- checksum: 10c0/201f37c4813453fed5bfb4c01816696b099d2db9ff1e8fb610acc4771fdde91d2a22b6094721edb0fedb21ca3c46f04263f68be4beb3e35b8c72278f0cedc7b7
+ version: 5.5.0
+ resolution: "papaparse@npm:5.5.0"
+ checksum: 10c0/09e2550e349e71762592403a9eeaf411466e937f4474e1f9d688868df74b77c4e0550c8e6b392f788393e145a8aecb3f4d9710ebf86794ccc8623901992db23a
languageName: node
linkType: hard
@@ -18692,28 +18513,28 @@ __metadata:
linkType: hard
"rollup@npm:^4.24.0":
- version: 4.29.2
- resolution: "rollup@npm:4.29.2"
- dependencies:
- "@rollup/rollup-android-arm-eabi": "npm:4.29.2"
- "@rollup/rollup-android-arm64": "npm:4.29.2"
- "@rollup/rollup-darwin-arm64": "npm:4.29.2"
- "@rollup/rollup-darwin-x64": "npm:4.29.2"
- "@rollup/rollup-freebsd-arm64": "npm:4.29.2"
- "@rollup/rollup-freebsd-x64": "npm:4.29.2"
- "@rollup/rollup-linux-arm-gnueabihf": "npm:4.29.2"
- "@rollup/rollup-linux-arm-musleabihf": "npm:4.29.2"
- "@rollup/rollup-linux-arm64-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-arm64-musl": "npm:4.29.2"
- "@rollup/rollup-linux-loongarch64-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-riscv64-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-s390x-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-x64-gnu": "npm:4.29.2"
- "@rollup/rollup-linux-x64-musl": "npm:4.29.2"
- "@rollup/rollup-win32-arm64-msvc": "npm:4.29.2"
- "@rollup/rollup-win32-ia32-msvc": "npm:4.29.2"
- "@rollup/rollup-win32-x64-msvc": "npm:4.29.2"
+ version: 4.30.1
+ resolution: "rollup@npm:4.30.1"
+ dependencies:
+ "@rollup/rollup-android-arm-eabi": "npm:4.30.1"
+ "@rollup/rollup-android-arm64": "npm:4.30.1"
+ "@rollup/rollup-darwin-arm64": "npm:4.30.1"
+ "@rollup/rollup-darwin-x64": "npm:4.30.1"
+ "@rollup/rollup-freebsd-arm64": "npm:4.30.1"
+ "@rollup/rollup-freebsd-x64": "npm:4.30.1"
+ "@rollup/rollup-linux-arm-gnueabihf": "npm:4.30.1"
+ "@rollup/rollup-linux-arm-musleabihf": "npm:4.30.1"
+ "@rollup/rollup-linux-arm64-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-arm64-musl": "npm:4.30.1"
+ "@rollup/rollup-linux-loongarch64-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-riscv64-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-s390x-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-x64-gnu": "npm:4.30.1"
+ "@rollup/rollup-linux-x64-musl": "npm:4.30.1"
+ "@rollup/rollup-win32-arm64-msvc": "npm:4.30.1"
+ "@rollup/rollup-win32-ia32-msvc": "npm:4.30.1"
+ "@rollup/rollup-win32-x64-msvc": "npm:4.30.1"
"@types/estree": "npm:1.0.6"
fsevents: "npm:~2.3.2"
dependenciesMeta:
@@ -18759,7 +18580,7 @@ __metadata:
optional: true
bin:
rollup: dist/bin/rollup
- checksum: 10c0/460931c5a43e2f7aca59a7db585b938145201bd2ad0bbd941d3bc15f74a21d0437e6f8a398605ab7d870f49ce346ae5759940a0158e679da5c0228afc477ff81
+ checksum: 10c0/a318c57e2ca9741e1503bcd75483949c6e83edd72234a468010a3098a34248f523e44f7ad4fde90dc5c2da56abc1b78ac42a9329e1dbd708682728adbd8df7cc
languageName: node
linkType: hard
@@ -20462,12 +20283,12 @@ __metadata:
languageName: node
linkType: hard
-"ts-api-utils@npm:^1.3.0":
- version: 1.4.3
- resolution: "ts-api-utils@npm:1.4.3"
+"ts-api-utils@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "ts-api-utils@npm:2.0.0"
peerDependencies:
- typescript: ">=4.2.0"
- checksum: 10c0/e65dc6e7e8141140c23e1dc94984bf995d4f6801919c71d6dc27cf0cd51b100a91ffcfe5217626193e5bea9d46831e8586febdc7e172df3f1091a7384299e23a
+ typescript: ">=4.8.4"
+ checksum: 10c0/6165e29a5b75bd0218e3cb0f9ee31aa893dbd819c2e46dbb086c841121eb0436ed47c2c18a20cb3463d74fd1fb5af62e2604ba5971cc48e5b38ebbdc56746dfc
languageName: node
linkType: hard
@@ -20904,9 +20725,9 @@ __metadata:
linkType: hard
"type-fest@npm:^4.10.0":
- version: 4.31.0
- resolution: "type-fest@npm:4.31.0"
- checksum: 10c0/a5bb69e3b0f82e068af8c645ac3d50b1fa5c588ebc83735a6add4ef6dacf277bb3605801f66c72c069af20120ee7387a3ae6dd84e12c152f5982784c710b4051
+ version: 4.32.0
+ resolution: "type-fest@npm:4.32.0"
+ checksum: 10c0/e2e877055487d109eba99afc58211db4a480837ff7b243c7de0b3e2ac29fdce55ab55e201c64cb1a8b2aeffce7e8f60ae3ce3a2f7e6fb68261d62743e54288ba
languageName: node
linkType: hard
@@ -21301,16 +21122,16 @@ __metadata:
linkType: hard
"update-browserslist-db@npm:^1.1.1":
- version: 1.1.1
- resolution: "update-browserslist-db@npm:1.1.1"
+ version: 1.1.2
+ resolution: "update-browserslist-db@npm:1.1.2"
dependencies:
escalade: "npm:^3.2.0"
- picocolors: "npm:^1.1.0"
+ picocolors: "npm:^1.1.1"
peerDependencies:
browserslist: ">= 4.21.0"
bin:
update-browserslist-db: cli.js
- checksum: 10c0/536a2979adda2b4be81b07e311bd2f3ad5e978690987956bc5f514130ad50cac87cd22c710b686d79731e00fbee8ef43efe5fcd72baa241045209195d43dcc80
+ checksum: 10c0/9cb353998d6d7d6ba1e46b8fa3db888822dd972212da4eda609d185eb5c3557a93fd59780ceb757afd4d84240518df08542736969e6a5d6d6ce2d58e9363aac6
languageName: node
linkType: hard
@@ -22366,8 +22187,8 @@ __metadata:
linkType: hard
"zustand@npm:^5.0.2":
- version: 5.0.2
- resolution: "zustand@npm:5.0.2"
+ version: 5.0.3
+ resolution: "zustand@npm:5.0.3"
peerDependencies:
"@types/react": ">=18.0.0"
immer: ">=9.0.6"
@@ -22382,7 +22203,7 @@ __metadata:
optional: true
use-sync-external-store:
optional: true
- checksum: 10c0/d9bb048d8129fd1aaed3fda974991b15a7c9c31ef06f78e9bf5c4b3678f249850764a6dadb8c93127257d07831995cf7a048281658a37c5d1143ad6f397fe37c
+ checksum: 10c0/dad96c6c123fda088c583d5df6692e9245cd207583078dc15f93d255a67b0f346bad4535545c98852ecde93d254812a0c799579dfde2ab595016b99fbe20e4d5
languageName: node
linkType: hard