@lexical/list
Advanced tools
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { LexicalCommand, LexicalEditor } from 'lexical'; | ||
| import { Signal } from '@lexical/extension'; | ||
| export declare const INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>; | ||
| /** | ||
| * Registers the checklist plugin with the editor. | ||
| * @param editor The LexicalEditor instance. | ||
| * @param options Optional configuration. | ||
| * - disableTakeFocusOnClick: If true, clicking a checklist item will not focus the editor (useful for mobile). | ||
| */ | ||
| export declare function registerCheckList(editor: LexicalEditor, options?: { | ||
| disableTakeFocusOnClick?: boolean | Signal<boolean>; | ||
| }): () => void; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import { ListItemNode, ListNode } from './'; | ||
| import { ListType } from './LexicalListNode'; | ||
| /** | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export declare function $insertList(listType: ListType): void; | ||
| /** | ||
| * A recursive function that goes through each list and their children, including nested lists, | ||
| * appending list2 children after list1 children and updating ListItemNode values. | ||
| * @param list1 - The first list to be merged. | ||
| * @param list2 - The second list to be merged. | ||
| */ | ||
| export declare function mergeLists(list1: ListNode, list2: ListNode): void; | ||
| /** | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| */ | ||
| export declare function $removeList(): void; | ||
| /** | ||
| * Takes the value of a child ListItemNode and makes it the value the ListItemNode | ||
| * should be if it isn't already. Also ensures that checked is undefined if the | ||
| * parent does not have a list type of 'check'. | ||
| * @param list - The list whose children are updated. | ||
| */ | ||
| export declare function updateChildrenListItemValue(list: ListNode): void; | ||
| /** | ||
| * Merge the next sibling list if same type. | ||
| * <ul> will merge with <ul>, but NOT <ul> with <ol>. | ||
| * @param list - The list whose next sibling should be potentially merged | ||
| */ | ||
| export declare function mergeNextSiblingListIfSameType(list: ListNode): void; | ||
| /** | ||
| * Adds an empty ListNode/ListItemNode chain at listItemNode, so as to | ||
| * create an indent effect. Won't indent ListItemNodes that have a ListNode as | ||
| * a child, but does merge sibling ListItemNodes if one has a nested ListNode. | ||
| * @param listItemNode - The ListItemNode to be indented. | ||
| */ | ||
| export declare function $handleIndent(listItemNode: ListItemNode): void; | ||
| /** | ||
| * Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode | ||
| * has a great grandparent node of type ListNode, which is where the ListItemNode will reside | ||
| * within as a child. | ||
| * @param listItemNode - The ListItemNode to remove the indent (outdent). | ||
| */ | ||
| export declare function $handleOutdent(listItemNode: ListItemNode): void; | ||
| /** | ||
| * Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode | ||
| * or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode | ||
| * (which should be the parent node) and insert the ParagraphNode as a sibling to the ListNode. If the ListNode is | ||
| * nested in a ListItemNode instead, it will add the ParagraphNode after the grandparent ListItemNode. | ||
| * Throws an invariant if the selection is not a child of a ListNode. | ||
| * @returns true if a ParagraphNode was inserted successfully, false if there is no selection | ||
| * or the selection does not contain a ListItemNode or the node already holds text. | ||
| */ | ||
| export declare function $handleListInsertParagraph(restoreNumbering?: boolean): boolean; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { SerializedListItemNode } from './LexicalListItemNode'; | ||
| import type { ListNodeTagType, ListType, SerializedListNode } from './LexicalListNode'; | ||
| import type { LexicalEditor } from 'lexical'; | ||
| import { INSERT_CHECK_LIST_COMMAND, registerCheckList } from './checkList'; | ||
| import { $handleListInsertParagraph, $insertList, $removeList } from './formatList'; | ||
| import { $createListItemNode, $isListItemNode, ListItemNode } from './LexicalListItemNode'; | ||
| import { $createListNode, $isListNode, ListNode } from './LexicalListNode'; | ||
| import { $getListDepth } from './utils'; | ||
| export { type CheckListConfig, CheckListExtension, type ListConfig, ListExtension, } from './LexicalListExtension'; | ||
| export { ListImportExtension, ListImportRules, ListSchema, } from './ListImportExtension'; | ||
| export { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, registerList, type RegisterListOptions, registerListStrictIndentTransform, REMOVE_LIST_COMMAND, UPDATE_LIST_START_COMMAND, } from './registerList'; | ||
| export { $createListItemNode, $createListNode, $getListDepth, $handleListInsertParagraph, $insertList, $isListItemNode, $isListNode, $removeList, INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode, ListNodeTagType, ListType, registerCheckList, SerializedListItemNode, SerializedListNode, }; | ||
| /** | ||
| * @deprecated use {@link $insertList} from an update or command listener. | ||
| * | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param editor - The lexical editor. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export declare function insertList(editor: LexicalEditor, listType: ListType): void; | ||
| /** | ||
| * @deprecated use {@link $removeList} from an update or command listener. | ||
| * | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| * @param editor - The lexical editor. | ||
| */ | ||
| export declare function removeList(editor: LexicalEditor): void; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| /** | ||
| * 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' | ||
| const LexicalList = process.env.NODE_ENV !== 'production' ? require('./LexicalList.dev.js') : require('./LexicalList.prod.js'); | ||
| module.exports = LexicalList; |
Sorry, the diff of this file is not supported yet
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import * as modDev from './LexicalList.dev.mjs'; | ||
| import * as modProd from './LexicalList.prod.mjs'; | ||
| const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd; | ||
| export const $createListItemNode = mod.$createListItemNode; | ||
| export const $createListNode = mod.$createListNode; | ||
| export const $getListDepth = mod.$getListDepth; | ||
| export const $handleListInsertParagraph = mod.$handleListInsertParagraph; | ||
| export const $insertList = mod.$insertList; | ||
| export const $isListItemNode = mod.$isListItemNode; | ||
| export const $isListNode = mod.$isListNode; | ||
| export const $removeList = mod.$removeList; | ||
| export const CheckListExtension = mod.CheckListExtension; | ||
| export const INSERT_CHECK_LIST_COMMAND = mod.INSERT_CHECK_LIST_COMMAND; | ||
| export const INSERT_ORDERED_LIST_COMMAND = mod.INSERT_ORDERED_LIST_COMMAND; | ||
| export const INSERT_UNORDERED_LIST_COMMAND = mod.INSERT_UNORDERED_LIST_COMMAND; | ||
| export const ListExtension = mod.ListExtension; | ||
| export const ListImportExtension = mod.ListImportExtension; | ||
| export const ListImportRules = mod.ListImportRules; | ||
| export const ListItemNode = mod.ListItemNode; | ||
| export const ListNode = mod.ListNode; | ||
| export const ListSchema = mod.ListSchema; | ||
| export const REMOVE_LIST_COMMAND = mod.REMOVE_LIST_COMMAND; | ||
| export const UPDATE_LIST_START_COMMAND = mod.UPDATE_LIST_START_COMMAND; | ||
| export const insertList = mod.insertList; | ||
| export const registerCheckList = mod.registerCheckList; | ||
| export const registerList = mod.registerList; | ||
| export const registerListStrictIndentTransform = mod.registerListStrictIndentTransform; | ||
| export const removeList = mod.removeList; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalList.dev.mjs') : import('./LexicalList.prod.mjs')); | ||
| export const $createListItemNode = mod.$createListItemNode; | ||
| export const $createListNode = mod.$createListNode; | ||
| export const $getListDepth = mod.$getListDepth; | ||
| export const $handleListInsertParagraph = mod.$handleListInsertParagraph; | ||
| export const $insertList = mod.$insertList; | ||
| export const $isListItemNode = mod.$isListItemNode; | ||
| export const $isListNode = mod.$isListNode; | ||
| export const $removeList = mod.$removeList; | ||
| export const CheckListExtension = mod.CheckListExtension; | ||
| export const INSERT_CHECK_LIST_COMMAND = mod.INSERT_CHECK_LIST_COMMAND; | ||
| export const INSERT_ORDERED_LIST_COMMAND = mod.INSERT_ORDERED_LIST_COMMAND; | ||
| export const INSERT_UNORDERED_LIST_COMMAND = mod.INSERT_UNORDERED_LIST_COMMAND; | ||
| export const ListExtension = mod.ListExtension; | ||
| export const ListImportExtension = mod.ListImportExtension; | ||
| export const ListImportRules = mod.ListImportRules; | ||
| export const ListItemNode = mod.ListItemNode; | ||
| export const ListNode = mod.ListNode; | ||
| export const ListSchema = mod.ListSchema; | ||
| export const REMOVE_LIST_COMMAND = mod.REMOVE_LIST_COMMAND; | ||
| export const UPDATE_LIST_START_COMMAND = mod.UPDATE_LIST_START_COMMAND; | ||
| export const insertList = mod.insertList; | ||
| export const registerCheckList = mod.registerCheckList; | ||
| export const registerList = mod.registerList; | ||
| export const registerListStrictIndentTransform = mod.registerListStrictIndentTransform; | ||
| export const removeList = mod.removeList; |
| /** | ||
| * 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 e=require("@lexical/utils"),t=require("lexical"),n=require("@lexical/extension"),r=require("@lexical/html");function i(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function s(e){let t=1,n=e.getParent();for(;null!=n;){if(O(n)){const e=n.getParent();if(M(e)){t++,n=e.getParent();continue}i(40)}return t}return t}function o(e){let t=e.getParent();M(t)||i(40);let n=t;for(;null!==n;)n=n.getParent(),M(n)&&(t=n);return t}function l(e){let t=[];const n=e.getChildren().filter(O);for(let e=0;e<n.length;e++){const r=n[e],i=r.getFirstChild();M(i)?t=t.concat(l(i)):t.push(r)}return t}function c(e){return O(e)&&M(e.getFirstChild())}function a(e,t){return O(e)&&(0===t.length||1===t.length&&e.is(t[0])&&0===e.getChildrenSize())}function d(e){const n=t.$getSelection();if(null!==n){let r=n.getNodes();if(t.$isRangeSelection(n)){const[i]=n.getStartEndPoints(),s=i.getNode(),o=s.getParent();if(t.$isRootOrShadowRoot(s)){const e=s.getFirstChild();if(e)r=e.selectStart().getNodes();else{const e=t.$createParagraphNode();s.append(e),r=e.select().getNodes()}}else if(a(s,r)){const n=k(e);if(t.$isRootOrShadowRoot(o)){s.replace(n);const e=x();t.$isElementNode(s)&&(e.setFormat(s.getFormatType()),e.setIndent(s.getIndent())),n.append(e)}else if(O(s)){const e=s.getParentOrThrow();g(n,e.getChildren()),e.replace(n)}return}}const i=new Set;for(let n=0;n<r.length;n++){const s=r[n];if(t.$isElementNode(s)&&s.isEmpty()&&!O(s)&&!i.has(s.getKey())){u(s,e);continue}let o=t.$isLeafNode(s)?s.getParent():O(s)&&s.isEmpty()?s:null;for(;null!=o;){const n=o.getKey();if(M(o)){if(!i.has(n)){const t=k(e);g(t,o.getChildren()),o.replace(t),i.add(n)}break}{const r=o.getParent();if(t.$isRootOrShadowRoot(r)&&!i.has(n)){i.add(n),u(o,e);break}o=r}}}}}function g(e,t){e.splice(e.getChildrenSize(),0,t)}function u(e,n){if(M(e))return e;const r=e.getPreviousSibling(),i=e.getNextSibling(),s=x();let o;if(g(s,e.getChildren()),M(r)&&n===r.getListType())r.append(s),M(i)&&n===i.getListType()&&(g(r,i.getChildren()),i.remove()),o=r;else if(M(i)&&n===i.getListType())i.getFirstChildOrThrow().insertBefore(s),o=i;else{const t=k(n);t.append(s),e.replace(t),o=t}s.setFormat(e.getFormatType()),s.setIndent(e.getIndent());const l=t.$getSelection();return t.$isRangeSelection(l)&&(o.getKey()===l.anchor.key&&l.anchor.set(s.getKey(),l.anchor.offset,"element"),o.getKey()===l.focus.key&&l.focus.set(s.getKey(),l.focus.offset,"element")),e.remove(),o}function h(e,t){const n=e.getLastChild(),r=t.getFirstChild();n&&r&&c(n)&&c(r)&&(h(n.getFirstChild(),r.getFirstChild()),r.remove());const i=t.getChildren();i.length>0&&e.append(...i),t.remove()}function p(){const n=t.$getSelection();if(t.$isRangeSelection(n)){const r=new Set,i=n.getNodes(),s=n.anchor.getNode();if(a(s,i))r.add(o(s));else for(let n=0;n<i.length;n++){const s=i[n];if(t.$isLeafNode(s)){const t=e.$getNearestNodeOfType(s,N);null!=t&&r.add(o(t))}}for(const e of r){let r=e;const i=l(e);for(const e of i){const i=t.$createParagraphNode().setTextStyle(n.style).setTextFormat(n.format);g(i,e.getChildren()),r.insertAfter(i),r=i,e.__key===n.anchor.key&&t.$setPointFromCaret(n.anchor,t.$normalizeCaret(t.$getChildCaret(i,"next"))),e.__key===n.focus.key&&t.$setPointFromCaret(n.focus,t.$normalizeCaret(t.$getChildCaret(i,"next"))),e.remove()}e.remove()}}}function f(e){const t="check"!==e.getListType();let n=e.getStart();for(const r of e.getChildren())O(r)&&(r.getValue()!==n&&r.setValue(n),t&&null!=r.getLatest().__checked&&r.setChecked(void 0),M(r.getFirstChild())||n++)}function m(e){const n=new Set;if(c(e)||n.has(e.getKey()))return;const r=e.getParent(),i=e.getNextSibling(),s=e.getPreviousSibling();if(c(i)&&c(s)){const t=s.getFirstChild();if(M(t)){t.append(e);const r=i.getFirstChild();if(M(r)){g(t,r.getChildren()),i.remove(),n.add(i.getKey())}}}else if(c(i)){const t=i.getFirstChild();if(M(t)){const n=t.getFirstChild();null!==n&&n.insertBefore(e)}}else if(c(s)){const t=s.getFirstChild();M(t)&&t.append(e)}else if(M(r)){const n=t.$copyNode(e),o=t.$copyNode(r);n.append(o),o.append(e),s?s.insertAfter(n):i?i.insertBefore(n):r.append(n)}}function _(e){if(c(e))return;const n=e.getParent(),r=n?n.getParent():void 0;if(M(r?r.getParent():void 0)&&O(r)&&M(n)){const i=n?n.getFirstChild():void 0,s=n?n.getLastChild():void 0;if(e.is(i))r.insertBefore(e),n.isEmpty()&&r.remove();else if(e.is(s))r.insertAfter(e),n.isEmpty()&&r.remove();else{const i=t.$copyNode(e),s=t.$copyNode(n);i.append(s),e.getPreviousSiblings().forEach(e=>s.append(e));const o=t.$copyNode(e),l=t.$copyNode(n);o.append(l),g(l,e.getNextSiblings()),r.insertBefore(i),r.insertAfter(o),r.replace(e)}}}function C(e=!1){const n=t.$getSelection();if(!t.$isRangeSelection(n)||!n.isCollapsed())return!1;const r=n.anchor.getNode();let s=null;if(O(r)&&0===r.getChildrenSize())s=r;else if(t.$isTextNode(r)){const e=r.getParent();O(e)&&e.getChildren().every(e=>t.$isTextNode(e)&&""===e.getTextContent().trim())&&(s=e)}if(null===s)return!1;const l=o(s),c=s.getParent();M(c)||i(40);const a=c.getParent();let d;if(t.$isRootOrShadowRoot(a))d=t.$createParagraphNode(),l.insertAfter(d);else{if(!O(a))return!1;d=t.$copyNode(a),a.insertAfter(d)}d.setTextStyle(n.style).setTextFormat(n.format).select();const g=s.getNextSiblings();if(g.length>0){const n=e?function(e,t){return e.getStart()+t.getIndexWithinParent()}(c,s):1,r=t.$copyNode(c).setStart(n);if(O(d)){const e=t.$copyNode(d);e.append(r),d.insertAfter(e)}else d.insertAfter(r);r.append(...g)}return function(e){let t=e;for(;null==t.getNextSibling()&&null==t.getPreviousSibling();){const e=t.getParent();if(null==e||!O(e)&&!M(e))break;t=e}t.remove()}(s),!0}class N extends t.ElementNode{__value;__checked;$config(){return this.config("listitem",{$transform:n=>{const r=n.getParent();if(M(r))"check"!==r.getListType()&&null!=n.getChecked()&&n.setChecked(void 0);else if(r){const s=n.createParentElementNode();M(s)||i(340);const o=[n];for(const e of["previous","next"]){o.reverse();for(const{origin:r}of t.$getSiblingCaret(n,e)){if(!O(r))break;o.push(r)}}n.insertBefore(s),s.splice(0,0,o),t.$isRootOrShadowRoot(r)||(e.$insertNodeToNearestRootAtCaret(s,t.$rewindSiblingCaret(t.$getSiblingCaret(s,"next")),{$shouldSplit:()=>!1,removeEmptyDestination:!0}),r.isEmpty()&&r.isAttached()&&r.remove())}},extends:t.ElementNode,importDOM:t.buildImportMap({li:()=>({conversion:y,priority:0})})})}constructor(e=1,t=void 0,n){super(n),this.__value=void 0===e?1:e,this.__checked=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__value=e.__value,this.__checked=e.__checked}createDOM(e){const t=document.createElement("li");return this.updateListItemDOM(null,t,e),t}updateListItemDOM(n,r,i){!function(e,t){const n=t.getParent();!M(n)||"check"!==n.getListType()||M(t.getFirstChild())?(e.removeAttribute("role"),e.removeAttribute("tabIndex"),e.removeAttribute("aria-checked")):(e.setAttribute("role","checkbox"),e.setAttribute("tabIndex","-1"),e.setAttribute("aria-checked",t.getChecked()?"true":"false"))}(r,this),r.value=this.__value,function(n,r,i){const s=r.list;if(!s)return;const o=s.listitem,l=s.nested&&s.nested.listitem,c=i.getParent(),a=M(c)&&"check"===c.getListType(),d=i.getChecked(),g=i.getChildren().some(e=>M(e)),u=[];void 0!==s.listitemChecked&&u.push(s.listitemChecked);void 0!==s.listitemUnchecked&&u.push(s.listitemUnchecked);void 0!==l&&u.push(...t.normalizeClassNames(l));u.length>0&&e.removeClassNamesFromElement(n,...u);const h=[];void 0!==o&&h.push(...t.normalizeClassNames(o));if(a){const e=d?s.listitemChecked:s.listitemUnchecked;void 0!==e&&h.push(e)}void 0!==l&&g&&h.push(...t.normalizeClassNames(l));h.length>0&&e.addClassNamesToElement(n,...h)}(r,i.theme,this);const s=n?n.__style:"",o=this.__style;s!==o&&t.setDOMStyleFromCSS(r.style,o,s),function(e,n,r){const i=n.__textStyle,s=r?r.__textStyle:"";if(null!==r&&s===i)return;const o=t.getStyleObjectFromCSS(i);for(const t in o)e.style.setProperty(`--listitem-marker-${t}`,o[t]);if(""!==s)for(const n in t.getStyleObjectFromCSS(s))n in o||e.style.removeProperty(`--listitem-marker-${n}`)}(r,this,n)}updateDOM(e,t,n){const r=t;return this.updateListItemDOM(e,r,n),!1}updateFromJSON(e){return super.updateFromJSON(e).setValue(e.value).setChecked(e.checked)}exportDOM(e){const n=this.createDOM(e._config),r=this.getFormatType();r&&(n.style.textAlign=r);const i=this.getDirection();return i&&(n.dir=i),c(this)?{after(e){if(t.isHTMLElement(e)){const n=e.previousElementSibling;if(t.isHTMLElement(n)&&"LI"===n.nodeName){for(;e.firstChild;)n.append(e.firstChild);e.remove()}}return e},element:n}:{element:n}}exportJSON(){return{...super.exportJSON(),checked:this.getChecked(),value:this.getValue()}}append(...e){for(let n=0;n<e.length;n++){const r=e[n];if(t.$isElementNode(r)&&this.canMergeWith(r)){const e=r.getChildren();this.append(...e),r.remove()}else super.append(r)}return this}replace(e,n){if(O(e))return super.replace(e);this.setIndent(0);const r=this.getParentOrThrow();if(!M(r))return e;if(r.__first===this.getKey())r.insertBefore(e);else if(r.__last===this.getKey())r.insertAfter(e);else{const n=t.$copyNode(r);let i=this.getNextSibling();for(;i;){const e=i;i=i.getNextSibling(),n.append(e)}r.insertAfter(e),e.insertAfter(n)}const s=this.__key;let o=0;if(n&&(t.$isElementNode(e)||i(139),o=e.getChildrenSize(),e.splice(o,0,this.getChildren())),n&&t.$isElementNode(e)){const n=t.$getSelection();if(t.$isRangeSelection(n))for(const t of n.getStartEndPoints())t.key===s&&"element"===t.type&&t.set(e.getKey(),o+t.offset,"element")}return this.remove(),0===r.getChildrenSize()&&r.remove(),e}insertAfter(e,n=!0){const r=this.getParentOrThrow();if(M(r)||i(39),O(e))return super.insertAfter(e,n);const s=this.getNextSiblings();if(r.insertAfter(e,n),0!==s.length){const i=t.$copyNode(r);s.forEach(e=>i.append(e)),e.insertAfter(i,n)}return e}remove(e){const t=this.getPreviousSibling(),n=this.getNextSibling();super.remove(e),t&&n&&c(t)&&c(n)&&(h(t.getFirstChild(),n.getFirstChild()),n.remove())}resetOnCopyNodeFrom(e){super.resetOnCopyNodeFrom(e),e.getChecked()&&this.setChecked(!1)}insertNewAfter(e,n=!0){const r=t.$copyNode(this);return this.insertAfter(r,n),r}collapseAtStart(e){const n=t.$createParagraphNode();this.getChildren().forEach(e=>n.append(e));const r=this.getParentOrThrow(),i=r.getParentOrThrow(),s=O(i);if(1===r.getChildrenSize())if(s)r.remove(),i.select();else{r.insertBefore(n),r.remove();const t=e.anchor,i=e.focus,s=n.getKey();"element"===t.type&&t.getNode().is(this)&&t.set(s,t.offset,"element"),"element"===i.type&&i.getNode().is(this)&&i.set(s,i.offset,"element")}else r.insertBefore(n),this.remove();return!0}getValue(){return this.getLatest().__value}setValue(e){const t=this.getWritable();return t.__value=e,t}getChecked(){const e=this.getLatest();let t;const n=this.getParent();return M(n)&&(t=n.getListType()),"check"===t?Boolean(e.__checked):void 0}setChecked(e){const t=this.getWritable();return t.__checked=e,t}toggleChecked(){const e=this.getWritable();return e.setChecked(!e.__checked)}getIndent(){const e=this.getParent();if(null===e||!this.isAttached())return this.getLatest().__indent;let t=e.getParentOrThrow(),n=0;for(;O(t);)t=t.getParentOrThrow().getParentOrThrow(),n++;return n}setIndent(e){"number"!=typeof e&&i(117),(e=Math.floor(e))>=0||i(199);let t=this.getIndent();for(;t!==e;)t<e?(m(this),t++):(_(this),t--);return this}canInsertAfter(e){return O(e)}canReplaceWith(e){return O(e)}canMergeWith(e){return O(e)||t.$isParagraphNode(e)}extractWithChild(e,n){if(!t.$isRangeSelection(n))return!1;const r=n.anchor.getNode(),i=n.focus.getNode();return this.isParentOf(r)&&this.isParentOf(i)&&this.getTextContent().length===n.getTextContent().length}isParentRequired(){return!0}createParentElementNode(){return k("bullet")}canMergeWhenEmpty(){return!0}}function y(e){if(e.classList.contains("task-list-item"))for(const t of e.children)if("INPUT"===t.tagName)return T(t);if(e.classList.contains("joplin-checkbox"))for(const t of e.children)if(t.classList.contains("checkbox-wrapper")&&t.children.length>0&&"INPUT"===t.children[0].tagName)return T(t.children[0]);const n=e.getAttribute("aria-checked"),r=x("true"===n||"false"!==n&&void 0);return t.$setFormatFromDOM(r,e),{after:S.bind(null,r),node:t.$setDirectionFromDOM(r,e)}}function T(e){if(!("checkbox"===e.getAttribute("type")))return{node:null};const t=x(e.hasAttribute("checked"));return{after:S.bind(null,t),node:t}}function S(e,n){const r=n[0];return 1===n.length&&t.$isParagraphNode(r)&&!e.getFormatType()&&r.getFormatType()?(e.setFormat(r.getFormatType()),r.getChildren()):n}function x(e){return t.$applyNodeReplacement(new N(void 0,e))}function O(e){return e instanceof N}class L extends t.ElementNode{__tag;__start;__listType;$config(){return this.config("list",{$transform:e=>{!function(e){const t=e.getNextSibling();M(t)&&e.getListType()===t.getListType()&&h(e,t)}(e),f(e)},extends:t.ElementNode,importDOM:t.buildImportMap({ol:()=>({conversion:E,priority:0}),ul:()=>({conversion:E,priority:0})})})}constructor(e="number",t=1,n){super(n);const r=$[e]||e;this.__listType=r,this.__tag="number"===r?"ol":"ul",this.__start=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__listType=e.__listType,this.__tag=e.__tag,this.__start=e.__start}getTag(){return this.getLatest().__tag}setListType(e){const t=this.getWritable();return t.__listType=e,t.__tag="number"===e?"ol":"ul",t}getListType(){return this.getLatest().__listType}getStart(){return this.getLatest().__start}setStart(e){const t=this.getWritable();return t.__start=e,t}createDOM(e,t){const n=this.__tag,r=document.createElement(n);return 1!==this.__start&&r.setAttribute("start",String(this.__start)),r.__lexicalListType=this.__listType,v(r,e.theme,this),r}updateDOM(e,t,n){return e.__tag!==this.__tag||e.__listType!==this.__listType||(v(t,n.theme,this),e.__start!==this.__start&&t.setAttribute("start",String(this.__start)),!1)}updateFromJSON(e){return super.updateFromJSON(e).setListType(e.listType).setStart(e.start)}exportDOM(t){const n=this.createDOM(t._config,t);return e.isHTMLElement(n)&&(1!==this.__start&&n.setAttribute("start",String(this.__start)),"check"===this.__listType&&n.setAttribute("__lexicalListType","check")),{element:n}}exportJSON(){return{...super.exportJSON(),listType:this.getListType(),start:this.getStart(),tag:this.getTag()}}canBeEmpty(){return!1}canIndent(){return!1}splice(e,n,r){let i=r;for(let e=0;e<r.length;e++){const n=r[e];O(n)||(i===r&&(i=[...r]),i[e]=this.createListItemNode().append(!t.$isElementNode(n)||M(n)||n.isInline()?n:t.$createTextNode(n.getTextContent())))}return super.splice(e,n,i)}extractWithChild(e){return O(e)}createListItemNode(){return x()}}function v(n,r,i){const o=[],l=[],c=r.list;if(void 0!==c){const e=c[`${i.__tag}Depth`]||[],n=s(i)-1,r=n%e.length,a=e[r],d=c[i.__tag];let g;const u=c.nested,h=c.checklist;if(void 0!==u&&u.list&&(g=u.list),void 0!==d&&o.push(d),void 0!==h&&"check"===i.__listType&&o.push(h),void 0!==a){o.push(...t.normalizeClassNames(a));for(let t=0;t<e.length;t++)t!==r&&l.push(i.__tag+t)}if(void 0!==g){const e=t.normalizeClassNames(g);n>1?o.push(...e):l.push(...e)}}l.length>0&&e.removeClassNamesFromElement(n,...l),o.length>0&&e.addClassNamesToElement(n,...o)}function E(n){let r;if(function(t){return e.isHTMLElement(t)&&"ol"===t.nodeName.toLowerCase()}(n)){const e=n.start;r=k("number",e)}else r=function(t){if("check"===t.getAttribute("__lexicallisttype")||t.classList.contains("contains-task-list")||"1"===t.getAttribute("data-is-checklist"))return!0;for(const n of t.childNodes)if(e.isHTMLElement(n)&&n.hasAttribute("aria-checked"))return!0;return!1}(n)?k("check"):k("bullet");return t.$setDirectionFromDOM(r,n),{after:e=>function(e,t){const n=t.createListItemNode.bind(t),r=[];for(let t=0;t<e.length;t++){const i=e[t];if(O(i)){r.push(i);const e=i.getChildren();e.length>1&&e.forEach(e=>{M(e)&&r.push(n().append(e))})}else r.push(n().append(i))}return r}(e,r),node:r}}const $={ol:"number",ul:"bullet"};function k(e="number",n=1){return t.$applyNodeReplacement(new L(e,n))}function M(e){return e instanceof L}const b=t.createCommand("INSERT_CHECK_LIST_COMMAND");function P(n,r){const i=r&&r.disableTakeFocusOnClick||!1,s="boolean"==typeof i?()=>i:i.peek.bind(i),o=t=>{const n=t.target;if(!e.isHTMLElement(n))return!1;const r=n.__lexicalCheckListLastHandled;return void 0!==r&&t.timeStamp-r<500},l=t=>{const n=t.target;e.isHTMLElement(n)&&(n.__lexicalCheckListLastHandled=t.timeStamp)},c=e=>{o(e)||(l(e),F(e,s()))},a=e=>{"touch"===e.pointerType&&(o(e)||(l(e),F(e,s())))},g=e=>{!function(e,t){I(e,()=>{e.preventDefault(),t&&e.stopPropagation()})}(e,s())};return e.mergeRegister(n.registerCommand(b,()=>(d("check"),!0),t.COMMAND_PRIORITY_LOW),n.registerCommand(t.KEY_ARROW_DOWN_COMMAND,e=>A(e,n,!1),t.COMMAND_PRIORITY_LOW),n.registerCommand(t.KEY_ARROW_UP_COMMAND,e=>A(e,n,!0),t.COMMAND_PRIORITY_LOW),n.registerCommand(t.KEY_ESCAPE_COMMAND,()=>{if(null!=R()){const e=n.getRootElement();return null!=e&&e.focus(),!0}return!1},t.COMMAND_PRIORITY_LOW),n.registerCommand(t.KEY_SPACE_COMMAND,e=>{const r=R();return!(null==r||!n.isEditable())&&(n.update(()=>{const n=t.$getNearestNodeFromDOMNode(r);O(n)&&(e.preventDefault(),n.toggleChecked())}),!0)},t.COMMAND_PRIORITY_LOW),n.registerCommand(t.KEY_ARROW_LEFT_COMMAND,r=>n.getEditorState().read(()=>{const i=t.$getSelection();if(t.$isRangeSelection(i)&&i.isCollapsed()){const{anchor:s}=i,o="element"===s.type;if(o||0===s.offset){const i=s.getNode(),l=e.$findMatchingParent(i,e=>t.$isElementNode(e)&&!e.isInline());if(O(l)){const e=l.getParent();if(M(e)&&"check"===e.getListType()&&(o||l.getFirstDescendant()===i)){const e=n.getElementByKey(l.__key);if(null!=e&&document.activeElement!==e)return e.focus(),r.preventDefault(),!0}}}}return!1}),t.COMMAND_PRIORITY_LOW),n.registerRootListener(e=>{if(null!==e)return e.addEventListener("click",c),e.addEventListener("pointerup",a),e.addEventListener("pointerdown",g,{capture:!0}),e.addEventListener("mousedown",g,{capture:!0}),e.addEventListener("touchstart",g,{capture:!0,passive:!1}),()=>{e.removeEventListener("click",c),e.removeEventListener("pointerup",a),e.removeEventListener("pointerdown",g,{capture:!0}),e.removeEventListener("mousedown",g,{capture:!0}),e.removeEventListener("touchstart",g,{capture:!0})}}))}function I(t,n){const r=t.target;if(!e.isHTMLElement(r))return;const i=r.firstChild;if(e.isHTMLElement(i)&&("UL"===i.tagName||"OL"===i.tagName))return;const s=r.parentNode;if(!s||"check"!==s.__lexicalListType)return;let o=null,l=null;if("clientX"in t)o=t.clientX;else if("touches"in t){const e=t.touches;e.length>0&&(o=e[0].clientX,l="touch")}if(null==o)return;const c=r.getBoundingClientRect(),a=o/e.calculateZoomLevel(r),d=window.getComputedStyle?window.getComputedStyle(r,"::before"):{width:"0px"},g=parseFloat(d.width),u="touch"===l||"pointerType"in t&&"touch"===t.pointerType?32:0;("rtl"===r.dir?a<c.right+u&&a>c.right-g-u:a>c.left-u&&a<c.left+g+u)&&n()}function F(n,r){I(n,()=>{if(e.isHTMLElement(n.target)){const e=n.target,i=t.getNearestEditorFromDOMNode(e);null!=i&&i.isEditable()&&i.update(()=>{const n=t.$getNearestNodeFromDOMNode(e);O(n)&&(r?(t.$addUpdateTag(t.SKIP_SELECTION_FOCUS_TAG),t.$addUpdateTag(t.SKIP_DOM_SELECTION_TAG)):e.focus(),n.toggleChecked())})}})}function R(){const t=document.activeElement;return e.isHTMLElement(t)&&"LI"===t.tagName&&null!=t.parentNode&&"check"===t.parentNode.__lexicalListType?t:null}function A(e,n,r){const i=R();return null!=i&&n.update(()=>{const s=t.$getNearestNodeFromDOMNode(i);if(!O(s))return;const o=function(e,t){let n=t?e.getPreviousSibling():e.getNextSibling(),r=e;for(;null==n&&O(r);)r=r.getParentOrThrow().getParent(),null!=r&&(n=t?r.getPreviousSibling():r.getNextSibling());for(;O(n);){const e=t?n.getLastChild():n.getFirstChild();if(!M(e))return n;n=t?e.getLastChild():e.getFirstChild()}return null}(s,r);if(null!=o){o.selectStart();const t=n.getElementByKey(o.__key);null!=t&&(e.preventDefault(),setTimeout(()=>{t.focus()},0))}}),!1}const D=t.createCommand("UPDATE_LIST_START_COMMAND"),w=t.createCommand("INSERT_UNORDERED_LIST_COMMAND"),W=t.createCommand("INSERT_ORDERED_LIST_COMMAND"),K=t.createCommand("REMOVE_LIST_COMMAND");function H(n,r){return e.mergeRegister(n.registerCommand(W,()=>(d("number"),!0),t.COMMAND_PRIORITY_LOW),n.registerCommand(D,e=>{const{listNodeKey:n,newStart:r}=e,i=t.$getNodeByKey(n);return!!M(i)&&("number"===i.getListType()&&(i.setStart(r),f(i)),!0)},t.COMMAND_PRIORITY_LOW),n.registerCommand(w,()=>(d("bullet"),!0),t.COMMAND_PRIORITY_LOW),n.registerCommand(K,()=>(p(),!0),t.COMMAND_PRIORITY_LOW),n.registerCommand(t.INSERT_PARAGRAPH_COMMAND,()=>C(!!(r&&r.restoreNumbering)),t.COMMAND_PRIORITY_LOW),n.registerNodeTransform(N,e=>{const n=e.getFirstChild();if(n){if(t.$isTextNode(n)){const t=n.getStyle(),r=n.getFormat();e.getTextStyle()!==t&&e.setTextStyle(t),e.getTextFormat()!==r&&e.setTextFormat(r)}}else{const n=t.$getSelection();t.$isRangeSelection(n)&&(n.style!==e.getTextStyle()||n.format!==e.getTextFormat())&&n.isCollapsed()&&e.is(n.anchor.getNode())&&e.setTextStyle(n.style).setTextFormat(n.format)}}),n.registerNodeTransform(t.TextNode,e=>{const t=e.getParent();if(O(t)&&e.is(t.getFirstChild())){const n=e.getStyle(),r=e.getFormat();n===t.getTextStyle()&&r===t.getTextFormat()||t.setTextStyle(n).setTextFormat(r)}}))}function U(t){const n=t=>{const n=t.getParent();if(M(t.getFirstChild())||!M(n))return;const r=e.$findMatchingParent(t,e=>O(e)&&M(e.getParent())&&O(e.getPreviousSibling()));if(null===r&&t.getIndent()>0)t.setIndent(0);else if(O(r)){const e=r.getPreviousSibling();if(O(e)){const r=function(e){let t=e,n=t.getFirstChild();for(;M(n);){const e=n.getLastChild();if(!O(e))break;t=e,n=t.getFirstChild()}return t}(e),i=r.getParent();if(M(i)){const e=s(i);e+1<s(n)&&t.setIndent(e)}}}};return t.registerNodeTransform(L,e=>{const t=[e];for(;t.length>0;){const e=t.shift();if(M(e))for(const r of e.getChildren())if(O(r)){n(r);const e=r.getFirstChild();M(e)&&t.push(e)}}})}const Y=t.defineExtension({build:(e,t,r)=>n.namedSignals(t),config:t.safeCast({hasStrictIndent:!1,shouldPreserveNumbering:!1}),name:"@lexical/list/List",nodes:()=>[L,N],register(t,r,i){const s=i.getOutput();return e.mergeRegister(n.effect(()=>H(t,{restoreNumbering:s.shouldPreserveNumbering.value})),n.effect(()=>s.hasStrictIndent.value?U(t):void 0))}}),B=t.defineExtension({build:(e,t)=>n.namedSignals(t),config:t.safeCast({disableTakeFocusOnClick:!1}),dependencies:[Y],name:"@lexical/list/CheckList",register:(e,t,n)=>P(e,n.getOutput())});function z(e){const t=[];for(const n of e)if(O(n)){t.push(n);const e=n.getChildren();if(e.length>1)for(const n of e)M(n)&&t.push(x().append(n))}else t.push(x().append(n));return t}const q=r.defineImportRule({$import:(e,n)=>{let i;var s;return r.isElementOfTag(n,"ol")?i=k("number",n.start):i=(s=n).matches('[__lexicallisttype="check"], .contains-task-list, [data-is-checklist="1"]')||null!==s.querySelector(":scope > [aria-checked]")?k("check"):k("bullet"),t.$setDirectionFromDOM(i,n),[i.splice(0,0,z(e.$importChildren(n)))]},match:r.sel.tag("ol","ul"),name:"@lexical/list/list"});function J(e,n){if(1!==n.length)return n;const r=n[0];return t.$isParagraphNode(r)&&!e.getFormatType()&&r.getFormatType()?(e.setFormat(r.getFormatType()),r.getChildren()):n}const V=r.defineImportRule({$import:(e,n)=>{const r=n.getAttribute("aria-checked"),i=x("true"===r||"false"!==r&&void 0);return t.$setFormatFromDOM(i,n),t.$setDirectionFromDOM(i,n),[i.splice(0,0,J(i,e.$importChildren(n)))]},match:r.sel.tag("li"),name:"@lexical/list/li"});function j(e,n,i){const s=r.isElementOfTag(i,"input")?i:i.querySelector('input[type="checkbox"]');if(!s||"checkbox"!==s.getAttribute("type"))return[];const o=x(s.hasAttribute("checked"));return t.$setFormatFromDOM(o,n),t.$setDirectionFromDOM(o,n),[o.splice(0,0,J(o,e.$importChildren(n)))]}const G={$accepts:e=>O(e)||M(e),$packageRun:e=>[x().splice(0,0,e)],name:"ListSchema"},X=[r.defineImportRule({$import:(e,t,n)=>{const r=t.querySelector(':scope > input[type="checkbox"]');return r?j(e,t,r):n()},match:r.sel.tag("li").classAll("task-list-item"),name:"@lexical/list/li-task-list-item"}),r.defineImportRule({$import:(e,t,n)=>{const r=t.querySelector(":scope > .checkbox-wrapper");if(!r)return n();const i=r.querySelector(':scope > input[type="checkbox"]');return i?j(e,t,i):n()},match:r.sel.tag("li").classAll("joplin-checkbox"),name:"@lexical/list/li-joplin-checkbox"}),q,V],Z=t.defineExtension({dependencies:[r.CoreImportExtension,Y,t.configExtension(r.DOMImportExtension,{rules:X})],name:"@lexical/list/Import"});exports.$createListItemNode=x,exports.$createListNode=k,exports.$getListDepth=s,exports.$handleListInsertParagraph=C,exports.$insertList=d,exports.$isListItemNode=O,exports.$isListNode=M,exports.$removeList=p,exports.CheckListExtension=B,exports.INSERT_CHECK_LIST_COMMAND=b,exports.INSERT_ORDERED_LIST_COMMAND=W,exports.INSERT_UNORDERED_LIST_COMMAND=w,exports.ListExtension=Y,exports.ListImportExtension=Z,exports.ListImportRules=X,exports.ListItemNode=N,exports.ListNode=L,exports.ListSchema=G,exports.REMOVE_LIST_COMMAND=K,exports.UPDATE_LIST_START_COMMAND=D,exports.insertList=function(e,t){e.update(()=>d(t))},exports.registerCheckList=P,exports.registerList=H,exports.registerListStrictIndentTransform=U,exports.removeList=function(e){e.update(()=>p())}; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import{$getNearestNodeOfType as e,$insertNodeToNearestRootAtCaret as t,removeClassNamesFromElement as n,addClassNamesToElement as r,isHTMLElement as i,mergeRegister as s,$findMatchingParent as o,calculateZoomLevel as l}from"@lexical/utils";import{$getSelection as c,$isRangeSelection as a,$isTextNode as u,$isRootOrShadowRoot as g,$createParagraphNode as h,$copyNode as d,$isElementNode as f,$isLeafNode as p,$setPointFromCaret as m,$normalizeCaret as _,$getChildCaret as y,$applyNodeReplacement as C,ElementNode as v,buildImportMap as k,$getSiblingCaret as T,$rewindSiblingCaret as b,setDOMStyleFromCSS as S,isHTMLElement as x,$isParagraphNode as L,$setFormatFromDOM as N,$setDirectionFromDOM as F,normalizeClassNames as P,getStyleObjectFromCSS as A,$createTextNode as E,createCommand as O,COMMAND_PRIORITY_LOW as I,KEY_ARROW_DOWN_COMMAND as w,KEY_ARROW_UP_COMMAND as D,KEY_ESCAPE_COMMAND as M,KEY_SPACE_COMMAND as $,$getNearestNodeFromDOMNode as R,KEY_ARROW_LEFT_COMMAND as K,getNearestEditorFromDOMNode as B,$addUpdateTag as W,SKIP_SELECTION_FOCUS_TAG as U,SKIP_DOM_SELECTION_TAG as J,$getNodeByKey as V,INSERT_PARAGRAPH_COMMAND as q,TextNode as z,defineExtension as H,safeCast as j,configExtension as X}from"lexical";import{namedSignals as G,effect as Q}from"@lexical/extension";import{CoreImportExtension as Y,DOMImportExtension as Z,defineImportRule as ee,sel as te,isElementOfTag as ne}from"@lexical/html";function re(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function ie(e){let t=1,n=e.getParent();for(;null!=n;){if(be(n)){const e=n.getParent();if(Pe(e)){t++,n=e.getParent();continue}re(40)}return t}return t}function se(e){let t=e.getParent();Pe(t)||re(40);let n=t;for(;null!==n;)n=n.getParent(),Pe(n)&&(t=n);return t}function oe(e){let t=[];const n=e.getChildren().filter(be);for(let e=0;e<n.length;e++){const r=n[e],i=r.getFirstChild();Pe(i)?t=t.concat(oe(i)):t.push(r)}return t}function le(e){return be(e)&&Pe(e.getFirstChild())}function ce(e,t){return be(e)&&(0===t.length||1===t.length&&e.is(t[0])&&0===e.getChildrenSize())}function ae(e){const t=c();if(null!==t){let n=t.getNodes();if(a(t)){const[r]=t.getStartEndPoints(),i=r.getNode(),s=i.getParent();if(g(i)){const e=i.getFirstChild();if(e)n=e.selectStart().getNodes();else{const e=h();i.append(e),n=e.select().getNodes()}}else if(ce(i,n)){const t=Fe(e);if(g(s)){i.replace(t);const e=Te();f(i)&&(e.setFormat(i.getFormatType()),e.setIndent(i.getIndent())),t.append(e)}else if(be(i)){const e=i.getParentOrThrow();ue(t,e.getChildren()),e.replace(t)}return}}const r=new Set;for(let t=0;t<n.length;t++){const i=n[t];if(f(i)&&i.isEmpty()&&!be(i)&&!r.has(i.getKey())){ge(i,e);continue}let s=p(i)?i.getParent():be(i)&&i.isEmpty()?i:null;for(;null!=s;){const t=s.getKey();if(Pe(s)){if(!r.has(t)){const n=Fe(e);ue(n,s.getChildren()),s.replace(n),r.add(t)}break}{const n=s.getParent();if(g(n)&&!r.has(t)){r.add(t),ge(s,e);break}s=n}}}}}function ue(e,t){e.splice(e.getChildrenSize(),0,t)}function ge(e,t){if(Pe(e))return e;const n=e.getPreviousSibling(),r=e.getNextSibling(),i=Te();let s;if(ue(i,e.getChildren()),Pe(n)&&t===n.getListType())n.append(i),Pe(r)&&t===r.getListType()&&(ue(n,r.getChildren()),r.remove()),s=n;else if(Pe(r)&&t===r.getListType())r.getFirstChildOrThrow().insertBefore(i),s=r;else{const n=Fe(t);n.append(i),e.replace(n),s=n}i.setFormat(e.getFormatType()),i.setIndent(e.getIndent());const o=c();return a(o)&&(s.getKey()===o.anchor.key&&o.anchor.set(i.getKey(),o.anchor.offset,"element"),s.getKey()===o.focus.key&&o.focus.set(i.getKey(),o.focus.offset,"element")),e.remove(),s}function he(e,t){const n=e.getLastChild(),r=t.getFirstChild();n&&r&&le(n)&&le(r)&&(he(n.getFirstChild(),r.getFirstChild()),r.remove());const i=t.getChildren();i.length>0&&e.append(...i),t.remove()}function de(){const t=c();if(a(t)){const n=new Set,r=t.getNodes(),i=t.anchor.getNode();if(ce(i,r))n.add(se(i));else for(let t=0;t<r.length;t++){const i=r[t];if(p(i)){const t=e(i,ye);null!=t&&n.add(se(t))}}for(const e of n){let n=e;const r=oe(e);for(const e of r){const r=h().setTextStyle(t.style).setTextFormat(t.format);ue(r,e.getChildren()),n.insertAfter(r),n=r,e.__key===t.anchor.key&&m(t.anchor,_(y(r,"next"))),e.__key===t.focus.key&&m(t.focus,_(y(r,"next"))),e.remove()}e.remove()}}}function fe(e){const t="check"!==e.getListType();let n=e.getStart();for(const r of e.getChildren())be(r)&&(r.getValue()!==n&&r.setValue(n),t&&null!=r.getLatest().__checked&&r.setChecked(void 0),Pe(r.getFirstChild())||n++)}function pe(e){const t=new Set;if(le(e)||t.has(e.getKey()))return;const n=e.getParent(),r=e.getNextSibling(),i=e.getPreviousSibling();if(le(r)&&le(i)){const n=i.getFirstChild();if(Pe(n)){n.append(e);const i=r.getFirstChild();if(Pe(i)){ue(n,i.getChildren()),r.remove(),t.add(r.getKey())}}}else if(le(r)){const t=r.getFirstChild();if(Pe(t)){const n=t.getFirstChild();null!==n&&n.insertBefore(e)}}else if(le(i)){const t=i.getFirstChild();Pe(t)&&t.append(e)}else if(Pe(n)){const t=d(e),s=d(n);t.append(s),s.append(e),i?i.insertAfter(t):r?r.insertBefore(t):n.append(t)}}function me(e){if(le(e))return;const t=e.getParent(),n=t?t.getParent():void 0;if(Pe(n?n.getParent():void 0)&&be(n)&&Pe(t)){const r=t?t.getFirstChild():void 0,i=t?t.getLastChild():void 0;if(e.is(r))n.insertBefore(e),t.isEmpty()&&n.remove();else if(e.is(i))n.insertAfter(e),t.isEmpty()&&n.remove();else{const r=d(e),i=d(t);r.append(i),e.getPreviousSiblings().forEach(e=>i.append(e));const s=d(e),o=d(t);s.append(o),ue(o,e.getNextSiblings()),n.insertBefore(r),n.insertAfter(s),n.replace(e)}}}function _e(e=!1){const t=c();if(!a(t)||!t.isCollapsed())return!1;const n=t.anchor.getNode();let r=null;if(be(n)&&0===n.getChildrenSize())r=n;else if(u(n)){const e=n.getParent();be(e)&&e.getChildren().every(e=>u(e)&&""===e.getTextContent().trim())&&(r=e)}if(null===r)return!1;const i=se(r),s=r.getParent();Pe(s)||re(40);const o=s.getParent();let l;if(g(o))l=h(),i.insertAfter(l);else{if(!be(o))return!1;l=d(o),o.insertAfter(l)}l.setTextStyle(t.style).setTextFormat(t.format).select();const f=r.getNextSiblings();if(f.length>0){const t=e?function(e,t){return e.getStart()+t.getIndexWithinParent()}(s,r):1,n=d(s).setStart(t);if(be(l)){const e=d(l);e.append(n),l.insertAfter(e)}else l.insertAfter(n);n.append(...f)}return function(e){let t=e;for(;null==t.getNextSibling()&&null==t.getPreviousSibling();){const e=t.getParent();if(null==e||!be(e)&&!Pe(e))break;t=e}t.remove()}(r),!0}class ye extends v{__value;__checked;$config(){return this.config("listitem",{$transform:e=>{const n=e.getParent();if(Pe(n))"check"!==n.getListType()&&null!=e.getChecked()&&e.setChecked(void 0);else if(n){const r=e.createParentElementNode();Pe(r)||re(340);const i=[e];for(const t of["previous","next"]){i.reverse();for(const{origin:n}of T(e,t)){if(!be(n))break;i.push(n)}}e.insertBefore(r),r.splice(0,0,i),g(n)||(t(r,b(T(r,"next")),{$shouldSplit:()=>!1,removeEmptyDestination:!0}),n.isEmpty()&&n.isAttached()&&n.remove())}},extends:v,importDOM:k({li:()=>({conversion:Ce,priority:0})})})}constructor(e=1,t=void 0,n){super(n),this.__value=void 0===e?1:e,this.__checked=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__value=e.__value,this.__checked=e.__checked}createDOM(e){const t=document.createElement("li");return this.updateListItemDOM(null,t,e),t}updateListItemDOM(e,t,i){!function(e,t){const n=t.getParent();!Pe(n)||"check"!==n.getListType()||Pe(t.getFirstChild())?(e.removeAttribute("role"),e.removeAttribute("tabIndex"),e.removeAttribute("aria-checked")):(e.setAttribute("role","checkbox"),e.setAttribute("tabIndex","-1"),e.setAttribute("aria-checked",t.getChecked()?"true":"false"))}(t,this),t.value=this.__value,function(e,t,i){const s=t.list;if(!s)return;const o=s.listitem,l=s.nested&&s.nested.listitem,c=i.getParent(),a=Pe(c)&&"check"===c.getListType(),u=i.getChecked(),g=i.getChildren().some(e=>Pe(e)),h=[];void 0!==s.listitemChecked&&h.push(s.listitemChecked);void 0!==s.listitemUnchecked&&h.push(s.listitemUnchecked);void 0!==l&&h.push(...P(l));h.length>0&&n(e,...h);const d=[];void 0!==o&&d.push(...P(o));if(a){const e=u?s.listitemChecked:s.listitemUnchecked;void 0!==e&&d.push(e)}void 0!==l&&g&&d.push(...P(l));d.length>0&&r(e,...d)}(t,i.theme,this);const s=e?e.__style:"",o=this.__style;s!==o&&S(t.style,o,s),function(e,t,n){const r=t.__textStyle,i=n?n.__textStyle:"";if(null!==n&&i===r)return;const s=A(r);for(const t in s)e.style.setProperty(`--listitem-marker-${t}`,s[t]);if(""!==i)for(const t in A(i))t in s||e.style.removeProperty(`--listitem-marker-${t}`)}(t,this,e)}updateDOM(e,t,n){const r=t;return this.updateListItemDOM(e,r,n),!1}updateFromJSON(e){return super.updateFromJSON(e).setValue(e.value).setChecked(e.checked)}exportDOM(e){const t=this.createDOM(e._config),n=this.getFormatType();n&&(t.style.textAlign=n);const r=this.getDirection();return r&&(t.dir=r),le(this)?{after(e){if(x(e)){const t=e.previousElementSibling;if(x(t)&&"LI"===t.nodeName){for(;e.firstChild;)t.append(e.firstChild);e.remove()}}return e},element:t}:{element:t}}exportJSON(){return{...super.exportJSON(),checked:this.getChecked(),value:this.getValue()}}append(...e){for(let t=0;t<e.length;t++){const n=e[t];if(f(n)&&this.canMergeWith(n)){const e=n.getChildren();this.append(...e),n.remove()}else super.append(n)}return this}replace(e,t){if(be(e))return super.replace(e);this.setIndent(0);const n=this.getParentOrThrow();if(!Pe(n))return e;if(n.__first===this.getKey())n.insertBefore(e);else if(n.__last===this.getKey())n.insertAfter(e);else{const t=d(n);let r=this.getNextSibling();for(;r;){const e=r;r=r.getNextSibling(),t.append(e)}n.insertAfter(e),e.insertAfter(t)}const r=this.__key;let i=0;if(t&&(f(e)||re(139),i=e.getChildrenSize(),e.splice(i,0,this.getChildren())),t&&f(e)){const t=c();if(a(t))for(const n of t.getStartEndPoints())n.key===r&&"element"===n.type&&n.set(e.getKey(),i+n.offset,"element")}return this.remove(),0===n.getChildrenSize()&&n.remove(),e}insertAfter(e,t=!0){const n=this.getParentOrThrow();if(Pe(n)||re(39),be(e))return super.insertAfter(e,t);const r=this.getNextSiblings();if(n.insertAfter(e,t),0!==r.length){const i=d(n);r.forEach(e=>i.append(e)),e.insertAfter(i,t)}return e}remove(e){const t=this.getPreviousSibling(),n=this.getNextSibling();super.remove(e),t&&n&&le(t)&&le(n)&&(he(t.getFirstChild(),n.getFirstChild()),n.remove())}resetOnCopyNodeFrom(e){super.resetOnCopyNodeFrom(e),e.getChecked()&&this.setChecked(!1)}insertNewAfter(e,t=!0){const n=d(this);return this.insertAfter(n,t),n}collapseAtStart(e){const t=h();this.getChildren().forEach(e=>t.append(e));const n=this.getParentOrThrow(),r=n.getParentOrThrow(),i=be(r);if(1===n.getChildrenSize())if(i)n.remove(),r.select();else{n.insertBefore(t),n.remove();const r=e.anchor,i=e.focus,s=t.getKey();"element"===r.type&&r.getNode().is(this)&&r.set(s,r.offset,"element"),"element"===i.type&&i.getNode().is(this)&&i.set(s,i.offset,"element")}else n.insertBefore(t),this.remove();return!0}getValue(){return this.getLatest().__value}setValue(e){const t=this.getWritable();return t.__value=e,t}getChecked(){const e=this.getLatest();let t;const n=this.getParent();return Pe(n)&&(t=n.getListType()),"check"===t?Boolean(e.__checked):void 0}setChecked(e){const t=this.getWritable();return t.__checked=e,t}toggleChecked(){const e=this.getWritable();return e.setChecked(!e.__checked)}getIndent(){const e=this.getParent();if(null===e||!this.isAttached())return this.getLatest().__indent;let t=e.getParentOrThrow(),n=0;for(;be(t);)t=t.getParentOrThrow().getParentOrThrow(),n++;return n}setIndent(e){"number"!=typeof e&&re(117),(e=Math.floor(e))>=0||re(199);let t=this.getIndent();for(;t!==e;)t<e?(pe(this),t++):(me(this),t--);return this}canInsertAfter(e){return be(e)}canReplaceWith(e){return be(e)}canMergeWith(e){return be(e)||L(e)}extractWithChild(e,t){if(!a(t))return!1;const n=t.anchor.getNode(),r=t.focus.getNode();return this.isParentOf(n)&&this.isParentOf(r)&&this.getTextContent().length===t.getTextContent().length}isParentRequired(){return!0}createParentElementNode(){return Fe("bullet")}canMergeWhenEmpty(){return!0}}function Ce(e){if(e.classList.contains("task-list-item"))for(const t of e.children)if("INPUT"===t.tagName)return ve(t);if(e.classList.contains("joplin-checkbox"))for(const t of e.children)if(t.classList.contains("checkbox-wrapper")&&t.children.length>0&&"INPUT"===t.children[0].tagName)return ve(t.children[0]);const t=e.getAttribute("aria-checked"),n=Te("true"===t||"false"!==t&&void 0);return N(n,e),{after:ke.bind(null,n),node:F(n,e)}}function ve(e){if(!("checkbox"===e.getAttribute("type")))return{node:null};const t=Te(e.hasAttribute("checked"));return{after:ke.bind(null,t),node:t}}function ke(e,t){const n=t[0];return 1===t.length&&L(n)&&!e.getFormatType()&&n.getFormatType()?(e.setFormat(n.getFormatType()),n.getChildren()):t}function Te(e){return C(new ye(void 0,e))}function be(e){return e instanceof ye}class Se extends v{__tag;__start;__listType;$config(){return this.config("list",{$transform:e=>{!function(e){const t=e.getNextSibling();Pe(t)&&e.getListType()===t.getListType()&&he(e,t)}(e),fe(e)},extends:v,importDOM:k({ol:()=>({conversion:Le,priority:0}),ul:()=>({conversion:Le,priority:0})})})}constructor(e="number",t=1,n){super(n);const r=Ne[e]||e;this.__listType=r,this.__tag="number"===r?"ol":"ul",this.__start=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__listType=e.__listType,this.__tag=e.__tag,this.__start=e.__start}getTag(){return this.getLatest().__tag}setListType(e){const t=this.getWritable();return t.__listType=e,t.__tag="number"===e?"ol":"ul",t}getListType(){return this.getLatest().__listType}getStart(){return this.getLatest().__start}setStart(e){const t=this.getWritable();return t.__start=e,t}createDOM(e,t){const n=this.__tag,r=document.createElement(n);return 1!==this.__start&&r.setAttribute("start",String(this.__start)),r.__lexicalListType=this.__listType,xe(r,e.theme,this),r}updateDOM(e,t,n){return e.__tag!==this.__tag||e.__listType!==this.__listType||(xe(t,n.theme,this),e.__start!==this.__start&&t.setAttribute("start",String(this.__start)),!1)}updateFromJSON(e){return super.updateFromJSON(e).setListType(e.listType).setStart(e.start)}exportDOM(e){const t=this.createDOM(e._config,e);return i(t)&&(1!==this.__start&&t.setAttribute("start",String(this.__start)),"check"===this.__listType&&t.setAttribute("__lexicalListType","check")),{element:t}}exportJSON(){return{...super.exportJSON(),listType:this.getListType(),start:this.getStart(),tag:this.getTag()}}canBeEmpty(){return!1}canIndent(){return!1}splice(e,t,n){let r=n;for(let e=0;e<n.length;e++){const t=n[e];be(t)||(r===n&&(r=[...n]),r[e]=this.createListItemNode().append(!f(t)||Pe(t)||t.isInline()?t:E(t.getTextContent())))}return super.splice(e,t,r)}extractWithChild(e){return be(e)}createListItemNode(){return Te()}}function xe(e,t,i){const s=[],o=[],l=t.list;if(void 0!==l){const e=l[`${i.__tag}Depth`]||[],t=ie(i)-1,n=t%e.length,r=e[n],c=l[i.__tag];let a;const u=l.nested,g=l.checklist;if(void 0!==u&&u.list&&(a=u.list),void 0!==c&&s.push(c),void 0!==g&&"check"===i.__listType&&s.push(g),void 0!==r){s.push(...P(r));for(let t=0;t<e.length;t++)t!==n&&o.push(i.__tag+t)}if(void 0!==a){const e=P(a);t>1?s.push(...e):o.push(...e)}}o.length>0&&n(e,...o),s.length>0&&r(e,...s)}function Le(e){let t;if(function(e){return i(e)&&"ol"===e.nodeName.toLowerCase()}(e)){const n=e.start;t=Fe("number",n)}else t=function(e){if("check"===e.getAttribute("__lexicallisttype")||e.classList.contains("contains-task-list")||"1"===e.getAttribute("data-is-checklist"))return!0;for(const t of e.childNodes)if(i(t)&&t.hasAttribute("aria-checked"))return!0;return!1}(e)?Fe("check"):Fe("bullet");return F(t,e),{after:e=>function(e,t){const n=t.createListItemNode.bind(t),r=[];for(let t=0;t<e.length;t++){const i=e[t];if(be(i)){r.push(i);const e=i.getChildren();e.length>1&&e.forEach(e=>{Pe(e)&&r.push(n().append(e))})}else r.push(n().append(i))}return r}(e,t),node:t}}const Ne={ol:"number",ul:"bullet"};function Fe(e="number",t=1){return C(new Se(e,t))}function Pe(e){return e instanceof Se}const Ae=O("INSERT_CHECK_LIST_COMMAND");function Ee(e,t){const n=t&&t.disableTakeFocusOnClick||!1,r="boolean"==typeof n?()=>n:n.peek.bind(n),l=e=>{const t=e.target;if(!i(t))return!1;const n=t.__lexicalCheckListLastHandled;return void 0!==n&&e.timeStamp-n<500},u=e=>{const t=e.target;i(t)&&(t.__lexicalCheckListLastHandled=e.timeStamp)},g=e=>{l(e)||(u(e),Ie(e,r()))},h=e=>{"touch"===e.pointerType&&(l(e)||(u(e),Ie(e,r())))},d=e=>{!function(e,t){Oe(e,()=>{e.preventDefault(),t&&e.stopPropagation()})}(e,r())};return s(e.registerCommand(Ae,()=>(ae("check"),!0),I),e.registerCommand(w,t=>De(t,e,!1),I),e.registerCommand(D,t=>De(t,e,!0),I),e.registerCommand(M,()=>{if(null!=we()){const t=e.getRootElement();return null!=t&&t.focus(),!0}return!1},I),e.registerCommand($,t=>{const n=we();return!(null==n||!e.isEditable())&&(e.update(()=>{const e=R(n);be(e)&&(t.preventDefault(),e.toggleChecked())}),!0)},I),e.registerCommand(K,t=>e.getEditorState().read(()=>{const n=c();if(a(n)&&n.isCollapsed()){const{anchor:r}=n,i="element"===r.type;if(i||0===r.offset){const n=r.getNode(),s=o(n,e=>f(e)&&!e.isInline());if(be(s)){const r=s.getParent();if(Pe(r)&&"check"===r.getListType()&&(i||s.getFirstDescendant()===n)){const n=e.getElementByKey(s.__key);if(null!=n&&document.activeElement!==n)return n.focus(),t.preventDefault(),!0}}}}return!1}),I),e.registerRootListener(e=>{if(null!==e)return e.addEventListener("click",g),e.addEventListener("pointerup",h),e.addEventListener("pointerdown",d,{capture:!0}),e.addEventListener("mousedown",d,{capture:!0}),e.addEventListener("touchstart",d,{capture:!0,passive:!1}),()=>{e.removeEventListener("click",g),e.removeEventListener("pointerup",h),e.removeEventListener("pointerdown",d,{capture:!0}),e.removeEventListener("mousedown",d,{capture:!0}),e.removeEventListener("touchstart",d,{capture:!0})}}))}function Oe(e,t){const n=e.target;if(!i(n))return;const r=n.firstChild;if(i(r)&&("UL"===r.tagName||"OL"===r.tagName))return;const s=n.parentNode;if(!s||"check"!==s.__lexicalListType)return;let o=null,c=null;if("clientX"in e)o=e.clientX;else if("touches"in e){const t=e.touches;t.length>0&&(o=t[0].clientX,c="touch")}if(null==o)return;const a=n.getBoundingClientRect(),u=o/l(n),g=window.getComputedStyle?window.getComputedStyle(n,"::before"):{width:"0px"},h=parseFloat(g.width),d="touch"===c||"pointerType"in e&&"touch"===e.pointerType?32:0;("rtl"===n.dir?u<a.right+d&&u>a.right-h-d:u>a.left-d&&u<a.left+h+d)&&t()}function Ie(e,t){Oe(e,()=>{if(i(e.target)){const n=e.target,r=B(n);null!=r&&r.isEditable()&&r.update(()=>{const e=R(n);be(e)&&(t?(W(U),W(J)):n.focus(),e.toggleChecked())})}})}function we(){const e=document.activeElement;return i(e)&&"LI"===e.tagName&&null!=e.parentNode&&"check"===e.parentNode.__lexicalListType?e:null}function De(e,t,n){const r=we();return null!=r&&t.update(()=>{const i=R(r);if(!be(i))return;const s=function(e,t){let n=t?e.getPreviousSibling():e.getNextSibling(),r=e;for(;null==n&&be(r);)r=r.getParentOrThrow().getParent(),null!=r&&(n=t?r.getPreviousSibling():r.getNextSibling());for(;be(n);){const e=t?n.getLastChild():n.getFirstChild();if(!Pe(e))return n;n=t?e.getLastChild():e.getFirstChild()}return null}(i,n);if(null!=s){s.selectStart();const n=t.getElementByKey(s.__key);null!=n&&(e.preventDefault(),setTimeout(()=>{n.focus()},0))}}),!1}const Me=O("UPDATE_LIST_START_COMMAND"),$e=O("INSERT_UNORDERED_LIST_COMMAND"),Re=O("INSERT_ORDERED_LIST_COMMAND"),Ke=O("REMOVE_LIST_COMMAND");function Be(e,t){return s(e.registerCommand(Re,()=>(ae("number"),!0),I),e.registerCommand(Me,e=>{const{listNodeKey:t,newStart:n}=e,r=V(t);return!!Pe(r)&&("number"===r.getListType()&&(r.setStart(n),fe(r)),!0)},I),e.registerCommand($e,()=>(ae("bullet"),!0),I),e.registerCommand(Ke,()=>(de(),!0),I),e.registerCommand(q,()=>_e(!!(t&&t.restoreNumbering)),I),e.registerNodeTransform(ye,e=>{const t=e.getFirstChild();if(t){if(u(t)){const n=t.getStyle(),r=t.getFormat();e.getTextStyle()!==n&&e.setTextStyle(n),e.getTextFormat()!==r&&e.setTextFormat(r)}}else{const t=c();a(t)&&(t.style!==e.getTextStyle()||t.format!==e.getTextFormat())&&t.isCollapsed()&&e.is(t.anchor.getNode())&&e.setTextStyle(t.style).setTextFormat(t.format)}}),e.registerNodeTransform(z,e=>{const t=e.getParent();if(be(t)&&e.is(t.getFirstChild())){const n=e.getStyle(),r=e.getFormat();n===t.getTextStyle()&&r===t.getTextFormat()||t.setTextStyle(n).setTextFormat(r)}}))}function We(e){const t=e=>{const t=e.getParent();if(Pe(e.getFirstChild())||!Pe(t))return;const n=o(e,e=>be(e)&&Pe(e.getParent())&&be(e.getPreviousSibling()));if(null===n&&e.getIndent()>0)e.setIndent(0);else if(be(n)){const r=n.getPreviousSibling();if(be(r)){const n=function(e){let t=e,n=t.getFirstChild();for(;Pe(n);){const e=n.getLastChild();if(!be(e))break;t=e,n=t.getFirstChild()}return t}(r),i=n.getParent();if(Pe(i)){const n=ie(i);n+1<ie(t)&&e.setIndent(n)}}}};return e.registerNodeTransform(Se,e=>{const n=[e];for(;n.length>0;){const e=n.shift();if(Pe(e))for(const r of e.getChildren())if(be(r)){t(r);const e=r.getFirstChild();Pe(e)&&n.push(e)}}})}const Ue=H({build:(e,t,n)=>G(t),config:j({hasStrictIndent:!1,shouldPreserveNumbering:!1}),name:"@lexical/list/List",nodes:()=>[Se,ye],register(e,t,n){const r=n.getOutput();return s(Q(()=>Be(e,{restoreNumbering:r.shouldPreserveNumbering.value})),Q(()=>r.hasStrictIndent.value?We(e):void 0))}}),Je=H({build:(e,t)=>G(t),config:j({disableTakeFocusOnClick:!1}),dependencies:[Ue],name:"@lexical/list/CheckList",register:(e,t,n)=>Ee(e,n.getOutput())});function Ve(e){const t=[];for(const n of e)if(be(n)){t.push(n);const e=n.getChildren();if(e.length>1)for(const n of e)Pe(n)&&t.push(Te().append(n))}else t.push(Te().append(n));return t}const qe=ee({$import:(e,t)=>{let n;var r;return ne(t,"ol")?n=Fe("number",t.start):n=(r=t).matches('[__lexicallisttype="check"], .contains-task-list, [data-is-checklist="1"]')||null!==r.querySelector(":scope > [aria-checked]")?Fe("check"):Fe("bullet"),F(n,t),[n.splice(0,0,Ve(e.$importChildren(t)))]},match:te.tag("ol","ul"),name:"@lexical/list/list"});function ze(e,t){if(1!==t.length)return t;const n=t[0];return L(n)&&!e.getFormatType()&&n.getFormatType()?(e.setFormat(n.getFormatType()),n.getChildren()):t}const He=ee({$import:(e,t)=>{const n=t.getAttribute("aria-checked"),r=Te("true"===n||"false"!==n&&void 0);return N(r,t),F(r,t),[r.splice(0,0,ze(r,e.$importChildren(t)))]},match:te.tag("li"),name:"@lexical/list/li"});function je(e,t,n){const r=ne(n,"input")?n:n.querySelector('input[type="checkbox"]');if(!r||"checkbox"!==r.getAttribute("type"))return[];const i=Te(r.hasAttribute("checked"));return N(i,t),F(i,t),[i.splice(0,0,ze(i,e.$importChildren(t)))]}const Xe={$accepts:e=>be(e)||Pe(e),$packageRun:e=>[Te().splice(0,0,e)],name:"ListSchema"},Ge=[ee({$import:(e,t,n)=>{const r=t.querySelector(':scope > input[type="checkbox"]');return r?je(e,t,r):n()},match:te.tag("li").classAll("task-list-item"),name:"@lexical/list/li-task-list-item"}),ee({$import:(e,t,n)=>{const r=t.querySelector(":scope > .checkbox-wrapper");if(!r)return n();const i=r.querySelector(':scope > input[type="checkbox"]');return i?je(e,t,i):n()},match:te.tag("li").classAll("joplin-checkbox"),name:"@lexical/list/li-joplin-checkbox"}),qe,He],Qe=H({dependencies:[Y,Ue,X(Z,{rules:Ge})],name:"@lexical/list/Import"});function Ye(e,t){e.update(()=>ae(t))}function Ze(e){e.update(()=>de())}export{Te as $createListItemNode,Fe as $createListNode,ie as $getListDepth,_e as $handleListInsertParagraph,ae as $insertList,be as $isListItemNode,Pe as $isListNode,de as $removeList,Je as CheckListExtension,Ae as INSERT_CHECK_LIST_COMMAND,Re as INSERT_ORDERED_LIST_COMMAND,$e as INSERT_UNORDERED_LIST_COMMAND,Ue as ListExtension,Qe as ListImportExtension,Ge as ListImportRules,ye as ListItemNode,Se as ListNode,Xe as ListSchema,Ke as REMOVE_LIST_COMMAND,Me as UPDATE_LIST_START_COMMAND,Ye as insertList,Ee as registerCheckList,Be as registerList,We as registerListStrictIndentTransform,Ze as removeList}; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| export interface ListConfig { | ||
| /** | ||
| * When `true`, enforces strict indentation rules for list items, ensuring consistent structure. | ||
| * When `false` (default), indentation is more flexible. | ||
| */ | ||
| hasStrictIndent: boolean; | ||
| shouldPreserveNumbering: boolean; | ||
| } | ||
| /** | ||
| * Configures {@link ListNode}, {@link ListItemNode} and registers | ||
| * the strict indent transform if `hasStrictIndent` is true (default false). | ||
| */ | ||
| export declare const ListExtension: import("lexical").LexicalExtension<ListConfig, "@lexical/list/List", import("@lexical/extension").NamedSignalsOutput<ListConfig>, unknown>; | ||
| export interface CheckListConfig { | ||
| disableTakeFocusOnClick: boolean; | ||
| } | ||
| /** | ||
| * Registers checklist functionality for {@link ListNode} and | ||
| * {@link ListItemNode} with a `INSERT_CHECK_LIST_COMMAND` listener and | ||
| * the expected keyboard and mouse interactions for checkboxes. | ||
| */ | ||
| export declare const CheckListExtension: import("lexical").LexicalExtension<CheckListConfig, "@lexical/list/CheckList", import("@lexical/extension").NamedSignalsOutput<CheckListConfig>, unknown>; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { ListNode } from './'; | ||
| import type { BaseSelection, DOMExportOutput, EditorConfig, LexicalNode, LexicalUpdateJSON, NodeKey, ParagraphNode, RangeSelection, SerializedElementNode, Spread } from 'lexical'; | ||
| import { ElementNode, LexicalEditor } from 'lexical'; | ||
| export type SerializedListItemNode = Spread<{ | ||
| checked: boolean | undefined; | ||
| value: number; | ||
| }, SerializedElementNode>; | ||
| /** @noInheritDoc */ | ||
| export declare class ListItemNode extends ElementNode { | ||
| /** @internal */ | ||
| __value: number; | ||
| /** @internal */ | ||
| __checked?: boolean; | ||
| /** @internal */ | ||
| $config(): import("lexical").StaticNodeConfigRecord<"listitem", { | ||
| $transform: (node: ListItemNode) => void; | ||
| extends: typeof ElementNode; | ||
| importDOM: import("lexical").DOMConversionMap<HTMLElement>; | ||
| }>; | ||
| constructor(value?: number, checked?: undefined | boolean, key?: NodeKey); | ||
| afterCloneFrom(prevNode: this): void; | ||
| createDOM(config: EditorConfig): HTMLElement; | ||
| updateListItemDOM(prevNode: ListItemNode | null, dom: HTMLLIElement, config: EditorConfig): void; | ||
| updateDOM(prevNode: ListItemNode, dom: HTMLElement, config: EditorConfig): boolean; | ||
| updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedListItemNode>): this; | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput; | ||
| exportJSON(): SerializedListItemNode; | ||
| append(...nodes: LexicalNode[]): this; | ||
| replace<N extends LexicalNode>(replaceWithNode: N, includeChildren?: boolean): N; | ||
| insertAfter(node: LexicalNode, restoreSelection?: boolean): LexicalNode; | ||
| remove(preserveEmptyParent?: boolean): void; | ||
| resetOnCopyNodeFrom(original: this): void; | ||
| insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ListItemNode | ParagraphNode; | ||
| collapseAtStart(selection: RangeSelection): true; | ||
| getValue(): number; | ||
| setValue(value: number): this; | ||
| getChecked(): boolean | undefined; | ||
| setChecked(checked?: boolean): this; | ||
| toggleChecked(): this; | ||
| getIndent(): number; | ||
| setIndent(indent: number): this; | ||
| /** @deprecated @internal */ | ||
| canInsertAfter(node: LexicalNode): boolean; | ||
| /** @deprecated @internal */ | ||
| canReplaceWith(replacement: LexicalNode): boolean; | ||
| canMergeWith(node: LexicalNode): boolean; | ||
| extractWithChild(child: LexicalNode, selection: BaseSelection): boolean; | ||
| isParentRequired(): true; | ||
| createParentElementNode(): ListNode; | ||
| canMergeWhenEmpty(): true; | ||
| } | ||
| /** | ||
| * Creates a new List Item node, passing true/false will convert it to a checkbox input. | ||
| * @param checked - Is the List Item a checkbox and, if so, is it checked? undefined/null: not a checkbox, true/false is a checkbox and checked/unchecked, respectively. | ||
| * @returns The new List Item. | ||
| */ | ||
| export declare function $createListItemNode(checked?: boolean): ListItemNode; | ||
| /** | ||
| * Checks to see if the node is a ListItemNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode, false otherwise. | ||
| */ | ||
| export declare function $isListItemNode(node: LexicalNode | null | undefined): node is ListItemNode; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import { DOMExportOutput, EditorConfig, ElementNode, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey, SerializedElementNode, Spread } from 'lexical'; | ||
| import { ListItemNode } from '.'; | ||
| export type SerializedListNode = Spread<{ | ||
| listType: ListType; | ||
| start: number; | ||
| tag: ListNodeTagType; | ||
| }, SerializedElementNode>; | ||
| export type ListType = 'number' | 'bullet' | 'check'; | ||
| export type ListNodeTagType = 'ul' | 'ol'; | ||
| /** @noInheritDoc */ | ||
| export declare class ListNode extends ElementNode { | ||
| /** @internal */ | ||
| __tag: ListNodeTagType; | ||
| /** @internal */ | ||
| __start: number; | ||
| /** @internal */ | ||
| __listType: ListType; | ||
| /** @internal */ | ||
| $config(): import("lexical").StaticNodeConfigRecord<"list", { | ||
| $transform: (node: ListNode) => void; | ||
| extends: typeof ElementNode; | ||
| importDOM: import("lexical").DOMConversionMap<HTMLElement>; | ||
| }>; | ||
| constructor(listType?: ListType, start?: number, key?: NodeKey); | ||
| afterCloneFrom(prevNode: this): void; | ||
| getTag(): ListNodeTagType; | ||
| setListType(type: ListType): this; | ||
| getListType(): ListType; | ||
| getStart(): number; | ||
| setStart(start: number): this; | ||
| createDOM(config: EditorConfig, _editor?: LexicalEditor): HTMLElement; | ||
| updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean; | ||
| updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedListNode>): this; | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput; | ||
| exportJSON(): SerializedListNode; | ||
| canBeEmpty(): false; | ||
| canIndent(): false; | ||
| splice(start: number, deleteCount: number, nodesToInsert: LexicalNode[]): this; | ||
| extractWithChild(child: LexicalNode): boolean; | ||
| /** | ||
| * Create an appropriate ListItemNode to be a child of this ListNode, | ||
| * {@link $createListItemNode} is the default implementation. | ||
| * | ||
| * @returns A new ListItemNode. | ||
| */ | ||
| createListItemNode(): ListItemNode; | ||
| } | ||
| /** | ||
| * Creates a ListNode of listType. | ||
| * @param listType - The type of list to be created. Can be 'number', 'bullet', or 'check'. | ||
| * @param start - Where an ordered list starts its count, start = 1 if left undefined. | ||
| * @returns The new ListNode | ||
| */ | ||
| export declare function $createListNode(listType?: ListType, start?: number): ListNode; | ||
| /** | ||
| * Checks to see if the node is a ListNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListNode, false otherwise. | ||
| */ | ||
| export declare function $isListNode(node: LexicalNode | null | undefined): node is ListNode; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { ChildSchema } from '@lexical/html'; | ||
| /** | ||
| * A {@link ChildSchema} that enforces ListNode invariants: only | ||
| * `ListItemNode` and (immediately-nested) `ListNode` children are | ||
| * accepted; runs of other children get wrapped in a fresh | ||
| * `ListItemNode`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ListSchema: ChildSchema; | ||
| /** | ||
| * Import rules for {@link ListNode} and {@link ListItemNode}, including | ||
| * GitHub task-list and Joplin checkbox heuristics. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ListImportRules: (import("@lexical/html").DOMImportRule<import("@lexical/html").ElementSelectorBuilder<HTMLOListElement | HTMLUListElement, Record<string, never>>> | import("@lexical/html").DOMImportRule<import("@lexical/html").ElementSelectorBuilder<HTMLLIElement, Record<string, never>>>)[]; | ||
| /** | ||
| * Bundles {@link ListImportRules} (plus {@link CoreImportExtension}) into | ||
| * a single dependency. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ListImportExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@lexical/list/Import", unknown, unknown>; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { LexicalCommand, LexicalEditor, NodeKey } from 'lexical'; | ||
| export declare const UPDATE_LIST_START_COMMAND: LexicalCommand<{ | ||
| listNodeKey: NodeKey; | ||
| newStart: number; | ||
| }>; | ||
| export declare const INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>; | ||
| export declare const INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>; | ||
| export declare const REMOVE_LIST_COMMAND: LexicalCommand<void>; | ||
| export interface RegisterListOptions { | ||
| restoreNumbering?: boolean; | ||
| } | ||
| export declare function registerList(editor: LexicalEditor, options?: RegisterListOptions): () => void; | ||
| export declare function registerListStrictIndentTransform(editor: LexicalEditor): () => void; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { LexicalNode, Spread } from 'lexical'; | ||
| import { ListItemNode, ListNode } from './'; | ||
| /** | ||
| * Checks the depth of listNode from the root node. | ||
| * @param listNode - The ListNode to be checked. | ||
| * @returns The depth of the ListNode. | ||
| */ | ||
| export declare function $getListDepth(listNode: ListNode): number; | ||
| /** | ||
| * Finds the nearest ancestral ListNode and returns it, throws an invariant if listItem is not a ListItemNode. | ||
| * @param listItem - The node to be checked. | ||
| * @returns The ListNode found. | ||
| */ | ||
| export declare function $getTopListNode(listItem: LexicalNode): ListNode; | ||
| /** | ||
| * Checks if listItem has no child ListNodes and has no ListItemNode ancestors with siblings. | ||
| * @param listItem - the ListItemNode to be checked. | ||
| * @returns true if listItem has no child ListNode and no ListItemNode ancestors with siblings, false otherwise. | ||
| */ | ||
| export declare function $isLastItemInList(listItem: ListItemNode): boolean; | ||
| /** | ||
| * A recursive Depth-First Search (Postorder Traversal) that finds all of a node's children | ||
| * that are of type ListItemNode and returns them in an array. | ||
| * @param node - The ListNode to start the search. | ||
| * @returns An array containing all nodes of type ListItemNode found. | ||
| */ | ||
| export declare function $getAllListItems(node: ListNode): Array<ListItemNode>; | ||
| declare const NestedListNodeBrand: unique symbol; | ||
| /** | ||
| * Checks to see if the passed node is a ListItemNode and has a ListNode as a child. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode and has a ListNode child, false otherwise. | ||
| */ | ||
| export declare function isNestedListNode(node: LexicalNode | null | undefined): node is Spread<{ | ||
| getFirstChild(): ListNode; | ||
| [NestedListNodeBrand]: never; | ||
| }, ListItemNode>; | ||
| /** | ||
| * Traverses up the tree and returns the first ListItemNode found. | ||
| * @param node - Node to start the search. | ||
| * @returns The first ListItemNode found, or null if none exist. | ||
| */ | ||
| export declare function $findNearestListItemNode(node: LexicalNode): ListItemNode | null; | ||
| /** | ||
| * Takes a deeply nested ListNode or ListItemNode and traverses up the branch to delete the first | ||
| * ancestral ListNode (which could be the root ListNode) or ListItemNode with siblings, essentially | ||
| * bringing the deeply nested node up the branch once. Would remove sublist if it has siblings. | ||
| * Should not break ListItem -> List -> ListItem chain as empty List/ItemNodes should be removed on .remove(). | ||
| * @param sublist - The nested ListNode or ListItemNode to be brought up the branch. | ||
| */ | ||
| export declare function $removeHighestEmptyListParent(sublist: ListItemNode | ListNode): void; | ||
| /** | ||
| * Calculates the start value for a new list created by splitting an existing list. | ||
| */ | ||
| export declare function $getNewListStart(list: ListNode, listItem: ListItemNode): number; | ||
| export {}; |
+469
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {ListItemNode} from './LexicalListItemNode'; | ||
| import type {LexicalCommand, LexicalEditor} from 'lexical'; | ||
| import {Signal} from '@lexical/extension'; | ||
| import { | ||
| $findMatchingParent, | ||
| calculateZoomLevel, | ||
| isHTMLElement, | ||
| mergeRegister, | ||
| } from '@lexical/utils'; | ||
| import { | ||
| $addUpdateTag, | ||
| $getNearestNodeFromDOMNode, | ||
| $getSelection, | ||
| $isElementNode, | ||
| $isRangeSelection, | ||
| COMMAND_PRIORITY_LOW, | ||
| createCommand, | ||
| getNearestEditorFromDOMNode, | ||
| KEY_ARROW_DOWN_COMMAND, | ||
| KEY_ARROW_LEFT_COMMAND, | ||
| KEY_ARROW_UP_COMMAND, | ||
| KEY_ESCAPE_COMMAND, | ||
| KEY_SPACE_COMMAND, | ||
| SKIP_DOM_SELECTION_TAG, | ||
| SKIP_SELECTION_FOCUS_TAG, | ||
| } from 'lexical'; | ||
| import {$insertList} from './formatList'; | ||
| import {$isListItemNode} from './LexicalListItemNode'; | ||
| import {$isListNode} from './LexicalListNode'; | ||
| export const INSERT_CHECK_LIST_COMMAND: LexicalCommand<void> = createCommand( | ||
| 'INSERT_CHECK_LIST_COMMAND', | ||
| ); | ||
| /** | ||
| * Registers the checklist plugin with the editor. | ||
| * @param editor The LexicalEditor instance. | ||
| * @param options Optional configuration. | ||
| * - disableTakeFocusOnClick: If true, clicking a checklist item will not focus the editor (useful for mobile). | ||
| */ | ||
| export function registerCheckList( | ||
| editor: LexicalEditor, | ||
| options?: {disableTakeFocusOnClick?: boolean | Signal<boolean>}, | ||
| ) { | ||
| const disableTakeFocusOnClick = | ||
| (options && options.disableTakeFocusOnClick) || false; | ||
| const peekDisableTakeFocusOnClick = | ||
| typeof disableTakeFocusOnClick === 'boolean' | ||
| ? () => disableTakeFocusOnClick | ||
| : disableTakeFocusOnClick.peek.bind(disableTakeFocusOnClick); | ||
| // Mobile tap fix: the touchstart listener registered below calls | ||
| // event.preventDefault() to keep the caret away from the marker. On iOS | ||
| // Safari and Android Chrome that suppression also cancels the synthesized | ||
| // click, so handleClick never runs and the checkbox cannot be toggled by | ||
| // tap. We additionally listen for pointerup with pointerType === 'touch' | ||
| // and run the same toggle logic, deduplicating against any click that | ||
| // does fire on browsers where preventDefault doesn't suppress it. | ||
| // | ||
| // Dedup state is per-target: recorded as `__lexicalCheckListLastHandled` | ||
| // on the target element. A global window would | ||
| // block tapping a second checkbox within 500ms of toggling the first. | ||
| const DEDUP_WINDOW_MS = 500; | ||
| const isWithinDedupWindow = ( | ||
| event: PointerEvent | MouseEvent | TouchEvent, | ||
| ): boolean => { | ||
| const target = event.target; | ||
| if (!isHTMLElement(target)) { | ||
| return false; | ||
| } | ||
| // @ts-ignore internal field | ||
| const last = target.__lexicalCheckListLastHandled as number | undefined; | ||
| return last !== undefined && event.timeStamp - last < DEDUP_WINDOW_MS; | ||
| }; | ||
| const recordHandled = (event: PointerEvent | MouseEvent | TouchEvent) => { | ||
| const target = event.target; | ||
| if (isHTMLElement(target)) { | ||
| // @ts-ignore internal field | ||
| target.__lexicalCheckListLastHandled = event.timeStamp; | ||
| } | ||
| }; | ||
| const configHandleClick = (event: PointerEvent | MouseEvent | TouchEvent) => { | ||
| if (isWithinDedupWindow(event)) { | ||
| return; | ||
| } | ||
| recordHandled(event); | ||
| handleClick(event, peekDisableTakeFocusOnClick()); | ||
| }; | ||
| const configHandlePointerUp = (event: PointerEvent) => { | ||
| if (event.pointerType !== 'touch') { | ||
| return; | ||
| } | ||
| if (isWithinDedupWindow(event)) { | ||
| return; | ||
| } | ||
| recordHandled(event); | ||
| handleClick(event, peekDisableTakeFocusOnClick()); | ||
| }; | ||
| const configHandleSelectDefaults = ( | ||
| event: PointerEvent | MouseEvent | TouchEvent, | ||
| ) => { | ||
| handleSelectDefaults(event, peekDisableTakeFocusOnClick()); | ||
| }; | ||
| return mergeRegister( | ||
| editor.registerCommand( | ||
| INSERT_CHECK_LIST_COMMAND, | ||
| () => { | ||
| $insertList('check'); | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand<KeyboardEvent>( | ||
| KEY_ARROW_DOWN_COMMAND, | ||
| event => { | ||
| return handleArrowUpOrDown(event, editor, false); | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand<KeyboardEvent>( | ||
| KEY_ARROW_UP_COMMAND, | ||
| event => { | ||
| return handleArrowUpOrDown(event, editor, true); | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand<KeyboardEvent>( | ||
| KEY_ESCAPE_COMMAND, | ||
| () => { | ||
| const activeItem = getActiveCheckListItem(); | ||
| if (activeItem != null) { | ||
| const rootElement = editor.getRootElement(); | ||
| if (rootElement != null) { | ||
| rootElement.focus(); | ||
| } | ||
| return true; | ||
| } | ||
| return false; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand<KeyboardEvent>( | ||
| KEY_SPACE_COMMAND, | ||
| event => { | ||
| const activeItem = getActiveCheckListItem(); | ||
| if (activeItem != null && editor.isEditable()) { | ||
| editor.update(() => { | ||
| const listItemNode = $getNearestNodeFromDOMNode(activeItem); | ||
| if ($isListItemNode(listItemNode)) { | ||
| event.preventDefault(); | ||
| listItemNode.toggleChecked(); | ||
| } | ||
| }); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand<KeyboardEvent>( | ||
| KEY_ARROW_LEFT_COMMAND, | ||
| event => { | ||
| return editor.getEditorState().read(() => { | ||
| const selection = $getSelection(); | ||
| if ($isRangeSelection(selection) && selection.isCollapsed()) { | ||
| const {anchor} = selection; | ||
| const isElement = anchor.type === 'element'; | ||
| if (isElement || anchor.offset === 0) { | ||
| const anchorNode = anchor.getNode(); | ||
| const elementNode = $findMatchingParent( | ||
| anchorNode, | ||
| node => $isElementNode(node) && !node.isInline(), | ||
| ); | ||
| if ($isListItemNode(elementNode)) { | ||
| const parent = elementNode.getParent(); | ||
| if ( | ||
| $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; | ||
| }); | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerRootListener(rootElement => { | ||
| if (rootElement !== null) { | ||
| rootElement.addEventListener('click', configHandleClick); | ||
| rootElement.addEventListener('pointerup', configHandlePointerUp); | ||
| // Use capture so we run before other listeners that might move focus. | ||
| rootElement.addEventListener( | ||
| 'pointerdown', | ||
| configHandleSelectDefaults, | ||
| { | ||
| capture: true, | ||
| }, | ||
| ); | ||
| // Some browsers / integrations still generate mousedown events; handle them too. | ||
| rootElement.addEventListener('mousedown', configHandleSelectDefaults, { | ||
| capture: true, | ||
| }); | ||
| // Intercept touchstart to stop the mobile browser from placing the caret | ||
| // and opening the keyboard when tapping the checklist marker. | ||
| rootElement.addEventListener('touchstart', configHandleSelectDefaults, { | ||
| capture: true, | ||
| passive: false, | ||
| }); | ||
| return () => { | ||
| rootElement.removeEventListener('click', configHandleClick); | ||
| rootElement.removeEventListener('pointerup', configHandlePointerUp); | ||
| rootElement.removeEventListener( | ||
| 'pointerdown', | ||
| configHandleSelectDefaults, | ||
| { | ||
| capture: true, | ||
| }, | ||
| ); | ||
| rootElement.removeEventListener( | ||
| 'mousedown', | ||
| configHandleSelectDefaults, | ||
| { | ||
| capture: true, | ||
| }, | ||
| ); | ||
| rootElement.removeEventListener( | ||
| 'touchstart', | ||
| configHandleSelectDefaults, | ||
| { | ||
| capture: true, | ||
| }, | ||
| ); | ||
| }; | ||
| } | ||
| }), | ||
| ); | ||
| } | ||
| function handleCheckItemEvent( | ||
| event: PointerEvent | MouseEvent | TouchEvent, | ||
| callback: () => void, | ||
| ) { | ||
| const target = event.target; | ||
| if (!isHTMLElement(target)) { | ||
| return; | ||
| } | ||
| // Ignore clicks on LI that have nested lists | ||
| const firstChild = target.firstChild; | ||
| if ( | ||
| isHTMLElement(firstChild) && | ||
| (firstChild.tagName === 'UL' || firstChild.tagName === 'OL') | ||
| ) { | ||
| return; | ||
| } | ||
| const parentNode = target.parentNode; | ||
| // @ts-ignore internal field | ||
| if (!parentNode || parentNode.__lexicalListType !== 'check') { | ||
| return; | ||
| } | ||
| let clientX: number | null = null; | ||
| let pointerType: string | null = null; | ||
| if ('clientX' in event) { | ||
| clientX = event.clientX; | ||
| } else if ('touches' in event) { | ||
| const touches = event.touches; | ||
| if (touches.length > 0) { | ||
| clientX = touches[0].clientX; | ||
| pointerType = 'touch'; | ||
| } | ||
| } | ||
| // If we couldn't resolve a clientX (unexpected input), bail out. | ||
| if (clientX == null) { | ||
| return; | ||
| } | ||
| const rect = target.getBoundingClientRect(); | ||
| const zoom = calculateZoomLevel(target); | ||
| const clientXInPixels = clientX / zoom; | ||
| // Use getComputedStyle if available, otherwise fallback to 0px width | ||
| const beforeStyles = window.getComputedStyle | ||
| ? window.getComputedStyle(target, '::before') | ||
| : ({width: '0px'} as CSSStyleDeclaration); | ||
| const beforeWidthInPixels = parseFloat(beforeStyles.width); | ||
| // Make click area slightly larger for touch devices to improve accessibility | ||
| // Determine whether this is a touch event; some environments may supply | ||
| // pointerType on PointerEvent while touch events use the `touches` API above. | ||
| const isTouchEvent = | ||
| pointerType === 'touch' || | ||
| ('pointerType' in event && event.pointerType === 'touch'); | ||
| const clickAreaPadding = isTouchEvent ? 32 : 0; // Add 32px padding for touch events | ||
| if ( | ||
| target.dir === 'rtl' | ||
| ? clientXInPixels < rect.right + clickAreaPadding && | ||
| clientXInPixels > rect.right - beforeWidthInPixels - clickAreaPadding | ||
| : clientXInPixels > rect.left - clickAreaPadding && | ||
| clientXInPixels < rect.left + beforeWidthInPixels + clickAreaPadding | ||
| ) { | ||
| callback(); | ||
| } | ||
| } | ||
| function handleClick( | ||
| event: PointerEvent | MouseEvent | TouchEvent, | ||
| disableFocusOnClick: boolean, | ||
| ) { | ||
| handleCheckItemEvent(event, () => { | ||
| if (isHTMLElement(event.target)) { | ||
| const domNode = event.target; | ||
| const editor = getNearestEditorFromDOMNode(domNode); | ||
| if (editor != null && editor.isEditable()) { | ||
| editor.update(() => { | ||
| const node = $getNearestNodeFromDOMNode(domNode); | ||
| if ($isListItemNode(node)) { | ||
| if (disableFocusOnClick) { | ||
| $addUpdateTag(SKIP_SELECTION_FOCUS_TAG); | ||
| $addUpdateTag(SKIP_DOM_SELECTION_TAG); | ||
| } else { | ||
| domNode.focus(); | ||
| } | ||
| node.toggleChecked(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Prevents default focus switch behavior | ||
| * | ||
| * @param event might be of type PointerEvent, MouseEvent, or TouchEvent, hence the generic Event type | ||
| * | ||
| */ | ||
| function handleSelectDefaults( | ||
| event: PointerEvent | MouseEvent | TouchEvent, | ||
| disableTakeFocusOnClick: boolean, | ||
| ) { | ||
| handleCheckItemEvent(event, () => { | ||
| // Prevents caret moving when clicking on check mark. | ||
| event.preventDefault(); | ||
| if (disableTakeFocusOnClick) { | ||
| event.stopPropagation(); | ||
| } | ||
| }); | ||
| } | ||
| function getActiveCheckListItem(): HTMLElement | null { | ||
| const activeElement = document.activeElement; | ||
| return isHTMLElement(activeElement) && | ||
| activeElement.tagName === 'LI' && | ||
| activeElement.parentNode != null && | ||
| // @ts-ignore internal field | ||
| activeElement.parentNode.__lexicalListType === 'check' | ||
| ? activeElement | ||
| : null; | ||
| } | ||
| function findCheckListItemSibling( | ||
| node: ListItemNode, | ||
| backward: boolean, | ||
| ): ListItemNode | null { | ||
| let sibling = backward ? node.getPreviousSibling() : node.getNextSibling(); | ||
| let parent: ListItemNode | null = node; | ||
| // Going up in a tree to get non-null sibling | ||
| while (sibling == null && $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 ($isListItemNode(sibling)) { | ||
| const firstChild = backward | ||
| ? sibling.getLastChild() | ||
| : sibling.getFirstChild(); | ||
| if (!$isListNode(firstChild)) { | ||
| return sibling; | ||
| } | ||
| sibling = backward ? firstChild.getLastChild() : firstChild.getFirstChild(); | ||
| } | ||
| return null; | ||
| } | ||
| function handleArrowUpOrDown( | ||
| event: KeyboardEvent, | ||
| editor: LexicalEditor, | ||
| backward: boolean, | ||
| ) { | ||
| const activeItem = getActiveCheckListItem(); | ||
| if (activeItem != null) { | ||
| editor.update(() => { | ||
| const listItem = $getNearestNodeFromDOMNode(activeItem); | ||
| if (!$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; | ||
| } |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import {$getNearestNodeOfType} from '@lexical/utils'; | ||
| import { | ||
| $copyNode, | ||
| $createParagraphNode, | ||
| $getChildCaret, | ||
| $getSelection, | ||
| $isElementNode, | ||
| $isLeafNode, | ||
| $isRangeSelection, | ||
| $isRootOrShadowRoot, | ||
| $isTextNode, | ||
| $normalizeCaret, | ||
| $setPointFromCaret, | ||
| ElementNode, | ||
| LexicalNode, | ||
| NodeKey, | ||
| ParagraphNode, | ||
| } from 'lexical'; | ||
| import { | ||
| $createListItemNode, | ||
| $createListNode, | ||
| $isListItemNode, | ||
| $isListNode, | ||
| ListItemNode, | ||
| ListNode, | ||
| } from './'; | ||
| import {ListType} from './LexicalListNode'; | ||
| import { | ||
| $getAllListItems, | ||
| $getNewListStart, | ||
| $getTopListNode, | ||
| $removeHighestEmptyListParent, | ||
| isNestedListNode, | ||
| } from './utils'; | ||
| function $isSelectingEmptyListItem( | ||
| anchorNode: ListItemNode | LexicalNode, | ||
| nodes: Array<LexicalNode>, | ||
| ): boolean { | ||
| return ( | ||
| $isListItemNode(anchorNode) && | ||
| (nodes.length === 0 || | ||
| (nodes.length === 1 && | ||
| anchorNode.is(nodes[0]) && | ||
| anchorNode.getChildrenSize() === 0)) | ||
| ); | ||
| } | ||
| /** | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export function $insertList(listType: ListType): void { | ||
| const selection = $getSelection(); | ||
| if (selection !== null) { | ||
| let nodes = selection.getNodes(); | ||
| if ($isRangeSelection(selection)) { | ||
| const [anchor] = selection.getStartEndPoints(); | ||
| const anchorNode = anchor.getNode(); | ||
| const anchorNodeParent = anchorNode.getParent(); | ||
| if ($isRootOrShadowRoot(anchorNode)) { | ||
| const firstChild = anchorNode.getFirstChild(); | ||
| if (firstChild) { | ||
| nodes = firstChild.selectStart().getNodes(); | ||
| } else { | ||
| const paragraph = $createParagraphNode(); | ||
| anchorNode.append(paragraph); | ||
| nodes = paragraph.select().getNodes(); | ||
| } | ||
| } else if ($isSelectingEmptyListItem(anchorNode, nodes)) { | ||
| const list = $createListNode(listType); | ||
| if ($isRootOrShadowRoot(anchorNodeParent)) { | ||
| anchorNode.replace(list); | ||
| const listItem = $createListItemNode(); | ||
| if ($isElementNode(anchorNode)) { | ||
| listItem.setFormat(anchorNode.getFormatType()); | ||
| listItem.setIndent(anchorNode.getIndent()); | ||
| } | ||
| list.append(listItem); | ||
| } else if ($isListItemNode(anchorNode)) { | ||
| const parent = anchorNode.getParentOrThrow(); | ||
| append(list, parent.getChildren()); | ||
| parent.replace(list); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| const handled = new Set(); | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ( | ||
| $isElementNode(node) && | ||
| node.isEmpty() && | ||
| !$isListItemNode(node) && | ||
| !handled.has(node.getKey()) | ||
| ) { | ||
| $createListOrMerge(node, listType); | ||
| continue; | ||
| } | ||
| let parent = $isLeafNode(node) | ||
| ? node.getParent() | ||
| : $isListItemNode(node) && node.isEmpty() | ||
| ? node | ||
| : null; | ||
| while (parent != null) { | ||
| const parentKey = parent.getKey(); | ||
| if ($isListNode(parent)) { | ||
| if (!handled.has(parentKey)) { | ||
| const newListNode = $createListNode(listType); | ||
| append(newListNode, parent.getChildren()); | ||
| parent.replace(newListNode); | ||
| handled.add(parentKey); | ||
| } | ||
| break; | ||
| } else { | ||
| const nextParent = parent.getParent(); | ||
| if ($isRootOrShadowRoot(nextParent) && !handled.has(parentKey)) { | ||
| handled.add(parentKey); | ||
| $createListOrMerge(parent, listType); | ||
| break; | ||
| } | ||
| parent = nextParent; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function append(node: ElementNode, nodesToAppend: Array<LexicalNode>) { | ||
| node.splice(node.getChildrenSize(), 0, nodesToAppend); | ||
| } | ||
| function $createListOrMerge(node: ElementNode, listType: ListType): ListNode { | ||
| if ($isListNode(node)) { | ||
| return node; | ||
| } | ||
| const previousSibling = node.getPreviousSibling(); | ||
| const nextSibling = node.getNextSibling(); | ||
| const listItem = $createListItemNode(); | ||
| append(listItem, node.getChildren()); | ||
| let targetList; | ||
| if ( | ||
| $isListNode(previousSibling) && | ||
| listType === previousSibling.getListType() | ||
| ) { | ||
| previousSibling.append(listItem); | ||
| // if the same type of list is on both sides, merge them. | ||
| if ($isListNode(nextSibling) && listType === nextSibling.getListType()) { | ||
| append(previousSibling, nextSibling.getChildren()); | ||
| nextSibling.remove(); | ||
| } | ||
| targetList = previousSibling; | ||
| } else if ( | ||
| $isListNode(nextSibling) && | ||
| listType === nextSibling.getListType() | ||
| ) { | ||
| nextSibling.getFirstChildOrThrow().insertBefore(listItem); | ||
| targetList = nextSibling; | ||
| } else { | ||
| const list = $createListNode(listType); | ||
| list.append(listItem); | ||
| node.replace(list); | ||
| targetList = list; | ||
| } | ||
| // listItem needs to be attached to root prior to setting indent | ||
| listItem.setFormat(node.getFormatType()); | ||
| listItem.setIndent(node.getIndent()); | ||
| // Preserve element-anchored selections by updating them to anchor to the listItem instead of the listNode. | ||
| const selection = $getSelection(); | ||
| if ($isRangeSelection(selection)) { | ||
| if (targetList.getKey() === selection.anchor.key) { | ||
| selection.anchor.set( | ||
| listItem.getKey(), | ||
| selection.anchor.offset, | ||
| 'element', | ||
| ); | ||
| } | ||
| if (targetList.getKey() === selection.focus.key) { | ||
| selection.focus.set(listItem.getKey(), selection.focus.offset, 'element'); | ||
| } | ||
| } | ||
| node.remove(); | ||
| return targetList; | ||
| } | ||
| /** | ||
| * A recursive function that goes through each list and their children, including nested lists, | ||
| * appending list2 children after list1 children and updating ListItemNode values. | ||
| * @param list1 - The first list to be merged. | ||
| * @param list2 - The second list to be merged. | ||
| */ | ||
| export function mergeLists(list1: ListNode, list2: ListNode): void { | ||
| const listItem1 = list1.getLastChild(); | ||
| const listItem2 = list2.getFirstChild(); | ||
| if ( | ||
| listItem1 && | ||
| listItem2 && | ||
| isNestedListNode(listItem1) && | ||
| isNestedListNode(listItem2) | ||
| ) { | ||
| mergeLists(listItem1.getFirstChild(), listItem2.getFirstChild()); | ||
| listItem2.remove(); | ||
| } | ||
| const toMerge = list2.getChildren(); | ||
| if (toMerge.length > 0) { | ||
| list1.append(...toMerge); | ||
| } | ||
| list2.remove(); | ||
| } | ||
| /** | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| */ | ||
| export function $removeList(): void { | ||
| const selection = $getSelection(); | ||
| if ($isRangeSelection(selection)) { | ||
| const listNodes = new Set<ListNode>(); | ||
| const nodes = selection.getNodes(); | ||
| const anchorNode = selection.anchor.getNode(); | ||
| if ($isSelectingEmptyListItem(anchorNode, nodes)) { | ||
| listNodes.add($getTopListNode(anchorNode)); | ||
| } else { | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ($isLeafNode(node)) { | ||
| const listItemNode = $getNearestNodeOfType(node, ListItemNode); | ||
| if (listItemNode != null) { | ||
| listNodes.add($getTopListNode(listItemNode)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (const listNode of listNodes) { | ||
| let insertionPoint: ListNode | ParagraphNode = listNode; | ||
| const listItems = $getAllListItems(listNode); | ||
| for (const listItemNode of listItems) { | ||
| const paragraph = $createParagraphNode() | ||
| .setTextStyle(selection.style) | ||
| .setTextFormat(selection.format); | ||
| append(paragraph, listItemNode.getChildren()); | ||
| insertionPoint.insertAfter(paragraph); | ||
| insertionPoint = paragraph; | ||
| // When the anchor and focus fall on the textNode | ||
| // we don't have to change the selection because the textNode will be appended to | ||
| // the newly generated paragraph. | ||
| // When selection is in empty nested list item, selection is actually on the listItemNode. | ||
| // When the corresponding listItemNode is deleted and replaced by the newly generated paragraph | ||
| // we should manually set the selection's focus and anchor to the newly generated paragraph. | ||
| if (listItemNode.__key === selection.anchor.key) { | ||
| $setPointFromCaret( | ||
| selection.anchor, | ||
| $normalizeCaret($getChildCaret(paragraph, 'next')), | ||
| ); | ||
| } | ||
| if (listItemNode.__key === selection.focus.key) { | ||
| $setPointFromCaret( | ||
| selection.focus, | ||
| $normalizeCaret($getChildCaret(paragraph, 'next')), | ||
| ); | ||
| } | ||
| listItemNode.remove(); | ||
| } | ||
| listNode.remove(); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Takes the value of a child ListItemNode and makes it the value the ListItemNode | ||
| * should be if it isn't already. Also ensures that checked is undefined if the | ||
| * parent does not have a list type of 'check'. | ||
| * @param list - The list whose children are updated. | ||
| */ | ||
| export function updateChildrenListItemValue(list: ListNode): void { | ||
| const isNotChecklist = list.getListType() !== 'check'; | ||
| let value = list.getStart(); | ||
| for (const child of list.getChildren()) { | ||
| if ($isListItemNode(child)) { | ||
| if (child.getValue() !== value) { | ||
| child.setValue(value); | ||
| } | ||
| if (isNotChecklist && child.getLatest().__checked != null) { | ||
| child.setChecked(undefined); | ||
| } | ||
| if (!$isListNode(child.getFirstChild())) { | ||
| value++; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Merge the next sibling list if same type. | ||
| * <ul> will merge with <ul>, but NOT <ul> with <ol>. | ||
| * @param list - The list whose next sibling should be potentially merged | ||
| */ | ||
| export function mergeNextSiblingListIfSameType(list: ListNode): void { | ||
| const nextSibling = list.getNextSibling(); | ||
| if ( | ||
| $isListNode(nextSibling) && | ||
| list.getListType() === nextSibling.getListType() | ||
| ) { | ||
| mergeLists(list, nextSibling); | ||
| } | ||
| } | ||
| /** | ||
| * Adds an empty ListNode/ListItemNode chain at listItemNode, so as to | ||
| * create an indent effect. Won't indent ListItemNodes that have a ListNode as | ||
| * a child, but does merge sibling ListItemNodes if one has a nested ListNode. | ||
| * @param listItemNode - The ListItemNode to be indented. | ||
| */ | ||
| export function $handleIndent(listItemNode: ListItemNode): void { | ||
| // go through each node and decide where to move it. | ||
| const removed = new Set<NodeKey>(); | ||
| if (isNestedListNode(listItemNode) || removed.has(listItemNode.getKey())) { | ||
| return; | ||
| } | ||
| const parent = listItemNode.getParent(); | ||
| // We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards | ||
| const nextSibling = | ||
| listItemNode.getNextSibling<ListItemNode>() as ListItemNode; | ||
| const previousSibling = | ||
| listItemNode.getPreviousSibling<ListItemNode>() as ListItemNode; | ||
| // if there are nested lists on either side, merge them all together. | ||
| if (isNestedListNode(nextSibling) && isNestedListNode(previousSibling)) { | ||
| const innerList = previousSibling.getFirstChild(); | ||
| if ($isListNode(innerList)) { | ||
| innerList.append(listItemNode); | ||
| const nextInnerList = nextSibling.getFirstChild(); | ||
| if ($isListNode(nextInnerList)) { | ||
| const children = nextInnerList.getChildren(); | ||
| append(innerList, children); | ||
| nextSibling.remove(); | ||
| removed.add(nextSibling.getKey()); | ||
| } | ||
| } | ||
| } else if (isNestedListNode(nextSibling)) { | ||
| // if the ListItemNode is next to a nested ListNode, merge them | ||
| const innerList = nextSibling.getFirstChild(); | ||
| if ($isListNode(innerList)) { | ||
| const firstChild = innerList.getFirstChild(); | ||
| if (firstChild !== null) { | ||
| firstChild.insertBefore(listItemNode); | ||
| } | ||
| } | ||
| } else if (isNestedListNode(previousSibling)) { | ||
| const innerList = previousSibling.getFirstChild(); | ||
| if ($isListNode(innerList)) { | ||
| innerList.append(listItemNode); | ||
| } | ||
| } else { | ||
| // otherwise, we need to create a new nested ListNode | ||
| if ($isListNode(parent)) { | ||
| const newListItem = $copyNode(listItemNode); | ||
| const newList = $copyNode(parent); | ||
| newListItem.append(newList); | ||
| newList.append(listItemNode); | ||
| if (previousSibling) { | ||
| previousSibling.insertAfter(newListItem); | ||
| } else if (nextSibling) { | ||
| nextSibling.insertBefore(newListItem); | ||
| } else { | ||
| parent.append(newListItem); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode | ||
| * has a great grandparent node of type ListNode, which is where the ListItemNode will reside | ||
| * within as a child. | ||
| * @param listItemNode - The ListItemNode to remove the indent (outdent). | ||
| */ | ||
| export function $handleOutdent(listItemNode: ListItemNode): void { | ||
| // go through each node and decide where to move it. | ||
| if (isNestedListNode(listItemNode)) { | ||
| return; | ||
| } | ||
| const parentList = listItemNode.getParent(); | ||
| const grandparentListItem = parentList ? parentList.getParent() : undefined; | ||
| const greatGrandparentList = grandparentListItem | ||
| ? grandparentListItem.getParent() | ||
| : undefined; | ||
| // If it doesn't have these ancestors, it's not indented. | ||
| if ( | ||
| $isListNode(greatGrandparentList) && | ||
| $isListItemNode(grandparentListItem) && | ||
| $isListNode(parentList) | ||
| ) { | ||
| // if it's the first child in it's parent list, insert it into the | ||
| // great grandparent list before the grandparent | ||
| const firstChild = parentList ? parentList.getFirstChild() : undefined; | ||
| const lastChild = parentList ? parentList.getLastChild() : undefined; | ||
| if (listItemNode.is(firstChild)) { | ||
| grandparentListItem.insertBefore(listItemNode); | ||
| if (parentList.isEmpty()) { | ||
| grandparentListItem.remove(); | ||
| } | ||
| // if it's the last child in it's parent list, insert it into the | ||
| // great grandparent list after the grandparent. | ||
| } else if (listItemNode.is(lastChild)) { | ||
| grandparentListItem.insertAfter(listItemNode); | ||
| if (parentList.isEmpty()) { | ||
| grandparentListItem.remove(); | ||
| } | ||
| } else { | ||
| // otherwise, we need to split the siblings into two new nested lists | ||
| const previousSiblingsListItem = $copyNode(listItemNode); | ||
| const previousSiblingsList = $copyNode(parentList); | ||
| previousSiblingsListItem.append(previousSiblingsList); | ||
| listItemNode | ||
| .getPreviousSiblings() | ||
| .forEach(sibling => previousSiblingsList.append(sibling)); | ||
| const nextSiblingsListItem = $copyNode(listItemNode); | ||
| const nextSiblingsList = $copyNode(parentList); | ||
| nextSiblingsListItem.append(nextSiblingsList); | ||
| append(nextSiblingsList, listItemNode.getNextSiblings()); | ||
| // put the sibling nested lists on either side of the grandparent list item in the great grandparent. | ||
| grandparentListItem.insertBefore(previousSiblingsListItem); | ||
| grandparentListItem.insertAfter(nextSiblingsListItem); | ||
| // replace the grandparent list item (now between the siblings) with the outdented list item. | ||
| grandparentListItem.replace(listItemNode); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode | ||
| * or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode | ||
| * (which should be the parent node) and insert the ParagraphNode as a sibling to the ListNode. If the ListNode is | ||
| * nested in a ListItemNode instead, it will add the ParagraphNode after the grandparent ListItemNode. | ||
| * Throws an invariant if the selection is not a child of a ListNode. | ||
| * @returns true if a ParagraphNode was inserted successfully, false if there is no selection | ||
| * or the selection does not contain a ListItemNode or the node already holds text. | ||
| */ | ||
| export function $handleListInsertParagraph( | ||
| restoreNumbering: boolean = false, | ||
| ): boolean { | ||
| const selection = $getSelection(); | ||
| if (!$isRangeSelection(selection) || !selection.isCollapsed()) { | ||
| return false; | ||
| } | ||
| // Only run this code on empty list items (including whitespace-only) | ||
| const anchor = selection.anchor.getNode(); | ||
| let listItem: ListItemNode | null = null; | ||
| if ($isListItemNode(anchor) && anchor.getChildrenSize() === 0) { | ||
| // Truly empty list item (element selection) | ||
| listItem = anchor; | ||
| } else if ($isTextNode(anchor)) { | ||
| // Check if the entire list item contains only whitespace text nodes | ||
| const parentListItem = anchor.getParent(); | ||
| if ( | ||
| $isListItemNode(parentListItem) && | ||
| parentListItem | ||
| .getChildren() | ||
| .every(node => $isTextNode(node) && node.getTextContent().trim() === '') | ||
| ) { | ||
| listItem = parentListItem; | ||
| } | ||
| } | ||
| if (listItem === null) { | ||
| return false; | ||
| } | ||
| const topListNode = $getTopListNode(listItem); | ||
| const parent = listItem.getParent(); | ||
| invariant( | ||
| $isListNode(parent), | ||
| 'A ListItemNode must have a ListNode for a parent.', | ||
| ); | ||
| const grandparent = parent.getParent(); | ||
| let replacementNode: ParagraphNode | ListItemNode; | ||
| if ($isRootOrShadowRoot(grandparent)) { | ||
| replacementNode = $createParagraphNode(); | ||
| topListNode.insertAfter(replacementNode); | ||
| } else if ($isListItemNode(grandparent)) { | ||
| replacementNode = $copyNode(grandparent); | ||
| grandparent.insertAfter(replacementNode); | ||
| } else { | ||
| return false; | ||
| } | ||
| replacementNode | ||
| .setTextStyle(selection.style) | ||
| .setTextFormat(selection.format) | ||
| .select(); | ||
| const nextSiblings = listItem.getNextSiblings(); | ||
| if (nextSiblings.length > 0) { | ||
| const newStart = restoreNumbering ? $getNewListStart(parent, listItem) : 1; | ||
| const newList = $copyNode(parent).setStart(newStart); | ||
| if ($isListItemNode(replacementNode)) { | ||
| const newListItem = $copyNode(replacementNode); | ||
| newListItem.append(newList); | ||
| replacementNode.insertAfter(newListItem); | ||
| } else { | ||
| replacementNode.insertAfter(newList); | ||
| } | ||
| newList.append(...nextSiblings); | ||
| } | ||
| // Don't leave hanging nested empty lists | ||
| $removeHighestEmptyListParent(listItem); | ||
| return true; | ||
| } |
+98
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {SerializedListItemNode} from './LexicalListItemNode'; | ||
| import type { | ||
| ListNodeTagType, | ||
| ListType, | ||
| SerializedListNode, | ||
| } from './LexicalListNode'; | ||
| import type {LexicalEditor} from 'lexical'; | ||
| import {INSERT_CHECK_LIST_COMMAND, registerCheckList} from './checkList'; | ||
| import { | ||
| $handleListInsertParagraph, | ||
| $insertList, | ||
| $removeList, | ||
| } from './formatList'; | ||
| import { | ||
| $createListItemNode, | ||
| $isListItemNode, | ||
| ListItemNode, | ||
| } from './LexicalListItemNode'; | ||
| import {$createListNode, $isListNode, ListNode} from './LexicalListNode'; | ||
| import {$getListDepth} from './utils'; | ||
| export { | ||
| type CheckListConfig, | ||
| CheckListExtension, | ||
| type ListConfig, | ||
| ListExtension, | ||
| } from './LexicalListExtension'; | ||
| export { | ||
| ListImportExtension, | ||
| ListImportRules, | ||
| ListSchema, | ||
| } from './ListImportExtension'; | ||
| export { | ||
| INSERT_ORDERED_LIST_COMMAND, | ||
| INSERT_UNORDERED_LIST_COMMAND, | ||
| registerList, | ||
| type RegisterListOptions, | ||
| registerListStrictIndentTransform, | ||
| REMOVE_LIST_COMMAND, | ||
| UPDATE_LIST_START_COMMAND, | ||
| } from './registerList'; | ||
| export { | ||
| $createListItemNode, | ||
| $createListNode, | ||
| $getListDepth, | ||
| $handleListInsertParagraph, | ||
| $insertList, | ||
| $isListItemNode, | ||
| $isListNode, | ||
| $removeList, | ||
| INSERT_CHECK_LIST_COMMAND, | ||
| ListItemNode, | ||
| ListNode, | ||
| ListNodeTagType, | ||
| ListType, | ||
| registerCheckList, | ||
| SerializedListItemNode, | ||
| SerializedListNode, | ||
| }; | ||
| /** | ||
| * @deprecated use {@link $insertList} from an update or command listener. | ||
| * | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param editor - The lexical editor. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export function insertList(editor: LexicalEditor, listType: ListType): void { | ||
| editor.update(() => $insertList(listType)); | ||
| } | ||
| /** | ||
| * @deprecated use {@link $removeList} from an update or command listener. | ||
| * | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| * @param editor - The lexical editor. | ||
| */ | ||
| export function removeList(editor: LexicalEditor): void { | ||
| editor.update(() => $removeList()); | ||
| } |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import {effect, namedSignals} from '@lexical/extension'; | ||
| import {mergeRegister} from '@lexical/utils'; | ||
| import {defineExtension, safeCast} from 'lexical'; | ||
| import {registerCheckList} from './checkList'; | ||
| import {ListItemNode} from './LexicalListItemNode'; | ||
| import {ListNode} from './LexicalListNode'; | ||
| import {registerList, registerListStrictIndentTransform} from './registerList'; | ||
| export interface ListConfig { | ||
| /** | ||
| * When `true`, enforces strict indentation rules for list items, ensuring consistent structure. | ||
| * When `false` (default), indentation is more flexible. | ||
| */ | ||
| hasStrictIndent: boolean; | ||
| shouldPreserveNumbering: boolean; | ||
| } | ||
| /** | ||
| * Configures {@link ListNode}, {@link ListItemNode} and registers | ||
| * the strict indent transform if `hasStrictIndent` is true (default false). | ||
| */ | ||
| export const ListExtension = defineExtension({ | ||
| build(editor, config, state) { | ||
| return namedSignals(config); | ||
| }, | ||
| config: safeCast<ListConfig>({ | ||
| hasStrictIndent: false, | ||
| shouldPreserveNumbering: false, | ||
| }), | ||
| name: '@lexical/list/List', | ||
| nodes: () => [ListNode, ListItemNode], | ||
| register(editor, config, state) { | ||
| const stores = state.getOutput(); | ||
| return mergeRegister( | ||
| effect(() => { | ||
| return registerList(editor, { | ||
| restoreNumbering: stores.shouldPreserveNumbering.value, | ||
| }); | ||
| }), | ||
| effect(() => | ||
| stores.hasStrictIndent.value | ||
| ? registerListStrictIndentTransform(editor) | ||
| : undefined, | ||
| ), | ||
| ); | ||
| }, | ||
| }); | ||
| export interface CheckListConfig { | ||
| disableTakeFocusOnClick: boolean; | ||
| } | ||
| /** | ||
| * Registers checklist functionality for {@link ListNode} and | ||
| * {@link ListItemNode} with a `INSERT_CHECK_LIST_COMMAND` listener and | ||
| * the expected keyboard and mouse interactions for checkboxes. | ||
| */ | ||
| export const CheckListExtension = defineExtension({ | ||
| build: (editor, config) => namedSignals(config), | ||
| config: safeCast<CheckListConfig>({ | ||
| disableTakeFocusOnClick: false, | ||
| }), | ||
| dependencies: [ListExtension], | ||
| name: '@lexical/list/CheckList', | ||
| register: (editor, config, state) => | ||
| registerCheckList(editor, state.getOutput()), | ||
| }); |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {ListNode, ListType} from './'; | ||
| import type { | ||
| BaseSelection, | ||
| DOMConversionOutput, | ||
| DOMExportOutput, | ||
| EditorConfig, | ||
| EditorThemeClasses, | ||
| LexicalNode, | ||
| LexicalUpdateJSON, | ||
| NodeKey, | ||
| ParagraphNode, | ||
| RangeSelection, | ||
| SerializedElementNode, | ||
| Spread, | ||
| } from 'lexical'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import { | ||
| $insertNodeToNearestRootAtCaret, | ||
| addClassNamesToElement, | ||
| removeClassNamesFromElement, | ||
| } from '@lexical/utils'; | ||
| import { | ||
| $applyNodeReplacement, | ||
| $copyNode, | ||
| $createParagraphNode, | ||
| $getSelection, | ||
| $getSiblingCaret, | ||
| $isElementNode, | ||
| $isParagraphNode, | ||
| $isRangeSelection, | ||
| $isRootOrShadowRoot, | ||
| $rewindSiblingCaret, | ||
| $setDirectionFromDOM, | ||
| $setFormatFromDOM, | ||
| buildImportMap, | ||
| ElementNode, | ||
| getStyleObjectFromCSS, | ||
| isHTMLElement, | ||
| LexicalEditor, | ||
| normalizeClassNames, | ||
| setDOMStyleFromCSS, | ||
| } from 'lexical'; | ||
| import {$createListNode, $isListNode} from './'; | ||
| import {$handleIndent, $handleOutdent, mergeLists} from './formatList'; | ||
| import {isNestedListNode} from './utils'; | ||
| export type SerializedListItemNode = Spread< | ||
| { | ||
| checked: boolean | undefined; | ||
| value: number; | ||
| }, | ||
| SerializedElementNode | ||
| >; | ||
| function applyMarkerStyles( | ||
| dom: HTMLElement, | ||
| node: ListItemNode, | ||
| prevNode: ListItemNode | null, | ||
| ): void { | ||
| const nextTextStyle = node.__textStyle; | ||
| const prevTextStyle = prevNode ? prevNode.__textStyle : ''; | ||
| if (prevNode !== null && prevTextStyle === nextTextStyle) { | ||
| return; | ||
| } | ||
| const styles: Record<string, string> = getStyleObjectFromCSS(nextTextStyle); | ||
| for (const k in styles) { | ||
| dom.style.setProperty(`--listitem-marker-${k}`, styles[k]); | ||
| } | ||
| if (prevTextStyle !== '') { | ||
| for (const k in getStyleObjectFromCSS(prevTextStyle)) { | ||
| if (!(k in styles)) { | ||
| dom.style.removeProperty(`--listitem-marker-${k}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** @noInheritDoc */ | ||
| export class ListItemNode extends ElementNode { | ||
| /** @internal */ | ||
| __value: number; | ||
| /** @internal */ | ||
| __checked?: boolean; | ||
| /** @internal */ | ||
| $config() { | ||
| return this.config('listitem', { | ||
| $transform: (node: ListItemNode): void => { | ||
| const parent = node.getParent(); | ||
| if ($isListNode(parent)) { | ||
| if (parent.getListType() !== 'check' && node.getChecked() != null) { | ||
| node.setChecked(undefined); | ||
| } | ||
| } else if (parent) { | ||
| const newParent = node.createParentElementNode(); | ||
| invariant( | ||
| $isListNode(newParent), | ||
| 'ListItemNode.createParentElementNode() must return a ListNode', | ||
| ); | ||
| // Insert an empty ListNode at the orphan's position, splitting | ||
| // any enclosing non-shadow-root blocks so the ListNode lifts to | ||
| // a valid container before we move the orphan in. The ListNode | ||
| // $transform merges adjacent same-type lists, so neighbouring | ||
| // orphans will coalesce once their own transforms run. | ||
| const children = [node]; | ||
| for (const dir of ['previous', 'next'] as const) { | ||
| children.reverse(); | ||
| for (const {origin} of $getSiblingCaret(node, dir)) { | ||
| if (!$isListItemNode(origin)) { | ||
| break; | ||
| } | ||
| children.push(origin); | ||
| } | ||
| } | ||
| node.insertBefore(newParent); | ||
| newParent.splice(0, 0, children); | ||
| if (!$isRootOrShadowRoot(parent)) { | ||
| $insertNodeToNearestRootAtCaret( | ||
| newParent, | ||
| $rewindSiblingCaret($getSiblingCaret(newParent, 'next')), | ||
| {$shouldSplit: () => false, removeEmptyDestination: true}, | ||
| ); | ||
| if (parent.isEmpty() && parent.isAttached()) { | ||
| parent.remove(); | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| extends: ElementNode, | ||
| importDOM: buildImportMap({ | ||
| li: () => ({ | ||
| conversion: $convertListItemElement, | ||
| priority: 0, | ||
| }), | ||
| }), | ||
| }); | ||
| } | ||
| constructor( | ||
| value: number = 1, | ||
| checked: undefined | boolean = undefined, | ||
| key?: NodeKey, | ||
| ) { | ||
| super(key); | ||
| this.__value = value === undefined ? 1 : value; | ||
| this.__checked = checked; | ||
| } | ||
| afterCloneFrom(prevNode: this): void { | ||
| super.afterCloneFrom(prevNode); | ||
| this.__value = prevNode.__value; | ||
| this.__checked = prevNode.__checked; | ||
| } | ||
| createDOM(config: EditorConfig): HTMLElement { | ||
| const element = document.createElement('li'); | ||
| this.updateListItemDOM(null, element, config); | ||
| return element; | ||
| } | ||
| updateListItemDOM( | ||
| prevNode: ListItemNode | null, | ||
| dom: HTMLLIElement, | ||
| config: EditorConfig, | ||
| ) { | ||
| updateListItemChecked(dom, this, prevNode); | ||
| dom.value = this.__value; | ||
| $setListItemThemeClassNames(dom, config.theme, this); | ||
| const prevStyle = prevNode ? prevNode.__style : ''; | ||
| const nextStyle = this.__style; | ||
| if (prevStyle !== nextStyle) { | ||
| setDOMStyleFromCSS(dom.style, nextStyle, prevStyle); | ||
| } | ||
| applyMarkerStyles(dom, this, prevNode); | ||
| } | ||
| updateDOM( | ||
| prevNode: ListItemNode, | ||
| dom: HTMLElement, | ||
| config: EditorConfig, | ||
| ): boolean { | ||
| // @ts-expect-error - this is always HTMLListItemElement | ||
| const element: HTMLLIElement = dom; | ||
| this.updateListItemDOM(prevNode, element, config); | ||
| return false; | ||
| } | ||
| updateFromJSON( | ||
| serializedNode: LexicalUpdateJSON<SerializedListItemNode>, | ||
| ): this { | ||
| return super | ||
| .updateFromJSON(serializedNode) | ||
| .setValue(serializedNode.value) | ||
| .setChecked(serializedNode.checked); | ||
| } | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput { | ||
| const element = this.createDOM(editor._config); | ||
| const formatType = this.getFormatType(); | ||
| if (formatType) { | ||
| element.style.textAlign = formatType; | ||
| } | ||
| const direction = this.getDirection(); | ||
| if (direction) { | ||
| element.dir = direction; | ||
| } | ||
| if (isNestedListNode(this)) { | ||
| return { | ||
| after(containerElement) { | ||
| if (isHTMLElement(containerElement)) { | ||
| const prevSibling = containerElement.previousElementSibling; | ||
| if (isHTMLElement(prevSibling) && prevSibling.nodeName === 'LI') { | ||
| while (containerElement.firstChild) { | ||
| prevSibling.append(containerElement.firstChild); | ||
| } | ||
| containerElement.remove(); | ||
| } | ||
| } | ||
| return containerElement; | ||
| }, | ||
| element, | ||
| }; | ||
| } | ||
| return { | ||
| element, | ||
| }; | ||
| } | ||
| exportJSON(): SerializedListItemNode { | ||
| return { | ||
| ...super.exportJSON(), | ||
| checked: this.getChecked(), | ||
| value: this.getValue(), | ||
| }; | ||
| } | ||
| append(...nodes: LexicalNode[]): this { | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ($isElementNode(node) && this.canMergeWith(node)) { | ||
| const children = node.getChildren(); | ||
| this.append(...children); | ||
| node.remove(); | ||
| } else { | ||
| super.append(node); | ||
| } | ||
| } | ||
| return this; | ||
| } | ||
| replace<N extends LexicalNode>( | ||
| replaceWithNode: N, | ||
| includeChildren?: boolean, | ||
| ): N { | ||
| if ($isListItemNode(replaceWithNode)) { | ||
| return super.replace(replaceWithNode); | ||
| } | ||
| this.setIndent(0); | ||
| const list = this.getParentOrThrow(); | ||
| if (!$isListNode(list)) { | ||
| return replaceWithNode; | ||
| } | ||
| if (list.__first === this.getKey()) { | ||
| list.insertBefore(replaceWithNode); | ||
| } else if (list.__last === this.getKey()) { | ||
| list.insertAfter(replaceWithNode); | ||
| } else { | ||
| // Split the list | ||
| const newList = $copyNode(list); | ||
| let nextSibling = this.getNextSibling(); | ||
| while (nextSibling) { | ||
| const nodeToAppend = nextSibling; | ||
| nextSibling = nextSibling.getNextSibling(); | ||
| newList.append(nodeToAppend); | ||
| } | ||
| list.insertAfter(replaceWithNode); | ||
| replaceWithNode.insertAfter(newList); | ||
| } | ||
| const toReplaceKey = this.__key; | ||
| let prevSizeBeforeChildrenTransfer = 0; | ||
| if (includeChildren) { | ||
| invariant( | ||
| $isElementNode(replaceWithNode), | ||
| 'includeChildren should only be true for ElementNodes', | ||
| ); | ||
| prevSizeBeforeChildrenTransfer = replaceWithNode.getChildrenSize(); | ||
| replaceWithNode.splice( | ||
| prevSizeBeforeChildrenTransfer, | ||
| 0, | ||
| this.getChildren(), | ||
| ); | ||
| } | ||
| // The base LexicalNode.replace remaps element-anchored selection points | ||
| // from the replaced node to the replacement, but this override skips | ||
| // super and the trailing this.remove() would otherwise drop selection | ||
| // onto a sibling list item via moveSelectionPointToSibling. Mirror the | ||
| // base behavior here for the element-anchored case. | ||
| if (includeChildren && $isElementNode(replaceWithNode)) { | ||
| const selection = $getSelection(); | ||
| if ($isRangeSelection(selection)) { | ||
| for (const point of selection.getStartEndPoints()) { | ||
| if (point.key === toReplaceKey && point.type === 'element') { | ||
| point.set( | ||
| replaceWithNode.getKey(), | ||
| prevSizeBeforeChildrenTransfer + point.offset, | ||
| 'element', | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| this.remove(); | ||
| if (list.getChildrenSize() === 0) { | ||
| list.remove(); | ||
| } | ||
| return replaceWithNode; | ||
| } | ||
| insertAfter(node: LexicalNode, restoreSelection = true): LexicalNode { | ||
| const listNode = this.getParentOrThrow(); | ||
| if (!$isListNode(listNode)) { | ||
| invariant( | ||
| false, | ||
| 'insertAfter: list node is not parent of list item node', | ||
| ); | ||
| } | ||
| if ($isListItemNode(node)) { | ||
| return super.insertAfter(node, restoreSelection); | ||
| } | ||
| const siblings = this.getNextSiblings(); | ||
| // Split the lists and insert the node in between them | ||
| listNode.insertAfter(node, restoreSelection); | ||
| if (siblings.length !== 0) { | ||
| const newListNode = $copyNode(listNode); | ||
| siblings.forEach(sibling => newListNode.append(sibling)); | ||
| node.insertAfter(newListNode, restoreSelection); | ||
| } | ||
| return node; | ||
| } | ||
| remove(preserveEmptyParent?: boolean): void { | ||
| const prevSibling = this.getPreviousSibling(); | ||
| const nextSibling = this.getNextSibling(); | ||
| super.remove(preserveEmptyParent); | ||
| if ( | ||
| prevSibling && | ||
| nextSibling && | ||
| isNestedListNode(prevSibling) && | ||
| isNestedListNode(nextSibling) | ||
| ) { | ||
| mergeLists(prevSibling.getFirstChild(), nextSibling.getFirstChild()); | ||
| nextSibling.remove(); | ||
| } | ||
| } | ||
| resetOnCopyNodeFrom(original: this): void { | ||
| super.resetOnCopyNodeFrom(original); | ||
| if (original.getChecked()) { | ||
| this.setChecked(false); | ||
| } | ||
| } | ||
| insertNewAfter( | ||
| _: RangeSelection, | ||
| restoreSelection = true, | ||
| ): ListItemNode | ParagraphNode { | ||
| const newElement = $copyNode(this); | ||
| this.insertAfter(newElement, restoreSelection); | ||
| return newElement; | ||
| } | ||
| collapseAtStart(selection: RangeSelection): true { | ||
| const paragraph = $createParagraphNode(); | ||
| const children = this.getChildren(); | ||
| children.forEach(child => paragraph.append(child)); | ||
| const listNode = this.getParentOrThrow(); | ||
| const listNodeParent = listNode.getParentOrThrow(); | ||
| const isIndented = $isListItemNode(listNodeParent); | ||
| if (listNode.getChildrenSize() === 1) { | ||
| if (isIndented) { | ||
| // if the list node is nested, we just want to remove it, | ||
| // effectively unindenting it. | ||
| listNode.remove(); | ||
| listNodeParent.select(); | ||
| } else { | ||
| listNode.insertBefore(paragraph); | ||
| listNode.remove(); | ||
| // If we have selection on the list item, we'll need to move it | ||
| // to the paragraph | ||
| const anchor = selection.anchor; | ||
| const focus = selection.focus; | ||
| const key = paragraph.getKey(); | ||
| if (anchor.type === 'element' && anchor.getNode().is(this)) { | ||
| anchor.set(key, anchor.offset, 'element'); | ||
| } | ||
| if (focus.type === 'element' && focus.getNode().is(this)) { | ||
| focus.set(key, focus.offset, 'element'); | ||
| } | ||
| } | ||
| } else { | ||
| listNode.insertBefore(paragraph); | ||
| this.remove(); | ||
| } | ||
| return true; | ||
| } | ||
| getValue(): number { | ||
| const self = this.getLatest(); | ||
| return self.__value; | ||
| } | ||
| setValue(value: number): this { | ||
| const self = this.getWritable(); | ||
| self.__value = value; | ||
| return self; | ||
| } | ||
| getChecked(): boolean | undefined { | ||
| const self = this.getLatest(); | ||
| let listType: ListType | undefined; | ||
| const parent = this.getParent(); | ||
| if ($isListNode(parent)) { | ||
| listType = parent.getListType(); | ||
| } | ||
| return listType === 'check' ? Boolean(self.__checked) : undefined; | ||
| } | ||
| setChecked(checked?: boolean): this { | ||
| const self = this.getWritable(); | ||
| self.__checked = checked; | ||
| return self; | ||
| } | ||
| toggleChecked(): this { | ||
| const self = this.getWritable(); | ||
| return self.setChecked(!self.__checked); | ||
| } | ||
| getIndent(): number { | ||
| // If we don't have a parent, we are likely serializing | ||
| const parent = this.getParent(); | ||
| if (parent === null || !this.isAttached()) { | ||
| return this.getLatest().__indent; | ||
| } | ||
| // ListItemNode should always have a ListNode for a parent. | ||
| let listNodeParent = parent.getParentOrThrow(); | ||
| let indentLevel = 0; | ||
| while ($isListItemNode(listNodeParent)) { | ||
| listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow(); | ||
| indentLevel++; | ||
| } | ||
| return indentLevel; | ||
| } | ||
| setIndent(indent: number): this { | ||
| invariant(typeof indent === 'number', 'Invalid indent value.'); | ||
| indent = Math.floor(indent); | ||
| invariant(indent >= 0, 'Indent value must be non-negative.'); | ||
| let currentIndent = this.getIndent(); | ||
| while (currentIndent !== indent) { | ||
| if (currentIndent < indent) { | ||
| $handleIndent(this); | ||
| currentIndent++; | ||
| } else { | ||
| $handleOutdent(this); | ||
| currentIndent--; | ||
| } | ||
| } | ||
| return this; | ||
| } | ||
| /** @deprecated @internal */ | ||
| canInsertAfter(node: LexicalNode): boolean { | ||
| return $isListItemNode(node); | ||
| } | ||
| /** @deprecated @internal */ | ||
| canReplaceWith(replacement: LexicalNode): boolean { | ||
| return $isListItemNode(replacement); | ||
| } | ||
| canMergeWith(node: LexicalNode): boolean { | ||
| return $isListItemNode(node) || $isParagraphNode(node); | ||
| } | ||
| extractWithChild(child: LexicalNode, selection: BaseSelection): boolean { | ||
| if (!$isRangeSelection(selection)) { | ||
| return false; | ||
| } | ||
| const anchorNode = selection.anchor.getNode(); | ||
| const focusNode = selection.focus.getNode(); | ||
| return ( | ||
| this.isParentOf(anchorNode) && | ||
| this.isParentOf(focusNode) && | ||
| this.getTextContent().length === selection.getTextContent().length | ||
| ); | ||
| } | ||
| isParentRequired(): true { | ||
| return true; | ||
| } | ||
| createParentElementNode(): ListNode { | ||
| return $createListNode('bullet'); | ||
| } | ||
| canMergeWhenEmpty(): true { | ||
| return true; | ||
| } | ||
| } | ||
| function $setListItemThemeClassNames( | ||
| dom: HTMLElement, | ||
| editorThemeClasses: EditorThemeClasses, | ||
| node: ListItemNode, | ||
| ): void { | ||
| const listTheme = editorThemeClasses.list; | ||
| if (!listTheme) { | ||
| return; | ||
| } | ||
| const listItemClassName = listTheme.listitem; | ||
| const nestedListItemClassName = listTheme.nested && listTheme.nested.listitem; | ||
| const parentNode = node.getParent(); | ||
| const isCheckList = | ||
| $isListNode(parentNode) && parentNode.getListType() === 'check'; | ||
| const checked = node.getChecked(); | ||
| const isNested = node.getChildren().some(child => $isListNode(child)); | ||
| // Always remove the variable theme classes first so that the className | ||
| // string stays in a canonical order regardless of how the dom got here | ||
| // (fresh create vs. cross-parent reuse). classList.remove on a missing | ||
| // class is a no-op, so this is safe even on a freshly-created element. | ||
| const classesToRemove: string[] = []; | ||
| if (listTheme.listitemChecked !== undefined) { | ||
| classesToRemove.push(listTheme.listitemChecked); | ||
| } | ||
| if (listTheme.listitemUnchecked !== undefined) { | ||
| classesToRemove.push(listTheme.listitemUnchecked); | ||
| } | ||
| if (nestedListItemClassName !== undefined) { | ||
| classesToRemove.push(...normalizeClassNames(nestedListItemClassName)); | ||
| } | ||
| if (classesToRemove.length > 0) { | ||
| removeClassNamesFromElement(dom, ...classesToRemove); | ||
| } | ||
| const classesToAdd: string[] = []; | ||
| if (listItemClassName !== undefined) { | ||
| classesToAdd.push(...normalizeClassNames(listItemClassName)); | ||
| } | ||
| if (isCheckList) { | ||
| const checkClassName = checked | ||
| ? listTheme.listitemChecked | ||
| : listTheme.listitemUnchecked; | ||
| if (checkClassName !== undefined) { | ||
| classesToAdd.push(checkClassName); | ||
| } | ||
| } | ||
| if (nestedListItemClassName !== undefined && isNested) { | ||
| classesToAdd.push(...normalizeClassNames(nestedListItemClassName)); | ||
| } | ||
| if (classesToAdd.length > 0) { | ||
| addClassNamesToElement(dom, ...classesToAdd); | ||
| } | ||
| } | ||
| function updateListItemChecked( | ||
| dom: HTMLElement, | ||
| listItemNode: ListItemNode, | ||
| prevListItemNode: ListItemNode | null, | ||
| ): void { | ||
| const parent = listItemNode.getParent(); | ||
| const isCheckbox = | ||
| $isListNode(parent) && | ||
| parent.getListType() === 'check' && | ||
| // Only add attributes for leaf list items | ||
| !$isListNode(listItemNode.getFirstChild()); | ||
| if (!isCheckbox) { | ||
| dom.removeAttribute('role'); | ||
| dom.removeAttribute('tabIndex'); | ||
| dom.removeAttribute('aria-checked'); | ||
| } else { | ||
| dom.setAttribute('role', 'checkbox'); | ||
| dom.setAttribute('tabIndex', '-1'); | ||
| dom.setAttribute( | ||
| 'aria-checked', | ||
| listItemNode.getChecked() ? 'true' : 'false', | ||
| ); | ||
| } | ||
| } | ||
| function $convertListItemElement(domNode: HTMLElement): DOMConversionOutput { | ||
| const isGitHubCheckList = domNode.classList.contains('task-list-item'); | ||
| if (isGitHubCheckList) { | ||
| for (const child of domNode.children) { | ||
| if (child.tagName === 'INPUT') { | ||
| return $convertCheckboxInput(child); | ||
| } | ||
| } | ||
| } | ||
| const isJoplinCheckList = domNode.classList.contains('joplin-checkbox'); | ||
| if (isJoplinCheckList) { | ||
| for (const child of domNode.children) { | ||
| if ( | ||
| child.classList.contains('checkbox-wrapper') && | ||
| child.children.length > 0 && | ||
| child.children[0].tagName === 'INPUT' | ||
| ) { | ||
| return $convertCheckboxInput(child.children[0]); | ||
| } | ||
| } | ||
| } | ||
| const ariaCheckedAttr = domNode.getAttribute('aria-checked'); | ||
| const checked = | ||
| ariaCheckedAttr === 'true' | ||
| ? true | ||
| : ariaCheckedAttr === 'false' | ||
| ? false | ||
| : undefined; | ||
| const node = $createListItemNode(checked); | ||
| $setFormatFromDOM(node, domNode); | ||
| return { | ||
| after: setFormatFromChildren.bind(null, node), | ||
| node: $setDirectionFromDOM(node, domNode), | ||
| }; | ||
| } | ||
| function $convertCheckboxInput(domNode: Element): DOMConversionOutput { | ||
| const isCheckboxInput = domNode.getAttribute('type') === 'checkbox'; | ||
| if (!isCheckboxInput) { | ||
| return {node: null}; | ||
| } | ||
| const checked = domNode.hasAttribute('checked'); | ||
| const node = $createListItemNode(checked); | ||
| return {after: setFormatFromChildren.bind(null, node), node}; | ||
| } | ||
| function setFormatFromChildren( | ||
| listItemNode: ListItemNode, | ||
| children: LexicalNode[], | ||
| ): LexicalNode[] { | ||
| const firstChild = children[0]; | ||
| // google doc sets the alignment of the <p> tag inside the <li> | ||
| if ( | ||
| children.length === 1 && | ||
| $isParagraphNode(firstChild) && | ||
| !listItemNode.getFormatType() && | ||
| firstChild.getFormatType() | ||
| ) { | ||
| listItemNode.setFormat(firstChild.getFormatType()); | ||
| return firstChild.getChildren(); | ||
| } | ||
| return children; | ||
| } | ||
| /** | ||
| * Creates a new List Item node, passing true/false will convert it to a checkbox input. | ||
| * @param checked - Is the List Item a checkbox and, if so, is it checked? undefined/null: not a checkbox, true/false is a checkbox and checked/unchecked, respectively. | ||
| * @returns The new List Item. | ||
| */ | ||
| export function $createListItemNode(checked?: boolean): ListItemNode { | ||
| return $applyNodeReplacement(new ListItemNode(undefined, checked)); | ||
| } | ||
| /** | ||
| * Checks to see if the node is a ListItemNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode, false otherwise. | ||
| */ | ||
| export function $isListItemNode( | ||
| node: LexicalNode | null | undefined, | ||
| ): node is ListItemNode { | ||
| return node instanceof ListItemNode; | ||
| } |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import { | ||
| addClassNamesToElement, | ||
| isHTMLElement, | ||
| removeClassNamesFromElement, | ||
| } from '@lexical/utils'; | ||
| import { | ||
| $applyNodeReplacement, | ||
| $createTextNode, | ||
| $isElementNode, | ||
| $setDirectionFromDOM, | ||
| buildImportMap, | ||
| DOMConversionOutput, | ||
| DOMExportOutput, | ||
| EditorConfig, | ||
| EditorThemeClasses, | ||
| ElementNode, | ||
| LexicalEditor, | ||
| LexicalNode, | ||
| LexicalUpdateJSON, | ||
| NodeKey, | ||
| normalizeClassNames, | ||
| SerializedElementNode, | ||
| Spread, | ||
| } from 'lexical'; | ||
| import {$createListItemNode, $isListItemNode, ListItemNode} from '.'; | ||
| import { | ||
| mergeNextSiblingListIfSameType, | ||
| updateChildrenListItemValue, | ||
| } from './formatList'; | ||
| import {$getListDepth} from './utils'; | ||
| export type SerializedListNode = Spread< | ||
| { | ||
| listType: ListType; | ||
| start: number; | ||
| tag: ListNodeTagType; | ||
| }, | ||
| SerializedElementNode | ||
| >; | ||
| export type ListType = 'number' | 'bullet' | 'check'; | ||
| export type ListNodeTagType = 'ul' | 'ol'; | ||
| /** @noInheritDoc */ | ||
| export class ListNode extends ElementNode { | ||
| /** @internal */ | ||
| __tag: ListNodeTagType; | ||
| /** @internal */ | ||
| __start: number; | ||
| /** @internal */ | ||
| __listType: ListType; | ||
| /** @internal */ | ||
| $config() { | ||
| return this.config('list', { | ||
| $transform: (node: ListNode): void => { | ||
| mergeNextSiblingListIfSameType(node); | ||
| updateChildrenListItemValue(node); | ||
| }, | ||
| extends: ElementNode, | ||
| importDOM: buildImportMap({ | ||
| ol: () => ({ | ||
| conversion: $convertListNode, | ||
| priority: 0, | ||
| }), | ||
| ul: () => ({ | ||
| conversion: $convertListNode, | ||
| priority: 0, | ||
| }), | ||
| }), | ||
| }); | ||
| } | ||
| constructor(listType: ListType = 'number', start: number = 1, key?: NodeKey) { | ||
| super(key); | ||
| const _listType = TAG_TO_LIST_TYPE[listType] || listType; | ||
| this.__listType = _listType; | ||
| this.__tag = _listType === 'number' ? 'ol' : 'ul'; | ||
| this.__start = start; | ||
| } | ||
| afterCloneFrom(prevNode: this): void { | ||
| super.afterCloneFrom(prevNode); | ||
| this.__listType = prevNode.__listType; | ||
| this.__tag = prevNode.__tag; | ||
| this.__start = prevNode.__start; | ||
| } | ||
| getTag(): ListNodeTagType { | ||
| return this.getLatest().__tag; | ||
| } | ||
| setListType(type: ListType): this { | ||
| const writable = this.getWritable(); | ||
| writable.__listType = type; | ||
| writable.__tag = type === 'number' ? 'ol' : 'ul'; | ||
| return writable; | ||
| } | ||
| getListType(): ListType { | ||
| return this.getLatest().__listType; | ||
| } | ||
| getStart(): number { | ||
| return this.getLatest().__start; | ||
| } | ||
| setStart(start: number): this { | ||
| const self = this.getWritable(); | ||
| self.__start = start; | ||
| return self; | ||
| } | ||
| // View | ||
| createDOM(config: EditorConfig, _editor?: LexicalEditor): HTMLElement { | ||
| const tag = this.__tag; | ||
| const dom = document.createElement(tag); | ||
| if (this.__start !== 1) { | ||
| dom.setAttribute('start', String(this.__start)); | ||
| } | ||
| // @ts-expect-error Internal field. | ||
| dom.__lexicalListType = this.__listType; | ||
| $setListThemeClassNames(dom, config.theme, this); | ||
| return dom; | ||
| } | ||
| updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean { | ||
| if ( | ||
| prevNode.__tag !== this.__tag || | ||
| prevNode.__listType !== this.__listType | ||
| ) { | ||
| return true; | ||
| } | ||
| $setListThemeClassNames(dom, config.theme, this); | ||
| if (prevNode.__start !== this.__start) { | ||
| dom.setAttribute('start', String(this.__start)); | ||
| } | ||
| return false; | ||
| } | ||
| updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedListNode>): this { | ||
| return super | ||
| .updateFromJSON(serializedNode) | ||
| .setListType(serializedNode.listType) | ||
| .setStart(serializedNode.start); | ||
| } | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput { | ||
| const element = this.createDOM(editor._config, editor); | ||
| if (isHTMLElement(element)) { | ||
| if (this.__start !== 1) { | ||
| element.setAttribute('start', String(this.__start)); | ||
| } | ||
| if (this.__listType === 'check') { | ||
| element.setAttribute('__lexicalListType', 'check'); | ||
| } | ||
| } | ||
| return { | ||
| element, | ||
| }; | ||
| } | ||
| exportJSON(): SerializedListNode { | ||
| return { | ||
| ...super.exportJSON(), | ||
| listType: this.getListType(), | ||
| start: this.getStart(), | ||
| tag: this.getTag(), | ||
| }; | ||
| } | ||
| canBeEmpty(): false { | ||
| return false; | ||
| } | ||
| canIndent(): false { | ||
| return false; | ||
| } | ||
| splice( | ||
| start: number, | ||
| deleteCount: number, | ||
| nodesToInsert: LexicalNode[], | ||
| ): this { | ||
| let listItemNodesToInsert = nodesToInsert; | ||
| for (let i = 0; i < nodesToInsert.length; i++) { | ||
| const node = nodesToInsert[i]; | ||
| if (!$isListItemNode(node)) { | ||
| if (listItemNodesToInsert === nodesToInsert) { | ||
| listItemNodesToInsert = [...nodesToInsert]; | ||
| } | ||
| listItemNodesToInsert[i] = this.createListItemNode().append( | ||
| $isElementNode(node) && !($isListNode(node) || node.isInline()) | ||
| ? $createTextNode(node.getTextContent()) | ||
| : node, | ||
| ); | ||
| } | ||
| } | ||
| return super.splice(start, deleteCount, listItemNodesToInsert); | ||
| } | ||
| extractWithChild(child: LexicalNode): boolean { | ||
| return $isListItemNode(child); | ||
| } | ||
| /** | ||
| * Create an appropriate ListItemNode to be a child of this ListNode, | ||
| * {@link $createListItemNode} is the default implementation. | ||
| * | ||
| * @returns A new ListItemNode. | ||
| */ | ||
| createListItemNode(): ListItemNode { | ||
| return $createListItemNode(); | ||
| } | ||
| } | ||
| function $setListThemeClassNames( | ||
| dom: HTMLElement, | ||
| editorThemeClasses: EditorThemeClasses, | ||
| node: ListNode, | ||
| ): void { | ||
| const classesToAdd = []; | ||
| const classesToRemove = []; | ||
| const listTheme = editorThemeClasses.list; | ||
| if (listTheme !== undefined) { | ||
| const listLevelsClassNames = listTheme[`${node.__tag}Depth`] || []; | ||
| const listDepth = $getListDepth(node) - 1; | ||
| const normalizedListDepth = listDepth % listLevelsClassNames.length; | ||
| const listLevelClassName = listLevelsClassNames[normalizedListDepth]; | ||
| const listClassName = listTheme[node.__tag]; | ||
| let nestedListClassName; | ||
| const nestedListTheme = listTheme.nested; | ||
| const checklistClassName = listTheme.checklist; | ||
| if (nestedListTheme !== undefined && nestedListTheme.list) { | ||
| nestedListClassName = nestedListTheme.list; | ||
| } | ||
| if (listClassName !== undefined) { | ||
| classesToAdd.push(listClassName); | ||
| } | ||
| if (checklistClassName !== undefined && node.__listType === 'check') { | ||
| classesToAdd.push(checklistClassName); | ||
| } | ||
| if (listLevelClassName !== undefined) { | ||
| classesToAdd.push(...normalizeClassNames(listLevelClassName)); | ||
| for (let i = 0; i < listLevelsClassNames.length; i++) { | ||
| if (i !== normalizedListDepth) { | ||
| classesToRemove.push(node.__tag + i); | ||
| } | ||
| } | ||
| } | ||
| if (nestedListClassName !== undefined) { | ||
| const nestedListItemClasses = normalizeClassNames(nestedListClassName); | ||
| if (listDepth > 1) { | ||
| classesToAdd.push(...nestedListItemClasses); | ||
| } else { | ||
| classesToRemove.push(...nestedListItemClasses); | ||
| } | ||
| } | ||
| } | ||
| if (classesToRemove.length > 0) { | ||
| removeClassNamesFromElement(dom, ...classesToRemove); | ||
| } | ||
| if (classesToAdd.length > 0) { | ||
| addClassNamesToElement(dom, ...classesToAdd); | ||
| } | ||
| } | ||
| /* | ||
| * This function normalizes the children of a ListNode after the conversion from HTML, | ||
| * ensuring that they are all ListItemNodes and contain either a single nested ListNode | ||
| * or some other inline content. | ||
| */ | ||
| function $normalizeChildren( | ||
| nodes: LexicalNode[], | ||
| listNode: ListNode, | ||
| ): ListItemNode[] { | ||
| const $createWrapperItem = listNode.createListItemNode.bind(listNode); | ||
| const normalizedListItems: ListItemNode[] = []; | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ($isListItemNode(node)) { | ||
| normalizedListItems.push(node); | ||
| const children = node.getChildren(); | ||
| if (children.length > 1) { | ||
| children.forEach(child => { | ||
| if ($isListNode(child)) { | ||
| normalizedListItems.push($createWrapperItem().append(child)); | ||
| } | ||
| }); | ||
| } | ||
| } else { | ||
| normalizedListItems.push($createWrapperItem().append(node)); | ||
| } | ||
| } | ||
| return normalizedListItems; | ||
| } | ||
| function isDomChecklist(domNode: HTMLElement) { | ||
| if ( | ||
| domNode.getAttribute('__lexicallisttype') === 'check' || | ||
| // is github checklist | ||
| domNode.classList.contains('contains-task-list') || | ||
| // is joplin checklist | ||
| domNode.getAttribute('data-is-checklist') === '1' | ||
| ) { | ||
| return true; | ||
| } | ||
| // if children are checklist items, the node is a checklist ul. Applicable for googledoc checklist pasting. | ||
| for (const child of domNode.childNodes) { | ||
| if (isHTMLElement(child) && child.hasAttribute('aria-checked')) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function isHTMLOListElement(node: unknown): node is HTMLOListElement { | ||
| return isHTMLElement(node) && node.nodeName.toLowerCase() === 'ol'; | ||
| } | ||
| function $convertListNode( | ||
| domNode: HTMLOListElement | HTMLUListElement, | ||
| ): DOMConversionOutput { | ||
| let node: ListNode; | ||
| if (isHTMLOListElement(domNode)) { | ||
| const start = domNode.start; | ||
| node = $createListNode('number', start); | ||
| } else if (isDomChecklist(domNode)) { | ||
| node = $createListNode('check'); | ||
| } else { | ||
| node = $createListNode('bullet'); | ||
| } | ||
| $setDirectionFromDOM(node, domNode); | ||
| return { | ||
| after: children => $normalizeChildren(children, node), | ||
| node, | ||
| }; | ||
| } | ||
| const TAG_TO_LIST_TYPE: Record<string, ListType> = { | ||
| ol: 'number', | ||
| ul: 'bullet', | ||
| }; | ||
| /** | ||
| * Creates a ListNode of listType. | ||
| * @param listType - The type of list to be created. Can be 'number', 'bullet', or 'check'. | ||
| * @param start - Where an ordered list starts its count, start = 1 if left undefined. | ||
| * @returns The new ListNode | ||
| */ | ||
| export function $createListNode( | ||
| listType: ListType = 'number', | ||
| start = 1, | ||
| ): ListNode { | ||
| return $applyNodeReplacement(new ListNode(listType, start)); | ||
| } | ||
| /** | ||
| * Checks to see if the node is a ListNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListNode, false otherwise. | ||
| */ | ||
| export function $isListNode( | ||
| node: LexicalNode | null | undefined, | ||
| ): node is ListNode { | ||
| return node instanceof ListNode; | ||
| } |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {ChildSchema, DOMImportContext} from '@lexical/html'; | ||
| import { | ||
| CoreImportExtension, | ||
| defineImportRule, | ||
| DOMImportExtension, | ||
| isElementOfTag, | ||
| sel, | ||
| } from '@lexical/html'; | ||
| import { | ||
| $isParagraphNode, | ||
| $setDirectionFromDOM, | ||
| $setFormatFromDOM, | ||
| configExtension, | ||
| defineExtension, | ||
| type LexicalNode, | ||
| } from 'lexical'; | ||
| import {ListExtension} from './LexicalListExtension'; | ||
| import { | ||
| $createListItemNode, | ||
| $isListItemNode, | ||
| type ListItemNode, | ||
| } from './LexicalListItemNode'; | ||
| import {$createListNode, $isListNode} from './LexicalListNode'; | ||
| /** | ||
| * Mirrors the legacy `isDomChecklist` heuristic from | ||
| * `@lexical/list`. | ||
| */ | ||
| function isDomChecklist(domNode: HTMLElement): boolean { | ||
| return ( | ||
| domNode.matches( | ||
| '[__lexicallisttype="check"], .contains-task-list, [data-is-checklist="1"]', | ||
| ) || domNode.querySelector(':scope > [aria-checked]') !== null | ||
| ); | ||
| } | ||
| /** | ||
| * Lift nested `ListNode`s out of `ListItemNode`s into sibling | ||
| * `ListItemNode`s (the legacy `$normalizeChildren` shape). Also wraps any | ||
| * non-`ListItemNode` children in a new `ListItemNode`. | ||
| */ | ||
| function $normalizeListChildren(children: LexicalNode[]): ListItemNode[] { | ||
| const out: ListItemNode[] = []; | ||
| for (const child of children) { | ||
| if ($isListItemNode(child)) { | ||
| out.push(child); | ||
| const innerChildren = child.getChildren(); | ||
| if (innerChildren.length > 1) { | ||
| for (const inner of innerChildren) { | ||
| if ($isListNode(inner)) { | ||
| out.push($createListItemNode().append(inner)); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| out.push($createListItemNode().append(child)); | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| const ListRule = defineImportRule({ | ||
| $import: (ctx, el) => { | ||
| let node; | ||
| if (isElementOfTag(el, 'ol')) { | ||
| node = $createListNode('number', el.start); | ||
| } else if (isDomChecklist(el)) { | ||
| node = $createListNode('check'); | ||
| } else { | ||
| node = $createListNode('bullet'); | ||
| } | ||
| $setDirectionFromDOM(node, el); | ||
| return [node.splice(0, 0, $normalizeListChildren(ctx.$importChildren(el)))]; | ||
| }, | ||
| match: sel.tag('ol', 'ul'), | ||
| name: '@lexical/list/list', | ||
| }); | ||
| /** | ||
| * Apply formatting from the first child paragraph of `<li>` to the list | ||
| * item itself, then unwrap that paragraph (Google Docs sets the alignment | ||
| * of the `<p>` inside the `<li>`). Mirrors the legacy | ||
| * `setFormatFromChildren`. | ||
| */ | ||
| function $liftFormatFromSingleParagraph( | ||
| listItemNode: ListItemNode, | ||
| children: LexicalNode[], | ||
| ): LexicalNode[] { | ||
| if (children.length !== 1) { | ||
| return children; | ||
| } | ||
| const firstChild = children[0]; | ||
| if ( | ||
| $isParagraphNode(firstChild) && | ||
| !listItemNode.getFormatType() && | ||
| firstChild.getFormatType() | ||
| ) { | ||
| listItemNode.setFormat(firstChild.getFormatType()); | ||
| return firstChild.getChildren(); | ||
| } | ||
| return children; | ||
| } | ||
| const ListItemRule = defineImportRule({ | ||
| $import: (ctx, el) => { | ||
| const ariaChecked = el.getAttribute('aria-checked'); | ||
| const checked = | ||
| ariaChecked === 'true' | ||
| ? true | ||
| : ariaChecked === 'false' | ||
| ? false | ||
| : undefined; | ||
| const node = $createListItemNode(checked); | ||
| $setFormatFromDOM(node, el); | ||
| $setDirectionFromDOM(node, el); | ||
| return [ | ||
| node.splice( | ||
| 0, | ||
| 0, | ||
| $liftFormatFromSingleParagraph(node, ctx.$importChildren(el)), | ||
| ), | ||
| ]; | ||
| }, | ||
| match: sel.tag('li'), | ||
| name: '@lexical/list/li', | ||
| }); | ||
| function $buildChecklistItem( | ||
| ctx: DOMImportContext, | ||
| el: HTMLElement, | ||
| checkboxOwner: Element, | ||
| ): LexicalNode[] { | ||
| const checkboxInput = isElementOfTag(checkboxOwner, 'input') | ||
| ? checkboxOwner | ||
| : checkboxOwner.querySelector<HTMLInputElement>('input[type="checkbox"]'); | ||
| if (!checkboxInput || checkboxInput.getAttribute('type') !== 'checkbox') { | ||
| return []; | ||
| } | ||
| const checked = checkboxInput.hasAttribute('checked'); | ||
| const node = $createListItemNode(checked); | ||
| $setFormatFromDOM(node, el); | ||
| $setDirectionFromDOM(node, el); | ||
| return [ | ||
| node.splice( | ||
| 0, | ||
| 0, | ||
| $liftFormatFromSingleParagraph(node, ctx.$importChildren(el)), | ||
| ), | ||
| ]; | ||
| } | ||
| const TaskListItemRule = defineImportRule({ | ||
| $import: (ctx, el, $next) => { | ||
| const input = el.querySelector(':scope > input[type="checkbox"]'); | ||
| if (!input) { | ||
| return $next(); | ||
| } | ||
| return $buildChecklistItem(ctx, el, input); | ||
| }, | ||
| match: sel.tag('li').classAll('task-list-item'), | ||
| name: '@lexical/list/li-task-list-item', | ||
| }); | ||
| const JoplinChecklistItemRule = defineImportRule({ | ||
| $import: (ctx, el, $next) => { | ||
| const wrapper = el.querySelector(':scope > .checkbox-wrapper'); | ||
| if (!wrapper) { | ||
| return $next(); | ||
| } | ||
| const input = wrapper.querySelector(':scope > input[type="checkbox"]'); | ||
| if (!input) { | ||
| return $next(); | ||
| } | ||
| return $buildChecklistItem(ctx, el, input); | ||
| }, | ||
| match: sel.tag('li').classAll('joplin-checkbox'), | ||
| name: '@lexical/list/li-joplin-checkbox', | ||
| }); | ||
| /** | ||
| * A {@link ChildSchema} that enforces ListNode invariants: only | ||
| * `ListItemNode` and (immediately-nested) `ListNode` children are | ||
| * accepted; runs of other children get wrapped in a fresh | ||
| * `ListItemNode`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ListSchema: ChildSchema = { | ||
| $accepts: child => $isListItemNode(child) || $isListNode(child), | ||
| // Inline runs inside a `<ul>`/`<ol>` (e.g. text between two `<li>`s) | ||
| // become the children of a synthetic `ListItemNode`. `ListItemNode` | ||
| // is itself a block-level container of inlines, so no intermediate | ||
| // `ParagraphNode` is needed (and the demoted-paragraph normalization | ||
| // would strip one anyway). | ||
| $packageRun: run => [$createListItemNode().splice(0, 0, run)], | ||
| name: 'ListSchema', | ||
| }; | ||
| /** | ||
| * Import rules for {@link ListNode} and {@link ListItemNode}, including | ||
| * GitHub task-list and Joplin checkbox heuristics. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ListImportRules = [ | ||
| // More specific rules (class-restricted) must precede the generic `li` | ||
| // rule so they win the dispatch race (lower array index = higher | ||
| // priority). | ||
| TaskListItemRule, | ||
| JoplinChecklistItemRule, | ||
| ListRule, | ||
| ListItemRule, | ||
| ]; | ||
| /** | ||
| * Bundles {@link ListImportRules} (plus {@link CoreImportExtension}) into | ||
| * a single dependency. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ListImportExtension = defineExtension({ | ||
| dependencies: [ | ||
| CoreImportExtension, | ||
| ListExtension, | ||
| configExtension(DOMImportExtension, {rules: ListImportRules}), | ||
| ], | ||
| name: '@lexical/list/Import', | ||
| }); |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {LexicalCommand, LexicalEditor, NodeKey} from 'lexical'; | ||
| import {$findMatchingParent, mergeRegister} from '@lexical/utils'; | ||
| import { | ||
| $getNodeByKey, | ||
| $getSelection, | ||
| $isRangeSelection, | ||
| $isTextNode, | ||
| COMMAND_PRIORITY_LOW, | ||
| createCommand, | ||
| INSERT_PARAGRAPH_COMMAND, | ||
| TextNode, | ||
| } from 'lexical'; | ||
| import { | ||
| $handleListInsertParagraph, | ||
| $insertList, | ||
| $removeList, | ||
| updateChildrenListItemValue, | ||
| } from './formatList'; | ||
| import {$isListItemNode, ListItemNode} from './LexicalListItemNode'; | ||
| import {$isListNode, ListNode} from './LexicalListNode'; | ||
| import {$getListDepth} from './utils'; | ||
| export const UPDATE_LIST_START_COMMAND: LexicalCommand<{ | ||
| listNodeKey: NodeKey; | ||
| newStart: number; | ||
| }> = createCommand('UPDATE_LIST_START_COMMAND'); | ||
| export const INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void> = | ||
| createCommand('INSERT_UNORDERED_LIST_COMMAND'); | ||
| export const INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void> = createCommand( | ||
| 'INSERT_ORDERED_LIST_COMMAND', | ||
| ); | ||
| export const REMOVE_LIST_COMMAND: LexicalCommand<void> = createCommand( | ||
| 'REMOVE_LIST_COMMAND', | ||
| ); | ||
| export interface RegisterListOptions { | ||
| restoreNumbering?: boolean; | ||
| } | ||
| export function registerList( | ||
| editor: LexicalEditor, | ||
| options?: RegisterListOptions, | ||
| ): () => void { | ||
| const removeListener = mergeRegister( | ||
| editor.registerCommand( | ||
| INSERT_ORDERED_LIST_COMMAND, | ||
| () => { | ||
| $insertList('number'); | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand( | ||
| UPDATE_LIST_START_COMMAND, | ||
| payload => { | ||
| const {listNodeKey, newStart} = payload; | ||
| const listNode = $getNodeByKey(listNodeKey); | ||
| if (!$isListNode(listNode)) { | ||
| return false; | ||
| } | ||
| if (listNode.getListType() === 'number') { | ||
| listNode.setStart(newStart); | ||
| updateChildrenListItemValue(listNode); | ||
| } | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand( | ||
| INSERT_UNORDERED_LIST_COMMAND, | ||
| () => { | ||
| $insertList('bullet'); | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand( | ||
| REMOVE_LIST_COMMAND, | ||
| () => { | ||
| $removeList(); | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerCommand( | ||
| INSERT_PARAGRAPH_COMMAND, | ||
| () => { | ||
| const shouldRestore = options && options.restoreNumbering; | ||
| return $handleListInsertParagraph(!!shouldRestore); | ||
| }, | ||
| COMMAND_PRIORITY_LOW, | ||
| ), | ||
| editor.registerNodeTransform(ListItemNode, node => { | ||
| const firstChild = node.getFirstChild(); | ||
| if (firstChild) { | ||
| if ($isTextNode(firstChild)) { | ||
| const style = firstChild.getStyle(); | ||
| const format = firstChild.getFormat(); | ||
| if (node.getTextStyle() !== style) { | ||
| node.setTextStyle(style); | ||
| } | ||
| if (node.getTextFormat() !== format) { | ||
| node.setTextFormat(format); | ||
| } | ||
| } | ||
| } else { | ||
| // If it's empty, check the selection | ||
| const selection = $getSelection(); | ||
| if ( | ||
| $isRangeSelection(selection) && | ||
| (selection.style !== node.getTextStyle() || | ||
| selection.format !== node.getTextFormat()) && | ||
| selection.isCollapsed() && | ||
| node.is(selection.anchor.getNode()) | ||
| ) { | ||
| node.setTextStyle(selection.style).setTextFormat(selection.format); | ||
| } | ||
| } | ||
| }), | ||
| editor.registerNodeTransform(TextNode, node => { | ||
| const listItemParentNode = node.getParent(); | ||
| if ( | ||
| $isListItemNode(listItemParentNode) && | ||
| node.is(listItemParentNode.getFirstChild()) | ||
| ) { | ||
| const style = node.getStyle(); | ||
| const format = node.getFormat(); | ||
| if ( | ||
| style !== listItemParentNode.getTextStyle() || | ||
| format !== listItemParentNode.getTextFormat() | ||
| ) { | ||
| listItemParentNode.setTextStyle(style).setTextFormat(format); | ||
| } | ||
| } | ||
| }), | ||
| ); | ||
| return removeListener; | ||
| } | ||
| export function registerListStrictIndentTransform( | ||
| editor: LexicalEditor, | ||
| ): () => void { | ||
| const $formatListIndentStrict = (listItemNode: ListItemNode): void => { | ||
| const listNode = listItemNode.getParent(); | ||
| if ($isListNode(listItemNode.getFirstChild()) || !$isListNode(listNode)) { | ||
| return; | ||
| } | ||
| const startingListItemNode = $findMatchingParent( | ||
| listItemNode, | ||
| node => | ||
| $isListItemNode(node) && | ||
| $isListNode(node.getParent()) && | ||
| $isListItemNode(node.getPreviousSibling()), | ||
| ); | ||
| if (startingListItemNode === null && listItemNode.getIndent() > 0) { | ||
| listItemNode.setIndent(0); | ||
| } else if ($isListItemNode(startingListItemNode)) { | ||
| const prevListItemNode = startingListItemNode.getPreviousSibling(); | ||
| if ($isListItemNode(prevListItemNode)) { | ||
| const endListItemNode = $findChildrenEndListItemNode(prevListItemNode); | ||
| const endListNode = endListItemNode.getParent(); | ||
| if ($isListNode(endListNode)) { | ||
| const prevDepth = $getListDepth(endListNode); | ||
| const depth = $getListDepth(listNode); | ||
| if (prevDepth + 1 < depth) { | ||
| listItemNode.setIndent(prevDepth); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const $processListWithStrictIndent = (listNode: ListNode): void => { | ||
| const queue: ListNode[] = [listNode]; | ||
| while (queue.length > 0) { | ||
| const node = queue.shift(); | ||
| if (!$isListNode(node)) { | ||
| continue; | ||
| } | ||
| for (const child of node.getChildren()) { | ||
| if ($isListItemNode(child)) { | ||
| $formatListIndentStrict(child); | ||
| const firstChild = child.getFirstChild(); | ||
| if ($isListNode(firstChild)) { | ||
| queue.push(firstChild); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| return editor.registerNodeTransform(ListNode, $processListWithStrictIndent); | ||
| } | ||
| function $findChildrenEndListItemNode( | ||
| listItemNode: ListItemNode, | ||
| ): ListItemNode { | ||
| let current = listItemNode; | ||
| let firstChild = current.getFirstChild(); | ||
| while ($isListNode(firstChild)) { | ||
| const lastChild = firstChild.getLastChild(); | ||
| if ($isListItemNode(lastChild)) { | ||
| current = lastChild; | ||
| firstChild = current.getFirstChild(); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| return current; | ||
| } |
+196
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type {LexicalNode, Spread} from 'lexical'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import {$findMatchingParent} from '@lexical/utils'; | ||
| import {$isListItemNode, $isListNode, ListItemNode, ListNode} from './'; | ||
| /** | ||
| * Checks the depth of listNode from the root node. | ||
| * @param listNode - The ListNode to be checked. | ||
| * @returns The depth of the ListNode. | ||
| */ | ||
| export function $getListDepth(listNode: ListNode): number { | ||
| let depth = 1; | ||
| let parent = listNode.getParent(); | ||
| while (parent != null) { | ||
| if ($isListItemNode(parent)) { | ||
| const parentList = parent.getParent(); | ||
| if ($isListNode(parentList)) { | ||
| depth++; | ||
| parent = parentList.getParent(); | ||
| continue; | ||
| } | ||
| invariant(false, 'A ListItemNode must have a ListNode for a parent.'); | ||
| } | ||
| return depth; | ||
| } | ||
| return depth; | ||
| } | ||
| /** | ||
| * Finds the nearest ancestral ListNode and returns it, throws an invariant if listItem is not a ListItemNode. | ||
| * @param listItem - The node to be checked. | ||
| * @returns The ListNode found. | ||
| */ | ||
| export function $getTopListNode(listItem: LexicalNode): ListNode { | ||
| let list = listItem.getParent<ListNode>(); | ||
| if (!$isListNode(list)) { | ||
| invariant(false, 'A ListItemNode must have a ListNode for a parent.'); | ||
| } | ||
| let parent: ListNode | null = list; | ||
| while (parent !== null) { | ||
| parent = parent.getParent(); | ||
| if ($isListNode(parent)) { | ||
| list = parent; | ||
| } | ||
| } | ||
| return list; | ||
| } | ||
| /** | ||
| * Checks if listItem has no child ListNodes and has no ListItemNode ancestors with siblings. | ||
| * @param listItem - the ListItemNode to be checked. | ||
| * @returns true if listItem has no child ListNode and no ListItemNode ancestors with siblings, false otherwise. | ||
| */ | ||
| export function $isLastItemInList(listItem: ListItemNode): boolean { | ||
| let isLast = true; | ||
| const firstChild = listItem.getFirstChild(); | ||
| if ($isListNode(firstChild)) { | ||
| return false; | ||
| } | ||
| let parent: ListItemNode | null = listItem; | ||
| while (parent !== null) { | ||
| if ($isListItemNode(parent)) { | ||
| if (parent.getNextSiblings().length > 0) { | ||
| isLast = false; | ||
| } | ||
| } | ||
| parent = parent.getParent(); | ||
| } | ||
| return isLast; | ||
| } | ||
| /** | ||
| * A recursive Depth-First Search (Postorder Traversal) that finds all of a node's children | ||
| * that are of type ListItemNode and returns them in an array. | ||
| * @param node - The ListNode to start the search. | ||
| * @returns An array containing all nodes of type ListItemNode found. | ||
| */ | ||
| // This should probably be $getAllChildrenOfType | ||
| export function $getAllListItems(node: ListNode): Array<ListItemNode> { | ||
| let listItemNodes: Array<ListItemNode> = []; | ||
| const listChildren: Array<ListItemNode> = node | ||
| .getChildren() | ||
| .filter($isListItemNode); | ||
| for (let i = 0; i < listChildren.length; i++) { | ||
| const listItemNode = listChildren[i]; | ||
| const firstChild = listItemNode.getFirstChild(); | ||
| if ($isListNode(firstChild)) { | ||
| listItemNodes = listItemNodes.concat($getAllListItems(firstChild)); | ||
| } else { | ||
| listItemNodes.push(listItemNode); | ||
| } | ||
| } | ||
| return listItemNodes; | ||
| } | ||
| const NestedListNodeBrand: unique symbol = Symbol.for( | ||
| '@lexical/NestedListNodeBrand', | ||
| ); | ||
| /** | ||
| * Checks to see if the passed node is a ListItemNode and has a ListNode as a child. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode and has a ListNode child, false otherwise. | ||
| */ | ||
| export function isNestedListNode( | ||
| node: LexicalNode | null | undefined, | ||
| ): node is Spread< | ||
| {getFirstChild(): ListNode; [NestedListNodeBrand]: never}, | ||
| ListItemNode | ||
| > { | ||
| return $isListItemNode(node) && $isListNode(node.getFirstChild()); | ||
| } | ||
| /** | ||
| * Traverses up the tree and returns the first ListItemNode found. | ||
| * @param node - Node to start the search. | ||
| * @returns The first ListItemNode found, or null if none exist. | ||
| */ | ||
| export function $findNearestListItemNode( | ||
| node: LexicalNode, | ||
| ): ListItemNode | null { | ||
| const matchingParent = $findMatchingParent(node, parent => | ||
| $isListItemNode(parent), | ||
| ); | ||
| return matchingParent as ListItemNode | null; | ||
| } | ||
| /** | ||
| * Takes a deeply nested ListNode or ListItemNode and traverses up the branch to delete the first | ||
| * ancestral ListNode (which could be the root ListNode) or ListItemNode with siblings, essentially | ||
| * bringing the deeply nested node up the branch once. Would remove sublist if it has siblings. | ||
| * Should not break ListItem -> List -> ListItem chain as empty List/ItemNodes should be removed on .remove(). | ||
| * @param sublist - The nested ListNode or ListItemNode to be brought up the branch. | ||
| */ | ||
| export function $removeHighestEmptyListParent( | ||
| sublist: ListItemNode | ListNode, | ||
| ) { | ||
| // Nodes may be repeatedly indented, to create deeply nested lists that each | ||
| // contain just one bullet. | ||
| // Our goal is to remove these (empty) deeply nested lists. The easiest | ||
| // way to do that is crawl back up the tree until we find a node that has siblings | ||
| // (e.g. is actually part of the list contents) and delete that, or delete | ||
| // the root of the list (if no list nodes have siblings.) | ||
| let emptyListPtr = sublist; | ||
| while ( | ||
| emptyListPtr.getNextSibling() == null && | ||
| emptyListPtr.getPreviousSibling() == null | ||
| ) { | ||
| const parent = emptyListPtr.getParent(); | ||
| if (parent == null || !($isListItemNode(parent) || $isListNode(parent))) { | ||
| break; | ||
| } | ||
| emptyListPtr = parent; | ||
| } | ||
| emptyListPtr.remove(); | ||
| } | ||
| /** | ||
| * Calculates the start value for a new list created by splitting an existing list. | ||
| */ | ||
| export function $getNewListStart( | ||
| list: ListNode, | ||
| listItem: ListItemNode, | ||
| ): number { | ||
| return list.getStart() + listItem.getIndexWithinParent(); | ||
| } |
+33
-17
@@ -11,9 +11,11 @@ { | ||
| "license": "MIT", | ||
| "version": "0.44.1-nightly.20260519.0", | ||
| "main": "LexicalList.js", | ||
| "types": "index.d.ts", | ||
| "version": "0.45.0", | ||
| "main": "./dist/LexicalList.js", | ||
| "types": "./dist/index.d.ts", | ||
| "dependencies": { | ||
| "@lexical/extension": "0.44.1-nightly.20260519.0", | ||
| "@lexical/utils": "0.44.1-nightly.20260519.0", | ||
| "lexical": "0.44.1-nightly.20260519.0" | ||
| "@lexical/extension": "0.45.0", | ||
| "@lexical/internal": "0.45.0", | ||
| "lexical": "0.45.0", | ||
| "@lexical/utils": "0.45.0", | ||
| "@lexical/html": "0.45.0" | ||
| }, | ||
@@ -25,21 +27,35 @@ "repository": { | ||
| }, | ||
| "module": "LexicalList.mjs", | ||
| "module": "./dist/LexicalList.mjs", | ||
| "sideEffects": false, | ||
| "exports": { | ||
| ".": { | ||
| "source": "./src/index.ts", | ||
| "import": { | ||
| "types": "./index.d.ts", | ||
| "development": "./LexicalList.dev.mjs", | ||
| "production": "./LexicalList.prod.mjs", | ||
| "node": "./LexicalList.node.mjs", | ||
| "default": "./LexicalList.mjs" | ||
| "types": "./dist/index.d.ts", | ||
| "development": "./dist/LexicalList.dev.mjs", | ||
| "production": "./dist/LexicalList.prod.mjs", | ||
| "node": "./dist/LexicalList.node.mjs", | ||
| "default": "./dist/LexicalList.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./index.d.ts", | ||
| "development": "./LexicalList.dev.js", | ||
| "production": "./LexicalList.prod.js", | ||
| "default": "./LexicalList.js" | ||
| "types": "./dist/index.d.ts", | ||
| "development": "./dist/LexicalList.dev.js", | ||
| "production": "./dist/LexicalList.prod.js", | ||
| "default": "./dist/LexicalList.js" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "src", | ||
| "!src/__tests__", | ||
| "!src/__bench__", | ||
| "!src/__mocks__", | ||
| "!src/**/*.test.ts", | ||
| "!src/**/*.test.tsx", | ||
| "!src/**/*.bench.ts", | ||
| "!src/**/*.bench.tsx", | ||
| "README.md", | ||
| "LICENSE" | ||
| ] | ||
| } |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { LexicalCommand, LexicalEditor } from 'lexical'; | ||
| import { Signal } from '@lexical/extension'; | ||
| export declare const INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>; | ||
| /** | ||
| * Registers the checklist plugin with the editor. | ||
| * @param editor The LexicalEditor instance. | ||
| * @param options Optional configuration. | ||
| * - disableTakeFocusOnClick: If true, clicking a checklist item will not focus the editor (useful for mobile). | ||
| */ | ||
| export declare function registerCheckList(editor: LexicalEditor, options?: { | ||
| disableTakeFocusOnClick?: boolean | Signal<boolean>; | ||
| }): () => void; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import { ListItemNode, ListNode } from './'; | ||
| import { ListType } from './LexicalListNode'; | ||
| /** | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export declare function $insertList(listType: ListType): void; | ||
| /** | ||
| * A recursive function that goes through each list and their children, including nested lists, | ||
| * appending list2 children after list1 children and updating ListItemNode values. | ||
| * @param list1 - The first list to be merged. | ||
| * @param list2 - The second list to be merged. | ||
| */ | ||
| export declare function mergeLists(list1: ListNode, list2: ListNode): void; | ||
| /** | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| */ | ||
| export declare function $removeList(): void; | ||
| /** | ||
| * Takes the value of a child ListItemNode and makes it the value the ListItemNode | ||
| * should be if it isn't already. Also ensures that checked is undefined if the | ||
| * parent does not have a list type of 'check'. | ||
| * @param list - The list whose children are updated. | ||
| */ | ||
| export declare function updateChildrenListItemValue(list: ListNode): void; | ||
| /** | ||
| * Merge the next sibling list if same type. | ||
| * <ul> will merge with <ul>, but NOT <ul> with <ol>. | ||
| * @param list - The list whose next sibling should be potentially merged | ||
| */ | ||
| export declare function mergeNextSiblingListIfSameType(list: ListNode): void; | ||
| /** | ||
| * Adds an empty ListNode/ListItemNode chain at listItemNode, so as to | ||
| * create an indent effect. Won't indent ListItemNodes that have a ListNode as | ||
| * a child, but does merge sibling ListItemNodes if one has a nested ListNode. | ||
| * @param listItemNode - The ListItemNode to be indented. | ||
| */ | ||
| export declare function $handleIndent(listItemNode: ListItemNode): void; | ||
| /** | ||
| * Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode | ||
| * has a great grandparent node of type ListNode, which is where the ListItemNode will reside | ||
| * within as a child. | ||
| * @param listItemNode - The ListItemNode to remove the indent (outdent). | ||
| */ | ||
| export declare function $handleOutdent(listItemNode: ListItemNode): void; | ||
| /** | ||
| * Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode | ||
| * or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode | ||
| * (which should be the parent node) and insert the ParagraphNode as a sibling to the ListNode. If the ListNode is | ||
| * nested in a ListItemNode instead, it will add the ParagraphNode after the grandparent ListItemNode. | ||
| * Throws an invariant if the selection is not a child of a ListNode. | ||
| * @returns true if a ParagraphNode was inserted successfully, false if there is no selection | ||
| * or the selection does not contain a ListItemNode or the node already holds text. | ||
| */ | ||
| export declare function $handleListInsertParagraph(restoreNumbering?: boolean): boolean; |
-75
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { SerializedListItemNode } from './LexicalListItemNode'; | ||
| import type { ListNodeTagType, ListType, SerializedListNode } from './LexicalListNode'; | ||
| import type { LexicalCommand, LexicalEditor, NodeKey } from 'lexical'; | ||
| import { INSERT_CHECK_LIST_COMMAND, registerCheckList } from './checkList'; | ||
| import { $handleListInsertParagraph, $insertList, $removeList } from './formatList'; | ||
| import { $createListItemNode, $isListItemNode, ListItemNode } from './LexicalListItemNode'; | ||
| import { $createListNode, $isListNode, ListNode } from './LexicalListNode'; | ||
| import { $getListDepth } from './utils'; | ||
| export { $createListItemNode, $createListNode, $getListDepth, $handleListInsertParagraph, $insertList, $isListItemNode, $isListNode, $removeList, INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode, ListNodeTagType, ListType, registerCheckList, SerializedListItemNode, SerializedListNode, }; | ||
| export declare const UPDATE_LIST_START_COMMAND: LexicalCommand<{ | ||
| listNodeKey: NodeKey; | ||
| newStart: number; | ||
| }>; | ||
| export declare const INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>; | ||
| export declare const INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>; | ||
| export declare const REMOVE_LIST_COMMAND: LexicalCommand<void>; | ||
| export interface RegisterListOptions { | ||
| restoreNumbering?: boolean; | ||
| } | ||
| export declare function registerList(editor: LexicalEditor, options?: RegisterListOptions): () => void; | ||
| export declare function registerListStrictIndentTransform(editor: LexicalEditor): () => void; | ||
| /** | ||
| * @deprecated use {@link $insertList} from an update or command listener. | ||
| * | ||
| * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of | ||
| * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode. | ||
| * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children. | ||
| * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode, | ||
| * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with | ||
| * a new ListNode, or create a new ListNode at the nearest root/shadow root. | ||
| * @param editor - The lexical editor. | ||
| * @param listType - The type of list, "number" | "bullet" | "check". | ||
| */ | ||
| export declare function insertList(editor: LexicalEditor, listType: ListType): void; | ||
| /** | ||
| * @deprecated use {@link $removeList} from an update or command listener. | ||
| * | ||
| * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode | ||
| * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode, | ||
| * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node | ||
| * inside a ListItemNode will be appended to the new ParagraphNodes. | ||
| * @param editor - The lexical editor. | ||
| */ | ||
| export declare function removeList(editor: LexicalEditor): void; | ||
| export interface ListConfig { | ||
| /** | ||
| * When `true`, enforces strict indentation rules for list items, ensuring consistent structure. | ||
| * When `false` (default), indentation is more flexible. | ||
| */ | ||
| hasStrictIndent: boolean; | ||
| shouldPreserveNumbering: boolean; | ||
| } | ||
| /** | ||
| * Configures {@link ListNode}, {@link ListItemNode} and registers | ||
| * the strict indent transform if `hasStrictIndent` is true (default false). | ||
| */ | ||
| export declare const ListExtension: import("lexical").LexicalExtension<ListConfig, "@lexical/list/List", import("@lexical/extension").NamedSignalsOutput<ListConfig>, unknown>; | ||
| export interface CheckListConfig { | ||
| disableTakeFocusOnClick: boolean; | ||
| } | ||
| /** | ||
| * Registers checklist functionality for {@link ListNode} and | ||
| * {@link ListItemNode} with a | ||
| * {@link INSERT_CHECK_LIST_COMMAND} listener and | ||
| * the expected keyboard and mouse interactions for | ||
| * checkboxes. | ||
| */ | ||
| export declare const CheckListExtension: import("lexical").LexicalExtension<CheckListConfig, "@lexical/list/CheckList", import("@lexical/extension").NamedSignalsOutput<CheckListConfig>, unknown>; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| /** | ||
| * 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' | ||
| const LexicalList = process.env.NODE_ENV !== 'production' ? require('./LexicalList.dev.js') : require('./LexicalList.prod.js'); | ||
| module.exports = LexicalList; |
Sorry, the diff of this file is not supported yet
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import * as modDev from './LexicalList.dev.mjs'; | ||
| import * as modProd from './LexicalList.prod.mjs'; | ||
| const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd; | ||
| export const $createListItemNode = mod.$createListItemNode; | ||
| export const $createListNode = mod.$createListNode; | ||
| export const $getListDepth = mod.$getListDepth; | ||
| export const $handleListInsertParagraph = mod.$handleListInsertParagraph; | ||
| export const $insertList = mod.$insertList; | ||
| export const $isListItemNode = mod.$isListItemNode; | ||
| export const $isListNode = mod.$isListNode; | ||
| export const $removeList = mod.$removeList; | ||
| export const CheckListExtension = mod.CheckListExtension; | ||
| export const INSERT_CHECK_LIST_COMMAND = mod.INSERT_CHECK_LIST_COMMAND; | ||
| export const INSERT_ORDERED_LIST_COMMAND = mod.INSERT_ORDERED_LIST_COMMAND; | ||
| export const INSERT_UNORDERED_LIST_COMMAND = mod.INSERT_UNORDERED_LIST_COMMAND; | ||
| export const ListExtension = mod.ListExtension; | ||
| export const ListItemNode = mod.ListItemNode; | ||
| export const ListNode = mod.ListNode; | ||
| export const REMOVE_LIST_COMMAND = mod.REMOVE_LIST_COMMAND; | ||
| export const UPDATE_LIST_START_COMMAND = mod.UPDATE_LIST_START_COMMAND; | ||
| export const insertList = mod.insertList; | ||
| export const registerCheckList = mod.registerCheckList; | ||
| export const registerList = mod.registerList; | ||
| export const registerListStrictIndentTransform = mod.registerListStrictIndentTransform; | ||
| export const removeList = mod.removeList; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalList.dev.mjs') : import('./LexicalList.prod.mjs')); | ||
| export const $createListItemNode = mod.$createListItemNode; | ||
| export const $createListNode = mod.$createListNode; | ||
| export const $getListDepth = mod.$getListDepth; | ||
| export const $handleListInsertParagraph = mod.$handleListInsertParagraph; | ||
| export const $insertList = mod.$insertList; | ||
| export const $isListItemNode = mod.$isListItemNode; | ||
| export const $isListNode = mod.$isListNode; | ||
| export const $removeList = mod.$removeList; | ||
| export const CheckListExtension = mod.CheckListExtension; | ||
| export const INSERT_CHECK_LIST_COMMAND = mod.INSERT_CHECK_LIST_COMMAND; | ||
| export const INSERT_ORDERED_LIST_COMMAND = mod.INSERT_ORDERED_LIST_COMMAND; | ||
| export const INSERT_UNORDERED_LIST_COMMAND = mod.INSERT_UNORDERED_LIST_COMMAND; | ||
| export const ListExtension = mod.ListExtension; | ||
| export const ListItemNode = mod.ListItemNode; | ||
| export const ListNode = mod.ListNode; | ||
| export const REMOVE_LIST_COMMAND = mod.REMOVE_LIST_COMMAND; | ||
| export const UPDATE_LIST_START_COMMAND = mod.UPDATE_LIST_START_COMMAND; | ||
| export const insertList = mod.insertList; | ||
| export const registerCheckList = mod.registerCheckList; | ||
| export const registerList = mod.registerList; | ||
| export const registerListStrictIndentTransform = mod.registerListStrictIndentTransform; | ||
| export const removeList = mod.removeList; |
| /** | ||
| * 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 e=require("@lexical/extension"),t=require("@lexical/utils"),n=require("lexical");function r(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function i(e){let t=1,n=e.getParent();for(;null!=n;){if(L(n)){const e=n.getParent();if($(e)){t++,n=e.getParent();continue}r(40)}return t}return t}function s(e){let t=e.getParent();$(t)||r(40);let n=t;for(;null!==n;)n=n.getParent(),$(n)&&(t=n);return t}function o(e){let t=[];const n=e.getChildren().filter(L);for(let e=0;e<n.length;e++){const r=n[e],i=r.getFirstChild();$(i)?t=t.concat(o(i)):t.push(r)}return t}function l(e){return L(e)&&$(e.getFirstChild())}function a(e,t){return L(e)&&(0===t.length||1===t.length&&e.is(t[0])&&0===e.getChildrenSize())}function c(e){const t=n.$getSelection();if(null!==t){let r=t.getNodes();if(n.$isRangeSelection(t)){const[i]=t.getStartEndPoints(),s=i.getNode(),o=s.getParent();if(n.$isRootOrShadowRoot(s)){const e=s.getFirstChild();if(e)r=e.selectStart().getNodes();else{const e=n.$createParagraphNode();s.append(e),r=e.select().getNodes()}}else if(a(s,r)){const t=M(e);if(n.$isRootOrShadowRoot(o)){s.replace(t);const e=y();n.$isElementNode(s)&&(e.setFormat(s.getFormatType()),e.setIndent(s.getIndent())),t.append(e)}else if(L(s)){const e=s.getParentOrThrow();d(t,e.getChildren()),e.replace(t)}return}}const i=new Set;for(let t=0;t<r.length;t++){const s=r[t];if(n.$isElementNode(s)&&s.isEmpty()&&!L(s)&&!i.has(s.getKey())){g(s,e);continue}let o=n.$isLeafNode(s)?s.getParent():L(s)&&s.isEmpty()?s:null;for(;null!=o;){const t=o.getKey();if($(o)){if(!i.has(t)){const n=M(e);d(n,o.getChildren()),o.replace(n),i.add(t)}break}{const r=o.getParent();if(n.$isRootOrShadowRoot(r)&&!i.has(t)){i.add(t),g(o,e);break}o=r}}}}}function d(e,t){e.splice(e.getChildrenSize(),0,t)}function g(e,t){if($(e))return e;const r=e.getPreviousSibling(),i=e.getNextSibling(),s=y();let o;if(d(s,e.getChildren()),$(r)&&t===r.getListType())r.append(s),$(i)&&t===i.getListType()&&(d(r,i.getChildren()),i.remove()),o=r;else if($(i)&&t===i.getListType())i.getFirstChildOrThrow().insertBefore(s),o=i;else{const n=M(t);n.append(s),e.replace(n),o=n}s.setFormat(e.getFormatType()),s.setIndent(e.getIndent());const l=n.$getSelection();return n.$isRangeSelection(l)&&(o.getKey()===l.anchor.key&&l.anchor.set(s.getKey(),l.anchor.offset,"element"),o.getKey()===l.focus.key&&l.focus.set(s.getKey(),l.focus.offset,"element")),e.remove(),o}function u(e,t){const n=e.getLastChild(),r=t.getFirstChild();n&&r&&l(n)&&l(r)&&(u(n.getFirstChild(),r.getFirstChild()),r.remove());const i=t.getChildren();i.length>0&&e.append(...i),t.remove()}function h(){const e=n.$getSelection();if(n.$isRangeSelection(e)){const r=new Set,i=e.getNodes(),l=e.anchor.getNode();if(a(l,i))r.add(s(l));else for(let e=0;e<i.length;e++){const o=i[e];if(n.$isLeafNode(o)){const e=t.$getNearestNodeOfType(o,C);null!=e&&r.add(s(e))}}for(const t of r){let r=t;const i=o(t);for(const t of i){const i=n.$createParagraphNode().setTextStyle(e.style).setTextFormat(e.format);d(i,t.getChildren()),r.insertAfter(i),r=i,t.__key===e.anchor.key&&n.$setPointFromCaret(e.anchor,n.$normalizeCaret(n.$getChildCaret(i,"next"))),t.__key===e.focus.key&&n.$setPointFromCaret(e.focus,n.$normalizeCaret(n.$getChildCaret(i,"next"))),t.remove()}t.remove()}}}function f(e){const t="check"!==e.getListType();let n=e.getStart();for(const r of e.getChildren())L(r)&&(r.getValue()!==n&&r.setValue(n),t&&null!=r.getLatest().__checked&&r.setChecked(void 0),$(r.getFirstChild())||n++)}function p(e){const t=new Set;if(l(e)||t.has(e.getKey()))return;const r=e.getParent(),i=e.getNextSibling(),s=e.getPreviousSibling();if(l(i)&&l(s)){const n=s.getFirstChild();if($(n)){n.append(e);const r=i.getFirstChild();if($(r)){d(n,r.getChildren()),i.remove(),t.add(i.getKey())}}}else if(l(i)){const t=i.getFirstChild();if($(t)){const n=t.getFirstChild();null!==n&&n.insertBefore(e)}}else if(l(s)){const t=s.getFirstChild();$(t)&&t.append(e)}else if($(r)){const t=n.$copyNode(e),o=n.$copyNode(r);t.append(o),o.append(e),s?s.insertAfter(t):i?i.insertBefore(t):r.append(t)}}function m(e){if(l(e))return;const t=e.getParent(),r=t?t.getParent():void 0;if($(r?r.getParent():void 0)&&L(r)&&$(t)){const i=t?t.getFirstChild():void 0,s=t?t.getLastChild():void 0;if(e.is(i))r.insertBefore(e),t.isEmpty()&&r.remove();else if(e.is(s))r.insertAfter(e),t.isEmpty()&&r.remove();else{const i=n.$copyNode(e),s=n.$copyNode(t);i.append(s),e.getPreviousSiblings().forEach(e=>s.append(e));const o=n.$copyNode(e),l=n.$copyNode(t);o.append(l),d(l,e.getNextSiblings()),r.insertBefore(i),r.insertAfter(o),r.replace(e)}}}function _(e=!1){const t=n.$getSelection();if(!n.$isRangeSelection(t)||!t.isCollapsed())return!1;const i=t.anchor.getNode();let o=null;if(L(i)&&0===i.getChildrenSize())o=i;else if(n.$isTextNode(i)){const e=i.getParent();L(e)&&e.getChildren().every(e=>n.$isTextNode(e)&&""===e.getTextContent().trim())&&(o=e)}if(null===o)return!1;const l=s(o),a=o.getParent();$(a)||r(40);const c=a.getParent();let d;if(n.$isRootOrShadowRoot(c))d=n.$createParagraphNode(),l.insertAfter(d);else{if(!L(c))return!1;d=n.$copyNode(c),c.insertAfter(d)}d.setTextStyle(t.style).setTextFormat(t.format).select();const g=o.getNextSiblings();if(g.length>0){const t=e?function(e,t){return e.getStart()+t.getIndexWithinParent()}(a,o):1,r=n.$copyNode(a).setStart(t);if(L(d)){const e=n.$copyNode(d);e.append(r),d.insertAfter(e)}else d.insertAfter(r);r.append(...g)}return function(e){let t=e;for(;null==t.getNextSibling()&&null==t.getPreviousSibling();){const e=t.getParent();if(null==e||!L(e)&&!$(e))break;t=e}t.remove()}(o),!0}class C extends n.ElementNode{__value;__checked;$config(){return this.config("listitem",{$transform:e=>{const i=e.getParent();if($(i))"check"!==i.getListType()&&null!=e.getChecked()&&e.setChecked(void 0);else if(i){const s=e.createParentElementNode();$(s)||r(340);const o=[e];for(const t of["previous","next"]){o.reverse();for(const{origin:r}of n.$getSiblingCaret(e,t)){if(!L(r))break;o.push(r)}}e.insertBefore(s),s.splice(0,0,o),n.$isRootOrShadowRoot(i)||(t.$insertNodeToNearestRootAtCaret(s,n.$rewindSiblingCaret(n.$getSiblingCaret(s,"next")),{$shouldSplit:()=>!1,removeEmptyDestination:!0}),i.isEmpty()&&i.isAttached()&&i.remove())}},extends:n.ElementNode,importDOM:n.buildImportMap({li:()=>({conversion:N,priority:0})})})}constructor(e=1,t=void 0,n){super(n),this.__value=void 0===e?1:e,this.__checked=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__value=e.__value,this.__checked=e.__checked}createDOM(e){const t=document.createElement("li");return this.updateListItemDOM(null,t,e),t}updateListItemDOM(e,r,i){!function(e,t){const n=t.getParent();!$(n)||"check"!==n.getListType()||$(t.getFirstChild())?(e.removeAttribute("role"),e.removeAttribute("tabIndex"),e.removeAttribute("aria-checked")):(e.setAttribute("role","checkbox"),e.setAttribute("tabIndex","-1"),e.setAttribute("aria-checked",t.getChecked()?"true":"false"))}(r,this),r.value=this.__value,function(e,r,i){const s=r.list;if(!s)return;const o=s.listitem,l=s.nested&&s.nested.listitem,a=i.getParent(),c=$(a)&&"check"===a.getListType(),d=i.getChecked(),g=i.getChildren().some(e=>$(e)),u=[];void 0!==s.listitemChecked&&u.push(s.listitemChecked);void 0!==s.listitemUnchecked&&u.push(s.listitemUnchecked);void 0!==l&&u.push(...n.normalizeClassNames(l));u.length>0&&t.removeClassNamesFromElement(e,...u);const h=[];void 0!==o&&h.push(...n.normalizeClassNames(o));if(c){const e=d?s.listitemChecked:s.listitemUnchecked;void 0!==e&&h.push(e)}void 0!==l&&g&&h.push(...n.normalizeClassNames(l));h.length>0&&t.addClassNamesToElement(e,...h)}(r,i.theme,this);const s=e?e.__style:"",o=this.__style;s!==o&&n.setDOMStyleFromCSS(r.style,o,s),function(e,t,r){const i=t.__textStyle,s=r?r.__textStyle:"";if(null!==r&&s===i)return;const o=n.getStyleObjectFromCSS(i);for(const t in o)e.style.setProperty(`--listitem-marker-${t}`,o[t]);if(""!==s)for(const t in n.getStyleObjectFromCSS(s))t in o||e.style.removeProperty(`--listitem-marker-${t}`)}(r,this,e)}updateDOM(e,t,n){const r=t;return this.updateListItemDOM(e,r,n),!1}updateFromJSON(e){return super.updateFromJSON(e).setValue(e.value).setChecked(e.checked)}exportDOM(e){const t=this.createDOM(e._config),r=this.getFormatType();r&&(t.style.textAlign=r);const i=this.getDirection();return i&&(t.dir=i),l(this)?{after(e){if(n.isHTMLElement(e)){const t=e.previousElementSibling;if(n.isHTMLElement(t)&&"LI"===t.nodeName){for(;e.firstChild;)t.append(e.firstChild);e.remove()}}return e},element:t}:{element:t}}exportJSON(){return{...super.exportJSON(),checked:this.getChecked(),value:this.getValue()}}append(...e){for(let t=0;t<e.length;t++){const r=e[t];if(n.$isElementNode(r)&&this.canMergeWith(r)){const e=r.getChildren();this.append(...e),r.remove()}else super.append(r)}return this}replace(e,t){if(L(e))return super.replace(e);this.setIndent(0);const i=this.getParentOrThrow();if(!$(i))return e;if(i.__first===this.getKey())i.insertBefore(e);else if(i.__last===this.getKey())i.insertAfter(e);else{const t=n.$copyNode(i);let r=this.getNextSibling();for(;r;){const e=r;r=r.getNextSibling(),t.append(e)}i.insertAfter(e),e.insertAfter(t)}const s=this.__key;let o=0;if(t&&(n.$isElementNode(e)||r(139),o=e.getChildrenSize(),e.splice(o,0,this.getChildren())),t&&n.$isElementNode(e)){const t=n.$getSelection();if(n.$isRangeSelection(t))for(const n of t.getStartEndPoints())n.key===s&&"element"===n.type&&n.set(e.getKey(),o+n.offset,"element")}return this.remove(),0===i.getChildrenSize()&&i.remove(),e}insertAfter(e,t=!0){const i=this.getParentOrThrow();if($(i)||r(39),L(e))return super.insertAfter(e,t);const s=this.getNextSiblings();if(i.insertAfter(e,t),0!==s.length){const r=n.$copyNode(i);s.forEach(e=>r.append(e)),e.insertAfter(r,t)}return e}remove(e){const t=this.getPreviousSibling(),n=this.getNextSibling();super.remove(e),t&&n&&l(t)&&l(n)&&(u(t.getFirstChild(),n.getFirstChild()),n.remove())}resetOnCopyNodeFrom(e){super.resetOnCopyNodeFrom(e),e.getChecked()&&this.setChecked(!1)}insertNewAfter(e,t=!0){const r=n.$copyNode(this);return this.insertAfter(r,t),r}collapseAtStart(e){const t=n.$createParagraphNode();this.getChildren().forEach(e=>t.append(e));const r=this.getParentOrThrow(),i=r.getParentOrThrow(),s=L(i);if(1===r.getChildrenSize())if(s)r.remove(),i.select();else{r.insertBefore(t),r.remove();const n=e.anchor,i=e.focus,s=t.getKey();"element"===n.type&&n.getNode().is(this)&&n.set(s,n.offset,"element"),"element"===i.type&&i.getNode().is(this)&&i.set(s,i.offset,"element")}else r.insertBefore(t),this.remove();return!0}getValue(){return this.getLatest().__value}setValue(e){const t=this.getWritable();return t.__value=e,t}getChecked(){const e=this.getLatest();let t;const n=this.getParent();return $(n)&&(t=n.getListType()),"check"===t?Boolean(e.__checked):void 0}setChecked(e){const t=this.getWritable();return t.__checked=e,t}toggleChecked(){const e=this.getWritable();return e.setChecked(!e.__checked)}getIndent(){const e=this.getParent();if(null===e||!this.isAttached())return this.getLatest().__indent;let t=e.getParentOrThrow(),n=0;for(;L(t);)t=t.getParentOrThrow().getParentOrThrow(),n++;return n}setIndent(e){"number"!=typeof e&&r(117),(e=Math.floor(e))>=0||r(199);let t=this.getIndent();for(;t!==e;)t<e?(p(this),t++):(m(this),t--);return this}canInsertAfter(e){return L(e)}canReplaceWith(e){return L(e)}canMergeWith(e){return L(e)||n.$isParagraphNode(e)}extractWithChild(e,t){if(!n.$isRangeSelection(t))return!1;const r=t.anchor.getNode(),i=t.focus.getNode();return this.isParentOf(r)&&this.isParentOf(i)&&this.getTextContent().length===t.getTextContent().length}isParentRequired(){return!0}createParentElementNode(){return M("bullet")}canMergeWhenEmpty(){return!0}}function N(e){if(e.classList.contains("task-list-item"))for(const t of e.children)if("INPUT"===t.tagName)return T(t);if(e.classList.contains("joplin-checkbox"))for(const t of e.children)if(t.classList.contains("checkbox-wrapper")&&t.children.length>0&&"INPUT"===t.children[0].tagName)return T(t.children[0]);const t=e.getAttribute("aria-checked"),r=y("true"===t||"false"!==t&&void 0);return n.$setFormatFromDOM(r,e),{after:S.bind(null,r),node:n.$setDirectionFromDOM(r,e)}}function T(e){if(!("checkbox"===e.getAttribute("type")))return{node:null};const t=y(e.hasAttribute("checked"));return{after:S.bind(null,t),node:t}}function S(e,t){const r=t[0];return 1===t.length&&n.$isParagraphNode(r)&&!e.getFormatType()&&r.getFormatType()?(e.setFormat(r.getFormatType()),r.getChildren()):t}function y(e){return n.$applyNodeReplacement(new C(void 0,e))}function L(e){return e instanceof C}class O extends n.ElementNode{__tag;__start;__listType;$config(){return this.config("list",{$transform:e=>{!function(e){const t=e.getNextSibling();$(t)&&e.getListType()===t.getListType()&&u(e,t)}(e),f(e)},extends:n.ElementNode,importDOM:n.buildImportMap({ol:()=>({conversion:x,priority:0}),ul:()=>({conversion:x,priority:0})})})}constructor(e="number",t=1,n){super(n);const r=E[e]||e;this.__listType=r,this.__tag="number"===r?"ol":"ul",this.__start=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__listType=e.__listType,this.__tag=e.__tag,this.__start=e.__start}getTag(){return this.getLatest().__tag}setListType(e){const t=this.getWritable();return t.__listType=e,t.__tag="number"===e?"ol":"ul",t}getListType(){return this.getLatest().__listType}getStart(){return this.getLatest().__start}setStart(e){const t=this.getWritable();return t.__start=e,t}createDOM(e,t){const n=this.__tag,r=document.createElement(n);return 1!==this.__start&&r.setAttribute("start",String(this.__start)),r.__lexicalListType=this.__listType,v(r,e.theme,this),r}updateDOM(e,t,n){return e.__tag!==this.__tag||e.__listType!==this.__listType||(v(t,n.theme,this),e.__start!==this.__start&&t.setAttribute("start",String(this.__start)),!1)}updateFromJSON(e){return super.updateFromJSON(e).setListType(e.listType).setStart(e.start)}exportDOM(e){const n=this.createDOM(e._config,e);return t.isHTMLElement(n)&&(1!==this.__start&&n.setAttribute("start",String(this.__start)),"check"===this.__listType&&n.setAttribute("__lexicalListType","check")),{element:n}}exportJSON(){return{...super.exportJSON(),listType:this.getListType(),start:this.getStart(),tag:this.getTag()}}canBeEmpty(){return!1}canIndent(){return!1}splice(e,t,r){let i=r;for(let e=0;e<r.length;e++){const t=r[e];L(t)||(i===r&&(i=[...r]),i[e]=this.createListItemNode().append(!n.$isElementNode(t)||$(t)||t.isInline()?t:n.$createTextNode(t.getTextContent())))}return super.splice(e,t,i)}extractWithChild(e){return L(e)}createListItemNode(){return y()}}function v(e,r,s){const o=[],l=[],a=r.list;if(void 0!==a){const e=a[`${s.__tag}Depth`]||[],t=i(s)-1,r=t%e.length,c=e[r],d=a[s.__tag];let g;const u=a.nested,h=a.checklist;if(void 0!==u&&u.list&&(g=u.list),void 0!==d&&o.push(d),void 0!==h&&"check"===s.__listType&&o.push(h),void 0!==c){o.push(...n.normalizeClassNames(c));for(let t=0;t<e.length;t++)t!==r&&l.push(s.__tag+t)}if(void 0!==g){const e=n.normalizeClassNames(g);t>1?o.push(...e):l.push(...e)}}l.length>0&&t.removeClassNamesFromElement(e,...l),o.length>0&&t.addClassNamesToElement(e,...o)}function x(e){let r;if(function(e){return t.isHTMLElement(e)&&"ol"===e.nodeName.toLowerCase()}(e)){const t=e.start;r=M("number",t)}else r=function(e){if("check"===e.getAttribute("__lexicallisttype")||e.classList.contains("contains-task-list")||"1"===e.getAttribute("data-is-checklist"))return!0;for(const n of e.childNodes)if(t.isHTMLElement(n)&&n.hasAttribute("aria-checked"))return!0;return!1}(e)?M("check"):M("bullet");return n.$setDirectionFromDOM(r,e),{after:e=>function(e,t){const n=t.createListItemNode.bind(t),r=[];for(let t=0;t<e.length;t++){const i=e[t];if(L(i)){r.push(i);const e=i.getChildren();e.length>1&&e.forEach(e=>{$(e)&&r.push(n().append(e))})}else r.push(n().append(i))}return r}(e,r),node:r}}const E={ol:"number",ul:"bullet"};function M(e="number",t=1){return n.$applyNodeReplacement(new O(e,t))}function $(e){return e instanceof O}const P=n.createCommand("INSERT_CHECK_LIST_COMMAND");function b(e,r){const i=r&&r.disableTakeFocusOnClick||!1,s="boolean"==typeof i?()=>i:i.peek.bind(i),o=e=>{const n=e.target;if(!t.isHTMLElement(n))return!1;const r=n.__lexicalCheckListLastHandled;return void 0!==r&&e.timeStamp-r<500},l=e=>{const n=e.target;t.isHTMLElement(n)&&(n.__lexicalCheckListLastHandled=e.timeStamp)},a=e=>{o(e)||(l(e),A(e,s()))},d=e=>{"touch"===e.pointerType&&(o(e)||(l(e),A(e,s())))},g=e=>{!function(e,t){k(e,()=>{e.preventDefault(),t&&e.stopPropagation()})}(e,s())};return t.mergeRegister(e.registerCommand(P,()=>(c("check"),!0),n.COMMAND_PRIORITY_LOW),e.registerCommand(n.KEY_ARROW_DOWN_COMMAND,t=>R(t,e,!1),n.COMMAND_PRIORITY_LOW),e.registerCommand(n.KEY_ARROW_UP_COMMAND,t=>R(t,e,!0),n.COMMAND_PRIORITY_LOW),e.registerCommand(n.KEY_ESCAPE_COMMAND,()=>{if(null!=I()){const t=e.getRootElement();return null!=t&&t.focus(),!0}return!1},n.COMMAND_PRIORITY_LOW),e.registerCommand(n.KEY_SPACE_COMMAND,t=>{const r=I();return!(null==r||!e.isEditable())&&(e.update(()=>{const e=n.$getNearestNodeFromDOMNode(r);L(e)&&(t.preventDefault(),e.toggleChecked())}),!0)},n.COMMAND_PRIORITY_LOW),e.registerCommand(n.KEY_ARROW_LEFT_COMMAND,r=>e.getEditorState().read(()=>{const i=n.$getSelection();if(n.$isRangeSelection(i)&&i.isCollapsed()){const{anchor:s}=i,o="element"===s.type;if(o||0===s.offset){const i=s.getNode(),l=t.$findMatchingParent(i,e=>n.$isElementNode(e)&&!e.isInline());if(L(l)){const t=l.getParent();if($(t)&&"check"===t.getListType()&&(o||l.getFirstDescendant()===i)){const t=e.getElementByKey(l.__key);if(null!=t&&document.activeElement!==t)return t.focus(),r.preventDefault(),!0}}}}return!1}),n.COMMAND_PRIORITY_LOW),e.registerRootListener(e=>{if(null!==e)return e.addEventListener("click",a),e.addEventListener("pointerup",d),e.addEventListener("pointerdown",g,{capture:!0}),e.addEventListener("mousedown",g,{capture:!0}),e.addEventListener("touchstart",g,{capture:!0,passive:!1}),()=>{e.removeEventListener("click",a),e.removeEventListener("pointerup",d),e.removeEventListener("pointerdown",g,{capture:!0}),e.removeEventListener("mousedown",g,{capture:!0}),e.removeEventListener("touchstart",g,{capture:!0})}}))}function k(e,n){const r=e.target;if(!t.isHTMLElement(r))return;const i=r.firstChild;if(t.isHTMLElement(i)&&("UL"===i.tagName||"OL"===i.tagName))return;const s=r.parentNode;if(!s||"check"!==s.__lexicalListType)return;let o=null,l=null;if("clientX"in e)o=e.clientX;else if("touches"in e){const t=e.touches;t.length>0&&(o=t[0].clientX,l="touch")}if(null==o)return;const a=r.getBoundingClientRect(),c=o/t.calculateZoomLevel(r),d=window.getComputedStyle?window.getComputedStyle(r,"::before"):{width:"0px"},g=parseFloat(d.width),u="touch"===l||"pointerType"in e&&"touch"===e.pointerType?32:0;("rtl"===r.dir?c<a.right+u&&c>a.right-g-u:c>a.left-u&&c<a.left+g+u)&&n()}function A(e,r){k(e,()=>{if(t.isHTMLElement(e.target)){const t=e.target,i=n.getNearestEditorFromDOMNode(t);null!=i&&i.isEditable()&&i.update(()=>{const e=n.$getNearestNodeFromDOMNode(t);L(e)&&(r?(n.$addUpdateTag(n.SKIP_SELECTION_FOCUS_TAG),n.$addUpdateTag(n.SKIP_DOM_SELECTION_TAG)):t.focus(),e.toggleChecked())})}})}function I(){const e=document.activeElement;return t.isHTMLElement(e)&&"LI"===e.tagName&&null!=e.parentNode&&"check"===e.parentNode.__lexicalListType?e:null}function R(e,t,r){const i=I();return null!=i&&t.update(()=>{const s=n.$getNearestNodeFromDOMNode(i);if(!L(s))return;const o=function(e,t){let n=t?e.getPreviousSibling():e.getNextSibling(),r=e;for(;null==n&&L(r);)r=r.getParentOrThrow().getParent(),null!=r&&(n=t?r.getPreviousSibling():r.getNextSibling());for(;L(n);){const e=t?n.getLastChild():n.getFirstChild();if(!$(e))return n;n=t?e.getLastChild():e.getFirstChild()}return null}(s,r);if(null!=o){o.selectStart();const n=t.getElementByKey(o.__key);null!=n&&(e.preventDefault(),setTimeout(()=>{n.focus()},0))}}),!1}const F=n.createCommand("UPDATE_LIST_START_COMMAND"),D=n.createCommand("INSERT_UNORDERED_LIST_COMMAND"),w=n.createCommand("INSERT_ORDERED_LIST_COMMAND"),W=n.createCommand("REMOVE_LIST_COMMAND");function K(e,r){return t.mergeRegister(e.registerCommand(w,()=>(c("number"),!0),n.COMMAND_PRIORITY_LOW),e.registerCommand(F,e=>{const{listNodeKey:t,newStart:r}=e,i=n.$getNodeByKey(t);return!!$(i)&&("number"===i.getListType()&&(i.setStart(r),f(i)),!0)},n.COMMAND_PRIORITY_LOW),e.registerCommand(D,()=>(c("bullet"),!0),n.COMMAND_PRIORITY_LOW),e.registerCommand(W,()=>(h(),!0),n.COMMAND_PRIORITY_LOW),e.registerCommand(n.INSERT_PARAGRAPH_COMMAND,()=>_(!!(r&&r.restoreNumbering)),n.COMMAND_PRIORITY_LOW),e.registerNodeTransform(C,e=>{const t=e.getFirstChild();if(t){if(n.$isTextNode(t)){const n=t.getStyle(),r=t.getFormat();e.getTextStyle()!==n&&e.setTextStyle(n),e.getTextFormat()!==r&&e.setTextFormat(r)}}else{const t=n.$getSelection();n.$isRangeSelection(t)&&(t.style!==e.getTextStyle()||t.format!==e.getTextFormat())&&t.isCollapsed()&&e.is(t.anchor.getNode())&&e.setTextStyle(t.style).setTextFormat(t.format)}}),e.registerNodeTransform(n.TextNode,e=>{const t=e.getParent();if(L(t)&&e.is(t.getFirstChild())){const n=e.getStyle(),r=e.getFormat();n===t.getTextStyle()&&r===t.getTextFormat()||t.setTextStyle(n).setTextFormat(r)}}))}function H(e){const n=e=>{const n=e.getParent();if($(e.getFirstChild())||!$(n))return;const r=t.$findMatchingParent(e,e=>L(e)&&$(e.getParent())&&L(e.getPreviousSibling()));if(null===r&&e.getIndent()>0)e.setIndent(0);else if(L(r)){const t=r.getPreviousSibling();if(L(t)){const r=function(e){let t=e,n=t.getFirstChild();for(;$(n);){const e=n.getLastChild();if(!L(e))break;t=e,n=t.getFirstChild()}return t}(t),s=r.getParent();if($(s)){const t=i(s);t+1<i(n)&&e.setIndent(t)}}}};return e.registerNodeTransform(O,e=>{const t=[e];for(;t.length>0;){const e=t.shift();if($(e))for(const r of e.getChildren())if(L(r)){n(r);const e=r.getFirstChild();$(e)&&t.push(e)}}})}const U=n.defineExtension({build:(t,n,r)=>e.namedSignals(n),config:n.safeCast({hasStrictIndent:!1,shouldPreserveNumbering:!1}),name:"@lexical/list/List",nodes:()=>[O,C],register(n,r,i){const s=i.getOutput();return t.mergeRegister(e.effect(()=>K(n,{restoreNumbering:s.shouldPreserveNumbering.value})),e.effect(()=>s.hasStrictIndent.value?H(n):void 0))}}),Y=n.defineExtension({build:(t,n)=>e.namedSignals(n),config:n.safeCast({disableTakeFocusOnClick:!1}),dependencies:[U],name:"@lexical/list/CheckList",register:(e,t,n)=>b(e,n.getOutput())});exports.$createListItemNode=y,exports.$createListNode=M,exports.$getListDepth=i,exports.$handleListInsertParagraph=_,exports.$insertList=c,exports.$isListItemNode=L,exports.$isListNode=$,exports.$removeList=h,exports.CheckListExtension=Y,exports.INSERT_CHECK_LIST_COMMAND=P,exports.INSERT_ORDERED_LIST_COMMAND=w,exports.INSERT_UNORDERED_LIST_COMMAND=D,exports.ListExtension=U,exports.ListItemNode=C,exports.ListNode=O,exports.REMOVE_LIST_COMMAND=W,exports.UPDATE_LIST_START_COMMAND=F,exports.insertList=function(e,t){e.update(()=>c(t))},exports.registerCheckList=b,exports.registerList=K,exports.registerListStrictIndentTransform=H,exports.removeList=function(e){e.update(()=>h())}; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import{effect as e,namedSignals as t}from"@lexical/extension";import{$getNearestNodeOfType as n,$insertNodeToNearestRootAtCaret as r,removeClassNamesFromElement as i,addClassNamesToElement as s,isHTMLElement as o,mergeRegister as l,$findMatchingParent as c,calculateZoomLevel as a}from"@lexical/utils";import{$copyNode as u,$getSelection as g,$isRangeSelection as h,$isRootOrShadowRoot as d,$createParagraphNode as f,$isElementNode as p,$isLeafNode as m,$setPointFromCaret as _,$normalizeCaret as y,$getChildCaret as C,$isTextNode as v,ElementNode as T,buildImportMap as S,$getSiblingCaret as k,$rewindSiblingCaret as b,setDOMStyleFromCSS as x,isHTMLElement as L,$isParagraphNode as N,$applyNodeReplacement as P,$setFormatFromDOM as F,$setDirectionFromDOM as E,normalizeClassNames as O,getStyleObjectFromCSS as A,$createTextNode as I,COMMAND_PRIORITY_LOW as w,KEY_ARROW_DOWN_COMMAND as D,KEY_ARROW_UP_COMMAND as M,KEY_ESCAPE_COMMAND as K,KEY_SPACE_COMMAND as R,$getNearestNodeFromDOMNode as B,KEY_ARROW_LEFT_COMMAND as W,createCommand as U,getNearestEditorFromDOMNode as $,$addUpdateTag as J,SKIP_SELECTION_FOCUS_TAG as V,SKIP_DOM_SELECTION_TAG as z,defineExtension as H,safeCast as X,$getNodeByKey as j,INSERT_PARAGRAPH_COMMAND as q,TextNode as G}from"lexical";function Q(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function Y(e){let t=1,n=e.getParent();for(;null!=n;){if(_e(n)){const e=n.getParent();if(ke(e)){t++,n=e.getParent();continue}Q(40)}return t}return t}function Z(e){let t=e.getParent();ke(t)||Q(40);let n=t;for(;null!==n;)n=n.getParent(),ke(n)&&(t=n);return t}function ee(e){let t=[];const n=e.getChildren().filter(_e);for(let e=0;e<n.length;e++){const r=n[e],i=r.getFirstChild();ke(i)?t=t.concat(ee(i)):t.push(r)}return t}function te(e){return _e(e)&&ke(e.getFirstChild())}function ne(e,t){return _e(e)&&(0===t.length||1===t.length&&e.is(t[0])&&0===e.getChildrenSize())}function re(e){const t=g();if(null!==t){let n=t.getNodes();if(h(t)){const[r]=t.getStartEndPoints(),i=r.getNode(),s=i.getParent();if(d(i)){const e=i.getFirstChild();if(e)n=e.selectStart().getNodes();else{const e=f();i.append(e),n=e.select().getNodes()}}else if(ne(i,n)){const t=Se(e);if(d(s)){i.replace(t);const e=me();p(i)&&(e.setFormat(i.getFormatType()),e.setIndent(i.getIndent())),t.append(e)}else if(_e(i)){const e=i.getParentOrThrow();ie(t,e.getChildren()),e.replace(t)}return}}const r=new Set;for(let t=0;t<n.length;t++){const i=n[t];if(p(i)&&i.isEmpty()&&!_e(i)&&!r.has(i.getKey())){se(i,e);continue}let s=m(i)?i.getParent():_e(i)&&i.isEmpty()?i:null;for(;null!=s;){const t=s.getKey();if(ke(s)){if(!r.has(t)){const n=Se(e);ie(n,s.getChildren()),s.replace(n),r.add(t)}break}{const n=s.getParent();if(d(n)&&!r.has(t)){r.add(t),se(s,e);break}s=n}}}}}function ie(e,t){e.splice(e.getChildrenSize(),0,t)}function se(e,t){if(ke(e))return e;const n=e.getPreviousSibling(),r=e.getNextSibling(),i=me();let s;if(ie(i,e.getChildren()),ke(n)&&t===n.getListType())n.append(i),ke(r)&&t===r.getListType()&&(ie(n,r.getChildren()),r.remove()),s=n;else if(ke(r)&&t===r.getListType())r.getFirstChildOrThrow().insertBefore(i),s=r;else{const n=Se(t);n.append(i),e.replace(n),s=n}i.setFormat(e.getFormatType()),i.setIndent(e.getIndent());const o=g();return h(o)&&(s.getKey()===o.anchor.key&&o.anchor.set(i.getKey(),o.anchor.offset,"element"),s.getKey()===o.focus.key&&o.focus.set(i.getKey(),o.focus.offset,"element")),e.remove(),s}function oe(e,t){const n=e.getLastChild(),r=t.getFirstChild();n&&r&&te(n)&&te(r)&&(oe(n.getFirstChild(),r.getFirstChild()),r.remove());const i=t.getChildren();i.length>0&&e.append(...i),t.remove()}function le(){const e=g();if(h(e)){const t=new Set,r=e.getNodes(),i=e.anchor.getNode();if(ne(i,r))t.add(Z(i));else for(let e=0;e<r.length;e++){const i=r[e];if(m(i)){const e=n(i,he);null!=e&&t.add(Z(e))}}for(const n of t){let t=n;const r=ee(n);for(const n of r){const r=f().setTextStyle(e.style).setTextFormat(e.format);ie(r,n.getChildren()),t.insertAfter(r),t=r,n.__key===e.anchor.key&&_(e.anchor,y(C(r,"next"))),n.__key===e.focus.key&&_(e.focus,y(C(r,"next"))),n.remove()}n.remove()}}}function ce(e){const t="check"!==e.getListType();let n=e.getStart();for(const r of e.getChildren())_e(r)&&(r.getValue()!==n&&r.setValue(n),t&&null!=r.getLatest().__checked&&r.setChecked(void 0),ke(r.getFirstChild())||n++)}function ae(e){const t=new Set;if(te(e)||t.has(e.getKey()))return;const n=e.getParent(),r=e.getNextSibling(),i=e.getPreviousSibling();if(te(r)&&te(i)){const n=i.getFirstChild();if(ke(n)){n.append(e);const i=r.getFirstChild();if(ke(i)){ie(n,i.getChildren()),r.remove(),t.add(r.getKey())}}}else if(te(r)){const t=r.getFirstChild();if(ke(t)){const n=t.getFirstChild();null!==n&&n.insertBefore(e)}}else if(te(i)){const t=i.getFirstChild();ke(t)&&t.append(e)}else if(ke(n)){const t=u(e),s=u(n);t.append(s),s.append(e),i?i.insertAfter(t):r?r.insertBefore(t):n.append(t)}}function ue(e){if(te(e))return;const t=e.getParent(),n=t?t.getParent():void 0;if(ke(n?n.getParent():void 0)&&_e(n)&&ke(t)){const r=t?t.getFirstChild():void 0,i=t?t.getLastChild():void 0;if(e.is(r))n.insertBefore(e),t.isEmpty()&&n.remove();else if(e.is(i))n.insertAfter(e),t.isEmpty()&&n.remove();else{const r=u(e),i=u(t);r.append(i),e.getPreviousSiblings().forEach(e=>i.append(e));const s=u(e),o=u(t);s.append(o),ie(o,e.getNextSiblings()),n.insertBefore(r),n.insertAfter(s),n.replace(e)}}}function ge(e=!1){const t=g();if(!h(t)||!t.isCollapsed())return!1;const n=t.anchor.getNode();let r=null;if(_e(n)&&0===n.getChildrenSize())r=n;else if(v(n)){const e=n.getParent();_e(e)&&e.getChildren().every(e=>v(e)&&""===e.getTextContent().trim())&&(r=e)}if(null===r)return!1;const i=Z(r),s=r.getParent();ke(s)||Q(40);const o=s.getParent();let l;if(d(o))l=f(),i.insertAfter(l);else{if(!_e(o))return!1;l=u(o),o.insertAfter(l)}l.setTextStyle(t.style).setTextFormat(t.format).select();const c=r.getNextSiblings();if(c.length>0){const t=e?function(e,t){return e.getStart()+t.getIndexWithinParent()}(s,r):1,n=u(s).setStart(t);if(_e(l)){const e=u(l);e.append(n),l.insertAfter(e)}else l.insertAfter(n);n.append(...c)}return function(e){let t=e;for(;null==t.getNextSibling()&&null==t.getPreviousSibling();){const e=t.getParent();if(null==e||!_e(e)&&!ke(e))break;t=e}t.remove()}(r),!0}class he extends T{__value;__checked;$config(){return this.config("listitem",{$transform:e=>{const t=e.getParent();if(ke(t))"check"!==t.getListType()&&null!=e.getChecked()&&e.setChecked(void 0);else if(t){const n=e.createParentElementNode();ke(n)||Q(340);const i=[e];for(const t of["previous","next"]){i.reverse();for(const{origin:n}of k(e,t)){if(!_e(n))break;i.push(n)}}e.insertBefore(n),n.splice(0,0,i),d(t)||(r(n,b(k(n,"next")),{$shouldSplit:()=>!1,removeEmptyDestination:!0}),t.isEmpty()&&t.isAttached()&&t.remove())}},extends:T,importDOM:S({li:()=>({conversion:de,priority:0})})})}constructor(e=1,t=void 0,n){super(n),this.__value=void 0===e?1:e,this.__checked=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__value=e.__value,this.__checked=e.__checked}createDOM(e){const t=document.createElement("li");return this.updateListItemDOM(null,t,e),t}updateListItemDOM(e,t,n){!function(e,t){const n=t.getParent();!ke(n)||"check"!==n.getListType()||ke(t.getFirstChild())?(e.removeAttribute("role"),e.removeAttribute("tabIndex"),e.removeAttribute("aria-checked")):(e.setAttribute("role","checkbox"),e.setAttribute("tabIndex","-1"),e.setAttribute("aria-checked",t.getChecked()?"true":"false"))}(t,this),t.value=this.__value,function(e,t,n){const r=t.list;if(!r)return;const o=r.listitem,l=r.nested&&r.nested.listitem,c=n.getParent(),a=ke(c)&&"check"===c.getListType(),u=n.getChecked(),g=n.getChildren().some(e=>ke(e)),h=[];void 0!==r.listitemChecked&&h.push(r.listitemChecked);void 0!==r.listitemUnchecked&&h.push(r.listitemUnchecked);void 0!==l&&h.push(...O(l));h.length>0&&i(e,...h);const d=[];void 0!==o&&d.push(...O(o));if(a){const e=u?r.listitemChecked:r.listitemUnchecked;void 0!==e&&d.push(e)}void 0!==l&&g&&d.push(...O(l));d.length>0&&s(e,...d)}(t,n.theme,this);const r=e?e.__style:"",o=this.__style;r!==o&&x(t.style,o,r),function(e,t,n){const r=t.__textStyle,i=n?n.__textStyle:"";if(null!==n&&i===r)return;const s=A(r);for(const t in s)e.style.setProperty(`--listitem-marker-${t}`,s[t]);if(""!==i)for(const t in A(i))t in s||e.style.removeProperty(`--listitem-marker-${t}`)}(t,this,e)}updateDOM(e,t,n){const r=t;return this.updateListItemDOM(e,r,n),!1}updateFromJSON(e){return super.updateFromJSON(e).setValue(e.value).setChecked(e.checked)}exportDOM(e){const t=this.createDOM(e._config),n=this.getFormatType();n&&(t.style.textAlign=n);const r=this.getDirection();return r&&(t.dir=r),te(this)?{after(e){if(L(e)){const t=e.previousElementSibling;if(L(t)&&"LI"===t.nodeName){for(;e.firstChild;)t.append(e.firstChild);e.remove()}}return e},element:t}:{element:t}}exportJSON(){return{...super.exportJSON(),checked:this.getChecked(),value:this.getValue()}}append(...e){for(let t=0;t<e.length;t++){const n=e[t];if(p(n)&&this.canMergeWith(n)){const e=n.getChildren();this.append(...e),n.remove()}else super.append(n)}return this}replace(e,t){if(_e(e))return super.replace(e);this.setIndent(0);const n=this.getParentOrThrow();if(!ke(n))return e;if(n.__first===this.getKey())n.insertBefore(e);else if(n.__last===this.getKey())n.insertAfter(e);else{const t=u(n);let r=this.getNextSibling();for(;r;){const e=r;r=r.getNextSibling(),t.append(e)}n.insertAfter(e),e.insertAfter(t)}const r=this.__key;let i=0;if(t&&(p(e)||Q(139),i=e.getChildrenSize(),e.splice(i,0,this.getChildren())),t&&p(e)){const t=g();if(h(t))for(const n of t.getStartEndPoints())n.key===r&&"element"===n.type&&n.set(e.getKey(),i+n.offset,"element")}return this.remove(),0===n.getChildrenSize()&&n.remove(),e}insertAfter(e,t=!0){const n=this.getParentOrThrow();if(ke(n)||Q(39),_e(e))return super.insertAfter(e,t);const r=this.getNextSiblings();if(n.insertAfter(e,t),0!==r.length){const i=u(n);r.forEach(e=>i.append(e)),e.insertAfter(i,t)}return e}remove(e){const t=this.getPreviousSibling(),n=this.getNextSibling();super.remove(e),t&&n&&te(t)&&te(n)&&(oe(t.getFirstChild(),n.getFirstChild()),n.remove())}resetOnCopyNodeFrom(e){super.resetOnCopyNodeFrom(e),e.getChecked()&&this.setChecked(!1)}insertNewAfter(e,t=!0){const n=u(this);return this.insertAfter(n,t),n}collapseAtStart(e){const t=f();this.getChildren().forEach(e=>t.append(e));const n=this.getParentOrThrow(),r=n.getParentOrThrow(),i=_e(r);if(1===n.getChildrenSize())if(i)n.remove(),r.select();else{n.insertBefore(t),n.remove();const r=e.anchor,i=e.focus,s=t.getKey();"element"===r.type&&r.getNode().is(this)&&r.set(s,r.offset,"element"),"element"===i.type&&i.getNode().is(this)&&i.set(s,i.offset,"element")}else n.insertBefore(t),this.remove();return!0}getValue(){return this.getLatest().__value}setValue(e){const t=this.getWritable();return t.__value=e,t}getChecked(){const e=this.getLatest();let t;const n=this.getParent();return ke(n)&&(t=n.getListType()),"check"===t?Boolean(e.__checked):void 0}setChecked(e){const t=this.getWritable();return t.__checked=e,t}toggleChecked(){const e=this.getWritable();return e.setChecked(!e.__checked)}getIndent(){const e=this.getParent();if(null===e||!this.isAttached())return this.getLatest().__indent;let t=e.getParentOrThrow(),n=0;for(;_e(t);)t=t.getParentOrThrow().getParentOrThrow(),n++;return n}setIndent(e){"number"!=typeof e&&Q(117),(e=Math.floor(e))>=0||Q(199);let t=this.getIndent();for(;t!==e;)t<e?(ae(this),t++):(ue(this),t--);return this}canInsertAfter(e){return _e(e)}canReplaceWith(e){return _e(e)}canMergeWith(e){return _e(e)||N(e)}extractWithChild(e,t){if(!h(t))return!1;const n=t.anchor.getNode(),r=t.focus.getNode();return this.isParentOf(n)&&this.isParentOf(r)&&this.getTextContent().length===t.getTextContent().length}isParentRequired(){return!0}createParentElementNode(){return Se("bullet")}canMergeWhenEmpty(){return!0}}function de(e){if(e.classList.contains("task-list-item"))for(const t of e.children)if("INPUT"===t.tagName)return fe(t);if(e.classList.contains("joplin-checkbox"))for(const t of e.children)if(t.classList.contains("checkbox-wrapper")&&t.children.length>0&&"INPUT"===t.children[0].tagName)return fe(t.children[0]);const t=e.getAttribute("aria-checked"),n=me("true"===t||"false"!==t&&void 0);return F(n,e),{after:pe.bind(null,n),node:E(n,e)}}function fe(e){if(!("checkbox"===e.getAttribute("type")))return{node:null};const t=me(e.hasAttribute("checked"));return{after:pe.bind(null,t),node:t}}function pe(e,t){const n=t[0];return 1===t.length&&N(n)&&!e.getFormatType()&&n.getFormatType()?(e.setFormat(n.getFormatType()),n.getChildren()):t}function me(e){return P(new he(void 0,e))}function _e(e){return e instanceof he}class ye extends T{__tag;__start;__listType;$config(){return this.config("list",{$transform:e=>{!function(e){const t=e.getNextSibling();ke(t)&&e.getListType()===t.getListType()&&oe(e,t)}(e),ce(e)},extends:T,importDOM:S({ol:()=>({conversion:ve,priority:0}),ul:()=>({conversion:ve,priority:0})})})}constructor(e="number",t=1,n){super(n);const r=Te[e]||e;this.__listType=r,this.__tag="number"===r?"ol":"ul",this.__start=t}afterCloneFrom(e){super.afterCloneFrom(e),this.__listType=e.__listType,this.__tag=e.__tag,this.__start=e.__start}getTag(){return this.getLatest().__tag}setListType(e){const t=this.getWritable();return t.__listType=e,t.__tag="number"===e?"ol":"ul",t}getListType(){return this.getLatest().__listType}getStart(){return this.getLatest().__start}setStart(e){const t=this.getWritable();return t.__start=e,t}createDOM(e,t){const n=this.__tag,r=document.createElement(n);return 1!==this.__start&&r.setAttribute("start",String(this.__start)),r.__lexicalListType=this.__listType,Ce(r,e.theme,this),r}updateDOM(e,t,n){return e.__tag!==this.__tag||e.__listType!==this.__listType||(Ce(t,n.theme,this),e.__start!==this.__start&&t.setAttribute("start",String(this.__start)),!1)}updateFromJSON(e){return super.updateFromJSON(e).setListType(e.listType).setStart(e.start)}exportDOM(e){const t=this.createDOM(e._config,e);return o(t)&&(1!==this.__start&&t.setAttribute("start",String(this.__start)),"check"===this.__listType&&t.setAttribute("__lexicalListType","check")),{element:t}}exportJSON(){return{...super.exportJSON(),listType:this.getListType(),start:this.getStart(),tag:this.getTag()}}canBeEmpty(){return!1}canIndent(){return!1}splice(e,t,n){let r=n;for(let e=0;e<n.length;e++){const t=n[e];_e(t)||(r===n&&(r=[...n]),r[e]=this.createListItemNode().append(!p(t)||ke(t)||t.isInline()?t:I(t.getTextContent())))}return super.splice(e,t,r)}extractWithChild(e){return _e(e)}createListItemNode(){return me()}}function Ce(e,t,n){const r=[],o=[],l=t.list;if(void 0!==l){const e=l[`${n.__tag}Depth`]||[],t=Y(n)-1,i=t%e.length,s=e[i],c=l[n.__tag];let a;const u=l.nested,g=l.checklist;if(void 0!==u&&u.list&&(a=u.list),void 0!==c&&r.push(c),void 0!==g&&"check"===n.__listType&&r.push(g),void 0!==s){r.push(...O(s));for(let t=0;t<e.length;t++)t!==i&&o.push(n.__tag+t)}if(void 0!==a){const e=O(a);t>1?r.push(...e):o.push(...e)}}o.length>0&&i(e,...o),r.length>0&&s(e,...r)}function ve(e){let t;if(function(e){return o(e)&&"ol"===e.nodeName.toLowerCase()}(e)){const n=e.start;t=Se("number",n)}else t=function(e){if("check"===e.getAttribute("__lexicallisttype")||e.classList.contains("contains-task-list")||"1"===e.getAttribute("data-is-checklist"))return!0;for(const t of e.childNodes)if(o(t)&&t.hasAttribute("aria-checked"))return!0;return!1}(e)?Se("check"):Se("bullet");return E(t,e),{after:e=>function(e,t){const n=t.createListItemNode.bind(t),r=[];for(let t=0;t<e.length;t++){const i=e[t];if(_e(i)){r.push(i);const e=i.getChildren();e.length>1&&e.forEach(e=>{ke(e)&&r.push(n().append(e))})}else r.push(n().append(i))}return r}(e,t),node:t}}const Te={ol:"number",ul:"bullet"};function Se(e="number",t=1){return P(new ye(e,t))}function ke(e){return e instanceof ye}const be=U("INSERT_CHECK_LIST_COMMAND");function xe(e,t){const n=t&&t.disableTakeFocusOnClick||!1,r="boolean"==typeof n?()=>n:n.peek.bind(n),i=e=>{const t=e.target;if(!o(t))return!1;const n=t.__lexicalCheckListLastHandled;return void 0!==n&&e.timeStamp-n<500},s=e=>{const t=e.target;o(t)&&(t.__lexicalCheckListLastHandled=e.timeStamp)},a=e=>{i(e)||(s(e),Ne(e,r()))},u=e=>{"touch"===e.pointerType&&(i(e)||(s(e),Ne(e,r())))},d=e=>{!function(e,t){Le(e,()=>{e.preventDefault(),t&&e.stopPropagation()})}(e,r())};return l(e.registerCommand(be,()=>(re("check"),!0),w),e.registerCommand(D,t=>Fe(t,e,!1),w),e.registerCommand(M,t=>Fe(t,e,!0),w),e.registerCommand(K,()=>{if(null!=Pe()){const t=e.getRootElement();return null!=t&&t.focus(),!0}return!1},w),e.registerCommand(R,t=>{const n=Pe();return!(null==n||!e.isEditable())&&(e.update(()=>{const e=B(n);_e(e)&&(t.preventDefault(),e.toggleChecked())}),!0)},w),e.registerCommand(W,t=>e.getEditorState().read(()=>{const n=g();if(h(n)&&n.isCollapsed()){const{anchor:r}=n,i="element"===r.type;if(i||0===r.offset){const n=r.getNode(),s=c(n,e=>p(e)&&!e.isInline());if(_e(s)){const r=s.getParent();if(ke(r)&&"check"===r.getListType()&&(i||s.getFirstDescendant()===n)){const n=e.getElementByKey(s.__key);if(null!=n&&document.activeElement!==n)return n.focus(),t.preventDefault(),!0}}}}return!1}),w),e.registerRootListener(e=>{if(null!==e)return e.addEventListener("click",a),e.addEventListener("pointerup",u),e.addEventListener("pointerdown",d,{capture:!0}),e.addEventListener("mousedown",d,{capture:!0}),e.addEventListener("touchstart",d,{capture:!0,passive:!1}),()=>{e.removeEventListener("click",a),e.removeEventListener("pointerup",u),e.removeEventListener("pointerdown",d,{capture:!0}),e.removeEventListener("mousedown",d,{capture:!0}),e.removeEventListener("touchstart",d,{capture:!0})}}))}function Le(e,t){const n=e.target;if(!o(n))return;const r=n.firstChild;if(o(r)&&("UL"===r.tagName||"OL"===r.tagName))return;const i=n.parentNode;if(!i||"check"!==i.__lexicalListType)return;let s=null,l=null;if("clientX"in e)s=e.clientX;else if("touches"in e){const t=e.touches;t.length>0&&(s=t[0].clientX,l="touch")}if(null==s)return;const c=n.getBoundingClientRect(),u=s/a(n),g=window.getComputedStyle?window.getComputedStyle(n,"::before"):{width:"0px"},h=parseFloat(g.width),d="touch"===l||"pointerType"in e&&"touch"===e.pointerType?32:0;("rtl"===n.dir?u<c.right+d&&u>c.right-h-d:u>c.left-d&&u<c.left+h+d)&&t()}function Ne(e,t){Le(e,()=>{if(o(e.target)){const n=e.target,r=$(n);null!=r&&r.isEditable()&&r.update(()=>{const e=B(n);_e(e)&&(t?(J(V),J(z)):n.focus(),e.toggleChecked())})}})}function Pe(){const e=document.activeElement;return o(e)&&"LI"===e.tagName&&null!=e.parentNode&&"check"===e.parentNode.__lexicalListType?e:null}function Fe(e,t,n){const r=Pe();return null!=r&&t.update(()=>{const i=B(r);if(!_e(i))return;const s=function(e,t){let n=t?e.getPreviousSibling():e.getNextSibling(),r=e;for(;null==n&&_e(r);)r=r.getParentOrThrow().getParent(),null!=r&&(n=t?r.getPreviousSibling():r.getNextSibling());for(;_e(n);){const e=t?n.getLastChild():n.getFirstChild();if(!ke(e))return n;n=t?e.getLastChild():e.getFirstChild()}return null}(i,n);if(null!=s){s.selectStart();const n=t.getElementByKey(s.__key);null!=n&&(e.preventDefault(),setTimeout(()=>{n.focus()},0))}}),!1}const Ee=U("UPDATE_LIST_START_COMMAND"),Oe=U("INSERT_UNORDERED_LIST_COMMAND"),Ae=U("INSERT_ORDERED_LIST_COMMAND"),Ie=U("REMOVE_LIST_COMMAND");function we(e,t){return l(e.registerCommand(Ae,()=>(re("number"),!0),w),e.registerCommand(Ee,e=>{const{listNodeKey:t,newStart:n}=e,r=j(t);return!!ke(r)&&("number"===r.getListType()&&(r.setStart(n),ce(r)),!0)},w),e.registerCommand(Oe,()=>(re("bullet"),!0),w),e.registerCommand(Ie,()=>(le(),!0),w),e.registerCommand(q,()=>ge(!!(t&&t.restoreNumbering)),w),e.registerNodeTransform(he,e=>{const t=e.getFirstChild();if(t){if(v(t)){const n=t.getStyle(),r=t.getFormat();e.getTextStyle()!==n&&e.setTextStyle(n),e.getTextFormat()!==r&&e.setTextFormat(r)}}else{const t=g();h(t)&&(t.style!==e.getTextStyle()||t.format!==e.getTextFormat())&&t.isCollapsed()&&e.is(t.anchor.getNode())&&e.setTextStyle(t.style).setTextFormat(t.format)}}),e.registerNodeTransform(G,e=>{const t=e.getParent();if(_e(t)&&e.is(t.getFirstChild())){const n=e.getStyle(),r=e.getFormat();n===t.getTextStyle()&&r===t.getTextFormat()||t.setTextStyle(n).setTextFormat(r)}}))}function De(e){const t=e=>{const t=e.getParent();if(ke(e.getFirstChild())||!ke(t))return;const n=c(e,e=>_e(e)&&ke(e.getParent())&&_e(e.getPreviousSibling()));if(null===n&&e.getIndent()>0)e.setIndent(0);else if(_e(n)){const r=n.getPreviousSibling();if(_e(r)){const n=function(e){let t=e,n=t.getFirstChild();for(;ke(n);){const e=n.getLastChild();if(!_e(e))break;t=e,n=t.getFirstChild()}return t}(r),i=n.getParent();if(ke(i)){const n=Y(i);n+1<Y(t)&&e.setIndent(n)}}}};return e.registerNodeTransform(ye,e=>{const n=[e];for(;n.length>0;){const e=n.shift();if(ke(e))for(const r of e.getChildren())if(_e(r)){t(r);const e=r.getFirstChild();ke(e)&&n.push(e)}}})}function Me(e,t){e.update(()=>re(t))}function Ke(e){e.update(()=>le())}const Re=H({build:(e,n,r)=>t(n),config:X({hasStrictIndent:!1,shouldPreserveNumbering:!1}),name:"@lexical/list/List",nodes:()=>[ye,he],register(t,n,r){const i=r.getOutput();return l(e(()=>we(t,{restoreNumbering:i.shouldPreserveNumbering.value})),e(()=>i.hasStrictIndent.value?De(t):void 0))}}),Be=H({build:(e,n)=>t(n),config:X({disableTakeFocusOnClick:!1}),dependencies:[Re],name:"@lexical/list/CheckList",register:(e,t,n)=>xe(e,n.getOutput())});export{me as $createListItemNode,Se as $createListNode,Y as $getListDepth,ge as $handleListInsertParagraph,re as $insertList,_e as $isListItemNode,ke as $isListNode,le as $removeList,Be as CheckListExtension,be as INSERT_CHECK_LIST_COMMAND,Ae as INSERT_ORDERED_LIST_COMMAND,Oe as INSERT_UNORDERED_LIST_COMMAND,Re as ListExtension,he as ListItemNode,ye as ListNode,Ie as REMOVE_LIST_COMMAND,Ee as UPDATE_LIST_START_COMMAND,Me as insertList,xe as registerCheckList,we as registerList,De as registerListStrictIndentTransform,Ke as removeList}; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { ListNode } from './'; | ||
| import type { BaseSelection, DOMExportOutput, EditorConfig, LexicalNode, LexicalUpdateJSON, NodeKey, ParagraphNode, RangeSelection, SerializedElementNode, Spread } from 'lexical'; | ||
| import { ElementNode, LexicalEditor } from 'lexical'; | ||
| export type SerializedListItemNode = Spread<{ | ||
| checked: boolean | undefined; | ||
| value: number; | ||
| }, SerializedElementNode>; | ||
| /** @noInheritDoc */ | ||
| export declare class ListItemNode extends ElementNode { | ||
| /** @internal */ | ||
| __value: number; | ||
| /** @internal */ | ||
| __checked?: boolean; | ||
| /** @internal */ | ||
| $config(): import("lexical").StaticNodeConfigRecord<"listitem", { | ||
| $transform: (node: ListItemNode) => void; | ||
| extends: typeof ElementNode; | ||
| importDOM: import("lexical").DOMConversionMap<HTMLElement>; | ||
| }>; | ||
| constructor(value?: number, checked?: undefined | boolean, key?: NodeKey); | ||
| afterCloneFrom(prevNode: this): void; | ||
| createDOM(config: EditorConfig): HTMLElement; | ||
| updateListItemDOM(prevNode: ListItemNode | null, dom: HTMLLIElement, config: EditorConfig): void; | ||
| updateDOM(prevNode: ListItemNode, dom: HTMLElement, config: EditorConfig): boolean; | ||
| updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedListItemNode>): this; | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput; | ||
| exportJSON(): SerializedListItemNode; | ||
| append(...nodes: LexicalNode[]): this; | ||
| replace<N extends LexicalNode>(replaceWithNode: N, includeChildren?: boolean): N; | ||
| insertAfter(node: LexicalNode, restoreSelection?: boolean): LexicalNode; | ||
| remove(preserveEmptyParent?: boolean): void; | ||
| resetOnCopyNodeFrom(original: this): void; | ||
| insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ListItemNode | ParagraphNode; | ||
| collapseAtStart(selection: RangeSelection): true; | ||
| getValue(): number; | ||
| setValue(value: number): this; | ||
| getChecked(): boolean | undefined; | ||
| setChecked(checked?: boolean): this; | ||
| toggleChecked(): this; | ||
| getIndent(): number; | ||
| setIndent(indent: number): this; | ||
| /** @deprecated @internal */ | ||
| canInsertAfter(node: LexicalNode): boolean; | ||
| /** @deprecated @internal */ | ||
| canReplaceWith(replacement: LexicalNode): boolean; | ||
| canMergeWith(node: LexicalNode): boolean; | ||
| extractWithChild(child: LexicalNode, selection: BaseSelection): boolean; | ||
| isParentRequired(): true; | ||
| createParentElementNode(): ListNode; | ||
| canMergeWhenEmpty(): true; | ||
| } | ||
| /** | ||
| * Creates a new List Item node, passing true/false will convert it to a checkbox input. | ||
| * @param checked - Is the List Item a checkbox and, if so, is it checked? undefined/null: not a checkbox, true/false is a checkbox and checked/unchecked, respectively. | ||
| * @returns The new List Item. | ||
| */ | ||
| export declare function $createListItemNode(checked?: boolean): ListItemNode; | ||
| /** | ||
| * Checks to see if the node is a ListItemNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode, false otherwise. | ||
| */ | ||
| export declare function $isListItemNode(node: LexicalNode | null | undefined): node is ListItemNode; |
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import { DOMExportOutput, EditorConfig, ElementNode, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey, SerializedElementNode, Spread } from 'lexical'; | ||
| import { ListItemNode } from '.'; | ||
| export type SerializedListNode = Spread<{ | ||
| listType: ListType; | ||
| start: number; | ||
| tag: ListNodeTagType; | ||
| }, SerializedElementNode>; | ||
| export type ListType = 'number' | 'bullet' | 'check'; | ||
| export type ListNodeTagType = 'ul' | 'ol'; | ||
| /** @noInheritDoc */ | ||
| export declare class ListNode extends ElementNode { | ||
| /** @internal */ | ||
| __tag: ListNodeTagType; | ||
| /** @internal */ | ||
| __start: number; | ||
| /** @internal */ | ||
| __listType: ListType; | ||
| /** @internal */ | ||
| $config(): import("lexical").StaticNodeConfigRecord<"list", { | ||
| $transform: (node: ListNode) => void; | ||
| extends: typeof ElementNode; | ||
| importDOM: import("lexical").DOMConversionMap<HTMLElement>; | ||
| }>; | ||
| constructor(listType?: ListType, start?: number, key?: NodeKey); | ||
| afterCloneFrom(prevNode: this): void; | ||
| getTag(): ListNodeTagType; | ||
| setListType(type: ListType): this; | ||
| getListType(): ListType; | ||
| getStart(): number; | ||
| setStart(start: number): this; | ||
| createDOM(config: EditorConfig, _editor?: LexicalEditor): HTMLElement; | ||
| updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean; | ||
| updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedListNode>): this; | ||
| exportDOM(editor: LexicalEditor): DOMExportOutput; | ||
| exportJSON(): SerializedListNode; | ||
| canBeEmpty(): false; | ||
| canIndent(): false; | ||
| splice(start: number, deleteCount: number, nodesToInsert: LexicalNode[]): this; | ||
| extractWithChild(child: LexicalNode): boolean; | ||
| /** | ||
| * Create an appropriate ListItemNode to be a child of this ListNode, | ||
| * {@link $createListItemNode} is the default implementation. | ||
| * | ||
| * @returns A new ListItemNode. | ||
| */ | ||
| createListItemNode(): ListItemNode; | ||
| } | ||
| /** | ||
| * Creates a ListNode of listType. | ||
| * @param listType - The type of list to be created. Can be 'number', 'bullet', or 'check'. | ||
| * @param start - Where an ordered list starts its count, start = 1 if left undefined. | ||
| * @returns The new ListNode | ||
| */ | ||
| export declare function $createListNode(listType?: ListType, start?: number): ListNode; | ||
| /** | ||
| * Checks to see if the node is a ListNode. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListNode, false otherwise. | ||
| */ | ||
| export declare function $isListNode(node: LexicalNode | null | undefined): node is ListNode; |
-63
| /** | ||
| * 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. | ||
| * | ||
| */ | ||
| import type { LexicalNode, Spread } from 'lexical'; | ||
| import { ListItemNode, ListNode } from './'; | ||
| /** | ||
| * Checks the depth of listNode from the root node. | ||
| * @param listNode - The ListNode to be checked. | ||
| * @returns The depth of the ListNode. | ||
| */ | ||
| export declare function $getListDepth(listNode: ListNode): number; | ||
| /** | ||
| * Finds the nearest ancestral ListNode and returns it, throws an invariant if listItem is not a ListItemNode. | ||
| * @param listItem - The node to be checked. | ||
| * @returns The ListNode found. | ||
| */ | ||
| export declare function $getTopListNode(listItem: LexicalNode): ListNode; | ||
| /** | ||
| * Checks if listItem has no child ListNodes and has no ListItemNode ancestors with siblings. | ||
| * @param listItem - the ListItemNode to be checked. | ||
| * @returns true if listItem has no child ListNode and no ListItemNode ancestors with siblings, false otherwise. | ||
| */ | ||
| export declare function $isLastItemInList(listItem: ListItemNode): boolean; | ||
| /** | ||
| * A recursive Depth-First Search (Postorder Traversal) that finds all of a node's children | ||
| * that are of type ListItemNode and returns them in an array. | ||
| * @param node - The ListNode to start the search. | ||
| * @returns An array containing all nodes of type ListItemNode found. | ||
| */ | ||
| export declare function $getAllListItems(node: ListNode): Array<ListItemNode>; | ||
| declare const NestedListNodeBrand: unique symbol; | ||
| /** | ||
| * Checks to see if the passed node is a ListItemNode and has a ListNode as a child. | ||
| * @param node - The node to be checked. | ||
| * @returns true if the node is a ListItemNode and has a ListNode child, false otherwise. | ||
| */ | ||
| export declare function isNestedListNode(node: LexicalNode | null | undefined): node is Spread<{ | ||
| getFirstChild(): ListNode; | ||
| [NestedListNodeBrand]: never; | ||
| }, ListItemNode>; | ||
| /** | ||
| * Traverses up the tree and returns the first ListItemNode found. | ||
| * @param node - Node to start the search. | ||
| * @returns The first ListItemNode found, or null if none exist. | ||
| */ | ||
| export declare function $findNearestListItemNode(node: LexicalNode): ListItemNode | null; | ||
| /** | ||
| * Takes a deeply nested ListNode or ListItemNode and traverses up the branch to delete the first | ||
| * ancestral ListNode (which could be the root ListNode) or ListItemNode with siblings, essentially | ||
| * bringing the deeply nested node up the branch once. Would remove sublist if it has siblings. | ||
| * Should not break ListItem -> List -> ListItem chain as empty List/ItemNodes should be removed on .remove(). | ||
| * @param sublist - The nested ListNode or ListItemNode to be brought up the branch. | ||
| */ | ||
| export declare function $removeHighestEmptyListParent(sublist: ListItemNode | ListNode): void; | ||
| /** | ||
| * Calculates the start value for a new list created by splitting an existing list. | ||
| */ | ||
| export declare function $getNewListStart(list: ListNode, listItem: ListItemNode): number; | ||
| export {}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
320776
50.22%29
70.59%7325
73.09%5
66.67%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated