From 89119cc0bff55b3cad9636b6572b9466b11e68a2 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 16 Dec 2024 18:22:20 +0100 Subject: [PATCH 1/6] lint --- apps/www/src/components/plugins-tab-content.tsx | 2 +- apps/www/src/components/setting-checkbox.tsx | 2 +- apps/www/src/components/theme-customizer.tsx | 2 +- apps/www/src/registry/default/example/editable-voids-demo.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/www/src/components/plugins-tab-content.tsx b/apps/www/src/components/plugins-tab-content.tsx index e82488677f..b873eac841 100644 --- a/apps/www/src/components/plugins-tab-content.tsx +++ b/apps/www/src/components/plugins-tab-content.tsx @@ -10,6 +10,7 @@ import { useDebounce } from '@/registry/default/hooks/use-debounce'; import { Button } from '@/registry/default/plate-ui/button'; import { Checkbox } from '@/registry/default/plate-ui/checkbox'; +import { Label } from '../registry/default/plate-ui/label'; import { categoryIds, settingsStore } from './context/settings-store'; import { Icons } from './icons'; import { SettingCheckbox } from './setting-checkbox'; @@ -20,7 +21,6 @@ import { AccordionItem, AccordionTrigger, } from './ui/accordion'; -import { Label } from '../registry/default/plate-ui/label'; export function SettingsEffect() { const checkedPluginsNext = settingsStore.use.checkedPluginsNext(); diff --git a/apps/www/src/components/setting-checkbox.tsx b/apps/www/src/components/setting-checkbox.tsx index 2e7801707c..211ef5808a 100644 --- a/apps/www/src/components/setting-checkbox.tsx +++ b/apps/www/src/components/setting-checkbox.tsx @@ -12,12 +12,12 @@ import { PopoverTrigger, } from '@/registry/default/plate-ui/popover'; +import { Label } from '../registry/default/plate-ui/label'; import { Code } from './code'; import { settingsStore } from './context/settings-store'; import { Icons } from './icons'; import { TreeIcon } from './tree-icon'; import { Badge } from './ui/badge'; -import { Label } from '../registry/default/plate-ui/label'; export function SettingCheckbox({ id, diff --git a/apps/www/src/components/theme-customizer.tsx b/apps/www/src/components/theme-customizer.tsx index a3e3230e68..489c267100 100644 --- a/apps/www/src/components/theme-customizer.tsx +++ b/apps/www/src/components/theme-customizer.tsx @@ -14,9 +14,9 @@ import { useMounted } from '@/registry/default/hooks/use-mounted'; import { Button } from '@/registry/default/plate-ui/button'; import { Separator } from '@/registry/default/plate-ui/separator'; +import { Label } from '../registry/default/plate-ui/label'; import { CopyCodeButton, getThemeCode } from './copy-code-button'; import { ThemesSwitcher } from './themes-selector-mini'; -import { Label } from '../registry/default/plate-ui/label'; import { Skeleton } from './ui/skeleton'; export function ThemeCustomizer() { diff --git a/apps/www/src/registry/default/example/editable-voids-demo.tsx b/apps/www/src/registry/default/example/editable-voids-demo.tsx index 399b1f81b2..28da7a05ca 100644 --- a/apps/www/src/registry/default/example/editable-voids-demo.tsx +++ b/apps/www/src/registry/default/example/editable-voids-demo.tsx @@ -6,13 +6,13 @@ import type { PlateRenderElementProps } from '@udecode/plate-common/react'; import { Plate, createPlatePlugin } from '@udecode/plate-common/react'; -import { Label } from '@/registry/default/plate-ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { editorPlugins } from '@/registry/default/components/editor/plugins/editor-plugins'; import { useCreateEditor } from '@/registry/default/components/editor/use-create-editor'; import { editableVoidsValue } from '@/registry/default/example/values/editable-voids-value'; import { Editor, EditorContainer } from '@/registry/default/plate-ui/editor'; import { Input } from '@/registry/default/plate-ui/input'; +import { Label } from '@/registry/default/plate-ui/label'; export const EditableVoidPlugin = createPlatePlugin({ key: 'editable-void', From 403dd2d2157807ef9f932887847657a760dd5c21 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 16 Dec 2024 18:22:37 +0100 Subject: [PATCH 2/6] lint --- config/eslint/bases/unicorn.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/config/eslint/bases/unicorn.cjs b/config/eslint/bases/unicorn.cjs index 08a3172d8a..a3c94d9b1a 100644 --- a/config/eslint/bases/unicorn.cjs +++ b/config/eslint/bases/unicorn.cjs @@ -32,6 +32,7 @@ module.exports = { 'unicorn/no-for-loop': 'off', 'unicorn/no-null': 'off', 'unicorn/no-thenable': 'off', + 'unicorn/no-useless-undefined': 'off', 'unicorn/prefer-export-from': 'off', 'unicorn/prefer-module': 'off', 'unicorn/prefer-optional-catch-binding': 'off', From 1de8bb27a81a82a5211ece212178f682546d8cee Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 16 Dec 2024 18:22:44 +0100 Subject: [PATCH 3/6] vendor --- yarn.lock | 164 +++++++++++++++++++++++++++--------------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/yarn.lock b/yarn.lock index d21c3e7b69..45c187f143 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6422,7 +6422,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6440,7 +6440,7 @@ __metadata: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6451,16 +6451,16 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-basic-elements@npm:40.2.6, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": +"@udecode/plate-basic-elements@npm:40.3.4, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": version: 0.0.0-use.local resolution: "@udecode/plate-basic-elements@workspace:packages/basic-elements" dependencies: "@udecode/plate-block-quote": "npm:40.0.0" - "@udecode/plate-code-block": "npm:40.0.0" + "@udecode/plate-code-block": "npm:40.3.4" "@udecode/plate-common": "workspace:^" "@udecode/plate-heading": "npm:40.2.6" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6477,7 +6477,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6494,7 +6494,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6511,7 +6511,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6528,7 +6528,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6546,7 +6546,7 @@ __metadata: "@udecode/plate-common": "workspace:^" react-textarea-autosize: "npm:^8.5.3" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6566,7 +6566,7 @@ __metadata: delay: "npm:5.0.0" p-defer: "npm:^4.0.1" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6577,14 +6577,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-code-block@npm:40.0.0, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": +"@udecode/plate-code-block@npm:40.3.4, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": version: 0.0.0-use.local resolution: "@udecode/plate-code-block@workspace:packages/code-block" dependencies: "@udecode/plate-common": "workspace:^" prismjs: "npm:^1.29.0" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6601,7 +6601,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6612,14 +6612,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-comments@npm:40.0.0, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": +"@udecode/plate-comments@npm:40.3.0, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": version: 0.0.0-use.local resolution: "@udecode/plate-comments@workspace:packages/comments" dependencies: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6630,17 +6630,17 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-common@npm:40.2.8, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": +"@udecode/plate-common@npm:40.3.1, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": version: 0.0.0-use.local resolution: "@udecode/plate-common@workspace:packages/common" dependencies: - "@udecode/plate-core": "npm:40.2.8" - "@udecode/plate-utils": "npm:40.2.8" + "@udecode/plate-core": "npm:40.3.1" + "@udecode/plate-utils": "npm:40.3.1" "@udecode/react-hotkeys": "npm:37.0.0" "@udecode/react-utils": "npm:40.2.8" - "@udecode/slate": "npm:39.2.1" - "@udecode/slate-react": "npm:40.2.8" - "@udecode/slate-utils": "npm:40.2.7" + "@udecode/slate": "npm:40.3.1" + "@udecode/slate-react": "npm:40.3.1" + "@udecode/slate-utils": "npm:40.3.1" "@udecode/utils": "npm:37.0.0" peerDependencies: react: ">=16.8.0" @@ -6653,15 +6653,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-core@npm:40.2.8, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core": +"@udecode/plate-core@npm:40.3.1, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core": version: 0.0.0-use.local resolution: "@udecode/plate-core@workspace:packages/core" dependencies: "@udecode/react-hotkeys": "npm:37.0.0" "@udecode/react-utils": "npm:40.2.8" - "@udecode/slate": "npm:39.2.1" - "@udecode/slate-react": "npm:40.2.8" - "@udecode/slate-utils": "npm:40.2.7" + "@udecode/slate": "npm:40.3.1" + "@udecode/slate-react": "npm:40.3.1" + "@udecode/slate-utils": "npm:40.3.1" "@udecode/utils": "npm:37.0.0" clsx: "npm:^2.1.1" is-hotkey: "npm:^0.2.0" @@ -6694,7 +6694,7 @@ __metadata: "@udecode/plate-table": "npm:40.0.0" papaparse: "npm:^5.4.1" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6711,7 +6711,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6728,7 +6728,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.94.0" @@ -6746,7 +6746,7 @@ __metadata: diff-match-patch-ts: "npm:^0.6.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6765,7 +6765,7 @@ __metadata: lodash: "npm:^4.17.21" raf: "npm:^3.4.1" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dnd: ">=14.0.0" react-dnd-html5-backend: ">=14.0.0" @@ -6790,7 +6790,7 @@ __metadata: "@udecode/plate-table": "npm:40.0.0" validator: "npm:^13.12.0" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6809,7 +6809,7 @@ __metadata: "@udecode/plate-combobox": "npm:40.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6827,7 +6827,7 @@ __metadata: "@excalidraw/excalidraw": "npm:0.16.4" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6844,7 +6844,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6863,7 +6863,7 @@ __metadata: "@floating-ui/react": "npm:^0.26.23" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6881,7 +6881,7 @@ __metadata: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6898,7 +6898,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6915,7 +6915,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6932,7 +6932,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6951,7 +6951,7 @@ __metadata: "@udecode/plate-common": "workspace:^" html-entities: "npm:^2.5.2" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6971,7 +6971,7 @@ __metadata: "@udecode/plate-list": "npm:40.0.0" clsx: "npm:^2.1.1" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -6988,7 +6988,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7006,7 +7006,7 @@ __metadata: "@udecode/plate-common": "workspace:^" juice: "npm:^8.1.0" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7023,7 +7023,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7040,7 +7040,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7057,7 +7057,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7076,7 +7076,7 @@ __metadata: "@udecode/plate-floating": "npm:40.0.0" "@udecode/plate-normalizers": "npm:40.0.0" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7095,7 +7095,7 @@ __metadata: "@udecode/plate-reset-node": "npm:40.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7116,7 +7116,7 @@ __metadata: remark-parse: "npm:^11.0.0" unified: "npm:^11.0.5" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7135,7 +7135,7 @@ __metadata: "@udecode/plate-common": "workspace:^" katex: "npm:0.16.11" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7153,7 +7153,7 @@ __metadata: "@udecode/plate-common": "workspace:^" js-video-url-parser: "npm:^0.5.1" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7171,7 +7171,7 @@ __metadata: "@udecode/plate-combobox": "npm:40.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7189,7 +7189,7 @@ __metadata: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7207,7 +7207,7 @@ __metadata: "@udecode/plate-common": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7225,7 +7225,7 @@ __metadata: "@udecode/plate-common": "workspace:^" peerDependencies: "@playwright/test": ">=1.42.1" - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7242,7 +7242,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7259,7 +7259,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7276,7 +7276,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7294,7 +7294,7 @@ __metadata: "@udecode/plate-common": "workspace:^" copy-to-clipboard: "npm:^3.3.3" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7312,7 +7312,7 @@ __metadata: "@udecode/plate-combobox": "npm:40.0.0" "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7331,7 +7331,7 @@ __metadata: "@udecode/plate-diff": "npm:40.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7349,7 +7349,7 @@ __metadata: "@udecode/plate-common": "workspace:^" tabbable: "npm:^6.2.0" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7368,7 +7368,7 @@ __metadata: "@udecode/plate-resizable": "npm:40.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7384,7 +7384,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7413,7 +7413,7 @@ __metadata: "@udecode/plate-node-id": "npm:40.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7430,7 +7430,7 @@ __metadata: dependencies: "@udecode/plate-common": "workspace:^" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7441,15 +7441,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-utils@npm:40.2.8, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils": +"@udecode/plate-utils@npm:40.3.1, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils": version: 0.0.0-use.local resolution: "@udecode/plate-utils@workspace:packages/plate-utils" dependencies: - "@udecode/plate-core": "npm:40.2.8" + "@udecode/plate-core": "npm:40.3.1" "@udecode/react-utils": "npm:40.2.8" - "@udecode/slate": "npm:39.2.1" - "@udecode/slate-react": "npm:40.2.8" - "@udecode/slate-utils": "npm:40.2.7" + "@udecode/slate": "npm:40.3.1" + "@udecode/slate-react": "npm:40.3.1" + "@udecode/slate-utils": "npm:40.3.1" "@udecode/utils": "npm:37.0.0" clsx: "npm:^2.1.1" lodash: "npm:^4.17.21" @@ -7472,7 +7472,7 @@ __metadata: "@udecode/plate-common": "workspace:^" yjs: "npm:^13.6.19" peerDependencies: - "@udecode/plate-common": ">=40.2.8" + "@udecode/plate-common": ">=40.3.1" react: ">=16.8.0" react-dom: ">=16.8.0" slate: ">=0.112.0" @@ -7489,14 +7489,14 @@ __metadata: dependencies: "@udecode/plate-alignment": "npm:40.0.0" "@udecode/plate-autoformat": "npm:40.0.0" - "@udecode/plate-basic-elements": "npm:40.2.6" + "@udecode/plate-basic-elements": "npm:40.3.4" "@udecode/plate-basic-marks": "npm:40.0.0" "@udecode/plate-block-quote": "npm:40.0.0" "@udecode/plate-break": "npm:40.0.0" - "@udecode/plate-code-block": "npm:40.0.0" + "@udecode/plate-code-block": "npm:40.3.4" "@udecode/plate-combobox": "npm:40.0.0" - "@udecode/plate-comments": "npm:40.0.0" - "@udecode/plate-common": "npm:40.2.8" + "@udecode/plate-comments": "npm:40.3.0" + "@udecode/plate-common": "npm:40.3.1" "@udecode/plate-csv": "npm:40.0.0" "@udecode/plate-diff": "npm:40.0.0" "@udecode/plate-docx": "npm:40.2.7" @@ -7562,12 +7562,12 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate-react@npm:40.2.8, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": +"@udecode/slate-react@npm:40.3.1, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": version: 0.0.0-use.local resolution: "@udecode/slate-react@workspace:packages/slate-react" dependencies: "@udecode/react-utils": "npm:40.2.8" - "@udecode/slate": "npm:39.2.1" + "@udecode/slate": "npm:40.3.1" "@udecode/utils": "npm:37.0.0" peerDependencies: react: ">=16.8.0" @@ -7578,11 +7578,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate-utils@npm:40.2.7, @udecode/slate-utils@workspace:^, @udecode/slate-utils@workspace:packages/slate-utils": +"@udecode/slate-utils@npm:40.3.1, @udecode/slate-utils@workspace:^, @udecode/slate-utils@workspace:packages/slate-utils": version: 0.0.0-use.local resolution: "@udecode/slate-utils@workspace:packages/slate-utils" dependencies: - "@udecode/slate": "npm:39.2.1" + "@udecode/slate": "npm:40.3.1" "@udecode/utils": "npm:37.0.0" lodash: "npm:^4.17.21" peerDependencies: @@ -7591,7 +7591,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate@npm:39.2.1, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate": +"@udecode/slate@npm:40.3.1, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate": version: 0.0.0-use.local resolution: "@udecode/slate@workspace:packages/slate" dependencies: From d9e16e5d3a627b538c7f2c88c29884176f38660c Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 16 Dec 2024 19:10:24 +0100 Subject: [PATCH 4/6] feat --- .changeset/dnd-major.md | 9 + .changeset/dnd-minor.md | 11 + .../www/content/docs/components/changelog.mdx | 17 ++ apps/www/content/docs/dnd.mdx | 145 ++++++------ .../default/plate-ui/column-element.tsx | 122 ++++++++++- .../default/plate-ui/column-group-element.tsx | 4 +- .../registry/default/plate-ui/draggable.tsx | 83 ++++--- packages/dnd/src/DndPlugin.tsx | 13 +- packages/dnd/src/components/index.ts | 1 + packages/dnd/src/components/useDraggable.ts | 53 ++--- packages/dnd/src/components/useDropLine.ts | 51 +++++ .../dnd/src/components/useWithDraggable.ts | 4 +- packages/dnd/src/hooks/index.ts | 3 - packages/dnd/src/hooks/useDndBlock.ts | 11 - packages/dnd/src/hooks/useDndNode.ts | 70 +++--- packages/dnd/src/hooks/useDragBlock.ts | 12 - packages/dnd/src/hooks/useDropBlock.ts | 10 - packages/dnd/src/hooks/useDropNode.ts | 33 ++- .../dnd/src/transforms/onDropNode.spec.ts | 207 ++++++++++++++++++ packages/dnd/src/transforms/onDropNode.ts | 69 +++--- .../dnd/src/transforms/onHoverNode.spec.ts | 142 ++++++++++++ packages/dnd/src/transforms/onHoverNode.ts | 52 +++-- .../transforms/selectBlocksBySelectionOrId.ts | 6 +- packages/dnd/src/types.ts | 4 +- .../dnd/src/utils/getHoverDirection.spec.ts | 106 +++++++++ packages/dnd/src/utils/getHoverDirection.ts | 49 +++-- packages/dnd/src/utils/getNewDirection.ts | 6 + 27 files changed, 974 insertions(+), 319 deletions(-) create mode 100644 .changeset/dnd-major.md create mode 100644 .changeset/dnd-minor.md create mode 100644 packages/dnd/src/components/useDropLine.ts delete mode 100644 packages/dnd/src/hooks/useDndBlock.ts delete mode 100644 packages/dnd/src/hooks/useDragBlock.ts delete mode 100644 packages/dnd/src/hooks/useDropBlock.ts create mode 100644 packages/dnd/src/transforms/onDropNode.spec.ts create mode 100644 packages/dnd/src/transforms/onHoverNode.spec.ts create mode 100644 packages/dnd/src/utils/getHoverDirection.spec.ts diff --git a/.changeset/dnd-major.md b/.changeset/dnd-major.md new file mode 100644 index 0000000000..eb565617c9 --- /dev/null +++ b/.changeset/dnd-major.md @@ -0,0 +1,9 @@ +--- +'@udecode/plate-dnd': major +--- + +- Removed the `useDndBlock`, `useDragBlock`, and `useDropBlock` hooks in favor of `useDndNode` and `useDragNode`, `useDropNode`. +- Remove `DndProvider` and `useDraggableStore`. The drop line state is now managed by `DndPlugin`: `dropTarget` as a single state object containing both `id` and `line`. Migration steps: + - Remove `DndProvider` from your draggable component (e.g. `draggable.tsx`) + - Replace `useDraggableStore` with `useEditorPlugin(DndPlugin).useOption`. +- `useDropNode`: remove `onChangeDropLine`, `dropLine` options diff --git a/.changeset/dnd-minor.md b/.changeset/dnd-minor.md new file mode 100644 index 0000000000..2adbe2dbfd --- /dev/null +++ b/.changeset/dnd-minor.md @@ -0,0 +1,11 @@ +--- +'@udecode/plate-dnd': minor +--- + +- `useDndNode` now supports horizontal orientation. New option is `orientation?: 'horizontal' | 'vertical'`. Default is `vertical`. +- `useDraggableState`, `useDndNode`: add `canDropNode` callback option to query if a dragged node can be dropped onto a hovered node. +- `useDropLine`: + - Added `id` option to show dropline only for hovered element. Default is `useElement().id`. + - Added `orientation` option to filter droplines by orientation (`'horizontal' | 'vertical'`). Default is `vertical`. + - Returns empty dropline if orientation doesn't match (e.g., horizontal dropline in vertical orientation) + - Returns empty dropline if elementId doesn't match current hovered element diff --git a/apps/www/content/docs/components/changelog.mdx b/apps/www/content/docs/components/changelog.mdx index abfeefd01f..e84f2377bf 100644 --- a/apps/www/content/docs/components/changelog.mdx +++ b/apps/www/content/docs/components/changelog.mdx @@ -9,6 +9,23 @@ Since Plate UI is not a component library, a changelog is maintained here. Use the [CLI](https://platejs.org/docs/components/cli) to install the latest version of the components. +## December 2024 #17 + +### December 16 #17.1 + +- `column-element`: + - Add drag and drop support for columns + - Add drag handle with tooltip + - Fix column spacing and padding + +- `column-group-element`: + - Remove gap between columns + - Remove margin top + +- `draggable`: + - Remove `DraggableProvider` HOC + - Remove `DropLine` children prop + ## November 2024 #16 ### November 26 #16.9 diff --git a/apps/www/content/docs/dnd.mdx b/apps/www/content/docs/dnd.mdx index c53dfc2bf8..27897f6422 100644 --- a/apps/www/content/docs/dnd.mdx +++ b/apps/www/content/docs/dnd.mdx @@ -50,6 +50,11 @@ Enables the scroller feature. Props for the `Scroller` component. + + + +The current drop target state containing both the target element id and drop line direction. +Internal state. @@ -150,16 +155,6 @@ Enhances a component with draggable behavior. ## API Components -### DraggableProvider - -A new component that provides context for managing draggable state. - - - - The child components to be wrapped with the draggable context. - - - ### DndScroller A wrapper component for the `Scroller` component that is conditionally rendered based on the dragging state. @@ -192,17 +187,6 @@ A wrapper component for the `Scroller` component that is conditionally rendered -### useDndBlock - -A custom hook that wraps the `useDndNode` hook and configures it for dragging block items. - - - - Options for the `useDndNode` hook, with the `type` property set to - `DRAG_ITEM_BLOCK`. - - - ### useDndNode A custom hook that combines the `useDragNode` and `useDropNode` hooks to enable dragging and dropping of a node from the editor. It provides a default preview for the dragged node, which can be customized or disabled. @@ -214,11 +198,17 @@ A custom hook that combines the `useDragNode` and `useDropNode` hooks to enable The ID of the node to be dragged. - The type of the node to be dragged. + The type of drag item. Defaults to `'block'`. The ref of the node to be dragged. + + The orientation of drag and drop. Defaults to `'vertical'`. + + + Callback to determine if a node can be dropped at the current location. + The preview options for the dragged node. @@ -246,29 +236,12 @@ A custom hook that combines the `useDragNode` and `useDropNode` hooks to enable Indicates whether the dragged node is currently over a drop target. - - The direction of the drop line, indicating the position where the node can be - dropped. - The drag reference that should be assigned to the draggable element. -### useDragBlock - -A custom hook that enables dragging of a block node from the editor. It internally uses the `useDragNode` hook. - - - - The editor instance. - - - The unique ID of the block node to be dragged. - - - ### useDragNode A custom hook that enables dragging of a node from the editor using the `useDrag` hook from `react-dnd`. @@ -296,21 +269,50 @@ A custom hook that enables dragging of a node from the editor using the `useDrag -### useDraggable +### useDraggableState -A custom hook that provides the necessary properties and event handlers for making an element draggable. +A custom hook that manages the draggable state for a node. - + + + + + The element to make draggable. + + + The orientation of drag and drop. Defaults to `'vertical'`. + + + The type of drag item. Defaults to `'block'`. + + + Handler called when the element is dropped. + + + + + + + + The drag source connector function. + - Indicates whether the node is currently being dragged. + Whether the element is currently being dragged. - - The ref of the node to be dragged. + + Reference to the draggable element. - - The drag reference that should be assigned to the draggable element. + + +### useDraggable + +A custom hook that provides the necessary properties and event handlers for making an element draggable. + + + + The state returned from `useDraggableState`. - + @@ -321,19 +323,6 @@ A custom hook that provides the necessary properties and event handlers for maki -### useDropBlock - -A custom hook that enables dropping a block into the editor. It internally uses the `useDropNode` hook to handle the drop behavior. - - - - The editor instance. - - - Options for the drop behavior. - - - ### useDropNode A custom hook that enables dropping a node on the editor. It uses the `useDrop` hook from `react-dnd` to handle the drop behavior. @@ -382,23 +371,23 @@ Returns props for the draggable gutter. ### useDropLine -Returns the current drop line state and props. +Returns the current drop line state for an element. + + + + + + The element ID to show drop line for. Defaults to current element ID. + + + Filter drop lines by orientation. Defaults to 'vertical'. + + + + - - The current direction of the drop line. - - - Props to be spread on the drop line element. - + + The current drop line direction. + - -### useDraggableStore - -Draggable store. - - - - The current direction of the drop line. - - diff --git a/apps/www/src/registry/default/plate-ui/column-element.tsx b/apps/www/src/registry/default/plate-ui/column-element.tsx index 7d22126ff7..58a90ffde5 100644 --- a/apps/www/src/registry/default/plate-ui/column-element.tsx +++ b/apps/www/src/registry/default/plate-ui/column-element.tsx @@ -4,12 +4,30 @@ import React from 'react'; import type { TColumnElement } from '@udecode/plate-layout'; -import { cn, withRef } from '@udecode/cn'; +import { cn, useComposedRef, withRef } from '@udecode/cn'; import { useElement, withHOC } from '@udecode/plate-common/react'; +import { + useDraggable, + useDraggableState, + useDropLine, +} from '@udecode/plate-dnd'; import { ResizableProvider } from '@udecode/plate-resizable'; +import { GripHorizontal } from 'lucide-react'; +import { Path } from 'slate'; import { useReadOnly } from 'slate-react'; +import { Button } from '@/registry/default/plate-ui/button'; + import { PlateElement } from './plate-element'; +import { + Tooltip, + TooltipContent, + TooltipPortal, + TooltipProvider, + TooltipTrigger, +} from './tooltip'; + +const DRAG_ITEM_COLUMN = 'column'; export const ColumnElement = withHOC( ResizableProvider, @@ -17,18 +35,98 @@ export const ColumnElement = withHOC( const readOnly = useReadOnly(); const { width } = useElement(); + const state = useDraggableState({ + canDropNode: ({ dragEntry, dropEntry }) => + Path.equals(Path.parent(dragEntry[1]), Path.parent(dropEntry[1])), + element: props.element, + orientation: 'horizontal', + type: DRAG_ITEM_COLUMN, + }); + + const { previewRef, handleRef } = useDraggable(state); + return ( - - {children} - +
+
+ +
+ + +
+ {children} + +
+
+
); }) ); + +const ColumnDragHandle = React.memo(() => { + return ( + + + + + + + Drag to move column + + + + ); +}); + +const DropLine = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const state = useDropLine({ orientation: 'horizontal' }); + + if (!state.dropLine) return null; + + return ( +
+ ); +}); diff --git a/apps/www/src/registry/default/plate-ui/column-group-element.tsx b/apps/www/src/registry/default/plate-ui/column-group-element.tsx index 10d903bd74..17293165dd 100644 --- a/apps/www/src/registry/default/plate-ui/column-group-element.tsx +++ b/apps/www/src/registry/default/plate-ui/column-group-element.tsx @@ -22,9 +22,9 @@ import { Separator } from './separator'; export const ColumnGroupElement = withRef( ({ children, className, ...props }, ref) => { return ( - + -
{children}
+
{children}
); diff --git a/apps/www/src/registry/default/plate-ui/draggable.tsx b/apps/www/src/registry/default/plate-ui/draggable.tsx index 08d4a92a49..ec514ce65c 100644 --- a/apps/www/src/registry/default/plate-ui/draggable.tsx +++ b/apps/www/src/registry/default/plate-ui/draggable.tsx @@ -12,11 +12,9 @@ import { MemoizedChildren, useEditorPlugin, useEditorRef, - withHOC, } from '@udecode/plate-common/react'; import { type DragItemNode, - DraggableProvider, useDraggable, useDraggableGutter, useDraggableState, @@ -51,50 +49,47 @@ export interface DraggableProps extends PlateElementProps { ) => boolean; } -export const Draggable = withHOC( - DraggableProvider, - withRef<'div', DraggableProps>( - ({ className, onDropHandler, ...props }, ref) => { - const { children, element } = props; - - const state = useDraggableState({ element, onDropHandler }); - const { isDragging } = state; - const { previewRef, handleRef } = useDraggable(state); - - return ( -
- -
-
-
- -
+export const Draggable = withRef<'div', DraggableProps>( + ({ className, onDropHandler, ...props }, ref) => { + const { children, element } = props; + + const state = useDraggableState({ element, onDropHandler }); + const { isDragging } = state; + const { previewRef, handleRef } = useDraggable(state); + + return ( +
+ +
+
+
+
- +
+
-
- {children} +
+ {children} - -
+
- ); - } - ) +
+ ); + } ); const Gutter = React.forwardRef< @@ -154,7 +149,7 @@ const DragHandle = React.memo(() => { const DropLine = React.memo( React.forwardRef>( - ({ children, className, ...props }, ref) => { + ({ className, ...props }, ref) => { const state = useDropLine(); if (!state.dropLine) return null; @@ -172,9 +167,7 @@ const DropLine = React.memo( state.dropLine === 'bottom' && '-bottom-px', className )} - > - {children} -
+ /> ); } ) diff --git a/packages/dnd/src/DndPlugin.tsx b/packages/dnd/src/DndPlugin.tsx index 563fe0d8d2..a06d890f4c 100644 --- a/packages/dnd/src/DndPlugin.tsx +++ b/packages/dnd/src/DndPlugin.tsx @@ -9,13 +9,23 @@ import { createTPlatePlugin, } from '@udecode/plate-common/react'; -import type { DragItemNode, FileDragItemNode } from './types'; +import type { + DragItemNode, + DropLineDirection, + FileDragItemNode, +} from './types'; import { type ScrollerProps, DndScroller } from './components/Scroller'; +export const DRAG_ITEM_BLOCK = 'block'; + export type DndConfig = PluginConfig< 'dnd', { + dropTarget?: { + id: string | null; + line: DropLineDirection; + }; draggingId?: string | null; enableScroller?: boolean; isDragging?: boolean; @@ -35,6 +45,7 @@ export const DndPlugin = createTPlatePlugin({ key: 'dnd', options: { draggingId: null, + dropTarget: { id: null, line: '' }, isDragging: false, }, handlers: { diff --git a/packages/dnd/src/components/index.ts b/packages/dnd/src/components/index.ts index 27070539ea..895f403539 100644 --- a/packages/dnd/src/components/index.ts +++ b/packages/dnd/src/components/index.ts @@ -3,6 +3,7 @@ */ export * from './useDraggable'; +export * from './useDropLine'; export * from './useWithDraggable'; export * from './withDraggable'; export * from './Scroller/index'; diff --git a/packages/dnd/src/components/useDraggable.ts b/packages/dnd/src/components/useDraggable.ts index 42212e39fc..769bf003a5 100644 --- a/packages/dnd/src/components/useDraggable.ts +++ b/packages/dnd/src/components/useDraggable.ts @@ -1,18 +1,8 @@ import React from 'react'; -import type { TEditor, TElement } from '@udecode/plate-common'; -import type { DropTargetMonitor } from 'react-dnd'; +import type { TElement } from '@udecode/plate-common'; -import { createAtomStore } from '@udecode/plate-common/react'; - -import { type DragItemNode, type DropLineDirection, useDndBlock } from '..'; - -export const { DraggableProvider, useDraggableStore } = createAtomStore( - { - dropLine: '' as DropLineDirection, - }, - { name: 'draggable' } -); +import { type UseDndNodeOptions, DRAG_ITEM_BLOCK, useDndNode } from '..'; export type DraggableState = { dragRef: ( @@ -22,25 +12,25 @@ export type DraggableState = { nodeRef: React.RefObject; }; -export const useDraggableState = (props: { - element: TElement; - onDropHandler?: ( - editor: TEditor, - props: { - id: string; - dragItem: DragItemNode; - monitor: DropTargetMonitor; - nodeRef: any; - } - ) => boolean; -}): DraggableState => { - const { element, onDropHandler } = props; +export const useDraggableState = ( + props: UseDndNodeOptions & { element: TElement } +): DraggableState => { + const { + element, + orientation = 'vertical', + type = DRAG_ITEM_BLOCK, + onDropHandler, + } = props; const nodeRef = React.useRef(null); - const { dragRef, isDragging } = useDndBlock({ + + const { dragRef, isDragging } = useDndNode({ id: element.id as string, nodeRef, + orientation, + type, onDropHandler, + ...props, }); return { @@ -64,14 +54,3 @@ export const useDraggableGutter = () => { }, }; }; - -export const useDropLine = () => { - const dropLine = useDraggableStore().get.dropLine(); - - return { - dropLine, - props: { - contentEditable: false, - }, - }; -}; diff --git a/packages/dnd/src/components/useDropLine.ts b/packages/dnd/src/components/useDropLine.ts new file mode 100644 index 0000000000..cfeb0ef5ff --- /dev/null +++ b/packages/dnd/src/components/useDropLine.ts @@ -0,0 +1,51 @@ +import { useEditorPlugin, useElement } from '@udecode/plate-common/react'; + +import { DndPlugin } from '../DndPlugin'; + +export const useDropLine = ({ + id: idProp, + orientation = 'vertical', +}: { + /** The id of the element to show the dropline for. */ + id?: string; + orientation?: 'horizontal' | 'vertical'; +} = {}) => { + const element = useElement(); + const id = idProp || (element.id as string); + const dropTarget = useEditorPlugin(DndPlugin).useOption('dropTarget'); + const dropLine = dropTarget?.line; + + // Only show dropline for currently hovered element + if (id && dropTarget?.id !== id) { + return { + dropLine: '', + props: { + contentEditable: false, + }, + }; + } + if (orientation) { + const isHorizontalDropLine = dropLine === 'left' || dropLine === 'right'; + const isVerticalDropLine = dropLine === 'top' || dropLine === 'bottom'; + + // If the orientation is vertical but we got a horizontal dropline, clear it. + if ( + (orientation === 'vertical' && isHorizontalDropLine) || + (orientation === 'horizontal' && isVerticalDropLine) + ) { + return { + dropLine: '', + props: { + contentEditable: false, + }, + }; + } + } + + return { + dropLine, + props: { + contentEditable: false, + }, + }; +}; diff --git a/packages/dnd/src/components/useWithDraggable.ts b/packages/dnd/src/components/useWithDraggable.ts index 5a455e5840..43c255661e 100644 --- a/packages/dnd/src/components/useWithDraggable.ts +++ b/packages/dnd/src/components/useWithDraggable.ts @@ -1,9 +1,9 @@ import React from 'react'; -import type { TEditor } from '@udecode/plate-common'; import type { Path } from 'slate'; import { + type PlateEditor, type PlateRenderElementProps, findNodePath, } from '@udecode/plate-common/react'; @@ -16,7 +16,7 @@ export interface WithDraggableOptions { draggableProps?: T; /** Filter out elements that can't be dragged. */ - filter?: (editor: TEditor, path: Path) => boolean; + filter?: (editor: PlateEditor, path: Path) => boolean; /** * Document level where dnd is enabled. 0 = root blocks, 1 = first level of * children, etc. Set to null to allow all levels. diff --git a/packages/dnd/src/hooks/index.ts b/packages/dnd/src/hooks/index.ts index bb208b66f5..3f1b38bd21 100644 --- a/packages/dnd/src/hooks/index.ts +++ b/packages/dnd/src/hooks/index.ts @@ -2,9 +2,6 @@ * @file Automatically generated by barrelsby. */ -export * from './useDndBlock'; export * from './useDndNode'; -export * from './useDragBlock'; export * from './useDragNode'; -export * from './useDropBlock'; export * from './useDropNode'; diff --git a/packages/dnd/src/hooks/useDndBlock.ts b/packages/dnd/src/hooks/useDndBlock.ts deleted file mode 100644 index df84b889f1..0000000000 --- a/packages/dnd/src/hooks/useDndBlock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { WithPartial } from '@udecode/plate-common'; - -import { type UseDndNodeOptions, useDndNode } from './useDndNode'; -import { DRAG_ITEM_BLOCK } from './useDragBlock'; - -/** {@link useDndNode} */ -export const useDndBlock = (options: WithPartial) => - useDndNode({ - type: DRAG_ITEM_BLOCK, - ...options, - }); diff --git a/packages/dnd/src/hooks/useDndNode.ts b/packages/dnd/src/hooks/useDndNode.ts index edea21453b..0cd8b69997 100644 --- a/packages/dnd/src/hooks/useDndNode.ts +++ b/packages/dnd/src/hooks/useDndNode.ts @@ -7,32 +7,41 @@ import { type PlateEditor, useEditorRef } from '@udecode/plate-common/react'; import type { DragItemNode } from '../types'; -import { useDraggableStore } from '../components/useDraggable'; +import { DRAG_ITEM_BLOCK, DndPlugin } from '../DndPlugin'; import { type UseDragNodeOptions, useDragNode } from './useDragNode'; import { type UseDropNodeOptions, useDropNode } from './useDropNode'; -export interface UseDndNodeOptions - extends Pick, - Pick { - onDropHandler?: ( - editor: PlateEditor, - props: { - id: string; - dragItem: DragItemNode; - monitor: DropTargetMonitor; - nodeRef: any; - } - ) => boolean; - preview?: { - /** Whether to disable the preview. */ - disable?: boolean; +export type UseDndNodeOptions = Partial< + Pick +> & + Partial> & { + preview?: { + /** Whether to disable the preview. */ + disable?: boolean; + + /** The reference to the preview element. */ + ref?: any; + }; + + /** Options passed to the drag hook. */ + drag?: Partial>; + + /** Options passed to the drop hook, excluding id, nodeRef. */ + drop?: Partial>; - /** The reference to the preview element. */ - ref?: any; + /** Orientation of the drag and drop interaction. */ + orientation?: 'horizontal' | 'vertical'; + + onDropHandler?: ( + editor: PlateEditor, + props: { + id: string; + dragItem: DragItemNode; + monitor: DropTargetMonitor; + nodeRef: any; + } + ) => boolean; }; - drag?: UseDragNodeOptions; - drop?: UseDropNodeOptions; -} /** * {@link useDragNode} and {@link useDropNode} hooks to drag and drop a node from @@ -40,28 +49,29 @@ export interface UseDndNodeOptions * can be customized or removed. Returns the drag ref and drop line direction. */ export const useDndNode = ({ - id, + id = '', + canDropNode, drag: dragOptions, drop: dropOptions, nodeRef, + orientation = 'vertical', preview: previewOptions = {}, - type, + type = DRAG_ITEM_BLOCK, onDropHandler, }: UseDndNodeOptions) => { const editor = useEditorRef(); - const [dropLine, setDropLine] = useDraggableStore().use.dropLine(); - const [{ isDragging }, dragRef, preview] = useDragNode(editor, { id, type, ...dragOptions, }); + const [{ isOver }, drop] = useDropNode(editor, { id, accept: [type, NativeTypes.FILE], - dropLine, + canDropNode, nodeRef, - onChangeDropLine: setDropLine, + orientation, onDropHandler, ...dropOptions, }); @@ -77,10 +87,10 @@ export const useDndNode = ({ } useEffect(() => { - if (!isOver && dropLine) { - setDropLine(''); + if (!isOver && editor.getOptions(DndPlugin).dropTarget?.id) { + editor.setOption(DndPlugin, 'dropTarget', { id: null, line: '' }); } - }, [isOver, dropLine, setDropLine]); + }, [isOver, editor]); return { dragRef, diff --git a/packages/dnd/src/hooks/useDragBlock.ts b/packages/dnd/src/hooks/useDragBlock.ts deleted file mode 100644 index de8eb6f142..0000000000 --- a/packages/dnd/src/hooks/useDragBlock.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { PlateEditor } from '@udecode/plate-common/react'; - -import { useDragNode } from './useDragNode'; - -export const DRAG_ITEM_BLOCK = 'block'; - -/** {@link useDragNode} */ -export const useDragBlock = (editor: PlateEditor, id: string) => - useDragNode(editor, { - id, - type: DRAG_ITEM_BLOCK, - }); diff --git a/packages/dnd/src/hooks/useDropBlock.ts b/packages/dnd/src/hooks/useDropBlock.ts deleted file mode 100644 index 846a5b7171..0000000000 --- a/packages/dnd/src/hooks/useDropBlock.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { PlateEditor } from '@udecode/plate-common/react'; - -import { DRAG_ITEM_BLOCK } from './useDragBlock'; -import { type UseDropNodeOptions, useDropNode } from './useDropNode'; - -/** {@link useDropNode} */ -export const useDropBlock = ( - editor: PlateEditor, - options: Omit -) => useDropNode(editor, { accept: DRAG_ITEM_BLOCK, ...options }); diff --git a/packages/dnd/src/hooks/useDropNode.ts b/packages/dnd/src/hooks/useDropNode.ts index 5f88c1ab7f..90b1aeacd3 100644 --- a/packages/dnd/src/hooks/useDropNode.ts +++ b/packages/dnd/src/hooks/useDropNode.ts @@ -4,11 +4,11 @@ import { useDrop, } from 'react-dnd'; +import type { TElement, TNodeEntry } from '@udecode/plate-common'; import type { PlateEditor } from '@udecode/plate-common/react'; import type { DragItemNode, - DropLineDirection, ElementDragItemNode, FileDragItemNode, } from '../types'; @@ -17,19 +17,21 @@ import { DndPlugin } from '../DndPlugin'; import { getDropPath, onDropNode } from '../transforms/onDropNode'; import { onHoverNode } from '../transforms/onHoverNode'; +export type CanDropCallback = (args: { + dragEntry: TNodeEntry; + dragItem: DragItemNode; + dropEntry: TNodeEntry; + editor: PlateEditor; +}) => boolean; + export interface UseDropNodeOptions extends DropTargetHookSpec { /** Id of the node. */ id: string; - /** Current value of dropLine. */ - dropLine: string; - /** The reference to the node being dragged. */ nodeRef: any; - /** Callback called on dropLine change. */ - onChangeDropLine: (newValue: DropLineDirection) => void; /** * Intercepts the drop handling. If `false` is returned, the default drop * behavior is called after. If `true` is returned, the default behavior is @@ -44,6 +46,10 @@ export interface UseDropNodeOptions nodeRef: any; } ) => boolean; + + canDropNode?: CanDropCallback; + + orientation?: 'horizontal' | 'vertical'; } /** @@ -71,16 +77,18 @@ export const useDropNode = ( editor: PlateEditor, { id, - dropLine, + canDropNode, nodeRef, - onChangeDropLine, + orientation, onDropHandler, ...options }: UseDropNodeOptions ) => { return useDrop({ collect: (monitor) => ({ - isOver: monitor.isOver(), + isOver: monitor.isOver({ + shallow: true, + }), }), drop: (dragItem, monitor) => { // Don't call onDropNode if this is a file drop @@ -88,9 +96,11 @@ export const useDropNode = ( if (!(dragItem as ElementDragItemNode).id) { const result = getDropPath(editor, { id, + canDropNode, dragItem: dragItem as any, monitor, nodeRef, + orientation, }); const onDropFiles = editor.getOptions(DndPlugin).onDropFiles; @@ -123,16 +133,17 @@ export const useDropNode = ( dragItem: dragItem as ElementDragItemNode, monitor, nodeRef, + orientation, }); }, hover(item: DragItemNode, monitor: DropTargetMonitor) { onHoverNode(editor, { id, + canDropNode, dragItem: item, - dropLine, monitor, nodeRef, - onChangeDropLine, + orientation, }); }, ...options, diff --git a/packages/dnd/src/transforms/onDropNode.spec.ts b/packages/dnd/src/transforms/onDropNode.spec.ts new file mode 100644 index 0000000000..2367e79448 --- /dev/null +++ b/packages/dnd/src/transforms/onDropNode.spec.ts @@ -0,0 +1,207 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import type { PlateEditor } from '@udecode/plate-common/react'; +import type { DropTargetMonitor } from 'react-dnd'; + +import { moveNodes } from '@udecode/plate-common'; +import { findNode } from '@udecode/plate-common'; + +import type { ElementDragItemNode } from '../types'; + +import { onDropNode } from './onDropNode'; + +jest.mock('@udecode/plate-common', () => ({ + ...jest.requireActual('@udecode/plate-common'), + findNode: jest.fn(), + moveNodes: jest.fn(), +})); + +jest.mock('@udecode/plate-common/react', () => ({ + ...jest.requireActual('@udecode/plate-common/react'), + focusEditor: jest.fn(), +})); + +jest.mock('../utils', () => ({ + getHoverDirection: jest.fn(), +})); + +describe('onDropNode', () => { + const editor = { selection: {} } as unknown as PlateEditor; + const monitor = {} as DropTargetMonitor; + const nodeRef = {}; + const dragItem: ElementDragItemNode = { id: 'drag' }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when direction is undefined', () => { + it('should do nothing', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValueOnce(); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + }); + + describe('when nodes are not found', () => { + it('should do nothing if drag node is not found', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValueOnce('bottom'); + (findNode as jest.Mock).mockReturnValueOnce(undefined); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + + it('should do nothing if hover node is not found', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValueOnce('bottom'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [0]]) + .mockReturnValueOnce(undefined); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + }); + + describe('vertical orientation', () => { + it('should move node below when direction is bottom', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('bottom'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [0]]) + .mockReturnValueOnce([{}, [1]]); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).toHaveBeenCalledWith(editor, { + at: [0], + to: [1], + }); + }); + + it('should move node above when direction is top', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('top'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [2]]) + .mockReturnValueOnce([{}, [1]]); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).toHaveBeenCalledWith(editor, { + at: [2], + to: [1], + }); + }); + + it('should not move if already in position for bottom', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('bottom'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [1]]) + .mockReturnValueOnce([{}, [0]]); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + + it('should not move if already in position for top', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('top'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [0]]) + .mockReturnValueOnce([{}, [1]]); + + onDropNode(editor, { id: 'hover', dragItem, monitor, nodeRef }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + }); + + describe('horizontal orientation', () => { + it('should move node right when direction is right', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('right'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [2, 0]]) + .mockReturnValueOnce([{}, [2, 1]]); + + onDropNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(moveNodes).toHaveBeenCalledWith(editor, { + at: [2, 0], + to: [2, 1], + }); + }); + + it('should move node left when direction is left', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('left'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [2, 2]]) + .mockReturnValueOnce([{}, [2, 1]]); + + onDropNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(moveNodes).toHaveBeenCalledWith(editor, { + at: [2, 2], + to: [2, 1], + }); + }); + + it('should not move if already in position for right', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('right'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [2, 1]]) + .mockReturnValueOnce([{}, [2, 0]]); + + onDropNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + + it('should not move if already in position for left', () => { + const { getHoverDirection } = require('../utils'); + getHoverDirection.mockReturnValue('left'); + (findNode as jest.Mock) + .mockReturnValueOnce([{}, [2, 0]]) + .mockReturnValueOnce([{}, [2, 1]]); + + onDropNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(moveNodes).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/dnd/src/transforms/onDropNode.ts b/packages/dnd/src/transforms/onDropNode.ts index 213bc4e686..960a13e2e1 100644 --- a/packages/dnd/src/transforms/onDropNode.ts +++ b/packages/dnd/src/transforms/onDropNode.ts @@ -1,7 +1,7 @@ import type { DropTargetMonitor } from 'react-dnd'; -import { type TEditor, findNode, moveNodes } from '@udecode/plate-common'; -import { focusEditor } from '@udecode/plate-common/react'; +import { type TElement, findNode, moveNodes } from '@udecode/plate-common'; +import { type PlateEditor, focusEditor } from '@udecode/plate-common/react'; import { Path } from 'slate'; import type { UseDropNodeOptions } from '../hooks'; @@ -9,79 +9,98 @@ import type { ElementDragItemNode } from '../types'; import { getHoverDirection } from '../utils'; -/** Callback called on drag an drop a node with id. */ +/** Callback called on drag and drop a node with id. */ export const getDropPath = ( - editor: TEditor, + editor: PlateEditor, { id, + canDropNode, dragItem, monitor, nodeRef, + orientation = 'vertical', }: { dragItem: ElementDragItemNode; monitor: DropTargetMonitor; - } & Pick + } & Pick ) => { - const direction = getHoverDirection({ id, dragItem, monitor, nodeRef }); + const direction = getHoverDirection({ + id, + dragItem, + monitor, + nodeRef, + orientation, + }); if (!direction) return; - const dragEntry = findNode(editor, { + const dragEntry = findNode(editor, { at: [], match: { id: dragItem.id }, }); if (!dragEntry) return; + const dropEntry = findNode(editor, { at: [], match: { id } }); + + if (!dropEntry) return; + if (canDropNode && !canDropNode({ dragEntry, dragItem, dropEntry, editor })) { + return; + } + const [, dragPath] = dragEntry; + const [, hoveredPath] = dropEntry; focusEditor(editor); let dropPath: Path | undefined; - if (direction === 'bottom') { - dropPath = findNode(editor, { at: [], match: { id } })?.[1]; + // Treat 'right' like 'bottom' (after hovered) + // Treat 'left' like 'top' (before hovered) + if (direction === 'bottom' || direction === 'right') { + // Insert after hovered node + dropPath = hoveredPath; - if (!dropPath) return; + // If the dragged node is already right after hovered node, no change if (Path.equals(dragPath, Path.next(dropPath))) return; } - if (direction === 'top') { - const nodePath = findNode(editor, { at: [], match: { id } })?.[1]; - - if (!nodePath) return; - - dropPath = [...nodePath.slice(0, -1), nodePath.at(-1)! - 1]; + if (direction === 'top' || direction === 'left') { + // Insert before hovered node + dropPath = [...hoveredPath.slice(0, -1), hoveredPath.at(-1)! - 1]; + // If the dragged node is already right before hovered node, no change if (Path.equals(dragPath, dropPath)) return; } - if (direction) { - const _dropPath = dropPath as Path; - const before = - Path.isBefore(dragPath, _dropPath) && Path.isSibling(dragPath, _dropPath); - const to = before ? _dropPath : Path.next(_dropPath); + const _dropPath = dropPath as Path; + const before = + Path.isBefore(dragPath, _dropPath) && Path.isSibling(dragPath, _dropPath); + const to = before ? _dropPath : Path.next(_dropPath); - return { dragPath, to }; - } + return { direction, dragPath, to }; }; export const onDropNode = ( - editor: TEditor, + editor: PlateEditor, { id, + canDropNode, dragItem, monitor, nodeRef, + orientation = 'vertical', }: { dragItem: ElementDragItemNode; monitor: DropTargetMonitor; - } & Pick + } & Pick ) => { const result = getDropPath(editor, { id, + canDropNode, dragItem, monitor, nodeRef, + orientation, }); if (!result) return; diff --git a/packages/dnd/src/transforms/onHoverNode.spec.ts b/packages/dnd/src/transforms/onHoverNode.spec.ts new file mode 100644 index 0000000000..516f716643 --- /dev/null +++ b/packages/dnd/src/transforms/onHoverNode.spec.ts @@ -0,0 +1,142 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import type { PlateEditor } from '@udecode/plate-common/react'; +import type { DropTargetMonitor } from 'react-dnd'; + +import { collapseSelection, isExpanded } from '@udecode/plate-common'; +import { focusEditor } from '@udecode/plate-common/react'; + +import type { DragItemNode } from '../types'; + +import { DndPlugin } from '../DndPlugin'; +import { onHoverNode } from './onHoverNode'; + +jest.mock('@udecode/plate-common', () => ({ + ...jest.requireActual('@udecode/plate-common'), + collapseSelection: jest.fn(), + isExpanded: jest.fn(), +})); + +jest.mock('@udecode/plate-common/react', () => ({ + ...jest.requireActual('@udecode/plate-common/react'), + focusEditor: jest.fn(), +})); + +jest.mock('../utils', () => ({ + getHoverDirection: jest.fn(), + getNewDirection: jest.fn((dropLine, direction) => direction), +})); + +jest.mock('./onDropNode', () => ({ + getDropPath: jest.fn((editor, options) => ({ + direction: options.orientation === 'horizontal' ? 'left' : 'bottom', + dragPath: [0], + to: [1], + })), +})); + +describe('onHoverNode', () => { + const editor = { + getOptions: jest.fn(), + selection: {}, + setOption: jest.fn(), + } as unknown as PlateEditor; + const monitor = {} as DropTargetMonitor; + const nodeRef = {}; + const dragItem: DragItemNode = { id: 'drag' }; + + beforeEach(() => { + jest.clearAllMocks(); + (editor.getOptions as jest.Mock).mockReturnValue({ + dropTarget: { id: null, line: '' }, + }); + }); + + it('should update plugin options when direction changes', () => { + const { getDropPath } = require('./onDropNode'); + getDropPath.mockReturnValueOnce({ + direction: 'bottom', + dragPath: [0], + to: [1], + }); + + (isExpanded as jest.Mock).mockReturnValue(false); + + onHoverNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + }); + + expect(editor.setOption).toHaveBeenCalledWith(DndPlugin, 'dropTarget', { + id: 'hover', + line: 'bottom', + }); + }); + + it('should collapse selection and focus editor if direction is returned and selection is expanded', () => { + const { getDropPath } = require('./onDropNode'); + getDropPath.mockReturnValueOnce({ + direction: 'top', + dragPath: [0], + to: [1], + }); + + (isExpanded as jest.Mock).mockReturnValue(true); + + onHoverNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + }); + + expect(collapseSelection).toHaveBeenCalledWith(editor); + expect(focusEditor).toHaveBeenCalledWith(editor); + }); + + it('should handle horizontal orientation', () => { + const { getDropPath } = require('./onDropNode'); + getDropPath.mockReturnValueOnce({ + direction: 'left', + dragPath: [0], + to: [1], + }); + + (isExpanded as jest.Mock).mockReturnValue(false); + + onHoverNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(editor.setOption).toHaveBeenCalledWith(DndPlugin, 'dropTarget', { + id: 'hover', + line: 'left', + }); + }); + + it('should clear dropTarget when no direction is returned', () => { + const { getDropPath } = require('./onDropNode'); + getDropPath.mockReturnValueOnce(undefined); + + (editor.getOptions as jest.Mock).mockReturnValue({ + dropTarget: { id: 'hover', line: 'bottom' }, + }); + + onHoverNode(editor, { + id: 'hover', + dragItem, + monitor, + nodeRef, + }); + + expect(editor.setOption).toHaveBeenCalledWith(DndPlugin, 'dropTarget', { + id: null, + line: '', + }); + }); +}); diff --git a/packages/dnd/src/transforms/onHoverNode.ts b/packages/dnd/src/transforms/onHoverNode.ts index f3d0598268..3517e313d6 100644 --- a/packages/dnd/src/transforms/onHoverNode.ts +++ b/packages/dnd/src/transforms/onHoverNode.ts @@ -1,44 +1,60 @@ import type { DropTargetMonitor } from 'react-dnd'; -import { - type TEditor, - collapseSelection, - isExpanded, -} from '@udecode/plate-common'; -import { focusEditor } from '@udecode/plate-common/react'; +import { collapseSelection, isExpanded } from '@udecode/plate-common'; +import { type PlateEditor, focusEditor } from '@udecode/plate-common/react'; import type { UseDropNodeOptions } from '../hooks/useDropNode'; import type { DragItemNode } from '../types'; -import { getHoverDirection, getNewDirection } from '../utils'; +import { DndPlugin } from '../DndPlugin'; +import { getDropPath } from './onDropNode'; /** Callback called when dragging a node and hovering nodes. */ export const onHoverNode = ( - editor: TEditor, + editor: PlateEditor, { id, + canDropNode, dragItem, - dropLine, monitor, nodeRef, - onChangeDropLine, + orientation = 'vertical', }: { dragItem: DragItemNode; monitor: DropTargetMonitor; - } & Pick< - UseDropNodeOptions, - 'dropLine' | 'id' | 'nodeRef' | 'onChangeDropLine' - > + } & Pick ) => { - const direction = getHoverDirection({ + const { dropTarget } = editor.getOptions(DndPlugin); + const currentId = dropTarget?.id ?? null; + const currentLine = dropTarget?.line ?? ''; + + // Check if the drop would actually move the node. + const result = getDropPath(editor, { id, - dragItem, + canDropNode, + dragItem: dragItem as any, monitor, nodeRef, + orientation, }); - const dropLineDir = getNewDirection(dropLine, direction); - if (dropLineDir) onChangeDropLine(dropLineDir); + // If getDropPath returns undefined, it means no actual move would happen. + // In that case, don't show a drop target. + if (!result) { + if (currentId || currentLine) { + editor.setOption(DndPlugin, 'dropTarget', { id: null, line: '' }); + } + + return; + } + + const { direction } = result; + const newDropTarget = { id, line: direction }; + + if (newDropTarget.id !== currentId || newDropTarget.line !== currentLine) { + // Only set if there's a real change + editor.setOption(DndPlugin, 'dropTarget', newDropTarget); + } if (direction && isExpanded(editor.selection)) { focusEditor(editor); collapseSelection(editor); diff --git a/packages/dnd/src/transforms/selectBlocksBySelectionOrId.ts b/packages/dnd/src/transforms/selectBlocksBySelectionOrId.ts index d8666314dd..6d7939d541 100644 --- a/packages/dnd/src/transforms/selectBlocksBySelectionOrId.ts +++ b/packages/dnd/src/transforms/selectBlocksBySelectionOrId.ts @@ -1,7 +1,7 @@ import type { Range } from 'slate'; -import { type SlateEditor, getNodesRange, select } from '@udecode/plate-common'; -import { focusEditor } from '@udecode/plate-common/react'; +import { getNodesRange, select } from '@udecode/plate-common'; +import { type PlateEditor, focusEditor } from '@udecode/plate-common/react'; import { getBlocksWithId } from '../queries/getBlocksWithId'; import { selectBlockById } from './selectBlockById'; @@ -11,7 +11,7 @@ import { selectBlockById } from './selectBlockById'; * select the block with id. Else, select the blocks above the selection. */ export const selectBlocksBySelectionOrId = ( - editor: SlateEditor, + editor: PlateEditor, id: string ) => { if (!editor.selection) return; diff --git a/packages/dnd/src/types.ts b/packages/dnd/src/types.ts index f628e82f25..c54dd257a5 100644 --- a/packages/dnd/src/types.ts +++ b/packages/dnd/src/types.ts @@ -12,6 +12,6 @@ export interface FileDragItemNode { items: DataTransferItemList; } -export type DropLineDirection = '' | 'bottom' | 'top'; +export type DropLineDirection = '' | 'bottom' | 'left' | 'right' | 'top'; -export type DropDirection = 'bottom' | 'top' | undefined; +export type DropDirection = 'bottom' | 'left' | 'right' | 'top' | undefined; diff --git a/packages/dnd/src/utils/getHoverDirection.spec.ts b/packages/dnd/src/utils/getHoverDirection.spec.ts new file mode 100644 index 0000000000..012a4650b4 --- /dev/null +++ b/packages/dnd/src/utils/getHoverDirection.spec.ts @@ -0,0 +1,106 @@ +import type { DropTargetMonitor } from 'react-dnd'; + +import type { DragItemNode } from '../types'; + +import { getHoverDirection } from './getHoverDirection'; + +describe('getHoverDirection', () => { + const nodeRef = { + current: { + getBoundingClientRect: jest.fn(), + }, + } as any; + + const mockMonitor = { + getClientOffset: jest.fn(), + } as unknown as DropTargetMonitor; + + const dragItem: DragItemNode = { id: 'drag' }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return "top" when vertical and mouse is above middle', () => { + nodeRef.current.getBoundingClientRect.mockReturnValue({ + bottom: 200, + top: 100, + }); + (mockMonitor.getClientOffset as any).mockReturnValue({ x: 150, y: 120 }); + + const direction = getHoverDirection({ + id: 'hover', + dragItem, + monitor: mockMonitor, + nodeRef, + orientation: 'vertical', + }); + + expect(direction).toBe('top'); + }); + + it('should return "bottom" when vertical and mouse is below middle', () => { + nodeRef.current.getBoundingClientRect.mockReturnValue({ + bottom: 200, + top: 100, + }); + (mockMonitor.getClientOffset as any).mockReturnValue({ x: 150, y: 180 }); + + const direction = getHoverDirection({ + id: 'hover', + dragItem, + monitor: mockMonitor, + nodeRef, + orientation: 'vertical', + }); + + expect(direction).toBe('bottom'); + }); + + it('should return "left" when horizontal and mouse is left of middle', () => { + nodeRef.current.getBoundingClientRect.mockReturnValue({ + left: 100, + right: 200, + }); + (mockMonitor.getClientOffset as any).mockReturnValue({ x: 120, y: 150 }); + + const direction = getHoverDirection({ + id: 'hover', + dragItem, + monitor: mockMonitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(direction).toBe('left'); + }); + + it('should return "right" when horizontal and mouse is right of middle', () => { + nodeRef.current.getBoundingClientRect.mockReturnValue({ + left: 100, + right: 200, + }); + (mockMonitor.getClientOffset as any).mockReturnValue({ x: 180, y: 150 }); + + const direction = getHoverDirection({ + id: 'hover', + dragItem, + monitor: mockMonitor, + nodeRef, + orientation: 'horizontal', + }); + + expect(direction).toBe('right'); + }); + + it('should return undefined if dragId === id', () => { + const direction = getHoverDirection({ + id: 'drag', + dragItem: { id: 'drag' }, + monitor: mockMonitor, + nodeRef, + }); + + expect(direction).toBeUndefined(); + }); +}); diff --git a/packages/dnd/src/utils/getHoverDirection.ts b/packages/dnd/src/utils/getHoverDirection.ts index 67ed5d8346..65be13af99 100644 --- a/packages/dnd/src/utils/getHoverDirection.ts +++ b/packages/dnd/src/utils/getHoverDirection.ts @@ -16,6 +16,9 @@ export interface GetHoverDirectionOptions { /** The node ref of the node being dragged. */ nodeRef: any; + + /** The orientation of the drag operation. */ + orientation?: 'horizontal' | 'vertical'; } /** @@ -27,6 +30,7 @@ export const getHoverDirection = ({ dragItem, monitor, nodeRef, + orientation = 'vertical', }: GetHoverDirectionOptions): DropDirection => { if (!nodeRef.current) return; @@ -38,29 +42,40 @@ export const getHoverDirection = ({ // Determine rectangle on screen const hoverBoundingRect = nodeRef.current?.getBoundingClientRect(); - // Get vertical middle - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + if (!hoverBoundingRect) { + return; + } // Determine mouse position const clientOffset = monitor.getClientOffset(); - if (!clientOffset) return; + if (!clientOffset) { + return; + } + if (orientation === 'vertical') { + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - // Get pixels to the top - const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; + // Get pixels to the top + const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; - // Only perform the move when the mouse has crossed half of the items height - // When dragging downwards, only move when the cursor is below 50% - // When dragging upwards, only move when the cursor is above 50% + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% - // Dragging downwards - // if (dragId < hoverId && hoverClientY < hoverMiddleY) { - if (hoverClientY < hoverMiddleY) { - return 'top'; - } - // Dragging upwards - // if (dragId > hoverId && hoverClientY > hoverMiddleY) { - if (hoverClientY >= hoverMiddleY) { - return 'bottom'; + // Dragging downwards + if (hoverClientY < hoverMiddleY) { + return 'top'; + } + // Dragging upwards + if (hoverClientY >= hoverMiddleY) { + return 'bottom'; + } + } else { + // Horizontal orientation for columns + const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; + const hoverClientX = (clientOffset as XYCoord).x - hoverBoundingRect.left; + + return hoverClientX < hoverMiddleX ? 'left' : 'right'; } }; diff --git a/packages/dnd/src/utils/getNewDirection.ts b/packages/dnd/src/utils/getNewDirection.ts index 9d3bc78b2a..749291d4ce 100644 --- a/packages/dnd/src/utils/getNewDirection.ts +++ b/packages/dnd/src/utils/getNewDirection.ts @@ -14,4 +14,10 @@ export const getNewDirection = ( if (dir === 'bottom' && previousDir !== 'bottom') { return 'bottom'; } + if (dir === 'left' && previousDir !== 'left') { + return 'left'; + } + if (dir === 'right' && previousDir !== 'right') { + return 'right'; + } }; From d240906a8cb62af355caf8d06812f86879c2727f Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 16 Dec 2024 19:13:03 +0100 Subject: [PATCH 5/6] docs --- .changeset/dnd-major.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.changeset/dnd-major.md b/.changeset/dnd-major.md index eb565617c9..bd2cef4d9d 100644 --- a/.changeset/dnd-major.md +++ b/.changeset/dnd-major.md @@ -2,8 +2,11 @@ '@udecode/plate-dnd': major --- -- Removed the `useDndBlock`, `useDragBlock`, and `useDropBlock` hooks in favor of `useDndNode` and `useDragNode`, `useDropNode`. -- Remove `DndProvider` and `useDraggableStore`. The drop line state is now managed by `DndPlugin`: `dropTarget` as a single state object containing both `id` and `line`. Migration steps: - - Remove `DndProvider` from your draggable component (e.g. `draggable.tsx`) - - Replace `useDraggableStore` with `useEditorPlugin(DndPlugin).useOption`. -- `useDropNode`: remove `onChangeDropLine`, `dropLine` options +- Removed `useDndBlock`, `useDragBlock`, and `useDropBlock` hooks in favor of `useDndNode`, `useDragNode`, and `useDropNode`. +- Removed `DndProvider` and `useDraggableStore`. Drop line state is now managed by `DndPlugin` as a single state object `dropTarget` containing both `id` and `line`. +- `useDropNode`: removed `onChangeDropLine` and `dropLine` options + +Migration steps: + +- Remove `DndProvider` from your draggable component (e.g. `draggable.tsx`) +- Replace `useDraggableStore` with `useEditorPlugin(DndPlugin).useOption` From 7aaf46d552da010f88986114a365dae86b3a83cd Mon Sep 17 00:00:00 2001 From: zbeyens Date: Tue, 17 Dec 2024 13:41:13 +0100 Subject: [PATCH 6/6] fix --- apps/www/src/registry/default/plate-ui/column-element.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/www/src/registry/default/plate-ui/column-element.tsx b/apps/www/src/registry/default/plate-ui/column-element.tsx index 58a90ffde5..8f4a663748 100644 --- a/apps/www/src/registry/default/plate-ui/column-element.tsx +++ b/apps/www/src/registry/default/plate-ui/column-element.tsx @@ -16,8 +16,7 @@ import { GripHorizontal } from 'lucide-react'; import { Path } from 'slate'; import { useReadOnly } from 'slate-react'; -import { Button } from '@/registry/default/plate-ui/button'; - +import { Button } from './button'; import { PlateElement } from './plate-element'; import { Tooltip,