diff --git a/.changeset/clean-hotkeys-dance.md b/.changeset/clean-hotkeys-dance.md new file mode 100644 index 0000000000..a8b7f94c05 --- /dev/null +++ b/.changeset/clean-hotkeys-dance.md @@ -0,0 +1,5 @@ +--- +'slate-dom': minor +--- + +Expose `isHotkey` for custom keyboard shortcut checks. diff --git a/.changeset/decorate-compat-source-name.md b/.changeset/decorate-compat-source-name.md new file mode 100644 index 0000000000..15a749a56f --- /dev/null +++ b/.changeset/decorate-compat-source-name.md @@ -0,0 +1,5 @@ +--- +"slate-react": minor +--- + +Rename the legacy decoration projection adapter to `createSlateDecorateCompatSource`. diff --git a/.changeset/editor-method-target-fresh-marks.md b/.changeset/editor-method-target-fresh-marks.md new file mode 100644 index 0000000000..3c2bb8ae3e --- /dev/null +++ b/.changeset/editor-method-target-fresh-marks.md @@ -0,0 +1,5 @@ +--- +"slate": minor +--- + +Expose `editor.toggleMark`, `editor.setBlock`, and `editor.toggleBlock`, and resolve implicit mark/block targets through the transaction target runtime. diff --git a/.changeset/editor-node-normalizers.md b/.changeset/editor-node-normalizers.md new file mode 100644 index 0000000000..8557228496 --- /dev/null +++ b/.changeset/editor-node-normalizers.md @@ -0,0 +1,7 @@ +--- +"slate": major +--- + +Split extension normalizers into `normalizers.editor` for editor-root normalization and `normalizers.node` for non-root node normalization. + +**Migration:** Move root/value-level normalizers from `normalizers.node` to `normalizers.editor`. diff --git a/.changeset/full-transform-middleware.md b/.changeset/full-transform-middleware.md new file mode 100644 index 0000000000..b81b88c566 --- /dev/null +++ b/.changeset/full-transform-middleware.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Add extension transform middleware for all public mutating editor transforms. diff --git a/.changeset/operation-validation-guards.md b/.changeset/operation-validation-guards.md new file mode 100644 index 0000000000..2e724273f0 --- /dev/null +++ b/.changeset/operation-validation-guards.md @@ -0,0 +1,5 @@ +--- +"slate": minor +--- + +Add concrete operation type guards and reject unknown operation replay records. diff --git a/.changeset/quiet-transform-middleware.md b/.changeset/quiet-transform-middleware.md new file mode 100644 index 0000000000..14d0a6cd39 --- /dev/null +++ b/.changeset/quiet-transform-middleware.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Add extension transform middleware for `deleteBackward` and `insertText`. diff --git a/.changeset/remove-legacy-react-renderer-exports.md b/.changeset/remove-legacy-react-renderer-exports.md new file mode 100644 index 0000000000..7b14593159 --- /dev/null +++ b/.changeset/remove-legacy-react-renderer-exports.md @@ -0,0 +1,5 @@ +--- +"slate-react": minor +--- + +Remove legacy renderer component exports that are not used by the semantic `Editable` runtime. diff --git a/.changeset/remove-suppress-throw-dom-projection.md b/.changeset/remove-suppress-throw-dom-projection.md new file mode 100644 index 0000000000..c80d763203 --- /dev/null +++ b/.changeset/remove-suppress-throw-dom-projection.md @@ -0,0 +1,6 @@ +--- +"slate-dom": major +"slate-react": major +--- + +Remove `suppressThrow` from DOM-to-Slate projection options. Use `resolveSlatePoint` and `resolveSlateRange` for nullable projection; `toSlatePoint` and `toSlateRange` stay strict. diff --git a/.changeset/remove-transforms-namespace.md b/.changeset/remove-transforms-namespace.md new file mode 100644 index 0000000000..16e8c71f87 --- /dev/null +++ b/.changeset/remove-transforms-namespace.md @@ -0,0 +1,6 @@ +--- +"slate": major +--- + +Remove the public `Transforms` namespace and require primitive document and +selection writes to run inside `editor.update(...)`. diff --git a/.changeset/richtext-history-dom-repair.md b/.changeset/richtext-history-dom-repair.md new file mode 100644 index 0000000000..e54fe1953d --- /dev/null +++ b/.changeset/richtext-history-dom-repair.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Repair editor DOM after keyboard undo and redo history operations diff --git a/.changeset/runtime-id-live-path.md b/.changeset/runtime-id-live-path.md new file mode 100644 index 0000000000..ea23bcd7ed --- /dev/null +++ b/.changeset/runtime-id-live-path.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Add live runtime-id path lookup for renderer-owned subscriptions. diff --git a/.changeset/selectable-void-navigation.md b/.changeset/selectable-void-navigation.md new file mode 100644 index 0000000000..3937468706 --- /dev/null +++ b/.changeset/selectable-void-navigation.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Fix arrow-key navigation to select selectable block and inline voids. diff --git a/.changeset/sharp-input-rules.md b/.changeset/sharp-input-rules.md new file mode 100644 index 0000000000..f316c6f33d --- /dev/null +++ b/.changeset/sharp-input-rules.md @@ -0,0 +1,7 @@ +--- +"slate-react": major +--- + +Remove `Editable` input rules and `editableInputRules`. + +**Migration:** Use `editor.extend({ transforms: ... })` for model-owned input behavior, or `onDOMBeforeInput` for browser-specific input handling. diff --git a/.changeset/shell-backed-partial-paste.md b/.changeset/shell-backed-partial-paste.md new file mode 100644 index 0000000000..fc037b5192 --- /dev/null +++ b/.changeset/shell-backed-partial-paste.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Preserve fragment and rich paste for shell-backed large-document selections. diff --git a/.changeset/slate-commit-metadata.md b/.changeset/slate-commit-metadata.md new file mode 100644 index 0000000000..5c627178d4 --- /dev/null +++ b/.changeset/slate-commit-metadata.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Expose last commit metadata for transaction-aware runtime consumers diff --git a/.changeset/slate-dom-runtime-id-find-path.md b/.changeset/slate-dom-runtime-id-find-path.md new file mode 100644 index 0000000000..46513cf2d7 --- /dev/null +++ b/.changeset/slate-dom-runtime-id-find-path.md @@ -0,0 +1,5 @@ +--- +"slate-dom": patch +--- + +Resolve DOM element paths through live runtime ids before falling back to weak-map indexes. diff --git a/.changeset/slate-helper-api-names.md b/.changeset/slate-helper-api-names.md new file mode 100644 index 0000000000..9d021fbc82 --- /dev/null +++ b/.changeset/slate-helper-api-names.md @@ -0,0 +1,5 @@ +--- +"slate": major +--- + +Rename public helper value namespaces to `*Api` while keeping model type names unchanged. diff --git a/.changeset/slate-multiline-paste-expanded-selection.md b/.changeset/slate-multiline-paste-expanded-selection.md new file mode 100644 index 0000000000..073b84003f --- /dev/null +++ b/.changeset/slate-multiline-paste-expanded-selection.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Fix multiline plain-text paste after replacing the whole editor selection diff --git a/.changeset/slate-react-custom-placeholder-delete.md b/.changeset/slate-react-custom-placeholder-delete.md new file mode 100644 index 0000000000..87ccabc904 --- /dev/null +++ b/.changeset/slate-react-custom-placeholder-delete.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Fix custom placeholders restoring after all editor text is deleted diff --git a/.changeset/slate-react-decorate-adapter-internal.md b/.changeset/slate-react-decorate-adapter-internal.md new file mode 100644 index 0000000000..d226bf2e60 --- /dev/null +++ b/.changeset/slate-react-decorate-adapter-internal.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Route `decorate` through the projection-source compatibility adapter diff --git a/.changeset/slate-react-decoration-source-adapter.md b/.changeset/slate-react-decoration-source-adapter.md new file mode 100644 index 0000000000..72f04cdf3e --- /dev/null +++ b/.changeset/slate-react-decoration-source-adapter.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Add a projection-source adapter for legacy decoration callbacks diff --git a/.changeset/slate-react-dom-text-sync-capability.md b/.changeset/slate-react-dom-text-sync-capability.md new file mode 100644 index 0000000000..6c2f638024 --- /dev/null +++ b/.changeset/slate-react-dom-text-sync-capability.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Expose explicit DOM text sync opt-out reasons diff --git a/.changeset/slate-react-editable-command-handler.md b/.changeset/slate-react-editable-command-handler.md new file mode 100644 index 0000000000..27c46cebc3 --- /dev/null +++ b/.changeset/slate-react-editable-command-handler.md @@ -0,0 +1,5 @@ +--- +"slate-react": minor +--- + +Expose `Editable.onCommand` for native formatting input and keep native input listeners stable across handler prop changes. diff --git a/.changeset/slate-react-editable-semantic-runtime.md b/.changeset/slate-react-editable-semantic-runtime.md new file mode 100644 index 0000000000..e5028f97e0 --- /dev/null +++ b/.changeset/slate-react-editable-semantic-runtime.md @@ -0,0 +1,5 @@ +--- +"slate-react": major +--- + +Make `Editable` use the semantic-blocks runtime with projection sources, large-document islands, and browser-safe model-owned text input. diff --git a/.changeset/slate-react-element-selected-collapsed.md b/.changeset/slate-react-element-selected-collapsed.md new file mode 100644 index 0000000000..62d4b98b29 --- /dev/null +++ b/.changeset/slate-react-element-selected-collapsed.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Add options-based `useElementSelected` modes for explicit paths and block void selected UI. diff --git a/.changeset/slate-react-example-dx-cleanup.md b/.changeset/slate-react-example-dx-cleanup.md new file mode 100644 index 0000000000..17c5106a37 --- /dev/null +++ b/.changeset/slate-react-example-dx-cleanup.md @@ -0,0 +1,11 @@ +--- +"slate-react": patch +--- + +Rename React annotation and void helper props for clearer v2 authoring. + +**Migration:** + +- Use `` instead of `annotationStores={[store]}`. +- Use `useSlateAnnotations()` and `useSlateAnnotation(id)` inside `Slate` when reading the provider store. +- Use `renderVoid={({ element, path }) => ...}` instead of `target` for void paths. diff --git a/.changeset/slate-react-live-runtime-reads.md b/.changeset/slate-react-live-runtime-reads.md new file mode 100644 index 0000000000..47f3f660b8 --- /dev/null +++ b/.changeset/slate-react-live-runtime-reads.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Use live runtime reads for mounted text and large-document island lookup diff --git a/.changeset/slate-react-remove-child-count-chunking.md b/.changeset/slate-react-remove-child-count-chunking.md new file mode 100644 index 0000000000..800a0bc6ae --- /dev/null +++ b/.changeset/slate-react-remove-child-count-chunking.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Remove child-count chunking from the current React runtime diff --git a/.changeset/slate-react-render-path-props.md b/.changeset/slate-react-render-path-props.md new file mode 100644 index 0000000000..d04bd0d5a2 --- /dev/null +++ b/.changeset/slate-react-render-path-props.md @@ -0,0 +1,7 @@ +--- +"slate-react": minor +--- + +Remove eager `path` and `index` from element render props. + +Resolve the current element path inside handlers with `ReactEditor.findPath(editor, element)`, or use `useElementPath()` for path-dependent render UI. diff --git a/.changeset/slate-react-repeat-scroll-into-view.md b/.changeset/slate-react-repeat-scroll-into-view.md new file mode 100644 index 0000000000..5110ce7109 --- /dev/null +++ b/.changeset/slate-react-repeat-scroll-into-view.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Fix repeated caret autoscroll after typing in a scrolled editor. diff --git a/.changeset/slate-react-typed-editable-renderers.md b/.changeset/slate-react-typed-editable-renderers.md new file mode 100644 index 0000000000..dbdfc77270 --- /dev/null +++ b/.changeset/slate-react-typed-editable-renderers.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Infer element and void renderer props from keyed `editableRenderers` maps. diff --git a/.changeset/slate-react-virtualized-rendering.md b/.changeset/slate-react-virtualized-rendering.md new file mode 100644 index 0000000000..e09e4a9708 --- /dev/null +++ b/.changeset/slate-react-virtualized-rendering.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Add experimental TanStack-backed `renderingStrategy.type: 'virtualized'` for bounded large-document editor surfaces. diff --git a/.changeset/slate-state-nodes-to-array.md b/.changeset/slate-state-nodes-to-array.md new file mode 100644 index 0000000000..1ba27f998d --- /dev/null +++ b/.changeset/slate-state-nodes-to-array.md @@ -0,0 +1,5 @@ +--- +"slate": patch +--- + +Add `state.nodes.toArray(options, map?)` for explicit node query materialization inside read and update callbacks. diff --git a/.changeset/tame-dom-coverage-boundaries.md b/.changeset/tame-dom-coverage-boundaries.md new file mode 100644 index 0000000000..d583b4a5dc --- /dev/null +++ b/.changeset/tame-dom-coverage-boundaries.md @@ -0,0 +1,5 @@ +--- +"slate-react": patch +--- + +Add unstable DOM coverage boundary slots for hidden editable regions. diff --git a/.gitignore b/.gitignore index 8e01656f3d..a4db8d9433 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ dist/ lib/ node_modules/ site/out/ -tmp/ +*tmp/ test-results/ coverage .DS_Store +.clawpatch \ No newline at end of file diff --git a/bun.lock b/bun.lock index 22a0abf73e..fb6c03d8dc 100644 --- a/bun.lock +++ b/bun.lock @@ -14,7 +14,6 @@ "@playwright/test": "^1.52.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", - "@types/is-hotkey": "^0.1.10", "@types/is-url": "^1.2.32", "@types/lodash": "^4.14.200", "@types/node": "^20.8.7", @@ -26,7 +25,6 @@ "eslint": "^10.2.0", "eslint-plugin-react-hooks": "7.0.1", "image-extensions": "^1.1.0", - "is-hotkey": "^0.2.0", "is-url": "^1.2.4", "lodash": "^4.17.21", "next": "^16.2.4", @@ -76,12 +74,9 @@ "packages/slate-dom": { "name": "slate-dom", "version": "0.124.1", - "dependencies": { - "is-hotkey": "^0.2.0", - }, "devDependencies": { - "@types/is-hotkey": "^0.1.8", "@types/node": "^20.8.7", + "jsdom": "20.0.3", "slate": "workspace:*", }, "peerDependencies": { @@ -93,7 +88,7 @@ "version": "0.115.0", "devDependencies": { "lodash": "^4.17.21", - "slate": "^0.124.0", + "slate": "workspace:*", }, "peerDependencies": { "slate": ">=0.114.3", @@ -114,9 +109,10 @@ "version": "0.124.0", "dependencies": { "@juggle/resize-observer": "^3.4.0", + "@tanstack/react-virtual": "^3.13.24", "direction": "^1.0.4", "lodash": "^4.17.21", - "scroll-into-view-if-needed": "^3.1.0", + "slate-history": "workspace:*", }, "devDependencies": { "@testing-library/dom": "^10.4.1", @@ -131,7 +127,6 @@ "react-dom": "^19.2.5", "slate": "workspace:*", "slate-dom": "workspace:*", - "slate-history": "workspace:*", "slate-hyperscript": "workspace:*", "vitest": "^4.1.0", }, @@ -540,6 +535,10 @@ "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.24", "", { "dependencies": { "@tanstack/virtual-core": "3.14.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="], + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "7.29.0", "@babel/runtime": "7.29.2", "@types/aria-query": "5.0.4", "aria-query": "5.3.0", "dom-accessibility-api": "0.5.16", "lz-string": "1.5.0", "picocolors": "1.1.1", "pretty-format": "27.5.1" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "4.4.4", "aria-query": "5.3.2", "css.escape": "1.5.1", "dom-accessibility-api": "0.6.3", "picocolors": "1.1.1", "redent": "3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], @@ -572,8 +571,6 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/is-hotkey": ["@types/is-hotkey@0.1.10", "", {}, "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ=="], - "@types/is-url": ["@types/is-url@1.2.32", "", {}, "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], @@ -706,8 +703,6 @@ "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "content-disposition": ["content-disposition@0.5.2", "", {}, "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA=="], @@ -922,8 +917,6 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="], - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], @@ -1154,8 +1147,6 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "3.1.1" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "serve-handler": ["serve-handler@6.1.7", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="], diff --git a/config/bun-test-setup.ts b/config/bun-test-setup.ts index 9d884c15c8..e10daef3a7 100644 --- a/config/bun-test-setup.ts +++ b/config/bun-test-setup.ts @@ -1,76 +1,18 @@ -import { afterEach, mock as bunMock, expect, mock, spyOn } from 'bun:test' -import { dirname, relative } from 'node:path' -import { fileURLToPath } from 'node:url' +import { afterEach, expect, mock, spyOn } from 'bun:test' import { TextEncoder } from 'node:util' import { GlobalRegistrator } from '@happy-dom/global-registrator' import * as matchers from '@testing-library/jest-dom/matchers' import { cleanup } from '@testing-library/react' import React from 'react' +import { createHyperscript } from 'slate-hyperscript' -const legacyClassicJsxTranspiler = new Bun.Transpiler({ - loader: 'tsx', - tsconfig: { - compilerOptions: { - jsxFactory: 'jsx', - jsx: 'react', - }, - }, -}) -const legacyClassicJsxFixtureRe = - /[/\\]packages[/\\](slate|slate-history|slate-hyperscript)[/\\]test[/\\](?!bun[/\\])(?!.*\.spec\.).+\.(js|jsx|ts|tsx)$/ - -const hasLocalJsxFactoryRe = - /\b(?:const|let|var|function)\s+jsx\b|\bimport\s*\{\s*jsx\s*\}\s*from\b/ -const slateTestJsxRuntimePath = fileURLToPath( - new URL('./slate-test-jsx.js', import.meta.url) -) -const getInjectedJsxImport = (path: string) => { - const relativePath = relative( - dirname(path), - slateTestJsxRuntimePath - ).replaceAll('\\', '/') - const specifier = relativePath.startsWith('.') - ? relativePath - : `./${relativePath}` - - return `import { jsx } from '${specifier}'\n` -} -const importWorkspaceModule = (specifier: string) => import(specifier) - -Bun.plugin({ - name: 'legacy-hsx-fixtures', - setup(build) { - build.onLoad({ filter: legacyClassicJsxFixtureRe }, async (args) => { - const source = await Bun.file(args.path).text() - - return { - contents: legacyClassicJsxTranspiler.transformSync( - hasLocalJsxFactoryRe.test(source) - ? source - : `${getInjectedJsxImport(args.path)}${source}` - ), - loader: 'js', - } - }) +const jsx = createHyperscript({ + elements: { + block: {}, + inline: { inline: true }, }, }) -bunMock.module('slate', () => - importWorkspaceModule('../packages/slate/src/index') -) -bunMock.module('slate-dom', () => - importWorkspaceModule('../packages/slate-dom/src/index') -) -bunMock.module('slate-history', () => - importWorkspaceModule('../packages/slate-history/src/index') -) -bunMock.module('slate-hyperscript', () => - importWorkspaceModule('../packages/slate-hyperscript/src/index') -) -bunMock.module('slate-react', () => - importWorkspaceModule('../packages/slate-react/src/index') -) - GlobalRegistrator.register({ settings: { disableIframePageLoading: true, @@ -144,6 +86,12 @@ Object.defineProperty(globalThis, 'React', { writable: true, }) +Object.defineProperty(globalThis, 'jsx', { + configurable: true, + value: jsx, + writable: true, +}) + globalThis.jest = { fn: mock, spyOn, diff --git a/config/typescript/tsconfig.json b/config/typescript/tsconfig.json index 20c5d610d6..0b44f8ea78 100644 --- a/config/typescript/tsconfig.json +++ b/config/typescript/tsconfig.json @@ -1,13 +1,30 @@ { "compilerOptions": { "allowSyntheticDefaultImports": true, - "composite": true, - "declaration": true, - "declarationMap": true, + "baseUrl": "../..", "esModuleInterop": true, + "ignoreDeprecations": "6.0", "jsx": "react", "module": "esnext", "moduleResolution": "bundler", + "paths": { + "slate": ["packages/slate/src/index.ts"], + "slate/internal": ["packages/slate/src/internal/index.ts"], + "slate-browser": ["packages/slate-browser/src/index.ts"], + "slate-browser/browser": ["packages/slate-browser/src/browser/index.ts"], + "slate-browser/core": ["packages/slate-browser/src/core/index.ts"], + "slate-browser/playwright": [ + "packages/slate-browser/src/playwright/index.ts" + ], + "slate-browser/transports": [ + "packages/slate-browser/src/transports/index.ts" + ], + "slate-dom": ["packages/slate-dom/src/index.ts"], + "slate-dom/internal": ["packages/slate-dom/src/internal/index.ts"], + "slate-history": ["packages/slate-history/src/index.ts"], + "slate-hyperscript": ["packages/slate-hyperscript/src/index.ts"], + "slate-react": ["packages/slate-react/src/index.ts"] + }, "resolveJsonModule": true, "sourceMap": true, "strict": true, diff --git a/docs/Introduction.md b/docs/Introduction.md index 5de7fe0ae5..c89714054e 100644 --- a/docs/Introduction.md +++ b/docs/Introduction.md @@ -1,86 +1,62 @@ # Introduction -[Slate](http://slatejs.org) is a _completely_ customizable framework for building rich text editors. +[Slate](http://slatejs.org) is a customizable framework for building rich text editors. -Slate lets you build rich, intuitive editors like those in [Medium](https://medium.com/), [Dropbox Paper](https://www.dropbox.com/paper) or [Google Docs](https://www.google.com/docs/about/)—which are becoming table stakes for applications on the web—without your codebase getting mired in complexity. - -It can do this because all of its logic is implemented with a series of plugins, so you aren't ever constrained by what _is_ or _isn't_ in "core". You can think of it like a pluggable implementation of `contenteditable` built on top of [React](https://facebook.github.io/react/). It was inspired by libraries like [Draft.js](https://facebook.github.io/draft-js/), [Prosemirror](http://prosemirror.net/) and [Quill](http://quilljs.com/). - -> 🤖 **Slate is currently in beta**. Its core API is usable now, but you might need to pull request fixes for advanced use cases. Some of its APIs are not "finalized" and will \(breaking\) change over time as we find better solutions. +Slate gives you a small document model, a transaction runtime, and a React renderer that you can shape around your product. You bring the schema, the UI, and the behavior that makes your editor different. ## Why? -Why create Slate? Well... _\(Beware: this section has a few of_ [_my_](https://github.com/ianstormtaylor) _opinions!\)_ - -Before creating Slate, I tried a lot of the other rich text libraries out there—[**Draft.js**](https://facebook.github.io/draft-js/), [**Prosemirror**](http://prosemirror.net/), [**Quill**](http://quilljs.com/), etc. What I found was that while getting simple examples to work was easy enough, once you started trying to build something like [Medium](https://medium.com/), [Dropbox Paper](https://www.dropbox.com/paper) or [Google Docs](https://www.google.com/docs/about/), you ran into deeper issues... +Most rich text editors make the simple case feel easy. The hard part starts when your editor needs comments, embeds, mentions, tables, collaborative editing, or a document model that belongs to your application instead of the editor library. -- **The editor's "schema" was hardcoded and hard to customize.** Things like bold and italic were supported out of the box, but what about comments, or embeds, or even more domain-specific needs? -- **Transforming the documents programmatically was very convoluted.** Writing as a user may have worked, but making programmatic changes, which is critical for building advanced behaviors, was needlessly complex. -- **Serializing to HTML, Markdown, etc. seemed like an afterthought.** Simple things like transforming a document to HTML or Markdown involved writing lots of boilerplate code, for what seemed like very common use cases. -- **Re-inventing the view layer seemed inefficient and limiting.** Most editors rolled their own views, instead of using existing technologies like React, so you had to learn a whole new system with new "gotchas". -- **Collaborative editing wasn't designed for in advance.** Often the editor's internal representation of data made it impossible to use for a realtime, collaborative editing use case without basically rewriting the editor. -- **The repositories were monolithic, not small and reusable.** The code bases for many of the editors often didn't expose the internal tooling that could have been re-used by developers, leading to having to reinvent the wheel. -- **Building complex, nested documents was impossible.** Many editors were designed around simplistic "flat" documents, making things like tables, embeds and captions difficult to reason about and sometimes impossible. +Slate is built for that harder case. -Of course not every editor exhibits all of these issues, but if you've tried using another editor you might have run into similar problems. To get around the limitations of their APIs and achieve the user experience you're after, you have to resort to very hacky things. And some experiences are just plain impossible to achieve. +- **Your document model is yours.** Slate stores documents as nested JSON nodes, so paragraphs, links, images, tables, comments, and domain-specific elements all use the same tree model. +- **Core stays small.** Slate gives you primitives for reading, updating, normalizing, rendering, and replaying operations. It does not decide what a "blog post", "comment", or "task" node should mean. +- **Programmatic editing is explicit.** Reads go through `editor.read(...)`; writes go through `editor.update(...)`. This keeps user edits, app commands, history, and collaboration adapters on the same runtime path. +- **React is the view layer.** `slate-react` renders the editor and subscribes to committed editor state. App components render content; the runtime owns selection, DOM repair, void shells, and browser editing details. +- **Collaboration can target the operation model.** Slate exposes snapshots, commits, tags, and operation replay so a collaboration adapter can import and export document changes without persisting React or DOM state. -If that sounds familiar, you might like Slate. - -Which brings me to how Slate solves all of that... +If that sounds like the shape of editor you need, Slate is probably a good fit. ## Principles -Slate tries to solve the question of "[Why?](Introduction.md#why)" with a few principles: - -1. **First-class plugins.** The most important part of Slate is that plugins are first-class entities. That means you can _completely_ customize the editing experience, to build complex editors like Medium's or Dropbox's, without having to fight against the library's assumptions. -2. **Schema-less core.** Slate's core logic assumes very little about the schema of the data you'll be editing, which means that there are no assumptions baked into the library that'll trip you up when you need to go beyond the most basic use cases. -3. **Nested document model.** The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes are possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierarchy. -4. **Parallel to the DOM.** Slate's data model is based on the DOM—the document is a nested tree, it uses selections and ranges, and it exposes all the standard event handlers. This means that advanced behaviors like tables or nested block quotes are possible. Pretty much anything you can do in the DOM, you can do in Slate. -5. **Intuitive commands.** Slate documents are edited using "commands", that are designed to be high-level and extremely intuitive to write and read, so that custom functionality is as expressive as possible. This greatly increases your ability to reason about your code. -6. **Collaboration-ready data model.** The data model Slate uses—specifically how operations are applied to the document—has been designed to allow for collaborative editing to be layered on top, so you won't need to rethink everything if you decide to make your editor collaborative. -7. **Clear "core" boundaries.** With a plugin-first architecture, and a schema-less core, it becomes a lot clearer where the boundary is between "core" and "custom", which means that the core experience doesn't get bogged down in edge cases. - -## Demo +Slate follows a few principles: -Check out the [**live demo**](http://slatejs.org) of all of the examples! +1. **Schema-less core.** Slate's core does not bake in paragraphs, headings, lists, or images. Those are conventions you define with elements, text, rendering, and normalization. +2. **Nested document model.** Slate documents are recursive trees. This makes simple editors straightforward and gives complex editors enough room for tables, embeds, captions, and nested blocks. +3. **Explicit transactions.** Slate separates reads from writes. You read committed state with `editor.read(...)` and make document changes inside `editor.update(...)`. +4. **Operations as the shared layer.** User edits, commands, history, and collaboration all meet at the operation and commit layer. +5. **React as projection.** React renders the editor, but hot editing policy stays in the runtime. Renderers subscribe to the smallest editor facts they actually display. +6. **Unopinionated extension points.** Slate exposes low-level extension hooks for schema, namespaced reads and writes, normalization, commit listeners, operation middleware, and rendering. Product frameworks like Plate can build richer command conventions on top. ## Examples -To get a sense for how you might use Slate, check out a few of the examples: +To get a sense for how you might use Slate, check out a few examples: -- [**Plain text**](https://www.slatejs.org/examples/plaintext) — showing the most basic case: a glorified `