1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-01-16 21:18:39 +01:00

refactor: Split out slate dom package (#5734)

* Copied some things from slate-react into new react-dom package

* Refactor slate-react to use slate-dom

* Fixed failing tests

* Created changeset

* Ran fix:prettier

* Fixed name

* Removed duplicate code

* Fixed import

* Restored linting rule

* Bumped slate-dom version

* Bumped slate dependency version

* Added export of IS_NODE_MAP_DIRTY after rebase
This commit is contained in:
Brian Ingles 2024-10-31 09:20:34 -05:00 committed by GitHub
parent 7e1608018b
commit 9a21251270
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1847 additions and 1559 deletions

View File

@ -0,0 +1,6 @@
---
'slate-react': minor
'slate-dom': minor
---
Split out slate-dom package

View File

@ -12,6 +12,7 @@ import { startCase } from 'lodash'
import Core from '../../packages/slate/package.json'
import History from '../../packages/slate-history/package.json'
import Hyperscript from '../../packages/slate-hyperscript/package.json'
import DOM from '../../packages/slate-dom/package.json'
import React from '../../packages/slate-react/package.json'
/**
@ -203,5 +204,6 @@ export default [
...factory(Core),
...factory(History),
...factory(Hyperscript),
...factory(DOM),
...factory(React),
]

View File

@ -0,0 +1,64 @@
{
"name": "slate-dom",
"description": "Tools for building completely customizable richtext editors with React.",
"version": "0.110.2",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"umd": "dist/slate-dom.js",
"umdMin": "dist/slate-dom.min.js",
"sideEffects": false,
"files": [
"dist/"
],
"dependencies": {
"@juggle/resize-observer": "^3.4.0",
"direction": "^1.0.4",
"is-hotkey": "^0.2.0",
"is-plain-object": "^5.0.0",
"lodash": "^4.17.21",
"scroll-into-view-if-needed": "^3.1.0",
"tiny-invariant": "1.3.1"
},
"devDependencies": {
"@babel/runtime": "^7.23.2",
"@types/is-hotkey": "^0.1.8",
"@types/jest": "29.5.6",
"@types/jsdom": "^21.1.4",
"@types/lodash": "^4.14.200",
"@types/resize-observer-browser": "^0.1.8",
"slate": "^0.110.2",
"slate-hyperscript": "^0.100.0",
"source-map-loader": "^4.0.1"
},
"peerDependencies": {
"slate": ">=0.99.0"
},
"umdGlobals": {
"slate": "Slate"
},
"keywords": [
"canvas",
"contenteditable",
"docs",
"document",
"edit",
"editor",
"editable",
"html",
"immutable",
"markdown",
"medium",
"paper",
"react",
"rich",
"richtext",
"richtext",
"slate",
"text",
"wysiwyg",
"wysiwym"
]
}

View File

@ -0,0 +1,45 @@
import { BaseRange, BaseText } from 'slate'
import { DOMEditor } from './plugin/dom-editor'
declare module 'slate' {
interface CustomTypes {
Editor: DOMEditor
Text: BaseText & {
placeholder?: string
onPlaceholderResize?: (node: HTMLElement | null) => void
// FIXME: is unknown correct here?
[key: string]: unknown
}
Range: BaseRange & {
placeholder?: string
onPlaceholderResize?: (node: HTMLElement | null) => void
// FIXME: is unknown correct here?
[key: string]: unknown
}
}
}
declare global {
interface Window {
MSStream: boolean
}
interface DocumentOrShadowRoot {
getSelection(): Selection | null
}
interface CaretPosition {
readonly offsetNode: Node
readonly offset: number
getClientRect(): DOMRect | null
}
interface Document {
caretPositionFromPoint(x: number, y: number): CaretPosition | null
}
interface Node {
getRootNode(options?: GetRootNodeOptions): Document | ShadowRoot
}
}
export {}

View File

@ -0,0 +1,89 @@
// Plugin
export { DOMEditor, type DOMEditorInterface } from './plugin/dom-editor'
export { withDOM } from './plugin/with-dom'
// Utils
export { TRIPLE_CLICK } from './utils/constants'
export {
applyStringDiff,
mergeStringDiffs,
normalizePoint,
normalizeRange,
normalizeStringDiff,
StringDiff,
targetRange,
TextDiff,
verifyDiffState,
} from './utils/diff-text'
export {
DOMElement,
DOMNode,
DOMPoint,
DOMRange,
DOMSelection,
DOMStaticRange,
DOMText,
getActiveElement,
getDefaultView,
getSelection,
hasShadowRoot,
isAfter,
isBefore,
isDOMElement,
isDOMNode,
isDOMSelection,
isPlainTextOnlyPaste,
isTrackedMutation,
normalizeDOMPoint,
} from './utils/dom'
export {
CAN_USE_DOM,
HAS_BEFORE_INPUT_SUPPORT,
IS_ANDROID,
IS_CHROME,
IS_FIREFOX,
IS_FIREFOX_LEGACY,
IS_IOS,
IS_WEBKIT,
IS_UC_MOBILE,
IS_WECHATBROWSER,
} from './utils/environment'
export { default as Hotkeys } from './utils/hotkeys'
export { Key } from './utils/key'
export {
isElementDecorationsEqual,
isTextDecorationsEqual,
} from './utils/range-list'
export {
EDITOR_TO_ELEMENT,
EDITOR_TO_FORCE_RENDER,
EDITOR_TO_KEY_TO_ELEMENT,
EDITOR_TO_ON_CHANGE,
EDITOR_TO_PENDING_ACTION,
EDITOR_TO_PENDING_DIFFS,
EDITOR_TO_PENDING_INSERTION_MARKS,
EDITOR_TO_PENDING_SELECTION,
EDITOR_TO_PLACEHOLDER_ELEMENT,
EDITOR_TO_SCHEDULE_FLUSH,
EDITOR_TO_USER_MARKS,
EDITOR_TO_USER_SELECTION,
EDITOR_TO_WINDOW,
ELEMENT_TO_NODE,
IS_COMPOSING,
IS_FOCUSED,
IS_NODE_MAP_DIRTY,
IS_READ_ONLY,
MARK_PLACEHOLDER_SYMBOL,
NODE_TO_ELEMENT,
NODE_TO_INDEX,
NODE_TO_KEY,
NODE_TO_PARENT,
PLACEHOLDER_SYMBOL,
} from './utils/weak-maps'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,382 @@
import {
BaseEditor,
Editor,
Element,
Node,
Operation,
Path,
PathRef,
Point,
Range,
Transforms,
} from 'slate'
import {
TextDiff,
transformPendingPoint,
transformPendingRange,
transformTextDiff,
} from '../utils/diff-text'
import {
getPlainText,
getSlateFragmentAttribute,
isDOMText,
} from '../utils/dom'
import { Key } from '../utils/key'
import { findCurrentLineRange } from '../utils/lines'
import {
IS_NODE_MAP_DIRTY,
EDITOR_TO_KEY_TO_ELEMENT,
EDITOR_TO_ON_CHANGE,
EDITOR_TO_PENDING_ACTION,
EDITOR_TO_PENDING_DIFFS,
EDITOR_TO_PENDING_INSERTION_MARKS,
EDITOR_TO_PENDING_SELECTION,
EDITOR_TO_SCHEDULE_FLUSH,
EDITOR_TO_USER_MARKS,
EDITOR_TO_USER_SELECTION,
NODE_TO_KEY,
} from '../utils/weak-maps'
import { DOMEditor } from './dom-editor'
/**
* `withDOM` adds DOM specific behaviors to the editor.
*
* If you are using TypeScript, you must extend Slate's CustomTypes to use
* this plugin.
*
* See https://docs.slatejs.org/concepts/11-typescript to learn how.
*/
export const withDOM = <T extends BaseEditor>(
editor: T,
clipboardFormatKey = 'x-slate-fragment'
): T & DOMEditor => {
const e = editor as T & DOMEditor
const { apply, onChange, deleteBackward, addMark, removeMark } = e
// The WeakMap which maps a key to a specific HTMLElement must be scoped to the editor instance to
// avoid collisions between editors in the DOM that share the same value.
EDITOR_TO_KEY_TO_ELEMENT.set(e, new WeakMap())
e.addMark = (key, value) => {
EDITOR_TO_SCHEDULE_FLUSH.get(e)?.()
if (
!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) &&
EDITOR_TO_PENDING_DIFFS.get(e)?.length
) {
// Ensure the current pending diffs originating from changes before the addMark
// are applied with the current formatting
EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null)
}
EDITOR_TO_USER_MARKS.delete(e)
addMark(key, value)
}
e.removeMark = key => {
if (
!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) &&
EDITOR_TO_PENDING_DIFFS.get(e)?.length
) {
// Ensure the current pending diffs originating from changes before the addMark
// are applied with the current formatting
EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null)
}
EDITOR_TO_USER_MARKS.delete(e)
removeMark(key)
}
e.deleteBackward = unit => {
if (unit !== 'line') {
return deleteBackward(unit)
}
if (e.selection && Range.isCollapsed(e.selection)) {
const parentBlockEntry = Editor.above(e, {
match: n => Element.isElement(n) && Editor.isBlock(e, n),
at: e.selection,
})
if (parentBlockEntry) {
const [, parentBlockPath] = parentBlockEntry
const parentElementRange = Editor.range(
e,
parentBlockPath,
e.selection.anchor
)
const currentLineRange = findCurrentLineRange(e, parentElementRange)
if (!Range.isCollapsed(currentLineRange)) {
Transforms.delete(e, { at: currentLineRange })
}
}
}
}
// This attempts to reset the NODE_TO_KEY entry to the correct value
// as apply() changes the object reference and hence invalidates the NODE_TO_KEY entry
e.apply = (op: Operation) => {
const matches: [Path, Key][] = []
const pathRefMatches: [PathRef, Key][] = []
const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(e)
if (pendingDiffs?.length) {
const transformed = pendingDiffs
.map(textDiff => transformTextDiff(textDiff, op))
.filter(Boolean) as TextDiff[]
EDITOR_TO_PENDING_DIFFS.set(e, transformed)
}
const pendingSelection = EDITOR_TO_PENDING_SELECTION.get(e)
if (pendingSelection) {
EDITOR_TO_PENDING_SELECTION.set(
e,
transformPendingRange(e, pendingSelection, op)
)
}
const pendingAction = EDITOR_TO_PENDING_ACTION.get(e)
if (pendingAction?.at) {
const at = Point.isPoint(pendingAction?.at)
? transformPendingPoint(e, pendingAction.at, op)
: transformPendingRange(e, pendingAction.at, op)
EDITOR_TO_PENDING_ACTION.set(e, at ? { ...pendingAction, at } : null)
}
switch (op.type) {
case 'insert_text':
case 'remove_text':
case 'set_node':
case 'split_node': {
matches.push(...getMatches(e, op.path))
break
}
case 'set_selection': {
// Selection was manually set, don't restore the user selection after the change.
EDITOR_TO_USER_SELECTION.get(e)?.unref()
EDITOR_TO_USER_SELECTION.delete(e)
break
}
case 'insert_node':
case 'remove_node': {
matches.push(...getMatches(e, Path.parent(op.path)))
break
}
case 'merge_node': {
const prevPath = Path.previous(op.path)
matches.push(...getMatches(e, prevPath))
break
}
case 'move_node': {
const commonPath = Path.common(
Path.parent(op.path),
Path.parent(op.newPath)
)
matches.push(...getMatches(e, commonPath))
let changedPath: Path
if (Path.isBefore(op.path, op.newPath)) {
matches.push(...getMatches(e, Path.parent(op.path)))
changedPath = op.newPath
} else {
matches.push(...getMatches(e, Path.parent(op.newPath)))
changedPath = op.path
}
const changedNode = Node.get(editor, Path.parent(changedPath))
const changedNodeKey = DOMEditor.findKey(e, changedNode)
const changedPathRef = Editor.pathRef(e, Path.parent(changedPath))
pathRefMatches.push([changedPathRef, changedNodeKey])
break
}
}
apply(op)
switch (op.type) {
case 'insert_node':
case 'remove_node':
case 'merge_node':
case 'move_node':
case 'split_node': {
IS_NODE_MAP_DIRTY.set(e, true)
}
}
for (const [path, key] of matches) {
const [node] = Editor.node(e, path)
NODE_TO_KEY.set(node, key)
}
for (const [pathRef, key] of pathRefMatches) {
if (pathRef.current) {
const [node] = Editor.node(e, pathRef.current)
NODE_TO_KEY.set(node, key)
}
pathRef.unref()
}
}
e.setFragmentData = (data: Pick<DataTransfer, 'getData' | 'setData'>) => {
const { selection } = e
if (!selection) {
return
}
const [start, end] = Range.edges(selection)
const startVoid = Editor.void(e, { at: start.path })
const endVoid = Editor.void(e, { at: end.path })
if (Range.isCollapsed(selection) && !startVoid) {
return
}
// Create a fake selection so that we can add a Base64-encoded copy of the
// fragment to the HTML, to decode on future pastes.
const domRange = DOMEditor.toDOMRange(e, selection)
let contents = domRange.cloneContents()
let attach = contents.childNodes[0] as HTMLElement
// Make sure attach is non-empty, since empty nodes will not get copied.
contents.childNodes.forEach(node => {
if (node.textContent && node.textContent.trim() !== '') {
attach = node as HTMLElement
}
})
// COMPAT: If the end node is a void node, we need to move the end of the
// range from the void node's spacer span, to the end of the void node's
// content, since the spacer is before void's content in the DOM.
if (endVoid) {
const [voidNode] = endVoid
const r = domRange.cloneRange()
const domNode = DOMEditor.toDOMNode(e, voidNode)
r.setEndAfter(domNode)
contents = r.cloneContents()
}
// COMPAT: If the start node is a void node, we need to attach the encoded
// fragment to the void node's content node instead of the spacer, because
// attaching it to empty `<div>/<span>` nodes will end up having it erased by
// most browsers. (2018/04/27)
if (startVoid) {
attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement
}
// Remove any zero-width space spans from the cloned DOM so that they don't
// show up elsewhere when pasted.
Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(
zw => {
const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'
zw.textContent = isNewline ? '\n' : ''
}
)
// Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
// in the HTML, and can be used for intra-Slate pasting. If it's a text
// node, wrap it in a `<span>` so we have something to set an attribute on.
if (isDOMText(attach)) {
const span = attach.ownerDocument.createElement('span')
// COMPAT: In Chrome and Safari, if we don't add the `white-space` style
// then leading and trailing spaces will be ignored. (2017/09/21)
span.style.whiteSpace = 'pre'
span.appendChild(attach)
contents.appendChild(span)
attach = span
}
const fragment = e.getFragment()
const string = JSON.stringify(fragment)
const encoded = window.btoa(encodeURIComponent(string))
attach.setAttribute('data-slate-fragment', encoded)
data.setData(`application/${clipboardFormatKey}`, encoded)
// Add the content to a <div> so that we can get its inner HTML.
const div = contents.ownerDocument.createElement('div')
div.appendChild(contents)
div.setAttribute('hidden', 'true')
contents.ownerDocument.body.appendChild(div)
data.setData('text/html', div.innerHTML)
data.setData('text/plain', getPlainText(div))
contents.ownerDocument.body.removeChild(div)
return data
}
e.insertData = (data: DataTransfer) => {
if (!e.insertFragmentData(data)) {
e.insertTextData(data)
}
}
e.insertFragmentData = (data: DataTransfer): boolean => {
/**
* Checking copied fragment from application/x-slate-fragment or data-slate-fragment
*/
const fragment =
data.getData(`application/${clipboardFormatKey}`) ||
getSlateFragmentAttribute(data)
if (fragment) {
const decoded = decodeURIComponent(window.atob(fragment))
const parsed = JSON.parse(decoded) as Node[]
e.insertFragment(parsed)
return true
}
return false
}
e.insertTextData = (data: DataTransfer): boolean => {
const text = data.getData('text/plain')
if (text) {
const lines = text.split(/\r\n|\r|\n/)
let split = false
for (const line of lines) {
if (split) {
Transforms.splitNodes(e, { always: true })
}
e.insertText(line)
split = true
}
return true
}
return false
}
e.onChange = options => {
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
if (onContextChange) {
onContextChange(options)
}
onChange(options)
}
return e
}
const getMatches = (e: Editor, path: Path) => {
const matches: [Path, Key][] = []
for (const [n, p] of Editor.levels(e, { at: path })) {
const key = DOMEditor.findKey(e, n)
matches.push([p, key])
}
return matches
}

