@byline/richtext-lexical 0.9.3
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.
- package/LICENSE +373 -0
- package/README.md +18 -0
- package/dist/field/apply-value-plugin.d.ts +10 -0
- package/dist/field/apply-value-plugin.d.ts.map +1 -0
- package/dist/field/apply-value-plugin.js +60 -0
- package/dist/field/config/default.d.ts +6 -0
- package/dist/field/config/default.d.ts.map +1 -0
- package/dist/field/config/default.js +53 -0
- package/dist/field/config/editor-config-context.d.ts +14 -0
- package/dist/field/config/editor-config-context.d.ts.map +1 -0
- package/dist/field/config/editor-config-context.js +51 -0
- package/dist/field/config/types.d.ts +28 -0
- package/dist/field/config/types.d.ts.map +1 -0
- package/dist/field/config/types.js +1 -0
- package/dist/field/constants.d.ts +2 -0
- package/dist/field/constants.d.ts.map +1 -0
- package/dist/field/constants.js +2 -0
- package/dist/field/content-editable-inline.d.ts +13 -0
- package/dist/field/content-editable-inline.d.ts.map +1 -0
- package/dist/field/content-editable-inline.js +11 -0
- package/dist/field/content-editable.css +23 -0
- package/dist/field/content-editable.d.ts +13 -0
- package/dist/field/content-editable.d.ts.map +1 -0
- package/dist/field/content-editable.js +14 -0
- package/dist/field/context/shared-autocomplete-context.js +44 -0
- package/dist/field/context/shared-history-context.d.ts +19 -0
- package/dist/field/context/shared-history-context.d.ts.map +1 -0
- package/dist/field/context/shared-history-context.js +16 -0
- package/dist/field/context/shared-on-change-context.d.ts +13 -0
- package/dist/field/context/shared-on-change-context.d.ts.map +1 -0
- package/dist/field/context/shared-on-change-context.js +21 -0
- package/dist/field/debug.d.ts +10 -0
- package/dist/field/debug.d.ts.map +1 -0
- package/dist/field/debug.js +30 -0
- package/dist/field/editor-component.css +33 -0
- package/dist/field/editor-component.d.ts +13 -0
- package/dist/field/editor-component.d.ts.map +1 -0
- package/dist/field/editor-component.js +115 -0
- package/dist/field/editor-context.d.ts +23 -0
- package/dist/field/editor-context.d.ts.map +1 -0
- package/dist/field/editor-context.js +57 -0
- package/dist/field/editor-field.d.ts +34 -0
- package/dist/field/editor-field.d.ts.map +1 -0
- package/dist/field/editor-field.js +18 -0
- package/dist/field/editor.css +1449 -0
- package/dist/field/editor.d.ts +14 -0
- package/dist/field/editor.d.ts.map +1 -0
- package/dist/field/editor.js +190 -0
- package/dist/field/hooks/use-modal.d.ts +13 -0
- package/dist/field/hooks/use-modal.d.ts.map +1 -0
- package/dist/field/hooks/use-modal.js +37 -0
- package/dist/field/hooks/use-report.js +39 -0
- package/dist/field/lexical-populate-shared.d.ts +97 -0
- package/dist/field/lexical-populate-shared.d.ts.map +1 -0
- package/dist/field/lexical-populate-shared.js +60 -0
- package/dist/field/nodes/admonition-node/admonition-node-component.css +119 -0
- package/dist/field/nodes/admonition-node/admonition-node-component.d.ts +18 -0
- package/dist/field/nodes/admonition-node/admonition-node-component.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/admonition-node-component.js +190 -0
- package/dist/field/nodes/admonition-node/admonition-node.d.ts +35 -0
- package/dist/field/nodes/admonition-node/admonition-node.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/admonition-node.js +129 -0
- package/dist/field/nodes/admonition-node/icons/danger-icon.css +12 -0
- package/dist/field/nodes/admonition-node/icons/danger-icon.d.ts +8 -0
- package/dist/field/nodes/admonition-node/icons/danger-icon.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/icons/danger-icon.js +18 -0
- package/dist/field/nodes/admonition-node/icons/index.d.ts +5 -0
- package/dist/field/nodes/admonition-node/icons/index.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/icons/index.js +4 -0
- package/dist/field/nodes/admonition-node/icons/note-icon.css +12 -0
- package/dist/field/nodes/admonition-node/icons/note-icon.d.ts +8 -0
- package/dist/field/nodes/admonition-node/icons/note-icon.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/icons/note-icon.js +18 -0
- package/dist/field/nodes/admonition-node/icons/tip-icon.css +12 -0
- package/dist/field/nodes/admonition-node/icons/tip-icon.d.ts +8 -0
- package/dist/field/nodes/admonition-node/icons/tip-icon.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/icons/tip-icon.js +18 -0
- package/dist/field/nodes/admonition-node/icons/warning-icon.css +12 -0
- package/dist/field/nodes/admonition-node/icons/warning-icon.d.ts +8 -0
- package/dist/field/nodes/admonition-node/icons/warning-icon.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/icons/warning-icon.js +18 -0
- package/dist/field/nodes/admonition-node/index.d.ts +10 -0
- package/dist/field/nodes/admonition-node/index.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/index.js +2 -0
- package/dist/field/nodes/admonition-node/types.d.ts +21 -0
- package/dist/field/nodes/admonition-node/types.d.ts.map +1 -0
- package/dist/field/nodes/admonition-node/types.js +1 -0
- package/dist/field/nodes/document-relation.d.ts +33 -0
- package/dist/field/nodes/document-relation.d.ts.map +1 -0
- package/dist/field/nodes/document-relation.js +1 -0
- package/dist/field/nodes/index.d.ts +10 -0
- package/dist/field/nodes/index.d.ts.map +1 -0
- package/dist/field/nodes/index.js +37 -0
- package/dist/field/nodes/inline-image-node/index.d.ts +10 -0
- package/dist/field/nodes/inline-image-node/index.d.ts.map +1 -0
- package/dist/field/nodes/inline-image-node/index.js +2 -0
- package/dist/field/nodes/inline-image-node/inline-image-node-component.css +204 -0
- package/dist/field/nodes/inline-image-node/inline-image-node-component.d.ts +24 -0
- package/dist/field/nodes/inline-image-node/inline-image-node-component.d.ts.map +1 -0
- package/dist/field/nodes/inline-image-node/inline-image-node-component.js +221 -0
- package/dist/field/nodes/inline-image-node/inline-image-node.d.ts +45 -0
- package/dist/field/nodes/inline-image-node/inline-image-node.d.ts.map +1 -0
- package/dist/field/nodes/inline-image-node/inline-image-node.js +187 -0
- package/dist/field/nodes/inline-image-node/types.d.ts +30 -0
- package/dist/field/nodes/inline-image-node/types.d.ts.map +1 -0
- package/dist/field/nodes/inline-image-node/types.js +1 -0
- package/dist/field/nodes/layout-container-node/layout-container-node.d.ts +29 -0
- package/dist/field/nodes/layout-container-node/layout-container-node.d.ts.map +1 -0
- package/dist/field/nodes/layout-container-node/layout-container-node.js +54 -0
- package/dist/field/nodes/layout-container-node/layout-item-node.d.ts +23 -0
- package/dist/field/nodes/layout-container-node/layout-item-node.d.ts.map +1 -0
- package/dist/field/nodes/layout-container-node/layout-item-node.js +41 -0
- package/dist/field/nodes/link-nodes/auto-link-node.d.ts +17 -0
- package/dist/field/nodes/link-nodes/auto-link-node.d.ts.map +1 -0
- package/dist/field/nodes/link-nodes/auto-link-node.js +52 -0
- package/dist/field/nodes/link-nodes/index.d.ts +4 -0
- package/dist/field/nodes/link-nodes/index.d.ts.map +1 -0
- package/dist/field/nodes/link-nodes/index.js +3 -0
- package/dist/field/nodes/link-nodes/link-node.d.ts +49 -0
- package/dist/field/nodes/link-nodes/link-node.d.ts.map +1 -0
- package/dist/field/nodes/link-nodes/link-node.js +253 -0
- package/dist/field/nodes/link-nodes/types.d.ts +26 -0
- package/dist/field/nodes/link-nodes/types.d.ts.map +1 -0
- package/dist/field/nodes/link-nodes/types.js +1 -0
- package/dist/field/nodes/vimeo-node/index.d.ts +30 -0
- package/dist/field/nodes/vimeo-node/index.d.ts.map +1 -0
- package/dist/field/nodes/vimeo-node/index.js +109 -0
- package/dist/field/nodes/youtube-node/index.d.ts +30 -0
- package/dist/field/nodes/youtube-node/index.d.ts.map +1 -0
- package/dist/field/nodes/youtube-node/index.js +110 -0
- package/dist/field/plugins/admonition-plugin/admonition-modal.css +26 -0
- package/dist/field/plugins/admonition-plugin/admonition-modal.d.ts +12 -0
- package/dist/field/plugins/admonition-plugin/admonition-modal.d.ts.map +1 -0
- package/dist/field/plugins/admonition-plugin/admonition-modal.js +165 -0
- package/dist/field/plugins/admonition-plugin/fields.d.ts +21 -0
- package/dist/field/plugins/admonition-plugin/fields.d.ts.map +1 -0
- package/dist/field/plugins/admonition-plugin/fields.js +55 -0
- package/dist/field/plugins/admonition-plugin/index.d.ts +15 -0
- package/dist/field/plugins/admonition-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/admonition-plugin/index.js +54 -0
- package/dist/field/plugins/admonition-plugin/types.d.ts +34 -0
- package/dist/field/plugins/admonition-plugin/types.d.ts.map +1 -0
- package/dist/field/plugins/admonition-plugin/types.js +1 -0
- package/dist/field/plugins/auto-embed-plugin/auto-embed-modal.css +16 -0
- package/dist/field/plugins/auto-embed-plugin/auto-embed-modal.d.ts +8 -0
- package/dist/field/plugins/auto-embed-plugin/auto-embed-modal.d.ts.map +1 -0
- package/dist/field/plugins/auto-embed-plugin/auto-embed-modal.js +105 -0
- package/dist/field/plugins/auto-embed-plugin/index.d.ts +21 -0
- package/dist/field/plugins/auto-embed-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/auto-embed-plugin/index.js +152 -0
- package/dist/field/plugins/code-highlight-plugin/index.d.ts +10 -0
- package/dist/field/plugins/code-highlight-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/code-highlight-plugin/index.js +11 -0
- package/dist/field/plugins/drag-drop-paste-plugin/index.js +1 -0
- package/dist/field/plugins/floating-text-format-toolbar-plugin/index.css +148 -0
- package/dist/field/plugins/floating-text-format-toolbar-plugin/index.d.ts +13 -0
- package/dist/field/plugins/floating-text-format-toolbar-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/floating-text-format-toolbar-plugin/index.js +279 -0
- package/dist/field/plugins/inline-image-plugin/fields.d.ts +12 -0
- package/dist/field/plugins/inline-image-plugin/fields.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/fields.js +22 -0
- package/dist/field/plugins/inline-image-plugin/index.d.ts +34 -0
- package/dist/field/plugins/inline-image-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/index.js +107 -0
- package/dist/field/plugins/inline-image-plugin/inline-image-modal.css +25 -0
- package/dist/field/plugins/inline-image-plugin/inline-image-modal.d.ts +11 -0
- package/dist/field/plugins/inline-image-plugin/inline-image-modal.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/inline-image-modal.js +271 -0
- package/dist/field/plugins/inline-image-plugin/populate.d.ts +10 -0
- package/dist/field/plugins/inline-image-plugin/populate.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/populate.js +28 -0
- package/dist/field/plugins/inline-image-plugin/types.d.ts +37 -0
- package/dist/field/plugins/inline-image-plugin/types.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/types.js +1 -0
- package/dist/field/plugins/inline-image-plugin/utils.d.ts +42 -0
- package/dist/field/plugins/inline-image-plugin/utils.d.ts.map +1 -0
- package/dist/field/plugins/inline-image-plugin/utils.js +46 -0
- package/dist/field/plugins/layout-plugin/insert-layout-modal.css +20 -0
- package/dist/field/plugins/layout-plugin/insert-layout-modal.d.ts +22 -0
- package/dist/field/plugins/layout-plugin/insert-layout-modal.d.ts.map +1 -0
- package/dist/field/plugins/layout-plugin/insert-layout-modal.js +97 -0
- package/dist/field/plugins/layout-plugin/layout-plugin.d.ts +10 -0
- package/dist/field/plugins/layout-plugin/layout-plugin.d.ts.map +1 -0
- package/dist/field/plugins/layout-plugin/layout-plugin.js +81 -0
- package/dist/field/plugins/link-plugin/auto-link/auto-link-plugin.d.ts +22 -0
- package/dist/field/plugins/link-plugin/auto-link/auto-link-plugin.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/auto-link/auto-link-plugin.js +185 -0
- package/dist/field/plugins/link-plugin/auto-link/index.d.ts +10 -0
- package/dist/field/plugins/link-plugin/auto-link/index.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/auto-link/index.js +15 -0
- package/dist/field/plugins/link-plugin/link/floating-link-editor.css +124 -0
- package/dist/field/plugins/link-plugin/link/floating-link-editor.d.ts +15 -0
- package/dist/field/plugins/link-plugin/link/floating-link-editor.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/link/floating-link-editor.js +234 -0
- package/dist/field/plugins/link-plugin/link/index.d.ts +2 -0
- package/dist/field/plugins/link-plugin/link/index.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/link/index.js +43 -0
- package/dist/field/plugins/link-plugin/link/link-modal.d.ts +11 -0
- package/dist/field/plugins/link-plugin/link/link-modal.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/link/link-modal.js +311 -0
- package/dist/field/plugins/link-plugin/link/types.d.ts +19 -0
- package/dist/field/plugins/link-plugin/link/types.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/link/types.js +1 -0
- package/dist/field/plugins/link-plugin/populate.d.ts +22 -0
- package/dist/field/plugins/link-plugin/populate.d.ts.map +1 -0
- package/dist/field/plugins/link-plugin/populate.js +28 -0
- package/dist/field/plugins/table-action-menu-plugin/index.d.ts +6 -0
- package/dist/field/plugins/table-action-menu-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/table-action-menu-plugin/index.js +598 -0
- package/dist/field/plugins/table-plugin/index.d.ts +4 -0
- package/dist/field/plugins/table-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/table-plugin/index.js +26 -0
- package/dist/field/plugins/table-plugin/table-modal.css +20 -0
- package/dist/field/plugins/table-plugin/table-modal.d.ts +21 -0
- package/dist/field/plugins/table-plugin/table-modal.d.ts.map +1 -0
- package/dist/field/plugins/table-plugin/table-modal.js +111 -0
- package/dist/field/plugins/toolbar-plugin/index.d.ts +17 -0
- package/dist/field/plugins/toolbar-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/toolbar-plugin/index.js +837 -0
- package/dist/field/plugins/treeview-plugin/index.d.ts +10 -0
- package/dist/field/plugins/treeview-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/treeview-plugin/index.js +17 -0
- package/dist/field/plugins/vimeo-plugin/index.d.ts +4 -0
- package/dist/field/plugins/vimeo-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/vimeo-plugin/index.js +24 -0
- package/dist/field/plugins/youtube-plugin/index.d.ts +4 -0
- package/dist/field/plugins/youtube-plugin/index.d.ts.map +1 -0
- package/dist/field/plugins/youtube-plugin/index.js +24 -0
- package/dist/field/shared/canUseDOM.d.ts +9 -0
- package/dist/field/shared/canUseDOM.d.ts.map +1 -0
- package/dist/field/shared/canUseDOM.js +2 -0
- package/dist/field/shared/environment.d.ts +23 -0
- package/dist/field/shared/environment.d.ts.map +1 -0
- package/dist/field/shared/environment.js +10 -0
- package/dist/field/shared/invariant.d.ts +9 -0
- package/dist/field/shared/invariant.d.ts.map +1 -0
- package/dist/field/shared/invariant.js +6 -0
- package/dist/field/shared/simpleDiffWithCursor.js +15 -0
- package/dist/field/shared/useLayoutEffect.js +5 -0
- package/dist/field/shared/useModalFormState.d.ts +23 -0
- package/dist/field/shared/useModalFormState.d.ts.map +1 -0
- package/dist/field/shared/useModalFormState.js +23 -0
- package/dist/field/shared/warnOnlyOnce.js +9 -0
- package/dist/field/themes/lexical-editor-theme.css +537 -0
- package/dist/field/themes/lexical-editor-theme.d.ts +10 -0
- package/dist/field/themes/lexical-editor-theme.d.ts.map +1 -0
- package/dist/field/themes/lexical-editor-theme.js +105 -0
- package/dist/field/toolbar-extensions.d.ts +32 -0
- package/dist/field/toolbar-extensions.d.ts.map +1 -0
- package/dist/field/toolbar-extensions.js +56 -0
- package/dist/field/types.js +1 -0
- package/dist/field/ui/button.css +31 -0
- package/dist/field/ui/button.js +19 -0
- package/dist/field/ui/checkbox.css +46 -0
- package/dist/field/ui/color-picker.css +75 -0
- package/dist/field/ui/color-picker.d.ts +21 -0
- package/dist/field/ui/color-picker.d.ts.map +1 -0
- package/dist/field/ui/color-picker.js +360 -0
- package/dist/field/ui/dialog.css +18 -0
- package/dist/field/ui/dialog.js +17 -0
- package/dist/field/ui/dropdown.d.ts +25 -0
- package/dist/field/ui/dropdown.d.ts.map +1 -0
- package/dist/field/ui/dropdown.js +150 -0
- package/dist/field/ui/file-input.js +27 -0
- package/dist/field/ui/input.css +23 -0
- package/dist/field/ui/modal.css +73 -0
- package/dist/field/ui/modal.d.ts +17 -0
- package/dist/field/ui/modal.d.ts.map +1 -0
- package/dist/field/ui/modal.js +69 -0
- package/dist/field/ui/placeholder-inline.d.ts +14 -0
- package/dist/field/ui/placeholder-inline.d.ts.map +1 -0
- package/dist/field/ui/placeholder-inline.js +12 -0
- package/dist/field/ui/placeholder.css +26 -0
- package/dist/field/ui/placeholder.d.ts +14 -0
- package/dist/field/ui/placeholder.d.ts.map +1 -0
- package/dist/field/ui/placeholder.js +14 -0
- package/dist/field/ui/select.css +32 -0
- package/dist/field/ui/select.js +28 -0
- package/dist/field/ui/switch.js +25 -0
- package/dist/field/ui/text-area.css +31 -0
- package/dist/field/ui/text-area.js +28 -0
- package/dist/field/ui/text-input.d.ts +20 -0
- package/dist/field/ui/text-input.d.ts.map +1 -0
- package/dist/field/ui/text-input.js +29 -0
- package/dist/field/utils/cloneDeep.js +26 -0
- package/dist/field/utils/deepEqual.js +19 -0
- package/dist/field/utils/emoji-list.js +21389 -0
- package/dist/field/utils/getDOMRangeRect.d.ts +9 -0
- package/dist/field/utils/getDOMRangeRect.d.ts.map +1 -0
- package/dist/field/utils/getDOMRangeRect.js +11 -0
- package/dist/field/utils/getSelectedNode.d.ts +3 -0
- package/dist/field/utils/getSelectedNode.d.ts.map +1 -0
- package/dist/field/utils/getSelectedNode.js +12 -0
- package/dist/field/utils/guard.js +4 -0
- package/dist/field/utils/hashSerializedState.d.ts +3 -0
- package/dist/field/utils/hashSerializedState.d.ts.map +1 -0
- package/dist/field/utils/hashSerializedState.js +22 -0
- package/dist/field/utils/isMobileWidth.js +1 -0
- package/dist/field/utils/joinClasses.js +4 -0
- package/dist/field/utils/point.js +34 -0
- package/dist/field/utils/rect.js +95 -0
- package/dist/field/utils/setFloatingElemPosition.d.ts +2 -0
- package/dist/field/utils/setFloatingElemPosition.d.ts.map +1 -0
- package/dist/field/utils/setFloatingElemPosition.js +22 -0
- package/dist/field/utils/setFloatingElemPositionForLinkEditor.d.ts +2 -0
- package/dist/field/utils/setFloatingElemPositionForLinkEditor.d.ts.map +1 -0
- package/dist/field/utils/setFloatingElemPositionForLinkEditor.js +22 -0
- package/dist/field/utils/swipe.js +77 -0
- package/dist/field/utils/url.d.ts +11 -0
- package/dist/field/utils/url.d.ts.map +1 -0
- package/dist/field/utils/url.js +30 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/lexical-editor.d.ts +41 -0
- package/dist/lexical-editor.d.ts.map +1 -0
- package/dist/lexical-editor.js +13 -0
- package/dist/richtext-field.d.ts +25 -0
- package/dist/richtext-field.d.ts.map +1 -0
- package/dist/richtext-field.js +62 -0
- package/dist/richtext-field.module.js +8 -0
- package/dist/richtext-field_module.css +18 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +20 -0
- package/dist/static/svg/3-columns.svg +3 -0
- package/dist/static/svg/admonition.svg +1 -0
- package/dist/static/svg/ai.svg +5 -0
- package/dist/static/svg/arrow-clockwise.svg +1 -0
- package/dist/static/svg/arrow-counterclockwise.svg +1 -0
- package/dist/static/svg/bg-color.svg +1 -0
- package/dist/static/svg/camera.svg +1 -0
- package/dist/static/svg/caret-right-fill.svg +1 -0
- package/dist/static/svg/chat-square-quote.svg +1 -0
- package/dist/static/svg/chevron-down.svg +1 -0
- package/dist/static/svg/clipboard.svg +1 -0
- package/dist/static/svg/close.svg +1 -0
- package/dist/static/svg/code.svg +1 -0
- package/dist/static/svg/copy.svg +1 -0
- package/dist/static/svg/diagram-2.svg +1 -0
- package/dist/static/svg/download.svg +1 -0
- package/dist/static/svg/dropdown-more.svg +1 -0
- package/dist/static/svg/file-image.svg +1 -0
- package/dist/static/svg/filetype-gif.svg +1 -0
- package/dist/static/svg/font-color.svg +1 -0
- package/dist/static/svg/font-family.svg +1 -0
- package/dist/static/svg/gear.svg +1 -0
- package/dist/static/svg/horizontal-rule.svg +1 -0
- package/dist/static/svg/indent.svg +1 -0
- package/dist/static/svg/journal-code.svg +1 -0
- package/dist/static/svg/justify.svg +1 -0
- package/dist/static/svg/link.svg +1 -0
- package/dist/static/svg/list-ol.svg +1 -0
- package/dist/static/svg/list-ul.svg +1 -0
- package/dist/static/svg/lock-fill.svg +1 -0
- package/dist/static/svg/lock.svg +1 -0
- package/dist/static/svg/markdown.svg +1 -0
- package/dist/static/svg/mic.svg +1 -0
- package/dist/static/svg/outdent.svg +1 -0
- package/dist/static/svg/paint-bucket.svg +1 -0
- package/dist/static/svg/palette.svg +1 -0
- package/dist/static/svg/pencil-fill.svg +1 -0
- package/dist/static/svg/plug-fill.svg +1 -0
- package/dist/static/svg/plug.svg +1 -0
- package/dist/static/svg/plus.svg +1 -0
- package/dist/static/svg/prettier-error.svg +1 -0
- package/dist/static/svg/prettier.svg +1 -0
- package/dist/static/svg/square-check.svg +1 -0
- package/dist/static/svg/success.svg +1 -0
- package/dist/static/svg/table.svg +1 -0
- package/dist/static/svg/text-center.svg +1 -0
- package/dist/static/svg/text-left.svg +1 -0
- package/dist/static/svg/text-paragraph.svg +1 -0
- package/dist/static/svg/text-right.svg +1 -0
- package/dist/static/svg/trash.svg +1 -0
- package/dist/static/svg/type-bold.svg +1 -0
- package/dist/static/svg/type-h1.svg +1 -0
- package/dist/static/svg/type-h2.svg +1 -0
- package/dist/static/svg/type-h3.svg +1 -0
- package/dist/static/svg/type-h4.svg +1 -0
- package/dist/static/svg/type-h5.svg +1 -0
- package/dist/static/svg/type-h6.svg +1 -0
- package/dist/static/svg/type-italic.svg +1 -0
- package/dist/static/svg/type-strikethrough.svg +1 -0
- package/dist/static/svg/type-subscript.svg +1 -0
- package/dist/static/svg/type-superscript.svg +1 -0
- package/dist/static/svg/type-underline.svg +1 -0
- package/dist/static/svg/upload.svg +1 -0
- package/dist/static/svg/user.svg +1 -0
- package/dist/static/svg/vimeo.svg +1 -0
- package/dist/static/svg/youtube.svg +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/validate/createEmptyEditorState.d.ts +25 -0
- package/dist/validate/createEmptyEditorState.d.ts.map +1 -0
- package/dist/validate/createEmptyEditorState.js +26 -0
- package/dist/validate/hasText.d.ts +3 -0
- package/dist/validate/hasText.d.ts.map +1 -0
- package/dist/validate/hasText.js +20 -0
- package/dist/validate/validate.d.ts +5 -0
- package/dist/validate/validate.d.ts.map +1 -0
- package/dist/validate/validate.js +7 -0
- package/package.json +120 -0
- package/src/declarations.d.ts +4 -0
- package/src/field/apply-value-plugin.tsx +104 -0
- package/src/field/config/default.ts +58 -0
- package/src/field/config/editor-config-context.tsx +60 -0
- package/src/field/config/types.ts +67 -0
- package/src/field/constants.ts +1 -0
- package/src/field/content-editable-inline.tsx +27 -0
- package/src/field/content-editable.css +29 -0
- package/src/field/content-editable.tsx +27 -0
- package/src/field/context/shared-autocomplete-context.tsx +61 -0
- package/src/field/context/shared-history-context.tsx +30 -0
- package/src/field/context/shared-on-change-context.tsx +32 -0
- package/src/field/debug.tsx +39 -0
- package/src/field/editor-component.css +46 -0
- package/src/field/editor-component.md +87 -0
- package/src/field/editor-component.test.tsx +170 -0
- package/src/field/editor-component.tsx +207 -0
- package/src/field/editor-context.tsx +102 -0
- package/src/field/editor-field.tsx +51 -0
- package/src/field/editor.css +1481 -0
- package/src/field/editor.tsx +245 -0
- package/src/field/hooks/use-media-queryts +21 -0
- package/src/field/hooks/use-modal.tsx +59 -0
- package/src/field/hooks/use-report.ts +62 -0
- package/src/field/images/LICENSE.md +5 -0
- package/src/field/images/assets.d.ts +27 -0
- package/src/field/images/cat-typing.gif +0 -0
- package/src/field/images/emoji/1F600.png +0 -0
- package/src/field/images/emoji/1F641.png +0 -0
- package/src/field/images/emoji/1F642.png +0 -0
- package/src/field/images/emoji/2764.png +0 -0
- package/src/field/images/emoji/LICENSE.md +5 -0
- package/src/field/images/icons/3-columns.svg +3 -0
- package/src/field/images/icons/LICENSE.md +5 -0
- package/src/field/images/icons/admonition.svg +1 -0
- package/src/field/images/icons/ai.svg +5 -0
- package/src/field/images/icons/arrow-clockwise.svg +1 -0
- package/src/field/images/icons/arrow-counterclockwise.svg +1 -0
- package/src/field/images/icons/bg-color.svg +1 -0
- package/src/field/images/icons/camera.svg +1 -0
- package/src/field/images/icons/card-checklist.svg +1 -0
- package/src/field/images/icons/caret-right-fill.svg +1 -0
- package/src/field/images/icons/chat-left-text.svg +1 -0
- package/src/field/images/icons/chat-right-dots.svg +1 -0
- package/src/field/images/icons/chat-right-text.svg +1 -0
- package/src/field/images/icons/chat-right.svg +1 -0
- package/src/field/images/icons/chat-square-quote.svg +1 -0
- package/src/field/images/icons/chevron-down.svg +1 -0
- package/src/field/images/icons/clipboard.svg +1 -0
- package/src/field/images/icons/close.svg +1 -0
- package/src/field/images/icons/code.svg +1 -0
- package/src/field/images/icons/comments.svg +1 -0
- package/src/field/images/icons/copy.svg +1 -0
- package/src/field/images/icons/diagram-2.svg +1 -0
- package/src/field/images/icons/download.svg +1 -0
- package/src/field/images/icons/draggable-block-menu.svg +1 -0
- package/src/field/images/icons/dropdown-more.svg +1 -0
- package/src/field/images/icons/figma.svg +1 -0
- package/src/field/images/icons/file-earmark-text.svg +4 -0
- package/src/field/images/icons/file-image.svg +1 -0
- package/src/field/images/icons/filetype-gif.svg +1 -0
- package/src/field/images/icons/font-color.svg +1 -0
- package/src/field/images/icons/font-family.svg +1 -0
- package/src/field/images/icons/gear.svg +1 -0
- package/src/field/images/icons/horizontal-rule.svg +1 -0
- package/src/field/images/icons/indent.svg +1 -0
- package/src/field/images/icons/journal-code.svg +1 -0
- package/src/field/images/icons/journal-text.svg +1 -0
- package/src/field/images/icons/justify.svg +1 -0
- package/src/field/images/icons/link.svg +1 -0
- package/src/field/images/icons/list-ol.svg +1 -0
- package/src/field/images/icons/list-ul.svg +1 -0
- package/src/field/images/icons/lock-fill.svg +1 -0
- package/src/field/images/icons/lock.svg +1 -0
- package/src/field/images/icons/markdown.svg +1 -0
- package/src/field/images/icons/mic.svg +1 -0
- package/src/field/images/icons/outdent.svg +1 -0
- package/src/field/images/icons/paint-bucket.svg +1 -0
- package/src/field/images/icons/palette.svg +1 -0
- package/src/field/images/icons/pencil-fill.svg +1 -0
- package/src/field/images/icons/plug-fill.svg +1 -0
- package/src/field/images/icons/plug.svg +1 -0
- package/src/field/images/icons/plus-slash-minus.svg +1 -0
- package/src/field/images/icons/plus.svg +1 -0
- package/src/field/images/icons/prettier-error.svg +1 -0
- package/src/field/images/icons/prettier.svg +1 -0
- package/src/field/images/icons/send.svg +1 -0
- package/src/field/images/icons/square-check.svg +1 -0
- package/src/field/images/icons/sticky.svg +1 -0
- package/src/field/images/icons/success-alt.svg +1 -0
- package/src/field/images/icons/success.svg +1 -0
- package/src/field/images/icons/table.svg +1 -0
- package/src/field/images/icons/text-center.svg +1 -0
- package/src/field/images/icons/text-left.svg +1 -0
- package/src/field/images/icons/text-paragraph.svg +1 -0
- package/src/field/images/icons/text-right.svg +1 -0
- package/src/field/images/icons/trash.svg +1 -0
- package/src/field/images/icons/trash3.svg +1 -0
- package/src/field/images/icons/tweet.svg +1 -0
- package/src/field/images/icons/type-bold.svg +1 -0
- package/src/field/images/icons/type-h1.svg +1 -0
- package/src/field/images/icons/type-h2.svg +1 -0
- package/src/field/images/icons/type-h3.svg +1 -0
- package/src/field/images/icons/type-h4.svg +1 -0
- package/src/field/images/icons/type-h5.svg +1 -0
- package/src/field/images/icons/type-h6.svg +1 -0
- package/src/field/images/icons/type-italic.svg +1 -0
- package/src/field/images/icons/type-strikethrough.svg +1 -0
- package/src/field/images/icons/type-subscript.svg +1 -0
- package/src/field/images/icons/type-superscript.svg +1 -0
- package/src/field/images/icons/type-underline.svg +1 -0
- package/src/field/images/icons/upload.svg +1 -0
- package/src/field/images/icons/user.svg +1 -0
- package/src/field/images/icons/vimeo.svg +1 -0
- package/src/field/images/icons/youtube.svg +1 -0
- package/src/field/images/landscape.jpg +0 -0
- package/src/field/images/logo.svg +1 -0
- package/src/field/images/yellow-flower-small.jpg +0 -0
- package/src/field/images/yellow-flower.jpg +0 -0
- package/src/field/lexical-populate-shared.ts +188 -0
- package/src/field/nodes/admonition-node/admonition-node-component.css +115 -0
- package/src/field/nodes/admonition-node/admonition-node-component.tsx +256 -0
- package/src/field/nodes/admonition-node/admonition-node.tsx +189 -0
- package/src/field/nodes/admonition-node/icons/danger-icon.css +11 -0
- package/src/field/nodes/admonition-node/icons/danger-icon.js +17 -0
- package/src/field/nodes/admonition-node/icons/danger-icon.tsx +19 -0
- package/src/field/nodes/admonition-node/icons/index.js +4 -0
- package/src/field/nodes/admonition-node/icons/index.ts +4 -0
- package/src/field/nodes/admonition-node/icons/note-icon.css +11 -0
- package/src/field/nodes/admonition-node/icons/note-icon.js +17 -0
- package/src/field/nodes/admonition-node/icons/note-icon.tsx +19 -0
- package/src/field/nodes/admonition-node/icons/tip-icon.css +11 -0
- package/src/field/nodes/admonition-node/icons/tip-icon.js +17 -0
- package/src/field/nodes/admonition-node/icons/tip-icon.tsx +19 -0
- package/src/field/nodes/admonition-node/icons/warning-icon.css +11 -0
- package/src/field/nodes/admonition-node/icons/warning-icon.js +17 -0
- package/src/field/nodes/admonition-node/icons/warning-icon.tsx +19 -0
- package/src/field/nodes/admonition-node/index.ts +14 -0
- package/src/field/nodes/admonition-node/types.ts +33 -0
- package/src/field/nodes/document-relation.ts +33 -0
- package/src/field/nodes/index.ts +47 -0
- package/src/field/nodes/inline-image-node/index.ts +10 -0
- package/src/field/nodes/inline-image-node/inline-image-node-component.css +205 -0
- package/src/field/nodes/inline-image-node/inline-image-node-component.tsx +342 -0
- package/src/field/nodes/inline-image-node/inline-image-node.tsx +328 -0
- package/src/field/nodes/inline-image-node/types.ts +43 -0
- package/src/field/nodes/layout-container-node/layout-container-node.ts +97 -0
- package/src/field/nodes/layout-container-node/layout-item-node.ts +63 -0
- package/src/field/nodes/link-nodes/auto-link-node.ts +64 -0
- package/src/field/nodes/link-nodes/index.ts +3 -0
- package/src/field/nodes/link-nodes/link-node.ts +426 -0
- package/src/field/nodes/link-nodes/types.ts +33 -0
- package/src/field/nodes/vimeo-node/index.tsx +172 -0
- package/src/field/nodes/youtube-node/index.tsx +178 -0
- package/src/field/plugins/admonition-plugin/admonition-modal.css +25 -0
- package/src/field/plugins/admonition-plugin/admonition-modal.tsx +187 -0
- package/src/field/plugins/admonition-plugin/fields.ts +103 -0
- package/src/field/plugins/admonition-plugin/index.tsx +105 -0
- package/src/field/plugins/admonition-plugin/types.ts +37 -0
- package/src/field/plugins/auto-embed-plugin/auto-embed-modal.css +15 -0
- package/src/field/plugins/auto-embed-plugin/auto-embed-modal.tsx +110 -0
- package/src/field/plugins/auto-embed-plugin/index.tsx +323 -0
- package/src/field/plugins/code-highlight-plugin/index.ts +23 -0
- package/src/field/plugins/drag-drop-paste-plugin/index.ts +47 -0
- package/src/field/plugins/floating-text-format-toolbar-plugin/index.css +148 -0
- package/src/field/plugins/floating-text-format-toolbar-plugin/index.tsx +398 -0
- package/src/field/plugins/inline-image-plugin/fields.ts +21 -0
- package/src/field/plugins/inline-image-plugin/index.tsx +202 -0
- package/src/field/plugins/inline-image-plugin/inline-image-modal.css +29 -0
- package/src/field/plugins/inline-image-plugin/inline-image-modal.tsx +314 -0
- package/src/field/plugins/inline-image-plugin/populate.ts +48 -0
- package/src/field/plugins/inline-image-plugin/types.ts +39 -0
- package/src/field/plugins/inline-image-plugin/utils.ts +113 -0
- package/src/field/plugins/layout-plugin/insert-layout-modal.css +19 -0
- package/src/field/plugins/layout-plugin/insert-layout-modal.tsx +100 -0
- package/src/field/plugins/layout-plugin/layout-plugin.tsx +162 -0
- package/src/field/plugins/link-plugin/auto-link/auto-link-plugin.tsx +327 -0
- package/src/field/plugins/link-plugin/auto-link/index.tsx +35 -0
- package/src/field/plugins/link-plugin/link/floating-link-editor.css +128 -0
- package/src/field/plugins/link-plugin/link/floating-link-editor.tsx +348 -0
- package/src/field/plugins/link-plugin/link/index.tsx +99 -0
- package/src/field/plugins/link-plugin/link/link-modal.tsx +376 -0
- package/src/field/plugins/link-plugin/link/types.ts +20 -0
- package/src/field/plugins/link-plugin/populate.ts +48 -0
- package/src/field/plugins/table-action-menu-plugin/index.tsx +804 -0
- package/src/field/plugins/table-plugin/index.tsx +33 -0
- package/src/field/plugins/table-plugin/table-modal.css +19 -0
- package/src/field/plugins/table-plugin/table-modal.tsx +127 -0
- package/src/field/plugins/toolbar-plugin/index.tsx +937 -0
- package/src/field/plugins/treeview-plugin/index.tsx +29 -0
- package/src/field/plugins/vimeo-plugin/index.ts +42 -0
- package/src/field/plugins/youtube-plugin/index.ts +43 -0
- package/src/field/shared/canUseDOM.ts +10 -0
- package/src/field/shared/caretFromPoint.bak +40 -0
- package/src/field/shared/environment.ts +44 -0
- package/src/field/shared/invariant.ts +25 -0
- package/src/field/shared/simpleDiffWithCursor.ts +40 -0
- package/src/field/shared/useLayoutEffect.ts +15 -0
- package/src/field/shared/useModalFormState.ts +48 -0
- package/src/field/shared/warnOnlyOnce.ts +21 -0
- package/src/field/themes/lexical-editor-theme.css +565 -0
- package/src/field/themes/lexical-editor-theme.js +104 -0
- package/src/field/themes/lexical-editor-theme.tsx +115 -0
- package/src/field/toolbar-extensions.tsx +93 -0
- package/src/field/types.ts +19 -0
- package/src/field/ui/button.css +45 -0
- package/src/field/ui/button.tsx +51 -0
- package/src/field/ui/checkbox.css +52 -0
- package/src/field/ui/color-picker.css +88 -0
- package/src/field/ui/color-picker.tsx +395 -0
- package/src/field/ui/dialog.css +17 -0
- package/src/field/ui/dialog.tsx +29 -0
- package/src/field/ui/dropdown.tsx +217 -0
- package/src/field/ui/file-input.tsx +44 -0
- package/src/field/ui/input.css +32 -0
- package/src/field/ui/modal.css +84 -0
- package/src/field/ui/modal.tsx +106 -0
- package/src/field/ui/placeholder-inline.tsx +28 -0
- package/src/field/ui/placeholder.css +33 -0
- package/src/field/ui/placeholder.tsx +32 -0
- package/src/field/ui/select.css +34 -0
- package/src/field/ui/select.tsx +38 -0
- package/src/field/ui/switch.tsx +33 -0
- package/src/field/ui/text-area.css +45 -0
- package/src/field/ui/text-area.tsx +50 -0
- package/src/field/ui/text-input.tsx +51 -0
- package/src/field/utils/cloneDeep.ts +57 -0
- package/src/field/utils/deepEqual.ts +54 -0
- package/src/field/utils/emoji-list.ts +16615 -0
- package/src/field/utils/getDOMRangeRect.ts +24 -0
- package/src/field/utils/getSelectedNode.ts +25 -0
- package/src/field/utils/guard.ts +10 -0
- package/src/field/utils/hashSerializedState.ts +34 -0
- package/src/field/utils/isMobileWidth.ts +7 -0
- package/src/field/utils/joinClasses.ts +11 -0
- package/src/field/utils/point.ts +52 -0
- package/src/field/utils/rect.ts +142 -0
- package/src/field/utils/setFloatingElemPosition.ts +48 -0
- package/src/field/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
- package/src/field/utils/swipe.ts +129 -0
- package/src/field/utils/url.ts +49 -0
- package/src/index.ts +26 -0
- package/src/lexical-editor.tsx +56 -0
- package/src/richtext-field.module.css +29 -0
- package/src/richtext-field.tsx +113 -0
- package/src/server.ts +85 -0
- package/src/types.ts +43 -0
- package/src/validate/createEmptyEditorState.ts +52 -0
- package/src/validate/hasText.test.ts +89 -0
- package/src/validate/hasText.ts +34 -0
- package/src/validate/validate.ts +18 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.lexicalRichTextEditor {
|
|
2
|
+
display: flex;
|
|
3
|
+
isolation: isolate;
|
|
4
|
+
width: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.lexicalRichTextEditor--read-only {
|
|
8
|
+
.editor-shell::after {
|
|
9
|
+
content: "";
|
|
10
|
+
position: absolute;
|
|
11
|
+
top: 0;
|
|
12
|
+
left: 0;
|
|
13
|
+
right: 0;
|
|
14
|
+
bottom: 0;
|
|
15
|
+
background: var(--canvas-50);
|
|
16
|
+
opacity: 0.5;
|
|
17
|
+
z-index: 9;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.lexicalRichTextEditor__wrap {
|
|
22
|
+
width: 100%;
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.lexicalRichTextEditor.error {
|
|
27
|
+
.editor-shell::after {
|
|
28
|
+
content: "";
|
|
29
|
+
position: absolute;
|
|
30
|
+
top: 0;
|
|
31
|
+
left: 0;
|
|
32
|
+
right: 0;
|
|
33
|
+
bottom: 0;
|
|
34
|
+
background: var(--red-700);
|
|
35
|
+
opacity: 1;
|
|
36
|
+
z-index: -1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dark {
|
|
41
|
+
.lexicalRichTextEditor.error {
|
|
42
|
+
.editor-shell::after {
|
|
43
|
+
background: var(--red-700-dark);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Editor Component Architecture
|
|
2
|
+
|
|
3
|
+
This architecture is designed to solve the "two-way binding problem" common in rich text editors.
|
|
4
|
+
|
|
5
|
+
Rich text editors maintain their own complex internal state (the DOM/Virtual DOM). When you try to sync this with React state (props), you often get **infinite loops** (Editor changes $\rightarrow$ React updates $\rightarrow$ Prop changes $\rightarrow$ Editor updates $\rightarrow$ Editor changes...) or **cursor jumping** (re-rendering the editor while typing).
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
+-----------------------------------------------------------------------+
|
|
9
|
+
| EditorField (src/field/editor-field.tsx) |
|
|
10
|
+
| - Handles Lazy Loading & Suspense |
|
|
11
|
+
+-----------------------------------+-----------------------------------+
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
+-----------------------------------+-----------------------------------+
|
|
15
|
+
| EditorComponent (src/field/editor-component.tsx) |
|
|
16
|
+
| - Manages Hash Refs (lastEmitted, normalizedIncoming) |
|
|
17
|
+
| - Handles onChange (Debouncing & Hash Checks) |
|
|
18
|
+
+-----------------------------------+-----------------------------------+
|
|
19
|
+
|
|
|
20
|
+
| (renders)
|
|
21
|
+
v
|
|
22
|
+
+-----------------------------------+----------------------------------+
|
|
23
|
+
| EditorContext (src/field/editor-context.tsx) |
|
|
24
|
+
| - Wraps everything in <LexicalComposer> |
|
|
25
|
+
| - Provides SharedHistory & SharedOnChange Contexts |
|
|
26
|
+
+------------------+---------------------------------+-----------------+
|
|
27
|
+
| |
|
|
28
|
+
| (passed as children) | (renders)
|
|
29
|
+
v v
|
|
30
|
+
+------------------+------------------+ +-----------+------------------+
|
|
31
|
+
| ApplyValuePlugin | | Editor (src/field/editor.tsx)|
|
|
32
|
+
| (src/field/apply-value-plugin.tsx) | | - ToolbarPlugin |
|
|
33
|
+
| | | - ContentEditable |
|
|
34
|
+
| - Watches: incoming value & hash | | - Floating Toolbars |
|
|
35
|
+
| - Action: editor.update() | | - Auto-resize logic |
|
|
36
|
+
| (Syncs external props -> Editor) | +------------------------------+
|
|
37
|
+
+-------------------------------------+
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Key Relationships
|
|
41
|
+
1. `EditorComponent` is the "Brain". It holds the connection to the Payload form state (`useField`) and decides when to update the form value based on hashes.
|
|
42
|
+
2. `EditorContext` is the "Bridge". It initializes the Lexical instance (`LexicalComposer`) but doesn't contain the specific logic for syncing values or rendering the UI itself.
|
|
43
|
+
3. `ApplyValuePlugin` is the "Synchronizer". It sits inside the Lexical context. When `EditorComponent` receives a new value from the database (or parent), it passes it down here. This plugin forces the Lexical instance to update its state to match.
|
|
44
|
+
4. `Editor` is the "View". It handles the visual presentation, toolbars, and the actual `contentEditable` DOM element.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## EditorComponent
|
|
48
|
+
|
|
49
|
+
Here's how the `useRef` hooks inside editor-component.tsx solve editor-specific problems:
|
|
50
|
+
|
|
51
|
+
### 1. The "Debounce" Ref
|
|
52
|
+
**`dispatchFieldUpdateTask`**
|
|
53
|
+
* **Purpose:** Performance.
|
|
54
|
+
* **How it works:** When a user types, Lexical fires `onChange` on every keystroke. We don't want to update the main React state (and trigger re-renders up the tree) 60 times a second.
|
|
55
|
+
* **Mechanism:** This ref stores the ID of the current `requestIdleCallback`. If a user types again before the callback runs, we cancel the old one and start a new one. It ensures we only process the *latest* state when the browser is idle.
|
|
56
|
+
|
|
57
|
+
### 2. The "Fresh Props" Refs
|
|
58
|
+
**`valueRef`** and **`initialValueRef`**
|
|
59
|
+
* **Purpose:** Accessing state inside `useCallback` without dependencies.
|
|
60
|
+
* **How it works:** The `handleChange` function is memoized via `useCallback`. If we added `value` to its dependency array, `handleChange` would be re-created every time the user typed, breaking our debounce logic.
|
|
61
|
+
* **Mechanism:** We copy the props into these refs on every render. Inside `handleChange`, we read `valueRef.current`. This lets the function stay stable (same memory address) while still seeing the latest data.
|
|
62
|
+
|
|
63
|
+
### 3. The "Outbound Loop Breaker"
|
|
64
|
+
**`lastEmittedHashRef`**
|
|
65
|
+
* **Purpose:** Preventing the "Echo" loop.
|
|
66
|
+
* **The Problem:** You type "A". The editor emits "A". The parent saves "A" and passes "A" back down as a prop. The editor sees "A" and thinks, "Oh, a new value! I should process this."
|
|
67
|
+
* **The Fix:** Before calling `onChange`, we calculate a hash of the content and store it here.
|
|
68
|
+
* **Logic:** "If the hash I'm about to send is the same as the last one I sent, do nothing."
|
|
69
|
+
|
|
70
|
+
### 4. The "Inbound Normalization" Refs
|
|
71
|
+
**`normalizedIncomingHashRef`** and **`hasNormalizedBaselineRef`**
|
|
72
|
+
* **Purpose:** Handling Lexical's strictness.
|
|
73
|
+
* **The Problem:** Lexical is opinionated. If you load a value like `{"text": "hello"}`, Lexical might instantly transform it into a more complex structure (adding IDs, versions, etc.). This immediate transformation looks like a "change" event, which might trigger a save before the user has even touched the keyboard.
|
|
74
|
+
* **`normalizedIncomingHashRef`:**
|
|
75
|
+
* The `ApplyValuePlugin` loads data, waits for Lexical to "settle" (normalize it), and then saves the hash of that *settled* state here.
|
|
76
|
+
* The `handleChange` function checks this: "Is this 'new' change just the result of the data I just loaded?" If yes, it ignores it.
|
|
77
|
+
* **`hasNormalizedBaselineRef`:**
|
|
78
|
+
* This acts as a gatekeeper. It starts as `false`.
|
|
79
|
+
* It prevents the editor from emitting *any* changes until the initial value has been fully loaded and normalized. This stops the editor from accidentally overwriting the database with an empty state during the split-second it takes to mount.
|
|
80
|
+
|
|
81
|
+
### Summary Flow
|
|
82
|
+
1. **User types:** `handleChange` triggers.
|
|
83
|
+
2. **Debounce:** `dispatchFieldUpdateTask` ensures we wait for a pause.
|
|
84
|
+
3. **Check Baseline:** `hasNormalizedBaselineRef` ensures we aren't just booting up.
|
|
85
|
+
4. **Check Inbound:** `normalizedIncomingHashRef` ensures we aren't just reporting Lexical's own auto-formatting of the data we just gave it.
|
|
86
|
+
5. **Check Outbound:** `lastEmittedHashRef` ensures we aren't reporting the same thing twice.
|
|
87
|
+
6. **Success:** Only then do we call `onChange`.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { act } from 'react'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
SerializedEditorState,
|
|
5
|
+
SerializedElementNode,
|
|
6
|
+
SerializedRootNode,
|
|
7
|
+
SerializedTextNode,
|
|
8
|
+
} from 'lexical'
|
|
9
|
+
import { createRoot } from 'react-dom/client'
|
|
10
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
11
|
+
|
|
12
|
+
vi.mock('./editor-component.css', () => ({}))
|
|
13
|
+
vi.mock('./themes/lexical-editor-theme.css', () => ({}))
|
|
14
|
+
vi.mock('@infonomic/uikit/react', () => ({
|
|
15
|
+
HelpText: () => null,
|
|
16
|
+
Label: () => null,
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
import { ApplyValuePlugin } from './apply-value-plugin'
|
|
20
|
+
import { hashSerializedState } from './utils/hashSerializedState'
|
|
21
|
+
|
|
22
|
+
// Enable React act warnings suppression for this environment
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true
|
|
25
|
+
|
|
26
|
+
// Mock the Lexical composer context to avoid spinning up a real editor
|
|
27
|
+
const mockEditor = {
|
|
28
|
+
update: (fn: () => void) => fn(),
|
|
29
|
+
parseEditorState: vi.fn((val) => val),
|
|
30
|
+
setEditorState: vi.fn(),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
vi.mock('@lexical/react/LexicalComposerContext', () => {
|
|
34
|
+
return {
|
|
35
|
+
useLexicalComposerContext: () => [mockEditor],
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('ApplyValuePlugin', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
mockEditor.parseEditorState.mockClear()
|
|
42
|
+
mockEditor.setEditorState.mockClear()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const stateA: SerializedEditorState = {
|
|
46
|
+
root: {
|
|
47
|
+
children: [],
|
|
48
|
+
direction: 'ltr',
|
|
49
|
+
format: '',
|
|
50
|
+
indent: 0,
|
|
51
|
+
type: 'root',
|
|
52
|
+
version: 1,
|
|
53
|
+
} as SerializedRootNode,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const stateB: SerializedEditorState = {
|
|
57
|
+
root: {
|
|
58
|
+
children: [
|
|
59
|
+
{
|
|
60
|
+
children: [
|
|
61
|
+
{
|
|
62
|
+
detail: 0,
|
|
63
|
+
format: 0,
|
|
64
|
+
mode: 'normal',
|
|
65
|
+
style: '',
|
|
66
|
+
text: 'Hello world',
|
|
67
|
+
type: 'text',
|
|
68
|
+
version: 1,
|
|
69
|
+
} as SerializedTextNode,
|
|
70
|
+
],
|
|
71
|
+
direction: 'ltr',
|
|
72
|
+
format: '',
|
|
73
|
+
indent: 0,
|
|
74
|
+
type: 'paragraph',
|
|
75
|
+
version: 1,
|
|
76
|
+
} as SerializedElementNode,
|
|
77
|
+
],
|
|
78
|
+
direction: 'ltr',
|
|
79
|
+
format: '',
|
|
80
|
+
indent: 0,
|
|
81
|
+
type: 'root',
|
|
82
|
+
version: 1,
|
|
83
|
+
} as SerializedRootNode,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
it('applies incoming value once and ignores identical hashes', async () => {
|
|
87
|
+
const container = document.createElement('div')
|
|
88
|
+
const root = createRoot(container)
|
|
89
|
+
const lastEmitted = { current: undefined as string | undefined }
|
|
90
|
+
const normalizedIncoming = { current: undefined as string | undefined }
|
|
91
|
+
const hasNormalizedBaseline = { current: false }
|
|
92
|
+
|
|
93
|
+
const hashA = hashSerializedState(stateA)
|
|
94
|
+
|
|
95
|
+
await act(async () => {
|
|
96
|
+
root.render(
|
|
97
|
+
<ApplyValuePlugin
|
|
98
|
+
value={stateA}
|
|
99
|
+
incomingHash={hashA}
|
|
100
|
+
lastEmittedHashRef={lastEmitted}
|
|
101
|
+
normalizedIncomingHashRef={normalizedIncoming}
|
|
102
|
+
hasNormalizedBaselineRef={hasNormalizedBaseline}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
expect(mockEditor.setEditorState).toHaveBeenCalledTimes(1)
|
|
108
|
+
expect(mockEditor.setEditorState).toHaveBeenLastCalledWith(stateA)
|
|
109
|
+
|
|
110
|
+
// Re-render with same value -> no new apply
|
|
111
|
+
await act(async () => {
|
|
112
|
+
root.render(
|
|
113
|
+
<ApplyValuePlugin
|
|
114
|
+
value={stateA}
|
|
115
|
+
incomingHash={hashA}
|
|
116
|
+
lastEmittedHashRef={lastEmitted}
|
|
117
|
+
normalizedIncomingHashRef={normalizedIncoming}
|
|
118
|
+
hasNormalizedBaselineRef={hasNormalizedBaseline}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
})
|
|
122
|
+
expect(mockEditor.setEditorState).toHaveBeenCalledTimes(1)
|
|
123
|
+
|
|
124
|
+
// New value -> apply again
|
|
125
|
+
const hashB = hashSerializedState(stateB)
|
|
126
|
+
await act(async () => {
|
|
127
|
+
root.render(
|
|
128
|
+
<ApplyValuePlugin
|
|
129
|
+
value={stateB}
|
|
130
|
+
incomingHash={hashB}
|
|
131
|
+
lastEmittedHashRef={lastEmitted}
|
|
132
|
+
normalizedIncomingHashRef={normalizedIncoming}
|
|
133
|
+
hasNormalizedBaselineRef={hasNormalizedBaseline}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
expect(mockEditor.setEditorState).toHaveBeenCalledTimes(2)
|
|
138
|
+
expect(mockEditor.setEditorState).toHaveBeenLastCalledWith(stateB)
|
|
139
|
+
|
|
140
|
+
await act(async () => {
|
|
141
|
+
root.unmount()
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('skips apply when incoming hash matches last emitted (echo prevention)', async () => {
|
|
146
|
+
const container = document.createElement('div')
|
|
147
|
+
const root = createRoot(container)
|
|
148
|
+
const lastEmitted = { current: hashSerializedState(stateA) }
|
|
149
|
+
const normalizedIncoming = { current: undefined as string | undefined }
|
|
150
|
+
const hasNormalizedBaseline = { current: false }
|
|
151
|
+
const hashA = hashSerializedState(stateA)
|
|
152
|
+
|
|
153
|
+
await act(async () => {
|
|
154
|
+
root.render(
|
|
155
|
+
<ApplyValuePlugin
|
|
156
|
+
value={stateA}
|
|
157
|
+
incomingHash={hashA}
|
|
158
|
+
lastEmittedHashRef={lastEmitted}
|
|
159
|
+
normalizedIncomingHashRef={normalizedIncoming}
|
|
160
|
+
hasNormalizedBaselineRef={hasNormalizedBaseline}
|
|
161
|
+
/>
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
expect(mockEditor.setEditorState).not.toHaveBeenCalled()
|
|
166
|
+
await act(async () => {
|
|
167
|
+
root.unmount()
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Infonomic Company Limited
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type React from 'react'
|
|
12
|
+
import { memo, useCallback, useMemo, useRef } from 'react'
|
|
13
|
+
|
|
14
|
+
import { HelpText, Label } from '@infonomic/uikit/react'
|
|
15
|
+
import type { EditorState, SerializedEditorState } from 'lexical'
|
|
16
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
17
|
+
|
|
18
|
+
import { richTextValidate } from '../validate/validate'
|
|
19
|
+
import { ApplyValuePlugin } from './apply-value-plugin'
|
|
20
|
+
import { EditorContext } from './editor-context'
|
|
21
|
+
import { hashSerializedState } from './utils/hashSerializedState'
|
|
22
|
+
import type { EditorFieldProps } from '../types'
|
|
23
|
+
|
|
24
|
+
import './editor-component.css'
|
|
25
|
+
import './themes/lexical-editor-theme.css'
|
|
26
|
+
|
|
27
|
+
const baseClass = 'lexicalRichTextEditor'
|
|
28
|
+
|
|
29
|
+
// NOTE: @See ./editor-component.md mini doc for an explanation of our editor architecture.
|
|
30
|
+
|
|
31
|
+
// We memoize the EditorComponent to prevent re-renders from parent components or
|
|
32
|
+
// other editor instances. Only internal state changes for a given (this)
|
|
33
|
+
// editor instance should trigger re-renders. Our form-context and value handlers
|
|
34
|
+
// are subscription-based and so in theory this shouldn't be necessary, but
|
|
35
|
+
// here just in case.
|
|
36
|
+
export const EditorComponent = memo(function EditorComponent({
|
|
37
|
+
name,
|
|
38
|
+
id,
|
|
39
|
+
label,
|
|
40
|
+
description,
|
|
41
|
+
required,
|
|
42
|
+
readonly,
|
|
43
|
+
editorConfig,
|
|
44
|
+
defaultValue,
|
|
45
|
+
value,
|
|
46
|
+
onChange,
|
|
47
|
+
minHeight,
|
|
48
|
+
maxHeight,
|
|
49
|
+
validate = richTextValidate,
|
|
50
|
+
onError: _onError,
|
|
51
|
+
lexicalEditorProps: _lexicalEditorProps,
|
|
52
|
+
featureBeforeEditor,
|
|
53
|
+
featureAfterEditor,
|
|
54
|
+
featureChildren,
|
|
55
|
+
}: EditorFieldProps): React.JSX.Element {
|
|
56
|
+
const disabled = readonly ?? false
|
|
57
|
+
const dispatchFieldUpdateTask = useRef<number>(undefined)
|
|
58
|
+
const valueRef = useRef(value)
|
|
59
|
+
valueRef.current = value
|
|
60
|
+
const initialValueRef = useRef(defaultValue)
|
|
61
|
+
initialValueRef.current = defaultValue
|
|
62
|
+
const lastEmittedHashRef = useRef<string | undefined>(undefined)
|
|
63
|
+
const normalizedIncomingHashRef = useRef<string | undefined>(undefined)
|
|
64
|
+
const hasNormalizedBaselineRef = useRef<boolean>(false)
|
|
65
|
+
// const _debugLogCountRef = useRef<number>(0)
|
|
66
|
+
|
|
67
|
+
// TODO: implement validation handling (currently unused)
|
|
68
|
+
const _memoizedValidate = useCallback(
|
|
69
|
+
(val: SerializedEditorState | undefined) => {
|
|
70
|
+
if (typeof validate === 'function') {
|
|
71
|
+
return validate(val, { required })
|
|
72
|
+
}
|
|
73
|
+
return true
|
|
74
|
+
},
|
|
75
|
+
[validate, required]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// Debounced onChange -> emit to store
|
|
79
|
+
const handleChange = useCallback(
|
|
80
|
+
(editorState: EditorState, _editor: unknown, _tags?: Set<string>) => {
|
|
81
|
+
// const _capturedTags = tags != null ? Array.from(tags) : []
|
|
82
|
+
const updateFieldValue = (editorState: EditorState) => {
|
|
83
|
+
const newState = editorState.toJSON()
|
|
84
|
+
const nextHash = hashSerializedState(newState)
|
|
85
|
+
|
|
86
|
+
// If we have an incoming form value but haven't established a normalized baseline yet,
|
|
87
|
+
// ignore mount-time normalization updates (often appear as tags: []).
|
|
88
|
+
if (
|
|
89
|
+
(valueRef.current ?? initialValueRef.current) != null &&
|
|
90
|
+
hasNormalizedBaselineRef.current !== true
|
|
91
|
+
) {
|
|
92
|
+
// if (process.env.NODE_ENV === 'production' && _debugLogCountRef.current < 10) {
|
|
93
|
+
// _debugLogCountRef.current++
|
|
94
|
+
// // eslint-disable-next-line no-console
|
|
95
|
+
// console.log('[lexical][payload][skip] waiting baseline', {
|
|
96
|
+
// tags: _capturedTags,
|
|
97
|
+
// nextHash,
|
|
98
|
+
// normalizedIncomingHash: normalizedIncomingHashRef.current,
|
|
99
|
+
// lastEmittedHash: lastEmittedHashRef.current,
|
|
100
|
+
// })
|
|
101
|
+
// }
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
// Prefer comparing against Lexical-normalized incoming JSON (critical for nested editors).
|
|
105
|
+
if (
|
|
106
|
+
normalizedIncomingHashRef.current != null &&
|
|
107
|
+
nextHash === normalizedIncomingHashRef.current
|
|
108
|
+
) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Also avoid re-emitting the exact same state multiple times.
|
|
113
|
+
if (lastEmittedHashRef.current != null && nextHash === lastEmittedHashRef.current) return
|
|
114
|
+
|
|
115
|
+
lastEmittedHashRef.current = nextHash
|
|
116
|
+
|
|
117
|
+
// if (process.env.NODE_ENV === 'production' && _debugLogCountRef.current < 10) {
|
|
118
|
+
// _debugLogCountRef.current++
|
|
119
|
+
// // eslint-disable-next-line no-console
|
|
120
|
+
// console.log('[lexical][payload][setValue]', {
|
|
121
|
+
// tags: _capturedTags,
|
|
122
|
+
// nextHash,
|
|
123
|
+
// normalizedIncomingHash: normalizedIncomingHashRef.current,
|
|
124
|
+
// rawIncomingHash: rawIncomingHashRef.current,
|
|
125
|
+
// lastEmittedHash: lastEmittedHashRef.current,
|
|
126
|
+
// })
|
|
127
|
+
// }
|
|
128
|
+
|
|
129
|
+
if (typeof onChange === 'function') {
|
|
130
|
+
onChange(newState)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
135
|
+
// Cancel earlier scheduled value updates,
|
|
136
|
+
// so that a CPU-limited event loop isn't flooded with n callbacks for n keystrokes
|
|
137
|
+
// into the rich text field, but that there's only ever the latest one state update
|
|
138
|
+
// dispatch task, to be executed with the next idle time,
|
|
139
|
+
// or the deadline of 500ms.
|
|
140
|
+
if (typeof window.cancelIdleCallback === 'function' && dispatchFieldUpdateTask.current) {
|
|
141
|
+
cancelIdleCallback(dispatchFieldUpdateTask.current)
|
|
142
|
+
}
|
|
143
|
+
// Schedule the state update to happen the next time the browser has sufficient resources,
|
|
144
|
+
// or the latest after 500ms.
|
|
145
|
+
dispatchFieldUpdateTask.current = requestIdleCallback(() => updateFieldValue(editorState), {
|
|
146
|
+
timeout: 500,
|
|
147
|
+
})
|
|
148
|
+
} else {
|
|
149
|
+
updateFieldValue(editorState)
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
[onChange]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const incomingValue = value ?? defaultValue ?? undefined
|
|
156
|
+
|
|
157
|
+
const incomingHash = useMemo(
|
|
158
|
+
() => (incomingValue != null ? hashSerializedState(incomingValue) : undefined),
|
|
159
|
+
[incomingValue]
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className={baseClass}>
|
|
164
|
+
<div className={`${baseClass}__wrap`}>
|
|
165
|
+
{label != null &&
|
|
166
|
+
(typeof label === 'string' ? (
|
|
167
|
+
<Label id={`${id}-label`} label={label} htmlFor={id} required={required} />
|
|
168
|
+
) : (
|
|
169
|
+
label
|
|
170
|
+
))}
|
|
171
|
+
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
|
|
172
|
+
<EditorContext
|
|
173
|
+
composerKey={id}
|
|
174
|
+
editorConfig={editorConfig}
|
|
175
|
+
key={id}
|
|
176
|
+
onChange={handleChange}
|
|
177
|
+
readOnly={disabled}
|
|
178
|
+
value={incomingValue}
|
|
179
|
+
minHeight={minHeight}
|
|
180
|
+
maxHeight={maxHeight}
|
|
181
|
+
beforeEditor={featureBeforeEditor}
|
|
182
|
+
afterEditor={featureAfterEditor}
|
|
183
|
+
>
|
|
184
|
+
<ApplyValuePlugin
|
|
185
|
+
value={incomingValue}
|
|
186
|
+
incomingHash={incomingHash}
|
|
187
|
+
lastEmittedHashRef={lastEmittedHashRef}
|
|
188
|
+
normalizedIncomingHashRef={normalizedIncomingHashRef}
|
|
189
|
+
hasNormalizedBaselineRef={hasNormalizedBaselineRef}
|
|
190
|
+
/>
|
|
191
|
+
{featureChildren}
|
|
192
|
+
</EditorContext>
|
|
193
|
+
</ErrorBoundary>
|
|
194
|
+
{description && <HelpText text={description} />}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
function fallbackRender({ error }: any): React.JSX.Element {
|
|
201
|
+
return (
|
|
202
|
+
<div className="errorBoundary" role="alert">
|
|
203
|
+
<p>Something went wrong:</p>
|
|
204
|
+
<pre style={{ color: 'red' }}>{error.message}</pre>
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Infonomic Company Limited
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type * as React from 'react'
|
|
12
|
+
import { useMemo } from 'react'
|
|
13
|
+
|
|
14
|
+
import type { InitialConfigType } from '@lexical/react/LexicalComposer'
|
|
15
|
+
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
|
16
|
+
import type { EditorState, LexicalEditor, SerializedEditorState } from 'lexical'
|
|
17
|
+
|
|
18
|
+
import { EditorConfigContext } from './config/editor-config-context'
|
|
19
|
+
import { SharedHistoryContext } from './context/shared-history-context'
|
|
20
|
+
import { SharedOnChangeContext } from './context/shared-on-change-context'
|
|
21
|
+
import { Editor } from './editor'
|
|
22
|
+
import { Nodes } from './nodes'
|
|
23
|
+
import { ToolbarExtensionsProvider } from './toolbar-extensions'
|
|
24
|
+
import type { EditorConfig } from './config/types'
|
|
25
|
+
|
|
26
|
+
// Catch any errors that occur during Lexical updates and log them
|
|
27
|
+
// or throw them as needed. If you don't throw them, Lexical will
|
|
28
|
+
// try to recover gracefully without losing user data.
|
|
29
|
+
function _onError(error: Error, _editor: LexicalEditor): void {
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.error(error)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function EditorContext(props: {
|
|
35
|
+
composerKey: string
|
|
36
|
+
editorConfig: EditorConfig
|
|
37
|
+
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
|
|
38
|
+
readOnly: boolean
|
|
39
|
+
value?: SerializedEditorState
|
|
40
|
+
minHeight?: number | string
|
|
41
|
+
maxHeight?: number | string
|
|
42
|
+
children?: React.ReactNode
|
|
43
|
+
beforeEditor?: React.ReactNode[]
|
|
44
|
+
afterEditor?: React.ReactNode[]
|
|
45
|
+
}): React.JSX.Element {
|
|
46
|
+
const {
|
|
47
|
+
composerKey,
|
|
48
|
+
editorConfig,
|
|
49
|
+
onChange,
|
|
50
|
+
readOnly,
|
|
51
|
+
value,
|
|
52
|
+
beforeEditor,
|
|
53
|
+
afterEditor,
|
|
54
|
+
children,
|
|
55
|
+
} = props
|
|
56
|
+
|
|
57
|
+
// useMemo for the initialConfig that depends on readOnly and value
|
|
58
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: TODO: revisit
|
|
59
|
+
const initialConfig = useMemo<InitialConfigType>(() => {
|
|
60
|
+
return {
|
|
61
|
+
editable: readOnly !== true,
|
|
62
|
+
editorState: value != null ? JSON.stringify(value) : undefined,
|
|
63
|
+
namespace: editorConfig.lexical.namespace,
|
|
64
|
+
nodes: [...Nodes],
|
|
65
|
+
onError: (error: Error) => {
|
|
66
|
+
throw error
|
|
67
|
+
},
|
|
68
|
+
theme: editorConfig.lexical.theme,
|
|
69
|
+
}
|
|
70
|
+
// Important: do not add readOnly and value to the dependencies array.
|
|
71
|
+
// This will cause the entire lexical editor to re-render if the document
|
|
72
|
+
// is saved, which will cause the editor to lose focus.
|
|
73
|
+
|
|
74
|
+
// NOTE: 2025-04-26: This is NOT the case for our version of the editor.
|
|
75
|
+
// Without readOnly as a dependency, the editor will never transition
|
|
76
|
+
// from readOnly to editable during form loading, when disabledFromField
|
|
77
|
+
// in field-component will be briefly false.
|
|
78
|
+
}, [editorConfig, readOnly])
|
|
79
|
+
|
|
80
|
+
if (initialConfig == null) {
|
|
81
|
+
return <p>Loading...</p>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<LexicalComposer initialConfig={initialConfig} key={composerKey + initialConfig.editable}>
|
|
86
|
+
<EditorConfigContext config={editorConfig.settings}>
|
|
87
|
+
<SharedOnChangeContext onChange={onChange}>
|
|
88
|
+
<SharedHistoryContext>
|
|
89
|
+
<ToolbarExtensionsProvider>
|
|
90
|
+
<div className="editor-shell">
|
|
91
|
+
{beforeEditor}
|
|
92
|
+
<Editor minHeight={props.minHeight} maxHeight={props.maxHeight} />
|
|
93
|
+
{afterEditor}
|
|
94
|
+
{children}
|
|
95
|
+
</div>
|
|
96
|
+
</ToolbarExtensionsProvider>
|
|
97
|
+
</SharedHistoryContext>
|
|
98
|
+
</SharedOnChangeContext>
|
|
99
|
+
</EditorConfigContext>
|
|
100
|
+
</LexicalComposer>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portions Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
5
|
+
* Copyright notices appear at the top of source files where applicable
|
|
6
|
+
* and are licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* https://github.com/facebook/lexical
|
|
10
|
+
*
|
|
11
|
+
* Portions Copyright (c) Payload CMS, LLC info@payloadcms.com
|
|
12
|
+
* Copyright notices appear at the top of source files where applicable
|
|
13
|
+
* and are licensed under the MIT license found in the
|
|
14
|
+
* LICENSE file in the root directory of this source tree
|
|
15
|
+
*
|
|
16
|
+
* https://github.com/payloadcms/payload/
|
|
17
|
+
*
|
|
18
|
+
*
|
|
19
|
+
* Note: For historical context see...
|
|
20
|
+
*
|
|
21
|
+
* https://github.com/facebook/lexical/commits?author=58bits
|
|
22
|
+
* https://github.com/infonomic/payload-alternative-lexical-richtext-editor
|
|
23
|
+
* https://github.com/AlessioGr/payload-plugin-lexical/commits?author=58bits
|
|
24
|
+
* https://github.com/payloadcms/payload/commits?author=58bits
|
|
25
|
+
*
|
|
26
|
+
*
|
|
27
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
28
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
29
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
30
|
+
*
|
|
31
|
+
* Copyright (c) Infonomic Company Limited
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type * as React from 'react'
|
|
35
|
+
import { lazy, Suspense } from 'react'
|
|
36
|
+
|
|
37
|
+
import { Shimmer } from '@infonomic/uikit/react'
|
|
38
|
+
|
|
39
|
+
import type { EditorFieldProps } from '../types'
|
|
40
|
+
|
|
41
|
+
const EditorComponent = lazy(() =>
|
|
42
|
+
import('./editor-component').then((module) => ({ default: module.EditorComponent }))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export function EditorField(props: EditorFieldProps): React.JSX.Element {
|
|
46
|
+
return (
|
|
47
|
+
<Suspense fallback={<Shimmer height="35vh" />}>
|
|
48
|
+
<EditorComponent {...props} />
|
|
49
|
+
</Suspense>
|
|
50
|
+
)
|
|
51
|
+
}
|