@udecode/plate-core
Version:
The core of Plate – a plugin system for slate
1,701 lines (1,632 loc) • 108 kB
JavaScript
// src/react/slate-react.ts
import {
DefaultElement,
DefaultPlaceholder,
Editable,
Slate
} from "slate-react";
import {
useComposing,
useFocused,
useReadOnly,
useSelected
} from "slate-react";
import { withReact } from "slate-react";
// src/react/components/DefaultLeaf.tsx
import React from "react";
import { clsx as clsx2 } from "clsx";
// src/lib/editor/withSlate.ts
import {
createEditor
} from "@udecode/slate";
import { nanoid } from "nanoid";
// 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: {},
shortcuts: {},
transforms: {}
},
config
);
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 = (plugins) => {
let found = false;
const updatedPlugins = plugins.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 = (plugins) => {
let found = false;
const updatedPlugins = plugins.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);
};
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, plugin) {
const p = editor.getPlugin(plugin);
return p.node.type ?? p.key ?? "";
}
// src/internal/plugin/pipeNormalizeInitialValue.ts
var pipeNormalizeInitialValue = (editor) => {
editor.pluginList.forEach((p) => {
p.normalizeInitialValue?.({
...getEditorPlugin(editor, p),
value: editor.children
});
});
};
// src/internal/plugin/resolvePlugins.ts
import {
assignLegacyApi,
assignLegacyTransforms,
syncLegacyMethods
} from "@udecode/slate";
import { isDefined } from "@udecode/utils";
import merge2 from "lodash/merge.js";
import { createZustandStore } from "zustand-x";
var resolvePlugins = (editor, plugins = []) => {
editor.pluginList = [];
editor.plugins = {};
editor.shortcuts = {};
const resolvedPlugins = resolveAndSortPlugins(editor, plugins);
applyPluginsToEditor(editor, resolvedPlugins);
resolvePluginOverrides(editor);
resolvePluginStores(editor);
editor.pluginList.forEach((plugin) => {
if (plugin.extendEditor) {
editor = plugin.extendEditor(getEditorPlugin(editor, plugin));
syncLegacyMethods(editor);
}
resolvePluginMethods(editor, plugin);
});
resolvePluginShortcuts(editor);
return editor;
};
var resolvePluginStores = (editor) => {
editor.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) => {
const shortcutsByPriority = [];
editor.pluginList.forEach((plugin) => {
Object.entries(plugin.shortcuts).forEach(([key, hotkey]) => {
if (hotkey === null) {
const index = shortcutsByPriority.findIndex((item) => item.key === key);
if (index !== -1) {
shortcutsByPriority.splice(index, 1);
}
} else {
const priority = hotkey.priority ?? plugin.priority;
const existingIndex = shortcutsByPriority.findIndex(
(item) => item.key === key
);
if (existingIndex === -1 || priority >= shortcutsByPriority[existingIndex].priority) {
if (existingIndex !== -1) {
shortcutsByPriority.splice(existingIndex, 1);
}
shortcutsByPriority.push({ key, hotkey, priority });
}
}
});
});
shortcutsByPriority.sort((a, b) => b.hotkey.priority - a.hotkey.priority);
editor.shortcuts = Object.fromEntries(
shortcutsByPriority.map(({ key, hotkey }) => {
const { priority, ...hotkeyWithoutPriority } = hotkey;
return [key, hotkeyWithoutPriority];
})
);
};
var flattenAndResolvePlugins = (editor, plugins) => {
const pluginMap = /* @__PURE__ */ new Map();
const processPlugin = (plugin) => {
const resolvedPlugin = resolvePlugin(editor, plugin);
const existingPlugin = pluginMap.get(resolvedPlugin.key);
if (existingPlugin) {
pluginMap.set(
resolvedPlugin.key,
mergePlugins(existingPlugin, resolvedPlugin)
);
} else {
pluginMap.set(resolvedPlugin.key, resolvedPlugin);
}
if (resolvedPlugin.plugins && resolvedPlugin.plugins.length > 0) {
resolvedPlugin.plugins.forEach(processPlugin);
}
};
plugins.forEach(processPlugin);
return pluginMap;
};
var resolveAndSortPlugins = (editor, plugins) => {
const pluginMap = flattenAndResolvePlugins(editor, plugins);
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, plugins) => {
editor.pluginList = plugins;
editor.plugins = Object.fromEntries(
plugins.map((plugin) => [plugin.key, plugin])
);
};
var resolvePluginOverrides = (editor) => {
const applyOverrides = (plugins) => {
let overriddenPlugins = [...plugins];
const enabledOverrides = {};
const componentOverrides = {};
const pluginOverrides = {};
for (const plugin of plugins) {
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 (isDefined(enabled)) {
updatedPlugin.enabled = enabled;
}
return updatedPlugin;
});
return overriddenPlugins.filter((p) => p.enabled !== false).map((plugin) => ({
...plugin,
plugins: applyOverrides(plugin.plugins || [])
}));
};
editor.pluginList = applyOverrides(editor.pluginList);
editor.plugins = Object.fromEntries(
editor.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/DOMPlugin.ts
var DOMPlugin = createSlatePlugin({
key: "dom"
});
// 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/InlineVoidPlugin.ts
var withInlineVoid = ({
api: { isInline, isSelectable, isVoid, markableVoid },
editor
}) => {
const voidTypes = [];
const inlineTypes = [];
const markableVoidTypes = [];
const nonSelectableTypes = [];
editor.pluginList.forEach((plugin) => {
if (plugin.node.isInline) {
inlineTypes.push(plugin.node.type);
}
if (plugin.node.isVoid) {
voidTypes.push(plugin.node.type);
}
if (plugin.node.isMarkableVoid) {
markableVoidTypes.push(plugin.node.type);
}
if (plugin.node.isSelectable === false) {
nonSelectableTypes.push(plugin.node.type);
}
});
return {
api: {
isInline(element) {
return inlineTypes.includes(element.type) ? true : isInline(element);
},
isSelectable(element) {
return nonSelectableTypes.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 InlineVoidPlugin = createSlatePlugin({
key: "inlineVoid"
}).overrideEditor(withInlineVoid);
// 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, plugins, { data, dataTransfer }) => {
plugins.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, plugins, { fragment, ...options }) => {
plugins.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 (source instanceof 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/getInjectMatch.ts
import { ElementApi } 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 = ElementApi.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) => ElementApi.isElement(n) && excludeTypes.includes(n.type)
});
if (isBelow) return false;
}
}
return true;
};
};
// src/lib/utils/getInjectedPlugins.ts
var getInjectedPlugins = (editor, plugin) => {
const injectedPlugins = [];
[...editor.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/utils/getNodeDataAttributes.ts
import { TextApi } from "@udecode/slate";
import kebabCase from "lodash/kebabCase.js";
var getNodeDataAttributeKeys = (node) => {
return Object.keys(node).filter(
(key) => typeof node[key] !== "object" && (!TextApi.isText(node) || key !== "text")
).map((key) => keyToDataAttribute(key));
};
var keyToDataAttribute = (key) => {
return `data-slate-${kebabCase(key)}`;
};
// src/internal/plugin/pipeInjectNodeProps.tsx
import clsx from "clsx";
// src/internal/plugin/pluginInjectNodeProps.ts
import { isDefined as isDefined2 } 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 = 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 && (!isDefined2(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) {
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) => {
editor.pluginList.forEach((plugin) => {
if (plugin.inject.nodeProps) {
const newProps = pluginInjectNodeProps(
editor,
plugin,
nodeProps,
getElementPath
);
if (!newProps) return;
nodeProps = {
...nodeProps,
...newProps,
className: clsx(nodeProps.className, newProps.className),
style: {
...nodeProps.style,
...newProps.style
}
};
}
});
return nodeProps;
};
// src/lib/static/utils/pipeDecorate.ts
var pipeDecorate = (editor, decorateProp) => {
const relevantPlugins = editor.pluginList.filter((plugin) => plugin.decorate);
if (relevantPlugins.length === 0 && !decorateProp) return;
return (entry) => {
let ranges = [];
const addRanges = (newRanges) => {
if (newRanges?.length) ranges = [...ranges, ...newRanges];
};
relevantPlugins.forEach((plugin) => {
addRanges(
plugin.decorate({
...getEditorPlugin(editor, plugin),
entry
})
);
});
if (decorateProp) {
addRanges(
decorateProp({
editor,
entry
})
);
}
return ranges;
};
};
// src/lib/static/deserialize/checkUtils.ts
var isSlateVoid = (element) => {
return element.dataset.slateVoid === "true";
};
var isSlateElement = (element) => {
return element.dataset.slateNode === "element";
};
var isSlateText = (element) => {
return element.dataset.slateNode === "text";
};
var isSlateString = (element) => {
return element.dataset.slateString === "true";
};
var isSlateLeaf = (element) => {
return element.dataset.slateLeaf === "true";
};
var isSlateNode = (element) => {
return isSlateLeaf(element) || isSlateElement(element) || isSlateVoid(element) || isSlateString(element) || isSlateText(element);
};
var isSlatePluginNode = (element, pluginKey) => {
return element.classList.contains(`slate-${pluginKey}`);
};
// src/lib/utils/getPluginNodeProps.ts
var getPluginNodeProps = ({
attributes,
node,
plugin,
props
}) => {
let newProps = {};
if (plugin?.node.props) {
newProps = (typeof plugin.node.props === "function" ? plugin.node.props(props) : plugin.node.props) ?? {};
}
if (!newProps.nodeProps && attributes && plugin) {
newProps.nodeProps = pick(attributes, [
...plugin.node.dangerouslyAllowAttributes ?? [],
...node ? getNodeDataAttributeKeys(node) : []
]);
} else if (newProps.nodeProps && attributes && plugin) {
newProps.nodeProps = {
...newProps.nodeProps,
...pick(attributes, [...node ? getNodeDataAttributeKeys(node) : []])
};
}
props = { ...props, ...newProps };
if (props.nodeProps) {
Object.keys(props.nodeProps).forEach((key) => {
if (props.nodeProps?.[key] === void 0) {
delete props.nodeProps?.[key];
}
});
}
return props;
};
// src/lib/utils/getSlateClass.ts
var getSlateClass = (type) => type ? `slate-${type}` : "";
// src/lib/utils/mergeDeepToNodes.ts
import merge3 from "lodash/merge.js";
var mergeDeepToNodes = (options) => {
applyDeepToNodes({ ...options, apply: merge3 });
};
// src/lib/utils/normalizeDescendantsToDocumentFragment.ts
import {
ElementApi as ElementApi3,
TextApi as TextApi3
} from "@udecode/slate";
// src/lib/plugins/debug/DebugPlugin.ts
var PlateError = class extends Error {
constructor(message, type = "DEFAULT") {
super(`[${type}] ${message}`);
this.type = type;
this.name = "PlateError";
}
};
var DebugPlugin = createTSlatePlugin({
key: "debug",
options: {
isProduction: process.env.NODE_ENV === "production",
logger: {
error: (message, type, details) => console.error(`${type ? `[${type}] ` : ""}${message}`, details),
info: (message, type, details) => console.info(`${type ? `[${type}] ` : ""}${message}`, details),
log: (message, type, details) => console.log(`${type ? `[${type}] ` : ""}${message}`, details),
warn: (message, type, details) => console.warn(`${type ? `[${type}] ` : ""}${message}`, details)
},
logLevel: process.env.NODE_ENV === "production" ? "error" : "log",
throwErrors: true
}
}).extendEditorApi(({ getOptions }) => {
const logLevels = ["error", "warn", "info", "log"];
const log = (level, message, type, details) => {
if (process.env.NODE_ENV === "production") return;
const options = getOptions();
if (options.isProduction && level === "log") return;
if (logLevels.indexOf(level) <= logLevels.indexOf(options.logLevel)) {
if (level === "error" && options.throwErrors) {
throw new PlateError(message, type);
} else {
options.logger[level]?.(message, type, details);
}
}
};
return {
debug: {
error: (message, type, details) => log("error", message, type, details),
info: (message, type, details) => log("info", message, type, details),
log: (message, type, details) => log("log", message, type, details),
warn: (message, type, details) => log("warn", message, type, details)
}
};
});
// src/lib/plugins/html/HtmlPlugin.ts
import { bindFirst } from "@udecode/utils";
// src/lib/plugins/html/utils/isHtmlElement.ts
var isHtmlElement = (node) => node.nodeType === Node.ELEMENT_NODE;
// src/lib/plugins/html/utils/isHtmlText.ts
var isHtmlText = (node) => node.nodeType === Node.TEXT_NODE;
// src/lib/plugins/html/utils/inlineTagNames.ts
var inlineTagNames = /* @__PURE__ */ new Set([
"A",
"ABBR",
"ACRONYM",
"B",
"BDI",
"BDO",
"BIG",
"BR",
"BUTTON",
"CANVAS",
"CITE",
"CODE",
"CONTENT",
"DATA",
"DEL",
"DFN",
"EM",
"EMBED",
"FONT",
"I",
"IFRAME",
"IMG",
"IMG",
"INPUT",
"INS",
"KBD",
"LABEL",
"MAP",
"MARK",
"MARQUEE",
"math",
"MENUITEM",
"METER",
"NOBR",
"OBJECT",
"OUTPUT",
"PICTURE",
"PORTAL",
"PROGRESS",
"Q",
"S",
"SAMP",
"SELECT",
"SHADOW",
"SMALL",
"SOURCE",
"SPAN",
"STRIKE",
"STRONG",
"SUB",
"SUP",
"svg",
"TEXTAREA",
"TIME",
"TRACK",
"TT",
"U",
"VAR",
"VIDEO",
"WBR"
]);
// src/lib/plugins/html/utils/isHtmlInlineElement.ts
var isHtmlInlineElement = (node) => {
if (!isHtmlElement(node)) return false;
const element = node;
const tagNameIsInline = inlineTagNames.has(element.tagName);
const displayProperty = element.style.display.split(" ")[0];
if (displayProperty === "") {
return tagNameIsInline;
}
if (displayProperty.startsWith("inline")) {
return true;
}
if (displayProperty === "inherit" && element.parentElement) {
return isHtmlInlineElement(element.parentElement);
}
if (["contents", "initial", "none", "revert", "revert-layer", "unset"].includes(
displayProperty
)) {
return tagNameIsInline;
}
return false;
};
// src/lib/plugins/html/utils/isHtmlBlockElement.ts
var isHtmlBlockElement = (node) => {
if (!isHtmlElement(node)) return false;
const element = node;
return !isHtmlInlineElement(element);
};
// src/lib/plugins/html/utils/collapse-white-space/collapseString.ts
var collapseString = (text, {
shouldCollapseWhiteSpace = true,
trimEnd = "collapse",
trimStart = "collapse",
whiteSpaceIncludesNewlines = true
} = {}) => {
if (trimStart === "all") {
text = text.replace(/^\s+/, "");
}
if (trimEnd === "single-newline") {
text = text.replace(/\n$/, "");
}
if (shouldCollapseWhiteSpace) {
if (whiteSpaceIncludesNewlines) {
text = text.replaceAll(/\s+/g, " ");
} else {
text = text.replaceAll(/[^\S\n\r]+/g, " ");
text = text.replaceAll(/^[^\S\n\r]+/gm, "");
text = text.replaceAll(/[^\S\n\r]+$/gm, "");
}
}
return text;
};
// src/lib/plugins/html/utils/collapse-white-space/isLastNonEmptyTextOfInlineFormattingContext.ts
var isLastNonEmptyTextOfInlineFormattingContext = (initialText) => {
let currentNode = initialText;
while (true) {
if (currentNode.nextSibling) {
currentNode = currentNode.nextSibling;
} else {
currentNode = currentNode.parentElement;
if (currentNode && isHtmlBlockElement(currentNode)) {
return true;
}
currentNode = currentNode?.nextSibling || null;
}
if (!currentNode) {
return true;
}
if (isHtmlBlockElement(currentNode)) {
return true;
}
if ((currentNode.textContent || "").length > 0) {
return false;
}
}
};
// src/lib/plugins/html/utils/collapse-white-space/stateTransforms.ts
var upsertInlineFormattingContext = (state) => {
if (state.inlineFormattingContext) {
state.inlineFormattingContext.atStart = false;
} else {
state.inlineFormattingContext = {
atStart: true,
lastHasTrailingWhiteSpace: false
};
}
};
var endInlineFormattingContext = (state) => {
state.inlineFormattingContext = null;
};
// src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceText.ts
var collapseWhiteSpaceText = (text, state) => {
const textContent = text.textContent || "";
const isWhiteSpaceOnly = textContent.trim() === "";
if (state.inlineFormattingContext || !isWhiteSpaceOnly) {
upsertInlineFormattingContext(state);
}
const { whiteSpaceRule } = state;
const trimStart = (() => {
if (whiteSpaceRule !== "normal") return "collapse";
if (!state.inlineFormattingContext || state.inlineFormattingContext.atStart || state.inlineFormattingContext.lastHasTrailingWhiteSpace)
return "all";
return "collapse";
})();
const trimEnd = (() => {
if (whiteSpaceRule === "normal") return "collapse";
if (isLastNonEmptyTextOfInlineFormattingContext(text))
return "single-newline";
return "collapse";
})();
const shouldCollapseWhiteSpace = {
normal: true,
pre: false,
"pre-line": true
}[whiteSpaceRule];
const whiteSpaceIncludesNewlines = whiteSpaceRule !== "pre-line";
const collapsedTextContent = collapseString(textContent || "", {
shouldCollapseWhiteSpace,
trimEnd,
trimStart,
whiteSpaceIncludesNewlines
});
if (state.inlineFormattingContext && shouldCollapseWhiteSpace) {
state.inlineFormattingContext.lastHasTrailingWhiteSpace = collapsedTextContent.endsWith(" ");
}
text.textContent = collapsedTextContent;
};
// src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceNode.ts
var collapseWhiteSpaceNode = (node, state) => {
if (isHtmlElement(node)) {
collapseWhiteSpaceElement(node, state);
return;
}
if (isHtmlText(node)) {
collapseWhiteSpaceText(node, state);
return;
}
collapseWhiteSpaceChildren(node, state);
};
// src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceChildren.ts
var collapseWhiteSpaceChildren = (node, state) => {
const childNodes = Array.from(node.childNodes);
for (const childNode of childNodes) {
collapseWhiteSpaceNode(childNode, state);
}
};
// src/lib/plugins/html/utils/collapse-white-space/inferWhiteSpaceRule.ts
var inferWhiteSpaceRule = (element) => {
const whiteSpaceProperty = element.style.whiteSpace;
switch (whiteSpaceProperty) {
case "break-spaces":
case "pre":
case "pre-wrap": {
return "pre";
}
case "normal":
case "nowrap": {
return "normal";
}
case "pre-line": {
return "pre-line";
}
}
if (element.tagName === "PRE") {
return "pre";
}
if (whiteSpaceProperty === "initial") {
return "normal";
}
return null;
};
// src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceElement.ts
var collapseWhiteSpaceElement = (element, state) => {
const isInlineElement = isHtmlInlineElement(element);
const previousWhiteSpaceRule = state.whiteSpaceRule;
const inferredWhiteSpaceRule = inferWhiteSpaceRule(element);
if (inferredWhiteSpaceRule) {
state.whiteSpaceRule = inferredWhiteSpaceRule;
}
if (!isInlineElement) {
endInlineFormattingContext(state);
}
collapseWhiteSpaceChildren(element, state);
if (!isInlineElement) {
endInlineFormattingContext(state);
}
state.whiteSpaceRule = previousWhiteSpaceRule;
};
// src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpace.ts
var collapseWhiteSpace = (element) => {
const clonedElement = element.cloneNode(true);
const state = {
inlineFormattingContext: null,
whiteSpaceRule: "normal"
};
collapseWhiteSpaceElement(clonedElement, state);
return clonedElement;
};
// src/lib/plugins/html/utils/htmlBodyToFragment.ts
import { jsx } from "slate-hyperscript";
// src/lib/plugins/html/utils/deserializeHtmlNodeChildren.ts
var deserializeHtmlNodeChildren = (editor, node, isSlateParent = false) => {
return Array.from(node.childNodes).flatMap((child) => {
if (child.nodeType === 1 && !isSlateNode(child) && isSlateParent) {
return deserializeHtmlNodeChildren(
editor,
child,
isSlateParent
);
}
return deserializeHtmlNode(editor)(child);
});
};
// src/lib/plugins/html/utils/htmlBodyToFragment.ts
var htmlBodyToFragment = (editor, element) => {
if (element.nodeName === "BODY") {
return jsx(
"fragment",
{},
deserializeHtmlNodeChildren(editor, element)
);
}
};
// src/lib/plugins/html/utils/htmlBrToNewLine.ts
var htmlBrToNewLine = (node) => {
if (node.nodeName === "BR") {
return "\n";
}
};
// src/lib/plugins/html/utils/htmlElementToElement.ts
import { jsx as jsx2 } from "slate-hyperscript";
// src/lib/plugins/html/utils/pluginDeserializeHtml.ts
import { isDefined as isDefined3 } from "@udecode/utils";
import castArray from "lodash/castArray.js";
// src/lib/plugins/html/utils/getDataNodeProps.ts
var getDefaultNodeProps = ({
element,
type
}) => {
if (!isSlatePluginNode(element, type) && !isSlateLeaf(element)) return;
const dataAttributes = {};
Object.entries(element.dataset).forEach(([key, value]) => {
if (key.startsWith("slate") && value && // Ignore slate default attributes
!["slateInline", "slateLeaf", "slateNode", "slateVoid"].includes(key)) {
const attributeKey = key.slice(5).charAt(0).toLowerCase() + key.slice(6);
if (value === void 0) return;
let parsedValue = value;
if (value === "true") parsedValue = true;
else if (value === "false") parsedValue = false;
else if (!Number.isNaN(Number(value))) parsedValue = Number(value);
dataAttributes[attributeKey] = parsedValue;
}
});
if (Object.keys(dataAttributes).length > 0) {
return dataAttributes;
}
};
var getDataNodeProps = ({
editor,
element,
plugin
}) => {
const toNodeProps = plugin.parsers.html?.deserializer?.toNodeProps;
const disableDefaultNodeProps = plugin.parsers.html?.deserializer?.disableDefaultNodeProps ?? false;
const defaultNodeProps = disableDefaultNodeProps ? {} : getDefaultNodeProps({
...getEditorPlugin(editor, plugin),
element
});
if (!toNodeProps) return defaultNodeProps;
const customNodeProps = toNodeProps({
...getEditorPlugin(editor, plugin),
element
}) ?? {};
return {
...defaultNodeProps,
...customNodeProps
};
};
// src/lib/plugins/html/utils/pluginDeserializeHtml.ts
var getDeserializedWithStaticRules = (plugin) => {
let deserializer = plugin.parsers?.html?.deserializer;
const rules = deserializer?.rules ?? [];
const hasSlateRule = rules.some(
(rule) => rule.validClassName?.includes(`slate-${plugin.key}`)
);
const staticRules = hasSlateRule ? rules : [
{
validClassName: `slate-${plugin.key}`,
validNodeName: "*"
},
...rules
];
if (!deserializer) deserializer = { rules: staticRules };
deserializer.rules = staticRules;
return deserializer;
};
var pluginDeserializeHtml = (editor, plugin, {
deserializeLeaf,
element: el
}) => {
const {
node: { isElement: isElementRoot, isLeaf: isLeafRoot }
} = plugin;
const deserializer = getDeserializedWithStaticRules(plugin);
if (!deserializer) return;
const {
attributeNames,
isElement: isElementRule,
isLeaf: isLeafRule,
query,
rules
} = deserializer;
let { parse } = deserializer;
const isElement = isElementRule || isElementRoot;
const isLeaf = isLeafRule || isLeafRoot;
if (!deserializeLeaf && !isElement) {
return;
}
if (deserializeLeaf && !isLeaf) {
return;
}
if (rules) {
const isValid = rules.some(
({ validAttribute, validClassName, validNodeName = "*", validStyle }) => {
if (validNodeName) {
const validNodeNames = castArray(validNodeName);
if (validNodeNames.length > 0 && !validNodeNames.includes(el.nodeName) && validNodeName !== "*")
return false;
}
if (validClassName && !el.classList.contains(validClassName))
return false;
if (validStyle) {
for (const [key, value] of Object.entries(validStyle)) {
const values = castArray(value);
if (!values.includes(el.style[key]) && value !== "*")
return;
if (value === "*" && !el.style[key]) return;
const defaultNodeValue = plugin.inject.nodeProps?.defaultNodeValue;
if (defaultNodeValue && defaultNodeValue === el.style[key]) {
return false;
}
}
}
if (validAttribute) {
if (typeof validAttribute === "string") {
if (!el.getAttributeNames().includes(validAttribute)) return false;
} else {
for (const [attributeName, attributeValue] of Object.entries(
validAttribute
)) {
const attributeValues = castArray(attributeValue);
const elAttribute = el.getAttribute(attributeName);
if (!isDefined3(elAttribute) || !attributeValues.includes(elAttribute))
return false;
}
}
}
return true;
}
);
if (!isValid) return;
}
if (query && !query({ ...getEditorPlugin(editor, plugin), element: el })) {
return;
}
if (!parse)
if (isElement) {
parse = ({ type }) => ({ type });
} else if (isLeaf) {
parse = ({ type }) => ({ [type]: true });
} else {
return;
}
const parsedNode = (() => {
if (isSlateNode(el)) {
return {};
}
return parse({
...getEditorPlugin(editor, plugin),
element: el,
node: {}
}) ?? {};
})();
const dataNodeProps = getDataNodeProps({
editor,
element: el,
plugin
});
let node = {
...parsedNode,
...dataNodeProps
};
if (Object.keys(node).length === 0) return;
const injectedPlugins = getInjectedPlugins(editor, plugin);
injectedPlugins.forEach((injectedPlugin) => {
const res = injectedPlugin.parsers?.html?.deserializer?.parse?.({
...getEditorPlugin(editor, plugin),
element: el,
node
});
if (res && !isSlateNode(el)) {
node = {
...node,
...res
};
}
});
if (attributeNames) {
const elementAttributes = {};
const elementAttributeNames = el.getAttributeNames();
for (const elementAttributeName of elementAttributeNames) {
if (attributeNames.includes(elementAttributeName)) {
elementAttributes[elementAttributeName] = el.getAttribute(elementAttributeName);
}
}
if (Object.keys(elementAttributes).length > 0) {
node.attributes = elementAttributes;
}
}
return { ...deserializer, node };
};
// src/lib/plugins/html/utils/pipeDeserializeHtmlElement.ts
var pipeDeserializeHtmlElement = (editor, element) => {
let result;
[...editor.pluginList].reverse().some((plugin) => {
result = pluginDeserializeHtml(editor, plugin, { element });
return !!result;
});
return result;
};
// src/lib/plugins/html/utils/htmlElementToElement.ts
var htmlElementToElement = (editor, element, isSlate = false) => {
const deserialized = pipeDeserializeHtmlElement(editor, element);
if (deserialized) {
const { node, withoutChildren } = deserialized;
let descendants = node.children ?? deserializeHtmlNodeChildren(editor, element, isSlate);
if (descendants.length === 0 || withoutChildren || isSlateVoid(element)) {
descendants = [{ text: "" }];
}
return jsx2("element", node, descendants);
}
};
// src/lib/plugins/html/utils/htmlElementToLeaf.ts
import { ElementApi as ElementApi2, TextApi as TextApi2 } from "@udecode/slate";
import { jsx as jsx3 } from "slate-hyperscript";
// src/lib/plugins/html/utils/pipeDeserializeHtmlLeaf.ts
var pipeDeserializeHtmlLeaf = (editor, element) => {
let node = {};
[...editor.pluginList].reverse().forEach((plugin) => {
const deserialized = pluginDeserializeHtml(editor, plugin, {
deserializeLeaf: true,
element
});
if (!deserialized) return;
node = { ...node, ...deserialized.node };
});
return node;
};
// src/lib/plugins/html/utils/htmlElementToLeaf.ts
var htmlElementToLeaf = (editor, element) => {
const node = pipeDeserializeHtmlLeaf(editor, element);
return deserializeHtmlNodeChildren(editor, element).reduce(
(arr, child) => {
if (!child) return arr;
if (ElementApi2.isElement(child)) {
if (Object.keys(node).length > 0) {
mergeDeepToNodes({
node: child,
query: {
filter: ([n]) => TextApi2.isText(n)
},
source: node
});
}
arr.push(child);
} else {
const attributes = { ...node };
if (TextApi2.isText(child) && child.text) {
Object.keys(attributes).forEach((key) => {
if (attributes[key] && child[key]) {
attributes[key] = child[key];
}
});
}
arr.push(jsx3("text", attributes, child));
}
return arr;
},
[]
);
};
// src/lib/plugins/html/utils/htmlTextNodeToString.ts
var htmlTextNodeToString = (node) => {
if (isHtmlText(node)) {
if (node.parentElement?.dataset.platePreventDeserialization) return "";
return node.textContent || "";
}
};
// src/lib/plugins/html/utils/deserializeHtmlNode.ts
var deserializeHtmlNode = (editor) => (node) => {
const textNode = htmlTextNodeToString(node);
if (textNode) return textNode;
if (!isHtmlElement(node)) return null;
const breakLine = htmlBrToNewLine(node);
if (breakLine) return breakLine;
const fragment = htmlBodyToFragment(editor, node);
if (fragment) return fragment;
const element = htmlElementToElement(
editor,
node,
isSlateNode(node)
);
if (element) return element;
return htmlElementToLeaf(editor, node);
};
// src/lib/plugins/html/utils/deserializeHtmlElement.ts
var deserializeHtmlElement = (editor, element) => {
return deserializeHtmlNode(editor)(element);
};
// src/lib/plugins/html/utils/htmlStringToDOMNode.ts
var htmlStringToDOMNode = (rawHtml) => {
const node = document.createElement("body");
node.innerHTML = rawHtml;
return node;
};
// src/lib/plugins/html/utils/deserializeHtml.ts
var deserializeHtml = (editor, {
collapseWhiteSpace: shouldCollapseWhiteSpace = true,
defaultElementPlugin,
element
}) => {
if (typeof element === "string") {
element = htmlStringToDOMNode(element);
}
if (shouldCollapseWhiteSpace) {
element = collapseWhiteSpace(element);
}
const fragment = deserializeHtmlElement(editor, element);
return normalizeDescendantsToDocumentFragment(editor, {
defaultElementPlugin,
descendants: fragment
});
};
// src/lib/plugins/html/utils/parseHtmlDocument.ts
var parseHtmlDocument = (html) => {
return new DOMParser().parseFromString(html, "text/html");
};
// src/lib/plugins/html/HtmlPlugin.ts
var HtmlPlugin = createSlatePlugin({
key: "html"
}).extendApi(({ editor }) => ({
deserialize: bindFirst(deserializeHtml, editor)
})).extend({
parser: {
fo