@lexical/list 0.44.1-nightly.20260519.0 → 0.45.1-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,9 +8,10 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- var extension = require('@lexical/extension');
12
11
  var utils = require('@lexical/utils');
13
12
  var lexical = require('lexical');
13
+ var extension = require('@lexical/extension');
14
+ var html = require('@lexical/html');
14
15
 
15
16
  /**
16
17
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1295,7 +1296,7 @@ function $normalizeChildren(nodes, listNode) {
1295
1296
  }
1296
1297
  return normalizedListItems;
1297
1298
  }
1298
- function isDomChecklist(domNode) {
1299
+ function isDomChecklist$1(domNode) {
1299
1300
  if (domNode.getAttribute('__lexicallisttype') === 'check' ||
1300
1301
  // is github checklist
1301
1302
  domNode.classList.contains('contains-task-list') ||
@@ -1319,7 +1320,7 @@ function $convertListNode(domNode) {
1319
1320
  if (isHTMLOListElement(domNode)) {
1320
1321
  const start = domNode.start;
1321
1322
  node = $createListNode('number', start);
1322
- } else if (isDomChecklist(domNode)) {
1323
+ } else if (isDomChecklist$1(domNode)) {
1323
1324
  node = $createListNode('check');
1324
1325
  } else {
1325
1326
  node = $createListNode('bullet');
@@ -1787,33 +1788,13 @@ function $findChildrenEndListItemNode(listItemNode) {
1787
1788
  }
1788
1789
 
1789
1790
  /**
1790
- * @deprecated use {@link $insertList} from an update or command listener.
1791
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1791
1792
  *
1792
- * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
1793
- * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
1794
- * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children.
1795
- * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode,
1796
- * 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
1797
- * a new ListNode, or create a new ListNode at the nearest root/shadow root.
1798
- * @param editor - The lexical editor.
1799
- * @param listType - The type of list, "number" | "bullet" | "check".
1800
- */
1801
- function insertList(editor, listType) {
1802
- editor.update(() => $insertList(listType));
1803
- }
1804
-
1805
- /**
1806
- * @deprecated use {@link $removeList} from an update or command listener.
1793
+ * This source code is licensed under the MIT license found in the
1794
+ * LICENSE file in the root directory of this source tree.
1807
1795
  *
1808
- * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
1809
- * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
1810
- * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node
1811
- * inside a ListItemNode will be appended to the new ParagraphNodes.
1812
- * @param editor - The lexical editor.
1813
1796
  */
1814
- function removeList(editor) {
1815
- editor.update(() => $removeList());
1816
- }
1797
+
1817
1798
  /**
1818
1799
  * Configures {@link ListNode}, {@link ListItemNode} and registers
1819
1800
  * the strict indent transform if `hasStrictIndent` is true (default false).
@@ -1839,10 +1820,8 @@ const ListExtension = lexical.defineExtension({
1839
1820
  });
1840
1821
  /**
1841
1822
  * Registers checklist functionality for {@link ListNode} and
1842
- * {@link ListItemNode} with a
1843
- * {@link INSERT_CHECK_LIST_COMMAND} listener and
1844
- * the expected keyboard and mouse interactions for
1845
- * checkboxes.
1823
+ * {@link ListItemNode} with a `INSERT_CHECK_LIST_COMMAND` listener and
1824
+ * the expected keyboard and mouse interactions for checkboxes.
1846
1825
  */