View File

@ -12,7 +12,7 @@ import DOMText = globalThis.Text
import DOMRange = globalThis.Range
import DOMSelection = globalThis.Selection
import DOMStaticRange = globalThis.StaticRange
import { ReactEditor } from '../plugin/react-editor'
import { DOMEditor } from '../plugin/dom-editor'
export {
DOMNode,
@ -289,7 +289,7 @@ export const getSelection = (root: Document | ShadowRoot): Selection | null => {
*/
export const isTrackedMutation = (
editor: ReactEditor,
editor: DOMEditor,
mutation: MutationRecord,
batch: MutationRecord[]
): boolean => {
@ -298,9 +298,9 @@ export const isTrackedMutation = (
return false
}
const { document } = ReactEditor.getWindow(editor)
const { document } = DOMEditor.getWindow(editor)
if (document.contains(target)) {
return ReactEditor.hasDOMNode(editor, target, { editable: true })
return DOMEditor.hasDOMNode(editor, target, { editable: true })
}
const parentMutation = batch.find(({ addedNodes, removedNodes }) => {

View File

@ -0,0 +1,83 @@
export const IS_IOS =
typeof navigator !== 'undefined' &&
typeof window !== 'undefined' &&
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
!window.MSStream
export const IS_APPLE =
typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)
export const IS_ANDROID =
typeof navigator !== 'undefined' && /Android/.test(navigator.userAgent)
export const IS_FIREFOX =
typeof navigator !== 'undefined' &&
/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)
export const IS_WEBKIT =
typeof navigator !== 'undefined' &&
/AppleWebKit(?!.*Chrome)/i.test(navigator.userAgent)
// "modern" Edge was released at 79.x
export const IS_EDGE_LEGACY =
typeof navigator !== 'undefined' &&
/Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent)
export const IS_CHROME =
typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent)
// Native `beforeInput` events don't work well with react on Chrome 75
// and older, Chrome 76+ can use `beforeInput` though.
export const IS_CHROME_LEGACY =
typeof navigator !== 'undefined' &&
/Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent)
export const IS_ANDROID_CHROME_LEGACY =
IS_ANDROID &&
typeof navigator !== 'undefined' &&
/Chrome?\/(?:[0-5]?\d)(?:\.)/i.test(navigator.userAgent)
// Firefox did not support `beforeInput` until `v87`.
export const IS_FIREFOX_LEGACY =
typeof navigator !== 'undefined' &&
/^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(
navigator.userAgent
)
// UC mobile browser
export const IS_UC_MOBILE =
typeof navigator !== 'undefined' && /.*UCBrowser/.test(navigator.userAgent)
// Wechat browser (not including mac wechat)
export const IS_WECHATBROWSER =
typeof navigator !== 'undefined' &&
/.*Wechat/.test(navigator.userAgent) &&
!/.*MacWechat/.test(navigator.userAgent) // avoid lookbehind (buggy in safari < 16.4)
// Check if DOM is available as React does internally.
// https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js
export const CAN_USE_DOM = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
)
// Check if the browser is Safari and older than 17
export const IS_SAFARI_LEGACY =
typeof navigator !== 'undefined' &&
/Safari/.test(navigator.userAgent) &&
/Version\/(\d+)/.test(navigator.userAgent) &&
(navigator.userAgent.match(/Version\/(\d+)/)?.[1]
? parseInt(navigator.userAgent.match(/Version\/(\d+)/)?.[1]!, 10) < 17
: false)
// COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event
// Chrome Legacy doesn't support `beforeinput` correctly
export const HAS_BEFORE_INPUT_SUPPORT =
(!IS_CHROME_LEGACY || !IS_ANDROID_CHROME_LEGACY) &&
!IS_EDGE_LEGACY &&
// globalThis is undefined in older browsers
typeof globalThis !== 'undefined' &&
globalThis.InputEvent &&
// @ts-ignore The `getTargetRanges` property isn't recognized.
typeof globalThis.InputEvent.prototype.getTargetRanges === 'function'

