@byline/richtext-lexical 5.2.0 → 6.0.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.
Files changed (123) hide show
  1. package/dist/config.d.ts +1 -1
  2. package/dist/field/apply-value-plugin.js +31 -6
  3. package/dist/field/capabilities/filter-transformers.d.ts +29 -0
  4. package/dist/field/capabilities/filter-transformers.js +8 -0
  5. package/dist/field/capabilities/registered-node-types.d.ts +29 -0
  6. package/dist/field/capabilities/registered-node-types.js +7 -0
  7. package/dist/field/config/built-in-extension-names.d.ts +42 -0
  8. package/dist/field/config/built-in-extension-names.js +12 -2
  9. package/dist/field/config/default-extensions.js +4 -0
  10. package/dist/field/config/default.js +8 -8
  11. package/dist/field/config/editor-config-context.d.ts +2 -2
  12. package/dist/field/config/editor-config-context.js +10 -13
  13. package/dist/field/config/resolve-editor-config.js +3 -3
  14. package/dist/field/config/root-dependencies.d.ts +26 -0
  15. package/dist/field/config/root-dependencies.js +8 -0
  16. package/dist/field/config/types.d.ts +61 -8
  17. package/dist/field/editor-context.js +44 -27
  18. package/dist/field/editor.js +15 -8
  19. package/dist/field/extensions/core-nodes/core-nodes-extension.d.ts +24 -0
  20. package/dist/field/extensions/core-nodes/core-nodes-extension.js +11 -0
  21. package/dist/field/extensions/heading/heading-extension.d.ts +20 -0
  22. package/dist/field/extensions/heading/heading-extension.js +9 -0
  23. package/dist/field/extensions/quote/quote-extension.d.ts +16 -0
  24. package/dist/field/extensions/quote/quote-extension.js +9 -0
  25. package/dist/field/hooks/use-markdown-toggle.d.ts +15 -0
  26. package/dist/field/hooks/use-markdown-toggle.js +12 -4
  27. package/dist/field/hooks/use-platform-modifier.d.ts +16 -0
  28. package/dist/field/hooks/use-platform-modifier.js +8 -0
  29. package/dist/field/markdown/transformers.d.ts +1 -0
  30. package/dist/field/markdown/transformers.js +5 -4
  31. package/dist/field/nodes/index.d.ts +11 -1
  32. package/dist/field/nodes/index.js +2 -2
  33. package/dist/field/normalize/adapted-notice.css +34 -0
  34. package/dist/field/normalize/adapted-notice.d.ts +29 -0
  35. package/dist/field/normalize/adapted-notice.js +35 -0
  36. package/dist/field/normalize/content-labels.d.ts +15 -0
  37. package/dist/field/normalize/content-labels.js +19 -0
  38. package/dist/field/normalize/declared-conversions.d.ts +131 -0
  39. package/dist/field/normalize/declared-conversions.js +93 -0
  40. package/dist/field/normalize/normalization-status.d.ts +33 -0
  41. package/dist/field/normalize/normalization-status.js +28 -0
  42. package/dist/field/normalize/normalize-value.d.ts +38 -0
  43. package/dist/field/normalize/normalize-value.js +132 -0
  44. package/dist/field/normalize/use-notice-text.d.ts +11 -0
  45. package/dist/field/normalize/use-notice-text.js +34 -0
  46. package/dist/field/plugins/toolbar-plugin/index.d.ts +23 -0
  47. package/dist/field/plugins/toolbar-plugin/index.js +104 -78
  48. package/dist/field/test-support/build-test-editor.js +49 -0
  49. package/dist/field/test-support/capture-editor.js +7 -0
  50. package/dist/field/test-support/frame-control.js +40 -0
  51. package/dist/field/test-support/jsdom-setup.js +21 -0
  52. package/dist/index.d.ts +7 -1
  53. package/dist/index.js +6 -1
  54. package/dist/scan/capability-manifest.d.ts +30 -0
  55. package/dist/scan/capability-manifest.js +43 -0
  56. package/dist/scan/scan-documents.d.ts +29 -0
  57. package/dist/scan/scan-documents.js +39 -0
  58. package/dist/scan/types.d.ts +63 -0
  59. package/dist/scan/types.js +1 -0
  60. package/dist/scan.d.ts +25 -0
  61. package/dist/scan.js +1 -0
  62. package/package.json +14 -5
  63. package/src/config.ts +1 -1
  64. package/src/field/apply-value-plugin.tsx +71 -5
  65. package/src/field/capabilities/filter-transformers.test.tsx +71 -0
  66. package/src/field/capabilities/filter-transformers.ts +42 -0
  67. package/src/field/capabilities/markdown-behaviour.test.tsx +188 -0
  68. package/src/field/capabilities/no-blanket-registration.test.tsx +86 -0
  69. package/src/field/capabilities/registered-node-types.test.tsx +29 -0
  70. package/src/field/capabilities/registered-node-types.ts +36 -0
  71. package/src/field/config/built-in-extension-names.test.node.ts +38 -14
  72. package/src/field/config/built-in-extension-names.ts +29 -1
  73. package/src/field/config/default-extensions.ts +6 -0
  74. package/src/field/config/default.ts +8 -8
  75. package/src/field/config/editor-config-context.tsx +14 -9
  76. package/src/field/config/list-removal.test.tsx +44 -0
  77. package/src/field/config/resolve-editor-config.test.node.ts +27 -6
  78. package/src/field/config/resolve-editor-config.ts +3 -3
  79. package/src/field/config/root-dependencies.ts +32 -0
  80. package/src/field/config/types.ts +61 -17
  81. package/src/field/editor-component.test.tsx +18 -3
  82. package/src/field/editor-context.regression.test.tsx +124 -0
  83. package/src/field/editor-context.tsx +90 -22
  84. package/src/field/editor.tsx +22 -12
  85. package/src/field/extensions/core-nodes/core-nodes-extension.test.tsx +13 -0
  86. package/src/field/extensions/core-nodes/core-nodes-extension.ts +32 -0
  87. package/src/field/extensions/heading/heading-extension.test.tsx +32 -0
  88. package/src/field/extensions/heading/heading-extension.ts +27 -0
  89. package/src/field/extensions/quote/quote-extension.ts +23 -0
  90. package/src/field/hooks/markdown-source-mode.test.tsx +221 -0
  91. package/src/field/hooks/use-markdown-toggle.ts +39 -4
  92. package/src/field/hooks/use-platform-modifier.test.tsx +81 -0
  93. package/src/field/hooks/use-platform-modifier.ts +41 -0
  94. package/src/field/markdown/transformers.ts +18 -4
  95. package/src/field/nodes/index.ts +11 -1
  96. package/src/field/normalize/adapted-notice.css +52 -0
  97. package/src/field/normalize/adapted-notice.tsx +63 -0
  98. package/src/field/normalize/clipboard-equivalence.test.tsx +330 -0
  99. package/src/field/normalize/content-labels.ts +44 -0
  100. package/src/field/normalize/declared-conversions.test.node.ts +67 -0
  101. package/src/field/normalize/declared-conversions.ts +160 -0
  102. package/src/field/normalize/emission-gate.test.tsx +302 -0
  103. package/src/field/normalize/normalization-status.tsx +48 -0
  104. package/src/field/normalize/normalize-load.test.tsx +371 -0
  105. package/src/field/normalize/normalize-value.test.node.ts +530 -0
  106. package/src/field/normalize/normalize-value.ts +180 -0
  107. package/src/field/normalize/notice-i18n.test.tsx +128 -0
  108. package/src/field/normalize/use-notice-text.ts +82 -0
  109. package/src/field/normalize/wiring.test.tsx +348 -0
  110. package/src/field/plugins/toolbar-plugin/block-format-capabilities.test.tsx +57 -0
  111. package/src/field/plugins/toolbar-plugin/index.tsx +131 -68
  112. package/src/field/test-support/build-test-editor.test.tsx +17 -0
  113. package/src/field/test-support/build-test-editor.ts +99 -0
  114. package/src/field/test-support/capture-editor.tsx +20 -0
  115. package/src/field/test-support/frame-control.ts +91 -0
  116. package/src/field/test-support/jsdom-setup.ts +41 -0
  117. package/src/index.ts +12 -1
  118. package/src/scan/capability-manifest.test.tsx +82 -0
  119. package/src/scan/capability-manifest.ts +81 -0
  120. package/src/scan/scan-documents.test.node.ts +125 -0
  121. package/src/scan/scan-documents.ts +78 -0
  122. package/src/scan/types.ts +66 -0
  123. package/src/scan.ts +27 -0
