@rowsncolumns/ui 15.0.38
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.md +100 -0
- package/dist/cjs/arrow/index.d.ts +17 -0
- package/dist/cjs/arrow/index.d.ts.map +1 -0
- package/dist/cjs/arrow/index.js +46 -0
- package/dist/cjs/button/index.d.ts +14 -0
- package/dist/cjs/button/index.d.ts.map +1 -0
- package/dist/cjs/button/index.js +46 -0
- package/dist/cjs/calendar/index.d.ts +5 -0
- package/dist/cjs/calendar/index.d.ts.map +1 -0
- package/dist/cjs/calendar/index.js +13 -0
- package/dist/cjs/card/index.d.ts +10 -0
- package/dist/cjs/card/index.d.ts.map +1 -0
- package/dist/cjs/card/index.js +32 -0
- package/dist/cjs/cell-tooltip/index.d.ts +17 -0
- package/dist/cjs/cell-tooltip/index.d.ts.map +1 -0
- package/dist/cjs/cell-tooltip/index.js +38 -0
- package/dist/cjs/circular-loader/index.d.ts +9 -0
- package/dist/cjs/circular-loader/index.d.ts.map +1 -0
- package/dist/cjs/circular-loader/index.js +11 -0
- package/dist/cjs/color-picker/index.d.ts +12 -0
- package/dist/cjs/color-picker/index.d.ts.map +1 -0
- package/dist/cjs/color-picker/index.js +101 -0
- package/dist/cjs/color-picker/style.d.ts +5 -0
- package/dist/cjs/color-picker/style.d.ts.map +1 -0
- package/dist/cjs/color-picker/style.js +11 -0
- package/dist/cjs/command/index.d.ts +74 -0
- package/dist/cjs/command/index.d.ts.map +1 -0
- package/dist/cjs/command/index.js +37 -0
- package/dist/cjs/conditional-wrapper/index.d.ts +9 -0
- package/dist/cjs/conditional-wrapper/index.d.ts.map +1 -0
- package/dist/cjs/conditional-wrapper/index.js +4 -0
- package/dist/cjs/dialog/index.d.ts +17 -0
- package/dist/cjs/dialog/index.d.ts.map +1 -0
- package/dist/cjs/dialog/index.js +50 -0
- package/dist/cjs/dropdown/index.d.ts +34 -0
- package/dist/cjs/dropdown/index.d.ts.map +1 -0
- package/dist/cjs/dropdown/index.js +73 -0
- package/dist/cjs/editor/constants.d.ts +7 -0
- package/dist/cjs/editor/constants.d.ts.map +1 -0
- package/dist/cjs/editor/constants.js +50 -0
- package/dist/cjs/editor/editor-error-boundary.d.ts +3 -0
- package/dist/cjs/editor/editor-error-boundary.d.ts.map +1 -0
- package/dist/cjs/editor/editor-error-boundary.js +9 -0
- package/dist/cjs/editor/style.d.ts +12 -0
- package/dist/cjs/editor/style.d.ts.map +1 -0
- package/dist/cjs/editor/style.js +24 -0
- package/dist/cjs/editor/suggestions-dropdown.d.ts +25 -0
- package/dist/cjs/editor/suggestions-dropdown.d.ts.map +1 -0
- package/dist/cjs/editor/suggestions-dropdown.js +120 -0
- package/dist/cjs/editor-pm/constants.d.ts +2 -0
- package/dist/cjs/editor-pm/constants.d.ts.map +1 -0
- package/dist/cjs/editor-pm/constants.js +2 -0
- package/dist/cjs/editor-pm/decoration-plugin.d.ts +15 -0
- package/dist/cjs/editor-pm/decoration-plugin.d.ts.map +1 -0
- package/dist/cjs/editor-pm/decoration-plugin.js +38 -0
- package/dist/cjs/editor-pm/helpers.d.ts +197 -0
- package/dist/cjs/editor-pm/helpers.d.ts.map +1 -0
- package/dist/cjs/editor-pm/helpers.js +1170 -0
- package/dist/cjs/editor-pm/index.d.ts +10 -0
- package/dist/cjs/editor-pm/index.d.ts.map +1 -0
- package/dist/cjs/editor-pm/index.js +9 -0
- package/dist/cjs/editor-pm/mention-nodeview.d.ts +31 -0
- package/dist/cjs/editor-pm/mention-nodeview.d.ts.map +1 -0
- package/dist/cjs/editor-pm/mention-nodeview.js +105 -0
- package/dist/cjs/editor-pm/mentions-dropdown.d.ts +13 -0
- package/dist/cjs/editor-pm/mentions-dropdown.d.ts.map +1 -0
- package/dist/cjs/editor-pm/mentions-dropdown.js +19 -0
- package/dist/cjs/editor-pm/mentions-plugin.d.ts +18 -0
- package/dist/cjs/editor-pm/mentions-plugin.d.ts.map +1 -0
- package/dist/cjs/editor-pm/mentions-plugin.js +72 -0
- package/dist/cjs/editor-pm/placeholder-plugin.d.ts +4 -0
- package/dist/cjs/editor-pm/placeholder-plugin.d.ts.map +1 -0
- package/dist/cjs/editor-pm/placeholder-plugin.js +20 -0
- package/dist/cjs/editor-pm/prosemirror-editor.d.ts +493 -0
- package/dist/cjs/editor-pm/prosemirror-editor.d.ts.map +1 -0
- package/dist/cjs/editor-pm/prosemirror-editor.js +1145 -0
- package/dist/cjs/editor-pm/schema.d.ts +3 -0
- package/dist/cjs/editor-pm/schema.d.ts.map +1 -0
- package/dist/cjs/editor-pm/schema.js +183 -0
- package/dist/cjs/editor-pm/types.d.ts +8 -0
- package/dist/cjs/editor-pm/types.d.ts.map +1 -0
- package/dist/cjs/editor-pm/types.js +1 -0
- package/dist/cjs/editor-pm/unfocused-selection-plugin.d.ts +25 -0
- package/dist/cjs/editor-pm/unfocused-selection-plugin.d.ts.map +1 -0
- package/dist/cjs/editor-pm/unfocused-selection-plugin.js +61 -0
- package/dist/cjs/editor-pm/use-dropdown-change-pm.d.ts +24 -0
- package/dist/cjs/editor-pm/use-dropdown-change-pm.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-dropdown-change-pm.js +140 -0
- package/dist/cjs/editor-pm/use-editor-change-pm.d.ts +29 -0
- package/dist/cjs/editor-pm/use-editor-change-pm.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-editor-change-pm.js +154 -0
- package/dist/cjs/editor-pm/use-editor-decorator-pm.d.ts +17 -0
- package/dist/cjs/editor-pm/use-editor-decorator-pm.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-editor-decorator-pm.js +59 -0
- package/dist/cjs/editor-pm/use-editor-keydown-pm.d.ts +21 -0
- package/dist/cjs/editor-pm/use-editor-keydown-pm.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-editor-keydown-pm.js +81 -0
- package/dist/cjs/editor-pm/use-mention-keydown.d.ts +11 -0
- package/dist/cjs/editor-pm/use-mention-keydown.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-mention-keydown.js +59 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.d.ts +19 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.js +79 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.test.d.ts +2 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.test.d.ts.map +1 -0
- package/dist/cjs/editor-pm/use-parameter-suggestions.test.js +122 -0
- package/dist/cjs/eye-dropper/index.d.ts +10 -0
- package/dist/cjs/eye-dropper/index.d.ts.map +1 -0
- package/dist/cjs/eye-dropper/index.js +24 -0
- package/dist/cjs/function-info/helpers.d.ts +10 -0
- package/dist/cjs/function-info/helpers.d.ts.map +1 -0
- package/dist/cjs/function-info/helpers.js +20 -0
- package/dist/cjs/function-info/index.d.ts +11 -0
- package/dist/cjs/function-info/index.d.ts.map +1 -0
- package/dist/cjs/function-info/index.js +71 -0
- package/dist/cjs/function-info/style.d.ts +7 -0
- package/dist/cjs/function-info/style.d.ts.map +1 -0
- package/dist/cjs/function-info/style.js +13 -0
- package/dist/cjs/hex-color-picker/index.d.ts +8 -0
- package/dist/cjs/hex-color-picker/index.d.ts.map +1 -0
- package/dist/cjs/hex-color-picker/index.js +47 -0
- package/dist/cjs/hooks/use-cycle-reference.d.ts +16 -0
- package/dist/cjs/hooks/use-cycle-reference.d.ts.map +1 -0
- package/dist/cjs/hooks/use-cycle-reference.js +72 -0
- package/dist/cjs/hooks/use-dropdown-categories.d.ts +9 -0
- package/dist/cjs/hooks/use-dropdown-categories.d.ts.map +1 -0
- package/dist/cjs/hooks/use-dropdown-categories.js +45 -0
- package/dist/cjs/hooks/use-dropdown.d.ts +38 -0
- package/dist/cjs/hooks/use-dropdown.d.ts.map +1 -0
- package/dist/cjs/hooks/use-dropdown.js +236 -0
- package/dist/cjs/hooks/use-isomorphic-layout-effect.ts.d.ts +6 -0
- package/dist/cjs/hooks/use-isomorphic-layout-effect.ts.d.ts.map +1 -0
- package/dist/cjs/hooks/use-isomorphic-layout-effect.ts.js +8 -0
- package/dist/cjs/hooks/use-modal.d.ts +28 -0
- package/dist/cjs/hooks/use-modal.d.ts.map +1 -0
- package/dist/cjs/hooks/use-modal.js +101 -0
- package/dist/cjs/hooks/use-visibility.d.ts +2 -0
- package/dist/cjs/hooks/use-visibility.d.ts.map +1 -0
- package/dist/cjs/hooks/use-visibility.js +12 -0
- package/dist/cjs/icon-button/index.d.ts +15 -0
- package/dist/cjs/icon-button/index.d.ts.map +1 -0
- package/dist/cjs/icon-button/index.js +34 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/input/index.d.ts +9 -0
- package/dist/cjs/input/index.d.ts.map +1 -0
- package/dist/cjs/input/index.js +9 -0
- package/dist/cjs/popover/index.d.ts +13 -0
- package/dist/cjs/popover/index.d.ts.map +1 -0
- package/dist/cjs/popover/index.js +29 -0
- package/dist/cjs/progress/index.d.ts +10 -0
- package/dist/cjs/progress/index.d.ts.map +1 -0
- package/dist/cjs/progress/index.js +15 -0
- package/dist/cjs/select/index.d.ts +14 -0
- package/dist/cjs/select/index.d.ts.map +1 -0
- package/dist/cjs/select/index.js +46 -0
- package/dist/cjs/separator/index.d.ts +5 -0
- package/dist/cjs/separator/index.d.ts.map +1 -0
- package/dist/cjs/separator/index.js +10 -0
- package/dist/cjs/skeleton/index.d.ts +7 -0
- package/dist/cjs/skeleton/index.d.ts.map +1 -0
- package/dist/cjs/skeleton/index.js +11 -0
- package/dist/cjs/stack/index.d.ts +11 -0
- package/dist/cjs/stack/index.d.ts.map +1 -0
- package/dist/cjs/stack/index.js +22 -0
- package/dist/cjs/tabs/index.d.ts +8 -0
- package/dist/cjs/tabs/index.d.ts.map +1 -0
- package/dist/cjs/tabs/index.js +21 -0
- package/dist/cjs/tooltip/index.d.ts +12 -0
- package/dist/cjs/tooltip/index.d.ts.map +1 -0
- package/dist/cjs/tooltip/index.js +20 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +9 -0
- package/dist/cjs/vitest.config.d.ts +3 -0
- package/dist/cjs/vitest.config.d.ts.map +1 -0
- package/dist/cjs/vitest.config.js +21 -0
- package/dist/esm/arrow/index.d.ts +17 -0
- package/dist/esm/arrow/index.d.ts.map +1 -0
- package/dist/esm/arrow/index.js +45 -0
- package/dist/esm/button/index.d.ts +14 -0
- package/dist/esm/button/index.d.ts.map +1 -0
- package/dist/esm/button/index.js +44 -0
- package/dist/esm/calendar/index.d.ts +5 -0
- package/dist/esm/calendar/index.d.ts.map +1 -0
- package/dist/esm/calendar/index.js +34 -0
- package/dist/esm/card/index.d.ts +10 -0
- package/dist/esm/card/index.d.ts.map +1 -0
- package/dist/esm/card/index.js +24 -0
- package/dist/esm/cell-tooltip/index.d.ts +17 -0
- package/dist/esm/cell-tooltip/index.d.ts.map +1 -0
- package/dist/esm/cell-tooltip/index.js +31 -0
- package/dist/esm/circular-loader/index.d.ts +9 -0
- package/dist/esm/circular-loader/index.d.ts.map +1 -0
- package/dist/esm/circular-loader/index.js +7 -0
- package/dist/esm/color-picker/index.d.ts +12 -0
- package/dist/esm/color-picker/index.d.ts.map +1 -0
- package/dist/esm/color-picker/index.js +99 -0
- package/dist/esm/color-picker/style.d.ts +5 -0
- package/dist/esm/color-picker/style.d.ts.map +1 -0
- package/dist/esm/color-picker/style.js +7 -0
- package/dist/esm/command/index.d.ts +74 -0
- package/dist/esm/command/index.d.ts.map +1 -0
- package/dist/esm/command/index.js +18 -0
- package/dist/esm/conditional-wrapper/index.d.ts +9 -0
- package/dist/esm/conditional-wrapper/index.d.ts.map +1 -0
- package/dist/esm/conditional-wrapper/index.js +1 -0
- package/dist/esm/dialog/index.d.ts +17 -0
- package/dist/esm/dialog/index.d.ts.map +1 -0
- package/dist/esm/dialog/index.js +30 -0
- package/dist/esm/dropdown/index.d.ts +34 -0
- package/dist/esm/dropdown/index.d.ts.map +1 -0
- package/dist/esm/dropdown/index.js +42 -0
- package/dist/esm/editor/constants.d.ts +7 -0
- package/dist/esm/editor/constants.d.ts.map +1 -0
- package/dist/esm/editor/constants.js +51 -0
- package/dist/esm/editor/editor-error-boundary.d.ts +3 -0
- package/dist/esm/editor/editor-error-boundary.d.ts.map +1 -0
- package/dist/esm/editor/editor-error-boundary.js +9 -0
- package/dist/esm/editor/style.d.ts +12 -0
- package/dist/esm/editor/style.d.ts.map +1 -0
- package/dist/esm/editor/style.js +17 -0
- package/dist/esm/editor/suggestions-dropdown.d.ts +25 -0
- package/dist/esm/editor/suggestions-dropdown.d.ts.map +1 -0
- package/dist/esm/editor/suggestions-dropdown.js +115 -0
- package/dist/esm/editor-pm/constants.d.ts +2 -0
- package/dist/esm/editor-pm/constants.d.ts.map +1 -0
- package/dist/esm/editor-pm/constants.js +2 -0
- package/dist/esm/editor-pm/decoration-plugin.d.ts +15 -0
- package/dist/esm/editor-pm/decoration-plugin.d.ts.map +1 -0
- package/dist/esm/editor-pm/decoration-plugin.js +38 -0
- package/dist/esm/editor-pm/helpers.d.ts +197 -0
- package/dist/esm/editor-pm/helpers.d.ts.map +1 -0
- package/dist/esm/editor-pm/helpers.js +1113 -0
- package/dist/esm/editor-pm/index.d.ts +10 -0
- package/dist/esm/editor-pm/index.d.ts.map +1 -0
- package/dist/esm/editor-pm/index.js +9 -0
- package/dist/esm/editor-pm/mention-nodeview.d.ts +31 -0
- package/dist/esm/editor-pm/mention-nodeview.d.ts.map +1 -0
- package/dist/esm/editor-pm/mention-nodeview.js +88 -0
- package/dist/esm/editor-pm/mentions-dropdown.d.ts +13 -0
- package/dist/esm/editor-pm/mentions-dropdown.d.ts.map +1 -0
- package/dist/esm/editor-pm/mentions-dropdown.js +17 -0
- package/dist/esm/editor-pm/mentions-plugin.d.ts +18 -0
- package/dist/esm/editor-pm/mentions-plugin.d.ts.map +1 -0
- package/dist/esm/editor-pm/mentions-plugin.js +92 -0
- package/dist/esm/editor-pm/placeholder-plugin.d.ts +4 -0
- package/dist/esm/editor-pm/placeholder-plugin.d.ts.map +1 -0
- package/dist/esm/editor-pm/placeholder-plugin.js +20 -0
- package/dist/esm/editor-pm/prosemirror-editor.d.ts +493 -0
- package/dist/esm/editor-pm/prosemirror-editor.d.ts.map +1 -0
- package/dist/esm/editor-pm/prosemirror-editor.js +1135 -0
- package/dist/esm/editor-pm/schema.d.ts +3 -0
- package/dist/esm/editor-pm/schema.d.ts.map +1 -0
- package/dist/esm/editor-pm/schema.js +179 -0
- package/dist/esm/editor-pm/types.d.ts +8 -0
- package/dist/esm/editor-pm/types.d.ts.map +1 -0
- package/dist/esm/editor-pm/types.js +1 -0
- package/dist/esm/editor-pm/unfocused-selection-plugin.d.ts +25 -0
- package/dist/esm/editor-pm/unfocused-selection-plugin.d.ts.map +1 -0
- package/dist/esm/editor-pm/unfocused-selection-plugin.js +61 -0
- package/dist/esm/editor-pm/use-dropdown-change-pm.d.ts +24 -0
- package/dist/esm/editor-pm/use-dropdown-change-pm.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-dropdown-change-pm.js +137 -0
- package/dist/esm/editor-pm/use-editor-change-pm.d.ts +29 -0
- package/dist/esm/editor-pm/use-editor-change-pm.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-editor-change-pm.js +152 -0
- package/dist/esm/editor-pm/use-editor-decorator-pm.d.ts +17 -0
- package/dist/esm/editor-pm/use-editor-decorator-pm.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-editor-decorator-pm.js +57 -0
- package/dist/esm/editor-pm/use-editor-keydown-pm.d.ts +21 -0
- package/dist/esm/editor-pm/use-editor-keydown-pm.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-editor-keydown-pm.js +79 -0
- package/dist/esm/editor-pm/use-mention-keydown.d.ts +11 -0
- package/dist/esm/editor-pm/use-mention-keydown.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-mention-keydown.js +57 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.d.ts +19 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.js +66 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.test.d.ts +2 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.test.d.ts.map +1 -0
- package/dist/esm/editor-pm/use-parameter-suggestions.test.js +66 -0
- package/dist/esm/eye-dropper/index.d.ts +10 -0
- package/dist/esm/eye-dropper/index.d.ts.map +1 -0
- package/dist/esm/eye-dropper/index.js +23 -0
- package/dist/esm/function-info/helpers.d.ts +10 -0
- package/dist/esm/function-info/helpers.d.ts.map +1 -0
- package/dist/esm/function-info/helpers.js +20 -0
- package/dist/esm/function-info/index.d.ts +11 -0
- package/dist/esm/function-info/index.d.ts.map +1 -0
- package/dist/esm/function-info/index.js +68 -0
- package/dist/esm/function-info/style.d.ts +7 -0
- package/dist/esm/function-info/style.d.ts.map +1 -0
- package/dist/esm/function-info/style.js +10 -0
- package/dist/esm/hex-color-picker/index.d.ts +8 -0
- package/dist/esm/hex-color-picker/index.d.ts.map +1 -0
- package/dist/esm/hex-color-picker/index.js +44 -0
- package/dist/esm/hooks/use-cycle-reference.d.ts +16 -0
- package/dist/esm/hooks/use-cycle-reference.d.ts.map +1 -0
- package/dist/esm/hooks/use-cycle-reference.js +70 -0
- package/dist/esm/hooks/use-dropdown-categories.d.ts +9 -0
- package/dist/esm/hooks/use-dropdown-categories.d.ts.map +1 -0
- package/dist/esm/hooks/use-dropdown-categories.js +38 -0
- package/dist/esm/hooks/use-dropdown.d.ts +38 -0
- package/dist/esm/hooks/use-dropdown.d.ts.map +1 -0
- package/dist/esm/hooks/use-dropdown.js +236 -0
- package/dist/esm/hooks/use-isomorphic-layout-effect.ts.d.ts +6 -0
- package/dist/esm/hooks/use-isomorphic-layout-effect.ts.d.ts.map +1 -0
- package/dist/esm/hooks/use-isomorphic-layout-effect.ts.js +8 -0
- package/dist/esm/hooks/use-modal.d.ts +28 -0
- package/dist/esm/hooks/use-modal.d.ts.map +1 -0
- package/dist/esm/hooks/use-modal.js +93 -0
- package/dist/esm/hooks/use-visibility.d.ts +2 -0
- package/dist/esm/hooks/use-visibility.d.ts.map +1 -0
- package/dist/esm/hooks/use-visibility.js +12 -0
- package/dist/esm/icon-button/index.d.ts +15 -0
- package/dist/esm/icon-button/index.d.ts.map +1 -0
- package/dist/esm/icon-button/index.js +32 -0
- package/dist/esm/index.d.ts +34 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/input/index.d.ts +9 -0
- package/dist/esm/input/index.d.ts.map +1 -0
- package/dist/esm/input/index.js +7 -0
- package/dist/esm/popover/index.d.ts +13 -0
- package/dist/esm/popover/index.d.ts.map +1 -0
- package/dist/esm/popover/index.js +22 -0
- package/dist/esm/progress/index.d.ts +10 -0
- package/dist/esm/progress/index.d.ts.map +1 -0
- package/dist/esm/progress/index.js +12 -0
- package/dist/esm/select/index.d.ts +14 -0
- package/dist/esm/select/index.d.ts.map +1 -0
- package/dist/esm/select/index.js +30 -0
- package/dist/esm/separator/index.d.ts +5 -0
- package/dist/esm/separator/index.d.ts.map +1 -0
- package/dist/esm/separator/index.js +6 -0
- package/dist/esm/skeleton/index.d.ts +7 -0
- package/dist/esm/skeleton/index.d.ts.map +1 -0
- package/dist/esm/skeleton/index.js +9 -0
- package/dist/esm/stack/index.d.ts +11 -0
- package/dist/esm/stack/index.d.ts.map +1 -0
- package/dist/esm/stack/index.js +20 -0
- package/dist/esm/tabs/index.d.ts +8 -0
- package/dist/esm/tabs/index.d.ts.map +1 -0
- package/dist/esm/tabs/index.js +11 -0
- package/dist/esm/tooltip/index.d.ts +12 -0
- package/dist/esm/tooltip/index.d.ts.map +1 -0
- package/dist/esm/tooltip/index.js +13 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +5 -0
- package/dist/esm/vitest.config.d.ts +3 -0
- package/dist/esm/vitest.config.d.ts.map +1 -0
- package/dist/esm/vitest.config.js +21 -0
- package/package.json +78 -0
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
import { LoadingIcon, QuestionMarkIcon } from "@rowsncolumns/icons";
|
|
2
|
+
import { addressToCell, isAFormula, isNil, log } from "@rowsncolumns/utils";
|
|
3
|
+
import React, { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { history, undo, redo } from "prosemirror-history";
|
|
5
|
+
import { keymap } from "prosemirror-keymap";
|
|
6
|
+
import { baseKeymap, joinBackward, toggleMark } from "prosemirror-commands";
|
|
7
|
+
import { AllSelection, EditorState } from "prosemirror-state";
|
|
8
|
+
import { EditorView, DecorationSet } from "prosemirror-view";
|
|
9
|
+
import { TextSelection } from "prosemirror-state";
|
|
10
|
+
import { DOMSerializer } from "prosemirror-model";
|
|
11
|
+
import { useCycleFormulaReference } from "../hooks/use-cycle-reference";
|
|
12
|
+
import { useDropdown } from "../hooks/use-dropdown";
|
|
13
|
+
import { useDropdownChangePM } from "./use-dropdown-change-pm";
|
|
14
|
+
import { useEditorKeyDownPM } from "./use-editor-keydown-pm";
|
|
15
|
+
import { useEditorDecoratorPM } from "./use-editor-decorator-pm";
|
|
16
|
+
import { useEditorOnChangePM } from "./use-editor-change-pm";
|
|
17
|
+
import { createDecorationPlugin } from "./decoration-plugin";
|
|
18
|
+
import { createUnfocusedSelectionPlugin } from "./unfocused-selection-plugin";
|
|
19
|
+
import { createPlaceholderPlugin } from "./placeholder-plugin";
|
|
20
|
+
import { useIsomorphicLayoutEffect } from "../hooks/use-isomorphic-layout-effect.ts";
|
|
21
|
+
import { cn } from "../utils";
|
|
22
|
+
import { SPECIAL_ITEMS, THIS_ROW_ITEM } from "../editor/constants";
|
|
23
|
+
import { applyTextFormatRuns, applyTextFormatToSelection, getSelectionFontSize, deserializeString, getPropertyStartIndex, isCellToken, serializeEditorValue, } from "./helpers";
|
|
24
|
+
import { SuggestionsDropdown as DefaultSuggestionsDropdown, } from "../editor/suggestions-dropdown";
|
|
25
|
+
import { ErrorFallback } from "../editor/editor-error-boundary";
|
|
26
|
+
import { withErrorBoundary } from "react-error-boundary";
|
|
27
|
+
import * as Popper from "@radix-ui/react-popper";
|
|
28
|
+
import { Portal } from "@radix-ui/react-portal";
|
|
29
|
+
import { ConditionalWrapper } from "../conditional-wrapper";
|
|
30
|
+
import { schema } from "./schema";
|
|
31
|
+
import { StyledFormulaHelp } from "../editor/style";
|
|
32
|
+
import { tokenVocabulary } from "@rowsncolumns/fast-formula-parser";
|
|
33
|
+
import { createMentionsPlugin } from "./mentions-plugin";
|
|
34
|
+
import { MentionsDropdown } from "./mentions-dropdown";
|
|
35
|
+
import { useMentionKeyDown } from "./use-mention-keydown";
|
|
36
|
+
import { MentionNodeView } from "./mention-nodeview";
|
|
37
|
+
import { useCallbackRef } from "@rowsncolumns/grid";
|
|
38
|
+
import { useParameterSuggestions } from "./use-parameter-suggestions";
|
|
39
|
+
import { DEFAULT_ARRAY } from "@rowsncolumns/spreadsheet";
|
|
40
|
+
/**
|
|
41
|
+
* This fixes a IME bug in prosemirror
|
|
42
|
+
* Cmd+a and delete and then entering a character
|
|
43
|
+
* selects the entire character
|
|
44
|
+
* @param state
|
|
45
|
+
* @param dispatch
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
export const deleteAll = (state, dispatch) => {
|
|
49
|
+
if (dispatch) {
|
|
50
|
+
const emptyDoc = schema.node("doc", null, [schema.node("line", null, [])]);
|
|
51
|
+
const tr = state.tr.replaceWith(0, state.doc.content.size, emptyDoc.content);
|
|
52
|
+
tr.setSelection(TextSelection.create(tr.doc, 1));
|
|
53
|
+
dispatch(tr);
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* ProseMirror editor component for rich text and formula editing.
|
|
59
|
+
* Supports syntax highlighting, autocomplete, and keyboard shortcuts.
|
|
60
|
+
*/
|
|
61
|
+
export const ProseMirrorEditorThatMayError = memo(forwardRef(({ value = "", isNewValue, formulaMode, onChange, autoFocus, className, onKeyDown, disabled, showDropdown = true, persistFocus = true, editorZeroOutline, showFormulaHelp, onHideFormulaHelp, onShowFormulaHelp, tokenizer, onCancel, onSubmit, onFormulaStateChange, dropdownStyles, options, isOptionSelected, functionDescriptions, shouldFetchFunctionParameters, getFunctionParameterSuggestions, getMentions, getNamedRanges, getTableNames, getTableColumnNames, getStructuredProperties, onTeardown, SuggestionsDropdown = DefaultSuggestionsDropdown, MentionTagComponent, MentionDropdownItemComponent, isActive, stickyEditor = true, onFocus, onBlur, onCopy, onCut, onPaste, placeholder, textFormatRuns, resolveRunColor, navigationMode, enableContinuousSelection, cellFontFamily, ...props }, forwardedRef) => {
|
|
62
|
+
const editorRef = useRef(null);
|
|
63
|
+
const viewRef = useRef(null);
|
|
64
|
+
const cellFontFamilyRef = useRef(cellFontFamily);
|
|
65
|
+
const [cursorPosition, setCursorPosition] = useState();
|
|
66
|
+
const [tableToken, setTableToken] = useState();
|
|
67
|
+
const [functionToken, setFunctionToken] = useState();
|
|
68
|
+
const [parameterIndex, setParameterIndex] = useState(0);
|
|
69
|
+
const [isEdgeofCell, setIsEdgeOfCell] = useState(false);
|
|
70
|
+
const [parameterArguments, setParameterArguments] = useState(DEFAULT_ARRAY);
|
|
71
|
+
const [parameterRanges, setParameterRanges] = useState(DEFAULT_ARRAY);
|
|
72
|
+
const [cellPropertyToken, setCellPropertyToken] = useState();
|
|
73
|
+
const activeTokenRef = useRef(undefined);
|
|
74
|
+
const suggestionTokenRef = useRef(undefined);
|
|
75
|
+
const previousTokenRef = useRef(undefined);
|
|
76
|
+
const currentValueRef = useRef(undefined);
|
|
77
|
+
const onFocusRef = useRef(onFocus);
|
|
78
|
+
const onBlurRef = useRef(onBlur);
|
|
79
|
+
const onCopyRef = useRef(onCopy);
|
|
80
|
+
const onPasteRef = useRef(onPaste);
|
|
81
|
+
const onCutRef = useRef(onCut);
|
|
82
|
+
const navigationModeRef = useRef(navigationMode);
|
|
83
|
+
// Mentions
|
|
84
|
+
const [mentionState, onChangeMentionState] = useState({
|
|
85
|
+
active: false,
|
|
86
|
+
query: "",
|
|
87
|
+
range: null,
|
|
88
|
+
selectedIndex: 0,
|
|
89
|
+
});
|
|
90
|
+
const [mentions, setMentions] = useState([]);
|
|
91
|
+
const [isFetchingMentions, setIsFetchingMentions] = useState(false);
|
|
92
|
+
const mentionPositionLeft = useMemo(() => {
|
|
93
|
+
if (mentionState.range) {
|
|
94
|
+
const editorPositon = editorRef.current?.getBoundingClientRect();
|
|
95
|
+
return ((viewRef.current?.coordsAtPos(mentionState.range.from).left ?? 0) -
|
|
96
|
+
(editorPositon?.left ?? 0));
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}, [mentionState.range?.from]);
|
|
100
|
+
// Show mention dropdown
|
|
101
|
+
const showMentionDropdown = (mentionState.active && mentions.length > 1) || isFetchingMentions;
|
|
102
|
+
const showMentionDropdownEffective = showMentionDropdown && !navigationMode;
|
|
103
|
+
const { parameterSuggestions, isFetchingParameters } = useParameterSuggestions({
|
|
104
|
+
functionToken: !formulaMode || navigationMode || isEdgeofCell
|
|
105
|
+
? undefined
|
|
106
|
+
: functionToken,
|
|
107
|
+
parameterIndex,
|
|
108
|
+
parameterArguments,
|
|
109
|
+
getFunctionParameterSuggestions: !navigationMode
|
|
110
|
+
? getFunctionParameterSuggestions
|
|
111
|
+
: undefined,
|
|
112
|
+
shouldFetchFunctionParameters: !navigationMode
|
|
113
|
+
? shouldFetchFunctionParameters
|
|
114
|
+
: undefined,
|
|
115
|
+
});
|
|
116
|
+
useIsomorphicLayoutEffect(() => {
|
|
117
|
+
currentValueRef.current = value;
|
|
118
|
+
}, [value]);
|
|
119
|
+
useIsomorphicLayoutEffect(() => {
|
|
120
|
+
onFocusRef.current = onFocus;
|
|
121
|
+
}, [onFocus]);
|
|
122
|
+
useIsomorphicLayoutEffect(() => {
|
|
123
|
+
onBlurRef.current = onBlur;
|
|
124
|
+
}, [onBlur]);
|
|
125
|
+
useIsomorphicLayoutEffect(() => {
|
|
126
|
+
onCopyRef.current = onCopy;
|
|
127
|
+
}, [onCopy]);
|
|
128
|
+
useIsomorphicLayoutEffect(() => {
|
|
129
|
+
onPasteRef.current = onPaste;
|
|
130
|
+
}, [onPaste]);
|
|
131
|
+
useIsomorphicLayoutEffect(() => {
|
|
132
|
+
onCutRef.current = onCut;
|
|
133
|
+
}, [onCut]);
|
|
134
|
+
useIsomorphicLayoutEffect(() => {
|
|
135
|
+
cellFontFamilyRef.current = cellFontFamily;
|
|
136
|
+
}, [cellFontFamily]);
|
|
137
|
+
useIsomorphicLayoutEffect(() => {
|
|
138
|
+
const previousMode = navigationModeRef.current;
|
|
139
|
+
navigationModeRef.current = navigationMode;
|
|
140
|
+
// Reset formula-related states when entering navigation mode (and mode has changed)
|
|
141
|
+
if (navigationMode && previousMode !== navigationMode) {
|
|
142
|
+
setCursorPosition(undefined);
|
|
143
|
+
setTableToken(undefined);
|
|
144
|
+
setFunctionToken(undefined);
|
|
145
|
+
setParameterIndex(0);
|
|
146
|
+
setIsEdgeOfCell(false);
|
|
147
|
+
setParameterArguments(DEFAULT_ARRAY);
|
|
148
|
+
setParameterRanges(DEFAULT_ARRAY);
|
|
149
|
+
setCellPropertyToken(undefined);
|
|
150
|
+
}
|
|
151
|
+
if (viewRef.current?.dom) {
|
|
152
|
+
viewRef.current.dom.setAttribute("inputmode", navigationMode ? "none" : "text");
|
|
153
|
+
}
|
|
154
|
+
}, [navigationMode]);
|
|
155
|
+
// Store latest values in refs for the decorator plugin
|
|
156
|
+
const formulaModeRef = useRef(formulaMode);
|
|
157
|
+
// Update refs when values change
|
|
158
|
+
formulaModeRef.current = formulaMode;
|
|
159
|
+
// Helper function to create initial document
|
|
160
|
+
const createInitialDocument = useCallbackRef(useCallback(() => {
|
|
161
|
+
if (!isAFormula(value) && textFormatRuns?.length) {
|
|
162
|
+
// Use applyTextFormatRuns if format runs are provided
|
|
163
|
+
return applyTextFormatRuns(value, textFormatRuns, schema, resolveRunColor);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Use default deserialization
|
|
167
|
+
return deserializeString(value, schema);
|
|
168
|
+
}
|
|
169
|
+
}, [value, textFormatRuns]));
|
|
170
|
+
// Decorator for syntax highlighting - use ref to ensure latest values
|
|
171
|
+
const decoratorRef = useRef(undefined);
|
|
172
|
+
const getFormulaDecorations = useEditorDecoratorPM({
|
|
173
|
+
cursorPosition,
|
|
174
|
+
formulaMode,
|
|
175
|
+
tokenizer,
|
|
176
|
+
isActive,
|
|
177
|
+
});
|
|
178
|
+
// Combined decorator that includes formula highlighting
|
|
179
|
+
const getCombinedDecorations = useCallback((state) => {
|
|
180
|
+
if (navigationModeRef.current) {
|
|
181
|
+
return DecorationSet.create(state.doc, []);
|
|
182
|
+
}
|
|
183
|
+
const decorations = [];
|
|
184
|
+
// Add formula decorations
|
|
185
|
+
if (formulaMode) {
|
|
186
|
+
const formulaDecorations = getFormulaDecorations(state);
|
|
187
|
+
if (formulaDecorations && formulaDecorations.find) {
|
|
188
|
+
formulaDecorations
|
|
189
|
+
.find()
|
|
190
|
+
.forEach((deco) => decorations.push(deco));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return DecorationSet.create(state.doc, decorations);
|
|
194
|
+
}, [getFormulaDecorations, formulaMode]);
|
|
195
|
+
// React to mentions
|
|
196
|
+
useIsomorphicLayoutEffect(() => {
|
|
197
|
+
if (navigationModeRef.current) {
|
|
198
|
+
if (mentions.length) {
|
|
199
|
+
setMentions([]);
|
|
200
|
+
}
|
|
201
|
+
setIsFetchingMentions(false);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const fetchMentions = async (query) => {
|
|
205
|
+
setIsFetchingMentions(true);
|
|
206
|
+
const mentions = (await getMentions?.(query)) ?? [];
|
|
207
|
+
setMentions(mentions);
|
|
208
|
+
setIsFetchingMentions(false);
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
if (mentionState.active) {
|
|
212
|
+
fetchMentions(mentionState.query ?? "");
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
setMentions([]);
|
|
216
|
+
setIsFetchingMentions(false);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
console.log("Error fetching mentions", err);
|
|
221
|
+
}
|
|
222
|
+
}, [mentionState.active, mentionState.query]);
|
|
223
|
+
// Update ref with latest decorator
|
|
224
|
+
useIsomorphicLayoutEffect(() => {
|
|
225
|
+
decoratorRef.current = getCombinedDecorations;
|
|
226
|
+
}, [getCombinedDecorations]);
|
|
227
|
+
// Set active token ref
|
|
228
|
+
const setActiveToken = useCallback((token) => {
|
|
229
|
+
activeTokenRef.current = token;
|
|
230
|
+
}, []);
|
|
231
|
+
const setPreviousToken = useCallback((token) => {
|
|
232
|
+
previousTokenRef.current = token;
|
|
233
|
+
}, []);
|
|
234
|
+
// Callback when suggestion token changes
|
|
235
|
+
const handleSuggestionTokenChange = useCallback((token) => {
|
|
236
|
+
suggestionTokenRef.current = token;
|
|
237
|
+
const newQuery = token?.property ?? token?.image ?? "";
|
|
238
|
+
setQuery(newQuery);
|
|
239
|
+
}, []);
|
|
240
|
+
// Get editor view
|
|
241
|
+
const getView = useCallback(() => {
|
|
242
|
+
return viewRef.current;
|
|
243
|
+
}, []);
|
|
244
|
+
// Handle change hook
|
|
245
|
+
const onEditorChange = useEditorOnChangePM({
|
|
246
|
+
onChange: !navigationMode ? onChange : () => undefined,
|
|
247
|
+
getView,
|
|
248
|
+
isActive,
|
|
249
|
+
onChangeCursor: setCursorPosition,
|
|
250
|
+
onChangeActiveToken: setActiveToken,
|
|
251
|
+
tokenizer: !navigationMode ? tokenizer : () => [],
|
|
252
|
+
onFormulaStateChange: !navigationMode
|
|
253
|
+
? onFormulaStateChange
|
|
254
|
+
: undefined,
|
|
255
|
+
onSuggestionTokenChange: handleSuggestionTokenChange,
|
|
256
|
+
onTableTokenChange: setTableToken,
|
|
257
|
+
onChangeFunctionToken: setFunctionToken,
|
|
258
|
+
onChangeIsEdgeOfCell: setIsEdgeOfCell,
|
|
259
|
+
onChangeParameterIndex: setParameterIndex,
|
|
260
|
+
onCellPropertyTokenChange: setCellPropertyToken,
|
|
261
|
+
onChangeFunctionParameters: (params, ranges) => {
|
|
262
|
+
setParameterArguments(params);
|
|
263
|
+
setParameterRanges(ranges);
|
|
264
|
+
},
|
|
265
|
+
onChangePreviousToken: setPreviousToken,
|
|
266
|
+
enableContinuousSelection: enableContinuousSelection ?? isNil(navigationMode),
|
|
267
|
+
});
|
|
268
|
+
// Initialize editor
|
|
269
|
+
useIsomorphicLayoutEffect(() => {
|
|
270
|
+
if (!editorRef.current)
|
|
271
|
+
return;
|
|
272
|
+
try {
|
|
273
|
+
const state = EditorState.create({
|
|
274
|
+
doc: createInitialDocument(),
|
|
275
|
+
plugins: [
|
|
276
|
+
history(),
|
|
277
|
+
createUnfocusedSelectionPlugin(),
|
|
278
|
+
...(placeholder ? [createPlaceholderPlugin(placeholder)] : []),
|
|
279
|
+
createMentionsPlugin({
|
|
280
|
+
onChangeState: (nextState) => {
|
|
281
|
+
if (navigationModeRef.current)
|
|
282
|
+
return;
|
|
283
|
+
onChangeMentionState(nextState);
|
|
284
|
+
},
|
|
285
|
+
getFormulaMode: () => formulaModeRef.current ?? false,
|
|
286
|
+
}),
|
|
287
|
+
createDecorationPlugin({
|
|
288
|
+
formulaMode,
|
|
289
|
+
isActive,
|
|
290
|
+
tokenizer,
|
|
291
|
+
getDecorations: (state) => {
|
|
292
|
+
try {
|
|
293
|
+
if (navigationModeRef.current) {
|
|
294
|
+
return DecorationSet.create(state.doc, []);
|
|
295
|
+
}
|
|
296
|
+
// Use current ref values to get latest decorator
|
|
297
|
+
if (decoratorRef.current) {
|
|
298
|
+
return decoratorRef.current(state);
|
|
299
|
+
}
|
|
300
|
+
return DecorationSet.create(state.doc, []);
|
|
301
|
+
}
|
|
302
|
+
catch (decorationError) {
|
|
303
|
+
console.warn("Error creating decorations:", decorationError);
|
|
304
|
+
return DecorationSet.create(state.doc, []);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
keymap({
|
|
309
|
+
...baseKeymap,
|
|
310
|
+
Backspace: (state, dispatch, view) => {
|
|
311
|
+
if (view?.composing) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
// Check if all content is selected
|
|
315
|
+
const { from, to } = state.selection;
|
|
316
|
+
const allSelected = state.selection instanceof AllSelection ||
|
|
317
|
+
(from === 0 && to === state.doc.content.size);
|
|
318
|
+
if (allSelected) {
|
|
319
|
+
return deleteAll(state, dispatch);
|
|
320
|
+
}
|
|
321
|
+
// Handle backspace at the beginning of a line - merge with previous line
|
|
322
|
+
if (from === to && from > 0) {
|
|
323
|
+
const $pos = state.doc.resolve(from);
|
|
324
|
+
// Check if we're at the beginning of a line (parentOffset === 0 means start of line content)
|
|
325
|
+
if ($pos.parent.type.name === "line" &&
|
|
326
|
+
$pos.parentOffset === 0) {
|
|
327
|
+
// Use joinBackward to properly merge the lines
|
|
328
|
+
return joinBackward(state, dispatch);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Fall through to default backspace behavior
|
|
332
|
+
return false;
|
|
333
|
+
},
|
|
334
|
+
Delete: (state, dispatch, view) => {
|
|
335
|
+
if (view?.composing) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
const { from, to } = state.selection;
|
|
339
|
+
const allSelected = state.selection instanceof AllSelection ||
|
|
340
|
+
(from === 0 && to === state.doc.content.size);
|
|
341
|
+
if (allSelected) {
|
|
342
|
+
return deleteAll(state, dispatch);
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
},
|
|
346
|
+
"Mod-z": undo,
|
|
347
|
+
"Shift-Mod-z": redo,
|
|
348
|
+
"Mod-y": redo,
|
|
349
|
+
// Rich-text marks: only toggle when not in formula mode —
|
|
350
|
+
// we don't want Cmd+B to corrupt a formula. The Mod-key
|
|
351
|
+
// chord also reaches the canvas-grid keyboard handler so
|
|
352
|
+
// cell-level bold still works there for non-edit mode.
|
|
353
|
+
"Mod-b": (state, dispatch) => {
|
|
354
|
+
if (formulaModeRef.current)
|
|
355
|
+
return false;
|
|
356
|
+
return toggleMark(schema.marks.bold)(state, dispatch);
|
|
357
|
+
},
|
|
358
|
+
"Mod-i": (state, dispatch) => {
|
|
359
|
+
if (formulaModeRef.current)
|
|
360
|
+
return false;
|
|
361
|
+
return toggleMark(schema.marks.italic)(state, dispatch);
|
|
362
|
+
},
|
|
363
|
+
"Mod-u": (state, dispatch) => {
|
|
364
|
+
if (formulaModeRef.current)
|
|
365
|
+
return false;
|
|
366
|
+
return toggleMark(schema.marks.underline)(state, dispatch);
|
|
367
|
+
},
|
|
368
|
+
"Shift-Mod-x": (state, dispatch) => {
|
|
369
|
+
if (formulaModeRef.current)
|
|
370
|
+
return false;
|
|
371
|
+
return toggleMark(schema.marks.strikethrough)(state, dispatch);
|
|
372
|
+
},
|
|
373
|
+
// Google Sheets/Docs chords. The schema marks
|
|
374
|
+
// mutually exclude each other, so toggling one on
|
|
375
|
+
// clears the other.
|
|
376
|
+
"Mod-.": (state, dispatch) => {
|
|
377
|
+
if (formulaModeRef.current)
|
|
378
|
+
return false;
|
|
379
|
+
return toggleMark(schema.marks.superscript)(state, dispatch);
|
|
380
|
+
},
|
|
381
|
+
"Mod-,": (state, dispatch) => {
|
|
382
|
+
if (formulaModeRef.current)
|
|
383
|
+
return false;
|
|
384
|
+
return toggleMark(schema.marks.subscript)(state, dispatch);
|
|
385
|
+
},
|
|
386
|
+
Enter: () => {
|
|
387
|
+
// Let React handle Enter through onKeyDownEditor
|
|
388
|
+
return false; // Don't consume the event, let it bubble up
|
|
389
|
+
},
|
|
390
|
+
ArrowUp: () => {
|
|
391
|
+
return false;
|
|
392
|
+
},
|
|
393
|
+
ArrowDown: () => {
|
|
394
|
+
return false;
|
|
395
|
+
},
|
|
396
|
+
Escape: () => {
|
|
397
|
+
// Handle escape key
|
|
398
|
+
return true;
|
|
399
|
+
},
|
|
400
|
+
F4: () => {
|
|
401
|
+
// Handle F4 for cycling references
|
|
402
|
+
if (activeTokenRef.current) {
|
|
403
|
+
onCycleReference(activeTokenRef.current);
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
},
|
|
407
|
+
}),
|
|
408
|
+
],
|
|
409
|
+
});
|
|
410
|
+
// Stamp the cell's font onto each line in clipboard HTML so a
|
|
411
|
+
// paste lands with the source font. Skips when no font is set —
|
|
412
|
+
// the destination cell then keeps its own.
|
|
413
|
+
const clipboardSerializer = new DOMSerializer({
|
|
414
|
+
...DOMSerializer.nodesFromSchema(schema),
|
|
415
|
+
line: () => {
|
|
416
|
+
const font = cellFontFamilyRef.current;
|
|
417
|
+
return font
|
|
418
|
+
? ["div", { style: `font-family: ${font}` }, 0]
|
|
419
|
+
: ["div", 0];
|
|
420
|
+
},
|
|
421
|
+
}, DOMSerializer.marksFromSchema(schema));
|
|
422
|
+
const view = new EditorView(editorRef.current, {
|
|
423
|
+
state,
|
|
424
|
+
clipboardSerializer,
|
|
425
|
+
nodeViews: {
|
|
426
|
+
mention: (node, view, getPos) => new MentionNodeView(node, view, getPos, MentionTagComponent),
|
|
427
|
+
},
|
|
428
|
+
dispatchTransaction: (transaction) => {
|
|
429
|
+
try {
|
|
430
|
+
const newState = view.state.apply(transaction);
|
|
431
|
+
view.updateState(newState);
|
|
432
|
+
// Call the onChange handler which will handle all token analysis and state updates
|
|
433
|
+
if (!navigationModeRef.current) {
|
|
434
|
+
onEditorChange(newState);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
log("Error dispatching transaction:", error);
|
|
439
|
+
// Try to recover by reverting to current state
|
|
440
|
+
try {
|
|
441
|
+
if (!navigationModeRef.current) {
|
|
442
|
+
onEditorChange(view.state);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (recoveryError) {
|
|
446
|
+
log("Error during transaction recovery:", recoveryError);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
attributes: {
|
|
451
|
+
class: cn("h-full overflow-auto [&_div]:outline-none"),
|
|
452
|
+
spellcheck: formulaMode !== true ? "true" : "false",
|
|
453
|
+
contenteditable: !disabled ? "true" : "false",
|
|
454
|
+
inputmode: navigationModeRef.current ? "none" : "text",
|
|
455
|
+
tabindex: "0",
|
|
456
|
+
},
|
|
457
|
+
editable: () => !disabled,
|
|
458
|
+
handleDOMEvents: {
|
|
459
|
+
focus: (_, event) => {
|
|
460
|
+
onFocusRef.current?.(event);
|
|
461
|
+
return false; // Let ProseMirror handle the focus as well
|
|
462
|
+
},
|
|
463
|
+
blur: (_, event) => {
|
|
464
|
+
onBlurRef.current?.(event);
|
|
465
|
+
return false; // Let ProseMirror handle the blur as well
|
|
466
|
+
},
|
|
467
|
+
copy(_, event) {
|
|
468
|
+
onCopyRef.current?.(event);
|
|
469
|
+
return false;
|
|
470
|
+
},
|
|
471
|
+
paste(_, event) {
|
|
472
|
+
onPasteRef.current?.(event);
|
|
473
|
+
return false;
|
|
474
|
+
},
|
|
475
|
+
cut(_, event) {
|
|
476
|
+
onCutRef.current?.(event);
|
|
477
|
+
return false;
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
viewRef.current = view;
|
|
482
|
+
return () => {
|
|
483
|
+
try {
|
|
484
|
+
view.destroy();
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
log("Error destroying editor view:", error);
|
|
488
|
+
}
|
|
489
|
+
viewRef.current = null;
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
log("Error initializing ProseMirror editor:", error);
|
|
494
|
+
// Try to create a minimal fallback editor
|
|
495
|
+
try {
|
|
496
|
+
const fallbackState = EditorState.create({
|
|
497
|
+
doc: schema.node("doc", null, [schema.node("line", null, [])]),
|
|
498
|
+
plugins: [history()],
|
|
499
|
+
});
|
|
500
|
+
const fallbackView = new EditorView(editorRef.current, {
|
|
501
|
+
state: fallbackState,
|
|
502
|
+
});
|
|
503
|
+
viewRef.current = fallbackView;
|
|
504
|
+
}
|
|
505
|
+
catch (fallbackError) {
|
|
506
|
+
log("Error creating fallback editor:", fallbackError);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}, [createInitialDocument]); // Re-initialize if document creation logic changes
|
|
510
|
+
// Update editor content when value changes
|
|
511
|
+
useIsomorphicLayoutEffect(() => {
|
|
512
|
+
if (viewRef.current) {
|
|
513
|
+
try {
|
|
514
|
+
const currentContent = serializeEditorValue(viewRef.current.state.doc);
|
|
515
|
+
if (currentContent !== value) {
|
|
516
|
+
const newDoc = createInitialDocument();
|
|
517
|
+
const tr = viewRef.current.state.tr.replaceWith(0, viewRef.current.state.doc.content.size, newDoc.content);
|
|
518
|
+
viewRef.current.dispatch(tr);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
log("Error updating editor content:", error);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}, [value, createInitialDocument]);
|
|
526
|
+
// Update decorations when formula mode or active state changes
|
|
527
|
+
useIsomorphicLayoutEffect(() => {
|
|
528
|
+
if (navigationModeRef.current)
|
|
529
|
+
return;
|
|
530
|
+
if (viewRef.current) {
|
|
531
|
+
try {
|
|
532
|
+
// Force a decoration update by dispatching a metadata transaction
|
|
533
|
+
const tr = viewRef.current.state.tr.setMeta("updateDecorations", {
|
|
534
|
+
formulaMode,
|
|
535
|
+
isActive,
|
|
536
|
+
cursorPosition,
|
|
537
|
+
});
|
|
538
|
+
viewRef.current.dispatch(tr);
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
log("Error updating decorations:", error);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}, [formulaMode, isActive, cursorPosition]);
|
|
545
|
+
// Update editor disabled state when disabled prop changes
|
|
546
|
+
useIsomorphicLayoutEffect(() => {
|
|
547
|
+
if (viewRef.current) {
|
|
548
|
+
try {
|
|
549
|
+
// Update contenteditable attribute
|
|
550
|
+
const editorDom = viewRef.current.dom;
|
|
551
|
+
if (editorDom) {
|
|
552
|
+
editorDom.setAttribute("contenteditable", disabled ? "false" : "true");
|
|
553
|
+
}
|
|
554
|
+
// Update editable state in ProseMirror
|
|
555
|
+
// This forces ProseMirror to re-evaluate its editable state
|
|
556
|
+
viewRef.current.setProps({
|
|
557
|
+
...viewRef.current.props,
|
|
558
|
+
editable: () => !disabled,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
log("Error updating disabled state:", error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}, [disabled]);
|
|
566
|
+
// Focus editor
|
|
567
|
+
const focusEditor = useCallback(() => {
|
|
568
|
+
if (viewRef.current &&
|
|
569
|
+
viewRef.current.dom &&
|
|
570
|
+
!viewRef.current.hasFocus()) {
|
|
571
|
+
try {
|
|
572
|
+
viewRef.current.focus();
|
|
573
|
+
// Ensure the DOM element also gets focus
|
|
574
|
+
if (document.activeElement !== viewRef.current.dom) {
|
|
575
|
+
viewRef.current.dom.focus();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
console.warn("Failed to focus editor:", error);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}, []);
|
|
583
|
+
/**
|
|
584
|
+
* Teardown current editor
|
|
585
|
+
*/
|
|
586
|
+
const handleTeardown = useCallback(() => {
|
|
587
|
+
if (viewRef.current) {
|
|
588
|
+
viewRef.current.destroy();
|
|
589
|
+
viewRef.current = null;
|
|
590
|
+
}
|
|
591
|
+
onTeardown?.();
|
|
592
|
+
}, [onTeardown]);
|
|
593
|
+
// Get current value
|
|
594
|
+
const getCurrentValue = useCallback(() => {
|
|
595
|
+
if (viewRef.current) {
|
|
596
|
+
return serializeEditorValue(viewRef.current.state.doc);
|
|
597
|
+
}
|
|
598
|
+
return currentValueRef.current ?? "";
|
|
599
|
+
}, []);
|
|
600
|
+
// Is Cursor at end of input
|
|
601
|
+
const isCursorAtEnd = useCallback(() => {
|
|
602
|
+
const view = viewRef.current;
|
|
603
|
+
if (!view)
|
|
604
|
+
return false;
|
|
605
|
+
const { state } = view;
|
|
606
|
+
const { selection } = state;
|
|
607
|
+
if (!selection.empty)
|
|
608
|
+
return false;
|
|
609
|
+
const $pos = state.doc.resolve(selection.from);
|
|
610
|
+
const endPos = $pos.end(); // End of the current block
|
|
611
|
+
return selection.from === endPos;
|
|
612
|
+
}, []);
|
|
613
|
+
// Item to stirng
|
|
614
|
+
const itemToString = useCallback((item) => item?.value ?? item?.title ?? "", []);
|
|
615
|
+
// Final options
|
|
616
|
+
const finalOptions = useMemo(() => {
|
|
617
|
+
if (navigationMode) {
|
|
618
|
+
return [];
|
|
619
|
+
}
|
|
620
|
+
if (formulaMode) {
|
|
621
|
+
const isStructured = Boolean(tableToken);
|
|
622
|
+
const isCellProperty = Boolean(cellPropertyToken);
|
|
623
|
+
if (isCellProperty && cellPropertyToken) {
|
|
624
|
+
if (!cellPropertyToken.structured) {
|
|
625
|
+
const dotIndex = getPropertyStartIndex(cellPropertyToken);
|
|
626
|
+
if (dotIndex === -1) {
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
const address = cellPropertyToken.image.slice(0, dotIndex);
|
|
630
|
+
const cell = addressToCell(address);
|
|
631
|
+
if (!cell) {
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
const props = getStructuredProperties?.(cellPropertyToken.sheetName, cell.rowIndex, cell.columnIndex) ?? [];
|
|
635
|
+
return props;
|
|
636
|
+
}
|
|
637
|
+
if (cellPropertyToken.resolvedRowIndex !== undefined &&
|
|
638
|
+
cellPropertyToken.resolvedColumnIndex !== undefined) {
|
|
639
|
+
return (getStructuredProperties?.(cellPropertyToken.resolvedSheetName ??
|
|
640
|
+
cellPropertyToken.sheetName, cellPropertyToken.resolvedRowIndex, cellPropertyToken.resolvedColumnIndex) ?? []);
|
|
641
|
+
}
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
if (isStructured) {
|
|
645
|
+
const canSelectThisRow = activeTokenRef.current?.image !== "@";
|
|
646
|
+
const columns = getTableColumnNames?.(tableToken?.tableName) ?? [];
|
|
647
|
+
// If there are no columns, lets not display suggestions
|
|
648
|
+
if (!columns.length) {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
return [
|
|
652
|
+
...(canSelectThisRow ? [THIS_ROW_ITEM] : []),
|
|
653
|
+
...columns,
|
|
654
|
+
...SPECIAL_ITEMS,
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
const tableNames = getTableNames?.() ?? [];
|
|
658
|
+
const parameterItems = isEdgeofCell
|
|
659
|
+
? []
|
|
660
|
+
: parameterSuggestions?.map((item) => ({
|
|
661
|
+
...item,
|
|
662
|
+
category: item.category ?? "parameter",
|
|
663
|
+
})) ?? [];
|
|
664
|
+
const baseOptions = [
|
|
665
|
+
...tableNames,
|
|
666
|
+
...(getNamedRanges?.() ?? []),
|
|
667
|
+
...(functionDescriptions ?? []),
|
|
668
|
+
...(parameterItems ?? []),
|
|
669
|
+
];
|
|
670
|
+
if (suggestionTokenRef.current &&
|
|
671
|
+
suggestionTokenRef.current.tokenType?.name ===
|
|
672
|
+
tokenVocabulary.UnterminatedStringStart.name) {
|
|
673
|
+
return parameterItems;
|
|
674
|
+
}
|
|
675
|
+
return baseOptions;
|
|
676
|
+
}
|
|
677
|
+
return options ?? [];
|
|
678
|
+
}, [
|
|
679
|
+
navigationMode,
|
|
680
|
+
formulaMode,
|
|
681
|
+
getTableNames,
|
|
682
|
+
getTableColumnNames,
|
|
683
|
+
getStructuredProperties,
|
|
684
|
+
options,
|
|
685
|
+
functionDescriptions,
|
|
686
|
+
getNamedRanges,
|
|
687
|
+
tableToken,
|
|
688
|
+
cellPropertyToken,
|
|
689
|
+
functionToken,
|
|
690
|
+
parameterSuggestions,
|
|
691
|
+
isEdgeofCell,
|
|
692
|
+
showFormulaHelp,
|
|
693
|
+
]);
|
|
694
|
+
const activeParameterRange = useMemo(() => {
|
|
695
|
+
const range = parameterRanges[parameterIndex];
|
|
696
|
+
if (!range)
|
|
697
|
+
return undefined;
|
|
698
|
+
return {
|
|
699
|
+
start: range.start + 1,
|
|
700
|
+
end: range.end + 1,
|
|
701
|
+
};
|
|
702
|
+
}, [parameterRanges, parameterIndex]);
|
|
703
|
+
const itemsWhenEmpty = useMemo(() => {
|
|
704
|
+
if (navigationMode) {
|
|
705
|
+
return undefined;
|
|
706
|
+
}
|
|
707
|
+
if (!isEdgeofCell &&
|
|
708
|
+
functionToken &&
|
|
709
|
+
shouldFetchFunctionParameters?.(functionToken.image ?? "", parameterIndex, parameterArguments)) {
|
|
710
|
+
return parameterSuggestions ?? [];
|
|
711
|
+
}
|
|
712
|
+
return undefined;
|
|
713
|
+
}, [
|
|
714
|
+
navigationMode,
|
|
715
|
+
functionToken,
|
|
716
|
+
shouldFetchFunctionParameters,
|
|
717
|
+
parameterIndex,
|
|
718
|
+
parameterArguments,
|
|
719
|
+
parameterSuggestions,
|
|
720
|
+
isEdgeofCell,
|
|
721
|
+
]);
|
|
722
|
+
// Callback when user selects an item from dropdown
|
|
723
|
+
const handleDropdownChange = useDropdownChangePM({
|
|
724
|
+
onSubmit: !navigationMode ? onSubmit : undefined,
|
|
725
|
+
formulaMode,
|
|
726
|
+
itemToString,
|
|
727
|
+
suggestionToken: suggestionTokenRef.current,
|
|
728
|
+
previousToken: previousTokenRef.current,
|
|
729
|
+
propertyToken: cellPropertyToken,
|
|
730
|
+
getView,
|
|
731
|
+
activeToken: activeTokenRef.current,
|
|
732
|
+
parameterRange: activeParameterRange,
|
|
733
|
+
});
|
|
734
|
+
// Inserts a mention in the dom
|
|
735
|
+
const handleInsertMention = useCallback((mention) => {
|
|
736
|
+
if (viewRef.current && mentionState.range && mention) {
|
|
737
|
+
const { state } = viewRef.current;
|
|
738
|
+
const mentionNode = state.schema.nodes.mention.create({
|
|
739
|
+
label: mention.label,
|
|
740
|
+
value: mention.value,
|
|
741
|
+
});
|
|
742
|
+
const tr = state.tr
|
|
743
|
+
.delete(mentionState.range.from, mentionState.range.to)
|
|
744
|
+
.insert(mentionState.range.from, [
|
|
745
|
+
mentionNode,
|
|
746
|
+
state.schema.text(" "),
|
|
747
|
+
]);
|
|
748
|
+
viewRef.current.dispatch(tr);
|
|
749
|
+
}
|
|
750
|
+
}, [mentionState, onChangeMentionState]);
|
|
751
|
+
// Mention keydown
|
|
752
|
+
const { highlightedIndex: mentionHighlightedIndex, onMentionKeydown } = useMentionKeyDown({
|
|
753
|
+
mentions,
|
|
754
|
+
mentionState,
|
|
755
|
+
onSelect: handleInsertMention,
|
|
756
|
+
});
|
|
757
|
+
// Dropdown integration
|
|
758
|
+
const { isOpen, highlightedIndex, onSelect, onChangeHighlightedIndex, items, onKeyDown: onDropdownKeyDown, setQuery, query, } = useDropdown({
|
|
759
|
+
initialOpen: navigationMode
|
|
760
|
+
? false
|
|
761
|
+
: formulaMode
|
|
762
|
+
? false
|
|
763
|
+
: Boolean(options?.length),
|
|
764
|
+
initialHighlightedIndex: -1,
|
|
765
|
+
initialQuery: navigationMode
|
|
766
|
+
? undefined
|
|
767
|
+
: formulaMode
|
|
768
|
+
? undefined
|
|
769
|
+
: value,
|
|
770
|
+
isNewValue,
|
|
771
|
+
items: finalOptions,
|
|
772
|
+
onChange: handleDropdownChange,
|
|
773
|
+
itemToString,
|
|
774
|
+
itemsWhenEmpty,
|
|
775
|
+
// Only meaningful for the plain (data-validation) dropdown — formula
|
|
776
|
+
// mode's function/reference suggestions have no "current entry".
|
|
777
|
+
isItemSelected: !navigationMode && !formulaMode ? isOptionSelected : undefined,
|
|
778
|
+
limit: formulaMode
|
|
779
|
+
? tableToken
|
|
780
|
+
? Number.MAX_SAFE_INTEGER
|
|
781
|
+
: cellPropertyToken
|
|
782
|
+
? Number.MAX_SAFE_INTEGER
|
|
783
|
+
: 10
|
|
784
|
+
: Number.MAX_SAFE_INTEGER,
|
|
785
|
+
showAllIfEmpty: !formulaMode || !!tableToken || !!cellPropertyToken,
|
|
786
|
+
});
|
|
787
|
+
// Update dropdown query when suggestion token changes
|
|
788
|
+
useIsomorphicLayoutEffect(() => {
|
|
789
|
+
if (navigationModeRef.current)
|
|
790
|
+
return;
|
|
791
|
+
const token = suggestionTokenRef.current;
|
|
792
|
+
if (token) {
|
|
793
|
+
if (token.tokenType?.name ===
|
|
794
|
+
tokenVocabulary.UnterminatedStringStart.name) {
|
|
795
|
+
const cleaned = token.image?.replace(/"/g, "") ?? "";
|
|
796
|
+
setQuery(cleaned);
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
setQuery(token.image ?? "");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}, [setQuery]);
|
|
803
|
+
// Show dropdown if functiontoken is present
|
|
804
|
+
const isDropdownOpen = Boolean(isOpen ||
|
|
805
|
+
(formulaMode && !isEdgeofCell && (items.length > 1 || functionToken)));
|
|
806
|
+
const showDropdownEffective = showDropdown && !navigationMode;
|
|
807
|
+
// Handle update editor
|
|
808
|
+
const handleUpdate = useCallback((address, selectionMode) => {
|
|
809
|
+
if (!viewRef.current) {
|
|
810
|
+
console.warn("handleUpdate: Editor view not available");
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
const { state } = viewRef.current;
|
|
815
|
+
const { tr } = state;
|
|
816
|
+
const activeToken = activeTokenRef.current;
|
|
817
|
+
if (selectionMode === "modify" && activeToken) {
|
|
818
|
+
const from = Math.max(0, activeToken.startColumn);
|
|
819
|
+
const to = Math.min(state.doc.content.size, activeToken.endColumn + 1);
|
|
820
|
+
// Bounds checking
|
|
821
|
+
if (from >= 0 && to <= state.doc.content.size && from <= to) {
|
|
822
|
+
tr.delete(from, to);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
console.warn(`Invalid delete range: ${from}-${to}, doc size: ${state.doc.content.size}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (selectionMode === "append" &&
|
|
829
|
+
activeToken &&
|
|
830
|
+
isCellToken(activeToken)) {
|
|
831
|
+
tr.insertText(",");
|
|
832
|
+
}
|
|
833
|
+
if (address && typeof address === "string") {
|
|
834
|
+
// Sanitize address string
|
|
835
|
+
const cleanAddress = address.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
836
|
+
tr.insertText(cleanAddress);
|
|
837
|
+
}
|
|
838
|
+
viewRef.current.dispatch(tr);
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
log("Error updating editor:", error);
|
|
842
|
+
}
|
|
843
|
+
}, []);
|
|
844
|
+
// Cycle between absolute and relative references
|
|
845
|
+
const onCycleReference = useCycleFormulaReference({
|
|
846
|
+
onUpdate: handleUpdate,
|
|
847
|
+
});
|
|
848
|
+
// KeyDown handler
|
|
849
|
+
const handleKeyDown = useCallback((e) => {
|
|
850
|
+
if (!navigationMode && showDropdownEffective && isDropdownOpen) {
|
|
851
|
+
onDropdownKeyDown?.(e);
|
|
852
|
+
}
|
|
853
|
+
if (!navigationMode && mentionState.active) {
|
|
854
|
+
onMentionKeydown(e);
|
|
855
|
+
}
|
|
856
|
+
onKeyDown?.(e);
|
|
857
|
+
}, [
|
|
858
|
+
onKeyDown,
|
|
859
|
+
onDropdownKeyDown,
|
|
860
|
+
showDropdownEffective,
|
|
861
|
+
isDropdownOpen,
|
|
862
|
+
mentionState,
|
|
863
|
+
onMentionKeydown,
|
|
864
|
+
navigationMode,
|
|
865
|
+
]);
|
|
866
|
+
const handleKeyDownCapture = useCallback((e) => {
|
|
867
|
+
if (!navigationMode) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const key = e.key.toLowerCase();
|
|
871
|
+
const isUndoRedoShortcut = (e.metaKey || e.ctrlKey) &&
|
|
872
|
+
!e.altKey &&
|
|
873
|
+
(key === "z" || key === "y");
|
|
874
|
+
if (isUndoRedoShortcut) {
|
|
875
|
+
e.preventDefault();
|
|
876
|
+
}
|
|
877
|
+
}, [navigationMode]);
|
|
878
|
+
// Insert break handler for ProseMirror
|
|
879
|
+
const handleInsertBreak = useCallback(() => {
|
|
880
|
+
if (viewRef.current) {
|
|
881
|
+
const { state } = viewRef.current;
|
|
882
|
+
const { $from } = state.selection;
|
|
883
|
+
// Check if we can split at this position
|
|
884
|
+
const canSplit = $from.depth > 0;
|
|
885
|
+
if (canSplit) {
|
|
886
|
+
// Split the line at the cursor position
|
|
887
|
+
const tr = state.tr.split($from.pos);
|
|
888
|
+
// Move cursor to the start of the new line (right after the split)
|
|
889
|
+
const newPos = $from.pos + 2; // +2 to move into the new line node
|
|
890
|
+
if (newPos <= tr.doc.content.size) {
|
|
891
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(newPos)));
|
|
892
|
+
}
|
|
893
|
+
viewRef.current.dispatch(tr);
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// Fallback: just insert a new line node
|
|
897
|
+
const newLine = state.schema.nodes.line.create();
|
|
898
|
+
const tr = state.tr.replaceSelectionWith(newLine);
|
|
899
|
+
const newPos = tr.selection.from + 1;
|
|
900
|
+
if (newPos <= tr.doc.content.size) {
|
|
901
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(newPos)));
|
|
902
|
+
}
|
|
903
|
+
viewRef.current.dispatch(tr);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}, []);
|
|
907
|
+
const canSubmitInDropdown = () => {
|
|
908
|
+
const token = suggestionTokenRef.current;
|
|
909
|
+
const item = items[highlightedIndex];
|
|
910
|
+
if (token?.property && item) {
|
|
911
|
+
return item?.title === token?.property;
|
|
912
|
+
}
|
|
913
|
+
if (item && item.category === "namedrange") {
|
|
914
|
+
return item.title === token?.image;
|
|
915
|
+
}
|
|
916
|
+
return false;
|
|
917
|
+
};
|
|
918
|
+
// Editor keydown handler
|
|
919
|
+
const canSubmit = (highlightedIndex === -1 ||
|
|
920
|
+
items.length === 0 ||
|
|
921
|
+
canSubmitInDropdown()) &&
|
|
922
|
+
!showMentionDropdownEffective;
|
|
923
|
+
const onKeyDownEditor = useEditorKeyDownPM({
|
|
924
|
+
value,
|
|
925
|
+
formulaMode,
|
|
926
|
+
isNewValue,
|
|
927
|
+
isActive,
|
|
928
|
+
isCursorAtEnd,
|
|
929
|
+
onCancel: !navigationMode ? onCancel : undefined,
|
|
930
|
+
onSubmit: !navigationMode ? onSubmit : undefined,
|
|
931
|
+
onInsertBreak: handleInsertBreak,
|
|
932
|
+
onKeyDown: handleKeyDown,
|
|
933
|
+
canSubmit,
|
|
934
|
+
canCancel: !isOpen && !showMentionDropdownEffective,
|
|
935
|
+
isDropdownOpen,
|
|
936
|
+
getView,
|
|
937
|
+
});
|
|
938
|
+
// Move cursor to the end
|
|
939
|
+
const moveCursorToEnd = useCallback(() => {
|
|
940
|
+
if (!viewRef.current) {
|
|
941
|
+
console.warn("moveCursorToEnd: Editor view not available");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
const { state } = viewRef.current;
|
|
946
|
+
const pos = Math.min(state.doc.content.size, Math.max(0, state.doc.content.size));
|
|
947
|
+
if (pos >= 0 && pos <= state.doc.content.size) {
|
|
948
|
+
const tr = state.tr.setSelection(TextSelection.near(state.doc.resolve(pos)));
|
|
949
|
+
viewRef.current.dispatch(tr);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
console.warn(`Invalid cursor position for moveCursorToEnd: ${pos}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
catch (error) {
|
|
956
|
+
log("Error moving cursor to end:", error);
|
|
957
|
+
}
|
|
958
|
+
}, []);
|
|
959
|
+
// In Strict mode, memo refs are not changed afaik
|
|
960
|
+
const imperativeMethods = useMemo(() => ({
|
|
961
|
+
update: handleUpdate,
|
|
962
|
+
teardown: handleTeardown,
|
|
963
|
+
setValue: (value, formatRuns) => {
|
|
964
|
+
if (!viewRef.current) {
|
|
965
|
+
console.warn("setValue: Editor view not available");
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
viewRef.current.endComposition?.();
|
|
970
|
+
const newDoc = !isAFormula(value) && formatRuns && formatRuns.length > 0
|
|
971
|
+
? applyTextFormatRuns(value, formatRuns, schema, resolveRunColor)
|
|
972
|
+
: deserializeString(value, schema);
|
|
973
|
+
const { state } = viewRef.current;
|
|
974
|
+
const currentSelection = state.selection;
|
|
975
|
+
const tr = state.tr.replaceWith(0, state.doc.content.size, newDoc.content);
|
|
976
|
+
// Try to preserve cursor position if it's still valid
|
|
977
|
+
const newDocSize = tr.doc.content.size;
|
|
978
|
+
const preservedPos = Math.min(Math.max(0, currentSelection.from), newDocSize);
|
|
979
|
+
// Set selection to preserved position with bounds checking
|
|
980
|
+
if (preservedPos >= 0 && preservedPos <= newDocSize) {
|
|
981
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(preservedPos)));
|
|
982
|
+
}
|
|
983
|
+
viewRef.current.dispatch(tr);
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
log("Error setting editor value:", error);
|
|
987
|
+
// Fallback: try to set empty content
|
|
988
|
+
try {
|
|
989
|
+
const emptyDoc = schema.node("doc", null, [
|
|
990
|
+
schema.node("line", null, []),
|
|
991
|
+
]);
|
|
992
|
+
const tr = viewRef.current.state.tr.replaceWith(0, viewRef.current.state.doc.content.size, emptyDoc.content);
|
|
993
|
+
viewRef.current.dispatch(tr);
|
|
994
|
+
}
|
|
995
|
+
catch (fallbackError) {
|
|
996
|
+
log("Error in setValue fallback:", fallbackError);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
getValue() {
|
|
1001
|
+
return getCurrentValue();
|
|
1002
|
+
},
|
|
1003
|
+
applyTextFormat(format) {
|
|
1004
|
+
const view = viewRef.current;
|
|
1005
|
+
if (!view) {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
const handled = applyTextFormatToSelection(view.state, view.dispatch, format, schema, resolveRunColor);
|
|
1009
|
+
// Focus is NOT managed here. The ProseMirror selection survives
|
|
1010
|
+
// without DOM focus, and the toolbar's existing machinery
|
|
1011
|
+
// (ToolbarIconButton -> focusSheet, dropdown onCloseAutoFocus)
|
|
1012
|
+
// already returns focus to the sheet after a control is used —
|
|
1013
|
+
// taking focus synchronously from inside a control's event
|
|
1014
|
+
// handler lets that event's default action land in the editor.
|
|
1015
|
+
return handled;
|
|
1016
|
+
},
|
|
1017
|
+
getSelectionFontSize() {
|
|
1018
|
+
const view = viewRef.current;
|
|
1019
|
+
if (!view) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
return getSelectionFontSize(view.state);
|
|
1023
|
+
},
|
|
1024
|
+
moveCursorToEnd,
|
|
1025
|
+
moveCursorToTokenEnd(index) {
|
|
1026
|
+
if (index === undefined || index === null) {
|
|
1027
|
+
this.moveCursorToEnd();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const tokens = tokenizer(getCurrentValue());
|
|
1031
|
+
for (const token of tokens) {
|
|
1032
|
+
if ((token.selection || token.structured || token.isNamedRange) &&
|
|
1033
|
+
token.index === index &&
|
|
1034
|
+
token.endColumn !== undefined) {
|
|
1035
|
+
// Token columns are ProseMirror positions (see
|
|
1036
|
+
// `handleUpdate`); the caret slot after the token's
|
|
1037
|
+
// last character is endColumn + 1
|
|
1038
|
+
this.select(token.endColumn + 1);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
this.moveCursorToEnd();
|
|
1043
|
+
},
|
|
1044
|
+
focus(shouldMoveSelectionToEnd) {
|
|
1045
|
+
if (!persistFocus) {
|
|
1046
|
+
console.debug("Focus persisting disabled");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
focusEditor();
|
|
1050
|
+
if (shouldMoveSelectionToEnd) {
|
|
1051
|
+
this.moveCursorToEnd();
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
isFocused() {
|
|
1055
|
+
if (!viewRef.current)
|
|
1056
|
+
return false;
|
|
1057
|
+
return (viewRef.current.hasFocus() ||
|
|
1058
|
+
document.activeElement === viewRef.current.dom);
|
|
1059
|
+
},
|
|
1060
|
+
get selection() {
|
|
1061
|
+
return viewRef.current?.state.selection.from ?? null;
|
|
1062
|
+
},
|
|
1063
|
+
select(target) {
|
|
1064
|
+
if (!viewRef.current) {
|
|
1065
|
+
console.warn("select: Editor view not available");
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
const { state } = viewRef.current;
|
|
1070
|
+
// Validate target position
|
|
1071
|
+
if (typeof target !== "number" || !isFinite(target)) {
|
|
1072
|
+
console.warn(`Invalid select target: ${target}`);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const pos = Math.min(Math.max(0, target), state.doc.content.size);
|
|
1076
|
+
if (pos >= 0 && pos <= state.doc.content.size) {
|
|
1077
|
+
const tr = state.tr.setSelection(TextSelection.near(state.doc.resolve(pos)));
|
|
1078
|
+
viewRef.current.dispatch(tr);
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
console.warn(`Invalid cursor position for select: ${pos}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
log("Error selecting position:", error);
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
el() {
|
|
1089
|
+
return viewRef.current?.dom;
|
|
1090
|
+
},
|
|
1091
|
+
}), [
|
|
1092
|
+
focusEditor,
|
|
1093
|
+
handleUpdate,
|
|
1094
|
+
handleTeardown,
|
|
1095
|
+
persistFocus,
|
|
1096
|
+
getCurrentValue,
|
|
1097
|
+
moveCursorToEnd,
|
|
1098
|
+
tokenizer,
|
|
1099
|
+
resolveRunColor,
|
|
1100
|
+
]);
|
|
1101
|
+
// Expose editor methods
|
|
1102
|
+
useImperativeHandle(forwardedRef, () => imperativeMethods, [
|
|
1103
|
+
imperativeMethods,
|
|
1104
|
+
]);
|
|
1105
|
+
// Auto focus
|
|
1106
|
+
useIsomorphicLayoutEffect(() => {
|
|
1107
|
+
if (autoFocus && viewRef.current) {
|
|
1108
|
+
focusEditor();
|
|
1109
|
+
moveCursorToEnd();
|
|
1110
|
+
}
|
|
1111
|
+
}, [autoFocus, focusEditor, moveCursorToEnd]);
|
|
1112
|
+
return (React.createElement(Popper.Root, null,
|
|
1113
|
+
!showDropdownEffective ||
|
|
1114
|
+
showFormulaHelp ||
|
|
1115
|
+
!functionToken ? null : (React.createElement(StyledFormulaHelp, { onMouseDown: onShowFormulaHelp, title: "Show formula help", className: cn(editorZeroOutline ? "mr-px top-px" : undefined) },
|
|
1116
|
+
React.createElement(QuestionMarkIcon, { width: 12, height: 12 }))),
|
|
1117
|
+
!navigationMode && isFetchingParameters && (React.createElement("div", { className: "absolute top-1/2 right-[-18px] -translate-y-1/2 h-[14px]" },
|
|
1118
|
+
React.createElement(LoadingIcon, { className: "animate-spin" }))),
|
|
1119
|
+
React.createElement("div", { className: "h-full overflow-auto [&_div]:outline-none" },
|
|
1120
|
+
React.createElement("div", { ref: editorRef, className: cn(className, "whitespace-pre-wrap"), onKeyDownCapture: handleKeyDownCapture, onKeyDown: onKeyDownEditor, ...props })),
|
|
1121
|
+
React.createElement(Popper.Anchor, { asChild: true },
|
|
1122
|
+
React.createElement("button", { tabIndex: -1, className: cn("absolute left-0 top-0 w-0 h-full overflow-hidden m-0 p-0 border-none bg-transparent shadow-none"), style: {
|
|
1123
|
+
left: showMentionDropdownEffective ? mentionPositionLeft : 0,
|
|
1124
|
+
} })),
|
|
1125
|
+
showDropdownEffective && isDropdownOpen && (React.createElement(ConditionalWrapper, { condition: stickyEditor, wrapper: (children) => React.createElement(Portal, { asChild: true }, children) },
|
|
1126
|
+
React.createElement(Popper.Content, { side: "bottom", align: "start", className: "z-50", sideOffset: 2 },
|
|
1127
|
+
React.createElement(SuggestionsDropdown, { functionDescriptions: functionDescriptions, formulaMode: formulaMode, dropdownStyles: dropdownStyles, functionToken: functionToken, parameterIndex: parameterIndex, items: items, highlightedIndex: highlightedIndex, onSelect: onSelect, onChangeHighlightedIndex: onChangeHighlightedIndex, showFormulaHelp: showFormulaHelp, onHideFormulaHelp: onHideFormulaHelp })))),
|
|
1128
|
+
showDropdownEffective && showMentionDropdownEffective && (React.createElement(ConditionalWrapper, { condition: stickyEditor, wrapper: (children) => React.createElement(Portal, { asChild: true }, children) },
|
|
1129
|
+
React.createElement(Popper.Content, { side: "bottom", align: "start", className: "z-50", sideOffset: 2 },
|
|
1130
|
+
React.createElement(MentionsDropdown, { mentions: mentions, onSelect: handleInsertMention, highlightedIndex: mentionHighlightedIndex, isFetchingMentions: isFetchingMentions, MentionDropdownItemComponent: MentionDropdownItemComponent }))))));
|
|
1131
|
+
}));
|
|
1132
|
+
export const ProseMirrorEditor = withErrorBoundary(ProseMirrorEditorThatMayError, {
|
|
1133
|
+
FallbackComponent: ErrorFallback,
|
|
1134
|
+
});
|
|
1135
|
+
ProseMirrorEditor.displayName = "ProseMirrorEditor";
|