diff --git a/.changeset/purple-poems-attend.md b/.changeset/purple-poems-attend.md new file mode 100644 index 0000000000..039fa19b36 --- /dev/null +++ b/.changeset/purple-poems-attend.md @@ -0,0 +1,6 @@ +--- +'@udecode/plate-menu': minor +'@udecode/plate-ai': minor +--- + +Release package diff --git a/packages/ai/.npmignore b/packages/ai/.npmignore new file mode 100644 index 0000000000..7d3b305b17 --- /dev/null +++ b/packages/ai/.npmignore @@ -0,0 +1,3 @@ +__tests__ +__test-utils__ +__mocks__ diff --git a/packages/ai/README.md b/packages/ai/README.md new file mode 100644 index 0000000000..6d3f66599c --- /dev/null +++ b/packages/ai/README.md @@ -0,0 +1 @@ +WIP \ No newline at end of file diff --git a/packages/ai/package.json b/packages/ai/package.json new file mode 100644 index 0000000000..edc7addcef --- /dev/null +++ b/packages/ai/package.json @@ -0,0 +1,71 @@ +{ + "name": "@udecode/plate-ai", + "version": "39.0.0", + "description": "Text AI plugin for Plate", + "keywords": [ + "plate", + "plugin", + "slate" + ], + "homepage": "https://platejs.org", + "bugs": { + "url": "https://github.com/udecode/plate/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/udecode/plate.git", + "directory": "packages/ai" + }, + "license": "MIT", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./react": { + "types": "./dist/react/index.d.ts", + "import": "./dist/react/index.mjs", + "module": "./dist/react/index.mjs", + "require": "./dist/react/index.js" + } + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "brl": "yarn p:brl", + "build": "yarn p:build", + "build:watch": "yarn p:build:watch", + "clean": "yarn p:clean", + "lint": "yarn p:lint", + "lint:fix": "yarn p:lint:fix", + "test": "yarn p:test", + "test:watch": "yarn p:test:watch", + "typecheck": "yarn p:typecheck" + }, + "dependencies": { + "@udecode/plate-combobox": "39.0.0", + "@udecode/plate-markdown": "39.0.0", + "@udecode/plate-menu": "39.0.0", + "@udecode/plate-selection": "39.0.0", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@udecode/plate-common": ">=39.0.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "slate": ">=0.103.0", + "slate-history": ">=0.93.0", + "slate-hyperscript": ">=0.66.0", + "slate-react": ">=0.108.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts new file mode 100644 index 0000000000..e7cccc036f --- /dev/null +++ b/packages/ai/src/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './lib/index'; diff --git a/packages/ai/src/lib/BaseAIPlugin.ts b/packages/ai/src/lib/BaseAIPlugin.ts new file mode 100644 index 0000000000..7cdb51690a --- /dev/null +++ b/packages/ai/src/lib/BaseAIPlugin.ts @@ -0,0 +1,26 @@ +import type { TriggerComboboxPluginOptions } from '@udecode/plate-combobox'; + +import { + type PluginConfig, + type SlateEditor, + type TNodeEntry, + createTSlatePlugin, +} from '@udecode/plate-common'; + +import { withTriggerAIMenu } from './withTriggerAIMenu'; + +export type BaseAIOptions = { + onOpenAI?: (editor: SlateEditor, nodeEntry: TNodeEntry) => void; +} & TriggerComboboxPluginOptions; + +export type BaseAIPluginConfig = PluginConfig<'ai', BaseAIOptions>; + +export const BaseAIPlugin = createTSlatePlugin({ + key: 'ai', + extendEditor: withTriggerAIMenu, + options: { + scrollContainerSelector: '#scroll_container', + trigger: ' ', + triggerPreviousCharPattern: /^\s?$/, + }, +}); diff --git a/packages/ai/src/lib/index.ts b/packages/ai/src/lib/index.ts new file mode 100644 index 0000000000..99b9131797 --- /dev/null +++ b/packages/ai/src/lib/index.ts @@ -0,0 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './BaseAIPlugin'; +export * from './withTriggerAIMenu'; diff --git a/packages/ai/src/lib/withTriggerAIMenu.ts b/packages/ai/src/lib/withTriggerAIMenu.ts new file mode 100644 index 0000000000..44e18acc6c --- /dev/null +++ b/packages/ai/src/lib/withTriggerAIMenu.ts @@ -0,0 +1,75 @@ +import type { ExtendEditor } from '@udecode/plate-core'; + +import { + getAncestorNode, + getEditorString, + getNodeString, + getPointBefore, + getRange, +} from '@udecode/plate-common'; + +import type { BaseAIPluginConfig } from './BaseAIPlugin'; + +export const withTriggerAIMenu: ExtendEditor = ({ + editor, + ...ctx +}) => { + const { insertText } = editor; + + const matchesTrigger = (text: string) => { + const { trigger } = ctx.getOptions(); + + if (trigger instanceof RegExp) { + return trigger.test(text); + } + if (Array.isArray(trigger)) { + return trigger.includes(text); + } + + return text === trigger; + }; + + editor.insertText = (text) => { + const { triggerPreviousCharPattern, triggerQuery } = ctx.getOptions(); + + if ( + !editor.selection || + !matchesTrigger(text) || + (triggerQuery && !triggerQuery(editor)) + ) { + return insertText(text); + } + + // Make sure an input is created at the beginning of line or after a whitespace + const previousChar = getEditorString( + editor, + getRange( + editor, + editor.selection, + getPointBefore(editor, editor.selection) + ) + ); + + const matchesPreviousCharPattern = + triggerPreviousCharPattern?.test(previousChar); + + if (matchesPreviousCharPattern) { + const nodeEntry = getAncestorNode(editor); + + if (!nodeEntry) return insertText(text); + + const [node] = nodeEntry; + + // Make sure can only open menu in the first point + if (getNodeString(node).length > 0) return insertText(text); + + const { onOpenAI } = ctx.getOptions(); + + if (onOpenAI) return onOpenAI(editor, nodeEntry); + } + + return insertText(text); + }; + + return editor; +}; diff --git a/packages/ai/src/react/ai/AIPlugin.ts b/packages/ai/src/react/ai/AIPlugin.ts new file mode 100644 index 0000000000..ed56e328f7 --- /dev/null +++ b/packages/ai/src/react/ai/AIPlugin.ts @@ -0,0 +1,197 @@ +import type { ExtendConfig } from '@udecode/plate-core'; +import type { AriakitTypes } from '@udecode/plate-menu'; +import type { NodeEntry, Path } from 'slate'; + +import { + type PlateEditor, + toDOMNode, + toTPlatePlugin, +} from '@udecode/plate-common/react'; + +import { type BaseAIPluginConfig, BaseAIPlugin } from '../../lib'; +import { useAIHooks } from './useAIHook'; + +export const KEY_AI = 'ai'; + +export interface FetchAISuggestionProps { + abortSignal: AbortController; + prompt: string; + system?: string; +} + +interface ExposeOptions { + createAIEditor: () => PlateEditor; + scrollContainerSelector: string; + fetchStream?: (props: FetchAISuggestionProps) => Promise; + trigger?: RegExp | string[] | string; + + triggerPreviousCharPattern?: RegExp; +} + +export type AISelectors = { + isOpen: (editorId: string) => boolean; +}; + +export type AIApi = { + abort: () => void; + clearLast: () => void; + focusMenu: () => void; + hide: () => void; + setAnchorElement: (dom: HTMLElement) => void; + show: (editorId: string, dom: HTMLElement, nodeEntry: NodeEntry) => void; +}; + +export type AIActionGroup = { + group?: string; + value?: string; +}; + +export type AIPluginConfig = ExtendConfig< + BaseAIPluginConfig, + { + abortController: AbortController | null; + action: AIActionGroup | null; + aiEditor: PlateEditor | null; + aiState: 'done' | 'generating' | 'idle' | 'requesting'; + anchorDom: HTMLElement | null; + curNodeEntry: NodeEntry | null; + initNodeEntry: NodeEntry | null; + lastGenerate: string | null; + lastPrompt: string | null; + lastWorkPath: Path | null; + menuType: 'cursor' | 'selection' | null; + openEditorId: string | null; + store: AriakitTypes.MenuStore | null; + } & ExposeOptions & + AIApi & + AISelectors, + { + ai: AIApi; + } +>; + +export const AIPlugin = toTPlatePlugin(BaseAIPlugin, { + options: { + abortController: null, + action: null, + aiEditor: null, + aiState: 'idle', + anchorDom: null, + curNodeEntry: null, + initNodeEntry: null, + lastGenerate: null, + lastPrompt: null, + lastWorkPath: null, + menuType: null, + openEditorId: null, + store: null, + }, +}) + .extendOptions(({ getOptions }) => ({ + isOpen: (editorId: string) => { + const { openEditorId, store } = getOptions(); + const anchorElement = store?.getState().anchorElement; + const isAnchor = !!anchorElement && document.contains(anchorElement); + + return !!editorId && openEditorId === editorId && isAnchor; + }, + })) + .extendApi< + Required> + >(({ getOptions, setOptions }) => ({ + clearLast: () => { + setOptions({ + lastGenerate: null, + lastPrompt: null, + lastWorkPath: null, + }); + }, + focusMenu: () => { + const { store } = getOptions(); + + setTimeout(() => { + const searchInput = document.querySelector( + '#__potion_ai_menu_searchRef' + ) as HTMLInputElement; + + if (store) { + store.setAutoFocusOnShow(true); + store.setInitialFocus('first'); + searchInput?.focus(); + } + }, 0); + }, + setAnchorElement: (dom: HTMLElement) => { + const { store } = getOptions(); + + if (store) { + store.setAnchorElement(dom); + } + }, + })) + .extendApi>>( + ({ api, getOptions, setOption }) => ({ + abort: () => { + const { abortController } = getOptions(); + + abortController?.abort(); + setOption('aiState', 'idle'); + setTimeout(() => { + api.ai.focusMenu(); + }, 0); + }, + hide: () => { + setOption('openEditorId', null); + getOptions().store?.setAnchorElement(null); + }, + show: (editorId: string, dom: HTMLElement, nodeEntry: NodeEntry) => { + const { store } = getOptions(); + + setOption('openEditorId', editorId); + api.ai.clearLast(); + setOption('initNodeEntry', nodeEntry); + api.ai.setAnchorElement(dom); + store?.show(); + api.ai.focusMenu(); + }, + }) + ) + .extend(({ api, getOptions, setOptions }) => ({ + options: { + onOpenAI(editor, [node, path]) { + // NOTE: toDOMNode is dependent on the React make it to an options if want to support other frame. + const dom = toDOMNode(editor, node); + + if (!dom) return; + + const { scrollContainerSelector } = getOptions(); + + // TODO popup animation + if (scrollContainerSelector) { + const scrollContainer = document.querySelector( + scrollContainerSelector + ); + + if (!scrollContainer) return; + + // Make sure when popup in very bottom the menu within the viewport range. + const rect = dom.getBoundingClientRect(); + const windowHeight = window.innerHeight; + const distanceToBottom = windowHeight - rect.bottom; + + // 261 is height of the menu. + if (distanceToBottom < 261) { + // TODO: scroll animation + scrollContainer.scrollTop += 261 - distanceToBottom; + } + } + + api.ai.show(editor.id, dom, [node, path]); + setOptions({ + aiState: 'idle', + menuType: 'cursor', + }); + }, + }, + useHooks: useAIHooks, + })); diff --git a/packages/ai/src/react/ai/hook/index.ts b/packages/ai/src/react/ai/hook/index.ts new file mode 100644 index 0000000000..c2ddddf1da --- /dev/null +++ b/packages/ai/src/react/ai/hook/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './useAI'; diff --git a/packages/ai/src/react/ai/hook/useAI.ts b/packages/ai/src/react/ai/hook/useAI.ts new file mode 100644 index 0000000000..840e1b78c8 --- /dev/null +++ b/packages/ai/src/react/ai/hook/useAI.ts @@ -0,0 +1,204 @@ +import React, { + type KeyboardEvent, + startTransition, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; + +import { isHotkey } from '@udecode/plate-common'; +import { focusEditor, useEditorPlugin } from '@udecode/plate-common/react'; +import { + type Action, + Ariakit, + filterAndBuildMenuTree, +} from '@udecode/plate-menu'; + +import type { AIActions, AICommands } from '../types'; + +import { type AIActionGroup, AIPlugin } from '../AIPlugin'; +import { streamInsertText, streamInsertTextSelection } from '../stream'; +import { getContent } from '../utils'; + +interface UseAIStateProps { + aiActions: AIActions; + aiCommands: AICommands; + + defaultValues: Record; +} + +export type AICommandsAction = Record; + +export const useAI = ({ + aiActions, + aiCommands, + defaultValues, +}: UseAIStateProps) => { + const { + CursorCommandsActions, + CursorSuggestionActions, + SelectionCommandsActions, + SelectionSuggestionActions, + } = aiActions; + + const { + CursorCommands, + CursorSuggestions, + SelectionCommands, + SelectionSuggestions, + } = aiCommands; + + const { api, editor, setOption, setOptions, useOption } = + useEditorPlugin(AIPlugin); + + const isOpen = useOption('isOpen', editor.id); + const action = useOption('action'); + const aiState = useOption('aiState'); + const menuType = useOption('menuType'); + // eslint-disable-next-line react-hooks/exhaustive-deps + const setAction = (action: AIActionGroup) => setOption('action', action); + + const { aiEditor } = editor.useOptions(AIPlugin); + + const menu = Ariakit.useMenuStore(); + useEffect(() => { + setOptions({ + store: menu, + }); + // eslint-disable-next`-line react-hooks/exhaustive-deps + }, [isOpen, menu, setOptions]); + + const [values, setValues] = useState(defaultValues); + const [searchValue, setSearchValue] = useState(''); + + const streamInsert = useCallback(async () => { + if (!aiEditor) return; + if (menuType === 'selection') { + const content = getContent(editor, aiEditor); + + await streamInsertTextSelection(editor, aiEditor, { + prompt: `user prompt is ${searchValue} the content is ${content}`, + }); + } else if (menuType === 'cursor') { + await streamInsertText(editor, { + prompt: searchValue, + }); + } + }, [aiEditor, editor, menuType, searchValue]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const onInputKeyDown = async (e: KeyboardEvent) => { + if (isHotkey('backspace')(e) && searchValue.length === 0) { + e.preventDefault(); + api.ai.hide(); + focusEditor(editor); + } + if (isHotkey('enter')(e)) await streamInsert(); + }; + + const onCloseMenu = useCallback(() => { + // close menu if ai is not generating + if (aiState === 'idle' || aiState === 'done') { + api.ai.hide(); + focusEditor(editor); + } + // abort if ai is generating + if (aiState === 'generating' || aiState === 'requesting') { + api.ai.abort(); + } + }, [aiState, api.ai, editor]); + + // close on escape + useEffect(() => { + const keydown = (e: any) => { + if (!isOpen || !isHotkey('escape')(e)) return; + + onCloseMenu(); + }; + + document.addEventListener('keydown', keydown); + + return () => { + document.removeEventListener('keydown', keydown); + }; + }, [aiState, api.ai, editor, isOpen, onCloseMenu]); + + const [CurrentItems, CurrentActions] = React.useMemo(() => { + if (aiState === 'done') { + if (menuType === 'selection') + return [SelectionSuggestions, SelectionSuggestionActions]; + + return [CursorSuggestions, CursorSuggestionActions]; + } + if (menuType === 'selection') + return [SelectionCommands, SelectionCommandsActions]; + + return [CursorCommands, CursorCommandsActions]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [aiState, menuType]); + + /** IME */ + const [isComposing, setIsComposing] = useState(false); + + const searchItems = useMemo(() => { + return isComposing + ? [] + : filterAndBuildMenuTree(Object.values(CurrentActions), searchValue); + }, [CurrentActions, isComposing, searchValue]); + + /** Props */ + + const menuProps = useMemo(() => { + return { + flip: false, + loading: aiState === 'generating' || aiState === 'requesting', + open: isOpen, + setAction: setAction, + store: menu, + values: values, + onClickOutside: () => { + return editor.getApi(AIPlugin).ai.hide(); + }, + onValueChange: (value: string) => + startTransition(() => setSearchValue(value)), + onValuesChange: (values: typeof defaultValues) => { + setValues(values); + }, + }; + }, [aiState, editor, isOpen, menu, setAction, values]); + + const comboboxProps = useMemo(() => { + return { + id: '__potion_ai_menu_searchRef', + value: searchValue, + onChange: (e: React.ChangeEvent) => + setSearchValue(e.target.value), + onCompositionEnd: () => setIsComposing(false), + onCompositionStart: () => setIsComposing(true), + onKeyDown: onInputKeyDown, + }; + }, [onInputKeyDown, searchValue]); + + const submitButtonProps = useMemo(() => { + return { + disabled: searchValue.trim().length === 0, + onClick: async () => { + await streamInsert(); + }, + }; + }, [searchValue, streamInsert]); + + return { + CurrentItems, + action, + aiEditor, + aiState, + comboboxProps, + menuProps, + menuType, + searchItems, + submitButtonProps, + onCloseMenu, + }; +}; diff --git a/packages/ai/src/react/ai/index.ts b/packages/ai/src/react/ai/index.ts new file mode 100644 index 0000000000..b5a3f40ad6 --- /dev/null +++ b/packages/ai/src/react/ai/index.ts @@ -0,0 +1,10 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './AIPlugin'; +export * from './types'; +export * from './useAIHook'; +export * from './hook/index'; +export * from './stream/index'; +export * from './utils/index'; diff --git a/packages/ai/src/react/ai/stream/getSystemMessage.ts b/packages/ai/src/react/ai/stream/getSystemMessage.ts new file mode 100644 index 0000000000..abea050e19 --- /dev/null +++ b/packages/ai/src/react/ai/stream/getSystemMessage.ts @@ -0,0 +1,6 @@ +export const getAISystem = () => `\ +You are a text-based conversational robot that helps users with tasks such as continuation and refinement. +Users will provide you with some content, and you will help them with their needs. + +CRITICAL RULE:If you want to start a new line, output a '\n'. If you want to start a new paragraph, output two '\n'. +Do not respond to the user,generate the content directly.`; diff --git a/packages/ai/src/react/ai/stream/index.ts b/packages/ai/src/react/ai/stream/index.ts new file mode 100644 index 0000000000..88a3670ff2 --- /dev/null +++ b/packages/ai/src/react/ai/stream/index.ts @@ -0,0 +1,8 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './getSystemMessage'; +export * from './streamInsertText'; +export * from './streamInsertTextSelection'; +export * from './streamTraversal'; diff --git a/packages/ai/src/react/ai/stream/streamInsertText.ts b/packages/ai/src/react/ai/stream/streamInsertText.ts new file mode 100644 index 0000000000..b7a0f4493c --- /dev/null +++ b/packages/ai/src/react/ai/stream/streamInsertText.ts @@ -0,0 +1,168 @@ +'use client'; +import { + getAncestorNode, + getEndPoint, + insertEmptyElement, + insertText, + replaceNode, + withMerging, +} from '@udecode/plate-common'; +import { type PlateEditor, ParagraphPlugin } from '@udecode/plate-common/react'; +import { deserializeMd } from '@udecode/plate-markdown'; +import { BlockSelectionPlugin } from '@udecode/plate-selection/react'; +import { Path } from 'slate'; + +import { AIPlugin } from '../AIPlugin'; +import { updateMenuAnchorByPath } from '../utils'; +import { getNextPathByNumber } from '../utils/getNextPathByNumber'; +import { getAISystem } from './getSystemMessage'; +import { streamTraversal } from './streamTraversal'; + +interface streamInsertTextOptions { + prompt: string; + startWritingPath?: Path; + system?: string; +} + +export const streamInsertText = async ( + editor: PlateEditor, + { prompt, system = getAISystem(), ...options }: streamInsertTextOptions +) => { + editor.setOptions(AIPlugin, { + aiState: 'requesting', + lastPrompt: prompt, + }); + + const initNodeEntry = editor.getOptions(AIPlugin).initNodeEntry; + + if (!initNodeEntry) return; + + let chuck = ''; + + let workPath = options?.startWritingPath ?? initNodeEntry[1]; + let lastWorkPath: Path | null = null; + + const effectPath: Path[] = []; + + let matchStartCodeblock = false; + let matchEndCodeblock = false; + + let isFirst = true; + + let total = ''; + + await streamTraversal( + editor, + (delta, done) => { + total += delta; + + if (typeof delta !== 'string') return; + if (delta.includes('``') && matchStartCodeblock) { + matchEndCodeblock = true; + } + if (delta.includes('```') && !matchEndCodeblock) { + matchStartCodeblock = true; + } + + const matchParagraph = !matchStartCodeblock && /\n+/.test(delta); + const matchCodeblock = matchStartCodeblock && matchEndCodeblock; + + if (matchParagraph || matchCodeblock) { + const parts = delta.split(/\n+/); + const nextChunkStart = parts[1] ?? ''; + const previousChunkEnd = parts[0] ?? ''; + + if (previousChunkEnd.length > 0) { + const insert = () => { + insertText(editor, previousChunkEnd, { + at: getEndPoint(editor, workPath), + }); + }; + + withMerging(editor, insert); + + chuck += previousChunkEnd; + } + + matchStartCodeblock = false; + matchEndCodeblock = false; + + const v = deserializeMd(editor, chuck); + + const nextWorkPath = getNextPathByNumber(workPath, v.length); + const replace = () => { + // FIX: replace make the anchor disappear + editor.setOptions(AIPlugin, { + openEditorId: null, + }); + + replaceNode(editor, { + at: workPath, + nodes: v, + }); + + if (!done) { + insertEmptyElement(editor, ParagraphPlugin.key, { + at: nextWorkPath, + }); + } + }; + + withMerging(editor, replace); + + if (!done) workPath = nextWorkPath; + + chuck = nextChunkStart; + + return; + } else { + chuck += delta; + } + if (delta) { + if (lastWorkPath === null || !Path.equals(lastWorkPath, workPath)) { + updateMenuAnchorByPath(editor, workPath); + effectPath.push(workPath); + lastWorkPath = workPath; + } + + editor.setOption(AIPlugin, 'aiState', 'generating'); + + const insert = () => { + insertText(editor, delta, { + at: getEndPoint(editor, workPath), + }); + }; + + if (isFirst) { + insert(); + isFirst = false; + } else { + withMerging(editor, insert); + } + } + }, + { + prompt, + system, + } + ); + + /** After the stream */ + updateMenuAnchorByPath(editor, workPath); + + /** Add block selection to all the ai generated blocks */ + effectPath.forEach((path) => { + setTimeout(() => { + const nodeEntry = getAncestorNode(editor, path); + + if (nodeEntry) { + editor + .getApi(BlockSelectionPlugin) + .blockSelection.addSelectedRow(nodeEntry[0].id, { clear: false }); + } + }, 0); + }); + + editor.setOptions(AIPlugin, { aiState: 'done', lastGenerate: total }); + editor.getApi(AIPlugin).ai.focusMenu(); +}; diff --git a/packages/ai/src/react/ai/stream/streamInsertTextSelection.ts b/packages/ai/src/react/ai/stream/streamInsertTextSelection.ts new file mode 100644 index 0000000000..0ca90c36f6 --- /dev/null +++ b/packages/ai/src/react/ai/stream/streamInsertTextSelection.ts @@ -0,0 +1,119 @@ +'use client'; +import { + getEndPoint, + insertEmptyElement, + insertText, + replaceNode, + resetEditor, + withMerging, +} from '@udecode/plate-common'; +import { type PlateEditor, ParagraphPlugin } from '@udecode/plate-common/react'; +import { deserializeMd } from '@udecode/plate-markdown'; + +import { AIPlugin } from '../AIPlugin'; +import { getNextPathByNumber } from '../utils/getNextPathByNumber'; +import { getAISystem } from './getSystemMessage'; +import { streamTraversal } from './streamTraversal'; + +interface StreamInsertTextSelectionOptions { + prompt: string; + system?: string; +} + +export const streamInsertTextSelection = async ( + editor: PlateEditor, + aiEditor: PlateEditor, + { prompt, system = getAISystem() }: StreamInsertTextSelectionOptions +) => { + editor.setOptions(AIPlugin, { + aiState: 'requesting', + lastPrompt: prompt, + }); + + // const { output } = await generate(prompt, getSelectionMenuSystem()); + + let workPath = [0]; + let matchStartCodeblock = false; + let matchEndCodeblock = false; + let chuck = ''; + + aiEditor.children = [{ children: [{ text: '' }], type: 'p' }]; + resetEditor(aiEditor); + + await streamTraversal( + editor, + (delta, done) => { + if (typeof delta !== 'string') return; + // match code block + if (delta.includes('``') && matchStartCodeblock) { + matchEndCodeblock = true; + } + if (delta.includes('```') && !matchEndCodeblock) { + matchStartCodeblock = true; + } + + const matchParagraph = !matchStartCodeblock && delta.match(/\n+/g); + const matchCodeblock = matchStartCodeblock && matchEndCodeblock; + + if (matchParagraph || matchCodeblock) { + const parts = delta.split(/\n+/); + const nextChunkStart = parts[1] ?? ''; + const previousChunkEnd = parts[0] ?? ''; + + if (previousChunkEnd.length > 0) { + insertText(aiEditor, previousChunkEnd, { + at: getEndPoint(aiEditor, workPath), + }); + chuck += previousChunkEnd; + } + + matchStartCodeblock = false; + matchEndCodeblock = false; + + const v = deserializeMd(aiEditor, chuck); + + const nextWorkPath = getNextPathByNumber(workPath, v.length); + + const replace = () => { + replaceNode(aiEditor, { + at: workPath, + nodes: v, + }); + + if (!done) { + insertEmptyElement(aiEditor, ParagraphPlugin.key, { + at: nextWorkPath, + }); + } + }; + + withMerging(aiEditor, replace); + + workPath = nextWorkPath; + + chuck = nextChunkStart; + + return; + } else { + chuck += delta; + } + if (delta) { + insertText(aiEditor, delta, { + at: getEndPoint(aiEditor, workPath), + }); + } + }, + // streamInsertTextSelectionOptions + { + prompt, + system, + } + ); + + editor.setOptions(AIPlugin, { + aiState: 'done', + lastPrompt: prompt, + lastWorkPath: workPath, + }); + editor.getApi(AIPlugin).ai.focusMenu(); +}; diff --git a/packages/ai/src/react/ai/stream/streamTraversal.ts b/packages/ai/src/react/ai/stream/streamTraversal.ts new file mode 100644 index 0000000000..63ff20c8fb --- /dev/null +++ b/packages/ai/src/react/ai/stream/streamTraversal.ts @@ -0,0 +1,46 @@ +'use client'; + +import type { PlateEditor } from '@udecode/plate-core/react'; + +import { AIPlugin } from '../AIPlugin'; + +interface StreamTraversalOptions { + prompt: string; + system: string; +} + +export const streamTraversal = async ( + editor: PlateEditor, + fn: (delta: string, done: boolean) => void, + { prompt, system }: StreamTraversalOptions +) => { + const abortController = new AbortController(); + editor.setOptions(AIPlugin, { abortController }); + + const fetchStream = editor.getOptions(AIPlugin).fetchStream!; + + const response = await fetchStream({ + abortSignal: abortController, + prompt, + system, + }); + + const reader = response.getReader(); + const decoder = new TextDecoder(); + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read(); + + if (done) { + fn('\n', done); + + break; + } + if (value) { + const delta = decoder.decode(value); + + fn(delta, done); + } + } +}; diff --git a/packages/ai/src/react/ai/types.ts b/packages/ai/src/react/ai/types.ts new file mode 100644 index 0000000000..7a7e087bfd --- /dev/null +++ b/packages/ai/src/react/ai/types.ts @@ -0,0 +1,15 @@ +import type { Action } from '@udecode/plate-menu'; + +export interface AIActions { + CursorCommandsActions: Record; + CursorSuggestionActions: Record; + SelectionCommandsActions: Record; + SelectionSuggestionActions: Record; +} + +export interface AICommands { + CursorCommands: React.FC; + CursorSuggestions: React.FC; + SelectionCommands: React.FC; + SelectionSuggestions: React.FC; +} diff --git a/packages/ai/src/react/ai/useAIHook.ts b/packages/ai/src/react/ai/useAIHook.ts new file mode 100644 index 0000000000..93e46544e8 --- /dev/null +++ b/packages/ai/src/react/ai/useAIHook.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; + +import { useEditorPlugin } from '@udecode/plate-common/react'; + +import { type AIPluginConfig, AIPlugin } from './AIPlugin'; + +export const useAIHooks = () => { + const { getOptions, setOptions } = useEditorPlugin(AIPlugin); + useEffect(() => { + setTimeout(() => { + const editor = getOptions().createAIEditor(); + setOptions({ aiEditor: editor }); + }, 0); + }, [setOptions, getOptions]); +}; diff --git a/packages/ai/src/react/ai/utils/getContent.ts b/packages/ai/src/react/ai/utils/getContent.ts new file mode 100644 index 0000000000..18348e7e26 --- /dev/null +++ b/packages/ai/src/react/ai/utils/getContent.ts @@ -0,0 +1,41 @@ +import type { PlateEditor } from '@udecode/plate-common/react'; + +import { getNodeEntries, isBlock } from '@udecode/plate-common'; +import { serializeMd, serializeMdNodes } from '@udecode/plate-markdown'; +import { + type BlockSelectionConfig, + BlockSelectionPlugin, +} from '@udecode/plate-selection/react'; + +import { AIPlugin } from '../AIPlugin'; + +// If some content has already been generated using it to modify(improve) otherwise using the selection or block selected nodes. +export const getContent = (editor: PlateEditor, aiEditor: PlateEditor) => { + const aiState = editor.getOptions(AIPlugin).aiState; + + if (aiState === 'done') return serializeMd(aiEditor); + // Not Sure + if ( + editor.getOption( + BlockSelectionPlugin, + 'isSelecting', + editor.id + ) + ) { + const entries = editor + .getApi(BlockSelectionPlugin) + .blockSelection.getSelectedBlocks(); + + const nodes = Array.from(entries, (entry) => entry[0]); + + return serializeMdNodes(nodes as any); + } + + const entries = getNodeEntries(editor, { + match: (n) => isBlock(editor, n), + mode: 'highest', + }); + const nodes = Array.from(entries, (entry) => entry[0]); + + return serializeMdNodes(nodes as any); +}; diff --git a/packages/ai/src/react/ai/utils/getNextPathByNumber.ts b/packages/ai/src/react/ai/utils/getNextPathByNumber.ts new file mode 100644 index 0000000000..e994d244e6 --- /dev/null +++ b/packages/ai/src/react/ai/utils/getNextPathByNumber.ts @@ -0,0 +1,11 @@ +import { Path } from 'slate'; + +export const getNextPathByNumber = (startPath: Path, number: number) => { + let workPath = startPath; + + for (let i = 0; i < number; i++) { + workPath = Path.next(workPath); + } + + return workPath; +}; diff --git a/packages/ai/src/react/ai/utils/index.ts b/packages/ai/src/react/ai/utils/index.ts new file mode 100644 index 0000000000..c243b7956b --- /dev/null +++ b/packages/ai/src/react/ai/utils/index.ts @@ -0,0 +1,7 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './getContent'; +export * from './getNextPathByNumber'; +export * from './updateMenuAnchorByPath'; diff --git a/packages/ai/src/react/ai/utils/updateMenuAnchorByPath.ts b/packages/ai/src/react/ai/utils/updateMenuAnchorByPath.ts new file mode 100644 index 0000000000..e6c953c282 --- /dev/null +++ b/packages/ai/src/react/ai/utils/updateMenuAnchorByPath.ts @@ -0,0 +1,27 @@ +import type { PlateEditor } from '@udecode/plate-core/react'; +import type { Path } from 'slate'; + +import { getAncestorNode } from '@udecode/plate-common'; +import { toDOMNode } from '@udecode/plate-common/react'; + +import { AIPlugin } from '../AIPlugin'; + +export const updateMenuAnchorByPath = (editor: PlateEditor, path: Path) => { + // FIX: replace make the anchor disappear + editor.setOptions(AIPlugin, { + openEditorId: editor.id, + }); + + const nodeEntry = getAncestorNode(editor, path); + + if (nodeEntry) { + setTimeout(() => { + const dom = toDOMNode(editor, nodeEntry[0]); + + if (dom) { + editor.getApi(AIPlugin).ai.setAnchorElement(dom); + editor.setOption(AIPlugin, 'curNodeEntry', nodeEntry); + } + }, 0); + } +}; diff --git a/packages/ai/src/react/copilot/CopilotPlugin.tsx b/packages/ai/src/react/copilot/CopilotPlugin.tsx new file mode 100644 index 0000000000..0826c19bb1 --- /dev/null +++ b/packages/ai/src/react/copilot/CopilotPlugin.tsx @@ -0,0 +1,214 @@ +import React from 'react'; + +import type { InsertTextOperation } from 'slate'; + +import { + type PluginConfig, + type QueryNodeOptions, + withoutMergingHistory, +} from '@udecode/plate-common'; +import { + ParagraphPlugin, + createTPlatePlugin, +} from '@udecode/plate-common/react'; + +import { generateCopilotTextDebounce } from './generateCopilotText'; +import { InjectCopilot } from './injectCopilot'; +import { onKeyDownCopilot } from './onKeyDownCopilot'; +import { withoutAbort } from './utils/withoutAbort'; + +export interface CopilotHoverCardProps { + suggestionText: string; +} + +export interface FetchCopilotSuggestionProps { + abortSignal: AbortController; + prompt: string; +} + +export type CopilotPluginConfig = PluginConfig< + 'copilot', + { + hoverCard: (props: CopilotHoverCardProps) => JSX.Element; + abortController?: AbortController | null; + completedNodeId?: string | null; + copilotState?: 'completed' | 'idle'; + enableDebounce?: boolean; + enableShortCut?: boolean; + fetchSuggestion?: (props: FetchCopilotSuggestionProps) => Promise; + query?: QueryNodeOptions; + shouldAbort?: boolean; + suggestionText?: string | null; + } & CopilotApi & + CopilotSelectors +>; + +type CopilotSelectors = { + isCompleted?: (id: string) => boolean; +}; + +type CopilotApi = { + abortCopilot?: () => void; + setCopilot?: (id: string, text: string) => void; +}; + +export const CopilotPlugin = createTPlatePlugin({ + key: 'copilot', + options: { + copilotState: 'idle', + enableDebounce: false, + hoverCard: (props) => {props.suggestionText}, + query: { + allow: [ParagraphPlugin.key], + }, + shouldAbort: true, + }, +}) + .extendOptions>(({ getOptions }) => ({ + isCompleted: (id) => getOptions().completedNodeId === id, + })) + .extendApi>(({ getOptions, setOptions }) => ({ + abortCopilot: () => { + const { abortController } = getOptions(); + + abortController?.abort(); + + setOptions({ + abortController: null, + completedNodeId: null, + copilotState: 'idle', + }); + }, + setCopilot: (id, text) => { + setOptions({ + completedNodeId: id, + copilotState: 'completed', + suggestionText: text, + }); + }, + })) + .extend({ + extendEditor: ({ api, editor, getOptions, setOptions }) => { + type CopilotBatch = (typeof editor.history.undos)[number] & { + shouldAbort: boolean; + }; + + const { apply, insertText, redo, setSelection, undo, writeHistory } = + editor; + + editor.undo = () => { + if (getOptions().copilotState === 'idle') return undo(); + + const topUndo = editor.history.undos.at(-1) as CopilotBatch; + const oldText = getOptions().suggestionText; + + if ( + topUndo && + topUndo.shouldAbort === false && + topUndo.operations[0].type === 'insert_text' && + oldText + ) { + withoutAbort(editor, () => { + const shouldInsertText = ( + topUndo.operations[0] as InsertTextOperation + ).text; + + const newText = shouldInsertText + oldText; + setOptions({ suggestionText: newText }); + + undo(); + }); + + return; + } + + return undo(); + }; + + editor.redo = () => { + if (getOptions().copilotState === 'idle') return redo(); + + const topRedo = editor.history.redos.at(-1) as CopilotBatch; + const oldText = getOptions().suggestionText; + + if ( + topRedo && + topRedo.shouldAbort === false && + topRedo.operations[0].type === 'insert_text' && + oldText + ) { + withoutAbort(editor, () => { + const shouldRemoveText = ( + topRedo.operations[0] as InsertTextOperation + ).text; + + const newText = oldText.slice(shouldRemoveText.length); + setOptions({ suggestionText: newText }); + + redo(); + }); + + return; + } + + return redo(); + }; + + editor.writeHistory = (stacks, batch) => { + if (getOptions().copilotState === 'idle') + return writeHistory(stacks, batch); + + const { shouldAbort } = getOptions(); + batch.shouldAbort = shouldAbort; + + return writeHistory(stacks, batch); + }; + + editor.insertText = (text) => { + if (getOptions().copilotState === 'idle') return insertText(text); + + const oldText = getOptions().suggestionText; + + if (text.length === 1 && text === oldText?.at(0)) { + withoutAbort(editor, () => { + withoutMergingHistory(editor, () => { + const newText = oldText?.slice(1); + setOptions({ suggestionText: newText }); + insertText(text); + }); + }); + + return; + } + + insertText(text); + }; + + editor.apply = (operation) => { + const { shouldAbort } = getOptions(); + + if (shouldAbort) { + api.copilot.abortCopilot(); + } + + apply(operation); + }; + + editor.setSelection = (selection) => { + if (getOptions().enableDebounce) { + void generateCopilotTextDebounce(editor, { isDebounce: true }); + } + + return setSelection(selection); + }; + + return editor; + }, + render: { + // TODO: type + belowNodes: InjectCopilot as any, + }, + handlers: { + onKeyDown: onKeyDownCopilot as any, + }, + }); diff --git a/packages/ai/src/react/copilot/generateCopilotText.ts b/packages/ai/src/react/copilot/generateCopilotText.ts new file mode 100644 index 0000000000..cea8094afb --- /dev/null +++ b/packages/ai/src/react/copilot/generateCopilotText.ts @@ -0,0 +1,71 @@ +import type { KeyboardEvent } from 'react'; + +import type { PlateEditor } from '@udecode/plate-core/react'; + +import { + getAncestorNode, + getNodeString, + isEndPoint, + isExpanded, +} from '@udecode/plate-common'; +import debounce from 'lodash/debounce.js'; + +import { AIPlugin } from '../ai'; +import { CopilotPlugin } from './CopilotPlugin'; + +export const generateCopilotText = async ( + editor: PlateEditor, + options: { + event?: KeyboardEvent; + isDebounce?: boolean; + } +) => { + const { copilotState, fetchSuggestion, query } = + editor.getOptions(CopilotPlugin); + + if (copilotState === 'completed') return; + + const aiState = editor.getOption(AIPlugin, 'aiState'); + + if (aiState !== 'idle') return; + + const nodeEntry = getAncestorNode(editor); + + if (!nodeEntry) return; + if (isExpanded(editor.selection)) return; + + const isEnd = isEndPoint(editor, editor.selection?.focus, nodeEntry[1]); + + if (!isEnd) return; + + options.event?.preventDefault(); + + const [node] = nodeEntry; + + if (!query?.allow?.includes(node.type as string)) return; + + const prompt = getNodeString(node); + + if (prompt.length === 0) return; + + const abortController = new AbortController(); + + const { abortCopilot, setCopilot } = editor.getApi(CopilotPlugin).copilot; + + // abort the last request + abortCopilot(); + + editor.setOptions(CopilotPlugin, { abortController }); + + const suggestion = await fetchSuggestion?.({ + abortSignal: abortController, + prompt, + }); + + return setCopilot( + node.id, + suggestion ?? 'Can not get suggestion did you config fetchSuggestion?' + ); +}; + +export const generateCopilotTextDebounce = debounce(generateCopilotText, 500); diff --git a/packages/ai/src/react/copilot/index.ts b/packages/ai/src/react/copilot/index.ts new file mode 100644 index 0000000000..97be77d466 --- /dev/null +++ b/packages/ai/src/react/copilot/index.ts @@ -0,0 +1,9 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './CopilotPlugin'; +export * from './generateCopilotText'; +export * from './injectCopilot'; +export * from './onKeyDownCopilot'; +export * from './utils/index'; diff --git a/packages/ai/src/react/copilot/injectCopilot.tsx b/packages/ai/src/react/copilot/injectCopilot.tsx new file mode 100644 index 0000000000..10196f752d --- /dev/null +++ b/packages/ai/src/react/copilot/injectCopilot.tsx @@ -0,0 +1,48 @@ +import React, { useMemo } from 'react'; + +import { getAncestorNode } from '@udecode/plate-common'; +import { + type NodeWrapperComponentProps, + findNodePath, + useEditorPlugin, +} from '@udecode/plate-common/react'; + +import { type CopilotPluginConfig, CopilotPlugin } from './CopilotPlugin'; + +export const InjectCopilot = ( + injectProps: NodeWrapperComponentProps +) => { + const { element } = injectProps; + const { editor, getOptions } = useEditorPlugin(CopilotPlugin); + + const isCompleted = editor.useOption( + CopilotPlugin, + 'isCompleted', + element.id as string + ); + + const nodeType = useMemo(() => { + const path = findNodePath(editor, element); + + if (!path) return; + + const node = getAncestorNode(editor, path); + + return node?.[0].type; + }, [editor, element]); + + const { hoverCard: HoverCard, query, suggestionText } = getOptions(); + + if (query?.allow?.includes(nodeType as string)) { + return function Component({ children }: { children: React.ReactNode }) { + return ( + + {children} + {isCompleted && suggestionText && ( + + )} + + ); + }; + } +}; diff --git a/packages/ai/src/react/copilot/onKeyDownCopilot.ts b/packages/ai/src/react/copilot/onKeyDownCopilot.ts new file mode 100644 index 0000000000..f2d2fb3284 --- /dev/null +++ b/packages/ai/src/react/copilot/onKeyDownCopilot.ts @@ -0,0 +1,56 @@ +import { + insertText, + isHotkey, + withoutMergingHistory, +} from '@udecode/plate-common'; +import { type KeyboardHandler, Hotkeys } from '@udecode/plate-common/react'; + +import { type CopilotPluginConfig, CopilotPlugin } from './CopilotPlugin'; +import { generateCopilotText } from './generateCopilotText'; +import { withoutAbort } from './utils/withoutAbort'; + +export const onKeyDownCopilot: KeyboardHandler = ({ + editor, + event, + getOptions, + setOptions, +}) => { + if (event.defaultPrevented) return; + + const { + copilotState: state, + enableShortCut = true, + suggestionText: completionText, + } = getOptions(); + + if (state === 'completed' && Hotkeys.isTab(editor, event)) { + event.preventDefault(); + withoutMergingHistory(editor, () => { + insertText(editor, completionText!); + }); + } + if (isHotkey('ctrl+space')(event) && enableShortCut) { + void generateCopilotText(editor, { event, isDebounce: false }); + } + if (isHotkey('cmd+right')(event) && state === 'completed') { + event.preventDefault(); + const text = completionText!; + // TODO: support Chinese. + const firstWord = /^\s*\S+/.exec(text)?.[0] || ''; + const remainingText = text.slice(firstWord.length); + + setOptions({ suggestionText: remainingText }); + + withoutAbort(editor, () => { + withoutMergingHistory(editor, () => { + insertText(editor, firstWord); + }); + }); + } + if (state === 'completed' && isHotkey('escape')(event)) { + event.preventDefault(); + event.stopPropagation(); + + return editor.getApi(CopilotPlugin).copilot.abortCopilot(); + } +}; diff --git a/packages/ai/src/react/copilot/utils/index.ts b/packages/ai/src/react/copilot/utils/index.ts new file mode 100644 index 0000000000..c7c7868302 --- /dev/null +++ b/packages/ai/src/react/copilot/utils/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './withoutAbort'; diff --git a/packages/ai/src/react/copilot/utils/withoutAbort.ts b/packages/ai/src/react/copilot/utils/withoutAbort.ts new file mode 100644 index 0000000000..55c36254ee --- /dev/null +++ b/packages/ai/src/react/copilot/utils/withoutAbort.ts @@ -0,0 +1,9 @@ +import type { PlateEditor } from '@udecode/plate-common/react'; + +import { CopilotPlugin } from '..'; + +export const withoutAbort = (editor: PlateEditor, fn: () => void) => { + editor.setOptions(CopilotPlugin, { shouldAbort: false }); + fn(); + editor.setOptions(CopilotPlugin, { shouldAbort: true }); +}; diff --git a/packages/ai/src/react/index.ts b/packages/ai/src/react/index.ts new file mode 100644 index 0000000000..4b6a3dbc5f --- /dev/null +++ b/packages/ai/src/react/index.ts @@ -0,0 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './ai/index'; +export * from './copilot/index'; diff --git a/packages/ai/tsconfig.build.json b/packages/ai/tsconfig.build.json new file mode 100644 index 0000000000..425481e027 --- /dev/null +++ b/packages/ai/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../config/tsconfig.build.json", + "compilerOptions": { + "declarationDir": "./dist", + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/ai/tsconfig.json b/packages/ai/tsconfig.json new file mode 100644 index 0000000000..ad83d092a5 --- /dev/null +++ b/packages/ai/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["src"], + "exclude": [] +} diff --git a/packages/menu/.npmignore b/packages/menu/.npmignore new file mode 100644 index 0000000000..7d3b305b17 --- /dev/null +++ b/packages/menu/.npmignore @@ -0,0 +1,3 @@ +__tests__ +__test-utils__ +__mocks__ diff --git a/packages/menu/README.md b/packages/menu/README.md new file mode 100644 index 0000000000..6d3f66599c --- /dev/null +++ b/packages/menu/README.md @@ -0,0 +1 @@ +WIP \ No newline at end of file diff --git a/packages/menu/package.json b/packages/menu/package.json new file mode 100644 index 0000000000..9ece57ea8d --- /dev/null +++ b/packages/menu/package.json @@ -0,0 +1,71 @@ +{ + "name": "@udecode/plate-menu", + "version": "39.0.0", + "description": "Menu for Plate", + "keywords": [ + "plate", + "menu", + "slate" + ], + "homepage": "https://platejs.org", + "bugs": { + "url": "https://github.com/udecode/plate/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/udecode/plate.git", + "directory": "packages/menu" + }, + "license": "MIT", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./react": { + "types": "./dist/react/index.d.ts", + "import": "./dist/react/index.mjs", + "module": "./dist/react/index.mjs", + "require": "./dist/react/index.js" + } + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "brl": "yarn p:brl", + "build": "yarn p:build", + "build:watch": "yarn p:build:watch", + "clean": "yarn p:clean", + "lint": "yarn p:lint", + "lint:fix": "yarn p:lint:fix", + "test": "yarn p:test", + "test:watch": "yarn p:test:watch", + "typecheck": "yarn p:typecheck" + }, + "dependencies": { + "@ariakit/react": "0.4.7", + "match-sorter": "6.3.4" + }, + "devDependencies": { + "@udecode/plate-common": "workspace:^" + }, + "peerDependencies": { + "@udecode/plate-common": ">=39.0.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "slate": ">=0.103.0", + "slate-history": ">=0.93.0", + "slate-hyperscript": ">=0.66.0", + "slate-react": ">=0.108.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/menu/src/hooks/index.ts b/packages/menu/src/hooks/index.ts new file mode 100644 index 0000000000..097a2db989 --- /dev/null +++ b/packages/menu/src/hooks/index.ts @@ -0,0 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './useMenu'; +export * from './useMenuItem'; diff --git a/packages/menu/src/hooks/useMenu.tsx b/packages/menu/src/hooks/useMenu.tsx new file mode 100644 index 0000000000..9f52266d0a --- /dev/null +++ b/packages/menu/src/hooks/useMenu.tsx @@ -0,0 +1,96 @@ +import React from 'react'; + +import type { MenuProps, setAction } from '../types'; + +import { Ariakit } from '../lib'; +import { useOnClickOutside } from '../utils/useOnClickOutside'; + +export const SearchableContext = React.createContext(false); + +export const ActionContext = React.createContext(null); + +export const useMenu = ({ + children, + combobox, + comboboxClassName, + comboboxListClassName, + comboboxSubmitButton, + dragButton, + flip = true, + getAnchorRect, + icon, + injectAboveMenu, + label, + loading, + loadingPlaceholder, + open, + placement, + portal, + ref, + searchValue, + setAction, + store, + values, + onClickOutside, + onOpenChange, + onRootMenuClose, + onValueChange, + onValuesChange, + ...props +}: MenuProps & { ref: React.ForwardedRef }) => { + const parent = Ariakit.useMenuContext(); + const searchable = searchValue != null || !!onValueChange || !!combobox; + const ParentSetAction = React.useContext(ActionContext); + + const isRootMenu = !parent; + const isDraggleButtonMenu = !!dragButton; + const menuRef = React.useRef(null); + + useOnClickOutside(menuRef, onClickOutside); + + const menuProviderProps = { + open, + placement: isRootMenu ? placement : 'right', + setOpen: (v: boolean) => { + onOpenChange?.(v); + + if (!v && !parent && !dragButton) onRootMenuClose?.(); + }, + setValues: onValuesChange, + showTimeout: 100, + store, + values, + }; + + const menuButtonProps = { + ref, + ...props, + }; + + const menuProps = { + flip, + getAnchorRect, + gutter: isRootMenu ? 0 : 4, + portal, + ref: isRootMenu ? menuRef : undefined, + unmountOnHide: true, + }; + + const comboboxProviderProps = { + includesBaseElement: false, + resetValueOnHide: true, + setValue: onValueChange, + value: searchValue, + }; + + return { + ParentSetAction, + comboboxProviderProps, + isDraggleButtonMenu, + isRootMenu, + menuButtonProps, + menuProps, + menuProviderProps, + searchable, + }; +}; diff --git a/packages/menu/src/hooks/useMenuItem.tsx b/packages/menu/src/hooks/useMenuItem.tsx new file mode 100644 index 0000000000..1368fc0483 --- /dev/null +++ b/packages/menu/src/hooks/useMenuItem.tsx @@ -0,0 +1,113 @@ +import React from 'react'; + +import type { MenuItemProps } from '../types'; + +import { Ariakit } from '../lib'; +import { ActionContext, SearchableContext } from './useMenu'; + +type UseMenuItemProps = MenuItemProps & { + ref: React.ForwardedRef; +}; + +export const useMenuItem = ({ + className, + group, + icon, + label, + name, + parentGroup, + preventClose, + ref, + shortcut, + value, + ...props +}: UseMenuItemProps) => { + const menu = Ariakit.useMenuContext(); + + if (!menu) throw new Error('MenuItem should be used inside a Menu'); + + const setAction = React.useContext(ActionContext); + + const searchable = React.useContext(SearchableContext); + + const baseOnClick = (event: React.MouseEvent) => { + props.onClick?.(event); + + if (event.isDefaultPrevented()) return; + if (setAction === null) + console.warn('Did you forget to pass the setAction prop?'); + + setAction?.({ group, value }); + }; + + const baseProps: MenuItemProps = { + blurOnHoverEnd: false, + focusOnHover: true, + label, + ref, + ...props, + group: parentGroup, + name: group, + value: value || label, + onClick: baseOnClick, + }; + + const isCheckable = menu.useState((state) => { + if (!group) return false; + if (value == null) return false; + + return state.values[group] != null; + }); + + const isChecked = menu.useState((state) => { + if (!group) return false; + + return state.values[group] === value; + }); + + const isRadio = name != null && value != null; + + const radioProps = { + ...baseProps, + hideOnClick: true, + name: name as string, + value: value as string, + }; + + const hideOnClick = (event: React.MouseEvent) => { + const expandable = event.currentTarget.hasAttribute('aria-expanded'); + + if (expandable) return false; + if (preventClose) return false; + + menu.hideAll(); + + return false; + }; + + const selectValueOnClick = () => { + if (name == null || value == null) return false; + + menu.setValue(name, value); + + return true; + }; + + const comboboxProps = { + ...baseProps, + hideOnClick: hideOnClick, + selectValueOnClick: selectValueOnClick, + setValueOnClick: selectValueOnClick, + value: isCheckable ? value : undefined, + }; + + return { + baseProps, + comboboxProps, + isCheckable, + isChecked, + isRadio, + radioProps, + searchable, + }; +}; diff --git a/packages/menu/src/index.ts b/packages/menu/src/index.ts new file mode 100644 index 0000000000..cad68a5708 --- /dev/null +++ b/packages/menu/src/index.ts @@ -0,0 +1,8 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './hooks/index'; +export * from './lib/index'; +export * from './types/index'; +export * from './utils/index'; diff --git a/packages/menu/src/lib/Ariakit.ts b/packages/menu/src/lib/Ariakit.ts new file mode 100644 index 0000000000..20cabed108 --- /dev/null +++ b/packages/menu/src/lib/Ariakit.ts @@ -0,0 +1,3 @@ +export * as Ariakit from '@ariakit/react'; + +export type * as AriakitTypes from '@ariakit/react'; diff --git a/packages/menu/src/lib/index.ts b/packages/menu/src/lib/index.ts new file mode 100644 index 0000000000..dc5cc8d196 --- /dev/null +++ b/packages/menu/src/lib/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './Ariakit'; diff --git a/packages/menu/src/types/action.ts b/packages/menu/src/types/action.ts new file mode 100644 index 0000000000..1cb6173db2 --- /dev/null +++ b/packages/menu/src/types/action.ts @@ -0,0 +1,17 @@ +export interface Action { + group?: string; + groupName?: string; + icon?: React.ReactNode; + items?: Action[]; + keywords?: string[]; + label?: string; + shortcut?: string; + value?: string; +} + +export type actionGroup = { + group?: string; + value?: string; +}; + +export type setAction = (actionGroup: actionGroup) => void; diff --git a/packages/menu/src/types/index.ts b/packages/menu/src/types/index.ts new file mode 100644 index 0000000000..cfc956a561 --- /dev/null +++ b/packages/menu/src/types/index.ts @@ -0,0 +1,7 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './action'; +export * from './menu'; +export * from './menuItem'; diff --git a/packages/menu/src/types/menu.ts b/packages/menu/src/types/menu.ts new file mode 100644 index 0000000000..3b33a8a8c8 --- /dev/null +++ b/packages/menu/src/types/menu.ts @@ -0,0 +1,37 @@ +import type { Ariakit } from '../lib'; +import type { setAction } from './action'; + +export interface MenuProps extends Ariakit.MenuButtonProps<'div'> { + combobox?: Ariakit.ComboboxProps['render']; + comboboxClassName?: string; + comboboxListClassName?: string; + comboboxSubmitButton?: React.ReactElement; + dragButton?: Ariakit.MenuButtonProps['render']; + flip?: boolean; + getAnchorRect?: (anchor: HTMLElement | null) => AnchorRect | null; + icon?: React.ReactNode; + injectAboveMenu?: React.ReactElement; + label?: React.ReactNode; + loading?: boolean; + loadingPlaceholder?: React.ReactNode; + onClickOutside?: (event: MouseEvent) => void; + onOpenChange?: (open: boolean) => void; + onRootMenuClose?: () => void; + onValueChange?: (value: string) => void; + onValuesChange?: Ariakit.MenuProviderProps['setValues']; + open?: boolean; + placement?: Ariakit.MenuProviderProps['placement']; + portal?: Ariakit.MenuProps['portal']; + searchValue?: string; + setAction?: setAction; + values?: Ariakit.MenuProviderProps['values']; +} + +export interface AnchorRect { + bottom: number; + height: number; + left: number; + right: number; + top: number; + width: number; +} diff --git a/packages/menu/src/types/menuItem.ts b/packages/menu/src/types/menuItem.ts new file mode 100644 index 0000000000..d078457492 --- /dev/null +++ b/packages/menu/src/types/menuItem.ts @@ -0,0 +1,13 @@ +import type { Ariakit } from '../lib'; + +export interface MenuItemProps + extends Omit { + group?: string; + icon?: React.ReactNode; + label?: string; + name?: string; + parentGroup?: string; + preventClose?: boolean; + shortcut?: string; + value?: string; +} diff --git a/packages/menu/src/utils/buildMenuTree.ts b/packages/menu/src/utils/buildMenuTree.ts new file mode 100644 index 0000000000..d456a17c49 --- /dev/null +++ b/packages/menu/src/utils/buildMenuTree.ts @@ -0,0 +1,23 @@ +import type { Action } from '../types'; + +export function buildMenuTree(actions: Action[] | null) { + if (!actions) return null; + + return actions.reduce((actions, option) => { + if (option.groupName) { + const groupName = actions.find( + (action) => action.label === option.groupName + ); + + if (groupName) { + groupName.items!.push(option); + } else { + actions.push({ items: [option], label: option.groupName }); + } + } else { + actions.push(option); + } + + return actions; + }, []); +} diff --git a/packages/menu/src/utils/filterAndBuildMenuTree.ts b/packages/menu/src/utils/filterAndBuildMenuTree.ts new file mode 100644 index 0000000000..21c477132f --- /dev/null +++ b/packages/menu/src/utils/filterAndBuildMenuTree.ts @@ -0,0 +1,21 @@ +import { matchSorter } from 'match-sorter'; + +import type { Action } from '../types'; + +import { buildMenuTree } from './buildMenuTree'; +import { flattenMenuTree } from './flattenMenuTree'; + +export function filterAndBuildMenuTree( + actions: Action[], + searchValue: string +): Action[] | null { + if (!searchValue) return null; + + const options = flattenMenuTree(actions); + + const matches = matchSorter(options, searchValue, { + keys: ['label', 'group', 'value', 'keywords'], + }); + + return buildMenuTree(matches.slice(0, 15)); +} diff --git a/packages/menu/src/utils/flattenMenuTree.ts b/packages/menu/src/utils/flattenMenuTree.ts new file mode 100644 index 0000000000..fcb39ee254 --- /dev/null +++ b/packages/menu/src/utils/flattenMenuTree.ts @@ -0,0 +1,20 @@ +import type { Action } from '../types'; + +export function flattenMenuTree(actions: Action[]): Action[] { + return actions.flatMap((item) => { + if (item.items) { + const parentGroup = item.group ?? item.label; + const groupName = item.label; + + return flattenMenuTree( + item.items.map(({ group, ...item }) => ({ + ...item, + group: group ?? parentGroup, + groupName, + })) + ); + } + + return item; + }); +} diff --git a/packages/menu/src/utils/index.ts b/packages/menu/src/utils/index.ts new file mode 100644 index 0000000000..b37821fcc7 --- /dev/null +++ b/packages/menu/src/utils/index.ts @@ -0,0 +1,8 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './buildMenuTree'; +export * from './filterAndBuildMenuTree'; +export * from './flattenMenuTree'; +export * from './useOnClickOutside'; diff --git a/packages/menu/src/utils/useOnClickOutside.ts b/packages/menu/src/utils/useOnClickOutside.ts new file mode 100644 index 0000000000..ea829c96aa --- /dev/null +++ b/packages/menu/src/utils/useOnClickOutside.ts @@ -0,0 +1,120 @@ +import { useEffect, useLayoutEffect, useRef } from 'react'; + +/** + * Determines the appropriate effect hook to use based on the environment. If + * the code is running on the client-side (browser), it uses the + * `useLayoutEffect` hook, otherwise, it uses the `useEffect` hook. + */ +export const useIsomorphicLayoutEffect = + typeof window === 'undefined' ? useEffect : useLayoutEffect; + +// MediaQueryList Event based useEventListener interface +function useEventListener( + eventName: K, + handler: (event: MediaQueryListEventMap[K]) => void, + element: React.RefObject, + options?: AddEventListenerOptions | boolean +): void; + +// Window Event based useEventListener interface +function useEventListener( + eventName: K, + handler: (event: WindowEventMap[K]) => void, + element?: undefined, + options?: AddEventListenerOptions | boolean +): void; + +// Element Event based useEventListener interface +function useEventListener< + K extends keyof HTMLElementEventMap, + T extends HTMLElement = HTMLDivElement, +>( + eventName: K, + handler: (event: HTMLElementEventMap[K]) => void, + element: React.RefObject, + options?: AddEventListenerOptions | boolean +): void; + +// Document Event based useEventListener interface +function useEventListener( + eventName: K, + handler: (event: DocumentEventMap[K]) => void, + element: React.RefObject, + options?: AddEventListenerOptions | boolean +): void; + +// https://usehooks-ts.com/react-hook/use-event-listener +function useEventListener< + KW extends keyof WindowEventMap, + KH extends keyof HTMLElementEventMap, + KM extends keyof MediaQueryListEventMap, + T extends HTMLElement | MediaQueryList | void = void, +>( + eventName: KH | KM | KW, + handler: ( + event: + | Event + | HTMLElementEventMap[KH] + | MediaQueryListEventMap[KM] + | WindowEventMap[KW] + ) => void, + element?: React.RefObject, + options?: AddEventListenerOptions | boolean +) { + // Create a ref that stores handler + const savedHandler = useRef(handler); + + useIsomorphicLayoutEffect(() => { + savedHandler.current = handler; + }, [handler]); + + useEffect(() => { + // Define the listening target + const targetElement: T | Window = element?.current ?? window; + + if (!(targetElement && targetElement.addEventListener)) return; + + // Create event listener that calls handler function stored in ref + const listener: typeof handler = (event) => savedHandler.current(event); + + targetElement.addEventListener(eventName, listener, options); + + // Remove event listener on cleanup + return () => { + targetElement.removeEventListener(eventName, listener, options); + }; + }, [eventName, element, options]); +} + +type Handler = (event: MouseEvent) => void; + +/** + * Attaches an event listener to detect clicks that occur outside a given + * element. + * + * @template T - The type of HTMLElement that the ref is referring to. + * @param {RefObject} ref - A React ref object that points to the element to + * listen for clicks outside of. + * @param {Handler} handler - The callback function to be executed when a click + * occurs outside the element. + * @param {string} [mouseEvent='mousedown'] - The type of mouse event to listen + * for (e.g., 'mousedown', 'mouseup'). Default is `'mousedown'` + */ +export const useOnClickOutside = ( + ref: React.RefObject, + handler?: Handler, + mouseEvent: 'mousedown' | 'mouseup' = 'mousedown' +): void => { + useEventListener(mouseEvent, (event) => { + if (!handler) return; + + const el = ref?.current; + + // Do nothing if clicking ref's element or descendent elements + if (!el || el.contains(event.target as Node)) { + return; + } + + handler(event); + }); +}; diff --git a/packages/menu/tsconfig.build.json b/packages/menu/tsconfig.build.json new file mode 100644 index 0000000000..425481e027 --- /dev/null +++ b/packages/menu/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../config/tsconfig.build.json", + "compilerOptions": { + "declarationDir": "./dist", + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/menu/tsconfig.json b/packages/menu/tsconfig.json new file mode 100644 index 0000000000..ad83d092a5 --- /dev/null +++ b/packages/menu/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["src"], + "exclude": [] +} diff --git a/yarn.lock b/yarn.lock index 1789e7f269..073adac2f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,6 +58,13 @@ __metadata: languageName: node linkType: hard +"@ariakit/core@npm:0.4.7": + version: 0.4.7 + resolution: "@ariakit/core@npm:0.4.7" + checksum: 10c0/107f0cf197f2674c0e0b11660a8b5e4a966704914f49726f72ffa81c04ef6d8144c2df0a9f7ac23481b9598d1b10b73cf9e00f211c611172b4946ed024ee4460 + languageName: node + linkType: hard + "@ariakit/react-core@npm:0.4.11": version: 0.4.11 resolution: "@ariakit/react-core@npm:0.4.11" @@ -72,6 +79,20 @@ __metadata: languageName: node linkType: hard +"@ariakit/react-core@npm:0.4.7": + version: 0.4.7 + resolution: "@ariakit/react-core@npm:0.4.7" + dependencies: + "@ariakit/core": "npm:0.4.7" + "@floating-ui/dom": "npm:^1.0.0" + use-sync-external-store: "npm:^1.2.0" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/8cbd462bed3f328d523c05eee12424b2f70e35e585772d333d6c4513631f10bfab39cc0e36acca09afad4d3e50c44c2305fdbf917574e4e3ab084b425eb8a245 + languageName: node + linkType: hard + "@ariakit/react@npm:0.4.11": version: 0.4.11 resolution: "@ariakit/react@npm:0.4.11" @@ -84,6 +105,18 @@ __metadata: languageName: node linkType: hard +"@ariakit/react@npm:0.4.7": + version: 0.4.7 + resolution: "@ariakit/react@npm:0.4.7" + dependencies: + "@ariakit/react-core": "npm:0.4.7" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/9f919d72d3a57cc270747d83e10e3a0ef057cdaa038f2af07e16284892802bb365cff58fb0a287f6233ae24cf2d2f38c315411b16a68a6b4c212922b921a63cc + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2": version: 7.24.2 resolution: "@babel/code-frame@npm:7.24.2" @@ -469,6 +502,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.8": + version: 7.25.6 + resolution: "@babel/runtime@npm:7.25.6" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2 + languageName: node + linkType: hard + "@babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0, @babel/template@npm:^7.3.3": version: 7.24.0 resolution: "@babel/template@npm:7.24.0" @@ -5862,7 +5904,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/cn@workspace:packages/cn" dependencies: - "@udecode/react-utils": "npm:38.0.1" + "@udecode/react-utils": "npm:39.0.0" peerDependencies: class-variance-authority: ">=0.7.0" react: ">=16.8.0" @@ -5871,13 +5913,33 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-alignment@npm:38.0.1, @udecode/plate-alignment@workspace:^, @udecode/plate-alignment@workspace:packages/alignment": +"@udecode/plate-ai@workspace:packages/ai": + version: 0.0.0-use.local + resolution: "@udecode/plate-ai@workspace:packages/ai" + dependencies: + "@udecode/plate-combobox": "npm:39.0.0" + "@udecode/plate-markdown": "npm:39.0.0" + "@udecode/plate-menu": "npm:39.0.0" + "@udecode/plate-selection": "npm:39.0.0" + lodash: "npm:^4.17.21" + peerDependencies: + "@udecode/plate-common": ">=39.0.0" + react: ">=16.8.0" + react-dom: ">=16.8.0" + slate: ">=0.103.0" + slate-history: ">=0.93.0" + slate-hyperscript: ">=0.66.0" + slate-react: ">=0.108.0" + languageName: unknown + linkType: soft + +"@udecode/plate-alignment@npm:39.0.0, @udecode/plate-alignment@workspace:^, @udecode/plate-alignment@workspace:packages/alignment": version: 0.0.0-use.local resolution: "@udecode/plate-alignment@workspace:packages/alignment" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5887,14 +5949,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-autoformat@npm:38.0.1, @udecode/plate-autoformat@workspace:^, @udecode/plate-autoformat@workspace:packages/autoformat": +"@udecode/plate-autoformat@npm:39.0.0, @udecode/plate-autoformat@workspace:^, @udecode/plate-autoformat@workspace:packages/autoformat": version: 0.0.0-use.local resolution: "@udecode/plate-autoformat@workspace:packages/autoformat" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5904,16 +5966,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-basic-elements@npm:38.0.12, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": +"@udecode/plate-basic-elements@npm:39.0.0, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": version: 0.0.0-use.local resolution: "@udecode/plate-basic-elements@workspace:packages/basic-elements" dependencies: - "@udecode/plate-block-quote": "npm:38.0.1" - "@udecode/plate-code-block": "npm:38.0.1" + "@udecode/plate-block-quote": "npm:39.0.0" + "@udecode/plate-code-block": "npm:39.0.0" "@udecode/plate-common": "workspace:^" - "@udecode/plate-heading": "npm:38.0.12" + "@udecode/plate-heading": "npm:39.0.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5923,13 +5985,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-basic-marks@npm:38.0.1, @udecode/plate-basic-marks@workspace:^, @udecode/plate-basic-marks@workspace:packages/basic-marks": +"@udecode/plate-basic-marks@npm:39.0.0, @udecode/plate-basic-marks@workspace:^, @udecode/plate-basic-marks@workspace:packages/basic-marks": version: 0.0.0-use.local resolution: "@udecode/plate-basic-marks@workspace:packages/basic-marks" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5939,13 +6001,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-block-quote@npm:38.0.1, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": +"@udecode/plate-block-quote@npm:39.0.0, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": version: 0.0.0-use.local resolution: "@udecode/plate-block-quote@workspace:packages/block-quote" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5955,13 +6017,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-break@npm:38.0.1, @udecode/plate-break@workspace:^, @udecode/plate-break@workspace:packages/break": +"@udecode/plate-break@npm:39.0.0, @udecode/plate-break@workspace:^, @udecode/plate-break@workspace:packages/break": version: 0.0.0-use.local resolution: "@udecode/plate-break@workspace:packages/break" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5977,7 +6039,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -5994,7 +6056,7 @@ __metadata: "@udecode/plate-common": "workspace:^" react-textarea-autosize: "npm:^8.5.3" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6013,7 +6075,7 @@ __metadata: delay: "npm:5.0.0" p-defer: "npm:^4.0.1" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6023,14 +6085,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-code-block@npm:38.0.1, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": +"@udecode/plate-code-block@npm:39.0.0, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": version: 0.0.0-use.local resolution: "@udecode/plate-code-block@workspace:packages/code-block" dependencies: "@udecode/plate-common": "workspace:^" prismjs: "npm:^1.29.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6040,13 +6102,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-combobox@npm:38.0.1, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": +"@udecode/plate-combobox@npm:39.0.0, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": version: 0.0.0-use.local resolution: "@udecode/plate-combobox@workspace:packages/combobox" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6056,14 +6118,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-comments@npm:38.0.1, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": +"@udecode/plate-comments@npm:39.0.0, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": version: 0.0.0-use.local resolution: "@udecode/plate-comments@workspace:packages/comments" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6073,16 +6135,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-common@npm:38.0.6, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": +"@udecode/plate-common@npm:39.0.0, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": version: 0.0.0-use.local resolution: "@udecode/plate-common@workspace:packages/common" dependencies: - "@udecode/plate-core": "npm:38.0.6" - "@udecode/plate-utils": "npm:38.0.6" + "@udecode/plate-core": "npm:39.0.0" + "@udecode/plate-utils": "npm:39.0.0" "@udecode/react-hotkeys": "npm:37.0.0" - "@udecode/react-utils": "npm:38.0.1" + "@udecode/react-utils": "npm:39.0.0" "@udecode/slate": "npm:38.0.4" - "@udecode/slate-react": "npm:38.0.4" + "@udecode/slate-react": "npm:39.0.0" "@udecode/slate-utils": "npm:38.0.4" "@udecode/utils": "npm:37.0.0" peerDependencies: @@ -6095,14 +6157,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-core@npm:38.0.6, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core": +"@udecode/plate-core@npm:39.0.0, @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:38.0.1" + "@udecode/react-utils": "npm:39.0.0" "@udecode/slate": "npm:38.0.4" - "@udecode/slate-react": "npm:38.0.4" + "@udecode/slate-react": "npm:39.0.0" "@udecode/slate-utils": "npm:38.0.4" "@udecode/utils": "npm:37.0.0" clsx: "npm:^2.1.1" @@ -6126,16 +6188,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-csv@npm:38.0.8, @udecode/plate-csv@workspace:^, @udecode/plate-csv@workspace:packages/csv": +"@udecode/plate-csv@npm:39.0.1, @udecode/plate-csv@workspace:^, @udecode/plate-csv@workspace:packages/csv": version: 0.0.0-use.local resolution: "@udecode/plate-csv@workspace:packages/csv" dependencies: "@types/papaparse": "npm:^5.3.14" "@udecode/plate-common": "workspace:^" - "@udecode/plate-table": "npm:38.0.8" + "@udecode/plate-table": "npm:39.0.1" papaparse: "npm:^5.4.1" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6151,7 +6213,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6167,7 +6229,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.94.0" @@ -6177,7 +6239,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-diff@npm:38.0.0, @udecode/plate-diff@workspace:^, @udecode/plate-diff@workspace:packages/diff": +"@udecode/plate-diff@npm:39.0.0, @udecode/plate-diff@workspace:^, @udecode/plate-diff@workspace:packages/diff": version: 0.0.0-use.local resolution: "@udecode/plate-diff@workspace:packages/diff" dependencies: @@ -6185,7 +6247,7 @@ __metadata: diff-match-patch-ts: "npm:^0.6.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6203,7 +6265,7 @@ __metadata: lodash: "npm:^4.17.21" raf: "npm:^3.4.1" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dnd: ">=14.0.0" react-dnd-html5-backend: ">=14.0.0" @@ -6215,19 +6277,19 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-docx@npm:38.0.12, @udecode/plate-docx@workspace:^, @udecode/plate-docx@workspace:packages/docx": +"@udecode/plate-docx@npm:39.0.1, @udecode/plate-docx@workspace:^, @udecode/plate-docx@workspace:packages/docx": version: 0.0.0-use.local resolution: "@udecode/plate-docx@workspace:packages/docx" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-heading": "npm:38.0.12" - "@udecode/plate-indent": "npm:38.0.1" - "@udecode/plate-indent-list": "npm:38.0.10" - "@udecode/plate-media": "npm:38.0.6" - "@udecode/plate-table": "npm:38.0.8" + "@udecode/plate-heading": "npm:39.0.0" + "@udecode/plate-indent": "npm:39.0.0" + "@udecode/plate-indent-list": "npm:39.0.0" + "@udecode/plate-media": "npm:39.0.0" + "@udecode/plate-table": "npm:39.0.1" validator: "npm:^13.12.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6242,10 +6304,10 @@ __metadata: resolution: "@udecode/plate-emoji@workspace:packages/emoji" dependencies: "@emoji-mart/data": "npm:^1.2.1" - "@udecode/plate-combobox": "npm:38.0.1" + "@udecode/plate-combobox": "npm:39.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6262,7 +6324,7 @@ __metadata: "@excalidraw/excalidraw": "npm:0.16.4" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6272,13 +6334,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-find-replace@npm:38.0.0, @udecode/plate-find-replace@workspace:^, @udecode/plate-find-replace@workspace:packages/find-replace": +"@udecode/plate-find-replace@npm:39.0.0, @udecode/plate-find-replace@workspace:^, @udecode/plate-find-replace@workspace:packages/find-replace": version: 0.0.0-use.local resolution: "@udecode/plate-find-replace@workspace:packages/find-replace" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6288,7 +6350,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-floating@npm:38.0.1, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": +"@udecode/plate-floating@npm:39.0.0, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": version: 0.0.0-use.local resolution: "@udecode/plate-floating@workspace:packages/floating" dependencies: @@ -6296,7 +6358,7 @@ __metadata: "@floating-ui/react": "npm:^0.26.23" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6306,14 +6368,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-font@npm:38.0.1, @udecode/plate-font@workspace:^, @udecode/plate-font@workspace:packages/font": +"@udecode/plate-font@npm:39.0.0, @udecode/plate-font@workspace:^, @udecode/plate-font@workspace:packages/font": version: 0.0.0-use.local resolution: "@udecode/plate-font@workspace:packages/font" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6323,13 +6385,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-heading@npm:38.0.12, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": +"@udecode/plate-heading@npm:39.0.0, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": version: 0.0.0-use.local resolution: "@udecode/plate-heading@workspace:packages/heading" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6339,13 +6401,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-highlight@npm:38.0.1, @udecode/plate-highlight@workspace:^, @udecode/plate-highlight@workspace:packages/highlight": +"@udecode/plate-highlight@npm:39.0.0, @udecode/plate-highlight@workspace:^, @udecode/plate-highlight@workspace:packages/highlight": version: 0.0.0-use.local resolution: "@udecode/plate-highlight@workspace:packages/highlight" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6355,13 +6417,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-horizontal-rule@npm:38.0.1, @udecode/plate-horizontal-rule@workspace:^, @udecode/plate-horizontal-rule@workspace:packages/horizontal-rule": +"@udecode/plate-horizontal-rule@npm:39.0.0, @udecode/plate-horizontal-rule@workspace:^, @udecode/plate-horizontal-rule@workspace:packages/horizontal-rule": version: 0.0.0-use.local resolution: "@udecode/plate-horizontal-rule@workspace:packages/horizontal-rule" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6371,7 +6433,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-html@npm:38.0.1, @udecode/plate-html@workspace:^, @udecode/plate-html@workspace:packages/html": +"@udecode/plate-html@npm:39.0.0, @udecode/plate-html@workspace:^, @udecode/plate-html@workspace:packages/html": version: 0.0.0-use.local resolution: "@udecode/plate-html@workspace:packages/html" dependencies: @@ -6379,7 +6441,7 @@ __metadata: "@udecode/plate-common": "workspace:^" html-entities: "npm:^2.5.2" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6389,16 +6451,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-indent-list@npm:38.0.10, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list": +"@udecode/plate-indent-list@npm:39.0.0, @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: "@udecode/plate-common": "workspace:^" - "@udecode/plate-indent": "npm:38.0.1" - "@udecode/plate-list": "npm:38.0.1" + "@udecode/plate-indent": "npm:39.0.0" + "@udecode/plate-list": "npm:39.0.0" clsx: "npm:^2.1.1" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6408,13 +6470,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-indent@npm:38.0.1, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": +"@udecode/plate-indent@npm:39.0.0, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": version: 0.0.0-use.local resolution: "@udecode/plate-indent@workspace:packages/indent" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6431,7 +6493,7 @@ __metadata: "@udecode/plate-common": "workspace:^" juice: "npm:^8.1.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6441,13 +6503,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-kbd@npm:38.0.1, @udecode/plate-kbd@workspace:^, @udecode/plate-kbd@workspace:packages/kbd": +"@udecode/plate-kbd@npm:39.0.0, @udecode/plate-kbd@workspace:^, @udecode/plate-kbd@workspace:packages/kbd": version: 0.0.0-use.local resolution: "@udecode/plate-kbd@workspace:packages/kbd" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6457,13 +6519,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-layout@npm:38.0.1, @udecode/plate-layout@workspace:^, @udecode/plate-layout@workspace:packages/layout": +"@udecode/plate-layout@npm:39.0.0, @udecode/plate-layout@workspace:^, @udecode/plate-layout@workspace:packages/layout": version: 0.0.0-use.local resolution: "@udecode/plate-layout@workspace:packages/layout" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6473,13 +6535,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-line-height@npm:38.0.1, @udecode/plate-line-height@workspace:^, @udecode/plate-line-height@workspace:packages/line-height": +"@udecode/plate-line-height@npm:39.0.0, @udecode/plate-line-height@workspace:^, @udecode/plate-line-height@workspace:packages/line-height": version: 0.0.0-use.local resolution: "@udecode/plate-line-height@workspace:packages/line-height" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6489,15 +6551,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-link@npm:38.0.6, @udecode/plate-link@workspace:^, @udecode/plate-link@workspace:packages/link": +"@udecode/plate-link@npm:39.0.0, @udecode/plate-link@workspace:^, @udecode/plate-link@workspace:packages/link": version: 0.0.0-use.local resolution: "@udecode/plate-link@workspace:packages/link" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-floating": "npm:38.0.1" - "@udecode/plate-normalizers": "npm:38.0.1" + "@udecode/plate-floating": "npm:39.0.0" + "@udecode/plate-normalizers": "npm:39.0.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6507,15 +6569,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-list@npm:38.0.1, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": +"@udecode/plate-list@npm:39.0.0, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": version: 0.0.0-use.local resolution: "@udecode/plate-list@workspace:packages/list" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-reset-node": "npm:38.0.1" + "@udecode/plate-reset-node": "npm:39.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6525,7 +6587,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-markdown@npm:38.0.13, @udecode/plate-markdown@workspace:^, @udecode/plate-markdown@workspace:packages/markdown": +"@udecode/plate-markdown@npm:39.0.0, @udecode/plate-markdown@workspace:^, @udecode/plate-markdown@workspace:packages/markdown": version: 0.0.0-use.local resolution: "@udecode/plate-markdown@workspace:packages/markdown" dependencies: @@ -6534,7 +6596,7 @@ __metadata: remark-parse: "npm:^9.0.0" unified: "npm:^11.0.5" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6552,7 +6614,7 @@ __metadata: "@udecode/plate-common": "workspace:^" katex: "npm:0.16.11" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6562,14 +6624,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-media@npm:38.0.6, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": +"@udecode/plate-media@npm:39.0.0, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": version: 0.0.0-use.local resolution: "@udecode/plate-media@workspace:packages/media" dependencies: "@udecode/plate-common": "workspace:^" js-video-url-parser: "npm:^0.5.1" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6579,14 +6641,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-mention@npm:38.0.1, @udecode/plate-mention@workspace:^, @udecode/plate-mention@workspace:packages/mention": +"@udecode/plate-mention@npm:39.0.0, @udecode/plate-mention@workspace:^, @udecode/plate-mention@workspace:packages/mention": version: 0.0.0-use.local resolution: "@udecode/plate-mention@workspace:packages/mention" dependencies: - "@udecode/plate-combobox": "npm:38.0.1" + "@udecode/plate-combobox": "npm:39.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6596,14 +6658,32 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-node-id@npm:38.0.1, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": +"@udecode/plate-menu@npm:39.0.0, @udecode/plate-menu@workspace:packages/menu": + version: 0.0.0-use.local + resolution: "@udecode/plate-menu@workspace:packages/menu" + dependencies: + "@ariakit/react": "npm:0.4.7" + "@udecode/plate-common": "workspace:^" + match-sorter: "npm:6.3.4" + peerDependencies: + "@udecode/plate-common": ">=39.0.0" + react: ">=16.8.0" + react-dom: ">=16.8.0" + slate: ">=0.103.0" + slate-history: ">=0.93.0" + slate-hyperscript: ">=0.66.0" + slate-react: ">=0.108.0" + languageName: unknown + linkType: soft + +"@udecode/plate-node-id@npm:39.0.0, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": version: 0.0.0-use.local resolution: "@udecode/plate-node-id@workspace:packages/node-id" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6613,14 +6693,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-normalizers@npm:38.0.1, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": +"@udecode/plate-normalizers@npm:39.0.0, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": version: 0.0.0-use.local resolution: "@udecode/plate-normalizers@workspace:packages/normalizers" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6637,7 +6717,7 @@ __metadata: "@udecode/plate-common": "workspace:^" peerDependencies: "@playwright/test": ">=1.42.1" - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6647,13 +6727,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-reset-node@npm:38.0.1, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": +"@udecode/plate-reset-node@npm:39.0.0, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": version: 0.0.0-use.local resolution: "@udecode/plate-reset-node@workspace:packages/reset-node" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6663,13 +6743,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-resizable@npm:38.0.0, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": +"@udecode/plate-resizable@npm:39.0.0, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": version: 0.0.0-use.local resolution: "@udecode/plate-resizable@workspace:packages/resizable" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6679,13 +6759,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-select@npm:38.0.1, @udecode/plate-select@workspace:^, @udecode/plate-select@workspace:packages/select": +"@udecode/plate-select@npm:39.0.0, @udecode/plate-select@workspace:^, @udecode/plate-select@workspace:packages/select": version: 0.0.0-use.local resolution: "@udecode/plate-select@workspace:packages/select" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6695,14 +6775,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-selection@npm:38.0.11, @udecode/plate-selection@workspace:^, @udecode/plate-selection@workspace:packages/selection": +"@udecode/plate-selection@npm:39.0.0, @udecode/plate-selection@workspace:^, @udecode/plate-selection@workspace:packages/selection": version: 0.0.0-use.local resolution: "@udecode/plate-selection@workspace:packages/selection" dependencies: "@udecode/plate-common": "workspace:^" copy-to-clipboard: "npm:^3.3.3" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6712,14 +6792,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-slash-command@npm:38.0.1, @udecode/plate-slash-command@workspace:^, @udecode/plate-slash-command@workspace:packages/slash-command": +"@udecode/plate-slash-command@npm:39.0.0, @udecode/plate-slash-command@workspace:^, @udecode/plate-slash-command@workspace:packages/slash-command": version: 0.0.0-use.local resolution: "@udecode/plate-slash-command@workspace:packages/slash-command" dependencies: - "@udecode/plate-combobox": "npm:38.0.1" + "@udecode/plate-combobox": "npm:39.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6729,15 +6809,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-suggestion@npm:38.0.1, @udecode/plate-suggestion@workspace:^, @udecode/plate-suggestion@workspace:packages/suggestion": +"@udecode/plate-suggestion@npm:39.0.0, @udecode/plate-suggestion@workspace:^, @udecode/plate-suggestion@workspace:packages/suggestion": version: 0.0.0-use.local resolution: "@udecode/plate-suggestion@workspace:packages/suggestion" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-diff": "npm:38.0.0" + "@udecode/plate-diff": "npm:39.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6747,14 +6827,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-tabbable@npm:38.0.1, @udecode/plate-tabbable@workspace:^, @udecode/plate-tabbable@workspace:packages/tabbable": +"@udecode/plate-tabbable@npm:39.0.0, @udecode/plate-tabbable@workspace:^, @udecode/plate-tabbable@workspace:packages/tabbable": version: 0.0.0-use.local resolution: "@udecode/plate-tabbable@workspace:packages/tabbable" dependencies: "@udecode/plate-common": "workspace:^" tabbable: "npm:^6.2.0" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6764,15 +6844,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-table@npm:38.0.8, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": +"@udecode/plate-table@npm:39.0.1, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": version: 0.0.0-use.local resolution: "@udecode/plate-table@workspace:packages/table" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-resizable": "npm:38.0.0" + "@udecode/plate-resizable": "npm:39.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6790,16 +6870,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-toggle@npm:38.0.1, @udecode/plate-toggle@workspace:^, @udecode/plate-toggle@workspace:packages/toggle": +"@udecode/plate-toggle@npm:39.0.0, @udecode/plate-toggle@workspace:^, @udecode/plate-toggle@workspace:packages/toggle": version: 0.0.0-use.local resolution: "@udecode/plate-toggle@workspace:packages/toggle" dependencies: "@udecode/plate-common": "workspace:^" - "@udecode/plate-indent": "npm:38.0.1" - "@udecode/plate-node-id": "npm:38.0.1" + "@udecode/plate-indent": "npm:39.0.0" + "@udecode/plate-node-id": "npm:39.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6809,13 +6889,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-trailing-block@npm:38.0.1, @udecode/plate-trailing-block@workspace:^, @udecode/plate-trailing-block@workspace:packages/trailing-block": +"@udecode/plate-trailing-block@npm:39.0.0, @udecode/plate-trailing-block@workspace:^, @udecode/plate-trailing-block@workspace:packages/trailing-block": version: 0.0.0-use.local resolution: "@udecode/plate-trailing-block@workspace:packages/trailing-block" dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6856,14 +6936,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-utils@npm:38.0.6, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils": +"@udecode/plate-utils@npm:39.0.0, @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:38.0.6" - "@udecode/react-utils": "npm:38.0.1" + "@udecode/plate-core": "npm:39.0.0" + "@udecode/react-utils": "npm:39.0.0" "@udecode/slate": "npm:38.0.4" - "@udecode/slate-react": "npm:38.0.4" + "@udecode/slate-react": "npm:39.0.0" "@udecode/slate-utils": "npm:38.0.4" "@udecode/utils": "npm:37.0.0" clsx: "npm:^2.1.1" @@ -6887,7 +6967,7 @@ __metadata: "@udecode/plate-common": "workspace:^" yjs: "npm:^13.6.19" peerDependencies: - "@udecode/plate-common": ">=38.0.6" + "@udecode/plate-common": ">=39.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.103.0" @@ -6901,48 +6981,48 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate@workspace:packages/plate" dependencies: - "@udecode/plate-alignment": "npm:38.0.1" - "@udecode/plate-autoformat": "npm:38.0.1" - "@udecode/plate-basic-elements": "npm:38.0.12" - "@udecode/plate-basic-marks": "npm:38.0.1" - "@udecode/plate-block-quote": "npm:38.0.1" - "@udecode/plate-break": "npm:38.0.1" - "@udecode/plate-code-block": "npm:38.0.1" - "@udecode/plate-combobox": "npm:38.0.1" - "@udecode/plate-comments": "npm:38.0.1" - "@udecode/plate-common": "npm:38.0.6" - "@udecode/plate-csv": "npm:38.0.8" - "@udecode/plate-diff": "npm:38.0.0" - "@udecode/plate-docx": "npm:38.0.12" - "@udecode/plate-find-replace": "npm:38.0.0" - "@udecode/plate-floating": "npm:38.0.1" - "@udecode/plate-font": "npm:38.0.1" - "@udecode/plate-heading": "npm:38.0.12" - "@udecode/plate-highlight": "npm:38.0.1" - "@udecode/plate-horizontal-rule": "npm:38.0.1" - "@udecode/plate-html": "npm:38.0.1" - "@udecode/plate-indent": "npm:38.0.1" - "@udecode/plate-indent-list": "npm:38.0.10" - "@udecode/plate-kbd": "npm:38.0.1" - "@udecode/plate-layout": "npm:38.0.1" - "@udecode/plate-line-height": "npm:38.0.1" - "@udecode/plate-link": "npm:38.0.6" - "@udecode/plate-list": "npm:38.0.1" - "@udecode/plate-markdown": "npm:38.0.13" - "@udecode/plate-media": "npm:38.0.6" - "@udecode/plate-mention": "npm:38.0.1" - "@udecode/plate-node-id": "npm:38.0.1" - "@udecode/plate-normalizers": "npm:38.0.1" - "@udecode/plate-reset-node": "npm:38.0.1" - "@udecode/plate-resizable": "npm:38.0.0" - "@udecode/plate-select": "npm:38.0.1" - "@udecode/plate-selection": "npm:38.0.11" - "@udecode/plate-slash-command": "npm:38.0.1" - "@udecode/plate-suggestion": "npm:38.0.1" - "@udecode/plate-tabbable": "npm:38.0.1" - "@udecode/plate-table": "npm:38.0.8" - "@udecode/plate-toggle": "npm:38.0.1" - "@udecode/plate-trailing-block": "npm:38.0.1" + "@udecode/plate-alignment": "npm:39.0.0" + "@udecode/plate-autoformat": "npm:39.0.0" + "@udecode/plate-basic-elements": "npm:39.0.0" + "@udecode/plate-basic-marks": "npm:39.0.0" + "@udecode/plate-block-quote": "npm:39.0.0" + "@udecode/plate-break": "npm:39.0.0" + "@udecode/plate-code-block": "npm:39.0.0" + "@udecode/plate-combobox": "npm:39.0.0" + "@udecode/plate-comments": "npm:39.0.0" + "@udecode/plate-common": "npm:39.0.0" + "@udecode/plate-csv": "npm:39.0.1" + "@udecode/plate-diff": "npm:39.0.0" + "@udecode/plate-docx": "npm:39.0.1" + "@udecode/plate-find-replace": "npm:39.0.0" + "@udecode/plate-floating": "npm:39.0.0" + "@udecode/plate-font": "npm:39.0.0" + "@udecode/plate-heading": "npm:39.0.0" + "@udecode/plate-highlight": "npm:39.0.0" + "@udecode/plate-horizontal-rule": "npm:39.0.0" + "@udecode/plate-html": "npm:39.0.0" + "@udecode/plate-indent": "npm:39.0.0" + "@udecode/plate-indent-list": "npm:39.0.0" + "@udecode/plate-kbd": "npm:39.0.0" + "@udecode/plate-layout": "npm:39.0.0" + "@udecode/plate-line-height": "npm:39.0.0" + "@udecode/plate-link": "npm:39.0.0" + "@udecode/plate-list": "npm:39.0.0" + "@udecode/plate-markdown": "npm:39.0.0" + "@udecode/plate-media": "npm:39.0.0" + "@udecode/plate-mention": "npm:39.0.0" + "@udecode/plate-node-id": "npm:39.0.0" + "@udecode/plate-normalizers": "npm:39.0.0" + "@udecode/plate-reset-node": "npm:39.0.0" + "@udecode/plate-resizable": "npm:39.0.0" + "@udecode/plate-select": "npm:39.0.0" + "@udecode/plate-selection": "npm:39.0.0" + "@udecode/plate-slash-command": "npm:39.0.0" + "@udecode/plate-suggestion": "npm:39.0.0" + "@udecode/plate-tabbable": "npm:39.0.0" + "@udecode/plate-table": "npm:39.0.1" + "@udecode/plate-toggle": "npm:39.0.0" + "@udecode/plate-trailing-block": "npm:39.0.0" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -6962,7 +7042,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/react-utils@npm:38.0.1, @udecode/react-utils@workspace:^, @udecode/react-utils@workspace:packages/react-utils": +"@udecode/react-utils@npm:39.0.0, @udecode/react-utils@workspace:^, @udecode/react-utils@workspace:packages/react-utils": version: 0.0.0-use.local resolution: "@udecode/react-utils@workspace:packages/react-utils" dependencies: @@ -6975,11 +7055,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate-react@npm:38.0.4, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": +"@udecode/slate-react@npm:39.0.0, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": version: 0.0.0-use.local resolution: "@udecode/slate-react@workspace:packages/slate-react" dependencies: - "@udecode/react-utils": "npm:38.0.1" + "@udecode/react-utils": "npm:39.0.0" "@udecode/slate": "npm:38.0.4" "@udecode/utils": "npm:37.0.0" peerDependencies: @@ -14271,6 +14351,16 @@ __metadata: languageName: node linkType: hard +"match-sorter@npm:6.3.4": + version: 6.3.4 + resolution: "match-sorter@npm:6.3.4" + dependencies: + "@babel/runtime": "npm:^7.23.8" + remove-accents: "npm:0.5.0" + checksum: 10c0/35d2a6b6df003c677d9ec87ecd4683657638f5bce856f43f9cf90b03e357ed2f09813ebbac759defa7e7438706936dd34dc2bfe1a18771f7d2541f14d639b4ad + languageName: node + linkType: hard + "mdast-util-find-and-replace@npm:^3.0.0": version: 3.0.1 resolution: "mdast-util-find-and-replace@npm:3.0.1" @@ -18084,6 +18174,13 @@ __metadata: languageName: node linkType: hard +"remove-accents@npm:0.5.0": + version: 0.5.0 + resolution: "remove-accents@npm:0.5.0" + checksum: 10c0/a75321aa1b53d9abe82637115a492770bfe42bb38ed258be748bf6795871202bc8b4badff22013494a7029f5a241057ad8d3f72adf67884dbe15a9e37e87adc4 + languageName: node + linkType: hard + "repeat-string@npm:^1.6.1": version: 1.6.1 resolution: "repeat-string@npm:1.6.1"