@@ -2,11 +2,11 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Fragment as external_react_Fragment, useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { $createCodeNode, $isCodeNode, CODE_LANGUAGE_FRIENDLY_NAME_MAP, CODE_LANGUAGE_MAP, CodeNode, getLanguageFriendlyName } from "@lexical/code";
5
- import { $isListNode, CheckListExtension, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListExtension, ListNode, REMOVE_LIST_COMMAND } from "@lexical/list";
5
+ import { $isListNode, CheckListExtension, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode, REMOVE_LIST_COMMAND } from "@lexical/list";
6
6
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
7
7
  import { $isDecoratorBlockNode } from "@lexical/react/LexicalDecoratorBlockNode";
8
8
  import { useExtensionDependency, useOptionalExtensionDependency } from "@lexical/react/useExtensionComponent";
9
- import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode } from "@lexical/rich-text";
9
+ import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode } from "@lexical/rich-text";
10
10
  import { $setBlocksType } from "@lexical/selection";
11
11
  import { $isTableNode } from "@lexical/table";
12
12
  import { $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, mergeRegister } from "@lexical/utils";
@@ -14,8 +14,8 @@ import { $createParagraphNode, $getNodeByKey, $getSelection, $isRangeSelection,
14
14
  import { useEditorConfig } from "../../config/editor-config-context.js";
15
15
  import { BylineToolbarExtension, selectToolbarItems } from "../../extensions/byline-toolbar/byline-toolbar-extension.js";
16
16
  import { $isLinkNode, LinkExtension, OPEN_LINK_MODAL_COMMAND, TOGGLE_LINK_COMMAND } from "../../extensions/link/index.js";
17
- import { useMarkdownToggle } from "../../hooks/use-markdown-toggle.js";
18
- import { IS_APPLE } from "../../shared/environment.js";
17
+ import { canUseMarkdownSourceMode, useMarkdownToggle } from "../../hooks/use-markdown-toggle.js";
18
+ import { useIsApplePlatform } from "../../hooks/use-platform-modifier.js";
19
19
  import { DropDown, DropDownItem } from "../../ui/dropdown.js";
20
20
  import { getSelectedNode } from "../../utils/getSelectedNode.js";
21
21
  import { sanitizeUrl } from "../../utils/url.js";
@@ -47,13 +47,32 @@ function dropDownActiveClass(active) {
47
47
  if (active) return 'active dropdown-item-active';
48
48
  return '';
49
49
  }
50
+ function availableBlockFormats(editor, hasCheckListBehaviour) {
51
+ const formats = [
52
+ 'paragraph'
53
+ ];
54
+ if (editor.hasNodes([
55
+ HeadingNode
56
+ ])) formats.push('h1', 'h2', 'h3', 'h4');
57
+ if (editor.hasNodes([
58
+ ListNode,
59
+ ListItemNode
60
+ ])) {
61
+ formats.push('bullet', 'number');
62
+ if (hasCheckListBehaviour) formats.push('check');
63
+ }
64
+ if (editor.hasNodes([
65
+ QuoteNode
66
+ ])) formats.push('quote');
67
+ if (editor.hasNodes([
68
+ CodeNode
69
+ ])) formats.push('code');
70
+ return formats;
71
+ }
50
72
  function BlockFormatDropDown({ editor, blockType, rootType, disabled = false }) {
51
- const hasList = void 0 !== useOptionalExtensionDependency(ListExtension);
52
- const hasCheckList = void 0 !== useOptionalExtensionDependency(CheckListExtension);
53
73
  const [composerEditor] = useLexicalComposerContext();
54
- const hasCodeHighlight = composerEditor.hasNodes([
55
- CodeNode
56
- ]);
74
+ const hasCheckListBehaviour = void 0 !== useOptionalExtensionDependency(CheckListExtension);
75
+ const formats = availableBlockFormats(composerEditor, hasCheckListBehaviour);
57
76
  const formatParagraph = ()=>{
58
77
  editor.update(()=>{
59
78
  const selection = $getSelection();
@@ -99,6 +118,7 @@ function BlockFormatDropDown({ editor, blockType, rootType, disabled = false })
99
118
  }
100
119
  });
101
120
  };
121
+ if (1 === formats.length) return null;
102
122
  return /*#__PURE__*/ jsxs(DropDown, {
103
123
  disabled: disabled,
104
124
  buttonClassName: "toolbar-item block-controls",
@@ -119,67 +139,71 @@ function BlockFormatDropDown({ editor, blockType, rootType, disabled = false })
119
139
  })
120
140
  ]
