mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-23 23:42:56 +02:00
* mvp implementation for working with non-global window instances * remove unused element renderer * fix typo in comment * fix wrong example reference * Add @babel/helper-call-delegate to fix build error Co-authored-by: Lukas Buenger <lukasbuenger@gmail.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
||||
DOMElement,
|
||||
DOMNode,
|
||||
DOMRange,
|
||||
getDefaultView,
|
||||
isDOMElement,
|
||||
isDOMNode,
|
||||
isDOMText,
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
NODE_TO_ELEMENT,
|
||||
IS_FOCUSED,
|
||||
PLACEHOLDER_SYMBOL,
|
||||
EDITOR_TO_WINDOW,
|
||||
} from '../utils/weak-maps'
|
||||
|
||||
// COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event
|
||||
@@ -132,7 +134,9 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
// Update element-related weak maps with the DOM element ref.
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (ref.current) {
|
||||
let window
|
||||
if (ref.current && (window = getDefaultView(ref.current))) {
|
||||
EDITOR_TO_WINDOW.set(editor, window)
|
||||
EDITOR_TO_ELEMENT.set(editor, ref.current)
|
||||
NODE_TO_ELEMENT.set(editor, ref.current)
|
||||
ELEMENT_TO_NODE.set(ref.current, editor)
|
||||
@@ -144,6 +148,7 @@ export const Editable = (props: EditableProps) => {
|
||||
// Whenever the editor updates, make sure the DOM selection state is in sync.
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const { selection } = editor
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
const domSelection = window.getSelection()
|
||||
|
||||
if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
|
||||
@@ -354,8 +359,9 @@ export const Editable = (props: EditableProps) => {
|
||||
case 'insertFromYank':
|
||||
case 'insertReplacementText':
|
||||
case 'insertText': {
|
||||
if (data instanceof DataTransfer) {
|
||||
ReactEditor.insertData(editor, data)
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
if (data instanceof window.DataTransfer) {
|
||||
ReactEditor.insertData(editor, data as DataTransfer)
|
||||
} else if (typeof data === 'string') {
|
||||
Editor.insertText(editor, data)
|
||||
}
|
||||
@@ -394,6 +400,7 @@ export const Editable = (props: EditableProps) => {
|
||||
const onDOMSelectionChange = useCallback(
|
||||
throttle(() => {
|
||||
if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
const { activeElement } = window.document
|
||||
const el = ReactEditor.toDOMNode(editor, editor)
|
||||
const domSelection = window.getSelection()
|
||||
@@ -436,6 +443,7 @@ export const Editable = (props: EditableProps) => {
|
||||
// fire for any change to the selection inside the editor. (2019/11/04)
|
||||
// https://github.com/facebook/react/issues/5785
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
window.document.addEventListener('selectionchange', onDOMSelectionChange)
|
||||
|
||||
return () => {
|
||||
@@ -527,6 +535,8 @@ export const Editable = (props: EditableProps) => {
|
||||
return
|
||||
}
|
||||
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
|
||||
// COMPAT: If the current `activeElement` is still the previous
|
||||
// one, this is due to the window being blurred when the tab
|
||||
// itself becomes unfocused, so we want to abort early to allow to
|
||||
@@ -735,6 +745,7 @@ export const Editable = (props: EditableProps) => {
|
||||
!isEventHandled(event, attributes.onFocus)
|
||||
) {
|
||||
const el = ReactEditor.toDOMNode(editor, editor)
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
state.latestElement = window.document.activeElement
|
||||
|
||||
// COMPAT: If the editor has nested editable elements, the focus
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
NODE_TO_INDEX,
|
||||
NODE_TO_KEY,
|
||||
NODE_TO_PARENT,
|
||||
EDITOR_TO_WINDOW,
|
||||
} from '../utils/weak-maps'
|
||||
import {
|
||||
DOMElement,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
DOMStaticRange,
|
||||
isDOMElement,
|
||||
normalizeDOMPoint,
|
||||
isDOMSelection,
|
||||
} from '../utils/dom'
|
||||
|
||||
/**
|
||||
@@ -32,6 +34,18 @@ export interface ReactEditor extends Editor {
|
||||
}
|
||||
|
||||
export const ReactEditor = {
|
||||
/**
|
||||
* Return the host window of the current editor.
|
||||
*/
|
||||
|
||||
getWindow(editor: ReactEditor): Window {
|
||||
const window = EDITOR_TO_WINDOW.get(editor)
|
||||
if (!window) {
|
||||
throw new Error('Unable to find a host window element for this editor')
|
||||
}
|
||||
return window
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a key for a Slate node.
|
||||
*/
|
||||
@@ -104,7 +118,7 @@ export const ReactEditor = {
|
||||
blur(editor: ReactEditor): void {
|
||||
const el = ReactEditor.toDOMNode(editor, editor)
|
||||
IS_FOCUSED.set(editor, false)
|
||||
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
if (window.document.activeElement === el) {
|
||||
el.blur()
|
||||
}
|
||||
@@ -118,6 +132,7 @@ export const ReactEditor = {
|
||||
const el = ReactEditor.toDOMNode(editor, editor)
|
||||
IS_FOCUSED.set(editor, true)
|
||||
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
if (window.document.activeElement !== el) {
|
||||
el.focus({ preventScroll: true })
|
||||
}
|
||||
@@ -129,6 +144,7 @@ export const ReactEditor = {
|
||||
|
||||
deselect(editor: ReactEditor): void {
|
||||
const { selection } = editor
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
const domSelection = window.getSelection()
|
||||
|
||||
if (domSelection && domSelection.rangeCount > 0) {
|
||||
@@ -284,6 +300,7 @@ export const ReactEditor = {
|
||||
? domAnchor
|
||||
: ReactEditor.toDOMPoint(editor, focus)
|
||||
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
const domRange = window.document.createRange()
|
||||
const [startNode, startOffset] = isBackward ? domFocus : domAnchor
|
||||
const [endNode, endOffset] = isBackward ? domAnchor : domFocus
|
||||
@@ -410,6 +427,7 @@ export const ReactEditor = {
|
||||
// can determine what the offset relative to the text node is.
|
||||
if (leafNode) {
|
||||
textNode = leafNode.closest('[data-slate-node="text"]')!
|
||||
const window = ReactEditor.getWindow(editor)
|
||||
const range = window.document.createRange()
|
||||
range.setStart(textNode, 0)
|
||||
range.setEnd(nearestNode, nearestOffset)
|
||||
@@ -476,10 +494,9 @@ export const ReactEditor = {
|
||||
editor: ReactEditor,
|
||||
domRange: DOMRange | DOMStaticRange | DOMSelection
|
||||
): Range {
|
||||
const el =
|
||||
domRange instanceof Selection
|
||||
? domRange.anchorNode
|
||||
: domRange.startContainer
|
||||
const el = isDOMSelection(domRange)
|
||||
? domRange.anchorNode
|
||||
: domRange.startContainer
|
||||
let anchorNode
|
||||
let anchorOffset
|
||||
let focusNode
|
||||
@@ -487,7 +504,7 @@ export const ReactEditor = {
|
||||
let isCollapsed
|
||||
|
||||
if (el) {
|
||||
if (domRange instanceof Selection) {
|
||||
if (isDOMSelection(domRange)) {
|
||||
anchorNode = domRange.anchorNode
|
||||
anchorOffset = domRange.anchorOffset
|
||||
focusNode = domRange.focusNode
|
||||
|
@@ -23,8 +23,26 @@ export {
|
||||
DOMStaticRange,
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Selection: typeof Selection['constructor']
|
||||
DataTransfer: typeof DataTransfer['constructor']
|
||||
Node: typeof Node['constructor']
|
||||
}
|
||||
}
|
||||
|
||||
export type DOMPoint = [Node, number]
|
||||
|
||||
/**
|
||||
* Returns the host window of a DOM node
|
||||
*/
|
||||
|
||||
export const getDefaultView = (value: any): Window | null => {
|
||||
return (
|
||||
(value && value.ownerDocument && value.ownerDocument.defaultView) || null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a DOM node is a comment node.
|
||||
*/
|
||||
@@ -46,7 +64,17 @@ export const isDOMElement = (value: any): value is DOMElement => {
|
||||
*/
|
||||
|
||||
export const isDOMNode = (value: any): value is DOMNode => {
|
||||
return value instanceof Node
|
||||
const window = getDefaultView(value)
|
||||
return !!window && value instanceof window.Node
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a DOM selection.
|
||||
*/
|
||||
|
||||
export const isDOMSelection = (value: any): value is DOMSelection => {
|
||||
const window = value && value.anchorNode && getDefaultView(value.anchorNode)
|
||||
return !!window && value instanceof window.Selection
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,7 +14,7 @@ export const NODE_TO_PARENT: WeakMap<Node, Ancestor> = new WeakMap()
|
||||
* Weak maps that allow us to go between Slate nodes and DOM nodes. These
|
||||
* are used to resolve DOM event-related logic into Slate actions.
|
||||
*/
|
||||
|
||||
export const EDITOR_TO_WINDOW: WeakMap<Editor, Window> = new WeakMap()
|
||||
export const EDITOR_TO_ELEMENT: WeakMap<Editor, HTMLElement> = new WeakMap()
|
||||
export const EDITOR_TO_PLACEHOLDER: WeakMap<Editor, string> = new WeakMap()
|
||||
export const ELEMENT_TO_NODE: WeakMap<HTMLElement, Node> = new WeakMap()
|
||||
|
Reference in New Issue
Block a user