1847
1826
  const CheckListExtension = lexical.defineExtension({
1848
1827
  build: (editor, config) => extension.namedSignals(config),
@@ -1854,6 +1833,279 @@ const CheckListExtension = lexical.defineExtension({
1854
1833
  register: (editor, config, state) => registerCheckList(editor, state.getOutput())
1855
1834
  });
1856
1835
 
1836
+ /**
1837
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1838
+ *
1839
+ * This source code is licensed under the MIT license found in the
1840
+ * LICENSE file in the root directory of this source tree.
1841
+ *
1842
+ */
1843
+
1844
+
1845
+ /**
1846
+ * Mirrors the legacy `isDomChecklist` heuristic from
1847
+ * `@lexical/list`.
1848
+ */
1849
+ function isDomChecklist(domNode) {
1850
+ return domNode.matches('[__lexicallisttype="check"], .contains-task-list, [data-is-checklist="1"]') || domNode.querySelector(':scope > [aria-checked]') !== null;
1851
+ }
1852
+
1853
+ /**
1854
+ * Lift nested `ListNode`s out of `ListItemNode`s into sibling
1855
+ * `ListItemNode`s (the legacy `$normalizeChildren` shape). Also wraps any
1856
+ * non-`ListItemNode` children in a new `ListItemNode`.
1857
+ */
1858
+ function $normalizeListChildren(children) {
1859
+ const out = [];
1860
+ for (const child of children) {
1861
+ if ($isListItemNode(child)) {
1862
+ out.push(child);
1863
+ const innerChildren = child.getChildren();
1864
+ if (innerChildren.length > 1) {
1865
+ for (const inner of innerChildren) {
1866
+ if ($isListNode(inner)) {
1867
+ out.push($createListItemNode().append(inner));
1868
+ }
1869
+ }
1870
+ }
1871
+ } else {
1872
+ out.push($createListItemNode().append(child));
1873
+ }
1874
+ }
1875
+ return out;
1876
+ }
1877
+ const ListRule = html.defineImportRule({
1878
+ $import: (ctx, el) => {
1879
+ let node;
1880
+ if (html.isElementOfTag(el, 'ol')) {
1881
+ node = $createListNode('number', el.start);
1882
+ } else if (isDomChecklist(el)) {
1883
+ node = $createListNode('check');
1884
+ } else {
1885
+ node = $createListNode('bullet');
1886
+ }
1887
+ lexical.$setDirectionFromDOM(node, el);
1888
+ // Propagate the list's `text-align` onto each `ListItemNode` child
1889
+ // (legacy `wrapContinuousInlines` did the same), so pasting
1890
+ // `<ul style="text-align: left"><li>…</li></ul>` ends up with the
1891
+ // alignment on the list items where the reconciler renders it as
1892
+ // `style="text-align: left"`.
1893
+ return [node.splice(0, 0, html.$propagateTextAlignToBlockChildren($normalizeListChildren(ctx.$importChildren(el)), el))];
1894
+ },
1895
+ match: html.sel.tag('ol', 'ul'),
1896
+ name: '@lexical/list/list'
1897
+ });
1898
+
1899
+ /**
1900
+ * Apply formatting from the first child paragraph of `<li>` to the list
1901
+ * item itself, then unwrap that paragraph (Google Docs sets the alignment
1902
+ * of the `<p>` inside the `<li>`). Mirrors the legacy
1903
+ * `setFormatFromChildren`.
1904
+ */
1905
+ function $liftFormatFromSingleParagraph(listItemNode, children) {
1906
+ if (children.length !== 1) {
1907
+ return children;
1908
+ }
1909
+ const firstChild = children[0];
1910
+ if (lexical.$isParagraphNode(firstChild) && !listItemNode.getFormatType() && firstChild.getFormatType()) {
1911
+ listItemNode.setFormat(firstChild.getFormatType());
1912
+ return firstChild.getChildren();
1913
+ }
1914
+ return children;
1915
+ }
1916
+
1917
+ /**
1918
+ * Collapse block children of a `<li>` into inline-with-line-break form: a
1919
+ * `ListItemNode` is an inline-level container, so any block child marks a
1920
+ * boundary. Contiguous inline siblings are kept together as a single run and
1921
+ * one {@link $createLineBreakNode} is inserted between runs — reproducing the
1922
+ * legacy `wrapContinuousInlines` + `$unwrapArtificialNodes` shape
1923
+ * (`<li>1<div>2</div>3</li>` → `1<br>2<br>3`) without the
1924
+ * `ArtificialNode__DO_NOT_USE` marker.
1925
+ *
1926
+ * Boundaries are detected with {@link $isBlockLevel}, NOT `$isParagraphNode`:
1927
+ * the `<div>`/`<section>`/… `TransparentBlockRule` happens to emit
1928
+ * `ParagraphNode`s, but a `<blockquote>` (`QuoteNode`), heading
1929
+ * (`HeadingNode`), or block decorator (`HorizontalRuleNode`, …) is just as
1930
+ * much a block boundary and must not be silently spliced into the list item
1931
+ * as-is. A nested `ListNode` is the one deliberate exception — it is a valid
1932
+ * list-item child that {@link $normalizeListChildren} lifts into a sibling,
1933
+ * so it is preserved here rather than unwrapped.
1934
+ */
1935
+ function $flattenListItemBlocks(children) {
1936
+ const $isBoundary = node => html.$isBlockLevel(node) && !$isListNode(node);
1937
+ if (!children.some($isBoundary)) {
1938
+ return children;
1939
+ }
1940
+ // Partition into segments — each maximal run of inline siblings, and each
1941
+ // boundary's own content — then join the segments with a single line break.
1942
+ const segments = [];
1943
+ let inlineRun = [];
1944
+ const flushInlineRun = () => {
1945
+ if (inlineRun.length > 0) {
1946
+ segments.push(inlineRun);
1947
+ inlineRun = [];
1948
+ }
1949
+ };
1950
+ for (const child of children) {
1951
+ if ($isBoundary(child)) {
1952
+ flushInlineRun();
1953
+ // Unwrap a block ElementNode to its inline content; a childless block
1954
+ // DecoratorNode stands on its own line.
1955
+ segments.push(lexical.$isElementNode(child) ? child.getChildren() : [child]);
1956
+ } else {
1957
+ inlineRun.push(child);
1958
+ }
1959
+ }
1960
+ flushInlineRun();
1961
+ const out = [];
1962
+ for (const segment of segments) {
1963
+ if (out.length > 0) {
1964
+ out.push(lexical.$createLineBreakNode());
1965
+ }
1966
+ out.push(...segment);
1967
+ }
1968
+ return out;
1969
+ }
1970
+ const ListItemRule = html.defineImportRule({
1971
+ $import: (ctx, el) => {
1972
+ const ariaChecked = el.getAttribute('aria-checked');
1973
+ const checked = ariaChecked === 'true' ? true : ariaChecked === 'false' ? false : undefined;
1974
+ const node = $createListItemNode(checked);
1975
+ lexical.$setFormatFromDOM(node, el);
1976
+ lexical.$setDirectionFromDOM(node, el);
1977
+ return [node.splice(0, 0,
1978
+ // Lift a sole wrapping paragraph's format onto the item *before*
1979
+ // flattening, otherwise the paragraph would already be unwrapped and
1980
+ // its alignment lost.
1981
+ $flattenListItemBlocks($liftFormatFromSingleParagraph(node, ctx.$importChildren(el))))];
1982
+ },
1983
+ match: html.sel.tag('li'),
1984
+ name: '@lexical/list/li'
1985
+ });
1986
+ function $buildChecklistItem(ctx, el, checkboxOwner) {
1987
+ const checkboxInput = html.isElementOfTag(checkboxOwner, 'input') ? checkboxOwner : checkboxOwner.querySelector('input[type="checkbox"]');
1988
+ if (!checkboxInput || checkboxInput.getAttribute('type') !== 'checkbox') {
1989
+ return [];
1990
+ }
1991
+ const checked = checkboxInput.hasAttribute('checked');
1992
+ const node = $createListItemNode(checked);
1993
+ lexical.$setFormatFromDOM(node, el);
1994
+ lexical.$setDirectionFromDOM(node, el);
1995
+ return [node.splice(0, 0, $flattenListItemBlocks($liftFormatFromSingleParagraph(node, ctx.$importChildren(el))))];
1996
+ }
1997
+ const TaskListItemRule = html.defineImportRule({
1998
+ $import: (ctx, el, $next) => {
1999
+ const input = el.querySelector(':scope > input[type="checkbox"]');
2000
+ if (!input) {
2001
+ return $next();
2002
+ }
2003
+ return $buildChecklistItem(ctx, el, input);
2004
+ },
2005
+ match: html.sel.tag('li').classAll('task-list-item'),
2006
+ name: '@lexical/list/li-task-list-item'
2007
+ });
2008
+ const JoplinChecklistItemRule = html.defineImportRule({
2009
+ $import: (ctx, el, $next) => {
2010
+ const wrapper = el.querySelector(':scope > .checkbox-wrapper');
2011
+ if (!wrapper) {
2012
+ return $next();
2013
+ }
2014
+ const input = wrapper.querySelector(':scope > input[type="checkbox"]');
2015
+ if (!input) {
2016
+ return $next();
2017
+ }
2018
+ return $buildChecklistItem(ctx, el, input);
2019
+ },
2020
+ match: html.sel.tag('li').classAll('joplin-checkbox'),
2021
+ name: '@lexical/list/li-joplin-checkbox'
2022
+ });
2023
+
2024
+ /**
2025
+ * A {@link ChildSchema} that enforces ListNode invariants: only
2026
+ * `ListItemNode` and (immediately-nested) `ListNode` children are
2027
+ * accepted; runs of other children get wrapped in a fresh
2028
+ * `ListItemNode`.
2029
+ *
2030
+ * @experimental
2031
+ */
2032
+ const ListSchema = {
2033
+ $accepts: child => $isListItemNode(child) || $isListNode(child),
2034
+ // Inline runs inside a `<ul>`/`<ol>` (e.g. text between two `<li>`s)
2035
+ // become the children of a synthetic `ListItemNode`. `ListItemNode`
2036
+ // is itself a block-level container of inlines, so no intermediate
2037
+ // `ParagraphNode` is needed (and the demoted-paragraph normalization
2038
+ // would strip one anyway).
2039
+ $packageRun: run => [$createListItemNode().splice(0, 0, run)],
2040
+ name: 'ListSchema'
2041
+ };
2042
+
2043
+ /**
2044
+ * Import rules for {@link ListNode} and {@link ListItemNode}, including
2045
+ * GitHub task-list and Joplin checkbox heuristics.
2046
+ *
2047
+ * @experimental
2048
+ */
2049
+ const ListImportRules = [
2050
+ // More specific rules (class-restricted) must precede the generic `li`
2051
+ // rule so they win the dispatch race (lower array index = higher
2052
+ // priority).
2053
+ TaskListItemRule, JoplinChecklistItemRule, ListRule, ListItemRule];
2054
+
2055
+ /**
2056
+ * Bundles {@link ListImportRules} together with the runtime
2057
+ * {@link ListExtension}. The application is expected to already have
2058
+ * `CoreImportExtension` (or some equivalent) in its dependency graph —
2059
+ * the core/text/paragraph/inline-format rules are a shared baseline,
2060
+ * not something this leaf importer should re-declare.
2061
+ *
2062
+ * @experimental
2063
+ */
2064
+ const ListImportExtension = lexical.defineExtension({
2065
+ dependencies: [ListExtension, lexical.configExtension(html.DOMImportExtension, {
2066
+ rules: ListImportRules
2067
+ })],
2068
+ name: '@lexical/list/Import'
2069
+ });
2070
+
2071
+ /**
2072
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2073
+ *
2074
+ * This source code is licensed under the MIT license found in the
2075
+ * LICENSE file in the root directory of this source tree.
2076
+ *
2077
+ */
2078
+
2079
+
2080
+ /**
2081
+ * @deprecated use {@link $insertList} from an update or command listener.
2082
+ *
2083
+ * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
2084
+ * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
2085
+ * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children.
2086
+ * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode,
2087
+ * 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
2088
+ * a new ListNode, or create a new ListNode at the nearest root/shadow root.
2089
+ * @param editor - The lexical editor.
2090
+ * @param listType - The type of list, "number" | "bullet" | "check".
2091
+ */
2092
+ function insertList(editor, listType) {
2093
+ editor.update(() => $insertList(listType));
2094
+ }
2095
+
2096
+ /**
2097
+ * @deprecated use {@link $removeList} from an update or command listener.
2098
+ *
2099
+ * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
2100
+ * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
2101
+ * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node
2102
+ * inside a ListItemNode will be appended to the new ParagraphNodes.
2103
+ * @param editor - The lexical editor.
2104
+ */
2105
+ function removeList(editor) {
2106
+ editor.update(() => $removeList());
2107
+ }
2108
+
1857
2109
  exports.$createListItemNode = $createListItemNode;
1858
2110
  exports.$createListNode = $createListNode;
1859
2111
  exports.$getListDepth = $getListDepth;
@@ -1867,8 +2119,11 @@ exports.INSERT_CHECK_LIST_COMMAND = INSERT_CHECK_LIST_COMMAND;
1867
2119
  exports.INSERT_ORDERED_LIST_COMMAND = INSERT_ORDERED_LIST_COMMAND;
1868
2120
  exports.INSERT_UNORDERED_LIST_COMMAND = INSERT_UNORDERED_LIST_COMMAND;
1869
2121
  exports.ListExtension = ListExtension;
2122
+ exports.ListImportExtension = ListImportExtension;
2123
+ exports.ListImportRules = ListImportRules;
1870
2124
  exports.ListItemNode = ListItemNode;
1871
2125
  exports.ListNode = ListNode;
2126
+ exports.ListSchema = ListSchema;
1872
2127
  exports.REMOVE_LIST_COMMAND = REMOVE_LIST_COMMAND;
1873
2128
  exports.UPDATE_LIST_START_COMMAND = UPDATE_LIST_START_COMMAND;
1874
2129
  exports.insertList = insertList;