121
141
  }),
122
- /*#__PURE__*/ jsxs(DropDownItem, {
123
- className: `item ${dropDownActiveClass('h1' === blockType)}`,
124
- onClick: ()=>{
125
- formatHeading('h1');
126
- },
142
+ formats.includes('h1') && /*#__PURE__*/ jsxs(Fragment, {
127
143
  children: [
128
- /*#__PURE__*/ jsx("i", {
129
- className: "icon h1"
130
- }),
131
- /*#__PURE__*/ jsx("span", {
132
- className: "text",
133
- children: "Heading 1"
134
- })
135
- ]
136
- }),
137
- /*#__PURE__*/ jsxs(DropDownItem, {
138
- className: `item ${dropDownActiveClass('h2' === blockType)}`,
139
- onClick: ()=>{
140
- formatHeading('h2');
141
- },
142
- children: [
143
- /*#__PURE__*/ jsx("i", {
144
- className: "icon h2"
144
+ /*#__PURE__*/ jsxs(DropDownItem, {
145
+ className: `item ${dropDownActiveClass('h1' === blockType)}`,
146
+ onClick: ()=>{
147
+ formatHeading('h1');
148
+ },
149
+ children: [
150
+ /*#__PURE__*/ jsx("i", {
151
+ className: "icon h1"
152
+ }),
153
+ /*#__PURE__*/ jsx("span", {
154
+ className: "text",
155
+ children: "Heading 1"
156
+ })
157
+ ]
145
158
  }),
146
- /*#__PURE__*/ jsx("span", {
147
- className: "text",
148
- children: "Heading 2"
149
- })
150
- ]
151
- }),
152
- /*#__PURE__*/ jsxs(DropDownItem, {
153
- className: `item ${dropDownActiveClass('h3' === blockType)}`,
154
- onClick: ()=>{
155
- formatHeading('h3');
156
- },
157
- children: [
158
- /*#__PURE__*/ jsx("i", {
159
- className: "icon h3"
159
+ /*#__PURE__*/ jsxs(DropDownItem, {
160
+ className: `item ${dropDownActiveClass('h2' === blockType)}`,
161
+ onClick: ()=>{
162
+ formatHeading('h2');
163
+ },
164
+ children: [
165
+ /*#__PURE__*/ jsx("i", {
166
+ className: "icon h2"
167
+ }),
168
+ /*#__PURE__*/ jsx("span", {
169
+ className: "text",
170
+ children: "Heading 2"
171
+ })
172
+ ]
160
173
  }),
161
- /*#__PURE__*/ jsx("span", {
162
- className: "text",
163
- children: "Heading 3"
164
- })
165
- ]
166
- }),
167
- /*#__PURE__*/ jsxs(DropDownItem, {
168
- className: `item ${dropDownActiveClass('h4' === blockType)}`,
169
- onClick: ()=>{
170
- formatHeading('h4');
171
- },
172
- children: [
173
- /*#__PURE__*/ jsx("i", {
174
- className: "icon h4"
174
+ /*#__PURE__*/ jsxs(DropDownItem, {
175
+ className: `item ${dropDownActiveClass('h3' === blockType)}`,
176
+ onClick: ()=>{
177
+ formatHeading('h3');
178
+ },
179
+ children: [
180
+ /*#__PURE__*/ jsx("i", {
181
+ className: "icon h3"
182
+ }),
183
+ /*#__PURE__*/ jsx("span", {
184
+ className: "text",
185
+ children: "Heading 3"
186
+ })
187
+ ]
175
188
  }),
176
- /*#__PURE__*/ jsx("span", {
177
- className: "text",
178
- children: "Heading 4"
189
+ /*#__PURE__*/ jsxs(DropDownItem, {
190
+ className: `item ${dropDownActiveClass('h4' === blockType)}`,
191
+ onClick: ()=>{
192
+ formatHeading('h4');
193
+ },
194
+ children: [
195
+ /*#__PURE__*/ jsx("i", {
196
+ className: "icon h4"
197
+ }),
198
+ /*#__PURE__*/ jsx("span", {
199
+ className: "text",
200
+ children: "Heading 4"
201
+ })
202
+ ]
179
203
  })
180
204
  ]
181
205
  }),
182
- hasList && /*#__PURE__*/ jsxs(Fragment, {
206
+ formats.includes('bullet') && /*#__PURE__*/ jsxs(Fragment, {
183
207
  children: [
184
208
  /*#__PURE__*/ jsxs(DropDownItem, {
185
209
  className: `item ${dropDownActiveClass('bullet' === blockType)}`,
@@ -209,7 +233,7 @@ function BlockFormatDropDown({ editor, blockType, rootType, disabled = false })
209
233
  })
210
234
  ]
211
235
  }),
212
- hasCheckList && /*#__PURE__*/ jsxs(DropDownItem, {
236
+ formats.includes('check') && /*#__PURE__*/ jsxs(DropDownItem, {
213
237
  className: `item ${dropDownActiveClass('check' === blockType)}`,
214
238
  onClick: formatCheckList,
215
239
  children: [
@@ -222,7 +246,7 @@ function BlockFormatDropDown({ editor, blockType, rootType, disabled = false })
222
246
  })
223
247
  ]
224
248
  }),
225
- /*#__PURE__*/ jsxs(DropDownItem, {
249
+ formats.includes('quote') && /*#__PURE__*/ jsxs(DropDownItem, {
226
250
  className: `item ${dropDownActiveClass('quote' === blockType)}`,
227
251
  onClick: formatQuote,
228
252
  children: [
@@ -235,7 +259,7 @@ function BlockFormatDropDown({ editor, blockType, rootType, disabled = false })
235
259
  })
236
260
  ]
237
261
  }),
238
- hasCodeHighlight && /*#__PURE__*/ jsxs(DropDownItem, {
262
+ formats.includes('code') && /*#__PURE__*/ jsxs(DropDownItem, {
239
263
  className: `item ${dropDownActiveClass('code' === blockType)}`,
240
264
  onClick: formatCode,
241
265
  children: [
@@ -284,8 +308,10 @@ function ToolbarPlugin() {
284
308
  toolbarConfig.items
285
309
  ]);
286
310
  const hasLinkExtension = void 0 !== useOptionalExtensionDependency(LinkExtension);
287
- const { uuid, config: { options: { textAlignment, undoRedo, textStyle, inlineCode, markdownToggle } } } = useEditorConfig();
311
+ const { uuid, config: { controls: { textAlignment, undoRedo, blockFormat, inlineCode, markdownToggle } } } = useEditorConfig();
288
312
  const { isMarkdown, toggleMarkdown } = useMarkdownToggle();
313
+ const isApple = useIsApplePlatform();
314
+ const canToggleMarkdown = canUseMarkdownSourceMode(editor);
289
315
  const $updateToolbar = useCallback(()=>{
290
316
  const selection = $getSelection();
291
317
  if ($isRangeSelection(selection)) {
@@ -434,7 +460,7 @@ function ToolbarPlugin() {
434
460
  onClick: ()=>{
435
461
  activeEditor.dispatchCommand(UNDO_COMMAND, void 0);
436
462
  },
437
- title: IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)',
463
+ title: isApple ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)',
438
464
  type: "button",
439
465
  className: "toolbar-item spaced",
440
466
  "aria-label": "Undo",
@@ -447,7 +473,7 @@ function ToolbarPlugin() {
447
473
  onClick: ()=>{
448
474
  activeEditor.dispatchCommand(REDO_COMMAND, void 0);
449
475
  },
450
- title: IS_APPLE ? 'Redo (⌘Y)' : 'Redo (Ctrl+Y)',
476
+ title: isApple ? 'Redo (⌘Y)' : 'Redo (Ctrl+Y)',
451
477
  type: "button",
452
478
  className: "toolbar-item",
453
479
  "aria-label": "Redo",
@@ -460,7 +486,7 @@ function ToolbarPlugin() {
460
486
  }),
461
487
  blockType in blockTypeToBlockName && activeEditor === editor && /*#__PURE__*/ jsxs(Fragment, {
462
488
  children: [
463
- textStyle && /*#__PURE__*/ jsxs(Fragment, {
489
+ blockFormat && /*#__PURE__*/ jsxs(Fragment, {
464
490
  children: [
465
491
  /*#__PURE__*/ jsx(BlockFormatDropDown, {
466
492
  disabled: !isEditable,
@@ -601,9 +627,9 @@ function ToolbarPlugin() {
601
627
  activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
602
628
  },
603
629
  className: `toolbar-item spaced ${isBold ? 'active' : ''}`,
604
- title: IS_APPLE ? 'Bold (⌘B)' : 'Bold (Ctrl+B)',
630
+ title: isApple ? 'Bold (⌘B)' : 'Bold (Ctrl+B)',
605
631
  type: "button",
606
- "aria-label": `Format text as bold. Shortcut: ${IS_APPLE ? '⌘B' : 'Ctrl+B'}`,
632
+ "aria-label": `Format text as bold. Shortcut: ${isApple ? '⌘B' : 'Ctrl+B'}`,
607
633
  children: /*#__PURE__*/ jsx("i", {
608
634
  className: "format bold"
609
635
  })
@@ -614,9 +640,9 @@ function ToolbarPlugin() {
614
640
  activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
615
641
  },
616
642
  className: `toolbar-item spaced ${isItalic ? 'active' : ''}`,
617
- title: IS_APPLE ? 'Italic (⌘I)' : 'Italic (Ctrl+I)',
643
+ title: isApple ? 'Italic (⌘I)' : 'Italic (Ctrl+I)',
618
644
  type: "button",
619
- "aria-label": `Format text as italics. Shortcut: ${IS_APPLE ? '⌘I' : 'Ctrl+I'}`,
645
+ "aria-label": `Format text as italics. Shortcut: ${isApple ? '⌘I' : 'Ctrl+I'}`,
620
646
  children: /*#__PURE__*/ jsx("i", {
621
647
  className: "format italic"
622
648
  })
@@ -627,9 +653,9 @@ function ToolbarPlugin() {
627
653
  activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
628
654
  },
629
655
  className: `toolbar-item spaced ${isUnderline ? 'active' : ''}`,
630
- title: IS_APPLE ? 'Underline (⌘U)' : 'Underline (Ctrl+U)',
656
+ title: isApple ? 'Underline (⌘U)' : 'Underline (Ctrl+U)',
631
657
  type: "button",
632
- "aria-label": `Format text to underlined. Shortcut: ${IS_APPLE ? '⌘U' : 'Ctrl+U'}`,
658
+ "aria-label": `Format text to underlined. Shortcut: ${isApple ? '⌘U' : 'Ctrl+U'}`,
633
659
  children: /*#__PURE__*/ jsx("i", {
634
660
  className: "format underline"
635
661
  })
@@ -759,7 +785,7 @@ function ToolbarPlugin() {
759
785
  children: item.node
760
786
  }, item.id))
761
787
  }),
762
- markdownToggle && /*#__PURE__*/ jsxs(Fragment, {
788
+ markdownToggle && canToggleMarkdown && /*#__PURE__*/ jsxs(Fragment, {
763
789
  children: [
764
790
  /*#__PURE__*/ jsx(Divider, {}),
765
791
  /*#__PURE__*/ jsx("button", {
@@ -780,4 +806,4 @@ function ToolbarPlugin() {
780
806
  ]
781
807
  });
782
808
  }
783
- export { ToolbarPlugin };
809
+ export { ToolbarPlugin, availableBlockFormats };
@@ -0,0 +1,49 @@
1
+ import { buildEditorFromExtensions } from "@lexical/extension";
2
+ import { $generateNodesFromDOM } from "@lexical/html";
3
+ import { ReactPluginHostExtension } from "@lexical/react/ReactPluginHostExtension";
4
+ import { defineExtension } from "lexical";
5
+ import { builtInExtensions } from "../config/built-in-extension-names.js";
6
+ import { defaultExtensionsList } from "../config/default-extensions.js";
7
+ import { rootDependencies } from "../config/root-dependencies.js";
8
+ const NAMESPACE = 'LexicalRichText';
9
+ function build(extensions) {
10
+ return buildEditorFromExtensions(defineExtension({
11
+ name: '[test-root]',
12
+ namespace: NAMESPACE,
13
+ dependencies: [
14
+ ReactPluginHostExtension,
15
+ ...rootDependencies(extensions)
16
+ ]
17
+ }));
18
+ }
19
+ function buildFullEditor() {
20
+ return build(defaultExtensionsList());
21
+ }
22
+ function buildRestrictedEditor(removals) {
23
+ const names = removals ?? [
24
+ ...Object.values(builtInExtensions),
25
+ '@lexical/list/List',
26
+ '@lexical/list/CheckList'
27
+ ];
28
+ const list = defaultExtensionsList();
29
+ for (const name of names)list.remove(name);
30
+ return build(list);
31
+ }
32
+ function parseHtmlToTypes(editor, html) {
33
+ const dom = new DOMParser().parseFromString(html, 'text/html');
34
+ let result = {
35
+ types: [],
36
+ text: ''
37
+ };
38
+ editor.update(()=>{
39
+ const nodes = $generateNodesFromDOM(editor, dom);
40
+ result = {
41
+ types: nodes.map((node)=>node.getType()),
42
+ text: nodes.map((node)=>node.getTextContent()).join('|')
43
+ };
44
+ }, {
45
+ discrete: true
46
+ });
47
+ return result;
48
+ }
49
+ export { buildFullEditor, buildRestrictedEditor, parseHtmlToTypes };
@@ -0,0 +1,7 @@
1
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
2
+ function CaptureEditor({ onEditor }) {
3
+ const [editor] = useLexicalComposerContext();
4
+ onEditor(editor);
5
+ return null;
6
+ }
7
+ export { CaptureEditor };
@@ -0,0 +1,40 @@
1
+ function installFrameControl() {
2
+ const frameCallbacks = [];
3
+ const idleCallbacks = [];
4
+ const originalRaf = globalThis.requestAnimationFrame;
5
+ const originalCaf = globalThis.cancelAnimationFrame;
6
+ const originalRic = globalThis.requestIdleCallback;
7
+ const originalCic = globalThis.cancelIdleCallback;
8
+ globalThis.requestAnimationFrame = (callback)=>{
9
+ frameCallbacks.push(callback);
10
+ return frameCallbacks.length;
11
+ };
12
+ globalThis.cancelAnimationFrame = ()=>{};
13
+ globalThis.requestIdleCallback = (callback)=>{
14
+ idleCallbacks.push(callback);
15
+ return idleCallbacks.length;
16
+ };
17
+ globalThis.cancelIdleCallback = ()=>{};
18
+ const flushFrames = async (generations = 1)=>{
19
+ for(let generation = 0; generation < generations; generation++){
20
+ await Promise.resolve();
21
+ for (const callback of frameCallbacks.splice(0))callback(performance.now());
22
+ }
23
+ await Promise.resolve();
24
+ };
25
+ return {
26
+ flushFrames,
27
+ flushIdle: ()=>{
28
+ for (const callback of idleCallbacks.splice(0))callback();
29
+ },
30
+ pendingIdle: ()=>idleCallbacks.length,
31
+ settleBaseline: ()=>flushFrames(2),
32
+ restore: ()=>{
33
+ globalThis.requestAnimationFrame = originalRaf;
34
+ globalThis.cancelAnimationFrame = originalCaf;
35
+ globalThis.requestIdleCallback = originalRic;
36
+ globalThis.cancelIdleCallback = originalCic;
37
+ }
38
+ };
39
+ }
40
+ export { installFrameControl };
@@ -0,0 +1,21 @@
1
+ if ("u" > typeof window) {
2
+ if ('function' != typeof window.matchMedia) Object.defineProperty(window, 'matchMedia', {
3
+ writable: true,
4
+ value: (query)=>({
5
+ matches: false,
6
+ media: query,
7
+ onchange: null,
8
+ addListener: ()=>{},
9
+ removeListener: ()=>{},
10
+ addEventListener: ()=>{},
11
+ removeEventListener: ()=>{},
12
+ dispatchEvent: ()=>false
13
+ })
14
+ });
15
+ if (void 0 === globalThis.ResizeObserver) globalThis.ResizeObserver = class {
16
+ observe() {}
17
+ unobserve() {}
18
+ disconnect() {}
19
+ };
20
+ }
21
+ export { };
package/dist/index.d.ts CHANGED
@@ -9,20 +9,26 @@ export { AutoEmbedExtension } from './field/extensions/auto-embed/auto-embed-ext
9
9
  export { type BylineFloatingUIConfig, BylineFloatingUIExtension, type BylineFloatingUIItem, type BylineFloatingUIProps, selectFloatingUIItems, } from './field/extensions/byline-floating-ui/index.js';
10
10
  export { type BylineToolbarConfig, BylineToolbarExtension, type BylineToolbarItem, type BylineToolbarPlacement, selectToolbarItems, } from './field/extensions/byline-toolbar/index.js';
11
11
  export { CodeHighlightExtension } from './field/extensions/code-highlight/code-highlight-extension.js';
12
+ export { CoreNodesExtension } from './field/extensions/core-nodes/core-nodes-extension.js';
12
13
  export { FloatingTextFormatExtension } from './field/extensions/floating-text-format/floating-text-format-extension.js';
14
+ export { HeadingExtension } from './field/extensions/heading/heading-extension.js';
13
15
  export { HorizontalRuleExtension } from './field/extensions/horizontal-rule/horizontal-rule-extension.js';
14
16
  export { type InlineImageConfig, InlineImageExtension, } from './field/extensions/inline-image/inline-image-extension.js';
15
17
  export { LayoutExtension } from './field/extensions/layout/layout-extension.js';
16
18
  export { AutoLinkExtension, LinkExtension } from './field/extensions/link/index.js';
19
+ export { QuoteExtension } from './field/extensions/quote/quote-extension.js';
17
20
  export { TableExtension } from './field/extensions/table/table-extension.js';
18
21
  export { VimeoExtension } from './field/extensions/vimeo/vimeo-extension.js';
19
22
  export { YouTubeExtension } from './field/extensions/youtube/youtube-extension.js';
20
- export { Nodes } from './field/nodes/index.js';
23
+ export { READABLE_NODES } from './field/nodes/index.js';
21
24
  export { ToolbarActiveEditorProvider, useToolbarActiveEditor, } from './field/plugins/toolbar-plugin/toolbar-active-editor.js';
22
25
  export { lexicalEditor } from './lexical-editor.js';
23
26
  export { RichTextField } from './richtext-field.js';
27
+ export { buildManifest, capabilitiesFor, capabilitiesFromEditor } from './scan/capability-manifest.js';
28
+ export { scanDocument, summarise } from './scan/scan-documents.js';
24
29
  export { createEmptyEditorState } from './validate/createEmptyEditorState.js';
25
30
  export { hasText } from './validate/hasText.js';
26
31
  export type { EditorConfig, EditorSettings, EditorSettingsOverride } from './field/config/types.js';
27
32
  export type { InlineImageAttributes, Position as InlineImagePosition, SerializedInlineImageNode, } from './field/extensions/inline-image/node-types.js';
28
33
  export type { DocumentRelation } from './field/nodes/document-relation.js';
34
+ export type { CapabilityManifest, FieldCapabilities, ScanFinding } from './scan/types.js';
package/dist/index.js CHANGED
@@ -8,17 +8,22 @@ export { AutoEmbedExtension } from "./field/extensions/auto-embed/auto-embed-ext
8
8
  export { BylineFloatingUIExtension, selectFloatingUIItems } from "./field/extensions/byline-floating-ui/index.js";
9
9
  export { BylineToolbarExtension, selectToolbarItems } from "./field/extensions/byline-toolbar/index.js";
10
10
  export { CodeHighlightExtension } from "./field/extensions/code-highlight/code-highlight-extension.js";
11
+ export { CoreNodesExtension } from "./field/extensions/core-nodes/core-nodes-extension.js";
11
12
  export { FloatingTextFormatExtension } from "./field/extensions/floating-text-format/floating-text-format-extension.js";
13
+ export { HeadingExtension } from "./field/extensions/heading/heading-extension.js";
12
14
  export { HorizontalRuleExtension } from "./field/extensions/horizontal-rule/horizontal-rule-extension.js";
13
15
  export { InlineImageExtension } from "./field/extensions/inline-image/inline-image-extension.js";
14
16
  export { LayoutExtension } from "./field/extensions/layout/layout-extension.js";
15
17
  export { AutoLinkExtension, LinkExtension } from "./field/extensions/link/index.js";
18
+ export { QuoteExtension } from "./field/extensions/quote/quote-extension.js";
16
19
  export { TableExtension } from "./field/extensions/table/table-extension.js";
17
20
  export { VimeoExtension } from "./field/extensions/vimeo/vimeo-extension.js";
18
21
  export { YouTubeExtension } from "./field/extensions/youtube/youtube-extension.js";
19
- export { Nodes } from "./field/nodes/index.js";
22
+ export { READABLE_NODES } from "./field/nodes/index.js";
20
23
  export { ToolbarActiveEditorProvider, useToolbarActiveEditor } from "./field/plugins/toolbar-plugin/toolbar-active-editor.js";
21
24
  export { lexicalEditor } from "./lexical-editor.js";
22
25
  export { RichTextField } from "./richtext-field.js";
26
+ export { buildManifest, capabilitiesFor, capabilitiesFromEditor } from "./scan/capability-manifest.js";
27
+ export { scanDocument, summarise } from "./scan/scan-documents.js";
23
28
  export { createEmptyEditorState } from "./validate/createEmptyEditorState.js";
24
29
  export { hasText } from "./validate/hasText.js";
@@ -0,0 +1,30 @@
1
+ import { registeredNodeTypes } from '../field/capabilities/registered-node-types.js';
2
+ import type { EditorConfig } from '../field/config/types.js';
3
+ import type { CapabilityManifest, FieldCapabilities } from './types.js';
4
+ /**
5
+ * What one field accepts, measured by BUILDING its editor.
6
+ *
7
+ * Deliberately not derived by walking declared `nodes` on the extension
8
+ * list: that would recreate the resolver — dependency-contributed nodes,
9
+ * conflicts, replacements and always-on core nodes — and a manifest that
10
+ * disagrees with the runtime is worse than no manifest. This is the same
11
+ * construction `EditorContext` performs, so the two cannot drift.
12
+ *
13
+ * Requires a DOM and a React-capable module graph, because several
14
+ * extensions host React decorators. It therefore runs in a browser or
15
+ * jsdom, never in a plain Node script — which is why the manifest is a
16
+ * file handed to the scanner rather than something the scanner derives.
17
+ */
18
+ export declare function capabilitiesFor(collectionPath: string, fieldPath: string, config: Pick<EditorConfig, 'extensions' | 'lexical'>): FieldCapabilities;
19
+ /** Assemble a manifest from per-field capabilities. */
20
+ export declare function buildManifest(fields: FieldCapabilities[]): CapabilityManifest;
21
+ /**
22
+ * What a live editor accepts.
23
+ *
24
+ * `capabilitiesFor` needs a resolved `EditorConfig`, which is not always
25
+ * reachable: `lexicalEditor()` closes over its configure callback inside
26
+ * a lazy component, so a site's own preset exposes no config to inspect.
27
+ * Measuring a mounted editor works for any field however its editor was
28
+ * registered, which is what a manifest walk over real collections needs.
29
+ */
30
+ export declare function capabilitiesFromEditor(collectionPath: string, fieldPath: string, editor: Parameters<typeof registeredNodeTypes>[0]): FieldCapabilities;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { buildEditorFromExtensions } from "@lexical/extension";
3
+ import { ReactPluginHostExtension } from "@lexical/react/ReactPluginHostExtension";
4
+ import { defineExtension } from "lexical";
5
+ import { registeredNodeTypes } from "../field/capabilities/registered-node-types.js";
6
+ import { defaultExtensionsList } from "../field/config/default-extensions.js";
7
+ import { rootDependencies } from "../field/config/root-dependencies.js";
8
+ function capabilitiesFor(collectionPath, fieldPath, config) {
9
+ const extensions = config.extensions ?? defaultExtensionsList();
10
+ const editor = buildEditorFromExtensions(defineExtension({
11
+ name: '[capability-manifest]',
12
+ namespace: config.lexical?.namespace ?? 'LexicalRichText',
13
+ dependencies: [
14
+ ReactPluginHostExtension,
15
+ ...rootDependencies(extensions)
16
+ ]
17
+ }));
18
+ const supportedTypes = [
19
+ ...registeredNodeTypes(editor)
20
+ ].sort();
21
+ editor.dispose();
22
+ return {
23
+ collectionPath,
24
+ fieldPath,
25
+ supportedTypes
26
+ };
27
+ }
28
+ function buildManifest(fields) {
29
+ return {
30
+ generatedAt: new Date().toISOString(),
31
+ fields
32
+ };
33
+ }
34
+ function capabilitiesFromEditor(collectionPath, fieldPath, editor) {
35
+ return {
36
+ collectionPath,
37
+ fieldPath,
38
+ supportedTypes: [
39
+ ...registeredNodeTypes(editor)
40
+ ].sort()
41
+ };
42
+ }
43
+ export { buildManifest, capabilitiesFor, capabilitiesFromEditor };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { SerializedEditorState } from 'lexical';
9
+ import type { FieldCapabilities, ScanFinding } from './types.js';
10
+ /**
11
+ * Report what a field's current configuration will do with one stored
12
+ * value.
13
+ *
14
+ * Runs the same `normalizeValue` the editor runs, rather than a parallel
15
+ * rule set, so the report cannot drift from the behaviour it predicts.
16
+ * Pure: no database, no DOM, no editor.
17
+ */
18
+ export declare function scanDocument(value: SerializedEditorState | null | undefined, capabilities: FieldCapabilities, ids: {
19
+ documentId: string;
20
+ versionId: string;
21
+ locale?: string;
22
+ instancePath?: string;
23
+ }): ScanFinding | undefined;
24
+ /** Group findings for a report: what will adapt, and what will open read-only. */
25
+ export declare function summarise(findings: ScanFinding[]): {
26
+ adapted: ScanFinding[];
27
+ refused: ScanFinding[];
28
+ unmapped: ScanFinding[];
29
+ };
@@ -0,0 +1,39 @@
1
+ import { normalizeValue } from "../field/normalize/normalize-value.js";
2
+ function scanDocument(value, capabilities, ids) {
3
+ if (null == value) return;
4
+ if (null == capabilities.supportedTypes) {
5
+ const unmapped = {
6
+ collectionPath: capabilities.collectionPath,
7
+ fieldPath: capabilities.fieldPath,
8
+ documentId: ids.documentId,
9
+ versionId: ids.versionId,
10
+ adaptedTypes: [],
11
+ refusedTypes: [],
12
+ unmapped: true
13
+ };
14
+ if (null != ids.locale) unmapped.locale = ids.locale;
15
+ if (null != ids.instancePath) unmapped.instancePath = ids.instancePath;
16
+ return unmapped;
17
+ }
18
+ const result = normalizeValue(value, new Set(capabilities.supportedTypes));
19
+ if ('unchanged' === result.status) return;
20
+ const finding = {
21
+ collectionPath: capabilities.collectionPath,
22
+ fieldPath: capabilities.fieldPath,
23
+ documentId: ids.documentId,
24
+ versionId: ids.versionId,
25
+ adaptedTypes: 'adapted' === result.status ? result.convertedTypes : [],
26
+ refusedTypes: 'refused' === result.status ? result.unsupportedTypes : []
27
+ };
28
+ if (null != ids.locale) finding.locale = ids.locale;
29
+ if (null != ids.instancePath) finding.instancePath = ids.instancePath;
30
+ return finding;
31
+ }
32
+ function summarise(findings) {
33
+ return {
34
+ adapted: findings.filter((finding)=>true !== finding.unmapped && 0 === finding.refusedTypes.length),
35
+ refused: findings.filter((finding)=>true !== finding.unmapped && finding.refusedTypes.length > 0),
36
+ unmapped: findings.filter((finding)=>true === finding.unmapped)
37
+ };
38
+ }
39
+ export { scanDocument, summarise };