From f44ea5a85d3860b0711ffe7b5980ce06e0773b54 Mon Sep 17 00:00:00 2001 From: Liboul Date: Sun, 28 Jan 2024 17:35:12 +0100 Subject: [PATCH 01/16] Add toggle package --- packages/toggle/.npmignore | 3 + packages/toggle/CHANGELOG.md | 5 + packages/toggle/README.md | 12 ++ packages/toggle/package.json | 63 +++++++++++ .../toggle/src/createTogglePlugin.spec.tsx | 1 + packages/toggle/src/createTogglePlugin.ts | 18 +++ packages/toggle/src/hooks/index.ts | 6 + packages/toggle/src/hooks/useToggleButton.ts | 38 +++++++ .../src/hooks/useToggleToolbarButton.ts | 42 +++++++ packages/toggle/src/index.ts | 11 ++ packages/toggle/src/injectToggleWrapper.tsx | 40 +++++++ .../src/queries/getEnclosingToggleIds.ts | 45 ++++++++ .../queries/getLastEntryEnclosedInToggle.ts | 19 ++++ packages/toggle/src/queries/index.ts | 7 ++ packages/toggle/src/queries/someToggle.ts | 12 ++ packages/toggle/src/store.ts | 37 ++++++ packages/toggle/src/transforms/index.ts | 5 + .../src/transforms/openFutureToggles.ts | 31 +++++ packages/toggle/src/types.ts | 15 +++ packages/toggle/src/withToggle.spec.tsx | 1 + packages/toggle/src/withToggle.ts | 107 ++++++++++++++++++ packages/toggle/tsconfig.json | 10 ++ 22 files changed, 528 insertions(+) create mode 100644 packages/toggle/.npmignore create mode 100644 packages/toggle/CHANGELOG.md create mode 100644 packages/toggle/README.md create mode 100644 packages/toggle/package.json create mode 100644 packages/toggle/src/createTogglePlugin.spec.tsx create mode 100644 packages/toggle/src/createTogglePlugin.ts create mode 100644 packages/toggle/src/hooks/index.ts create mode 100644 packages/toggle/src/hooks/useToggleButton.ts create mode 100644 packages/toggle/src/hooks/useToggleToolbarButton.ts create mode 100644 packages/toggle/src/index.ts create mode 100644 packages/toggle/src/injectToggleWrapper.tsx create mode 100644 packages/toggle/src/queries/getEnclosingToggleIds.ts create mode 100644 packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts create mode 100644 packages/toggle/src/queries/index.ts create mode 100644 packages/toggle/src/queries/someToggle.ts create mode 100644 packages/toggle/src/store.ts create mode 100644 packages/toggle/src/transforms/index.ts create mode 100644 packages/toggle/src/transforms/openFutureToggles.ts create mode 100644 packages/toggle/src/types.ts create mode 100644 packages/toggle/src/withToggle.spec.tsx create mode 100644 packages/toggle/src/withToggle.ts create mode 100644 packages/toggle/tsconfig.json diff --git a/packages/toggle/.npmignore b/packages/toggle/.npmignore new file mode 100644 index 0000000000..7d3b305b17 --- /dev/null +++ b/packages/toggle/.npmignore @@ -0,0 +1,3 @@ +__tests__ +__test-utils__ +__mocks__ diff --git a/packages/toggle/CHANGELOG.md b/packages/toggle/CHANGELOG.md new file mode 100644 index 0000000000..8599d1d5e6 --- /dev/null +++ b/packages/toggle/CHANGELOG.md @@ -0,0 +1,5 @@ +# @udecode/plate-indent-list + +## 30.1.2 + +TODO ANNOUNCEMENT \ No newline at end of file diff --git a/packages/toggle/README.md b/packages/toggle/README.md new file mode 100644 index 0000000000..63ec597be5 --- /dev/null +++ b/packages/toggle/README.md @@ -0,0 +1,12 @@ +# Plate toggle plugin + +This package implements the toggle plugin for Plate. +It's similar to the indent list plugin, in that it relies on the indent of siblings. + +## Documentation + +Check out [Toggle](https://platejs.org/docs/toggle). + +## License + +[MIT](../../LICENSE) diff --git a/packages/toggle/package.json b/packages/toggle/package.json new file mode 100644 index 0000000000..0b31b49f5a --- /dev/null +++ b/packages/toggle/package.json @@ -0,0 +1,63 @@ +{ + "name": "@udecode/plate-toggle", + "version": "30.1.2", + "description": "Toggle plugin for Plate", + "license": "MIT", + "homepage": "https://platejs.org", + "repository": { + "type": "git", + "url": "https://github.com/udecode/plate.git", + "directory": "packages/toggle" + }, + "bugs": { + "url": "https://github.com/udecode/plate/issues" + }, + "sideEffects": false, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "yarn p:build", + "build:watch": "yarn p:build:watch", + "brl": "yarn p:brl", + "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-common": "30.1.2", + "@udecode/plate-indent": "30.1.2", + "@udecode/plate-indent-list": "30.1.2", + "@udecode/plate-node-id": "30.1.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "slate": ">=0.94.0", + "slate-history": ">=0.93.0", + "slate-hyperscript": ">=0.66.0", + "slate-react": ">=0.99.0" + }, + "keywords": [ + "plate", + "plugin", + "slate" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/toggle/src/createTogglePlugin.spec.tsx b/packages/toggle/src/createTogglePlugin.spec.tsx new file mode 100644 index 0000000000..1cf23ab59a --- /dev/null +++ b/packages/toggle/src/createTogglePlugin.spec.tsx @@ -0,0 +1 @@ +// TODO TEST diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts new file mode 100644 index 0000000000..494cdb3a79 --- /dev/null +++ b/packages/toggle/src/createTogglePlugin.ts @@ -0,0 +1,18 @@ +import { createPluginFactory, PlateEditor, Value } from '@udecode/plate-common'; + +import { injectToggleWrapper } from './injectToggleWrapper'; +import { ELEMENT_TOGGLE, ToggleEditor, TogglePlugin } from './types'; +import { withToggle } from './withToggle'; + +export const createTogglePlugin = createPluginFactory< + TogglePlugin, + Value, + PlateEditor & ToggleEditor +>({ + key: ELEMENT_TOGGLE, + isElement: true, + inject: { + aboveComponent: injectToggleWrapper, + }, + withOverrides: withToggle, +}); diff --git a/packages/toggle/src/hooks/index.ts b/packages/toggle/src/hooks/index.ts new file mode 100644 index 0000000000..399c62af59 --- /dev/null +++ b/packages/toggle/src/hooks/index.ts @@ -0,0 +1,6 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './useToggleButton'; +export * from './useToggleToolbarButton'; diff --git a/packages/toggle/src/hooks/useToggleButton.ts b/packages/toggle/src/hooks/useToggleButton.ts new file mode 100644 index 0000000000..6b54e2c6a1 --- /dev/null +++ b/packages/toggle/src/hooks/useToggleButton.ts @@ -0,0 +1,38 @@ +import { PlateEditor, useEditorRef } from '@udecode/plate-common'; +import { Value } from '@udecode/slate'; + +import { toggleToggleId } from '../store'; +import { ToggleEditor } from '../types'; + +type ToggleButtonState = { + toggleId: string; + open: boolean; + openIds: Set; +}; + +const useToggleStore = () => + useEditorRef().toggleStore; + +export const useToggleButtonState = (toggleId: string): ToggleButtonState => { + const store = useToggleStore(); + const openIds = store.use.openIds(); + const open = openIds.has(toggleId); + return { + toggleId, + open, + openIds, + }; +}; + +export const useToggleButton = (state: ToggleButtonState) => { + const store = useToggleStore(); + return { + ...state, + buttonProps: { + onClick: (e: React.MouseEvent) => { + e.preventDefault(); + store.set.openIds(toggleToggleId(state)); + }, + }, + }; +}; diff --git a/packages/toggle/src/hooks/useToggleToolbarButton.ts b/packages/toggle/src/hooks/useToggleToolbarButton.ts new file mode 100644 index 0000000000..25db2dffc5 --- /dev/null +++ b/packages/toggle/src/hooks/useToggleToolbarButton.ts @@ -0,0 +1,42 @@ +import { + collapseSelection, + focusEditor, + PlateEditor, + toggleNodeType, + useEditorRef, + useEditorSelector, + Value, +} from '@udecode/plate-common'; + +import { someToggle } from '../queries/someToggle'; +import { openFutureToggles } from '../transforms'; +import { ELEMENT_TOGGLE, ToggleEditor } from '../types'; + +export const useToggleToolbarButtonState = () => { + const pressed = useEditorSelector((editor) => someToggle(editor), []); + + return { + pressed, + }; +}; + +export const useToggleToolbarButton = ({ + pressed, +}: ReturnType) => { + const editor = useEditorRef & ToggleEditor>(); + + return { + props: { + pressed, + onMouseDown: (e: React.MouseEvent) => { + e.preventDefault(); + }, + onClick: () => { + openFutureToggles(editor); + toggleNodeType(editor, { activeType: ELEMENT_TOGGLE }); + collapseSelection(editor); + focusEditor(editor); + }, + }, + }; +}; diff --git a/packages/toggle/src/index.ts b/packages/toggle/src/index.ts new file mode 100644 index 0000000000..65798c5cd2 --- /dev/null +++ b/packages/toggle/src/index.ts @@ -0,0 +1,11 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './createTogglePlugin'; +export * from './injectToggleWrapper'; +export * from './types'; +export * from './withToggle'; +export * from './hooks/index'; +export * from './queries/index'; +export * from './transforms/index'; diff --git a/packages/toggle/src/injectToggleWrapper.tsx b/packages/toggle/src/injectToggleWrapper.tsx new file mode 100644 index 0000000000..584d00e7a4 --- /dev/null +++ b/packages/toggle/src/injectToggleWrapper.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { findNodePath, InjectComponentReturnType } from '@udecode/plate-common'; +import { TIndentElement } from '@udecode/plate-indent'; + +import { getEnclosingToggleIds } from './queries'; +import { ToggleEditor } from './types'; + +export const injectToggleWrapper = (): InjectComponentReturnType => WithToggle; + +const WithToggle: InjectComponentReturnType = ({ + element, + children, + editor, +}) => { + const store = editor.toggleStore as ToggleEditor['toggleStore']; + const openIds = store.use.openIds(); + const path = findNodePath(editor, element); + if (!path) return children; + + // TODO Instead of relying on editor.children, use the element's siblings + const inToggleIds = getEnclosingToggleIds( + editor.children as TIndentElement[], + [element, path] + ); + + const isOpen = inToggleIds.every((id) => openIds.has(id)); + if (isOpen) return children; + + return ( +
+ {children} +
+ ); +}; diff --git a/packages/toggle/src/queries/getEnclosingToggleIds.ts b/packages/toggle/src/queries/getEnclosingToggleIds.ts new file mode 100644 index 0000000000..a38e184f02 --- /dev/null +++ b/packages/toggle/src/queries/getEnclosingToggleIds.ts @@ -0,0 +1,45 @@ +import { KEY_INDENT, TIndentElement } from '@udecode/plate-indent'; +import { KEY_LIST_STYLE_TYPE } from '@udecode/plate-indent-list'; +import { memoize } from 'lodash'; +import { NodeEntry } from 'slate'; + +import { ELEMENT_TOGGLE } from '../types'; + +export function getEnclosingToggleIds( + elements: TIndentElement[], + [_node, path]: NodeEntry +): string[] { + // TODO Type so that there is no need to cast as indent elements + return memoizedBuildToggleIndex(elements)[path[0]]; +} + +// Returns, for each child, the enclosing toggle ids +function buildToggleIndex(elements: TIndentElement[]): string[][] { + const result: string[][] = []; + let currentEnclosingToggles: [string, number][] = []; // [toggleId, indent][] + elements.forEach((element, index) => { + const elementIndent = element[KEY_INDENT] || 0; + // For some reason, indent lists have a min indent of 1, even though they are not indented + const elementIndentWithIndentListCorrection = + element[KEY_LIST_STYLE_TYPE] && element[KEY_INDENT] + ? elementIndent - 1 + : elementIndent; + + const enclosingToggles = currentEnclosingToggles.filter(([_, indent]) => { + return indent < elementIndentWithIndentListCorrection; + }); + currentEnclosingToggles = enclosingToggles; + result[index] = enclosingToggles.map(([toggleId]) => toggleId); + if (element.type === ELEMENT_TOGGLE) { + // TODO Use the identifier provided as option instead of the default identifier of the node-id plugin, and type accordingly + currentEnclosingToggles.push([ + element.id as string, + element[KEY_INDENT] || 0, + ]); + } + }); + + return result; +} + +const memoizedBuildToggleIndex = memoize(buildToggleIndex); diff --git a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts new file mode 100644 index 0000000000..62d9c1efab --- /dev/null +++ b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts @@ -0,0 +1,19 @@ +import { PlateEditor, TNodeEntry } from '@udecode/plate-common'; +import { last } from 'lodash'; + +import { getEnclosingToggleIds } from './getEnclosingToggleIds'; + +export const getLastEntryEnclosedInToggle = ( + editor: PlateEditor, + toggleId: string +): TNodeEntry | undefined => { + const entriesInToggle = editor.children + .map((node, index) => [node, [index]] as TNodeEntry) + .filter(([node, path]) => { + // @ts-ignore TODO Instead of relying on editor.children, use the element's siblings + return getEnclosingToggleIds(editor.children, [node, path]).includes( + toggleId + ); + }); + return last(entriesInToggle); +}; diff --git a/packages/toggle/src/queries/index.ts b/packages/toggle/src/queries/index.ts new file mode 100644 index 0000000000..f33fd496cd --- /dev/null +++ b/packages/toggle/src/queries/index.ts @@ -0,0 +1,7 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './getEnclosingToggleIds'; +export * from './getLastEntryEnclosedInToggle'; +export * from './someToggle'; diff --git a/packages/toggle/src/queries/someToggle.ts b/packages/toggle/src/queries/someToggle.ts new file mode 100644 index 0000000000..2b14d02af9 --- /dev/null +++ b/packages/toggle/src/queries/someToggle.ts @@ -0,0 +1,12 @@ +import { PlateEditor, someNode, Value } from '@udecode/plate-common'; + +import { ELEMENT_TOGGLE } from '../types'; + +export const someToggle = (editor: PlateEditor) => { + return ( + !!editor.selection && + someNode(editor, { + match: (n) => n.type === ELEMENT_TOGGLE, + }) + ); +}; diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts new file mode 100644 index 0000000000..aa4da1d7b5 --- /dev/null +++ b/packages/toggle/src/store.ts @@ -0,0 +1,37 @@ +import { createZustandStore } from '@udecode/plate-common'; + +import { ELEMENT_TOGGLE } from './types'; + +export const createToggleStore = () => { + return createZustandStore(ELEMENT_TOGGLE)({ + openIds: new Set() as Set, + }); +}; + +export type ToggleStore = ReturnType; + +export const someToggleClosed = ( + store: ToggleStore, + toggleIds: string[] +): boolean => { + const openIds = store.get.openIds(); + return toggleIds.some((id) => !openIds.has(id)); +}; + +export const triggerStoreUpdate = (store: ToggleStore) => { + store.set.openIds(new Set(store.get.openIds().values())); +}; + +export const toggleToggleId = (state: { + toggleId: string; + open: boolean; // current open state + openIds: Set; +}): Set => { + const newOpenIds = new Set(state.openIds.values()); + if (state.open) { + newOpenIds.delete(state.toggleId); + } else { + newOpenIds.add(state.toggleId); + } + return newOpenIds; +}; diff --git a/packages/toggle/src/transforms/index.ts b/packages/toggle/src/transforms/index.ts new file mode 100644 index 0000000000..7d7afdab77 --- /dev/null +++ b/packages/toggle/src/transforms/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './openFutureToggles'; diff --git a/packages/toggle/src/transforms/openFutureToggles.ts b/packages/toggle/src/transforms/openFutureToggles.ts new file mode 100644 index 0000000000..79ccf7350a --- /dev/null +++ b/packages/toggle/src/transforms/openFutureToggles.ts @@ -0,0 +1,31 @@ +import { + getNodeEntries, + PlateEditor, + withoutNormalizing, +} from '@udecode/plate-common'; + +import { toggleToggleId } from '../store'; +import { ToggleEditor } from '../types'; + +// When creating a toggle, we open it by default. +// So before inserting the toggle, we update the store to mark the id of the blocks about to be turned into toggles as open. +export const openFutureToggles = (editor: PlateEditor & ToggleEditor) => { + const nodeEntries = Array.from( + getNodeEntries(editor, { + block: true, + mode: 'lowest', + }) + ); + + withoutNormalizing(editor, () => { + nodeEntries.forEach(([node]) => { + editor.toggleStore.set.openIds( + toggleToggleId({ + openIds: editor.toggleStore.get.openIds(), + open: false, + toggleId: node.id as string, + }) + ); + }); + }); +}; diff --git a/packages/toggle/src/types.ts b/packages/toggle/src/types.ts new file mode 100644 index 0000000000..1467510ab1 --- /dev/null +++ b/packages/toggle/src/types.ts @@ -0,0 +1,15 @@ +import { ToggleStore } from './store'; + +export interface TogglePlugin { + // Options would go here +} + +export const ELEMENT_TOGGLE = 'toggle'; + +export type ToggleEditor = { + toggleStore: ToggleStore; +}; + +export type TToggleElement = { + type: typeof ELEMENT_TOGGLE; +}; diff --git a/packages/toggle/src/withToggle.spec.tsx b/packages/toggle/src/withToggle.spec.tsx new file mode 100644 index 0000000000..1cf23ab59a --- /dev/null +++ b/packages/toggle/src/withToggle.spec.tsx @@ -0,0 +1 @@ +// TODO TEST diff --git a/packages/toggle/src/withToggle.ts b/packages/toggle/src/withToggle.ts new file mode 100644 index 0000000000..de6f78b8f8 --- /dev/null +++ b/packages/toggle/src/withToggle.ts @@ -0,0 +1,107 @@ +import { + findNodePath, + getBlockAbove, + isNode, + moveNodes, + PlateEditor, + toggleNodeType, + Value, +} from '@udecode/plate-common'; +import { indent, TIndentElement } from '@udecode/plate-indent'; + +import { getEnclosingToggleIds, getLastEntryEnclosedInToggle } from './queries'; +import { + createToggleStore, + someToggleClosed, + triggerStoreUpdate, +} from './store'; +import { ELEMENT_TOGGLE, ToggleEditor } from './types'; + +export const withToggle = < + V extends Value = Value, + E extends PlateEditor & ToggleEditor = PlateEditor & ToggleEditor, +>( + editor: E + // { options }: WithPlatePlugin +) => { + editor.toggleStore = createToggleStore(); + const { insertBreak, isSelectable, apply } = editor; + + editor.isSelectable = (element) => { + if (!isNode(element)) return isSelectable(element); + const path = findNodePath(editor, element); + if (!path) return isSelectable(element); + // @ts-ignore TODO Instead of relying on editor.children, use the element's siblings + const enclosingToggleIds = getEnclosingToggleIds(editor.children, [ + element, + path, + ]); + if (someToggleClosed(editor.toggleStore, enclosingToggleIds)) { + return false; + } + + return isSelectable(element); + }; + + editor.insertBreak = () => { + // If we are inserting a break in a toggle: + // If the toggle is open + // - Add a new paragraph right after the toggle + // - Focus on that paragraph + // If the the toggle is closed: + // - Add a new paragraph after the last sibling enclosed in the toggle + // - Focus on that paragraph + // Note: We are relying on the default behaviour of `insertBreak` which inserts a toggle right after the current toggle with the same indent + const currentBlockEntry = getBlockAbove(editor); + if (!currentBlockEntry || currentBlockEntry[0].type !== ELEMENT_TOGGLE) { + return insertBreak(); + } + + const toggleId = currentBlockEntry[0].id as string; + const openIds = editor.toggleStore.get.openIds(); + const isOpen = openIds.has(toggleId); + + editor.withoutNormalizing(() => { + if (isOpen) { + insertBreak(); + toggleNodeType(editor, { activeType: ELEMENT_TOGGLE }); + indent(editor); + } else { + const lastEntryEnclosedInToggle = getLastEntryEnclosedInToggle( + // TODO typing: There should be no need for casting + editor as PlateEditor, + toggleId + ); + + insertBreak(); + + if (lastEntryEnclosedInToggle) { + moveNodes(editor, { + at: [currentBlockEntry[1][0] + 1], // Newly inserted toggle + to: [lastEntryEnclosedInToggle[1][0] + 1], // Right after the last enclosed element + }); + } + } + }); + }; + + editor.apply = (operation) => { + // MNake sure all elements' visibility is re-calculated + apply(operation); + + // TODO Be more restrictive to improve performance, for instance by restricting operations on toggles + if ( + [ + 'merge_node', + 'move_node', + 'set_node', + 'insert_node', + 'remove_node', + ].includes(operation.type) + ) { + triggerStoreUpdate(editor.toggleStore); + } + }; + + return editor; +}; diff --git a/packages/toggle/tsconfig.json b/packages/toggle/tsconfig.json new file mode 100644 index 0000000000..0db2a27277 --- /dev/null +++ b/packages/toggle/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/tsconfig.build.json", + "compilerOptions": { + "declarationDir": "./dist", + "outDir": "./dist" + }, + "include": [ + "src" + ] +} From d066b58eb6a38661f388585e893fb27aee4fc0f3 Mon Sep 17 00:00:00 2001 From: Liboul Date: Sun, 28 Jan 2024 17:55:06 +0100 Subject: [PATCH 02/16] Document Toggle plugin --- .../docs/components/toggle-element.mdx | 63 ++++++++++ .../docs/components/toggle-toolbar-button.mdx | 86 ++++++++++++++ apps/www/content/docs/toggle.mdx | 110 ++++++++++++++++++ apps/www/package.json | 1 + apps/www/public/registry/index.json | 24 ++++ .../styles/default/toggle-toolbar-button.json | 16 +++ apps/www/src/__registry__/index.tsx | 14 +++ .../playground-fixed-toolbar-buttons.tsx | 3 + apps/www/src/components/settings-combobox.tsx | 1 + apps/www/src/config/customizer-components.ts | 8 ++ apps/www/src/config/customizer-items.ts | 18 +++ apps/www/src/config/customizer-list.ts | 2 + apps/www/src/config/customizer-plugins.ts | 9 ++ apps/www/src/config/descriptions.ts | 2 + apps/www/src/config/doc-to-package.ts | 1 + apps/www/src/config/docs.ts | 7 ++ apps/www/src/lib/plate/create-plate-ui.ts | 3 + .../plate/demo/plugins/autoformatBlocks.ts | 7 ++ .../lib/plate/demo/plugins/indentPlugin.ts | 2 + .../src/lib/plate/demo/values/toggleValue.tsx | 29 +++++ .../plate/demo/values/usePlaygroundValue.ts | 3 + .../default/example/cards/cards-toolbar.tsx | 3 + .../default/example/playground-demo.tsx | 2 + .../default/plate-ui/toggle-element.tsx | 43 +++++++ .../plate-ui/toggle-toolbar-button.tsx | 23 ++++ apps/www/src/registry/registry.ts | 14 +++ apps/www/src/types/plate-types.ts | 12 ++ config/aliases.js | 1 + .../withAutoformat/block/list.spec.tsx | 2 + packages/plate/package.json | 1 + .../plate/src/__tests__/all-plugins.spec.tsx | 1 + packages/plate/src/index.tsx | 1 + packages/serializer-html/src/serializeHtml.ts | 1 + packages/test-utils/src/jsx.ts | 2 + .../plate-playground-template/package.json | 1 + .../plate-ui/fixed-toolbar-buttons.tsx | 3 + .../components/plate-ui/toggle-element.tsx | 43 +++++++ .../plate-ui/toggle-toolbar-button.tsx | 23 ++++ .../src/lib/plate/autoformatBlocks.ts | 7 ++ .../src/lib/plate/plate-plugins.ts | 5 + .../src/lib/plate/plate-types.ts | 12 ++ 41 files changed, 609 insertions(+) create mode 100644 apps/www/content/docs/components/toggle-element.mdx create mode 100644 apps/www/content/docs/components/toggle-toolbar-button.mdx create mode 100644 apps/www/content/docs/toggle.mdx create mode 100644 apps/www/public/registry/styles/default/toggle-toolbar-button.json create mode 100644 apps/www/src/lib/plate/demo/values/toggleValue.tsx create mode 100644 apps/www/src/registry/default/plate-ui/toggle-element.tsx create mode 100644 apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx create mode 100644 templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx create mode 100644 templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx diff --git a/apps/www/content/docs/components/toggle-element.mdx b/apps/www/content/docs/components/toggle-element.mdx new file mode 100644 index 0000000000..4ace3fa55c --- /dev/null +++ b/apps/www/content/docs/components/toggle-element.mdx @@ -0,0 +1,63 @@ +--- +title: Toggle Element +description: Turn any block into a toggle. +component: true +docs: + - route: /docs/toggle + title: Toggle +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add toggle-element +``` + + + + + + + + + +Install the following dependencies: + +- [Toggle](/docs/toggle) +- [Toolbar](/docs/components/toolbar) + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + diff --git a/apps/www/content/docs/components/toggle-toolbar-button.mdx b/apps/www/content/docs/components/toggle-toolbar-button.mdx new file mode 100644 index 0000000000..c252fa5cf8 --- /dev/null +++ b/apps/www/content/docs/components/toggle-toolbar-button.mdx @@ -0,0 +1,86 @@ +--- +title: Toggle Toolbar Button +description: Turns blocks into toggles and vice-versa. +component: true +docs: + - route: /docs/toggle + title: Toggle + - route: /docs/components/toolbar + title: Toolbar +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add toggle-toolbar-button +``` + + + + + + + + + +Install the following dependencies: + +- [Toggle](/docs/toggle) +- [Toolbar](/docs/components/toolbar) + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + + + +Manual Installation + + +1. Install the following dependencies: + +- [Toggle](/docs/toggle) +- [Toolbar](/docs/components/toolbar) + +2. Copy and paste the following code into your project. + +- `components/plate-ui/toggle-toolbar-button.tsx` + + + + + + + + +## Examples + + + + diff --git a/apps/www/content/docs/toggle.mdx b/apps/www/content/docs/toggle.mdx new file mode 100644 index 0000000000..02e00a2f60 --- /dev/null +++ b/apps/www/content/docs/toggle.mdx @@ -0,0 +1,110 @@ +--- +title: Toggle +description: Turn any block into a toggle. +docs: + - route: /docs/components/toggle-toolbar-button + title: Toggle Button +--- + + + + + +## Features + +- Turn any bock into a toggle: + - Toggles are closed by default, and can be opened to reveal the content inside +- Refer to the [Indent documentation](/docs/indent) for more information. + +## Plugin dependencies + +- [Indent](/docs/indent) +- [Indent List](/docs/indent-list) +- [Node id](/docs/node-id) + + + +## Installation + +```bash +npm install @udecode/plate-indent @udecode/plate-indent-list @udecode/plate-node-id @udecode/toggle +``` + +## Usage + +```tsx +// ... +import { createIndentPlugin } from '@udecode/plate-indent'; +import { createIndentListPlugin } from '@udecode/plate-indent-list'; +import { createNodeIdPlugin } from '@udecode/plate-node-id'; +import { createTogglePlugin } from '@udecode/plate-toggle'; + +const plugins = [ + // ...otherPlugins, + createHeadingPlugin(), + createParagraphPlugin(), + createIndentPlugin({ + inject: { + props: { + validTypes: [ELEMENT_PARAGRAPH, ELEMENT_H1], + }, + }, + }), + createIndentListPlugin({ + inject: { + props: { + validTypes: [ELEMENT_PARAGRAPH, ELEMENT_H1], + }, + }, + }), + createNodeIdPlugin(), + createTogglePlugin(), +]; +``` + +## API + +### createTogglePlugin + + + {/* TODO */} + + +### openFutureToggles + + + + The editor instance. + + + +{/* TODO Add API DOC */} + +## API Components + +### useToggleToolbarButton + +A behavior hook for the toggle toolbar button. + + + + A boolean indicating whether the button is pressed or not. + + + + + + + + A boolean indicating whether the button is pressed or not. + + + A callback function to handle the click event of the button. It toggles + the toggle of the specified node type in the editor and focuses the + editor. + + + + + +{/* TODO Add components DOC */} \ No newline at end of file diff --git a/apps/www/package.json b/apps/www/package.json index dc7798efd5..4326a6f80c 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -97,6 +97,7 @@ "@udecode/plate-tabbable": "workspace:^", "@udecode/plate-table": "workspace:^", "@udecode/plate-test-utils": "workspace:^", + "@udecode/plate-toggle": "workspace:^", "@udecode/plate-trailing-block": "workspace:^", "@udecode/plate-utils": "workspace:^", "@udecode/react-utils": "workspace:^", diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index c4f80f6ee2..51a185306b 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -817,6 +817,30 @@ ], "type": "components:plate-ui" }, + { + "name": "toggle-element", + "dependencies": [ + "@udecode/plate-toggle" + ], + "registryDependencies": [], + "files": [ + "plate-ui/toggle-element.tsx" + ], + "type": "components:plate-ui" + }, + { + "name": "toggle-toolbar-button", + "dependencies": [ + "@udecode/plate-toggle" + ], + "registryDependencies": [ + "toolbar" + ], + "files": [ + "plate-ui/toggle-toolbar-button.tsx" + ], + "type": "components:plate-ui" + }, { "name": "toolbar", "dependencies": [ diff --git a/apps/www/public/registry/styles/default/toggle-toolbar-button.json b/apps/www/public/registry/styles/default/toggle-toolbar-button.json new file mode 100644 index 0000000000..61bc920d73 --- /dev/null +++ b/apps/www/public/registry/styles/default/toggle-toolbar-button.json @@ -0,0 +1,16 @@ +{ + "name": "toggle-toolbar-button", + "dependencies": [ + "@udecode/plate-toggle" + ], + "registryDependencies": [ + "toolbar" + ], + "files": [ + { + "name": "toggle-toolbar-button.tsx", + "content": "import React from 'react';\nimport { withRef } from '@udecode/cn';\nimport {\n useToggleToolbarButton,\n useToggleToolbarButtonState,\n} from '@udecode/plate-toggle';\n\nimport { Icons } from '@/components/icons';\n\nimport { ToolbarButton } from './toolbar';\n\nexport const ToggleToolbarButton = withRef((rest, ref) => {\n const state = useToggleToolbarButtonState();\n const { props } = useToggleToolbarButton(state);\n\n return (\n \n \n \n );\n});\n" + } + ], + "type": "components:plate-ui" +} \ No newline at end of file diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx index 5a5b9d9086..28074e0074 100644 --- a/apps/www/src/__registry__/index.tsx +++ b/apps/www/src/__registry__/index.tsx @@ -641,6 +641,20 @@ export const Index: Record = { files: ['registry/default/plate-ui/todo-list-element.tsx'], component: React.lazy(() => import('@/registry/default/plate-ui/todo-list-element')), }, + 'toggle-element': { + name: 'toggle-element', + type: 'components:plate-ui', + registryDependencies: [], + files: ['registry/default/plate-ui/toggle-element.tsx'], + component: React.lazy(() => import('@/registry/default/plate-ui/toggle-element')), + }, + 'toggle-toolbar-button': { + name: 'toggle-toolbar-button', + type: 'components:plate-ui', + registryDependencies: ["toolbar"], + files: ['registry/default/plate-ui/toggle-toolbar-button.tsx'], + component: React.lazy(() => import('@/registry/default/plate-ui/toggle-toolbar-button')), + }, 'toolbar': { name: 'toolbar', type: 'components:plate-ui', diff --git a/apps/www/src/components/plate-ui/playground-fixed-toolbar-buttons.tsx b/apps/www/src/components/plate-ui/playground-fixed-toolbar-buttons.tsx index dc6e17cfb1..8cfd537dbb 100644 --- a/apps/www/src/components/plate-ui/playground-fixed-toolbar-buttons.tsx +++ b/apps/www/src/components/plate-ui/playground-fixed-toolbar-buttons.tsx @@ -29,6 +29,7 @@ import { MarkToolbarButton } from '@/registry/default/plate-ui/mark-toolbar-butt import { MediaToolbarButton } from '@/registry/default/plate-ui/media-toolbar-button'; import { OutdentToolbarButton } from '@/registry/default/plate-ui/outdent-toolbar-button'; import { TableDropdownMenu } from '@/registry/default/plate-ui/table-dropdown-menu'; +import { ToggleToolbarButton } from '@/registry/default/plate-ui/toggle-toolbar-button'; import { ToolbarGroup } from '@/registry/default/plate-ui/toolbar'; import { PlaygroundInsertDropdownMenu } from './playground-insert-dropdown-menu'; @@ -137,6 +138,8 @@ export function PlaygroundFixedToolbarButtons({ id }: { id?: ValueId }) { {isEnabled('link', id) && } + {isEnabled('toggle', id) && } + {isEnabled('media', id) && ( )} diff --git a/apps/www/src/components/settings-combobox.tsx b/apps/www/src/components/settings-combobox.tsx index 9a3397ef13..6c369dbe9c 100644 --- a/apps/www/src/components/settings-combobox.tsx +++ b/apps/www/src/components/settings-combobox.tsx @@ -65,6 +65,7 @@ const categories = [ customizerPlugins.tabbable, customizerPlugins.table, customizerPlugins.todoli, + customizerPlugins.toggle, customizerPlugins.trailingblock, ], }, diff --git a/apps/www/src/config/customizer-components.ts b/apps/www/src/config/customizer-components.ts index 6c5bdc6c78..650bc961aa 100644 --- a/apps/www/src/config/customizer-components.ts +++ b/apps/www/src/config/customizer-components.ts @@ -240,6 +240,14 @@ export const customizerComponents = { href: '/docs/components/todo-list-element', label: 'Element', }, + toggleElement: { + title: 'Toggle', + href: '/docs/components/toggle', + }, + toggleToolbarButton: { + title: 'Toggle Toolbar Button', + href: '/docs/components/toggle-toolbar-button', + }, toolbar: { title: 'Toolbar', href: '/docs/components/toolbar' }, tooltip: { title: 'Tooltip', href: '/docs/components/tooltip' }, turnIntoDropdownMenu: { diff --git a/apps/www/src/config/customizer-items.ts b/apps/www/src/config/customizer-items.ts index 2794de203d..ce10b3ff19 100644 --- a/apps/www/src/config/customizer-items.ts +++ b/apps/www/src/config/customizer-items.ts @@ -46,6 +46,7 @@ import { KEY_DESERIALIZE_DOCX } from '@udecode/plate-serializer-docx'; import { KEY_DESERIALIZE_MD } from '@udecode/plate-serializer-md'; import { KEY_TABBABLE } from '@udecode/plate-tabbable'; import { ELEMENT_TABLE } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { KEY_TRAILING_BLOCK } from '@udecode/plate-trailing-block'; import { CustomizerBadge, customizerBadges } from '@/config/customizer-badges'; @@ -255,6 +256,23 @@ export const customizerItems: Record = { }, ], }, + [ELEMENT_TOGGLE]: { + id: ELEMENT_TOGGLE, + npmPackage: '@udecode/plate-toggle', + pluginFactory: 'createTogglePlugin', + label: 'Toggle', + badges: [customizerBadges.element], + route: customizerPlugins.toggle.route, + components: [ + { + id: 'toggle-element', + label: 'ToggleElement', + pluginKey: 'ELEMENT_TOGGLE', + usage: 'ToggleElement', + route: customizerComponents.toggleElement.href, + }, + ], + }, heading: { id: 'heading', npmPackage: '@udecode/plate-heading', diff --git a/apps/www/src/config/customizer-list.ts b/apps/www/src/config/customizer-list.ts index d046aa42d4..36202b0d31 100644 --- a/apps/www/src/config/customizer-list.ts +++ b/apps/www/src/config/customizer-list.ts @@ -46,6 +46,7 @@ import { KEY_DESERIALIZE_DOCX } from '@udecode/plate-serializer-docx'; import { KEY_DESERIALIZE_MD } from '@udecode/plate-serializer-md'; import { KEY_TABBABLE } from '@udecode/plate-tabbable'; import { ELEMENT_TABLE } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { KEY_TRAILING_BLOCK } from '@udecode/plate-trailing-block'; import { uniqBy } from 'lodash'; @@ -62,6 +63,7 @@ export const customizerList = [ customizerItems[ELEMENT_HR], customizerItems[ELEMENT_IMAGE], customizerItems[ELEMENT_LINK], + customizerItems[ELEMENT_TOGGLE], customizerItems.heading, customizerItems.list, customizerItems[ELEMENT_MEDIA_EMBED], diff --git a/apps/www/src/config/customizer-plugins.ts b/apps/www/src/config/customizer-plugins.ts index c3743ee09b..f4bb10b592 100644 --- a/apps/www/src/config/customizer-plugins.ts +++ b/apps/www/src/config/customizer-plugins.ts @@ -19,6 +19,7 @@ import { fontValue } from '@/plate/demo/values/fontValue'; import { highlightValue } from '@/plate/demo/values/highlightValue'; import { horizontalRuleValue } from '@/plate/demo/values/horizontalRuleValue'; import { indentListValue } from '@/plate/demo/values/indentListValue'; +import { toggleValue } from '@/plate/demo/values/toggleValue'; import { indentValue } from '@/plate/demo/values/indentValue'; import { kbdValue } from '@/plate/demo/values/kbdValue'; import { lineHeightValue } from '@/plate/demo/values/lineHeightValue'; @@ -64,6 +65,7 @@ import { KEY_DESERIALIZE_DOCX } from '@udecode/plate-serializer-docx'; import { KEY_DESERIALIZE_MD } from '@udecode/plate-serializer-md'; import { KEY_TABBABLE } from '@udecode/plate-tabbable'; import { ELEMENT_TABLE } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { KEY_TRAILING_BLOCK } from '@udecode/plate-trailing-block'; export type ValueId = keyof typeof customizerPlugins | 'tableMerge'; @@ -331,6 +333,13 @@ export const customizerPlugins = { route: '/docs/todo-list', plugins: [ELEMENT_TODO_LI], }, + toggle: { + id: 'toggle', + label: 'Toggle', + value: toggleValue, + route: '/docs/toggle', + plugins: [ELEMENT_TOGGLE], + }, trailingblock: { id: 'trailingblock', label: 'Trailing Block', diff --git a/apps/www/src/config/descriptions.ts b/apps/www/src/config/descriptions.ts index 44fc4ea073..6852379bc8 100644 --- a/apps/www/src/config/descriptions.ts +++ b/apps/www/src/config/descriptions.ts @@ -44,6 +44,7 @@ import { KEY_DESERIALIZE_DOCX } from '@udecode/plate-serializer-docx'; import { KEY_DESERIALIZE_MD } from '@udecode/plate-serializer-md'; import { KEY_TABBABLE } from '@udecode/plate-tabbable'; import { ELEMENT_TABLE } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { KEY_TRAILING_BLOCK } from '@udecode/plate-trailing-block'; export const descriptions: Record = { @@ -54,6 +55,7 @@ export const descriptions: Record = { [ELEMENT_HR]: 'Insert horizontal lines.', [ELEMENT_IMAGE]: 'Embed images into your document.', [ELEMENT_LINK]: 'Insert and manage hyperlinks.', + [ELEMENT_TOGGLE]: 'Turn any block into a toggle.', heading: 'Organize your document with up to 6 headings.', list: 'Organize nestable items in a bulleted or numbered list.', [ELEMENT_MEDIA_EMBED]: diff --git a/apps/www/src/config/doc-to-package.ts b/apps/www/src/config/doc-to-package.ts index 9cd3e9e2fc..054b65823f 100644 --- a/apps/www/src/config/doc-to-package.ts +++ b/apps/www/src/config/doc-to-package.ts @@ -35,6 +35,7 @@ const plateOverrides = { 'soft-break': 'plate-break', tabbable: 'plate-tabbable', table: 'plate-table', + toggle: 'plate-toggle', }; export const docToPackage = (name?: string) => { diff --git a/apps/www/src/config/docs.ts b/apps/www/src/config/docs.ts index 85763eb9cc..9a54b85851 100644 --- a/apps/www/src/config/docs.ts +++ b/apps/www/src/config/docs.ts @@ -228,6 +228,11 @@ export const docsConfig: DocsConfig = { href: '/docs/table', label: 'Element', }, + { + title: 'Toggle', + href: '/docs/toggle', + label: 'Element', + }, ], }, { @@ -773,6 +778,8 @@ export const docsConfig: DocsConfig = { customizerComponents.tableElement, customizerComponents.tableRowElement, customizerComponents.todoListElement, + customizerComponents.toggleElement, + customizerComponents.toggleToolbarButton, customizerComponents.toolbar, customizerComponents.tooltip, customizerComponents.turnIntoDropdownMenu, diff --git a/apps/www/src/lib/plate/create-plate-ui.ts b/apps/www/src/lib/plate/create-plate-ui.ts index 4c5c72552b..400c1b1a55 100644 --- a/apps/www/src/lib/plate/create-plate-ui.ts +++ b/apps/www/src/lib/plate/create-plate-ui.ts @@ -49,6 +49,7 @@ import { ELEMENT_TH, ELEMENT_TR, } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { BlockquoteElement } from '@/registry/default/plate-ui/blockquote-element'; import { CodeBlockElement } from '@/registry/default/plate-ui/code-block-element'; @@ -77,6 +78,7 @@ import { import { TableElement } from '@/registry/default/plate-ui/table-element'; import { TableRowElement } from '@/registry/default/plate-ui/table-row-element'; import { TodoListElement } from '@/registry/default/plate-ui/todo-list-element'; +import { ToggleElement } from '@/registry/default/plate-ui/toggle-element'; import { withDraggables } from '@/registry/default/plate-ui/with-draggables'; export const createPlateUI = ( @@ -111,6 +113,7 @@ export const createPlateUI = ( [ELEMENT_TD]: TableCellElement, [ELEMENT_TH]: TableCellHeaderElement, [ELEMENT_TODO_LI]: TodoListElement, + [ELEMENT_TOGGLE]: ToggleElement, [ELEMENT_TR]: TableRowElement, [ELEMENT_EXCALIDRAW]: ExcalidrawElement, [MARK_BOLD]: withProps(PlateLeaf, { as: 'strong' }), diff --git a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts index 1b5da1dcf8..9abf783f1c 100644 --- a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts +++ b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts @@ -14,6 +14,7 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_HR } from '@udecode/plate-horizontal-rule'; +import { ELEMENT_TOGGLE, openFutureToggles } from '@udecode/plate-toggle'; import { preFormat } from './autoformatUtils'; @@ -73,6 +74,12 @@ export const autoformatBlocks: AutoformatRule[] = [ }); }, }, + { + mode: 'block', + type: ELEMENT_TOGGLE, + match: '|> ', + preFormat: openFutureToggles, + }, { mode: 'block', type: ELEMENT_HR, diff --git a/apps/www/src/lib/plate/demo/plugins/indentPlugin.ts b/apps/www/src/lib/plate/demo/plugins/indentPlugin.ts index 73aef4467d..3e49aab9bd 100644 --- a/apps/www/src/lib/plate/demo/plugins/indentPlugin.ts +++ b/apps/www/src/lib/plate/demo/plugins/indentPlugin.ts @@ -9,6 +9,7 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_PARAGRAPH } from '@udecode/plate-paragraph'; +import { ELEMENT_TOGGLE } from '@udecode/plate-toggle'; export const indentPlugin = { inject: { @@ -23,6 +24,7 @@ export const indentPlugin = { ELEMENT_H6, ELEMENT_BLOCKQUOTE, ELEMENT_CODE_BLOCK, + ELEMENT_TOGGLE, ], }, }, diff --git a/apps/www/src/lib/plate/demo/values/toggleValue.tsx b/apps/www/src/lib/plate/demo/values/toggleValue.tsx new file mode 100644 index 0000000000..b46eba2ce7 --- /dev/null +++ b/apps/www/src/lib/plate/demo/values/toggleValue.tsx @@ -0,0 +1,29 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import { jsx } from '@udecode/plate-test-utils'; + +jsx; + +export const toggleValue: any = ( + + Toggle + + Create toggles with multiple levels of indentation + + + Level 1 toggle + + + Inside level 1 toggle + + + Level 2 toggle + + + Inside level 2 toggle + + + After toggles + + +); diff --git a/apps/www/src/lib/plate/demo/values/usePlaygroundValue.ts b/apps/www/src/lib/plate/demo/values/usePlaygroundValue.ts index 5772506b82..486df4d1ec 100644 --- a/apps/www/src/lib/plate/demo/values/usePlaygroundValue.ts +++ b/apps/www/src/lib/plate/demo/values/usePlaygroundValue.ts @@ -32,6 +32,7 @@ import { mentionValue } from './mentionValue'; import { softBreakValue } from './softBreakValue'; import { tabbableValue } from './tabbableValue'; import { tableMergeValue, tableValue } from './tableValue'; +import { toggleValue } from './toggleValue'; export const usePlaygroundValue = (id?: ValueId) => { let valueId = settingsStore.use.valueId(); @@ -75,6 +76,7 @@ export const usePlaygroundValue = (id?: ValueId) => { if (enabled.list) value.push(...listValue); if (enabled.img || enabled.media_embed) value.push(...mediaValue); if (enabled.table) value.push(...tableValue); + if (enabled.toggle) value.push(...toggleValue); // Functionalities if (enabled.autoformat) value.push(...autoformatValue); @@ -125,6 +127,7 @@ export const usePlaygroundValue = (id?: ValueId) => { enabled.softBreak, enabled.tabbable, enabled.table, + enabled.toggle, enabled.trailingBlock, valueId, ]); diff --git a/apps/www/src/registry/default/example/cards/cards-toolbar.tsx b/apps/www/src/registry/default/example/cards/cards-toolbar.tsx index 9452f3102e..3989eb09ca 100644 --- a/apps/www/src/registry/default/example/cards/cards-toolbar.tsx +++ b/apps/www/src/registry/default/example/cards/cards-toolbar.tsx @@ -28,6 +28,7 @@ import { ModeDropdownMenu } from '@/registry/default/plate-ui/mode-dropdown-menu import { MoreDropdownMenu } from '@/registry/default/plate-ui/more-dropdown-menu'; import { OutdentToolbarButton } from '@/registry/default/plate-ui/outdent-toolbar-button'; import { TableDropdownMenu } from '@/registry/default/plate-ui/table-dropdown-menu'; +import { ToggleToolbarButton } from '@/registry/default/plate-ui/toggle-toolbar-button'; import { ToolbarGroup } from '@/registry/default/plate-ui/toolbar'; import { TurnIntoDropdownMenu } from '@/registry/default/plate-ui/turn-into-dropdown-menu'; @@ -98,6 +99,8 @@ export function CardsToolbar() { + + diff --git a/apps/www/src/registry/default/example/playground-demo.tsx b/apps/www/src/registry/default/example/playground-demo.tsx index 2618da37fa..7456fb96bb 100644 --- a/apps/www/src/registry/default/example/playground-demo.tsx +++ b/apps/www/src/registry/default/example/playground-demo.tsx @@ -87,6 +87,7 @@ import { createDeserializeDocxPlugin } from '@udecode/plate-serializer-docx'; import { createDeserializeMdPlugin } from '@udecode/plate-serializer-md'; import { createTabbablePlugin } from '@udecode/plate-tabbable'; import { createTablePlugin } from '@udecode/plate-table'; +import { createTogglePlugin } from '@udecode/plate-toggle'; import { createTrailingBlockPlugin } from '@udecode/plate-trailing-block'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; @@ -157,6 +158,7 @@ export const usePlaygroundPlugins = ({ }, }), createTodoListPlugin({ enabled: !!enabled.action_item }), + createTogglePlugin({ enabled: !!enabled.toggle }), createExcalidrawPlugin({ enabled: !!enabled.excalidraw }), // Marks diff --git a/apps/www/src/registry/default/plate-ui/toggle-element.tsx b/apps/www/src/registry/default/plate-ui/toggle-element.tsx new file mode 100644 index 0000000000..41680fe351 --- /dev/null +++ b/apps/www/src/registry/default/plate-ui/toggle-element.tsx @@ -0,0 +1,43 @@ +import { withRef } from '@udecode/cn'; +import { PlateElement, useElement } from '@udecode/plate-common'; +import { useToggleButton, useToggleButtonState } from '@udecode/plate-toggle'; + +import { Icons } from '@/components/icons'; + +export const ToggleElement = withRef( + ({ children, ...props }, ref) => { + const element = useElement(); + const state = useToggleButtonState(element.id); + const { buttonProps } = useToggleButton(state); + + // TODO use tailwind instead of inline styles + return ( + +
+ + {state.open ? : } + + {children} +
+
+ ); + } +); diff --git a/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx new file mode 100644 index 0000000000..1afa48f2ab --- /dev/null +++ b/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { withRef } from '@udecode/cn'; +import { + useToggleToolbarButton, + useToggleToolbarButtonState, +} from '@udecode/plate-toggle'; + +import { Icons } from '@/components/icons'; + +import { ToolbarButton } from './toolbar'; + +export const ToggleToolbarButton = withRef( + (rest, ref) => { + const state = useToggleToolbarButtonState(); + const { props } = useToggleToolbarButton(state); + + return ( + + + + ); + } +); diff --git a/apps/www/src/registry/registry.ts b/apps/www/src/registry/registry.ts index 23afc93bcc..d7af1faf60 100644 --- a/apps/www/src/registry/registry.ts +++ b/apps/www/src/registry/registry.ts @@ -521,6 +521,20 @@ const ui: Registry = [ registryDependencies: ['checkbox'], files: ['plate-ui/todo-list-element.tsx'], }, + { + name: 'toggle-element', + type: 'components:plate-ui', + dependencies: ['@udecode/plate-toggle'], + registryDependencies: [], + files: ['plate-ui/link-element.tsx'], + }, + { + name: 'toggle-toolbar-button', + type: 'components:plate-ui', + dependencies: ['@udecode/plate-toggle'], + registryDependencies: ['toolbar'], + files: ['plate-ui/toggle-toolbar-button.tsx'], + }, { name: 'toolbar', type: 'components:plate-ui', diff --git a/apps/www/src/types/plate-types.ts b/apps/www/src/types/plate-types.ts index ae0fca7578..d3ff4f9ae6 100644 --- a/apps/www/src/types/plate-types.ts +++ b/apps/www/src/types/plate-types.ts @@ -68,6 +68,10 @@ import { ELEMENT_UL, TTodoListItemElement, } from '@udecode/plate-list'; +import { + ELEMENT_TOGGLE, + TToggleElement, +} from '@udecode/plate-toggle'; import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED, @@ -262,6 +266,13 @@ export interface MyTodoListElement children: MyInlineChildren; } +export interface MyToggleElement + extends TToggleElement, + MyBlockElement { + type: typeof ELEMENT_TOGGLE; + children: MyInlineChildren; +} + export interface MyImageElement extends TImageElement, MyBlockElement { type: typeof ELEMENT_IMAGE; children: [EmptyText]; @@ -305,6 +316,7 @@ export type MyRootBlock = | MyBulletedListElement | MyNumberedListElement | MyTodoListElement + | MyToggleElement | MyImageElement | MyMediaEmbedElement | MyHrElement diff --git a/config/aliases.js b/config/aliases.js index b66344b9b0..1e6e5bbfbe 100644 --- a/config/aliases.js +++ b/config/aliases.js @@ -46,6 +46,7 @@ module.exports = { '@udecode/plate-tabbable': 'tabbable', '@udecode/plate-table': 'table', '@udecode/plate-test-utils': 'test-utils', + '@udecode/plate-toggle': 'toggle', '@udecode/plate-trailing-block': 'trailing-block', '@udecode/plate-utils': 'plate-utils', '@udecode/react-utils': 'react-utils', diff --git a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx index 3820ba11a0..008fb9f2d2 100644 --- a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx +++ b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx @@ -132,3 +132,5 @@ describe('when [x].space', () => { expect(input.children).toEqual(output.children); }); }); + +// TODO Add toggle test diff --git a/packages/plate/package.json b/packages/plate/package.json index 0d800cbbbc..b9a459ccf2 100644 --- a/packages/plate/package.json +++ b/packages/plate/package.json @@ -79,6 +79,7 @@ "@udecode/plate-suggestion": "30.1.2", "@udecode/plate-tabbable": "30.1.2", "@udecode/plate-table": "30.1.2", + "@udecode/plate-toggle": "30.1.2", "@udecode/plate-trailing-block": "30.1.2" }, "peerDependencies": { diff --git a/packages/plate/src/__tests__/all-plugins.spec.tsx b/packages/plate/src/__tests__/all-plugins.spec.tsx index a2cf2af332..8aae31e11e 100644 --- a/packages/plate/src/__tests__/all-plugins.spec.tsx +++ b/packages/plate/src/__tests__/all-plugins.spec.tsx @@ -1,3 +1,4 @@ +// TODO add toggle import React from 'react'; import { render } from '@testing-library/react'; import { createAlignPlugin } from '@udecode/plate-alignment'; diff --git a/packages/plate/src/index.tsx b/packages/plate/src/index.tsx index d890cf146e..cfb258aa07 100644 --- a/packages/plate/src/index.tsx +++ b/packages/plate/src/index.tsx @@ -38,5 +38,6 @@ export * from '@udecode/plate-serializer-md'; export * from '@udecode/plate-suggestion'; export * from '@udecode/plate-tabbable'; export * from '@udecode/plate-table'; +export * from '@udecode/plate-toggle'; export * from '@udecode/plate-trailing-block'; export * from '@udecode/plate-resizable'; diff --git a/packages/serializer-html/src/serializeHtml.ts b/packages/serializer-html/src/serializeHtml.ts index ed733db3a7..aaf0377f08 100644 --- a/packages/serializer-html/src/serializeHtml.ts +++ b/packages/serializer-html/src/serializeHtml.ts @@ -1,3 +1,4 @@ +// TODO serialize toggle? import React from 'react'; import { EDescendant, diff --git a/packages/test-utils/src/jsx.ts b/packages/test-utils/src/jsx.ts index df44d3adc6..6bd79ebb2b 100644 --- a/packages/test-utils/src/jsx.ts +++ b/packages/test-utils/src/jsx.ts @@ -59,6 +59,7 @@ const ELEMENT_TABLE = 'table'; const ELEMENT_TD = 'td'; const ELEMENT_TH = 'th'; const ELEMENT_TODO_LI = 'action_item'; +const ELEMENT_TOGGLE = 'toggle'; const ELEMENT_TR = 'tr'; const ELEMENT_UL = 'ul'; @@ -88,6 +89,7 @@ const elements: HyperscriptShorthands = { htd: { type: ELEMENT_TD }, hth: { type: ELEMENT_TH }, htodoli: { type: ELEMENT_TODO_LI }, + htoggle: { type: ELEMENT_TOGGLE }, htr: { type: ELEMENT_TR }, hul: { type: ELEMENT_UL }, }; diff --git a/templates/plate-playground-template/package.json b/templates/plate-playground-template/package.json index 8275580a78..269b5b79a6 100644 --- a/templates/plate-playground-template/package.json +++ b/templates/plate-playground-template/package.json @@ -66,6 +66,7 @@ "@udecode/plate-serializer-md": "^29.0.1", "@udecode/plate-tabbable": "^29.0.1", "@udecode/plate-table": "^29.0.1", + "@udecode/plate-toggle": "^30.1.2", "@udecode/plate-trailing-block": "^29.0.1", "class-variance-authority": "0.7.0", "cmdk": "0.2.0", diff --git a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx index 9daf9f87e4..001104a990 100644 --- a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx +++ b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx @@ -24,6 +24,7 @@ import { MediaToolbarButton } from '@/components/plate-ui/media-toolbar-button'; import { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu'; import { OutdentToolbarButton } from '@/components/plate-ui/outdent-toolbar-button'; import { TableDropdownMenu } from '@/components/plate-ui/table-dropdown-menu'; +import { ToggleToolbarButton } from '@/components/plate-ui/toggle-toolbar-button'; import { InsertDropdownMenu } from './insert-dropdown-menu'; import { MarkToolbarButton } from './mark-toolbar-button'; @@ -101,6 +102,8 @@ export function FixedToolbarButtons() { + + diff --git a/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx b/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx new file mode 100644 index 0000000000..41680fe351 --- /dev/null +++ b/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx @@ -0,0 +1,43 @@ +import { withRef } from '@udecode/cn'; +import { PlateElement, useElement } from '@udecode/plate-common'; +import { useToggleButton, useToggleButtonState } from '@udecode/plate-toggle'; + +import { Icons } from '@/components/icons'; + +export const ToggleElement = withRef( + ({ children, ...props }, ref) => { + const element = useElement(); + const state = useToggleButtonState(element.id); + const { buttonProps } = useToggleButton(state); + + // TODO use tailwind instead of inline styles + return ( + +
+ + {state.open ? : } + + {children} +
+
+ ); + } +); diff --git a/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx b/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx new file mode 100644 index 0000000000..2331247840 --- /dev/null +++ b/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { withRef } from '@udecode/cn'; +import { + useToggleToolbarButton, + useToggleToolbarButtonState, +} from '@udecode/plate-toggle'; + +import { Icons } from '@/components/icons'; + +import { ToolbarButton } from './toolbar'; + +export const IndentListToolbarButton = withRef( + (rest, ref) => { + const state = useToggleToolbarButtonState(); + const { props } = useToggleToolbarButton(state); + + return ( + + + + ); + } +); diff --git a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts index a6ca6d2051..ac7bc9f679 100644 --- a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts +++ b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts @@ -14,6 +14,7 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_HR } from '@udecode/plate-horizontal-rule'; +import { ELEMENT_TOGGLE, openFutureToggles } from '@udecode/plate-toggle'; import { preFormat } from '@/lib/plate/autoformatUtils'; @@ -73,6 +74,12 @@ export const autoformatBlocks: AutoformatRule[] = [ }); }, }, + { + mode: 'block', + type: ELEMENT_TOGGLE, + match: '|> ', + preFormat: openFutureToggles, + }, { mode: 'block', type: ELEMENT_HR, diff --git a/templates/plate-playground-template/src/lib/plate/plate-plugins.ts b/templates/plate-playground-template/src/lib/plate/plate-plugins.ts index ee8eebcdbb..ce3e112bfc 100644 --- a/templates/plate-playground-template/src/lib/plate/plate-plugins.ts +++ b/templates/plate-playground-template/src/lib/plate/plate-plugins.ts @@ -120,6 +120,7 @@ import { ELEMENT_TH, ELEMENT_TR, } from '@udecode/plate-table'; +import { createTogglePlugin, ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { createTrailingBlockPlugin } from '@udecode/plate-trailing-block'; import { autoformatPlugin } from '@/lib/plate/autoformatPlugin'; @@ -152,6 +153,7 @@ import { import { TableElement } from '@/components/plate-ui/table-element'; import { TableRowElement } from '@/components/plate-ui/table-row-element'; import { TodoListElement } from '@/components/plate-ui/todo-list-element'; +import { ToggleElement } from '@/components/plate-ui/toggle-element'; import { withDraggables } from '@/components/plate-ui/with-draggables'; import { TabbableElement } from '@/components/tabbable-element'; @@ -185,6 +187,7 @@ export const plugins = createPlugins( createMentionPlugin(), createTablePlugin(), createTodoListPlugin(), + createTogglePlugin(), createExcalidrawPlugin(), // Marks @@ -219,6 +222,7 @@ export const plugins = createPlugins( ELEMENT_H3, ELEMENT_BLOCKQUOTE, ELEMENT_CODE_BLOCK, + ELEMENT_TOGGLE, ], }, }, @@ -402,6 +406,7 @@ export const plugins = createPlugins( [ELEMENT_TD]: TableCellElement, [ELEMENT_TH]: TableCellHeaderElement, [ELEMENT_TODO_LI]: TodoListElement, + [ELEMENT_TOGGLE]: ToggleElement, [ELEMENT_TR]: TableRowElement, [ELEMENT_EXCALIDRAW]: ExcalidrawElement, [MARK_BOLD]: withProps(PlateLeaf, { as: 'strong' }), diff --git a/templates/plate-playground-template/src/lib/plate/plate-types.ts b/templates/plate-playground-template/src/lib/plate/plate-types.ts index 84b7f8580c..0cdb2eafcc 100644 --- a/templates/plate-playground-template/src/lib/plate/plate-types.ts +++ b/templates/plate-playground-template/src/lib/plate/plate-types.ts @@ -69,6 +69,10 @@ import { ELEMENT_UL, TTodoListItemElement, } from '@udecode/plate-list'; +import { + ELEMENT_TOGGLE, + TToggleElement, +} from '@udecode/plate-toggle'; import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED, @@ -262,6 +266,13 @@ export interface MyTodoListElement children: MyInlineChildren; } +export interface MyToggleElement + extends TToggleElement, + MyBlockElement { + type: typeof ELEMENT_TOGGLE; + children: MyInlineChildren; +} + export interface MyImageElement extends TImageElement, MyBlockElement { type: typeof ELEMENT_IMAGE; children: [EmptyText]; @@ -305,6 +316,7 @@ export type MyRootBlock = | MyBulletedListElement | MyNumberedListElement | MyTodoListElement + | MyToggleElement | MyImageElement | MyMediaEmbedElement | MyHrElement From 1d9e86e742327011d8b5d64ba2f24958c68bbfb2 Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 06:45:35 +0100 Subject: [PATCH 03/16] Toggles > Review - Use JOTAI - Rename openFutureToggles into openNextToggles - Rename injectToggleWrapper into injectToggle - Don't rely on lodash treeshaking - Fix @udecode/toggle typo - Change description of apps/www/content/docs/toggle.mdx --- apps/www/content/docs/toggle.mdx | 8 +- .../plate/demo/plugins/autoformatBlocks.ts | 4 +- .../default/plate-ui/toggle-element.tsx | 4 +- packages/toggle/src/createTogglePlugin.ts | 12 ++- packages/toggle/src/hooks/index.ts | 1 + packages/toggle/src/hooks/useHooksToggle.ts | 23 +++++ packages/toggle/src/hooks/useToggleButton.ts | 32 ++----- .../src/hooks/useToggleToolbarButton.ts | 8 +- packages/toggle/src/index.ts | 3 +- ...jectToggleWrapper.tsx => injectToggle.tsx} | 7 +- .../queries/getLastEntryEnclosedInToggle.ts | 2 +- packages/toggle/src/store.ts | 96 ++++++++++++++----- packages/toggle/src/transforms/index.ts | 2 +- .../src/transforms/openFutureToggles.ts | 31 ------ .../toggle/src/transforms/openNextToggles.ts | 20 ++++ packages/toggle/src/types.ts | 9 +- packages/toggle/src/withToggle.ts | 20 ++-- .../src/lib/plate/autoformatBlocks.ts | 4 +- yarn.lock | 20 ++++ 19 files changed, 186 insertions(+), 120 deletions(-) create mode 100644 packages/toggle/src/hooks/useHooksToggle.ts rename packages/toggle/src/{injectToggleWrapper.tsx => injectToggle.tsx} (78%) delete mode 100644 packages/toggle/src/transforms/openFutureToggles.ts create mode 100644 packages/toggle/src/transforms/openNextToggles.ts diff --git a/apps/www/content/docs/toggle.mdx b/apps/www/content/docs/toggle.mdx index 02e00a2f60..71dd4ae255 100644 --- a/apps/www/content/docs/toggle.mdx +++ b/apps/www/content/docs/toggle.mdx @@ -12,8 +12,8 @@ docs: ## Features -- Turn any bock into a toggle: - - Toggles are closed by default, and can be opened to reveal the content inside +- Add toggles to your document. + - Toggles are closed by default, and can be opened to reveal the content inside. - Refer to the [Indent documentation](/docs/indent) for more information. ## Plugin dependencies @@ -27,7 +27,7 @@ docs: ## Installation ```bash -npm install @udecode/plate-indent @udecode/plate-indent-list @udecode/plate-node-id @udecode/toggle +npm install @udecode/plate-indent @udecode/plate-indent-list @udecode/plate-node-id @udecode/plate-toggle ``` ## Usage @@ -70,7 +70,7 @@ const plugins = [ {/* TODO */} -### openFutureToggles +### openNextToggles diff --git a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts index 9abf783f1c..ba0ea0c29c 100644 --- a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts +++ b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts @@ -14,7 +14,7 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_HR } from '@udecode/plate-horizontal-rule'; -import { ELEMENT_TOGGLE, openFutureToggles } from '@udecode/plate-toggle'; +import { ELEMENT_TOGGLE, openNextToggles } from '@udecode/plate-toggle'; import { preFormat } from './autoformatUtils'; @@ -78,7 +78,7 @@ export const autoformatBlocks: AutoformatRule[] = [ mode: 'block', type: ELEMENT_TOGGLE, match: '|> ', - preFormat: openFutureToggles, + preFormat: openNextToggles, }, { mode: 'block', diff --git a/apps/www/src/registry/default/plate-ui/toggle-element.tsx b/apps/www/src/registry/default/plate-ui/toggle-element.tsx index 41680fe351..1031222bad 100644 --- a/apps/www/src/registry/default/plate-ui/toggle-element.tsx +++ b/apps/www/src/registry/default/plate-ui/toggle-element.tsx @@ -8,7 +8,7 @@ export const ToggleElement = withRef( ({ children, ...props }, ref) => { const element = useElement(); const state = useToggleButtonState(element.id); - const { buttonProps } = useToggleButton(state); + const { open, buttonProps } = useToggleButton(state); // TODO use tailwind instead of inline styles return ( @@ -33,7 +33,7 @@ export const ToggleElement = withRef( }} {...buttonProps} > - {state.open ? : } + {open ? : } {children} diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts index 494cdb3a79..8b01e8acc0 100644 --- a/packages/toggle/src/createTogglePlugin.ts +++ b/packages/toggle/src/createTogglePlugin.ts @@ -1,18 +1,22 @@ import { createPluginFactory, PlateEditor, Value } from '@udecode/plate-common'; -import { injectToggleWrapper } from './injectToggleWrapper'; -import { ELEMENT_TOGGLE, ToggleEditor, TogglePlugin } from './types'; +import { useHooksToggle } from './hooks/useHooksToggle'; +import { injectToggle } from './injectToggle'; +import { ToggleControllerProvider } from './store'; +import { ELEMENT_TOGGLE, TogglePlugin } from './types'; import { withToggle } from './withToggle'; export const createTogglePlugin = createPluginFactory< TogglePlugin, Value, - PlateEditor & ToggleEditor + PlateEditor >({ key: ELEMENT_TOGGLE, isElement: true, + useHooks: useHooksToggle, + renderAboveEditable: ToggleControllerProvider, inject: { - aboveComponent: injectToggleWrapper, + aboveComponent: injectToggle, }, withOverrides: withToggle, }); diff --git a/packages/toggle/src/hooks/index.ts b/packages/toggle/src/hooks/index.ts index 399c62af59..e91ef60524 100644 --- a/packages/toggle/src/hooks/index.ts +++ b/packages/toggle/src/hooks/index.ts @@ -4,3 +4,4 @@ export * from './useToggleButton'; export * from './useToggleToolbarButton'; +export * from './useHooksToggle'; diff --git a/packages/toggle/src/hooks/useHooksToggle.ts b/packages/toggle/src/hooks/useHooksToggle.ts new file mode 100644 index 0000000000..baddfe6b61 --- /dev/null +++ b/packages/toggle/src/hooks/useHooksToggle.ts @@ -0,0 +1,23 @@ +import { useEffect } from 'react'; +import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; + +import { useToggleControllerStore } from '../store'; +import { ELEMENT_TOGGLE, TogglePlugin } from '../types'; + +export const useHooksToggle = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E +) => { + const [openIds, setOpenIds] = useToggleControllerStore().use.openIds(); + + useEffect(() => { + const options = getPluginOptions( + editor, + ELEMENT_TOGGLE + ); + options.openIds = openIds; + options.setOpenIds = setOpenIds; + }, [openIds, setOpenIds]); +}; diff --git a/packages/toggle/src/hooks/useToggleButton.ts b/packages/toggle/src/hooks/useToggleButton.ts index 6b54e2c6a1..b7ebdc23de 100644 --- a/packages/toggle/src/hooks/useToggleButton.ts +++ b/packages/toggle/src/hooks/useToggleButton.ts @@ -1,37 +1,25 @@ -import { PlateEditor, useEditorRef } from '@udecode/plate-common'; -import { Value } from '@udecode/slate'; +import { useEditorRef } from '@udecode/plate-common'; -import { toggleToggleId } from '../store'; -import { ToggleEditor } from '../types'; +import { toggleIds, useToggleControllerStore } from '../store'; -type ToggleButtonState = { - toggleId: string; - open: boolean; - openIds: Set; -}; - -const useToggleStore = () => - useEditorRef().toggleStore; - -export const useToggleButtonState = (toggleId: string): ToggleButtonState => { - const store = useToggleStore(); - const openIds = store.use.openIds(); - const open = openIds.has(toggleId); +export const useToggleButtonState = (toggleId: string) => { + const [openIds] = useToggleControllerStore().use.openIds(); return { toggleId, - open, - openIds, + open: openIds.has(toggleId), }; }; -export const useToggleButton = (state: ToggleButtonState) => { - const store = useToggleStore(); +export const useToggleButton = ( + state: ReturnType +) => { + const editor = useEditorRef(); return { ...state, buttonProps: { onClick: (e: React.MouseEvent) => { e.preventDefault(); - store.set.openIds(toggleToggleId(state)); + toggleIds(editor, [state.toggleId]); }, }, }; diff --git a/packages/toggle/src/hooks/useToggleToolbarButton.ts b/packages/toggle/src/hooks/useToggleToolbarButton.ts index 25db2dffc5..d724b24b46 100644 --- a/packages/toggle/src/hooks/useToggleToolbarButton.ts +++ b/packages/toggle/src/hooks/useToggleToolbarButton.ts @@ -9,8 +9,8 @@ import { } from '@udecode/plate-common'; import { someToggle } from '../queries/someToggle'; -import { openFutureToggles } from '../transforms'; -import { ELEMENT_TOGGLE, ToggleEditor } from '../types'; +import { openNextToggles } from '../transforms'; +import { ELEMENT_TOGGLE } from '../types'; export const useToggleToolbarButtonState = () => { const pressed = useEditorSelector((editor) => someToggle(editor), []); @@ -23,7 +23,7 @@ export const useToggleToolbarButtonState = () => { export const useToggleToolbarButton = ({ pressed, }: ReturnType) => { - const editor = useEditorRef & ToggleEditor>(); + const editor = useEditorRef>(); return { props: { @@ -32,7 +32,7 @@ export const useToggleToolbarButton = ({ e.preventDefault(); }, onClick: () => { - openFutureToggles(editor); + openNextToggles(editor); toggleNodeType(editor, { activeType: ELEMENT_TOGGLE }); collapseSelection(editor); focusEditor(editor); diff --git a/packages/toggle/src/index.ts b/packages/toggle/src/index.ts index 65798c5cd2..2ea81bda5a 100644 --- a/packages/toggle/src/index.ts +++ b/packages/toggle/src/index.ts @@ -3,9 +3,10 @@ */ export * from './createTogglePlugin'; -export * from './injectToggleWrapper'; +export * from './injectToggle'; export * from './types'; export * from './withToggle'; export * from './hooks/index'; export * from './queries/index'; export * from './transforms/index'; +export * from './store'; diff --git a/packages/toggle/src/injectToggleWrapper.tsx b/packages/toggle/src/injectToggle.tsx similarity index 78% rename from packages/toggle/src/injectToggleWrapper.tsx rename to packages/toggle/src/injectToggle.tsx index 584d00e7a4..0aafd75448 100644 --- a/packages/toggle/src/injectToggleWrapper.tsx +++ b/packages/toggle/src/injectToggle.tsx @@ -3,17 +3,16 @@ import { findNodePath, InjectComponentReturnType } from '@udecode/plate-common'; import { TIndentElement } from '@udecode/plate-indent'; import { getEnclosingToggleIds } from './queries'; -import { ToggleEditor } from './types'; +import { useToggleControllerStore } from './store'; -export const injectToggleWrapper = (): InjectComponentReturnType => WithToggle; +export const injectToggle = (): InjectComponentReturnType => WithToggle; const WithToggle: InjectComponentReturnType = ({ element, children, editor, }) => { - const store = editor.toggleStore as ToggleEditor['toggleStore']; - const openIds = store.use.openIds(); + const [openIds] = useToggleControllerStore().use.openIds(); const path = findNodePath(editor, element); if (!path) return children; diff --git a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts index 62d9c1efab..4d6213da73 100644 --- a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts +++ b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts @@ -1,5 +1,5 @@ import { PlateEditor, TNodeEntry } from '@udecode/plate-common'; -import { last } from 'lodash'; +import last from 'lodash/last'; import { getEnclosingToggleIds } from './getEnclosingToggleIds'; diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts index aa4da1d7b5..fb848e25bc 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/store.ts @@ -1,37 +1,85 @@ -import { createZustandStore } from '@udecode/plate-common'; +import { + atom, + createAtomStore, + getPluginOptions, + PlateEditor, + Value, +} from '@udecode/plate-common'; -import { ELEMENT_TOGGLE } from './types'; +import { ELEMENT_TOGGLE, TogglePlugin } from './types'; -export const createToggleStore = () => { - return createZustandStore(ELEMENT_TOGGLE)({ - openIds: new Set() as Set, - }); -}; +export const { + toggleControllerStore, + ToggleControllerProvider, + useToggleControllerStore, +} = createAtomStore( + { + openIds: atom(new Set()), + }, + { name: 'toggleController' as const } +); -export type ToggleStore = ReturnType; +export const triggerStoreUpdate = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E +) => { + const options = getPluginOptions(editor, ELEMENT_TOGGLE); + // TODO fix At this point openIds can be undefined + options.setOpenIds?.(new Set(options.openIds?.values() || [])); +}; -export const someToggleClosed = ( - store: ToggleStore, +export const someToggleClosed = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E, toggleIds: string[] ): boolean => { - const openIds = store.get.openIds(); + const options = getPluginOptions(editor, ELEMENT_TOGGLE); + const openIds = options.openIds; return toggleIds.some((id) => !openIds.has(id)); }; -export const triggerStoreUpdate = (store: ToggleStore) => { - store.set.openIds(new Set(store.get.openIds().values())); +export const isToggleOpen = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E, + toggleId: string +): boolean => { + const options = getPluginOptions(editor, ELEMENT_TOGGLE); + const openIds = options.openIds; + return openIds.has(toggleId); }; -export const toggleToggleId = (state: { - toggleId: string; - open: boolean; // current open state - openIds: Set; -}): Set => { - const newOpenIds = new Set(state.openIds.values()); - if (state.open) { - newOpenIds.delete(state.toggleId); - } else { - newOpenIds.add(state.toggleId); - } +export const toggleIds = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E, + toggleIds: string[], + force: boolean | undefined = undefined +): void => { + const options = getPluginOptions(editor, ELEMENT_TOGGLE); + options.setOpenIds((openIds) => _toggleIds(openIds, toggleIds, force)); +}; + +const _toggleIds = ( + openIds: Set, + toggleIds: string[], + force: boolean | undefined = undefined +) => { + const newOpenIds = new Set(openIds.values()); + toggleIds.forEach((toggleId) => { + const isCurrentlyOpen = openIds.has(toggleId); + const newIsOpen = force === undefined ? !isCurrentlyOpen : force; + if (newIsOpen) { + newOpenIds.add(toggleId); + } else { + newOpenIds.delete(toggleId); + } + }); return newOpenIds; }; diff --git a/packages/toggle/src/transforms/index.ts b/packages/toggle/src/transforms/index.ts index 7d7afdab77..6715cdf8d5 100644 --- a/packages/toggle/src/transforms/index.ts +++ b/packages/toggle/src/transforms/index.ts @@ -2,4 +2,4 @@ * @file Automatically generated by barrelsby. */ -export * from './openFutureToggles'; +export * from './openNextToggles'; diff --git a/packages/toggle/src/transforms/openFutureToggles.ts b/packages/toggle/src/transforms/openFutureToggles.ts deleted file mode 100644 index 79ccf7350a..0000000000 --- a/packages/toggle/src/transforms/openFutureToggles.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - getNodeEntries, - PlateEditor, - withoutNormalizing, -} from '@udecode/plate-common'; - -import { toggleToggleId } from '../store'; -import { ToggleEditor } from '../types'; - -// When creating a toggle, we open it by default. -// So before inserting the toggle, we update the store to mark the id of the blocks about to be turned into toggles as open. -export const openFutureToggles = (editor: PlateEditor & ToggleEditor) => { - const nodeEntries = Array.from( - getNodeEntries(editor, { - block: true, - mode: 'lowest', - }) - ); - - withoutNormalizing(editor, () => { - nodeEntries.forEach(([node]) => { - editor.toggleStore.set.openIds( - toggleToggleId({ - openIds: editor.toggleStore.get.openIds(), - open: false, - toggleId: node.id as string, - }) - ); - }); - }); -}; diff --git a/packages/toggle/src/transforms/openNextToggles.ts b/packages/toggle/src/transforms/openNextToggles.ts new file mode 100644 index 0000000000..ed233f4200 --- /dev/null +++ b/packages/toggle/src/transforms/openNextToggles.ts @@ -0,0 +1,20 @@ +import { getNodeEntries, PlateEditor } from '@udecode/plate-common'; + +import { toggleIds } from '../store'; + +// When creating a toggle, we open it by default. +// So before inserting the toggle, we update the store to mark the id of the blocks about to be turned into toggles as open. +export const openNextToggles = (editor: PlateEditor) => { + const nodeEntries = Array.from( + getNodeEntries(editor, { + block: true, + mode: 'lowest', + }) + ); + + toggleIds( + editor, + nodeEntries.map(([node]) => node.id as string), + true + ); +}; diff --git a/packages/toggle/src/types.ts b/packages/toggle/src/types.ts index 1467510ab1..64bdb7b95e 100644 --- a/packages/toggle/src/types.ts +++ b/packages/toggle/src/types.ts @@ -1,15 +1,14 @@ -import { ToggleStore } from './store'; +import { SetStateAction } from 'react'; export interface TogglePlugin { // Options would go here + // TODO a JOTAI layer instead of relying on plugin options + openIds: Set; + setOpenIds: (args_0: SetStateAction>) => void; } export const ELEMENT_TOGGLE = 'toggle'; -export type ToggleEditor = { - toggleStore: ToggleStore; -}; - export type TToggleElement = { type: typeof ELEMENT_TOGGLE; }; diff --git a/packages/toggle/src/withToggle.ts b/packages/toggle/src/withToggle.ts index de6f78b8f8..9e2d7f535e 100644 --- a/packages/toggle/src/withToggle.ts +++ b/packages/toggle/src/withToggle.ts @@ -6,25 +6,20 @@ import { PlateEditor, toggleNodeType, Value, + WithPlatePlugin, } from '@udecode/plate-common'; import { indent, TIndentElement } from '@udecode/plate-indent'; import { getEnclosingToggleIds, getLastEntryEnclosedInToggle } from './queries'; -import { - createToggleStore, - someToggleClosed, - triggerStoreUpdate, -} from './store'; -import { ELEMENT_TOGGLE, ToggleEditor } from './types'; +import { isToggleOpen, someToggleClosed, triggerStoreUpdate } from './store'; +import { ELEMENT_TOGGLE, TogglePlugin } from './types'; export const withToggle = < V extends Value = Value, - E extends PlateEditor & ToggleEditor = PlateEditor & ToggleEditor, + E extends PlateEditor = PlateEditor, >( editor: E - // { options }: WithPlatePlugin ) => { - editor.toggleStore = createToggleStore(); const { insertBreak, isSelectable, apply } = editor; editor.isSelectable = (element) => { @@ -36,7 +31,7 @@ export const withToggle = < element, path, ]); - if (someToggleClosed(editor.toggleStore, enclosingToggleIds)) { + if (someToggleClosed(editor, enclosingToggleIds)) { return false; } @@ -58,8 +53,7 @@ export const withToggle = < } const toggleId = currentBlockEntry[0].id as string; - const openIds = editor.toggleStore.get.openIds(); - const isOpen = openIds.has(toggleId); + const isOpen = isToggleOpen(editor, toggleId); editor.withoutNormalizing(() => { if (isOpen) { @@ -99,7 +93,7 @@ export const withToggle = < 'remove_node', ].includes(operation.type) ) { - triggerStoreUpdate(editor.toggleStore); + triggerStoreUpdate(editor); } }; diff --git a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts index ac7bc9f679..03562ff2a6 100644 --- a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts +++ b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts @@ -14,7 +14,7 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_HR } from '@udecode/plate-horizontal-rule'; -import { ELEMENT_TOGGLE, openFutureToggles } from '@udecode/plate-toggle'; +import { ELEMENT_TOGGLE, openNextToggles } from '@udecode/plate-toggle'; import { preFormat } from '@/lib/plate/autoformatUtils'; @@ -78,7 +78,7 @@ export const autoformatBlocks: AutoformatRule[] = [ mode: 'block', type: ELEMENT_TOGGLE, match: '|> ', - preFormat: openFutureToggles, + preFormat: openNextToggles, }, { mode: 'block', diff --git a/yarn.lock b/yarn.lock index 8315cb0559..d0dae47e86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6940,6 +6940,24 @@ __metadata: languageName: unknown linkType: soft +"@udecode/plate-toggle@npm:30.1.2, @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": "npm:30.1.2" + "@udecode/plate-indent": "npm:30.1.2" + "@udecode/plate-indent-list": "npm:30.1.2" + "@udecode/plate-node-id": "npm:30.1.2" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + slate: ">=0.94.0" + slate-history: ">=0.93.0" + slate-hyperscript: ">=0.66.0" + slate-react: ">=0.99.0" + languageName: unknown + linkType: soft + "@udecode/plate-trailing-block@npm:30.1.2, @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" @@ -7067,6 +7085,7 @@ __metadata: "@udecode/plate-suggestion": "npm:30.1.2" "@udecode/plate-tabbable": "npm:30.1.2" "@udecode/plate-table": "npm:30.1.2" + "@udecode/plate-toggle": "npm:30.1.2" "@udecode/plate-trailing-block": "npm:30.1.2" peerDependencies: react: ">=16.8.0" @@ -21684,6 +21703,7 @@ __metadata: "@udecode/plate-tabbable": "workspace:^" "@udecode/plate-table": "workspace:^" "@udecode/plate-test-utils": "workspace:^" + "@udecode/plate-toggle": "workspace:^" "@udecode/plate-trailing-block": "workspace:^" "@udecode/plate-utils": "workspace:^" "@udecode/react-utils": "workspace:^" From 841c0bf945a2f1adeb8792ce1ea9bd20824ed095 Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 10:22:42 +0100 Subject: [PATCH 04/16] Use a style tag to hide elements instead of subscribing every component to the openIds store --- .../toggle/src/RenderAboveEditableToggle.tsx | 17 ++++++ packages/toggle/src/ToggleStyle.tsx | 52 +++++++++++++++++++ packages/toggle/src/createTogglePlugin.ts | 10 ++-- packages/toggle/src/index.ts | 1 - packages/toggle/src/injectNodeIdClassName.ts | 11 ++++ packages/toggle/src/injectToggle.tsx | 39 -------------- .../src/queries/getEnclosingToggleIds.ts | 4 +- packages/toggle/src/store.ts | 11 ---- packages/toggle/src/withToggle.ts | 25 ++------- 9 files changed, 89 insertions(+), 81 deletions(-) create mode 100644 packages/toggle/src/RenderAboveEditableToggle.tsx create mode 100644 packages/toggle/src/ToggleStyle.tsx create mode 100644 packages/toggle/src/injectNodeIdClassName.ts delete mode 100644 packages/toggle/src/injectToggle.tsx diff --git a/packages/toggle/src/RenderAboveEditableToggle.tsx b/packages/toggle/src/RenderAboveEditableToggle.tsx new file mode 100644 index 0000000000..f00edf492d --- /dev/null +++ b/packages/toggle/src/RenderAboveEditableToggle.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { ToggleControllerProvider } from './store'; +import { ToggleStyle } from './ToggleStyle'; + +export const RenderAboveEditableToggle = ({ + children, +}: { + children: React.ReactNode; +}) => { + return ( + + + {children} + + ); +}; diff --git a/packages/toggle/src/ToggleStyle.tsx b/packages/toggle/src/ToggleStyle.tsx new file mode 100644 index 0000000000..4ad3ea9d38 --- /dev/null +++ b/packages/toggle/src/ToggleStyle.tsx @@ -0,0 +1,52 @@ +import React, { useMemo } from 'react'; +import { useEditorRef } from '@udecode/plate-common'; +import { TIndentElement } from '@udecode/plate-indent'; + +import { idToClassName } from './injectNodeIdClassName'; +import { buildToggleIndex } from './queries'; +import { useToggleControllerStore } from './store'; + +// TODO restrict to the editor +export const ToggleStyle = () => { + const { children } = useEditorRef(); + const [openIds] = useToggleControllerStore().use.openIds(); + const css = useMemo( + () => hideElementsInToggleCSS(openIds, children as TIndentElement[]), + [openIds, children] + ); + + return ; +}; + +const hideElementsInToggleCSS = ( + openIds: Set, + elements: TIndentElement[] +) => { + const elementIdsToHide = findElementIdsToHide(openIds, elements); + return `${elementIdsToSelector(elementIdsToHide)} ${hiddenElementsStyle}`; +}; + +const findElementIdsToHide = ( + openIds: Set, + elements: TIndentElement[] +): string[] => { + const toggleIndex = buildToggleIndex(elements); + return ( + elements + .filter((_, index) => + toggleIndex[index].some((toggleId) => !openIds.has(toggleId)) + ) + // TODO do not rely on id being the key for the identifier + .map((element) => element.id as string) + ); +}; + +const elementIdsToSelector = (ids: string[]) => { + return ids.map((id) => `.${idToClassName(id)}`).join(','); +}; + +const hiddenElementsStyle = `{ + visibility: hidden; + height: 0; + margin: 0; +}`; diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts index 8b01e8acc0..e761fc505a 100644 --- a/packages/toggle/src/createTogglePlugin.ts +++ b/packages/toggle/src/createTogglePlugin.ts @@ -1,8 +1,8 @@ import { createPluginFactory, PlateEditor, Value } from '@udecode/plate-common'; import { useHooksToggle } from './hooks/useHooksToggle'; -import { injectToggle } from './injectToggle'; -import { ToggleControllerProvider } from './store'; +import { injectNodeIdClassName } from './injectNodeIdClassName'; +import { RenderAboveEditableToggle } from './RenderAboveEditableToggle'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; import { withToggle } from './withToggle'; @@ -14,9 +14,7 @@ export const createTogglePlugin = createPluginFactory< key: ELEMENT_TOGGLE, isElement: true, useHooks: useHooksToggle, - renderAboveEditable: ToggleControllerProvider, - inject: { - aboveComponent: injectToggle, - }, + renderAboveEditable: RenderAboveEditableToggle, withOverrides: withToggle, + inject: injectNodeIdClassName, }); diff --git a/packages/toggle/src/index.ts b/packages/toggle/src/index.ts index 2ea81bda5a..5523543a35 100644 --- a/packages/toggle/src/index.ts +++ b/packages/toggle/src/index.ts @@ -3,7 +3,6 @@ */ export * from './createTogglePlugin'; -export * from './injectToggle'; export * from './types'; export * from './withToggle'; export * from './hooks/index'; diff --git a/packages/toggle/src/injectNodeIdClassName.ts b/packages/toggle/src/injectNodeIdClassName.ts new file mode 100644 index 0000000000..55584d148c --- /dev/null +++ b/packages/toggle/src/injectNodeIdClassName.ts @@ -0,0 +1,11 @@ +import { PlatePlugin } from '@udecode/plate-common'; + +export const injectNodeIdClassName: PlatePlugin['inject'] = { + props: { + query: () => true, + transformClassName: ({ element }) => + element ? idToClassName(element.id as string) : undefined, + }, +}; + +export const idToClassName = (id: string) => `slate-node-${id}`; diff --git a/packages/toggle/src/injectToggle.tsx b/packages/toggle/src/injectToggle.tsx deleted file mode 100644 index 0aafd75448..0000000000 --- a/packages/toggle/src/injectToggle.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { findNodePath, InjectComponentReturnType } from '@udecode/plate-common'; -import { TIndentElement } from '@udecode/plate-indent'; - -import { getEnclosingToggleIds } from './queries'; -import { useToggleControllerStore } from './store'; - -export const injectToggle = (): InjectComponentReturnType => WithToggle; - -const WithToggle: InjectComponentReturnType = ({ - element, - children, - editor, -}) => { - const [openIds] = useToggleControllerStore().use.openIds(); - const path = findNodePath(editor, element); - if (!path) return children; - - // TODO Instead of relying on editor.children, use the element's siblings - const inToggleIds = getEnclosingToggleIds( - editor.children as TIndentElement[], - [element, path] - ); - - const isOpen = inToggleIds.every((id) => openIds.has(id)); - if (isOpen) return children; - - return ( -
- {children} -
- ); -}; diff --git a/packages/toggle/src/queries/getEnclosingToggleIds.ts b/packages/toggle/src/queries/getEnclosingToggleIds.ts index a38e184f02..bbe1b3bec3 100644 --- a/packages/toggle/src/queries/getEnclosingToggleIds.ts +++ b/packages/toggle/src/queries/getEnclosingToggleIds.ts @@ -14,7 +14,7 @@ export function getEnclosingToggleIds( } // Returns, for each child, the enclosing toggle ids -function buildToggleIndex(elements: TIndentElement[]): string[][] { +export const buildToggleIndex = (elements: TIndentElement[]): string[][] => { const result: string[][] = []; let currentEnclosingToggles: [string, number][] = []; // [toggleId, indent][] elements.forEach((element, index) => { @@ -40,6 +40,6 @@ function buildToggleIndex(elements: TIndentElement[]): string[][] { }); return result; -} +}; const memoizedBuildToggleIndex = memoize(buildToggleIndex); diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts index fb848e25bc..7e8ad3bd65 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/store.ts @@ -19,17 +19,6 @@ export const { { name: 'toggleController' as const } ); -export const triggerStoreUpdate = < - V extends Value = Value, - E extends PlateEditor = PlateEditor, ->( - editor: E -) => { - const options = getPluginOptions(editor, ELEMENT_TOGGLE); - // TODO fix At this point openIds can be undefined - options.setOpenIds?.(new Set(options.openIds?.values() || [])); -}; - export const someToggleClosed = < V extends Value = Value, E extends PlateEditor = PlateEditor, diff --git a/packages/toggle/src/withToggle.ts b/packages/toggle/src/withToggle.ts index 9e2d7f535e..1ab16bf959 100644 --- a/packages/toggle/src/withToggle.ts +++ b/packages/toggle/src/withToggle.ts @@ -6,13 +6,12 @@ import { PlateEditor, toggleNodeType, Value, - WithPlatePlugin, } from '@udecode/plate-common'; import { indent, TIndentElement } from '@udecode/plate-indent'; import { getEnclosingToggleIds, getLastEntryEnclosedInToggle } from './queries'; -import { isToggleOpen, someToggleClosed, triggerStoreUpdate } from './store'; -import { ELEMENT_TOGGLE, TogglePlugin } from './types'; +import { isToggleOpen, someToggleClosed } from './store'; +import { ELEMENT_TOGGLE } from './types'; export const withToggle = < V extends Value = Value, @@ -20,7 +19,7 @@ export const withToggle = < >( editor: E ) => { - const { insertBreak, isSelectable, apply } = editor; + const { insertBreak, isSelectable } = editor; editor.isSelectable = (element) => { if (!isNode(element)) return isSelectable(element); @@ -79,23 +78,5 @@ export const withToggle = < }); }; - editor.apply = (operation) => { - // MNake sure all elements' visibility is re-calculated - apply(operation); - - // TODO Be more restrictive to improve performance, for instance by restricting operations on toggles - if ( - [ - 'merge_node', - 'move_node', - 'set_node', - 'insert_node', - 'remove_node', - ].includes(operation.type) - ) { - triggerStoreUpdate(editor); - } - }; - return editor; }; From 014a11d2ac14061a03cbb2981d5c20cbc3ef89ca Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 11:12:32 +0100 Subject: [PATCH 05/16] Identify hidden toggles by their id instead of by their path --- packages/toggle/src/ToggleStyle.tsx | 19 ++---------- .../queries/findElementIdsHiddenInToggle.ts | 18 +++++++++++ .../src/queries/getEnclosingToggleIds.ts | 30 +++++++++---------- .../queries/getLastEntryEnclosedInToggle.ts | 16 +++++----- packages/toggle/src/queries/index.ts | 2 ++ .../toggle/src/queries/isInClosedToggle.ts | 15 ++++++++++ packages/toggle/src/withToggle.ts | 22 ++++---------- 7 files changed, 64 insertions(+), 58 deletions(-) create mode 100644 packages/toggle/src/queries/findElementIdsHiddenInToggle.ts create mode 100644 packages/toggle/src/queries/isInClosedToggle.ts diff --git a/packages/toggle/src/ToggleStyle.tsx b/packages/toggle/src/ToggleStyle.tsx index 4ad3ea9d38..eb03a389a3 100644 --- a/packages/toggle/src/ToggleStyle.tsx +++ b/packages/toggle/src/ToggleStyle.tsx @@ -3,7 +3,7 @@ import { useEditorRef } from '@udecode/plate-common'; import { TIndentElement } from '@udecode/plate-indent'; import { idToClassName } from './injectNodeIdClassName'; -import { buildToggleIndex } from './queries'; +import { findElementIdsHiddenInToggle } from './queries'; import { useToggleControllerStore } from './store'; // TODO restrict to the editor @@ -22,25 +22,10 @@ const hideElementsInToggleCSS = ( openIds: Set, elements: TIndentElement[] ) => { - const elementIdsToHide = findElementIdsToHide(openIds, elements); + const elementIdsToHide = findElementIdsHiddenInToggle(openIds, elements); return `${elementIdsToSelector(elementIdsToHide)} ${hiddenElementsStyle}`; }; -const findElementIdsToHide = ( - openIds: Set, - elements: TIndentElement[] -): string[] => { - const toggleIndex = buildToggleIndex(elements); - return ( - elements - .filter((_, index) => - toggleIndex[index].some((toggleId) => !openIds.has(toggleId)) - ) - // TODO do not rely on id being the key for the identifier - .map((element) => element.id as string) - ); -}; - const elementIdsToSelector = (ids: string[]) => { return ids.map((id) => `.${idToClassName(id)}`).join(','); }; diff --git a/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts new file mode 100644 index 0000000000..30eba6f3bf --- /dev/null +++ b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts @@ -0,0 +1,18 @@ +import { TIndentElement } from '@udecode/plate-indent'; + +import { buildToggleIndex } from './getEnclosingToggleIds'; + +export const findElementIdsHiddenInToggle = ( + openToggleIds: Set, + elements: TIndentElement[] +): string[] => { + const toggleIndex = buildToggleIndex(elements); + return elements + .filter((element) => { + const enclosingToggleIds = toggleIndex.get(element.id as string) || []; + return enclosingToggleIds.some( + (toggleId) => !openToggleIds.has(toggleId) + ); + }) + .map((element) => element.id as string); +}; diff --git a/packages/toggle/src/queries/getEnclosingToggleIds.ts b/packages/toggle/src/queries/getEnclosingToggleIds.ts index bbe1b3bec3..a020ba4d9a 100644 --- a/packages/toggle/src/queries/getEnclosingToggleIds.ts +++ b/packages/toggle/src/queries/getEnclosingToggleIds.ts @@ -1,24 +1,23 @@ -import { KEY_INDENT, TIndentElement } from '@udecode/plate-indent'; +import { Value } from '@udecode/plate-common'; +import { KEY_INDENT } from '@udecode/plate-indent'; import { KEY_LIST_STYLE_TYPE } from '@udecode/plate-indent-list'; import { memoize } from 'lodash'; -import { NodeEntry } from 'slate'; import { ELEMENT_TOGGLE } from '../types'; export function getEnclosingToggleIds( - elements: TIndentElement[], - [_node, path]: NodeEntry + elements: Value, + elementId: string ): string[] { - // TODO Type so that there is no need to cast as indent elements - return memoizedBuildToggleIndex(elements)[path[0]]; + return memoizedBuildToggleIndex(elements).get(elementId) || []; } // Returns, for each child, the enclosing toggle ids -export const buildToggleIndex = (elements: TIndentElement[]): string[][] => { - const result: string[][] = []; +export const buildToggleIndex = (elements: Value): Map => { + const result: Map = new Map(); let currentEnclosingToggles: [string, number][] = []; // [toggleId, indent][] - elements.forEach((element, index) => { - const elementIndent = element[KEY_INDENT] || 0; + elements.forEach((element) => { + const elementIndent = (element[KEY_INDENT] as number) || 0; // For some reason, indent lists have a min indent of 1, even though they are not indented const elementIndentWithIndentListCorrection = element[KEY_LIST_STYLE_TYPE] && element[KEY_INDENT] @@ -29,13 +28,12 @@ export const buildToggleIndex = (elements: TIndentElement[]): string[][] => { return indent < elementIndentWithIndentListCorrection; }); currentEnclosingToggles = enclosingToggles; - result[index] = enclosingToggles.map(([toggleId]) => toggleId); + result.set( + element.id as string, + enclosingToggles.map(([toggleId]) => toggleId) + ); if (element.type === ELEMENT_TOGGLE) { - // TODO Use the identifier provided as option instead of the default identifier of the node-id plugin, and type accordingly - currentEnclosingToggles.push([ - element.id as string, - element[KEY_INDENT] || 0, - ]); + currentEnclosingToggles.push([element.id as string, elementIndent]); } }); diff --git a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts index 4d6213da73..6a68d9def7 100644 --- a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts +++ b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts @@ -1,19 +1,19 @@ -import { PlateEditor, TNodeEntry } from '@udecode/plate-common'; +import { PlateEditor, TNodeEntry, Value } from '@udecode/plate-common'; import last from 'lodash/last'; import { getEnclosingToggleIds } from './getEnclosingToggleIds'; -export const getLastEntryEnclosedInToggle = ( - editor: PlateEditor, +export const getLastEntryEnclosedInToggle = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E, toggleId: string ): TNodeEntry | undefined => { const entriesInToggle = editor.children .map((node, index) => [node, [index]] as TNodeEntry) - .filter(([node, path]) => { - // @ts-ignore TODO Instead of relying on editor.children, use the element's siblings - return getEnclosingToggleIds(editor.children, [node, path]).includes( - toggleId - ); + .filter(([node]) => { + return getEnclosingToggleIds(editor.children, node.id).includes(toggleId); }); return last(entriesInToggle); }; diff --git a/packages/toggle/src/queries/index.ts b/packages/toggle/src/queries/index.ts index f33fd496cd..fa245ffc4f 100644 --- a/packages/toggle/src/queries/index.ts +++ b/packages/toggle/src/queries/index.ts @@ -2,6 +2,8 @@ * @file Automatically generated by barrelsby. */ +export * from './findElementIdsHiddenInToggle'; export * from './getEnclosingToggleIds'; export * from './getLastEntryEnclosedInToggle'; +export * from './isInClosedToggle'; export * from './someToggle'; diff --git a/packages/toggle/src/queries/isInClosedToggle.ts b/packages/toggle/src/queries/isInClosedToggle.ts new file mode 100644 index 0000000000..64754a35a4 --- /dev/null +++ b/packages/toggle/src/queries/isInClosedToggle.ts @@ -0,0 +1,15 @@ +import { PlateEditor, Value } from '@udecode/plate-common'; + +import { someToggleClosed } from '../store'; +import { getEnclosingToggleIds } from './getEnclosingToggleIds'; + +export const isInClosedToggle = < + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>( + editor: E, + elementId: string +) => { + const enclosingToggleIds = getEnclosingToggleIds(editor.children, elementId); + return someToggleClosed(editor, enclosingToggleIds); +}; diff --git a/packages/toggle/src/withToggle.ts b/packages/toggle/src/withToggle.ts index 1ab16bf959..e042cecd5f 100644 --- a/packages/toggle/src/withToggle.ts +++ b/packages/toggle/src/withToggle.ts @@ -1,5 +1,4 @@ import { - findNodePath, getBlockAbove, isNode, moveNodes, @@ -9,8 +8,8 @@ import { } from '@udecode/plate-common'; import { indent, TIndentElement } from '@udecode/plate-indent'; -import { getEnclosingToggleIds, getLastEntryEnclosedInToggle } from './queries'; -import { isToggleOpen, someToggleClosed } from './store'; +import { getLastEntryEnclosedInToggle, isInClosedToggle } from './queries'; +import { isToggleOpen } from './store'; import { ELEMENT_TOGGLE } from './types'; export const withToggle = < @@ -22,18 +21,8 @@ export const withToggle = < const { insertBreak, isSelectable } = editor; editor.isSelectable = (element) => { - if (!isNode(element)) return isSelectable(element); - const path = findNodePath(editor, element); - if (!path) return isSelectable(element); - // @ts-ignore TODO Instead of relying on editor.children, use the element's siblings - const enclosingToggleIds = getEnclosingToggleIds(editor.children, [ - element, - path, - ]); - if (someToggleClosed(editor, enclosingToggleIds)) { + if (isNode(element) && isInClosedToggle(editor, element.id as string)) return false; - } - return isSelectable(element); }; @@ -60,9 +49,8 @@ export const withToggle = < toggleNodeType(editor, { activeType: ELEMENT_TOGGLE }); indent(editor); } else { - const lastEntryEnclosedInToggle = getLastEntryEnclosedInToggle( - // TODO typing: There should be no need for casting - editor as PlateEditor, + const lastEntryEnclosedInToggle = getLastEntryEnclosedInToggle( + editor, toggleId ); From e9f439db3c166e65032d933b3b590fdc6c2057f8 Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 11:23:26 +0100 Subject: [PATCH 06/16] Revert templates changes before release --- .../plate-playground-template/package.json | 1 - .../plate-ui/fixed-toolbar-buttons.tsx | 3 -- .../components/plate-ui/toggle-element.tsx | 43 ------------------- .../plate-ui/toggle-toolbar-button.tsx | 23 ---------- .../src/lib/plate/autoformatBlocks.ts | 7 --- .../src/lib/plate/plate-plugins.ts | 5 --- .../src/lib/plate/plate-types.ts | 12 ------ 7 files changed, 94 deletions(-) delete mode 100644 templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx delete mode 100644 templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx diff --git a/templates/plate-playground-template/package.json b/templates/plate-playground-template/package.json index 269b5b79a6..8275580a78 100644 --- a/templates/plate-playground-template/package.json +++ b/templates/plate-playground-template/package.json @@ -66,7 +66,6 @@ "@udecode/plate-serializer-md": "^29.0.1", "@udecode/plate-tabbable": "^29.0.1", "@udecode/plate-table": "^29.0.1", - "@udecode/plate-toggle": "^30.1.2", "@udecode/plate-trailing-block": "^29.0.1", "class-variance-authority": "0.7.0", "cmdk": "0.2.0", diff --git a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx index 001104a990..9daf9f87e4 100644 --- a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx +++ b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx @@ -24,7 +24,6 @@ import { MediaToolbarButton } from '@/components/plate-ui/media-toolbar-button'; import { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu'; import { OutdentToolbarButton } from '@/components/plate-ui/outdent-toolbar-button'; import { TableDropdownMenu } from '@/components/plate-ui/table-dropdown-menu'; -import { ToggleToolbarButton } from '@/components/plate-ui/toggle-toolbar-button'; import { InsertDropdownMenu } from './insert-dropdown-menu'; import { MarkToolbarButton } from './mark-toolbar-button'; @@ -102,8 +101,6 @@ export function FixedToolbarButtons() { - - diff --git a/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx b/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx deleted file mode 100644 index 41680fe351..0000000000 --- a/templates/plate-playground-template/src/components/plate-ui/toggle-element.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { withRef } from '@udecode/cn'; -import { PlateElement, useElement } from '@udecode/plate-common'; -import { useToggleButton, useToggleButtonState } from '@udecode/plate-toggle'; - -import { Icons } from '@/components/icons'; - -export const ToggleElement = withRef( - ({ children, ...props }, ref) => { - const element = useElement(); - const state = useToggleButtonState(element.id); - const { buttonProps } = useToggleButton(state); - - // TODO use tailwind instead of inline styles - return ( - -
- - {state.open ? : } - - {children} -
-
- ); - } -); diff --git a/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx b/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx deleted file mode 100644 index 2331247840..0000000000 --- a/templates/plate-playground-template/src/components/plate-ui/toggle-toolbar-button.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { withRef } from '@udecode/cn'; -import { - useToggleToolbarButton, - useToggleToolbarButtonState, -} from '@udecode/plate-toggle'; - -import { Icons } from '@/components/icons'; - -import { ToolbarButton } from './toolbar'; - -export const IndentListToolbarButton = withRef( - (rest, ref) => { - const state = useToggleToolbarButtonState(); - const { props } = useToggleToolbarButton(state); - - return ( - - - - ); - } -); diff --git a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts index 03562ff2a6..a6ca6d2051 100644 --- a/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts +++ b/templates/plate-playground-template/src/lib/plate/autoformatBlocks.ts @@ -14,7 +14,6 @@ import { ELEMENT_H6, } from '@udecode/plate-heading'; import { ELEMENT_HR } from '@udecode/plate-horizontal-rule'; -import { ELEMENT_TOGGLE, openNextToggles } from '@udecode/plate-toggle'; import { preFormat } from '@/lib/plate/autoformatUtils'; @@ -74,12 +73,6 @@ export const autoformatBlocks: AutoformatRule[] = [ }); }, }, - { - mode: 'block', - type: ELEMENT_TOGGLE, - match: '|> ', - preFormat: openNextToggles, - }, { mode: 'block', type: ELEMENT_HR, diff --git a/templates/plate-playground-template/src/lib/plate/plate-plugins.ts b/templates/plate-playground-template/src/lib/plate/plate-plugins.ts index ce3e112bfc..ee8eebcdbb 100644 --- a/templates/plate-playground-template/src/lib/plate/plate-plugins.ts +++ b/templates/plate-playground-template/src/lib/plate/plate-plugins.ts @@ -120,7 +120,6 @@ import { ELEMENT_TH, ELEMENT_TR, } from '@udecode/plate-table'; -import { createTogglePlugin, ELEMENT_TOGGLE } from '@udecode/plate-toggle'; import { createTrailingBlockPlugin } from '@udecode/plate-trailing-block'; import { autoformatPlugin } from '@/lib/plate/autoformatPlugin'; @@ -153,7 +152,6 @@ import { import { TableElement } from '@/components/plate-ui/table-element'; import { TableRowElement } from '@/components/plate-ui/table-row-element'; import { TodoListElement } from '@/components/plate-ui/todo-list-element'; -import { ToggleElement } from '@/components/plate-ui/toggle-element'; import { withDraggables } from '@/components/plate-ui/with-draggables'; import { TabbableElement } from '@/components/tabbable-element'; @@ -187,7 +185,6 @@ export const plugins = createPlugins( createMentionPlugin(), createTablePlugin(), createTodoListPlugin(), - createTogglePlugin(), createExcalidrawPlugin(), // Marks @@ -222,7 +219,6 @@ export const plugins = createPlugins( ELEMENT_H3, ELEMENT_BLOCKQUOTE, ELEMENT_CODE_BLOCK, - ELEMENT_TOGGLE, ], }, }, @@ -406,7 +402,6 @@ export const plugins = createPlugins( [ELEMENT_TD]: TableCellElement, [ELEMENT_TH]: TableCellHeaderElement, [ELEMENT_TODO_LI]: TodoListElement, - [ELEMENT_TOGGLE]: ToggleElement, [ELEMENT_TR]: TableRowElement, [ELEMENT_EXCALIDRAW]: ExcalidrawElement, [MARK_BOLD]: withProps(PlateLeaf, { as: 'strong' }), diff --git a/templates/plate-playground-template/src/lib/plate/plate-types.ts b/templates/plate-playground-template/src/lib/plate/plate-types.ts index 0cdb2eafcc..84b7f8580c 100644 --- a/templates/plate-playground-template/src/lib/plate/plate-types.ts +++ b/templates/plate-playground-template/src/lib/plate/plate-types.ts @@ -69,10 +69,6 @@ import { ELEMENT_UL, TTodoListItemElement, } from '@udecode/plate-list'; -import { - ELEMENT_TOGGLE, - TToggleElement, -} from '@udecode/plate-toggle'; import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED, @@ -266,13 +262,6 @@ export interface MyTodoListElement children: MyInlineChildren; } -export interface MyToggleElement - extends TToggleElement, - MyBlockElement { - type: typeof ELEMENT_TOGGLE; - children: MyInlineChildren; -} - export interface MyImageElement extends TImageElement, MyBlockElement { type: typeof ELEMENT_IMAGE; children: [EmptyText]; @@ -316,7 +305,6 @@ export type MyRootBlock = | MyBulletedListElement | MyNumberedListElement | MyTodoListElement - | MyToggleElement | MyImageElement | MyMediaEmbedElement | MyHrElement From 498f3f1aae362a292191f77eef47a823562bba46 Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 11:27:10 +0100 Subject: [PATCH 07/16] Do not lose focus when clicking on toggle button --- packages/toggle/src/hooks/useToggleButton.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/toggle/src/hooks/useToggleButton.ts b/packages/toggle/src/hooks/useToggleButton.ts index b7ebdc23de..d684e7fd66 100644 --- a/packages/toggle/src/hooks/useToggleButton.ts +++ b/packages/toggle/src/hooks/useToggleButton.ts @@ -17,6 +17,9 @@ export const useToggleButton = ( return { ...state, buttonProps: { + onMouseDown: (e: React.MouseEvent) => { + e.preventDefault(); + }, onClick: (e: React.MouseEvent) => { e.preventDefault(); toggleIds(editor, [state.toggleId]); From 0560c257a2d072711e136dee01e154ed01f6fb6e Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 16:23:08 +0100 Subject: [PATCH 08/16] Move back to injecting WithToggle above each component. Need to avoid re-rendering... --- .../toggle/src/RenderAboveEditableToggle.tsx | 17 --------- packages/toggle/src/ToggleStyle.tsx | 37 ------------------- packages/toggle/src/createTogglePlugin.ts | 8 ++-- packages/toggle/src/injectNodeIdClassName.ts | 11 ------ packages/toggle/src/injectToggle.tsx | 29 +++++++++++++++ packages/toggle/src/store.ts | 10 +++++ 6 files changed, 43 insertions(+), 69 deletions(-) delete mode 100644 packages/toggle/src/RenderAboveEditableToggle.tsx delete mode 100644 packages/toggle/src/ToggleStyle.tsx delete mode 100644 packages/toggle/src/injectNodeIdClassName.ts create mode 100644 packages/toggle/src/injectToggle.tsx diff --git a/packages/toggle/src/RenderAboveEditableToggle.tsx b/packages/toggle/src/RenderAboveEditableToggle.tsx deleted file mode 100644 index f00edf492d..0000000000 --- a/packages/toggle/src/RenderAboveEditableToggle.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import { ToggleControllerProvider } from './store'; -import { ToggleStyle } from './ToggleStyle'; - -export const RenderAboveEditableToggle = ({ - children, -}: { - children: React.ReactNode; -}) => { - return ( - - - {children} - - ); -}; diff --git a/packages/toggle/src/ToggleStyle.tsx b/packages/toggle/src/ToggleStyle.tsx deleted file mode 100644 index eb03a389a3..0000000000 --- a/packages/toggle/src/ToggleStyle.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useMemo } from 'react'; -import { useEditorRef } from '@udecode/plate-common'; -import { TIndentElement } from '@udecode/plate-indent'; - -import { idToClassName } from './injectNodeIdClassName'; -import { findElementIdsHiddenInToggle } from './queries'; -import { useToggleControllerStore } from './store'; - -// TODO restrict to the editor -export const ToggleStyle = () => { - const { children } = useEditorRef(); - const [openIds] = useToggleControllerStore().use.openIds(); - const css = useMemo( - () => hideElementsInToggleCSS(openIds, children as TIndentElement[]), - [openIds, children] - ); - - return ; -}; - -const hideElementsInToggleCSS = ( - openIds: Set, - elements: TIndentElement[] -) => { - const elementIdsToHide = findElementIdsHiddenInToggle(openIds, elements); - return `${elementIdsToSelector(elementIdsToHide)} ${hiddenElementsStyle}`; -}; - -const elementIdsToSelector = (ids: string[]) => { - return ids.map((id) => `.${idToClassName(id)}`).join(','); -}; - -const hiddenElementsStyle = `{ - visibility: hidden; - height: 0; - margin: 0; -}`; diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts index e761fc505a..149e332b57 100644 --- a/packages/toggle/src/createTogglePlugin.ts +++ b/packages/toggle/src/createTogglePlugin.ts @@ -1,8 +1,8 @@ import { createPluginFactory, PlateEditor, Value } from '@udecode/plate-common'; import { useHooksToggle } from './hooks/useHooksToggle'; -import { injectNodeIdClassName } from './injectNodeIdClassName'; -import { RenderAboveEditableToggle } from './RenderAboveEditableToggle'; +import { injectToggle } from './injectToggle'; +import { ToggleControllerProvider } from './store'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; import { withToggle } from './withToggle'; @@ -13,8 +13,8 @@ export const createTogglePlugin = createPluginFactory< >({ key: ELEMENT_TOGGLE, isElement: true, + inject: { aboveComponent: injectToggle }, useHooks: useHooksToggle, - renderAboveEditable: RenderAboveEditableToggle, + renderAboveEditable: ToggleControllerProvider, withOverrides: withToggle, - inject: injectNodeIdClassName, }); diff --git a/packages/toggle/src/injectNodeIdClassName.ts b/packages/toggle/src/injectNodeIdClassName.ts deleted file mode 100644 index 55584d148c..0000000000 --- a/packages/toggle/src/injectNodeIdClassName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PlatePlugin } from '@udecode/plate-common'; - -export const injectNodeIdClassName: PlatePlugin['inject'] = { - props: { - query: () => true, - transformClassName: ({ element }) => - element ? idToClassName(element.id as string) : undefined, - }, -}; - -export const idToClassName = (id: string) => `slate-node-${id}`; diff --git a/packages/toggle/src/injectToggle.tsx b/packages/toggle/src/injectToggle.tsx new file mode 100644 index 0000000000..ebf67e8234 --- /dev/null +++ b/packages/toggle/src/injectToggle.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { InjectComponentReturnType } from '@udecode/plate-common'; + +import { useToggleControllerStore, useToggleIndex } from './store'; + +export const injectToggle = (): InjectComponentReturnType => WithToggle; + +const WithToggle: InjectComponentReturnType = ({ element, children }) => { + // Instead of using both the toggle index and the open ids atoms, + // use a single atom that is subscribed to the sole value relevant for this element: isOpen + const [openIds] = useToggleControllerStore().use.openIds(); + const toggleIndex = useToggleIndex(); + const enclosedInToggleIds = toggleIndex.get(element.id as string) || []; + const isOpen = enclosedInToggleIds.every((id) => openIds.has(id)); + + if (isOpen) return children; + + return ( +
+ {children} +
+ ); +}; diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts index 7e8ad3bd65..4ac6363203 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/store.ts @@ -3,9 +3,13 @@ import { createAtomStore, getPluginOptions, PlateEditor, + plateStore, + usePlateStore, Value, } from '@udecode/plate-common'; +import { TIndentElement } from '@udecode/plate-indent'; +import { buildToggleIndex } from './queries'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; export const { @@ -19,6 +23,12 @@ export const { { name: 'toggleController' as const } ); +const editorAtom = plateStore.atom.trackedEditor; +export const toggleIndexAtom = atom((get) => + buildToggleIndex(get(editorAtom).editor.children as TIndentElement[]) +); +export const useToggleIndex = () => usePlateStore().get.atom(toggleIndexAtom); + export const someToggleClosed = < V extends Value = Value, E extends PlateEditor = PlateEditor, From 9cb431ab38c0f8a3350ca00bfe86a9d4bea42558 Mon Sep 17 00:00:00 2001 From: Liboul Date: Tue, 30 Jan 2024 19:02:44 +0100 Subject: [PATCH 09/16] Avoid unnecessary re-renders of elements in injectToggle when updating children and unnecessary computations of the toggleIndex --- .../docs/components/toggle-element.mdx | 2 +- apps/www/content/docs/toggle.mdx | 2 +- apps/www/src/config/descriptions.ts | 2 +- packages/toggle/src/hooks/useHooksToggle.ts | 8 ++- packages/toggle/src/injectToggle.tsx | 29 ++++------ .../queries/findElementIdsHiddenInToggle.ts | 2 +- .../src/queries/getEnclosingToggleIds.ts | 48 +++-------------- .../queries/getLastEntryEnclosedInToggle.ts | 5 +- .../toggle/src/queries/isInClosedToggle.ts | 2 +- packages/toggle/src/store.ts | 53 ++++++++++++++++++- packages/toggle/src/types.ts | 4 +- 11 files changed, 86 insertions(+), 71 deletions(-) diff --git a/apps/www/content/docs/components/toggle-element.mdx b/apps/www/content/docs/components/toggle-element.mdx index 4ace3fa55c..0cca783c52 100644 --- a/apps/www/content/docs/components/toggle-element.mdx +++ b/apps/www/content/docs/components/toggle-element.mdx @@ -1,6 +1,6 @@ --- title: Toggle Element -description: Turn any block into a toggle. +description: Add toggles to your document. component: true docs: - route: /docs/toggle diff --git a/apps/www/content/docs/toggle.mdx b/apps/www/content/docs/toggle.mdx index 71dd4ae255..f6840e7ec9 100644 --- a/apps/www/content/docs/toggle.mdx +++ b/apps/www/content/docs/toggle.mdx @@ -1,6 +1,6 @@ --- title: Toggle -description: Turn any block into a toggle. +description: Add toggles to your document. docs: - route: /docs/components/toggle-toolbar-button title: Toggle Button diff --git a/apps/www/src/config/descriptions.ts b/apps/www/src/config/descriptions.ts index 6852379bc8..2307290af0 100644 --- a/apps/www/src/config/descriptions.ts +++ b/apps/www/src/config/descriptions.ts @@ -55,7 +55,7 @@ export const descriptions: Record = { [ELEMENT_HR]: 'Insert horizontal lines.', [ELEMENT_IMAGE]: 'Embed images into your document.', [ELEMENT_LINK]: 'Insert and manage hyperlinks.', - [ELEMENT_TOGGLE]: 'Turn any block into a toggle.', + [ELEMENT_TOGGLE]: 'Add toggles to your document.', heading: 'Organize your document with up to 6 headings.', list: 'Organize nestable items in a bulleted or numbered list.', [ELEMENT_MEDIA_EMBED]: diff --git a/packages/toggle/src/hooks/useHooksToggle.ts b/packages/toggle/src/hooks/useHooksToggle.ts index baddfe6b61..7babb9eaac 100644 --- a/packages/toggle/src/hooks/useHooksToggle.ts +++ b/packages/toggle/src/hooks/useHooksToggle.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; -import { useToggleControllerStore } from '../store'; +import { useToggleControllerStore, useToggleIndex } from '../store'; import { ELEMENT_TOGGLE, TogglePlugin } from '../types'; export const useHooksToggle = < @@ -11,7 +11,10 @@ export const useHooksToggle = < editor: E ) => { const [openIds, setOpenIds] = useToggleControllerStore().use.openIds(); + const toggleIndex = useToggleIndex(); + // This is hacky + // TODO a JOTAI layer in plate-core instead of relying on plugin options useEffect(() => { const options = getPluginOptions( editor, @@ -19,5 +22,6 @@ export const useHooksToggle = < ); options.openIds = openIds; options.setOpenIds = setOpenIds; - }, [openIds, setOpenIds]); + options.toggleIndex = toggleIndex; + }, [openIds, setOpenIds, toggleIndex]); }; diff --git a/packages/toggle/src/injectToggle.tsx b/packages/toggle/src/injectToggle.tsx index ebf67e8234..3da0e9ee72 100644 --- a/packages/toggle/src/injectToggle.tsx +++ b/packages/toggle/src/injectToggle.tsx @@ -1,29 +1,20 @@ import React from 'react'; import { InjectComponentReturnType } from '@udecode/plate-common'; -import { useToggleControllerStore, useToggleIndex } from './store'; +import { useIsVisible } from './store'; export const injectToggle = (): InjectComponentReturnType => WithToggle; const WithToggle: InjectComponentReturnType = ({ element, children }) => { - // Instead of using both the toggle index and the open ids atoms, - // use a single atom that is subscribed to the sole value relevant for this element: isOpen - const [openIds] = useToggleControllerStore().use.openIds(); - const toggleIndex = useToggleIndex(); - const enclosedInToggleIds = toggleIndex.get(element.id as string) || []; - const isOpen = enclosedInToggleIds.every((id) => openIds.has(id)); + const isVisible = useIsVisible(element.id as string); - if (isOpen) return children; + if (isVisible) return children; - return ( -
- {children} -
- ); + return
{children}
; +}; + +const hiddenStyle: React.CSSProperties = { + visibility: 'hidden', + height: 0, + margin: 0, }; diff --git a/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts index 30eba6f3bf..bf2bcd1162 100644 --- a/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts +++ b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts @@ -1,6 +1,6 @@ import { TIndentElement } from '@udecode/plate-indent'; -import { buildToggleIndex } from './getEnclosingToggleIds'; +import { buildToggleIndex } from '../store'; export const findElementIdsHiddenInToggle = ( openToggleIds: Set, diff --git a/packages/toggle/src/queries/getEnclosingToggleIds.ts b/packages/toggle/src/queries/getEnclosingToggleIds.ts index a020ba4d9a..961dbdc787 100644 --- a/packages/toggle/src/queries/getEnclosingToggleIds.ts +++ b/packages/toggle/src/queries/getEnclosingToggleIds.ts @@ -1,43 +1,11 @@ -import { Value } from '@udecode/plate-common'; -import { KEY_INDENT } from '@udecode/plate-indent'; -import { KEY_LIST_STYLE_TYPE } from '@udecode/plate-indent-list'; -import { memoize } from 'lodash'; +import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; -import { ELEMENT_TOGGLE } from '../types'; +import { ELEMENT_TOGGLE, TogglePlugin } from '../types'; -export function getEnclosingToggleIds( - elements: Value, - elementId: string -): string[] { - return memoizedBuildToggleIndex(elements).get(elementId) || []; +export function getEnclosingToggleIds< + V extends Value = Value, + E extends PlateEditor = PlateEditor, +>(editor: E, elementId: string): string[] { + const options = getPluginOptions(editor, ELEMENT_TOGGLE); + return options.toggleIndex?.get(elementId) || []; } - -// Returns, for each child, the enclosing toggle ids -export const buildToggleIndex = (elements: Value): Map => { - const result: Map = new Map(); - let currentEnclosingToggles: [string, number][] = []; // [toggleId, indent][] - elements.forEach((element) => { - const elementIndent = (element[KEY_INDENT] as number) || 0; - // For some reason, indent lists have a min indent of 1, even though they are not indented - const elementIndentWithIndentListCorrection = - element[KEY_LIST_STYLE_TYPE] && element[KEY_INDENT] - ? elementIndent - 1 - : elementIndent; - - const enclosingToggles = currentEnclosingToggles.filter(([_, indent]) => { - return indent < elementIndentWithIndentListCorrection; - }); - currentEnclosingToggles = enclosingToggles; - result.set( - element.id as string, - enclosingToggles.map(([toggleId]) => toggleId) - ); - if (element.type === ELEMENT_TOGGLE) { - currentEnclosingToggles.push([element.id as string, elementIndent]); - } - }); - - return result; -}; - -const memoizedBuildToggleIndex = memoize(buildToggleIndex); diff --git a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts index 6a68d9def7..1a6be998c9 100644 --- a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts +++ b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts @@ -1,7 +1,7 @@ import { PlateEditor, TNodeEntry, Value } from '@udecode/plate-common'; import last from 'lodash/last'; -import { getEnclosingToggleIds } from './getEnclosingToggleIds'; +import { buildToggleIndex } from '../store'; export const getLastEntryEnclosedInToggle = < V extends Value = Value, @@ -10,10 +10,11 @@ export const getLastEntryEnclosedInToggle = < editor: E, toggleId: string ): TNodeEntry | undefined => { + const toggleIndex = buildToggleIndex(editor.children); const entriesInToggle = editor.children .map((node, index) => [node, [index]] as TNodeEntry) .filter(([node]) => { - return getEnclosingToggleIds(editor.children, node.id).includes(toggleId); + return (toggleIndex.get(node.id) || []).includes(toggleId); }); return last(entriesInToggle); }; diff --git a/packages/toggle/src/queries/isInClosedToggle.ts b/packages/toggle/src/queries/isInClosedToggle.ts index 64754a35a4..014c7f624d 100644 --- a/packages/toggle/src/queries/isInClosedToggle.ts +++ b/packages/toggle/src/queries/isInClosedToggle.ts @@ -10,6 +10,6 @@ export const isInClosedToggle = < editor: E, elementId: string ) => { - const enclosingToggleIds = getEnclosingToggleIds(editor.children, elementId); + const enclosingToggleIds = getEnclosingToggleIds(editor, elementId); return someToggleClosed(editor, enclosingToggleIds); }; diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts index 4ac6363203..ff746d421f 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/store.ts @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { atom, createAtomStore, @@ -7,9 +8,9 @@ import { usePlateStore, Value, } from '@udecode/plate-common'; -import { TIndentElement } from '@udecode/plate-indent'; +import { KEY_INDENT, TIndentElement } from '@udecode/plate-indent'; +import { KEY_LIST_STYLE_TYPE } from '@udecode/plate-indent-list'; -import { buildToggleIndex } from './queries'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; export const { @@ -23,6 +24,26 @@ export const { { name: 'toggleController' as const } ); +// Due to a limitation of jotai-x, it's not possible to derive a state from both `toggleControllerStore` and plateStore`. +// In order minimize re-renders, we subscribe to both separately, but only re-render unnecessarily when opening or closing a toggle, +// which is less frequent than changing the editor's children. +export const useIsVisible = (elementId: string) => { + const [openIds] = useToggleControllerStore().use.openIds(); + const isVisibleAtom = useMemo( + () => + atom((get) => { + const toggleIndex = get(toggleIndexAtom); + const enclosedInToggleIds = toggleIndex.get(elementId) || []; + return enclosedInToggleIds.every((enclosedId) => + openIds.has(enclosedId) + ); + }), + [elementId, openIds] + ); + + return usePlateStore().get.atom(isVisibleAtom); +}; + const editorAtom = plateStore.atom.trackedEditor; export const toggleIndexAtom = atom((get) => buildToggleIndex(get(editorAtom).editor.children as TIndentElement[]) @@ -82,3 +103,31 @@ const _toggleIds = ( }); return newOpenIds; }; + +// Returns, for each child, the enclosing toggle ids +export const buildToggleIndex = (elements: Value): Map => { + const result: Map = new Map(); + let currentEnclosingToggles: [string, number][] = []; // [toggleId, indent][] + elements.forEach((element) => { + const elementIndent = (element[KEY_INDENT] as number) || 0; + // For some reason, indent lists have a min indent of 1, even though they are not indented + const elementIndentWithIndentListCorrection = + element[KEY_LIST_STYLE_TYPE] && element[KEY_INDENT] + ? elementIndent - 1 + : elementIndent; + + const enclosingToggles = currentEnclosingToggles.filter(([_, indent]) => { + return indent < elementIndentWithIndentListCorrection; + }); + currentEnclosingToggles = enclosingToggles; + result.set( + element.id as string, + enclosingToggles.map(([toggleId]) => toggleId) + ); + if (element.type === ELEMENT_TOGGLE) { + currentEnclosingToggles.push([element.id as string, elementIndent]); + } + }); + + return result; +}; diff --git a/packages/toggle/src/types.ts b/packages/toggle/src/types.ts index 64bdb7b95e..eeb62190f7 100644 --- a/packages/toggle/src/types.ts +++ b/packages/toggle/src/types.ts @@ -1,10 +1,12 @@ import { SetStateAction } from 'react'; +import { buildToggleIndex } from './store'; export interface TogglePlugin { // Options would go here - // TODO a JOTAI layer instead of relying on plugin options + // TODO a JOTAI layer in plate-core instead of relying on plugin options openIds: Set; setOpenIds: (args_0: SetStateAction>) => void; + toggleIndex: ReturnType; } export const ELEMENT_TOGGLE = 'toggle'; From e76f9f3555f69d691c7e6b6c2e090115d5d2b3d7 Mon Sep 17 00:00:00 2001 From: Liboul Date: Wed, 31 Jan 2024 15:45:28 +0100 Subject: [PATCH 10/16] Toggle > Remove indent-list dependency, finalize documentation, add test to autoformat toggle, fix typing of plugin, lint --- apps/www/content/docs/toggle.mdx | 108 +++++++++++++++--- .../default/plate-ui/toggle-element.tsx | 23 +--- .../withAutoformat/block/list.spec.tsx | 48 +++++++- .../plate/src/__tests__/all-plugins.spec.tsx | 1 - packages/serializer-html/src/serializeHtml.ts | 1 - packages/toggle/CHANGELOG.md | 4 +- packages/toggle/README.md | 9 ++ packages/toggle/package.json | 4 +- .../toggle/src/createTogglePlugin.spec.tsx | 1 - packages/toggle/src/createTogglePlugin.ts | 8 +- packages/toggle/src/hooks/useHooksToggle.ts | 2 +- packages/toggle/src/store.ts | 24 ++-- packages/toggle/src/types.ts | 1 + packages/toggle/src/withToggle.spec.tsx | 1 - yarn.lock | 2 +- 15 files changed, 173 insertions(+), 64 deletions(-) delete mode 100644 packages/toggle/src/createTogglePlugin.spec.tsx delete mode 100644 packages/toggle/src/withToggle.spec.tsx diff --git a/apps/www/content/docs/toggle.mdx b/apps/www/content/docs/toggle.mdx index f6840e7ec9..4201b8bdbf 100644 --- a/apps/www/content/docs/toggle.mdx +++ b/apps/www/content/docs/toggle.mdx @@ -19,7 +19,6 @@ docs: ## Plugin dependencies - [Indent](/docs/indent) -- [Indent List](/docs/indent-list) - [Node id](/docs/node-id) @@ -27,7 +26,7 @@ docs: ## Installation ```bash -npm install @udecode/plate-indent @udecode/plate-indent-list @udecode/plate-node-id @udecode/plate-toggle +npm install @udecode/plate-indent @udecode/plate-node-id @udecode/plate-toggle ``` ## Usage @@ -35,9 +34,8 @@ npm install @udecode/plate-indent @udecode/plate-indent-list @udecode/plate-node ```tsx // ... import { createIndentPlugin } from '@udecode/plate-indent'; -import { createIndentListPlugin } from '@udecode/plate-indent-list'; import { createNodeIdPlugin } from '@udecode/plate-node-id'; -import { createTogglePlugin } from '@udecode/plate-toggle'; +import { createTogglePlugin, ELEMENT_TOGGLE } from '@udecode/plate-toggle'; const plugins = [ // ...otherPlugins, @@ -46,14 +44,7 @@ const plugins = [ createIndentPlugin({ inject: { props: { - validTypes: [ELEMENT_PARAGRAPH, ELEMENT_H1], - }, - }, - }), - createIndentListPlugin({ - inject: { - props: { - validTypes: [ELEMENT_PARAGRAPH, ELEMENT_H1], + validTypes: [ELEMENT_TOGGLE, ELEMENT_PARAGRAPH, ELEMENT_H1], }, }, }), @@ -66,22 +57,45 @@ const plugins = [ ### createTogglePlugin - - {/* TODO */} - - ### openNextToggles +Marks the block at the current selection as an open toggle. +Use this function right before inserting a block so that the toggle is expanded upon insertion. + The editor instance. -{/* TODO Add API DOC */} +### toggleIds + +Toggles the open state of toggles. + + + + The editor instance. + + + An array of element ids. + + + Set to true if you want to expand all toggles regardless of their current + state. Set to false if you want to collapse all toggles regardless of their + current state. + + ## API Components +### useToggleToolbarButtonState + + + + A boolean indicating whether the button is pressed or not. + + + ### useToggleToolbarButton A behavior hook for the toggle toolbar button. @@ -103,8 +117,66 @@ A behavior hook for the toggle toolbar button. the toggle of the specified node type in the editor and focuses the editor. + + A callback function to handle the mouse down event of the button. It + just prevents default so that focus is not lost when clicking on the + button. +
-{/* TODO Add components DOC */} \ No newline at end of file +### useToggleButtonState + + + + The id of the toggle element. + + + + + + The id of the toggle element. + + + A boolean indicating whether the toggle is open (expanded) or not + (collapsed). + + + +### useToggleButton + +A behavior hook for the toggle button that expands or collapses a toggle element. + + + + The id of the toggle element. + + + A boolean indicating whether the toggle is open (expanded) or not + (collapsed). + + + + + + The id of the toggle element. + + + A boolean indicating whether the toggle is open (expanded) or not + (collapsed). + + + + + A callback function to handle the click event of the button. It toggles + the open state of the toggle. + + + A callback function to handle the mouse down event of the button. It + just prevents default so that focus is not lost when clicking on the + button. + + + + diff --git a/apps/www/src/registry/default/plate-ui/toggle-element.tsx b/apps/www/src/registry/default/plate-ui/toggle-element.tsx index 1031222bad..e621e6336d 100644 --- a/apps/www/src/registry/default/plate-ui/toggle-element.tsx +++ b/apps/www/src/registry/default/plate-ui/toggle-element.tsx @@ -7,30 +7,17 @@ import { Icons } from '@/components/icons'; export const ToggleElement = withRef( ({ children, ...props }, ref) => { const element = useElement(); - const state = useToggleButtonState(element.id); + const state = useToggleButtonState(element.id as string); const { open, buttonProps } = useToggleButton(state); - // TODO use tailwind instead of inline styles return ( -
+
{open ? : } diff --git a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx index 008fb9f2d2..e2154f11a2 100644 --- a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx +++ b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx @@ -5,6 +5,7 @@ import { jsx } from '@udecode/plate-test-utils'; import { withReact } from 'slate-react'; import { autoformatPlugin } from 'www/src/lib/plate/demo/plugins/autoformatPlugin'; +import { AutoformatBlockRule } from '../../../types'; import { withAutoformat } from '../../../withAutoformat'; jsx; @@ -133,4 +134,49 @@ describe('when [x].space', () => { }); }); -// TODO Add toggle test +describe('when |>space', () => { + it('should format to a toggle', () => { + const input = ( + + + {'|>'} + + hello + + + ) as any; + + const output = ( + + hello + + ) as any; + + // See useHooksToggle.ts, we overload the plugin with a `setOpenIds` function until there's a JOTAI layer in plate-core, + // so here we need to remove the `preformat` property of the autoformat rule that uses this overload. + + const autoformatPluginRulesWitoutTogglePreformat = + autoformatPlugin.options!.rules!.map((rule) => { + const { preFormat, ...rest } = rule as AutoformatBlockRule; + if (rule.match === '|> ') return rest; + return rule; + }); + + const autoformatPluginWitoutTogglePreformat: typeof autoformatPlugin = { + ...autoformatPlugin, + options: { + ...autoformatPlugin.options, + rules: autoformatPluginRulesWitoutTogglePreformat as any, + }, + }; + + let editor = withAutoformat( + withReact(input), + mockPlugin(autoformatPluginWitoutTogglePreformat) + ); + + editor.insertText(' '); + + expect(input.children).toEqual(output.children); + }); +}); diff --git a/packages/plate/src/__tests__/all-plugins.spec.tsx b/packages/plate/src/__tests__/all-plugins.spec.tsx index 8aae31e11e..a2cf2af332 100644 --- a/packages/plate/src/__tests__/all-plugins.spec.tsx +++ b/packages/plate/src/__tests__/all-plugins.spec.tsx @@ -1,4 +1,3 @@ -// TODO add toggle import React from 'react'; import { render } from '@testing-library/react'; import { createAlignPlugin } from '@udecode/plate-alignment'; diff --git a/packages/serializer-html/src/serializeHtml.ts b/packages/serializer-html/src/serializeHtml.ts index aaf0377f08..ed733db3a7 100644 --- a/packages/serializer-html/src/serializeHtml.ts +++ b/packages/serializer-html/src/serializeHtml.ts @@ -1,4 +1,3 @@ -// TODO serialize toggle? import React from 'react'; import { EDescendant, diff --git a/packages/toggle/CHANGELOG.md b/packages/toggle/CHANGELOG.md index 8599d1d5e6..e4b0e44359 100644 --- a/packages/toggle/CHANGELOG.md +++ b/packages/toggle/CHANGELOG.md @@ -1,5 +1,5 @@ -# @udecode/plate-indent-list +# @udecode/plate-toggle ## 30.1.2 -TODO ANNOUNCEMENT \ No newline at end of file +TODO release \ No newline at end of file diff --git a/packages/toggle/README.md b/packages/toggle/README.md index 63ec597be5..af5ac56887 100644 --- a/packages/toggle/README.md +++ b/packages/toggle/README.md @@ -7,6 +7,15 @@ It's similar to the indent list plugin, in that it relies on the indent of sibli Check out [Toggle](https://platejs.org/docs/toggle). +## Ideas to improve this plugin + +1. Adding an option `initialValue` of open `toggleIds` and a callback `onChange`, for instance to store the state of open toggles in local storage and remember the state upon browser refresh. +2. Adding an option `defaultOpen`. Currently, toggles are closed on initial rendering. +3. Adding an option to specify how to get the indent value of elements, right now we are relying on this being the default `KEY_ELEMENT` from the `indent` plugin +4. An option to specify how to get the id of elements, right now we are using the default id attribute from the `node-id` plugin. +5. Adding a placeholder below the toggle, like Notion does, when the toggle is expanded without any elements below. +6. Make toggle button more accessible + ## License [MIT](../../LICENSE) diff --git a/packages/toggle/package.json b/packages/toggle/package.json index 0b31b49f5a..210e90ade9 100644 --- a/packages/toggle/package.json +++ b/packages/toggle/package.json @@ -41,8 +41,8 @@ "dependencies": { "@udecode/plate-common": "30.1.2", "@udecode/plate-indent": "30.1.2", - "@udecode/plate-indent-list": "30.1.2", - "@udecode/plate-node-id": "30.1.2" + "@udecode/plate-node-id": "30.1.2", + "lodash": "^4.17.21" }, "peerDependencies": { "react": ">=16.8.0", diff --git a/packages/toggle/src/createTogglePlugin.spec.tsx b/packages/toggle/src/createTogglePlugin.spec.tsx deleted file mode 100644 index 1cf23ab59a..0000000000 --- a/packages/toggle/src/createTogglePlugin.spec.tsx +++ /dev/null @@ -1 +0,0 @@ -// TODO TEST diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts index 149e332b57..ced371c0ea 100644 --- a/packages/toggle/src/createTogglePlugin.ts +++ b/packages/toggle/src/createTogglePlugin.ts @@ -1,4 +1,4 @@ -import { createPluginFactory, PlateEditor, Value } from '@udecode/plate-common'; +import { createPluginFactory } from '@udecode/plate-common'; import { useHooksToggle } from './hooks/useHooksToggle'; import { injectToggle } from './injectToggle'; @@ -6,11 +6,7 @@ import { ToggleControllerProvider } from './store'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; import { withToggle } from './withToggle'; -export const createTogglePlugin = createPluginFactory< - TogglePlugin, - Value, - PlateEditor ->({ +export const createTogglePlugin = createPluginFactory({ key: ELEMENT_TOGGLE, isElement: true, inject: { aboveComponent: injectToggle }, diff --git a/packages/toggle/src/hooks/useHooksToggle.ts b/packages/toggle/src/hooks/useHooksToggle.ts index 7babb9eaac..e827506006 100644 --- a/packages/toggle/src/hooks/useHooksToggle.ts +++ b/packages/toggle/src/hooks/useHooksToggle.ts @@ -23,5 +23,5 @@ export const useHooksToggle = < options.openIds = openIds; options.setOpenIds = setOpenIds; options.toggleIndex = toggleIndex; - }, [openIds, setOpenIds, toggleIndex]); + }, [editor, openIds, setOpenIds, toggleIndex]); }; diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/store.ts index ff746d421f..bcd11d06bd 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/store.ts @@ -9,10 +9,12 @@ import { Value, } from '@udecode/plate-common'; import { KEY_INDENT, TIndentElement } from '@udecode/plate-indent'; -import { KEY_LIST_STYLE_TYPE } from '@udecode/plate-indent-list'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; +// Duplicate constant instead of importing from "plate-indent-list" to avoid a dependency. +const KEY_LIST_STYLE_TYPE = 'listStyleType'; + export const { toggleControllerStore, ToggleControllerProvider, @@ -79,26 +81,26 @@ export const toggleIds = < E extends PlateEditor = PlateEditor, >( editor: E, - toggleIds: string[], - force: boolean | undefined = undefined + ids: string[], + force: boolean | null = null ): void => { const options = getPluginOptions(editor, ELEMENT_TOGGLE); - options.setOpenIds((openIds) => _toggleIds(openIds, toggleIds, force)); + options.setOpenIds((openIds) => _toggleIds(openIds, ids, force)); }; const _toggleIds = ( openIds: Set, - toggleIds: string[], - force: boolean | undefined = undefined + ids: string[], + force: boolean | null = null ) => { const newOpenIds = new Set(openIds.values()); - toggleIds.forEach((toggleId) => { - const isCurrentlyOpen = openIds.has(toggleId); - const newIsOpen = force === undefined ? !isCurrentlyOpen : force; + ids.forEach((id) => { + const isCurrentlyOpen = openIds.has(id); + const newIsOpen = force === null ? !isCurrentlyOpen : force; if (newIsOpen) { - newOpenIds.add(toggleId); + newOpenIds.add(id); } else { - newOpenIds.delete(toggleId); + newOpenIds.delete(id); } }); return newOpenIds; diff --git a/packages/toggle/src/types.ts b/packages/toggle/src/types.ts index eeb62190f7..9f40908213 100644 --- a/packages/toggle/src/types.ts +++ b/packages/toggle/src/types.ts @@ -1,4 +1,5 @@ import { SetStateAction } from 'react'; + import { buildToggleIndex } from './store'; export interface TogglePlugin { diff --git a/packages/toggle/src/withToggle.spec.tsx b/packages/toggle/src/withToggle.spec.tsx deleted file mode 100644 index 1cf23ab59a..0000000000 --- a/packages/toggle/src/withToggle.spec.tsx +++ /dev/null @@ -1 +0,0 @@ -// TODO TEST diff --git a/yarn.lock b/yarn.lock index d0dae47e86..03528055de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6946,8 +6946,8 @@ __metadata: dependencies: "@udecode/plate-common": "npm:30.1.2" "@udecode/plate-indent": "npm:30.1.2" - "@udecode/plate-indent-list": "npm:30.1.2" "@udecode/plate-node-id": "npm:30.1.2" + lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" From b690ce589028ae7c52ea6dc2b4cfe5bbf9f964e4 Mon Sep 17 00:00:00 2001 From: Liboul Date: Wed, 31 Jan 2024 16:24:04 +0100 Subject: [PATCH 11/16] Change autoformat toggle from |> to + --- apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts | 2 +- .../src/__tests__/withAutoformat/block/list.spec.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts index ba0ea0c29c..da88e0f0e5 100644 --- a/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts +++ b/apps/www/src/lib/plate/demo/plugins/autoformatBlocks.ts @@ -77,7 +77,7 @@ export const autoformatBlocks: AutoformatRule[] = [ { mode: 'block', type: ELEMENT_TOGGLE, - match: '|> ', + match: '+ ', preFormat: openNextToggles, }, { diff --git a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx index e2154f11a2..884b31a45a 100644 --- a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx +++ b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx @@ -134,12 +134,12 @@ describe('when [x].space', () => { }); }); -describe('when |>space', () => { +describe('when +space', () => { it('should format to a toggle', () => { const input = ( - {'|>'} + + hello @@ -158,7 +158,7 @@ describe('when |>space', () => { const autoformatPluginRulesWitoutTogglePreformat = autoformatPlugin.options!.rules!.map((rule) => { const { preFormat, ...rest } = rule as AutoformatBlockRule; - if (rule.match === '|> ') return rest; + if (rule.match === '+ ') return rest; return rule; }); From 636bad8763f6230672ad63d3bd584ca8dff9e8e7 Mon Sep 17 00:00:00 2001 From: Liboul Date: Wed, 31 Jan 2024 16:30:24 +0100 Subject: [PATCH 12/16] Add TODO in toggle plugin [ci skip] --- packages/toggle/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toggle/README.md b/packages/toggle/README.md index af5ac56887..b7208ebeaf 100644 --- a/packages/toggle/README.md +++ b/packages/toggle/README.md @@ -15,6 +15,7 @@ Check out [Toggle](https://platejs.org/docs/toggle). 4. An option to specify how to get the id of elements, right now we are using the default id attribute from the `node-id` plugin. 5. Adding a placeholder below the toggle, like Notion does, when the toggle is expanded without any elements below. 6. Make toggle button more accessible +7. When indenting an element right of a closed toggle, it becomes hidden. This makes sense, but a nicer UI would be to open the toggle in that case, like Notion does. ## License From 56af1a74b26d6de7d057fee15ee8cdd1d83ab484 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 31 Jan 2024 16:31:41 +0100 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=92=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../www/content/docs/components/changelog.mdx | 5 +++++ apps/www/public/registry/index.json | 2 +- .../styles/default/toggle-element.json | 14 +++++++++++++ .../styles/default/toggle-toolbar-button.json | 2 +- apps/www/src/__registry__/index.tsx | 2 +- apps/www/src/config/customizer-plugins.ts | 2 +- .../src/lib/plate/demo/values/toggleValue.tsx | 20 +++++-------------- .../default/plate-ui/toggle-element.tsx | 4 +--- .../plate-ui/toggle-toolbar-button.tsx | 2 +- apps/www/src/types/plate-types.ts | 9 ++------- packages/toggle/src/createTogglePlugin.ts | 2 +- packages/toggle/src/hooks/useHooksToggle.ts | 5 ++++- packages/toggle/src/hooks/useToggleButton.ts | 5 ++++- packages/toggle/src/index.ts | 2 +- packages/toggle/src/injectToggle.tsx | 2 +- .../queries/findElementIdsHiddenInToggle.ts | 2 +- .../queries/getLastEntryEnclosedInToggle.ts | 2 +- .../toggle/src/queries/isInClosedToggle.ts | 2 +- .../{store.ts => toggle-controller-store.ts} | 6 +++--- .../toggle/src/transforms/openNextToggles.ts | 2 +- packages/toggle/src/types.ts | 8 ++++---- packages/toggle/src/withToggle.ts | 2 +- 22 files changed, 55 insertions(+), 47 deletions(-) create mode 100644 apps/www/public/registry/styles/default/toggle-element.json rename packages/toggle/src/{store.ts => toggle-controller-store.ts} (96%) diff --git a/apps/www/content/docs/components/changelog.mdx b/apps/www/content/docs/components/changelog.mdx index 83d9391378..29cc8c1b83 100644 --- a/apps/www/content/docs/components/changelog.mdx +++ b/apps/www/content/docs/components/changelog.mdx @@ -10,6 +10,11 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver ## January 2024 #7 +### January 31 #7.5 + +- add `toggle-element` +- add `toggle-toolbar-button` + ### January 11 #7.4 - add support for custom ui dir in `components.json` diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 51a185306b..79bca50727 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -824,7 +824,7 @@ ], "registryDependencies": [], "files": [ - "plate-ui/toggle-element.tsx" + "plate-ui/link-element.tsx" ], "type": "components:plate-ui" }, diff --git a/apps/www/public/registry/styles/default/toggle-element.json b/apps/www/public/registry/styles/default/toggle-element.json new file mode 100644 index 0000000000..7d38569de4 --- /dev/null +++ b/apps/www/public/registry/styles/default/toggle-element.json @@ -0,0 +1,14 @@ +{ + "name": "toggle-element", + "dependencies": [ + "@udecode/plate-toggle" + ], + "registryDependencies": [], + "files": [ + { + "name": "link-element.tsx", + "content": "import React from 'react';\nimport { cn, withRef } from '@udecode/cn';\nimport { PlateElement, useElement } from '@udecode/plate-common';\nimport { TLinkElement, useLink } from '@udecode/plate-link';\n\nexport const LinkElement = withRef(\n ({ className, children, ...props }, ref) => {\n const element = useElement();\n const { props: linkProps } = useLink({ element });\n\n return (\n \n {children}\n \n );\n }\n);\n" + } + ], + "type": "components:plate-ui" +} \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/toggle-toolbar-button.json b/apps/www/public/registry/styles/default/toggle-toolbar-button.json index 61bc920d73..1aa2802041 100644 --- a/apps/www/public/registry/styles/default/toggle-toolbar-button.json +++ b/apps/www/public/registry/styles/default/toggle-toolbar-button.json @@ -9,7 +9,7 @@ "files": [ { "name": "toggle-toolbar-button.tsx", - "content": "import React from 'react';\nimport { withRef } from '@udecode/cn';\nimport {\n useToggleToolbarButton,\n useToggleToolbarButtonState,\n} from '@udecode/plate-toggle';\n\nimport { Icons } from '@/components/icons';\n\nimport { ToolbarButton } from './toolbar';\n\nexport const ToggleToolbarButton = withRef((rest, ref) => {\n const state = useToggleToolbarButtonState();\n const { props } = useToggleToolbarButton(state);\n\n return (\n \n \n \n );\n});\n" + "content": "import React from 'react';\nimport { withRef } from '@udecode/cn';\nimport {\n useToggleToolbarButton,\n useToggleToolbarButtonState,\n} from '@udecode/plate-toggle';\n\nimport { Icons } from '@/components/icons';\n\nimport { ToolbarButton } from './toolbar';\n\nexport const ToggleToolbarButton = withRef(\n (rest, ref) => {\n const state = useToggleToolbarButtonState();\n const { props } = useToggleToolbarButton(state);\n\n return (\n \n \n \n );\n }\n);\n" } ], "type": "components:plate-ui" diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx index 28074e0074..54b324d8b8 100644 --- a/apps/www/src/__registry__/index.tsx +++ b/apps/www/src/__registry__/index.tsx @@ -645,7 +645,7 @@ export const Index: Record = { name: 'toggle-element', type: 'components:plate-ui', registryDependencies: [], - files: ['registry/default/plate-ui/toggle-element.tsx'], + files: ['registry/default/plate-ui/link-element.tsx'], component: React.lazy(() => import('@/registry/default/plate-ui/toggle-element')), }, 'toggle-toolbar-button': { diff --git a/apps/www/src/config/customizer-plugins.ts b/apps/www/src/config/customizer-plugins.ts index f4bb10b592..51d1ba77ca 100644 --- a/apps/www/src/config/customizer-plugins.ts +++ b/apps/www/src/config/customizer-plugins.ts @@ -19,7 +19,6 @@ import { fontValue } from '@/plate/demo/values/fontValue'; import { highlightValue } from '@/plate/demo/values/highlightValue'; import { horizontalRuleValue } from '@/plate/demo/values/horizontalRuleValue'; import { indentListValue } from '@/plate/demo/values/indentListValue'; -import { toggleValue } from '@/plate/demo/values/toggleValue'; import { indentValue } from '@/plate/demo/values/indentValue'; import { kbdValue } from '@/plate/demo/values/kbdValue'; import { lineHeightValue } from '@/plate/demo/values/lineHeightValue'; @@ -32,6 +31,7 @@ import { singleLineValue } from '@/plate/demo/values/singleLineValue'; import { softBreakValue } from '@/plate/demo/values/softBreakValue'; import { tabbableValue } from '@/plate/demo/values/tabbableValue'; import { tableValue } from '@/plate/demo/values/tableValue'; +import { toggleValue } from '@/plate/demo/values/toggleValue'; import { KEY_ALIGN } from '@udecode/plate-alignment'; import { KEY_AUTOFORMAT } from '@udecode/plate-autoformat'; import { diff --git a/apps/www/src/lib/plate/demo/values/toggleValue.tsx b/apps/www/src/lib/plate/demo/values/toggleValue.tsx index b46eba2ce7..17e73b5380 100644 --- a/apps/www/src/lib/plate/demo/values/toggleValue.tsx +++ b/apps/www/src/lib/plate/demo/values/toggleValue.tsx @@ -7,23 +7,13 @@ jsx; export const toggleValue: any = ( Toggle - - Create toggles with multiple levels of indentation - - - Level 1 toggle - - - Inside level 1 toggle - + Create toggles with multiple levels of indentation + Level 1 toggle + Inside level 1 toggle Level 2 toggle - - Inside level 2 toggle - - - After toggles - + Inside level 2 toggle + After toggles ); diff --git a/apps/www/src/registry/default/plate-ui/toggle-element.tsx b/apps/www/src/registry/default/plate-ui/toggle-element.tsx index e621e6336d..bca433746f 100644 --- a/apps/www/src/registry/default/plate-ui/toggle-element.tsx +++ b/apps/www/src/registry/default/plate-ui/toggle-element.tsx @@ -15,9 +15,7 @@ export const ToggleElement = withRef(
{open ? : } diff --git a/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx index 1afa48f2ab..1403e97a6e 100644 --- a/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx +++ b/apps/www/src/registry/default/plate-ui/toggle-toolbar-button.tsx @@ -15,7 +15,7 @@ export const ToggleToolbarButton = withRef( const { props } = useToggleToolbarButton(state); return ( - + ); diff --git a/apps/www/src/types/plate-types.ts b/apps/www/src/types/plate-types.ts index d3ff4f9ae6..18c1a5c155 100644 --- a/apps/www/src/types/plate-types.ts +++ b/apps/www/src/types/plate-types.ts @@ -68,10 +68,6 @@ import { ELEMENT_UL, TTodoListItemElement, } from '@udecode/plate-list'; -import { - ELEMENT_TOGGLE, - TToggleElement, -} from '@udecode/plate-toggle'; import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED, @@ -91,6 +87,7 @@ import { ELEMENT_TR, TTableElement, } from '@udecode/plate-table'; +import { ELEMENT_TOGGLE, TToggleElement } from '@udecode/plate-toggle'; import { TText } from '@udecode/slate'; /** @@ -266,9 +263,7 @@ export interface MyTodoListElement children: MyInlineChildren; } -export interface MyToggleElement - extends TToggleElement, - MyBlockElement { +export interface MyToggleElement extends TToggleElement, MyBlockElement { type: typeof ELEMENT_TOGGLE; children: MyInlineChildren; } diff --git a/packages/toggle/src/createTogglePlugin.ts b/packages/toggle/src/createTogglePlugin.ts index ced371c0ea..9fc2a10cb4 100644 --- a/packages/toggle/src/createTogglePlugin.ts +++ b/packages/toggle/src/createTogglePlugin.ts @@ -2,7 +2,7 @@ import { createPluginFactory } from '@udecode/plate-common'; import { useHooksToggle } from './hooks/useHooksToggle'; import { injectToggle } from './injectToggle'; -import { ToggleControllerProvider } from './store'; +import { ToggleControllerProvider } from './toggle-controller-store'; import { ELEMENT_TOGGLE, TogglePlugin } from './types'; import { withToggle } from './withToggle'; diff --git a/packages/toggle/src/hooks/useHooksToggle.ts b/packages/toggle/src/hooks/useHooksToggle.ts index e827506006..67ac87fa7f 100644 --- a/packages/toggle/src/hooks/useHooksToggle.ts +++ b/packages/toggle/src/hooks/useHooksToggle.ts @@ -1,7 +1,10 @@ import { useEffect } from 'react'; import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; -import { useToggleControllerStore, useToggleIndex } from '../store'; +import { + useToggleControllerStore, + useToggleIndex, +} from '../toggle-controller-store'; import { ELEMENT_TOGGLE, TogglePlugin } from '../types'; export const useHooksToggle = < diff --git a/packages/toggle/src/hooks/useToggleButton.ts b/packages/toggle/src/hooks/useToggleButton.ts index d684e7fd66..af7b04ef93 100644 --- a/packages/toggle/src/hooks/useToggleButton.ts +++ b/packages/toggle/src/hooks/useToggleButton.ts @@ -1,6 +1,9 @@ import { useEditorRef } from '@udecode/plate-common'; -import { toggleIds, useToggleControllerStore } from '../store'; +import { + toggleIds, + useToggleControllerStore, +} from '../toggle-controller-store'; export const useToggleButtonState = (toggleId: string) => { const [openIds] = useToggleControllerStore().use.openIds(); diff --git a/packages/toggle/src/index.ts b/packages/toggle/src/index.ts index 5523543a35..0ee44da450 100644 --- a/packages/toggle/src/index.ts +++ b/packages/toggle/src/index.ts @@ -8,4 +8,4 @@ export * from './withToggle'; export * from './hooks/index'; export * from './queries/index'; export * from './transforms/index'; -export * from './store'; +export * from './toggle-controller-store'; diff --git a/packages/toggle/src/injectToggle.tsx b/packages/toggle/src/injectToggle.tsx index 3da0e9ee72..5eebf2d73f 100644 --- a/packages/toggle/src/injectToggle.tsx +++ b/packages/toggle/src/injectToggle.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { InjectComponentReturnType } from '@udecode/plate-common'; -import { useIsVisible } from './store'; +import { useIsVisible } from './toggle-controller-store'; export const injectToggle = (): InjectComponentReturnType => WithToggle; diff --git a/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts index bf2bcd1162..cb855b3a48 100644 --- a/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts +++ b/packages/toggle/src/queries/findElementIdsHiddenInToggle.ts @@ -1,6 +1,6 @@ import { TIndentElement } from '@udecode/plate-indent'; -import { buildToggleIndex } from '../store'; +import { buildToggleIndex } from '../toggle-controller-store'; export const findElementIdsHiddenInToggle = ( openToggleIds: Set, diff --git a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts index 1a6be998c9..373262da2b 100644 --- a/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts +++ b/packages/toggle/src/queries/getLastEntryEnclosedInToggle.ts @@ -1,7 +1,7 @@ import { PlateEditor, TNodeEntry, Value } from '@udecode/plate-common'; import last from 'lodash/last'; -import { buildToggleIndex } from '../store'; +import { buildToggleIndex } from '../toggle-controller-store'; export const getLastEntryEnclosedInToggle = < V extends Value = Value, diff --git a/packages/toggle/src/queries/isInClosedToggle.ts b/packages/toggle/src/queries/isInClosedToggle.ts index 014c7f624d..9248655551 100644 --- a/packages/toggle/src/queries/isInClosedToggle.ts +++ b/packages/toggle/src/queries/isInClosedToggle.ts @@ -1,6 +1,6 @@ import { PlateEditor, Value } from '@udecode/plate-common'; -import { someToggleClosed } from '../store'; +import { someToggleClosed } from '../toggle-controller-store'; import { getEnclosingToggleIds } from './getEnclosingToggleIds'; export const isInClosedToggle = < diff --git a/packages/toggle/src/store.ts b/packages/toggle/src/toggle-controller-store.ts similarity index 96% rename from packages/toggle/src/store.ts rename to packages/toggle/src/toggle-controller-store.ts index bcd11d06bd..a0fa633829 100644 --- a/packages/toggle/src/store.ts +++ b/packages/toggle/src/toggle-controller-store.ts @@ -60,7 +60,7 @@ export const someToggleClosed = < toggleIds: string[] ): boolean => { const options = getPluginOptions(editor, ELEMENT_TOGGLE); - const openIds = options.openIds; + const openIds = options.openIds!; return toggleIds.some((id) => !openIds.has(id)); }; @@ -72,7 +72,7 @@ export const isToggleOpen = < toggleId: string ): boolean => { const options = getPluginOptions(editor, ELEMENT_TOGGLE); - const openIds = options.openIds; + const openIds = options.openIds!; return openIds.has(toggleId); }; @@ -85,7 +85,7 @@ export const toggleIds = < force: boolean | null = null ): void => { const options = getPluginOptions(editor, ELEMENT_TOGGLE); - options.setOpenIds((openIds) => _toggleIds(openIds, ids, force)); + options.setOpenIds!((openIds) => _toggleIds(openIds, ids, force)); }; const _toggleIds = ( diff --git a/packages/toggle/src/transforms/openNextToggles.ts b/packages/toggle/src/transforms/openNextToggles.ts index ed233f4200..41319d7541 100644 --- a/packages/toggle/src/transforms/openNextToggles.ts +++ b/packages/toggle/src/transforms/openNextToggles.ts @@ -1,6 +1,6 @@ import { getNodeEntries, PlateEditor } from '@udecode/plate-common'; -import { toggleIds } from '../store'; +import { toggleIds } from '../toggle-controller-store'; // When creating a toggle, we open it by default. // So before inserting the toggle, we update the store to mark the id of the blocks about to be turned into toggles as open. diff --git a/packages/toggle/src/types.ts b/packages/toggle/src/types.ts index 9f40908213..13f3b8baff 100644 --- a/packages/toggle/src/types.ts +++ b/packages/toggle/src/types.ts @@ -1,13 +1,13 @@ import { SetStateAction } from 'react'; -import { buildToggleIndex } from './store'; +import { buildToggleIndex } from './toggle-controller-store'; export interface TogglePlugin { // Options would go here // TODO a JOTAI layer in plate-core instead of relying on plugin options - openIds: Set; - setOpenIds: (args_0: SetStateAction>) => void; - toggleIndex: ReturnType; + openIds?: Set; + setOpenIds?: (args_0: SetStateAction>) => void; + toggleIndex?: ReturnType; } export const ELEMENT_TOGGLE = 'toggle'; diff --git a/packages/toggle/src/withToggle.ts b/packages/toggle/src/withToggle.ts index e042cecd5f..c171949781 100644 --- a/packages/toggle/src/withToggle.ts +++ b/packages/toggle/src/withToggle.ts @@ -9,7 +9,7 @@ import { import { indent, TIndentElement } from '@udecode/plate-indent'; import { getLastEntryEnclosedInToggle, isInClosedToggle } from './queries'; -import { isToggleOpen } from './store'; +import { isToggleOpen } from './toggle-controller-store'; import { ELEMENT_TOGGLE } from './types'; export const withToggle = < From a4b05a070933582e04945d43d89f2d60cc0a4bde Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 31 Jan 2024 16:37:41 +0100 Subject: [PATCH 14/16] =?UTF-8?q?=F0=9F=92=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/www/public/registry/index.json | 2 +- apps/www/public/registry/styles/default/toggle-element.json | 4 ++-- apps/www/src/__registry__/index.tsx | 2 +- apps/www/src/registry/registry.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 79bca50727..51a185306b 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -824,7 +824,7 @@ ], "registryDependencies": [], "files": [ - "plate-ui/link-element.tsx" + "plate-ui/toggle-element.tsx" ], "type": "components:plate-ui" }, diff --git a/apps/www/public/registry/styles/default/toggle-element.json b/apps/www/public/registry/styles/default/toggle-element.json index 7d38569de4..8b8bb72435 100644 --- a/apps/www/public/registry/styles/default/toggle-element.json +++ b/apps/www/public/registry/styles/default/toggle-element.json @@ -6,8 +6,8 @@ "registryDependencies": [], "files": [ { - "name": "link-element.tsx", - "content": "import React from 'react';\nimport { cn, withRef } from '@udecode/cn';\nimport { PlateElement, useElement } from '@udecode/plate-common';\nimport { TLinkElement, useLink } from '@udecode/plate-link';\n\nexport const LinkElement = withRef(\n ({ className, children, ...props }, ref) => {\n const element = useElement();\n const { props: linkProps } = useLink({ element });\n\n return (\n \n {children}\n \n );\n }\n);\n" + "name": "toggle-element.tsx", + "content": "import { withRef } from '@udecode/cn';\nimport { PlateElement, useElement } from '@udecode/plate-common';\nimport { useToggleButton, useToggleButtonState } from '@udecode/plate-toggle';\n\nimport { Icons } from '@/components/icons';\n\nexport const ToggleElement = withRef(\n ({ children, ...props }, ref) => {\n const element = useElement();\n const state = useToggleButtonState(element.id as string);\n const { open, buttonProps } = useToggleButton(state);\n\n return (\n \n
\n \n {open ? : }\n \n {children}\n
\n
\n );\n }\n);\n" } ], "type": "components:plate-ui" diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx index 54b324d8b8..28074e0074 100644 --- a/apps/www/src/__registry__/index.tsx +++ b/apps/www/src/__registry__/index.tsx @@ -645,7 +645,7 @@ export const Index: Record = { name: 'toggle-element', type: 'components:plate-ui', registryDependencies: [], - files: ['registry/default/plate-ui/link-element.tsx'], + files: ['registry/default/plate-ui/toggle-element.tsx'], component: React.lazy(() => import('@/registry/default/plate-ui/toggle-element')), }, 'toggle-toolbar-button': { diff --git a/apps/www/src/registry/registry.ts b/apps/www/src/registry/registry.ts index d7af1faf60..8464b42ba5 100644 --- a/apps/www/src/registry/registry.ts +++ b/apps/www/src/registry/registry.ts @@ -526,7 +526,7 @@ const ui: Registry = [ type: 'components:plate-ui', dependencies: ['@udecode/plate-toggle'], registryDependencies: [], - files: ['plate-ui/link-element.tsx'], + files: ['plate-ui/toggle-element.tsx'], }, { name: 'toggle-toolbar-button', From ea845f5900801ff4256f474d6b4708bdf54aaaa6 Mon Sep 17 00:00:00 2001 From: Liboul Date: Wed, 31 Jan 2024 17:14:06 +0100 Subject: [PATCH 15/16] Fix lint --- .../autoformat/src/__tests__/withAutoformat/block/list.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx index 884b31a45a..c30bf8d560 100644 --- a/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx +++ b/packages/autoformat/src/__tests__/withAutoformat/block/list.spec.tsx @@ -170,7 +170,7 @@ describe('when +space', () => { }, }; - let editor = withAutoformat( + const editor = withAutoformat( withReact(input), mockPlugin(autoformatPluginWitoutTogglePreformat) ); From 0d8480924034afbe0e59b49afa2f48c1bfff7047 Mon Sep 17 00:00:00 2001 From: Ziad Beyens Date: Wed, 31 Jan 2024 18:07:07 +0100 Subject: [PATCH 16/16] Create dull-comics-fold.md --- .changeset/dull-comics-fold.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dull-comics-fold.md diff --git a/.changeset/dull-comics-fold.md b/.changeset/dull-comics-fold.md new file mode 100644 index 0000000000..5679b0fc40 --- /dev/null +++ b/.changeset/dull-comics-fold.md @@ -0,0 +1,5 @@ +--- +"@udecode/plate-toggle": minor +--- + +New plugin: toggle