UNPKG

@udecode/plate-core

Version:

The core of Plate – a plugin system for slate

1,601 lines (1,559 loc) 215 kB
// src/lib/editor/withSlate.ts import { createEditor } from "@udecode/slate"; import { nanoid as nanoid2 } from "nanoid"; // src/internal/plugin/resolvePlugins.ts import { assignLegacyApi, assignLegacyTransforms, syncLegacyMethods } from "@udecode/slate"; import { isDefined as isDefined2 } from "@udecode/utils"; import merge2 from "lodash/merge.js"; import { createZustandStore } from "zustand-x"; // src/lib/plugin/createSlatePlugin.ts import { isDefined } from "@udecode/utils"; // src/internal/utils/isFunction.ts function isFunction(value) { return typeof value === "function"; } // src/internal/utils/mergePlugins.ts import mergeWith from "lodash/mergeWith.js"; function mergePlugins(basePlugin, ...sourcePlugins) { return mergeWith( {}, basePlugin, ...sourcePlugins, (objValue, srcValue, key) => { if (Array.isArray(srcValue)) { return srcValue; } if (key === "options") { return { ...objValue, ...srcValue }; } } ); } // src/lib/plugin/createSlatePlugin.ts function createSlatePlugin(config = {}) { let baseConfig; let initialExtension; if (isFunction(config)) { baseConfig = { key: "" }; initialExtension = (editor) => config(editor); } else { baseConfig = config; } const key = baseConfig.key ?? ""; const plugin = mergePlugins( { key, __apiExtensions: [], __configuration: null, __extensions: initialExtension ? [initialExtension] : [], __selectorExtensions: [], api: {}, dependencies: [], editor: {}, handlers: {}, inject: {}, node: { type: key }, options: {}, override: {}, parser: {}, parsers: {}, plugins: [], priority: 100, render: {}, rules: {}, shortcuts: {}, transforms: {} }, config ); if (plugin.node.isLeaf && !isDefined(plugin.node.isDecoration)) { plugin.node.isDecoration = true; } plugin.configure = (config2) => { const newPlugin = { ...plugin }; newPlugin.__configuration = (ctx) => isFunction(config2) ? config2(ctx) : config2; return createSlatePlugin(newPlugin); }; plugin.configurePlugin = (p, config2) => { const newPlugin = { ...plugin }; const configureNestedPlugin = (plugins2) => { let found = false; const updatedPlugins = plugins2.map((nestedPlugin) => { if (nestedPlugin.key === p.key) { found = true; return createSlatePlugin({ ...nestedPlugin, __configuration: (ctx) => isFunction(config2) ? config2(ctx) : config2 }); } if (nestedPlugin.plugins && nestedPlugin.plugins.length > 0) { const result2 = configureNestedPlugin(nestedPlugin.plugins); if (result2.found) { found = true; return { ...nestedPlugin, plugins: result2.plugins }; } } return nestedPlugin; }); return { found, plugins: updatedPlugins }; }; const result = configureNestedPlugin(newPlugin.plugins); newPlugin.plugins = result.plugins; return createSlatePlugin(newPlugin); }; plugin.extendEditorApi = (extension) => { const newPlugin = { ...plugin }; newPlugin.__apiExtensions = [ ...newPlugin.__apiExtensions, { extension, isPluginSpecific: false } ]; return createSlatePlugin(newPlugin); }; plugin.extendSelectors = (extension) => { const newPlugin = { ...plugin }; newPlugin.__selectorExtensions = [ ...newPlugin.__selectorExtensions, extension ]; return createSlatePlugin(newPlugin); }; plugin.extendApi = (extension) => { const newPlugin = { ...plugin }; newPlugin.__apiExtensions = [ ...newPlugin.__apiExtensions, { extension, isPluginSpecific: true } ]; return createSlatePlugin(newPlugin); }; plugin.extendEditorTransforms = (extension) => { const newPlugin = { ...plugin }; newPlugin.__apiExtensions = [ ...newPlugin.__apiExtensions, { extension, isPluginSpecific: false, isTransform: true } ]; return createSlatePlugin(newPlugin); }; plugin.extendTransforms = (extension) => { const newPlugin = { ...plugin }; newPlugin.__apiExtensions = [ ...newPlugin.__apiExtensions, { extension, isPluginSpecific: true, isTransform: true } ]; return createSlatePlugin(newPlugin); }; plugin.overrideEditor = (extension) => { const newPlugin = { ...plugin }; newPlugin.__apiExtensions = [ ...newPlugin.__apiExtensions, { extension, isOverride: true, isPluginSpecific: false, isTransform: true } ]; return createSlatePlugin(newPlugin); }; plugin.extend = (extendConfig) => { let newPlugin = { ...plugin }; if (isFunction(extendConfig)) { newPlugin.__extensions = [ ...newPlugin.__extensions, extendConfig ]; } else { newPlugin = mergePlugins(newPlugin, extendConfig); } return createSlatePlugin(newPlugin); }; plugin.clone = () => mergePlugins(plugin); plugin.extendPlugin = (p, extendConfig) => { const newPlugin = { ...plugin }; const extendNestedPlugin = (plugins2) => { let found = false; const updatedPlugins = plugins2.map((nestedPlugin) => { if (nestedPlugin.key === p.key) { found = true; return createSlatePlugin({ ...nestedPlugin, __extensions: [ ...nestedPlugin.__extensions, (ctx) => isFunction(extendConfig) ? extendConfig(ctx) : extendConfig ] }); } if (nestedPlugin.plugins && nestedPlugin.plugins.length > 0) { const result2 = extendNestedPlugin(nestedPlugin.plugins); if (result2.found) { found = true; return { ...nestedPlugin, plugins: result2.plugins }; } } return nestedPlugin; }); return { found, plugins: updatedPlugins }; }; const result = extendNestedPlugin(newPlugin.plugins); newPlugin.plugins = result.plugins; if (!result.found) { newPlugin.plugins.push( createSlatePlugin({ key: p.key, __extensions: [ (ctx) => isFunction(extendConfig) ? extendConfig(ctx) : extendConfig ] }) ); } return createSlatePlugin(newPlugin); }; plugin.withComponent = (component) => { return plugin.extend({ node: { component }, render: { node: component } }); }; return plugin; } function createTSlatePlugin(config = {}) { return createSlatePlugin(config); } // src/lib/plugin/getEditorPlugin.ts function getEditorPlugin(editor, p) { const plugin = editor.getPlugin(p); return { api: editor.api, editor, plugin, setOption: (keyOrOptions, value) => editor.setOption(plugin, keyOrOptions, value), setOptions: (options) => editor.setOptions(plugin, options), tf: editor.transforms, type: plugin.node.type, getOption: (key, ...args) => editor.getOption(plugin, key, ...args), getOptions: () => editor.getOptions(plugin) }; } // src/internal/plugin/resolvePlugin.ts import merge from "lodash/merge.js"; var resolvePlugin = (editor, _plugin) => { let plugin = mergePlugins({}, _plugin); plugin.__resolved = true; if (plugin.__configuration) { const configResult = plugin.__configuration( getEditorPlugin(editor, plugin) ); plugin = mergePlugins(plugin, configResult); delete plugin.__configuration; } if (plugin.__extensions && plugin.__extensions.length > 0) { plugin.__extensions.forEach((extension) => { plugin = mergePlugins( plugin, extension(getEditorPlugin(editor, plugin)) ); }); plugin.__extensions = []; } const targetPluginToInject = plugin.inject?.targetPluginToInject; const targetPlugins = plugin.inject?.targetPlugins; if (targetPluginToInject && targetPlugins && targetPlugins.length > 0) { plugin.inject = plugin.inject || {}; plugin.inject.plugins = merge( {}, plugin.inject.plugins, Object.fromEntries( targetPlugins.map((targetPlugin) => { const injectedPlugin = targetPluginToInject({ ...getEditorPlugin(editor, plugin), targetPlugin }); return [targetPlugin, injectedPlugin]; }) ) ); } if (plugin.node?.component) { plugin.render.node = plugin.node.component; } if (plugin.render?.node) { plugin.node.component = plugin.render.node; } validatePlugin(editor, plugin); return plugin; }; var validatePlugin = (editor, plugin) => { if (!plugin.__extensions) { editor.api.debug.error( `Invalid plugin '${plugin.key}', you should use createSlatePlugin.`, "USE_CREATE_PLUGIN" ); } if (plugin.node.isElement && plugin.node.isLeaf) { editor.api.debug.error( `Plugin ${plugin.key} cannot be both an element and a leaf.`, "PLUGIN_NODE_TYPE" ); } }; // src/lib/plugin/getSlatePlugin.ts function getSlatePlugin(editor, p) { let plugin = p; const editorPlugin = editor.plugins[p.key]; if (!editorPlugin) { if (!plugin.node) { plugin = createSlatePlugin(plugin); } return plugin.__resolved ? plugin : resolvePlugin(editor, plugin); } return editorPlugin; } function getPluginType(editor, key) { const p = editor.getPlugin({ key }); return p.node.type ?? p.key ?? ""; } var getPluginTypes = (editor, keys) => keys.map((key) => editor.getType(key)); var getPluginKey = (editor, type) => editor.meta.pluginCache.node.types[type]; var getPluginByType = (editor, type) => { const key = getPluginKey(editor, type); if (!key) return null; return editor.getPlugin({ key }); }; var getContainerTypes = (editor) => { return getPluginTypes(editor, editor.meta.pluginCache.node.isContainer); }; // src/internal/plugin/resolvePlugins.ts var resolvePlugins = (editor, plugins2 = []) => { editor.plugins = {}; editor.meta.pluginList = []; editor.meta.shortcuts = {}; editor.meta.components = {}; editor.meta.pluginCache = { decorate: [], handlers: { onChange: [] }, inject: { nodeProps: [] }, node: { isContainer: [], isElement: [], isInline: [], isLeaf: [], isMarkableVoid: [], isNotSelectable: [], isStrictSiblings: [], isVoid: [], types: {} }, normalizeInitialValue: [], render: { aboveEditable: [], aboveNodes: [], aboveSlate: [], afterContainer: [], afterEditable: [], beforeContainer: [], beforeEditable: [], belowNodes: [], belowRootNodes: [] }, rules: { match: [] }, useHooks: [] }; const resolvedPlugins = resolveAndSortPlugins(editor, plugins2); applyPluginsToEditor(editor, resolvedPlugins); resolvePluginOverrides(editor); resolvePluginStores(editor); editor.meta.pluginList.forEach((plugin) => { if (plugin.extendEditor) { editor = plugin.extendEditor(getEditorPlugin(editor, plugin)); syncLegacyMethods(editor); } resolvePluginMethods(editor, plugin); if (plugin.node?.isContainer) { editor.meta.pluginCache.node.isContainer.push(plugin.key); } editor.meta.pluginCache.node.types[plugin.node.type] = plugin.key; if (plugin.inject?.nodeProps) { editor.meta.pluginCache.inject.nodeProps.push(plugin.key); } if (plugin.render?.node) { editor.meta.components[plugin.key] = plugin.render.node; } if (plugin.node?.isLeaf) { editor.meta.pluginCache.node.isLeaf.push(plugin.key); } if (plugin.node?.isElement) { editor.meta.pluginCache.node.isElement.push(plugin.key); } if (plugin.node?.isInline) { editor.meta.pluginCache.node.isInline.push(plugin.key); } if (plugin.node?.isVoid) { editor.meta.pluginCache.node.isVoid.push(plugin.key); } if (plugin.node?.isMarkableVoid) { editor.meta.pluginCache.node.isMarkableVoid.push(plugin.key); } if (plugin.node?.isStrictSiblings) { editor.meta.pluginCache.node.isStrictSiblings.push(plugin.key); } if (plugin.node?.isSelectable === false) { editor.meta.pluginCache.node.isNotSelectable.push(plugin.key); } if (plugin.render.aboveEditable) { editor.meta.pluginCache.render.aboveEditable.push(plugin.key); } if (plugin.render.aboveSlate) { editor.meta.pluginCache.render.aboveSlate.push(plugin.key); } if (plugin.render.afterEditable) { editor.meta.pluginCache.render.afterEditable.push(plugin.key); } if (plugin.render.beforeEditable) { editor.meta.pluginCache.render.beforeEditable.push(plugin.key); } if (plugin.rules?.match) { editor.meta.pluginCache.rules.match.push(plugin.key); } if (plugin.render.afterContainer) { editor.meta.pluginCache.render.afterContainer.push(plugin.key); } if (plugin.render.beforeContainer) { editor.meta.pluginCache.render.beforeContainer.push(plugin.key); } if (plugin.render.belowRootNodes) { editor.meta.pluginCache.render.belowRootNodes.push(plugin.key); } if (plugin.normalizeInitialValue) { editor.meta.pluginCache.normalizeInitialValue.push(plugin.key); } if (plugin.decorate) { editor.meta.pluginCache.decorate.push(plugin.key); } if (plugin.render.aboveNodes) { editor.meta.pluginCache.render.aboveNodes.push(plugin.key); } if (plugin.render.belowNodes) { editor.meta.pluginCache.render.belowNodes.push(plugin.key); } if (plugin.useHooks) { editor.meta.pluginCache.useHooks.push(plugin.key); } if (plugin.handlers?.onChange) { editor.meta.pluginCache.handlers.onChange.push(plugin.key); } }); resolvePluginShortcuts(editor); return editor; }; var resolvePluginStores = (editor) => { editor.meta.pluginList.forEach((plugin) => { let store = createZustandStore(plugin.options, { mutative: true, name: plugin.key }); if (plugin.__selectorExtensions && plugin.__selectorExtensions.length > 0) { plugin.__selectorExtensions.forEach((extension) => { const extendedOptions = extension(getEditorPlugin(editor, plugin)); store = store.extendSelectors(() => extendedOptions); }); } plugin.optionsStore = store; }); }; var resolvePluginMethods = (editor, plugin) => { Object.entries(plugin.api).forEach(([apiKey, apiFunction]) => { editor.api[apiKey] = apiFunction; }); if (plugin.__apiExtensions && plugin.__apiExtensions.length > 0) { plugin.__apiExtensions.forEach( ({ extension, isOverride, isPluginSpecific, isTransform }) => { const newExtensions = extension(getEditorPlugin(editor, plugin)); if (isOverride) { if (newExtensions.api) { merge2(editor.api, newExtensions.api); merge2(plugin.api, newExtensions.api); assignLegacyApi(editor, editor.api); } if (newExtensions.transforms) { merge2(editor.transforms, newExtensions.transforms); merge2(plugin.transforms, newExtensions.transforms); assignLegacyTransforms(editor, newExtensions.transforms); } } else if (isTransform) { if (isPluginSpecific) { if (!editor.transforms[plugin.key]) { editor.transforms[plugin.key] = {}; } if (!plugin.transforms[plugin.key]) { plugin.transforms[plugin.key] = {}; } merge2(editor.transforms[plugin.key], newExtensions); merge2(plugin.transforms[plugin.key], newExtensions); } else { merge2(editor.transforms, newExtensions); merge2(plugin.transforms, newExtensions); assignLegacyTransforms(editor, newExtensions); } } else { if (isPluginSpecific) { if (!editor.api[plugin.key]) { editor.api[plugin.key] = {}; } if (!plugin.api[plugin.key]) { plugin.api[plugin.key] = {}; } merge2(editor.api[plugin.key], newExtensions); merge2(plugin.api[plugin.key], newExtensions); } else { merge2(editor.api, newExtensions); merge2(plugin.api, newExtensions); assignLegacyApi(editor, editor.api); } } } ); delete plugin.__apiExtensions; } }; var resolvePluginShortcuts = (editor) => { editor.meta.shortcuts = {}; editor.meta.pluginList.forEach((plugin) => { Object.entries(plugin.shortcuts).forEach(([originalKey, hotkey]) => { const namespacedKey = `${plugin.key}.${originalKey}`; if (hotkey === null) { delete editor.meta.shortcuts[namespacedKey]; } else if (hotkey && typeof hotkey === "object") { const resolvedHotkey = { ...hotkey }; if (!resolvedHotkey.handler) { const pluginSpecificTransforms = plugin.transforms?.[plugin.key]; const pluginSpecificApi = plugin.api?.[plugin.key]; if (pluginSpecificTransforms?.[originalKey]) { resolvedHotkey.handler = () => { return pluginSpecificTransforms[originalKey](); }; } else if (pluginSpecificApi?.[originalKey]) { resolvedHotkey.handler = () => { return pluginSpecificApi[originalKey](); }; } } resolvedHotkey.priority = resolvedHotkey.priority ?? plugin.priority; editor.meta.shortcuts[namespacedKey] = resolvedHotkey; } }); }); }; var flattenAndResolvePlugins = (editor, plugins2) => { const pluginMap = /* @__PURE__ */ new Map(); const processPlugin = (plugin) => { const resolvedPlugin = resolvePlugin(editor, plugin); if (resolvedPlugin.key) { const existingPlugin = pluginMap.get(resolvedPlugin.key); if (existingPlugin) { pluginMap.set( resolvedPlugin.key, mergePlugins(existingPlugin, resolvedPlugin) ); } else { pluginMap.set(resolvedPlugin.key, resolvedPlugin); } } else { } if (resolvedPlugin.plugins && resolvedPlugin.plugins.length > 0) { resolvedPlugin.plugins.forEach(processPlugin); } }; plugins2.forEach(processPlugin); return pluginMap; }; var resolveAndSortPlugins = (editor, plugins2) => { const pluginMap = flattenAndResolvePlugins(editor, plugins2); const enabledPlugins = Array.from(pluginMap.values()).filter( (plugin) => plugin.enabled !== false ); enabledPlugins.sort((a, b) => b.priority - a.priority); const orderedPlugins = []; const visited = /* @__PURE__ */ new Set(); const visit = (plugin) => { if (visited.has(plugin.key)) return; visited.add(plugin.key); plugin.dependencies?.forEach((depKey) => { const depPlugin = pluginMap.get(depKey); if (depPlugin) { visit(depPlugin); } else { editor.api.debug.warn( `Plugin "${plugin.key}" depends on missing plugin "${depKey}"`, "PLUGIN_DEPENDENCY_MISSING" ); } }); orderedPlugins.push(plugin); }; enabledPlugins.forEach(visit); return orderedPlugins; }; var applyPluginsToEditor = (editor, plugins2) => { editor.meta.pluginList = plugins2; editor.plugins = Object.fromEntries( plugins2.map((plugin) => [plugin.key, plugin]) ); }; var resolvePluginOverrides = (editor) => { const applyOverrides = (plugins2) => { let overriddenPlugins = [...plugins2]; const enabledOverrides = {}; const componentOverrides = {}; const pluginOverrides = {}; for (const plugin of plugins2) { if (plugin.override.enabled) { Object.assign(enabledOverrides, plugin.override.enabled); } if (plugin.override.components) { Object.entries(plugin.override.components).forEach( ([key, component]) => { if (!componentOverrides[key] || plugin.priority > componentOverrides[key].priority) { componentOverrides[key] = { component, priority: plugin.priority }; } } ); } if (plugin.override.plugins) { Object.entries(plugin.override.plugins).forEach(([key, value]) => { pluginOverrides[key] = mergePlugins(pluginOverrides[key], value); if (value.enabled !== void 0) { enabledOverrides[key] = value.enabled; } }); } } overriddenPlugins = overriddenPlugins.map((p) => { let updatedPlugin = { ...p }; if (pluginOverrides[p.key]) { updatedPlugin = mergePlugins(updatedPlugin, pluginOverrides[p.key]); } if (componentOverrides[p.key] && (!p.render.node && !p.node.component || componentOverrides[p.key].priority > p.priority)) { updatedPlugin.render.node = componentOverrides[p.key].component; updatedPlugin.node.component = componentOverrides[p.key].component; } const enabled = enabledOverrides[p.key] ?? updatedPlugin.enabled; if (isDefined2(enabled)) { updatedPlugin.enabled = enabled; } return updatedPlugin; }); return overriddenPlugins.filter((p) => p.enabled !== false).map((plugin) => ({ ...plugin, plugins: applyOverrides(plugin.plugins || []) })); }; applyPluginsToEditor; editor.meta.pluginList = applyOverrides(editor.meta.pluginList); editor.plugins = Object.fromEntries( editor.meta.pluginList.map((plugin) => [plugin.key, plugin]) ); }; // src/lib/plugins/AstPlugin.ts var AstPlugin = createSlatePlugin({ key: "ast", parser: { format: "application/x-slate-fragment", deserialize: ({ data }) => { const decoded = decodeURIComponent(window.atob(data)); let parsed; try { parsed = JSON.parse(decoded); } catch { } return parsed; } } }); // src/lib/plugins/HistoryPlugin.ts import { withHistory } from "@udecode/slate"; var withPlateHistory = ({ editor }) => withHistory(editor); var HistoryPlugin = createSlatePlugin({ key: "history", extendEditor: withPlateHistory }); // src/lib/plugins/paragraph/BaseParagraphPlugin.ts var BaseParagraphPlugin = createSlatePlugin({ key: "p", node: { isElement: true }, parsers: { html: { deserializer: { rules: [ { validNodeName: "P" } ], query: ({ element }) => element.style.fontFamily !== "Consolas" } } }, rules: { merge: { removeEmpty: true } } }); // src/lib/plugins/override/withBreakRules.ts import { PathApi } from "@udecode/slate"; var withBreakRules = (ctx) => { const { editor, tf: { insertBreak } } = ctx; const checkMatchRulesOverride = (rule, blockNode, blockPath) => { const matchRulesKeys = editor.meta.pluginCache.rules.match; for (const key of matchRulesKeys) { const overridePlugin = editor.getPlugin({ key }); if (overridePlugin.rules?.break && overridePlugin.rules?.match?.({ ...ctx, node: blockNode, path: blockPath, rule })) { return overridePlugin.rules.break; } } return null; }; const executeBreakAction = (action, blockPath) => { if (action === "reset") { editor.tf.resetBlock({ at: blockPath }); return true; } if (action === "exit") { editor.tf.insertExitBreak(); return true; } if (action === "deleteExit") { editor.tf.deleteBackward("character"); editor.tf.insertExitBreak(); return true; } if (action === "lineBreak") { editor.tf.insertSoftBreak(); return true; } return false; }; return { transforms: { insertBreak() { if (editor.selection && editor.api.isCollapsed()) { const block = editor.api.block(); if (block) { const [blockNode, blockPath] = block; const plugin = getPluginByType(editor, blockNode.type); const breakRules = plugin?.rules.break; if (editor.api.isEmpty(editor.selection, { block: true })) { const overrideBreakRules = checkMatchRulesOverride( "break.empty", blockNode, blockPath ); const effectiveBreakRules = overrideBreakRules || breakRules; const emptyAction = effectiveBreakRules?.empty; if (executeBreakAction(emptyAction, blockPath)) return; } if (!editor.api.isEmpty(editor.selection, { block: true }) && editor.api.isAt({ end: true })) { const range = editor.api.range("before", editor.selection); if (range) { const char = editor.api.string(range); if (char === "\n") { const overrideBreakRules = checkMatchRulesOverride( "break.emptyLineEnd", blockNode, blockPath ); const effectiveBreakRules = overrideBreakRules || breakRules; const emptyLineEndAction = effectiveBreakRules?.emptyLineEnd; if (executeBreakAction(emptyLineEndAction, blockPath)) return; } } } const overrideDefaultBreakRules = checkMatchRulesOverride( "break.default", blockNode, blockPath ); const defaultAction = (overrideDefaultBreakRules || breakRules)?.default; if (executeBreakAction(defaultAction, blockPath)) return; const overrideSplitResetBreakRules = checkMatchRulesOverride( "break.splitReset", blockNode, blockPath ); const splitReset = overrideSplitResetBreakRules?.splitReset ?? breakRules?.splitReset; if (splitReset) { const isAtStart = editor.api.isAt({ start: true }); insertBreak(); editor.tf.resetBlock({ at: isAtStart ? blockPath : PathApi.next(blockPath) }); return; } } } insertBreak(); } } }; }; // src/lib/plugins/override/withDeleteRules.ts import { PointApi, RangeApi } from "@udecode/slate"; var withDeleteRules = (ctx) => { const { editor, tf: { deleteBackward, deleteForward, deleteFragment } } = ctx; const resetMarks = () => { if (editor.api.isAt({ start: true })) { editor.tf.removeMarks(); } }; const checkMatchRulesOverride = (rule, blockNode, blockPath) => { const matchRulesKeys = editor.meta.pluginCache.rules.match; for (const key of matchRulesKeys) { const overridePlugin = editor.getPlugin({ key }); if (overridePlugin.rules?.delete && overridePlugin.rules?.match?.({ ...ctx, node: blockNode, path: blockPath, rule })) { return overridePlugin.rules.delete; } } return null; }; const executeDeleteAction = (action, blockPath) => { if (action === "reset") { editor.tf.resetBlock({ at: blockPath }); return true; } return false; }; return { transforms: { deleteBackward(unit) { if (editor.selection && editor.api.isCollapsed()) { const block = editor.api.block(); if (block) { const [blockNode, blockPath] = block; const plugin = getPluginByType(editor, blockNode.type); const deleteRules = plugin?.rules.delete; if (editor.api.isAt({ start: true })) { const overrideDeleteRules = checkMatchRulesOverride( "delete.start", blockNode, blockPath ); const effectiveDeleteRules = overrideDeleteRules || deleteRules; const startAction = effectiveDeleteRules?.start; if (executeDeleteAction(startAction, blockPath)) { return; } } if (editor.api.isEmpty(editor.selection, { block: true })) { const overrideDeleteRules = checkMatchRulesOverride( "delete.empty", blockNode, blockPath ); const effectiveDeleteRules = overrideDeleteRules || deleteRules; const emptyAction = effectiveDeleteRules?.empty; if (executeDeleteAction(emptyAction, blockPath)) return; } } if (PointApi.equals(editor.selection.anchor, editor.api.start([]))) { editor.tf.resetBlock({ at: [0] }); return; } } deleteBackward(unit); resetMarks(); }, deleteForward(unit) { deleteForward(unit); resetMarks(); }, deleteFragment(options) { if (editor.selection && RangeApi.equals(editor.selection, editor.api.range([]))) { editor.tf.reset({ children: true, select: true }); return; } deleteFragment(options); resetMarks(); } } }; }; // src/lib/plugins/override/withMergeRules.ts import { ElementApi, PathApi as PathApi2, TextApi } from "@udecode/slate"; var withMergeRules = (ctx) => { const { editor, tf: { removeNodes } } = ctx; const checkMatchRulesOverride = (rule, blockNode, blockPath) => { const matchRulesKeys = editor.meta.pluginCache.rules.match; for (const key of matchRulesKeys) { const overridePlugin = editor.getPlugin({ key }); if (overridePlugin.rules.merge && overridePlugin.rules?.match?.({ ...ctx, node: blockNode, path: blockPath, rule })) { return overridePlugin.rules.merge; } } return null; }; return { api: { shouldMergeNodes(prevNodeEntry, nextNodeEntry, { reverse } = {}) { const [prevNode, prevPath] = prevNodeEntry; const [, nextPath] = nextNodeEntry; const [curNode, curPath] = reverse ? prevNodeEntry : nextNodeEntry; const [targetNode, targetPath] = reverse ? nextNodeEntry : prevNodeEntry; if (TextApi.isText(prevNode) && prevNode.text === "" && prevPath.at(-1) !== 0) { editor.tf.removeNodes({ at: prevPath }); return false; } const shouldRemove = (node, path) => { const plugin = getPluginByType(editor, node.type); if (!plugin) { return true; } const mergeRules = plugin.rules.merge; if (!mergeRules?.removeEmpty) { return false; } const overrideMergeRules = checkMatchRulesOverride( "merge.removeEmpty", node, path ); if (overrideMergeRules?.removeEmpty === false) { return false; } return true; }; if (ElementApi.isElement(targetNode) && editor.api.isVoid(targetNode)) { if (shouldRemove(targetNode, targetPath)) { editor.tf.removeNodes({ at: prevPath }); } else if (ElementApi.isElement(curNode) && editor.api.isEmpty(curNode)) { editor.tf.removeNodes({ at: curPath }); } return false; } if (ElementApi.isElement(prevNode) && editor.api.isEmpty(prevNode) && PathApi2.isSibling(prevPath, nextPath) && shouldRemove(prevNode, prevPath)) { editor.tf.removeNodes({ at: prevPath }); return false; } return true; } }, transforms: { removeNodes(options = {}) { if (options.event?.type === "mergeNodes" && options.at) { const nodeEntry = editor.api.node(options.at); if (nodeEntry) { const [node, path] = nodeEntry; if (ElementApi.isElement(node)) { const plugin = getPluginByType(editor, node.type); if (plugin) { const mergeRules = plugin.rules.merge; const overrideMergeRules = checkMatchRulesOverride( "merge.removeEmpty", node, path ); const shouldNotRemove = overrideMergeRules?.removeEmpty === false || mergeRules?.removeEmpty === false; if (shouldNotRemove) { return; } } } } } removeNodes(options); } } }; }; // src/lib/plugins/override/withNormalizeRules.ts import { ElementApi as ElementApi2 } from "@udecode/slate"; var withNormalizeRules = (ctx) => { const { editor, tf: { normalizeNode } } = ctx; const checkMatchRulesOverride = (rule, node, path) => { const matchRulesKeys = editor.meta.pluginCache.rules.match; for (const key of matchRulesKeys) { const overridePlugin = editor.getPlugin({ key }); if (overridePlugin.rules?.normalize && overridePlugin.rules?.match?.({ ...ctx, node, path, rule })) { return overridePlugin.rules.normalize; } } return null; }; return { transforms: { normalizeNode([node, path]) { if (ElementApi2.isElement(node) && node.type) { const plugin = getPluginByType(editor, node.type); const normalizeRules = plugin?.rules.normalize; const overridenormalizeRules = checkMatchRulesOverride( "normalize.removeEmpty", node, path ); const effectivenormalizeRules = overridenormalizeRules || normalizeRules; if (effectivenormalizeRules?.removeEmpty && editor.api.isEmpty(node)) { editor.tf.removeNodes({ at: path }); return; } } normalizeNode([node, path]); } } }; }; // src/lib/plugins/override/OverridePlugin.ts var withOverrides = ({ api: { isInline, isSelectable, isVoid, markableVoid }, editor }) => { const voidTypes = editor.meta.pluginCache.node.isVoid; const inlineTypes = editor.meta.pluginCache.node.isInline; const markableVoidTypes = editor.meta.pluginCache.node.isMarkableVoid; const notSelectableTypes = editor.meta.pluginCache.node.isNotSelectable; return { api: { create: { block: (node) => ({ children: [{ text: "" }], type: editor.getType(BaseParagraphPlugin.key), ...node }) }, isInline(element) { return inlineTypes.includes(element.type) ? true : isInline(element); }, isSelectable(element) { return notSelectableTypes.includes(element.type) ? false : isSelectable(element); }, isVoid(element) { return voidTypes.includes(element.type) ? true : isVoid(element); }, markableVoid(element) { return markableVoidTypes.includes(element.type) ? true : markableVoid(element); } } }; }; var OverridePlugin = createSlatePlugin({ key: "override" }).overrideEditor(withOverrides).overrideEditor(withBreakRules).overrideEditor(withDeleteRules).overrideEditor(withMergeRules).overrideEditor(withNormalizeRules); // src/internal/plugin/pipeInsertFragment.ts var pipeInsertFragment = (editor, injectedPlugins, { fragment, ...options }) => { editor.tf.withoutNormalizing(() => { injectedPlugins.some((p) => { return p.parser?.preInsert?.({ ...getEditorPlugin(editor, p), fragment, ...options }) === true; }); editor.tf.insertFragment(fragment); }); }; // src/internal/plugin/pipeTransformData.ts var pipeTransformData = (editor, plugins2, { data, dataTransfer }) => { plugins2.forEach((p) => { const transformData = p.parser?.transformData; if (!transformData) return; data = transformData({ ...getEditorPlugin(editor, p), data, dataTransfer }); }); return data; }; // src/internal/plugin/pipeTransformFragment.ts var pipeTransformFragment = (editor, plugins2, { fragment, ...options }) => { plugins2.forEach((p) => { const transformFragment = p.parser?.transformFragment; if (!transformFragment) return; fragment = transformFragment({ fragment, ...options, ...getEditorPlugin(editor, p) }); }); return fragment; }; // src/lib/utils/applyDeepToNodes.ts import { NodeApi, queryNode } from "@udecode/slate"; var applyDeepToNodes = ({ apply, node, path = [], query, source }) => { const entry = [node, path]; if (queryNode(entry, query)) { if (typeof source === "function") { apply(node, source()); } else { apply(node, source); } } if (!NodeApi.isAncestor(node)) return; node.children.forEach((child, index) => { applyDeepToNodes({ apply, node: child, path: path.concat([index]), query, source }); }); }; // src/lib/utils/defaultsDeepToNodes.ts import defaults from "lodash/defaults.js"; var defaultsDeepToNodes = (options) => { applyDeepToNodes({ ...options, apply: defaults }); }; // src/lib/utils/getInjectMatch.ts import { ElementApi as ElementApi3 } from "@udecode/slate"; // src/lib/utils/getKeysByTypes.ts var getKeysByTypes = (editor, types) => { return Object.values(editor.plugins).filter((plugin) => types.includes(plugin.node.type)).map((plugin) => plugin.key); }; var getKeyByType = (editor, type) => { const plugin = Object.values(editor.plugins).find( (plugin2) => plugin2.node.type === type ); return plugin?.key ?? type; }; // src/lib/utils/getInjectMatch.ts var getInjectMatch = (editor, plugin) => { return (node, path) => { const { inject: { excludeBelowPlugins, excludePlugins, isBlock: _isBlock, isElement: _isElement, isLeaf, maxLevel, targetPlugins } } = plugin; const element = ElementApi3.isElement(node) ? node : void 0; if (_isElement && !element) return false; if (_isBlock && (!element || !editor.api.isBlock(element))) return false; if (isLeaf && element) return false; if (element?.type) { if (excludePlugins?.includes(getKeyByType(editor, element.type))) { return false; } if (targetPlugins && !targetPlugins.includes(getKeyByType(editor, element.type))) { return false; } } if (excludeBelowPlugins || maxLevel) { if (maxLevel && path.length > maxLevel) { return false; } if (excludeBelowPlugins) { const excludeTypes = getKeysByTypes(editor, excludeBelowPlugins); const isBelow = editor.api.above({ at: path, match: (n) => ElementApi3.isElement(n) && excludeTypes.includes(n.type) }); if (isBelow) return false; } } return true; }; }; // src/lib/utils/getInjectedPlugins.ts var getInjectedPlugins = (editor, plugin) => { const injectedPlugins = []; [...editor.meta.pluginList].reverse().forEach((p) => { const injectedPlugin = p.inject.plugins?.[plugin.key]; if (injectedPlugin) injectedPlugins.push(injectedPlugin); }); return [plugin, ...injectedPlugins]; }; // src/lib/utils/getPluginNodeProps.ts import pick from "lodash/pick.js"; // src/lib/static/pipeRenderElementStatic.tsx import React4 from "react"; // src/lib/static/components/slate-nodes.tsx import React from "react"; import { clsx } from "clsx"; var useNodeAttributes = (props, ref) => { return { ...props.attributes, className: clsx(props.attributes.className, props.className) || void 0, ref, style: { ...props.attributes.style, ...props.style } }; }; var SlateElement = React.forwardRef(function SlateElement2({ as: Tag = "div", children, ...props }, ref) { const attributes = useNodeAttributes(props, ref); const block = !!props.element.id && !!props.editor.api.isBlock(props.element); return /* @__PURE__ */ React.createElement( Tag, { "data-slate-node": "element", "data-slate-inline": attributes["data-slate-inline"], "data-block-id": block ? props.element.id : void 0, ...attributes, style: { position: "relative", ...attributes?.style } }, children ); }); var SlateText = React.forwardRef(({ as: Tag = "span", children, ...props }, ref) => { const attributes = useNodeAttributes(props, ref); return /* @__PURE__ */ React.createElement(Tag, { ...attributes }, children); }); var NonBreakingSpace = () => /* @__PURE__ */ React.createElement("span", { style: { fontSize: 0 }, contentEditable: false }, String.fromCodePoint(160)); var SlateLeaf = React.forwardRef(({ as: Tag = "span", children, inset, ...props }, ref) => { const attributes = useNodeAttributes(props, ref); if (inset) { return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(NonBreakingSpace, null), /* @__PURE__ */ React.createElement(Tag, { ...attributes }, children, /* @__PURE__ */ React.createElement(NonBreakingSpace, null))); } return /* @__PURE__ */ React.createElement(Tag, { ...attributes }, children); }); // src/lib/static/pluginRenderElementStatic.tsx import React3 from "react"; // src/lib/static/utils/createStaticString.ts import React2 from "react"; function createStaticString({ text }) { return React2.createElement( "span", { "data-slate-string": true }, text === "" ? "\uFEFF" : text ); } // src/lib/static/utils/getNodeDataAttributes.ts import { TextApi as TextApi2 } from "@udecode/slate"; import kebabCase from "lodash/kebabCase.js"; var getNodeDataAttributes = (editor, node, { isElement: isElement2, isLeaf, isText }) => { const dataAttributes = Object.keys(node).reduce((acc, key) => { if (typeof node[key] === "object") return acc; if (isElement2 && key === "children") return acc; if ((isLeaf || isText) && key === "text") return acc; const plugin = editor.getPlugin({ key }); if (isLeaf && plugin?.node.isLeaf && plugin?.node.isDecoration !== true) { return acc; } if (isText && plugin?.node.isLeaf && plugin?.node.isDecoration !== false) { return acc; } const attributeName = keyToDataAttribute(key); return { ...acc, [attributeName]: node[key] }; }, {}); return dataAttributes; }; var getPluginDataAttributes = (editor, plugin, node) => { const isElement2 = plugin.node.isElement; const isLeaf = plugin.node.isLeaf && plugin.node.isDecoration === true; const isText = plugin.node.isLeaf && plugin.node.isDecoration === false; const dataAttributes = getNodeDataAttributes(editor, node, { isElement: isElement2, isLeaf, isText }); const customAttributes = plugin.node.toDataAttributes?.({ ...plugin ? getEditorPlugin(editor, plugin) : {}, node }) ?? {}; return { ...dataAttributes, ...customAttributes }; }; var getNodeDataAttributeKeys = (node) => { return Object.keys(node).filter( (key) => typeof node[key] !== "object" && (!TextApi2.isText(node) || key !== "text") ).map((key) => keyToDataAttribute(key)); }; var keyToDataAttribute = (key) => { return `data-slate-${kebabCase(key)}`; }; // src/lib/static/utils/getRenderNodeStaticProps.ts import clsx3 from "clsx"; // src/internal/plugin/pipeInjectNodeProps.tsx import clsx2 from "clsx"; // src/internal/plugin/isEditOnlyDisabled.ts var DEFAULT = { handlers: true, inject: true, normalizeInitialValue: false, render: true }; var isEditOnly = (readOnly, plugin, feature) => { if (!readOnly) return false; if (plugin.editOnly === true) { return DEFAULT[feature]; } if (typeof plugin.editOnly === "object") { return plugin.editOnly[feature] ?? DEFAULT[feature]; } return false; }; // src/internal/plugin/pluginInjectNodeProps.ts import { isDefined as isDefined3 } from "@udecode/utils"; var pluginInjectNodeProps = (editor, plugin, nodeProps, getElementPath) => { const { key, inject: { nodeProps: injectNodeProps } } = plugin; const { element, text } = nodeProps; const node = element ?? text; if (!node) return; if (!injectNodeProps) return; const { classNames, defaultNodeValue, nodeKey = editor.getType(key), query, styleKey = nodeKey, transformClassName, transformNodeValue, transformProps, transformStyle, validNodeValues } = injectNodeProps; const injectMatch = getInjectMatch(editor, plugin); if (!injectMatch(node, getElementPath(node))) return; const queryResult = query?.({ ...injectNodeProps, ...getEditorPlugin(editor, plugin), nodeProps }); if (query && !queryResult) { return; } const nodeValue = node[nodeKey]; if (!transformProps && (!isDefined3(nodeValue) || validNodeValues && !validNodeValues.includes(nodeValue) || nodeValue === defaultNodeValue)) { return; } const transformOptions = { ...nodeProps, ...getEditorPlugin(editor, plugin), nodeValue }; const value = transformNodeValue?.(transformOptions) ?? nodeValue; transformOptions.value = value; let newProps = {}; if (element && nodeKey && nodeValue) { newProps.className = `slate-${nodeKey}-${nodeValue}`; } if (classNames?.[nodeValue] || transformClassName) { newProps.className = transformClassName?.(transformOptions) ?? classNames?.[value]; } if (styleKey) { newProps.style = transformStyle?.(transformOptions) ?? { [styleKey]: value }; } if (transformProps) { newProps = transformProps({ ...transformOptions, props: newProps }) ?? newProps; } return newProps; }; // src/internal/plugin/pipeInjectNodeProps.tsx var pipeInjectNodeProps = (editor, nodeProps, getElementPath, readOnly = false) => { editor.meta.pluginCache.inject.nodeProps.forEach((key) => { const plugin = editor.getPlugin({ key }); const newAttributes = pluginInjectNodeProps( editor, plugin, nodeProps, getElementPath ); if (isEditOnly(readOnly, plugin, "inject")) { return; } if (!newAttributes) return; const attributes = nodeProps.attributes; nodeProps.attributes = { ...attributes, ...newAttributes, className: clsx2(attributes?.className, newAttributes.className) || void 0, style: { ...attributes?.style, ...newAttributes.style } }; }); return nodeProps; }; // src/lib/static/utils/getRenderNodeStaticProps.ts var getRenderNodeStaticProps = ({ attributes: nodeAttributes, editor, node, plugin, props }) => { let newProps = { ...props, ...plugin ? getEditorPlugin(editor, plugin) : { api: editor.api, editor, tf: editor.transforms } }; const { className } = props; const pluginProps = getPluginNodeProps({ attributes: nodeAttributes, node, plugin, props: newProps }); newProps = { ...pluginProps, attributes: { ...pluginProps.attributes, className: clsx3(getSlateClass(plugin?.node.type), className) || void 0 } }; newProps = pipeInjectNodeProps( editor, newProps, (node2) => editor.api.findPath(node2) ); if (newProps.style && Object.keys(newProps.style).length === 0) { delete newProps.style; } return newProps; }; // src/lib/static/utils/pipeDecorate.ts var pipeDecorate = (editor, decorateProp) => { if (editor.meta.pluginCache.decorate.length === 0 && !decorateProp) return; return (entry) => { let ranges = []; const addRanges = (newRanges) => { if (newRanges?.length) ranges = [...ranges, ...newRanges]; }; editor.meta.pluginCache.decorate.forEach((key) => { const plugin = editor.getPlugin({ key }); addRanges( plugin.decorate({ ...getEditorPlugin(editor, plugin), entry }) ); }); if (decorateProp) { addRanges( decorateProp({ editor, entry }) ); } return ranges; }; }; // src/lib/static/utils/stripHtmlClassNames.ts var classAttrRegExp = / class="([^"]*)"/g; var stripHtmlClassNames = (html, { preserveClassNames = ["slate-"] }) => { if (preserveClassNames.length === 0) { return html.replaceAll(classAttrRegExp, ""); } const preserveRegExp = new RegExp( preserveClassNames.map((cn) => `^${cn}`).join("|") ); return html.replaceAll( classAttrRegExp, (match, className) => { const classesToKeep = className.split(/\s+/).filter((cn) => preserveRegExp.test(cn)); return classesToKeep.length === 0 ? "" : ` class="${classesToKeep.join(" ")}"`; } ); }; // src/lib/static/utils/stripSlateDataAttributes.ts var stripSlateDataAttributes = (rawHtml) => rawHtml.replaceAll(/ data-slate(?:-node|-type|-leaf|-string)="[^"]+"/g, "").replaceAll(/ data-testid="[^"]+"/g, ""); // src/lib/static/pluginRenderElementStatic.tsx var pluginRenderElementStatic = (editor, plugin) => function render(nodeProps) { if (nodeProps.element.type === plugin.node.type) { const element = nodeProps.element; const Component = editor.meta.components?.[plugin.key]; const Element2 = Component ?? SlateElement; let { children } = nodeProps; const dataAttributes = getPluginDataAttributes(editor, plugin, element); nodeProps = getRenderNodeStaticProps({ attributes: { ...element.attributes, ...dataAttributes }, editor, node: element, plugin, props: nodeProps }); editor.meta.pluginCache.render.belowNodes.forEach((key) => { const hoc = editor.getPlugin({ key }).render.belowNodes({ ...nodeProps, key }); if (hoc) { children = hoc({ ...nodeProps, children }); } }); const defaultProps = Component ? {} : { as: plugin.r