slate#Range TypeScript Examples
The following examples show how to use
slate#Range.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utilities.tsx From payload with MIT License | 7 votes |
wrapLink = (editor: Editor, url?: string, newTab?: boolean): void => {
const { selection, blurSelection } = editor;
if (blurSelection) {
Transforms.select(editor, blurSelection);
}
if (isElementActive(editor, 'link')) {
unwrapLink(editor);
} else {
const selectionToUse = selection || blurSelection;
const isCollapsed = selectionToUse && Range.isCollapsed(selectionToUse);
const link = {
type: 'link',
url,
newTab,
children: isCollapsed ? [{ text: url }] : [],
};
if (isCollapsed) {
Transforms.insertNodes(editor, link);
} else {
Transforms.wrapNodes(editor, link, { split: true });
Transforms.collapse(editor, { edge: 'end' });
}
}
}
Example #2
Source File: link.ts From slate-yjs-example with MIT License | 6 votes |
wrapLink = (editor: Editor, href: string): void => {
if (isLinkActive(editor)) {
unwrapLink(editor);
}
const { selection } = editor;
const isCollapsed = selection && Range.isCollapsed(selection);
const link = {
type: "link",
href,
children: isCollapsed ? [{ text: href }] : [],
};
if (isCollapsed) {
Transforms.insertNodes(editor, link);
} else {
Transforms.wrapNodes(editor, link, { split: true });
Transforms.collapse(editor, { edge: "end" });
}
}
Example #3
Source File: MessageFormatEditor.tsx From project-loved-web with MIT License | 6 votes |
function addRanges(ranges: Range[], path: Path, elements: MessageFormatElement[]): void {
for (const element of elements) {
ranges.push({
anchor: { path, offset: element.location!.start.offset },
focus: { path, offset: element.location!.end.offset },
type: element.type,
} as Range);
if (element.type === TYPE.select || element.type === TYPE.plural) {
for (const option of Object.values(element.options)) {
addRanges(ranges, path, option.value);
}
}
if (element.type === TYPE.tag) {
addRanges(ranges, path, element.children);
}
}
}
Example #4
Source File: on-code-block-keydown.ts From fantasy-editor with MIT License | 6 votes |
onKeyDownCodeBlock = (e: KeyboardEvent, editor: Editor) => {
if (isHotkey('Enter', e)) {
const match = Editor.above(editor, {
match: n => n.type === BLOCK_CODE,
});
if (match) {
const [, path] = match;
const text = Editor.string(editor, path);
e.preventDefault();
const {selection} = editor;
if(selection && Range.isCollapsed(selection)){
const start = Editor.end(editor, path);
if(text.endsWith('\n')&&Point.equals(selection.anchor, start)){
Transforms.delete(editor, {
at: {
anchor: {
offset: selection.anchor.offset-1,
path: selection.anchor.path
},
focus: selection.focus
}
})
let nextPath = Path.next(path);
Transforms.insertNodes(editor, {
type: BLOCK_PARAGRAPH,
children: [{
text: ''
}]
}, {
at: nextPath
});
Transforms.select(editor, nextPath);
return;
}
}
editor.insertText('\n');
}
}
}
Example #5
Source File: slate-plugin.ts From slate-vue with MIT License | 6 votes |
createGvm = () => {
return new Vue({
data: {
// If editor is focused
focused: false,
// selected element key
selected: {
elements: []
}
},
methods: {
updateSelected() {
const editor = GVM_TO_EDITOR.get(this) as VueEditor
const {selection} = editor
if(selection) {
this.selected.elements.forEach(node => {
const key = NODE_TO_KEY.get(node)
if(key) {
const {id} = key
const p = VueEditor.findPath(editor, node)
const range = Editor.range(editor, p)
const selected = Range.intersection(range, selection)
this.$set(this.selected, id, !!selected)
}
})
}
}
}
})
}
Example #6
Source File: Editor.tsx From react-editor-kit with MIT License | 6 votes |
handleDecorate = (
entry: NodeEntry,
plugins: Plugin[],
editor: ReactEditor
) => {
let ranges: Range[] = [];
for (let plugin of plugins) {
if (plugin.decorate) {
const result = plugin.decorate(entry, editor);
if (result) {
return (ranges = ranges.concat(result));
}
}
}
return ranges;
}
Example #7
Source File: inlines.component.ts From slate-angular with MIT License | 6 votes |
onKeydown = (event: KeyboardEvent) => {
const { selection } = this.editor;
// Default left/right behavior is unit:'character'.
// This fails to distinguish between two cursor positions, such as
// <inline>foo<cursor/></inline> vs <inline>foo</inline><cursor/>.
// Here we modify the behavior to unit:'offset'.
// This lets the user step into and out of the inline without stepping over characters.
// You may wish to customize this further to only use unit:'offset' in specific cases.
if (selection && Range.isCollapsed(selection)) {
const nativeEvent = event
if (isKeyHotkey('left', nativeEvent)) {
event.preventDefault()
Transforms.move(this.editor, { unit: 'offset', reverse: true })
return
}
if (isKeyHotkey('right', nativeEvent)) {
event.preventDefault()
Transforms.move(this.editor, { unit: 'offset' })
return
}
}
};
Example #8
Source File: button-align.tsx From fantasy-editor with MIT License | 5 votes |
ButtonAlign: FunctionComponent<Props> = props => {
const [visible, setVisible] = useState(false);
const editor = useSlate();
const match = findBlockActive(editor, alignList);
const value: string = match?.[0]?.type || BLOCK_ALIGN_LEFT;
const [mark, setMark] = useState<Range | null>(null);
const onClick = (e: any) => {
e.domEvent.preventDefault();
const selectValue = e.key;
if (selectValue !== value && mark) {
ReactEditor.focus(editor);
Transforms.select(editor, mark);
if (match) {
Transforms.unwrapNodes(editor, {
match: n => n.type === match?.[0]?.type,
split: true,
});
}
Transforms.wrapNodes(editor, {
type: selectValue,
children: [],
});
}
setVisible(false);
};
const show = () => {
const { selection } = editor;
setMark(selection);
setVisible(true);
};
const menu = (
<Menu onClick={onClick} className="fc-btn-align-overlay">
<MenuItem key={BLOCK_ALIGN_LEFT}>
<IconAlignLeft />
</MenuItem>
<MenuItem key={BLOCK_ALIGN_CENTER}>
<IconAlignCenter />
</MenuItem>
<MenuItem key={BLOCK_ALIGN_RIGHT}>
<IconAlignRight />
</MenuItem>
<MenuItem key={BLOCK_ALIGN_JUSTIFY}>
<IconAlignJustify />
</MenuItem>
</Menu>
);
return (
<Dropdown trigger={['click']} overlay={menu} visible={visible} onVisibleChange={setVisible} disabled={isBlockActive(editor, BLOCK_CODE)}>
<DropdownButton width={45} onMouseDown={show} disabled={isBlockActive(editor, BLOCK_CODE)}>
{value === BLOCK_ALIGN_CENTER ? (
<IconAlignCenter />
) : value === BLOCK_ALIGN_RIGHT ? (
<IconAlignRight />
) : value === BLOCK_ALIGN_JUSTIFY ? (
<IconAlignJustify />
) : (
<IconAlignLeft />
)}
</DropdownButton>
</Dropdown>
);
}
Example #9
Source File: Editor.d.ts From react-editor-kit with MIT License | 5 votes |
addMarkAtRange: (editor: ReactEditor, range: Range, type: string, value: any) => void
Example #10
Source File: markdown-shortcuts.component.ts From slate-angular with MIT License | 5 votes |
withShortcuts = editor => {
const { deleteBackward, insertText } = editor;
editor.insertText = text => {
const { selection } = editor;
if (text === ' ' && selection && Range.isCollapsed(selection)) {
const { anchor } = selection;
const block = Editor.above<Element>(editor, {
match: n => Editor.isBlock(editor, n)
});
const path = block ? block[1] : [];
const start = Editor.start(editor, path);
const range = { anchor, focus: start };
const beforeText = Editor.string(editor, range);
const type = SHORTCUTS[beforeText];
if (type) {
Transforms.select(editor, range);
Transforms.delete(editor);
Transforms.setNodes(editor, { type }, { match: n => Editor.isBlock(editor, n) });
if (type === 'list-item') {
const list: BulletedListElement = { type: 'bulleted-list', children: [] };
Transforms.wrapNodes<Element>(editor, list, {
match: n => !Editor.isEditor(n) && Element.isElement(n) && n.type === 'list-item'
});
}
return;
}
}
insertText(text);
};
editor.deleteBackward = (...args) => {
const { selection } = editor;
if (selection && Range.isCollapsed(selection)) {
const match = Editor.above<Element>(editor, {
match: n => Editor.isBlock(editor, n)
});
if (match) {
const [block, path] = match;
const start = Editor.start(editor, path);
if (block.type !== 'paragraph' && Point.equals(selection.anchor, start)) {
Transforms.setNodes(editor, { type: 'paragraph' });
if (block.type === 'list-item') {
Transforms.unwrapNodes(editor, {
match: n => Element.isElement(n) && n.type === 'bulleted-list',
split: true
});
}
return;
}
}
deleteBackward(...args);
}
};
return editor;
}
Example #11
Source File: editable.tsx From slate-vue with MIT License | 4 votes |
Editable = tsx.component({
// some global props will provide for child component
props: {
autoFocus: Boolean,
renderLeaf: Function,
renderElement: Function,
readOnly: Boolean,
decorate: {
type: Function,
default: defaultDecorate
},
placeholder: String,
spellCheck: Boolean,
autoCorrect: String,
autoCapitalize: String,
// user event
onBeforeInput: {
type: Function as PropsEventListener
},
onKeyDown: {
type: Function as PropsEventListener
},
onClick: {
type: Function as PropsEventListener
},
onCompositionEnd: {
type: Function as PropsEventListener
},
onCompositionStart: {
type: Function as PropsEventListener
},
onCut: {
type: Function as PropsEventListener
},
onCopy: {
type: Function as PropsEventListener
},
onDragOver: {
type: Function as PropsEventListener
},
onDragStart: {
type: Function as PropsEventListener
},
onDragStop: {
type: Function as PropsEventListener
},
onPaste: {
type: Function as PropsEventListener
},
onFocus: {
type: Function as PropsEventListener
},
onBlur: {
type: Function as PropsEventListener
},
onDrop: {
type: Function as PropsEventListener
},
},
components: {
Children
},
mixins: [SlateMixin],
provide(): object {
return {
'renderLeaf': this.renderLeaf,
'renderElement': this.renderElement,
'decorate': this.decorate,
'readOnly': this.readOnly,
'placeholder': this.placeholder
}
},
data(): EditableData & UseRef {
return {
latestElement: null,
isComposing: false,
isUpdatingSelection: false,
ref: null
}
},
methods: {
_onClick(event: IEvent) {
EditableComponent.onClick(event, this.$editor, this)
},
onSelectionchange() {
EditableComponent.onSelectionchange(this.$editor, this)
},
_onBeforeInput(event: IEvent) {
EditableComponent.onBeforeInput(event, this.$editor, this)
},
_onCompositionEnd(event: any) {
EditableComponent.onCompositionEnd(event, this.$editor, this)
},
_onCompositionStart(event: IEvent) {
EditableComponent.onCompositionStart(event, this.$editor, this)
},
_onKeyDown(event: any) {
EditableComponent.onKeyDown(event, this.$editor, this)
},
_onFocus(event: any) {
EditableComponent.onFocus(event, this.$editor, this)
},
_onBlur(event: any) {
EditableComponent.onBlur(event, this.$editor, this)
},
_onCopy(event: any) {
EditableComponent.onCopy(event, this.$editor, this)
},
_onPaste(event: any) {
EditableComponent.onPaste(event, this.$editor, this)
},
_onCut(event: any) {
EditableComponent.onCut(event, this.$editor, this)
},
_onDragOver(event: any) {
EditableComponent.onDragOver(event, this.$editor, this)
},
_onDragStart(event: any) {
EditableComponent.onDragStart(event, this.$editor, this)
},
_onDrop(event: any) {
EditableComponent.onDrop(event, this.$editor, this)
}
},
hooks() {
const ref = this.ref = useRef(null);
const editor = this.$editor;
IS_READ_ONLY.set(editor, this.readOnly)
const initListener = ()=>{
// Attach a native DOM event handler for `selectionchange`
useEffect(()=>{
document.addEventListener('selectionchange', this.onSelectionchange)
return () => {
document.removeEventListener('selectionchange', this.onSelectionchange)
}
});
};
const updateAutoFocus = () => {
useEffect(() => {
if (ref.current && this.autoFocus) {
// can't focus in current event loop?
setTimeout(()=>{
ref.current.focus()
}, 0)
}
}, [this.autoFocus])
}
const updateRef = () => {
// Update element-related weak maps with the DOM element ref.
useEffect(() => {
if (ref.current) {
EDITOR_TO_ELEMENT.set(editor, ref.current)
NODE_TO_ELEMENT.set(editor, ref.current)
ELEMENT_TO_NODE.set(ref.current, editor)
} else {
NODE_TO_ELEMENT.delete(editor)
}
})
};
const updateSelection = () => {
useEffect(() => {
const { selection } = editor
const domSelection = window.getSelection()
if (this.isComposing || !domSelection || !VueEditor.isFocused(editor)) {
return
}
const hasDomSelection = domSelection.type !== 'None'
// If the DOM selection is properly unset, we're done.
if (!selection && !hasDomSelection) {
return
}
// verify that the dom selection is in the editor
const editorElement = EDITOR_TO_ELEMENT.get(editor)!
let hasDomSelectionInEditor = false
if (
editorElement.contains(domSelection.anchorNode) &&
editorElement.contains(domSelection.focusNode)
) {
hasDomSelectionInEditor = true
}
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
if (
hasDomSelection &&
hasDomSelectionInEditor &&
selection &&
Range.equals(VueEditor.toSlateRange(editor, domSelection), selection)
) {
return
}
// Otherwise the DOM selection is out of sync, so update it.
const el = VueEditor.toDOMNode(editor, editor)
this.isUpdatingSelection = true
const newDomRange = selection && VueEditor.toDOMRange(editor, selection)
if (newDomRange) {
if (Range.isBackward(selection as Range)) {
domSelection.setBaseAndExtent(
newDomRange.endContainer,
newDomRange.endOffset,
newDomRange.startContainer,
newDomRange.startOffset
)
} else {
domSelection.setBaseAndExtent(
newDomRange.startContainer,
newDomRange.startOffset,
newDomRange.endContainer,
newDomRange.endOffset
)
}
const leafEl = newDomRange.startContainer.parentElement!
// scrollIntoView(leafEl, {
// scrollMode: 'if-needed',
// boundary: el,
// })
} else {
domSelection.removeAllRanges()
}
setTimeout(() => {
// COMPAT: In Firefox, it's not enough to create a range, you also need
// to focus the contenteditable element too. (2016/11/16)
if (newDomRange && IS_FIREFOX) {
el.focus()
}
this.isUpdatingSelection = false
})
})
}
// init selectionchange
initListener();
// Update element-related weak maps with the DOM element ref.
updateRef();
// Whenever the editor updates, make sure the DOM selection state is in sync.
updateSelection();
// The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
// needs to be manually focused.
updateAutoFocus();
// patch beforeinput in FireFox
if(IS_FIREFOX) {
useEffect(() => {
addOnBeforeInput(ref.current, true)
}, [])
}
},
render() {
const editor = this.$editor;
const {ref} = this;
// name must be corresponded with standard
const on: any = {
click: this._onClick,
keydown: this._onKeyDown,
focus: this._onFocus,
blur: this._onBlur,
beforeinput: this._onBeforeInput,
copy: this._onCopy,
cut: this._onCut,
compositionend: this._onCompositionEnd,
compositionstart: this._onCompositionStart,
dragover: this._onDragOver,
dragstart: this._onDragStart,
drop: this._onDrop,
paste: this._onPaste
};
const attrs = {
spellcheck: !HAS_BEFORE_INPUT_SUPPORT ? undefined : this.spellCheck,
autocorrect: !HAS_BEFORE_INPUT_SUPPORT ? undefined : this.autoCorrect,
autocapitalize: !HAS_BEFORE_INPUT_SUPPORT ? undefined : this.autoCapitalize,
}
return (
<div
// COMPAT: The Grammarly Chrome extension works by changing the DOM
// out from under `contenteditable` elements, which leads to weird
// behaviors so we have to disable it like editor. (2017/04/24)
data-gramm={false}
role={this.readOnly ? undefined : 'textbox'}
ref = {(ref as any).id}
contenteditable={this.readOnly ? false : true}
data-slate-editor
data-slate-node="value"
style={{
// Prevent the default outline styles.
outline: 'none',
// Preserve adjacent whitespace and new lines.
whiteSpace: 'pre-wrap',
// Allow words to break if they are too long.
wordWrap: 'break-word',
// Allow for passed-in styles to override anything.
// ...style,
}}
{...{on}}
{...{attrs}}
>
<Children
node={editor}
selection={editor.selection}
/>
</div>
)
}
})
Example #12
Source File: Plugin.d.ts From react-editor-kit with MIT License | 4 votes |
createPlugin: (plugin: Plugin, ...triggers: Trigger[]) => {
triggers: Trigger[];
/**
* A name means your plugin can be called inside of React components
* that existing under the <EditorKit> context scope using usePlugin("name").
*
* Names also allow you to replace internal plugins with your own. When you
* register your plugins to <EditorKit plugins={[...]}>, the library will
* check the names of each if it finds a name of an internal plugin then it will
* swap the internal plugin with the plugin you registered. This allows you to
* inject your own icons into Editor Kit by providing a plugin named "icon-provider",
* for example.
*/
name: string;
/**
* The order the Plugin is registered is important if you listen on events
* that can only be handled by a single plugin. For example, EditorKit will
* return early onKeyDown once a Plugin returns true from onKeyDown.
*
* Order can also be important for Plugins that contribute CSS (see below)
* due to CSS cascading behaviour.
*
* Default order is the position in the Plugin array passed to EditorKit.
*
*/
order?: number;
/**
* Extend the Slate editor with additional functionality or
* wrap existing functions like normalizeNode
*/
withPlugin?(editor: ReactEditor): ReactEditor;
/**
* Array of actions that plugin provides. A name is required for each
* if more than one is available.
*/
actions?: PluginAction[];
/**
* Contribute style to an Element without having to render it. This avoids
* having to create unnecessary wrapper Elements just to change things like
* text-align of existing Elements.
*/
styleElement?: (props: RenderElementProps) => CSSProperties | undefined;
/**
* Contribute classes to an Element without having to render it.
* Similar to `styleElement` above.
*/
getClasses?: (element: Element) => string | undefined;
/**
* Render an Element into the Editor. You can output any JSX content you like
* here according to the `props.element.type`
*
* The is the same as Slate's renderElement function and requires that
* you spread `props.attributes` on your root element and pass
* the `props.children` prop to your lowest JSX element.
*/
renderElement?: (props: RenderElementProps) => JSX.Element | undefined;
/**
* Render an Leaf into the Editor. You can output any JSX content you like
* here according to the `props.leaf.type`. This Leaf content will become the
* child of an Element that gets rendered by renderElement above.
*
* The is the same as Slate's renderLeaf function and requires that
* you spread `props.attributes` on your root element and pass
* the `props.children` prop to your lowest JSX element.
*/
renderLeaf?: (props: RenderLeafProps, state: EditorState) => JSX.Element | undefined;
/**
*
*/
decorate?: (entry: NodeEntry<import("slate").Node>, editor: ReactEditor) => Range[];
/**
* Similar to triggers but typical used for keyboard shortcuts or modifying the
* Editors handling of delete/backspace or enter for certain elements
*/
onKey?: KeyHandler[];
/**
* Respond to key down events on the Editor element. Use onKey if you are interested in a certain
* key or key combo.
*/
onKeyDown?(event: React.KeyboardEvent, state: EditorState): boolean | undefined;
/**
* Respond to click events that happen on the Editor element.
*/
onClick?(event: React.MouseEvent<HTMLElement>, state: EditorState): void;
/**
* Allows contributing to the context menu and overriding the browser's default.
* See ContextMenuContribution for more info.
*/
onContextMenu?: ContextMenuContribution[];
/**
* Respond to drop events that happen on the Editor element.
*/
onDrop?(event: React.DragEvent, state: EditorState): boolean;
/**
* Styles that should be applied to the Editor instance. EditorKit uses the npm
* package stylis and supports nested selectors like:
*
* .someClass {
* &:hover {
*
* }
* }
*
*/
editorStyle?: string | StyleFunction;
/**
* These styles can target the whole page so be careful. Things like dialogs and toolbars
* are rendered outside of the Editor and can be targeted here.
*/
globalStyle?: string | StyleFunction;
}
Example #13
Source File: withShortcuts.ts From ui-schema with MIT License | 4 votes |
withShortcuts: (options: CustomOptions) => SlateHocType<ReactEditor> = (options) => (editor) => {
if(options.onlyInline){
return editor
}
const {deleteBackward, insertText} = editor
editor.insertText = (text: string) => {
const {selection} = editor
if (text === ' ' && selection && Range.isCollapsed(selection)) {
const {anchor} = selection
const block = Editor.above(editor, {
match: n => Editor.isBlock(editor, n),
})
const path = block ? block[1] : []
const start = Editor.start(editor, path)
const range = {anchor, focus: start}
let beforeText = Editor.string(editor, range)
const dotPos = beforeText.indexOf('.')
if (dotPos !== -1 && !isNaN(Number(beforeText.slice(0, dotPos)))) {
beforeText = '1'
}
const type = SHORTCUTS[beforeText]
if (
type &&
editorIsEnabled(options.enableOnly, type) &&
// only apply shortcuts when not inside a li, as this may produce invalid data
// @ts-ignore
!(block && block[0].type === pluginOptions.li.type)
) {
Transforms.select(editor, range)
Transforms.delete(editor)
const newProperties: Partial<SlateElement> = {
// @ts-ignore
type: type === pluginOptions.ol.type ? pluginOptions.li.type : type,
}
Transforms.setNodes(editor, newProperties, {
match: n => Editor.isBlock(editor, n),
})
if (type === pluginOptions.ol.type) {
const list: BulletedListElement = {
type: pluginOptions.ol.type,
children: [],
}
// todo: here seems to be the `li` error, currently missing `ul` around `li`
Transforms.wrapNodes(editor, list, {
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
// @ts-ignore
n.type === pluginOptions.li.type,
})
} else if (type === pluginOptions.li.type) {
const list: BulletedListElement = {
type: pluginOptions.ul.type,
children: [],
}
// todo: here seems to be the `li` error, currently missing `ul` around `li`
Transforms.wrapNodes(editor, list, {
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
// @ts-ignore
n.type === pluginOptions.li.type,
})
}
return
}
}
insertText(text)
}
editor.deleteBackward = (...args) => {
const {selection} = editor
if (selection && Range.isCollapsed(selection)) {
const match = Editor.above(editor, {
match: n => Editor.isBlock(editor, n),
})
if (match) {
const [block, path] = match
const start = Editor.start(editor, path)
if (
!Editor.isEditor(block) &&
SlateElement.isElement(block) &&
// @ts-ignore
block.type !== pluginOptions.p.type &&
Point.equals(selection.anchor, start)
) {
const newProperties: Partial<SlateElement> = {
// @ts-ignore
type: pluginOptions.p.type,
}
Transforms.setNodes(editor, newProperties)
// @ts-ignore
if (block.type === pluginOptions.li.type) {
Transforms.unwrapNodes(editor, {
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
(
// @ts-ignore
n.type === pluginOptions.ul.type ||
// @ts-ignore
n.type === pluginOptions.ol.type
),
split: true,
})
}
return
}
}
deleteBackward(...args)
}
}
return editor
}
Example #14
Source File: editable.component.ts From slate-angular with MIT License | 4 votes |
toNativeSelection() {
try {
const { selection } = this.editor;
const root = AngularEditor.findDocumentOrShadowRoot(this.editor)
const domSelection = (root as Document).getSelection();
if (this.isComposing || !domSelection || !AngularEditor.isFocused(this.editor)) {
return;
}
const hasDomSelection = domSelection.type !== 'None';
// If the DOM selection is properly unset, we're done.
if (!selection && !hasDomSelection) {
return;
}
// If the DOM selection is already correct, we're done.
// verify that the dom selection is in the editor
const editorElement = EDITOR_TO_ELEMENT.get(this.editor)!;
let hasDomSelectionInEditor = false;
if (editorElement.contains(domSelection.anchorNode) && editorElement.contains(domSelection.focusNode)) {
hasDomSelectionInEditor = true;
}
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
if (
hasDomSelection &&
hasDomSelectionInEditor &&
selection &&
hasStringTarget(domSelection) &&
Range.equals(AngularEditor.toSlateRange(this.editor, domSelection), selection)
) {
return;
}
// when <Editable/> is being controlled through external value
// then its children might just change - DOM responds to it on its own
// but Slate's value is not being updated through any operation
// and thus it doesn't transform selection on its own
if (selection && !AngularEditor.hasRange(this.editor, selection)) {
this.editor.selection = AngularEditor.toSlateRange(this.editor, domSelection);
return
}
// Otherwise the DOM selection is out of sync, so update it.
const el = AngularEditor.toDOMNode(this.editor, this.editor);
this.isUpdatingSelection = true;
const newDomRange = selection && AngularEditor.toDOMRange(this.editor, selection);
if (newDomRange) {
// COMPAT: Since the DOM range has no concept of backwards/forwards
// we need to check and do the right thing here.
if (Range.isBackward(selection)) {
// eslint-disable-next-line max-len
domSelection.setBaseAndExtent(
newDomRange.endContainer,
newDomRange.endOffset,
newDomRange.startContainer,
newDomRange.startOffset
);
} else {
// eslint-disable-next-line max-len
domSelection.setBaseAndExtent(
newDomRange.startContainer,
newDomRange.startOffset,
newDomRange.endContainer,
newDomRange.endOffset
);
}
} else {
domSelection.removeAllRanges();
}
setTimeout(() => {
// COMPAT: In Firefox, it's not enough to create a range, you also need
// to focus the contenteditable element too. (2016/11/16)
if (newDomRange && IS_FIREFOX) {
el.focus();
}
this.isUpdatingSelection = false;
});
} catch (error) {
this.editor.onError({ code: SlateErrorCode.ToNativeSelectionError, nativeError: error })
}
}