View File

@ -3,7 +3,7 @@
*/
import { Editor, Range } from 'slate'
import { ReactEditor } from '../plugin/react-editor'
import { DOMEditor } from '../plugin/dom-editor'
const doRectsIntersect = (rect: DOMRect, compareRect: DOMRect) => {
const middle = (compareRect.top + compareRect.bottom) / 2
@ -11,13 +11,9 @@ const doRectsIntersect = (rect: DOMRect, compareRect: DOMRect) => {
return rect.top <= middle && rect.bottom >= middle
}
const areRangesSameLine = (
editor: ReactEditor,
range1: Range,
range2: Range
) => {
const rect1 = ReactEditor.toDOMRange(editor, range1).getBoundingClientRect()
const rect2 = ReactEditor.toDOMRange(editor, range2).getBoundingClientRect()
const areRangesSameLine = (editor: DOMEditor, range1: Range, range2: Range) => {
const rect1 = DOMEditor.toDOMRange(editor, range1).getBoundingClientRect()
const rect2 = DOMEditor.toDOMRange(editor, range2).getBoundingClientRect()
return doRectsIntersect(rect1, rect2) && doRectsIntersect(rect2, rect1)
}
@ -31,7 +27,7 @@ const areRangesSameLine = (
* @returns {Range} A valid portion of the parentRange which is one a single line
*/
export const findCurrentLineRange = (
editor: ReactEditor,
editor: DOMEditor,
parentRange: Range
): Range => {
const parentRangeBoundary = Editor.range(editor, Range.end(parentRange))

View File

@ -1,8 +1,18 @@
import { Ancestor, Editor, Node, Operation, Range, RangeRef, Text } from 'slate'
import { Action } from '../hooks/android-input-manager/android-input-manager'
import {
Ancestor,
Editor,
Node,
Operation,
Point,
Range,
RangeRef,
Text,
} from 'slate'
import { TextDiff } from './diff-text'
import { Key } from './key'
export type Action = { at?: Point | Range; run: () => void }
/**
* Two weak maps that allow us rebuild a path given a node. They are populated
* at render time such that after a render occurs we can always backtrack.

View File

@ -0,0 +1,9 @@
{
"extends": "../../config/typescript/tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"references": [{ "path": "../slate" }]
}

View File

@ -35,13 +35,15 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"slate": "^0.110.2",
"slate-dom": "^0.110.2",
"slate-hyperscript": "^0.100.0",
"source-map-loader": "^4.0.1"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0",
"slate": ">=0.99.0"
"slate": ">=0.99.0",
"slate-dom": ">=0.110.2"
},
"umdGlobals": {
"react": "React",

View File

@ -31,7 +31,7 @@ import { ReadOnlyContext } from '../hooks/use-read-only'
import { useSlate } from '../hooks/use-slate'
import { useTrackUserInput } from '../hooks/use-track-user-input'
import { ReactEditor } from '../plugin/react-editor'
import { TRIPLE_CLICK } from '../utils/constants'
import { TRIPLE_CLICK } from 'slate-dom'
import {
DOMElement,
DOMRange,
@ -42,7 +42,7 @@ import {
isDOMElement,
isDOMNode,
isPlainTextOnlyPaste,
} from '../utils/dom'
} from 'slate-dom'
import {
CAN_USE_DOM,
HAS_BEFORE_INPUT_SUPPORT,
@ -54,8 +54,8 @@ import {
IS_WEBKIT,
IS_UC_MOBILE,
IS_WECHATBROWSER,
} from '../utils/environment'
import Hotkeys from '../utils/hotkeys'
} from 'slate-dom'
import { Hotkeys } from 'slate-dom'
import {
IS_NODE_MAP_DIRTY,
EDITOR_TO_ELEMENT,
@ -71,7 +71,7 @@ import {
MARK_PLACEHOLDER_SYMBOL,
NODE_TO_ELEMENT,
PLACEHOLDER_SYMBOL,
} from '../utils/weak-maps'
} from 'slate-dom'
import { RestoreDOM } from './restore-dom/restore-dom'
import { AndroidInputManager } from '../hooks/android-input-manager/android-input-manager'
import { ComposingContext } from '../hooks/use-composing'

View File

@ -4,14 +4,14 @@ import { JSX } from 'react'
import { Editor, Element as SlateElement, Node, Range } from 'slate'
import { ReactEditor, useReadOnly, useSlateStatic } from '..'
import useChildren from '../hooks/use-children'
import { isElementDecorationsEqual } from '../utils/range-list'
import { isElementDecorationsEqual } from 'slate-dom'
import {
EDITOR_TO_KEY_TO_ELEMENT,
ELEMENT_TO_NODE,
NODE_TO_ELEMENT,
NODE_TO_INDEX,
NODE_TO_PARENT,
} from '../utils/weak-maps'
} from 'slate-dom'
import {
RenderElementProps,
RenderLeafProps,

View File

@ -13,10 +13,10 @@ import {
PLACEHOLDER_SYMBOL,
EDITOR_TO_PLACEHOLDER_ELEMENT,
EDITOR_TO_FORCE_RENDER,
} from '../utils/weak-maps'
} from 'slate-dom'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import { useSlateStatic } from '../hooks/use-slate-static'
import { IS_WEBKIT, IS_ANDROID } from '../utils/environment'
import { IS_WEBKIT, IS_ANDROID } from 'slate-dom'
// Delay the placeholder on Android to prevent the keyboard from closing.
// (https://github.com/ianstormtaylor/slate/pull/5368)

View File

@ -1,6 +1,6 @@
import { RefObject } from 'react'
import { ReactEditor } from '../../plugin/react-editor'
import { isTrackedMutation } from '../../utils/dom'
import { isTrackedMutation } from 'slate-dom'
export type RestoreDOMManager = {
registerMutations: (mutations: MutationRecord[]) => void

View File

@ -6,7 +6,7 @@ import React, {
RefObject,
} from 'react'
import { EditorContext } from '../../hooks/use-slate-static'
import { IS_ANDROID } from '../../utils/environment'
import { IS_ANDROID } from 'slate-dom'
import {
createRestoreDomManager,
RestoreDOMManager,

View File

@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'
import { Descendant, Editor, Node, Operation, Scrubber, Selection } from 'slate'
import { EDITOR_TO_ON_CHANGE } from 'slate-dom'
import { FocusedContext } from '../hooks/use-focused'
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import { SlateContext, SlateContextValue } from '../hooks/use-slate'
@ -10,7 +11,6 @@ import {
import { EditorContext } from '../hooks/use-slate-static'
import { ReactEditor } from '../plugin/react-editor'
import { REACT_MAJOR_VERSION } from '../utils/environment'
import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
/**
* A wrapper around the provider to handle `onChange` events, because the editor

View File

@ -3,8 +3,8 @@ import { Editor, Text, Path, Element, Node } from 'slate'
import { ReactEditor, useSlateStatic } from '..'
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import { IS_ANDROID, IS_IOS } from '../utils/environment'
import { MARK_PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
import { IS_ANDROID, IS_IOS } from 'slate-dom'
import { MARK_PLACEHOLDER_SYMBOL } from 'slate-dom'
/**
* Leaf content strings.

View File

@ -1,12 +1,12 @@
import React, { useCallback, useRef } from 'react'
import { Element, Range, Text as SlateText } from 'slate'
import { ReactEditor, useSlateStatic } from '..'
import { isTextDecorationsEqual } from '../utils/range-list'
import { isTextDecorationsEqual } from 'slate-dom'
import {
EDITOR_TO_KEY_TO_ELEMENT,
ELEMENT_TO_NODE,
NODE_TO_ELEMENT,
} from '../utils/weak-maps'
} from 'slate-dom'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import Leaf from './leaf'

View File

@ -11,8 +11,8 @@ import {
targetRange,
TextDiff,
verifyDiffState,
} from '../../utils/diff-text'
import { isDOMSelection, isTrackedMutation } from '../../utils/dom'
} from 'slate-dom'
import { isDOMSelection, isTrackedMutation } from 'slate-dom'
import {
EDITOR_TO_FORCE_RENDER,
EDITOR_TO_PENDING_ACTION,
@ -23,7 +23,7 @@ import {
EDITOR_TO_USER_MARKS,
IS_COMPOSING,
IS_NODE_MAP_DIRTY,
} from '../../utils/weak-maps'
} from 'slate-dom'
export type Action = { at?: Point | Range; run: () => void }

View File

@ -1,7 +1,7 @@
import { RefObject, useState } from 'react'
import { useSlateStatic } from '../use-slate-static'
import { IS_ANDROID } from '../../utils/environment'
import { EDITOR_TO_SCHEDULE_FLUSH } from '../../utils/weak-maps'
import { IS_ANDROID } from 'slate-dom'
import { EDITOR_TO_SCHEDULE_FLUSH } from 'slate-dom'
import {
createAndroidInputManager,
CreateAndroidInputManagerOptions,

View File

@ -9,11 +9,7 @@ import {
import ElementComponent from '../components/element'
import TextComponent from '../components/text'
import { ReactEditor } from '../plugin/react-editor'
import {
IS_NODE_MAP_DIRTY,
NODE_TO_INDEX,
NODE_TO_PARENT,
} from '../utils/weak-maps'
import { IS_NODE_MAP_DIRTY, NODE_TO_INDEX, NODE_TO_PARENT } from 'slate-dom'
import { useDecorate } from './use-decorate'
import { SelectedContext } from './use-selected'
import { useSlateStatic } from './use-slate-static'

View File

@ -1,5 +1,5 @@
import { useLayoutEffect, useEffect } from 'react'
import { CAN_USE_DOM } from '../utils/environment'
import { CAN_USE_DOM } from 'slate-dom'
/**
* Prevent warning on SSR by falling back to useEffect when DOM isn't available

View File

@ -27,4 +27,4 @@ export { ReactEditor } from './plugin/react-editor'
export { withReact } from './plugin/with-react'
// Utils
export { NODE_TO_INDEX, NODE_TO_PARENT } from './utils/weak-maps'
export { NODE_TO_INDEX, NODE_TO_PARENT } from 'slate-dom'

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,6 @@
import ReactDOM from 'react-dom'
import {
BaseEditor,
Editor,
Element,
Node,
Operation,
Path,
PathRef,
Point,
Range,
Transforms,
} from 'slate'
import {
TextDiff,
transformPendingPoint,
transformPendingRange,
transformTextDiff,
} from '../utils/diff-text'
import {
getPlainText,
getSlateFragmentAttribute,
isDOMText,
} from '../utils/dom'
import { Key } from '../utils/key'
import { findCurrentLineRange } from '../utils/lines'
import {
IS_NODE_MAP_DIRTY,
EDITOR_TO_KEY_TO_ELEMENT,
EDITOR_TO_ON_CHANGE,
EDITOR_TO_PENDING_ACTION,
EDITOR_TO_PENDING_DIFFS,
EDITOR_TO_PENDING_INSERTION_MARKS,
EDITOR_TO_PENDING_SELECTION,
EDITOR_TO_SCHEDULE_FLUSH,
EDITOR_TO_USER_MARKS,
EDITOR_TO_USER_SELECTION,
NODE_TO_KEY,
} from '../utils/weak-maps'
import { BaseEditor } from 'slate'
import { withDOM } from 'slate-dom'
import { ReactEditor } from './react-editor'
import { REACT_MAJOR_VERSION } from '../utils/environment'
@ -48,318 +12,15 @@ import { REACT_MAJOR_VERSION } from '../utils/environment'
*
* See https://docs.slatejs.org/concepts/11-typescript to learn how.
*/
export const withReact = <T extends BaseEditor>(
editor: T,
clipboardFormatKey = 'x-slate-fragment'
): T & ReactEditor => {
const e = editor as T & ReactEditor
const { apply, onChange, deleteBackward, addMark, removeMark } = e
let e = editor as T & ReactEditor
// The WeakMap which maps a key to a specific HTMLElement must be scoped to the editor instance to
// avoid collisions between editors in the DOM that share the same value.
EDITOR_TO_KEY_TO_ELEMENT.set(e, new WeakMap())
e = withDOM(e, clipboardFormatKey)
e.addMark = (key, value) => {
EDITOR_TO_SCHEDULE_FLUSH.get(e)?.()
if (
!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) &&
EDITOR_TO_PENDING_DIFFS.get(e)?.length
) {
// Ensure the current pending diffs originating from changes before the addMark
// are applied with the current formatting
EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null)
}
EDITOR_TO_USER_MARKS.delete(e)
addMark(key, value)
}
e.removeMark = key => {
if (
!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) &&
EDITOR_TO_PENDING_DIFFS.get(e)?.length
) {
// Ensure the current pending diffs originating from changes before the addMark
// are applied with the current formatting
EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null)
}
EDITOR_TO_USER_MARKS.delete(e)
removeMark(key)
}
e.deleteBackward = unit => {
if (unit !== 'line') {
return deleteBackward(unit)
}
if (e.selection && Range.isCollapsed(e.selection)) {
const parentBlockEntry = Editor.above(e, {
match: n => Element.isElement(n) && Editor.isBlock(e, n),
at: e.selection,
})
if (parentBlockEntry) {
const [, parentBlockPath] = parentBlockEntry
const parentElementRange = Editor.range(
e,
parentBlockPath,
e.selection.anchor
)
const currentLineRange = findCurrentLineRange(e, parentElementRange)
if (!Range.isCollapsed(currentLineRange)) {
Transforms.delete(e, { at: currentLineRange })
}
}
}
}
// This attempts to reset the NODE_TO_KEY entry to the correct value
// as apply() changes the object reference and hence invalidates the NODE_TO_KEY entry
e.apply = (op: Operation) => {
const matches: [Path, Key][] = []
const pathRefMatches: [PathRef, Key][] = []
const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(e)
if (pendingDiffs?.length) {
const transformed = pendingDiffs
.map(textDiff => transformTextDiff(textDiff, op))
.filter(Boolean) as TextDiff[]
EDITOR_TO_PENDING_DIFFS.set(e, transformed)
}
const pendingSelection = EDITOR_TO_PENDING_SELECTION.get(e)
if (pendingSelection) {
EDITOR_TO_PENDING_SELECTION.set(
e,
transformPendingRange(e, pendingSelection, op)
)
}
const pendingAction = EDITOR_TO_PENDING_ACTION.get(e)
if (pendingAction?.at) {
const at = Point.isPoint(pendingAction?.at)
? transformPendingPoint(e, pendingAction.at, op)
: transformPendingRange(e, pendingAction.at, op)
EDITOR_TO_PENDING_ACTION.set(e, at ? { ...pendingAction, at } : null)
}
switch (op.type) {
case 'insert_text':
case 'remove_text':
case 'set_node':
case 'split_node': {
matches.push(...getMatches(e, op.path))
break
}
case 'set_selection': {
// Selection was manually set, don't restore the user selection after the change.
EDITOR_TO_USER_SELECTION.get(e)?.unref()
EDITOR_TO_USER_SELECTION.delete(e)
break
}
case 'insert_node':
case 'remove_node': {
matches.push(...getMatches(e, Path.parent(op.path)))
break
}
case 'merge_node': {
const prevPath = Path.previous(op.path)
matches.push(...getMatches(e, prevPath))
break
}
case 'move_node': {
const commonPath = Path.common(
Path.parent(op.path),
Path.parent(op.newPath)
)
matches.push(...getMatches(e, commonPath))
let changedPath: Path
if (Path.isBefore(op.path, op.newPath)) {
matches.push(...getMatches(e, Path.parent(op.path)))
changedPath = op.newPath
} else {
matches.push(...getMatches(e, Path.parent(op.newPath)))
changedPath = op.path
}
const changedNode = Node.get(editor, Path.parent(changedPath))
const changedNodeKey = ReactEditor.findKey(e, changedNode)
const changedPathRef = Editor.pathRef(e, Path.parent(changedPath))
pathRefMatches.push([changedPathRef, changedNodeKey])
break
}
}
apply(op)
switch (op.type) {
case 'insert_node':
case 'remove_node':
case 'merge_node':
case 'move_node':
case 'split_node': {
IS_NODE_MAP_DIRTY.set(e, true)
}
}
for (const [path, key] of matches) {
const [node] = Editor.node(e, path)
NODE_TO_KEY.set(node, key)
}
for (const [pathRef, key] of pathRefMatches) {
if (pathRef.current) {
const [node] = Editor.node(e, pathRef.current)
NODE_TO_KEY.set(node, key)
}
pathRef.unref()
}
}
e.setFragmentData = (data: Pick<DataTransfer, 'getData' | 'setData'>) => {
const { selection } = e
if (!selection) {
return
}
const [start, end] = Range.edges(selection)
const startVoid = Editor.void(e, { at: start.path })
const endVoid = Editor.void(e, { at: end.path })
if (Range.isCollapsed(selection) && !startVoid) {
return
}
// Create a fake selection so that we can add a Base64-encoded copy of the
// fragment to the HTML, to decode on future pastes.
const domRange = ReactEditor.toDOMRange(e, selection)
let contents = domRange.cloneContents()
let attach = contents.childNodes[0] as HTMLElement
// Make sure attach is non-empty, since empty nodes will not get copied.
contents.childNodes.forEach(node => {
if (node.textContent && node.textContent.trim() !== '') {
attach = node as HTMLElement
}
})
// COMPAT: If the end node is a void node, we need to move the end of the
// range from the void node's spacer span, to the end of the void node's
// content, since the spacer is before void's content in the DOM.
if (endVoid) {
const [voidNode] = endVoid
const r = domRange.cloneRange()
const domNode = ReactEditor.toDOMNode(e, voidNode)
r.setEndAfter(domNode)
contents = r.cloneContents()
}
// COMPAT: If the start node is a void node, we need to attach the encoded
// fragment to the void node's content node instead of the spacer, because
// attaching it to empty `<div>/<span>` nodes will end up having it erased by
// most browsers. (2018/04/27)
if (startVoid) {
attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement
}
// Remove any zero-width space spans from the cloned DOM so that they don't
// show up elsewhere when pasted.
Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(
zw => {
const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'
zw.textContent = isNewline ? '\n' : ''
}
)
// Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
// in the HTML, and can be used for intra-Slate pasting. If it's a text
// node, wrap it in a `<span>` so we have something to set an attribute on.
if (isDOMText(attach)) {
const span = attach.ownerDocument.createElement('span')
// COMPAT: In Chrome and Safari, if we don't add the `white-space` style
// then leading and trailing spaces will be ignored. (2017/09/21)
span.style.whiteSpace = 'pre'
span.appendChild(attach)
contents.appendChild(span)
attach = span
}
const fragment = e.getFragment()
const string = JSON.stringify(fragment)
const encoded = window.btoa(encodeURIComponent(string))
attach.setAttribute('data-slate-fragment', encoded)
data.setData(`application/${clipboardFormatKey}`, encoded)
// Add the content to a <div> so that we can get its inner HTML.
const div = contents.ownerDocument.createElement('div')
div.appendChild(contents)
div.setAttribute('hidden', 'true')
contents.ownerDocument.body.appendChild(div)
data.setData('text/html', div.innerHTML)
data.setData('text/plain', getPlainText(div))
contents.ownerDocument.body.removeChild(div)
return data
}
e.insertData = (data: DataTransfer) => {
if (!e.insertFragmentData(data)) {
e.insertTextData(data)
}
}
e.insertFragmentData = (data: DataTransfer): boolean => {
/**
* Checking copied fragment from application/x-slate-fragment or data-slate-fragment
*/
const fragment =
data.getData(`application/${clipboardFormatKey}`) ||
getSlateFragmentAttribute(data)
if (fragment) {
const decoded = decodeURIComponent(window.atob(fragment))
const parsed = JSON.parse(decoded) as Node[]
e.insertFragment(parsed)
return true
}
return false
}
e.insertTextData = (data: DataTransfer): boolean => {
const text = data.getData('text/plain')
if (text) {
const lines = text.split(/\r\n|\r|\n/)
let split = false
for (const line of lines) {
if (split) {
Transforms.splitNodes(e, { always: true })
}
e.insertText(line)
split = true
}
return true
}
return false
}
const { onChange } = e
e.onChange = options => {
// COMPAT: React < 18 doesn't batch `setState` hook calls, which means
@ -373,24 +34,9 @@ export const withReact = <T extends BaseEditor>(
: (callback: () => void) => callback()
maybeBatchUpdates(() => {
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
if (onContextChange) {
onContextChange(options)
}
onChange(options)
})
}
return e
}
const getMatches = (e: Editor, path: Path) => {
const matches: [Path, Key][] = []
for (const [n, p] of Editor.levels(e, { at: path })) {
const key = ReactEditor.findKey(e, n)
matches.push([p, key])
}
return matches
}

View File

@ -1,87 +1,3 @@
import React from 'react'
export const REACT_MAJOR_VERSION = parseInt(React.version.split('.')[0], 10)
export const IS_IOS =
typeof navigator !== 'undefined' &&
typeof window !== 'undefined' &&
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
!window.MSStream
export const IS_APPLE =
typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)
export const IS_ANDROID =
typeof navigator !== 'undefined' && /Android/.test(navigator.userAgent)
export const IS_FIREFOX =
typeof navigator !== 'undefined' &&
/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)
export const IS_WEBKIT =
typeof navigator !== 'undefined' &&
/AppleWebKit(?!.*Chrome)/i.test(navigator.userAgent)
// "modern" Edge was released at 79.x
export const IS_EDGE_LEGACY =
typeof navigator !== 'undefined' &&
/Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent)
export const IS_CHROME =
typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent)
// Native `beforeInput` events don't work well with react on Chrome 75
// and older, Chrome 76+ can use `beforeInput` though.
export const IS_CHROME_LEGACY =
typeof navigator !== 'undefined' &&
/Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent)
export const IS_ANDROID_CHROME_LEGACY =
IS_ANDROID &&
typeof navigator !== 'undefined' &&
/Chrome?\/(?:[0-5]?\d)(?:\.)/i.test(navigator.userAgent)
// Firefox did not support `beforeInput` until `v87`.
export const IS_FIREFOX_LEGACY =
typeof navigator !== 'undefined' &&
/^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(
navigator.userAgent
)
// UC mobile browser
export const IS_UC_MOBILE =
typeof navigator !== 'undefined' && /.*UCBrowser/.test(navigator.userAgent)
// Wechat browser (not including mac wechat)
export const IS_WECHATBROWSER =
typeof navigator !== 'undefined' &&
/.*Wechat/.test(navigator.userAgent) &&
!/.*MacWechat/.test(navigator.userAgent) // avoid lookbehind (buggy in safari < 16.4)
// Check if DOM is available as React does internally.
// https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js
export const CAN_USE_DOM = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
)
// Check if the browser is Safari and older than 17
export const IS_SAFARI_LEGACY =
typeof navigator !== 'undefined' &&
/Safari/.test(navigator.userAgent) &&
/Version\/(\d+)/.test(navigator.userAgent) &&
(navigator.userAgent.match(/Version\/(\d+)/)?.[1]
? parseInt(navigator.userAgent.match(/Version\/(\d+)/)?.[1]!, 10) < 17
: false)
// COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event
// Chrome Legacy doesn't support `beforeinput` correctly
export const HAS_BEFORE_INPUT_SUPPORT =
(!IS_CHROME_LEGACY || !IS_ANDROID_CHROME_LEGACY) &&
!IS_EDGE_LEGACY &&
// globalThis is undefined in older browsers
typeof globalThis !== 'undefined' &&
globalThis.InputEvent &&
// @ts-ignore The `getTargetRanges` property isn't recognized.
typeof globalThis.InputEvent.prototype.getTargetRanges === 'function'

