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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 })
        }
    }