@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
197 lines (187 loc) • 7.17 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
var list = require('@lexical/list');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var utils = require('@lexical/utils');
var lexical = require('lexical');
var react = require('react');
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
function CheckListPlugin() {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
react.useEffect(() => {
return utils.mergeRegister(editor.registerCommand(list.INSERT_CHECK_LIST_COMMAND, () => {
list.insertList(editor, 'check');
return true;
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => {
return handleArrownUpOrDown(event, editor, false);
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => {
return handleArrownUpOrDown(event, editor, true);
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, event => {
const activeItem = getActiveCheckListItem();
if (activeItem != null) {
const rootElement = editor.getRootElement();
if (rootElement != null) {
rootElement.focus();
}
return true;
}
return false;
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_SPACE_COMMAND, event => {
const activeItem = getActiveCheckListItem();
if (activeItem != null && editor.isEditable()) {
editor.update(() => {
const listItemNode = lexical.$getNearestNodeFromDOMNode(activeItem);
if (list.$isListItemNode(listItemNode)) {
event.preventDefault();
listItemNode.toggleChecked();
}
});
return true;
}
return false;
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => {
return editor.getEditorState().read(() => {
const selection = lexical.$getSelection();
if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
const {
anchor
} = selection;
const isElement = anchor.type === 'element';
if (isElement || anchor.offset === 0) {
const anchorNode = anchor.getNode();
const elementNode = utils.$findMatchingParent(anchorNode, node => lexical.$isElementNode(node) && !node.isInline());
if (list.$isListItemNode(elementNode)) {
const parent = elementNode.getParent();
if (list.$isListNode(parent) && parent.getListType() === 'check' && (isElement || elementNode.getFirstDescendant() === anchorNode)) {
const domNode = editor.getElementByKey(elementNode.__key);
if (domNode != null && document.activeElement !== domNode) {
domNode.focus();
event.preventDefault();
return true;
}
}
}
}
}
return false;
});
}, lexical.COMMAND_PRIORITY_LOW), editor.registerRootListener((rootElement, prevElement) => {
if (rootElement !== null) {
rootElement.addEventListener('click', handleClick);
rootElement.addEventListener('pointerdown', handlePointerDown);
}
if (prevElement !== null) {
prevElement.removeEventListener('click', handleClick);
prevElement.removeEventListener('pointerdown', handlePointerDown);
}
}));
});
return null;
}
function handleCheckItemEvent(event, callback) {
const target = event.target;
if (target === null || !utils.isHTMLElement(target)) {
return;
}
// Ignore clicks on LI that have nested lists
const firstChild = target.firstChild;
if (firstChild != null && utils.isHTMLElement(firstChild) && (firstChild.tagName === 'UL' || firstChild.tagName === 'OL')) {
return;
}
const parentNode = target.parentNode;
// @ts-ignore internal field
if (!parentNode || parentNode.__lexicalListType !== 'check') {
return;
}
const rect = target.getBoundingClientRect();
const pageX = event.pageX / utils.calculateZoomLevel(target);
if (target.dir === 'rtl' ? pageX < rect.right && pageX > rect.right - 20 : pageX > rect.left && pageX < rect.left + 20) {
callback();
}
}
function handleClick(event) {
handleCheckItemEvent(event, () => {
if (event.target instanceof HTMLElement) {
const domNode = event.target;
const editor = lexical.getNearestEditorFromDOMNode(domNode);
if (editor != null && editor.isEditable()) {
editor.update(() => {
const node = lexical.$getNearestNodeFromDOMNode(domNode);
if (list.$isListItemNode(node)) {
domNode.focus();
node.toggleChecked();
}
});
}
}
});
}
function handlePointerDown(event) {
handleCheckItemEvent(event, () => {
// Prevents caret moving when clicking on check mark
event.preventDefault();
});
}
function getActiveCheckListItem() {
const activeElement = document.activeElement;
return activeElement != null && activeElement.tagName === 'LI' && activeElement.parentNode != null &&
// @ts-ignore internal field
activeElement.parentNode.__lexicalListType === 'check' ? activeElement : null;
}
function findCheckListItemSibling(node, backward) {
let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
let parent = node;
// Going up in a tree to get non-null sibling
while (sibling == null && list.$isListItemNode(parent)) {
// Get li -> parent ul/ol -> parent li
parent = parent.getParentOrThrow().getParent();
if (parent != null) {
sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
}
}
// Going down in a tree to get first non-nested list item
while (list.$isListItemNode(sibling)) {
const firstChild = backward ? sibling.getLastChild() : sibling.getFirstChild();
if (!list.$isListNode(firstChild)) {
return sibling;
}
sibling = backward ? firstChild.getLastChild() : firstChild.getFirstChild();
}
return null;
}
function handleArrownUpOrDown(event, editor, backward) {
const activeItem = getActiveCheckListItem();
if (activeItem != null) {
editor.update(() => {
const listItem = lexical.$getNearestNodeFromDOMNode(activeItem);
if (!list.$isListItemNode(listItem)) {
return;
}
const nextListItem = findCheckListItemSibling(listItem, backward);
if (nextListItem != null) {
nextListItem.selectStart();
const dom = editor.getElementByKey(nextListItem.__key);
if (dom != null) {
event.preventDefault();
setTimeout(() => {
dom.focus();
}, 0);
}
}
});
}
return false;
}
exports.CheckListPlugin = CheckListPlugin;