View File

@ -5,5 +5,12 @@
"rootDir": "./src",
"outDir": "./lib"
},
"references": [{ "path": "../slate" }]
"references": [
{
"path": "../slate"
},
{
"path": "../slate-dom"
}
]
}

View File

@ -13258,6 +13258,31 @@ __metadata:
languageName: node
linkType: hard
"slate-dom@npm:^0.110.2, slate-dom@workspace:packages/slate-dom":
version: 0.0.0-use.local
resolution: "slate-dom@workspace:packages/slate-dom"
dependencies:
"@babel/runtime": "npm:^7.23.2"
"@juggle/resize-observer": "npm:^3.4.0"
"@types/is-hotkey": "npm:^0.1.8"
"@types/jest": "npm:29.5.6"
"@types/jsdom": "npm:^21.1.4"
"@types/lodash": "npm:^4.14.200"
"@types/resize-observer-browser": "npm:^0.1.8"
direction: "npm:^1.0.4"
is-hotkey: "npm:^0.2.0"
is-plain-object: "npm:^5.0.0"
lodash: "npm:^4.17.21"
scroll-into-view-if-needed: "npm:^3.1.0"
slate: "npm:^0.110.2"
slate-hyperscript: "npm:^0.100.0"
source-map-loader: "npm:^4.0.1"
tiny-invariant: "npm:1.3.1"
peerDependencies:
slate: ">=0.99.0"
languageName: unknown
linkType: soft
"slate-history@workspace:*, slate-history@workspace:packages/slate-history":
version: 0.0.0-use.local
resolution: "slate-history@workspace:packages/slate-history"
@ -13391,6 +13416,7 @@ __metadata:
react-dom: "npm:^18.2.0"
scroll-into-view-if-needed: "npm:^3.1.0"
slate: "npm:^0.110.2"
slate-dom: "npm:^0.110.2"
slate-hyperscript: "npm:^0.100.0"
source-map-loader: "npm:^4.0.1"
tiny-invariant: "npm:1.3.1"
@ -13398,6 +13424,7 @@ __metadata:
react: ">=18.2.0"
react-dom: ">=18.2.0"
slate: ">=0.99.0"
slate-dom: ">=0.110.2"
languageName: unknown
linkType: soft