@doist/typist 14.1.2 → 15.0.0-next.1

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/README.md CHANGED
@@ -48,6 +48,10 @@ function TypistEditorContainer({ content }) {
48
48
  }
49
49
  ```
50
50
 
51
+ > **Note**
52
+ >
53
+ > `content`, `extensions`, and `placeholder` are initialization-only: they're read when the editor first mounts, and later changes to these props are ignored. See the [Component Lifecycle](https://typist.doist.dev/?path=/docs/documentation-tips-tricks-component-lifecycle--docs) documentation for how to handle runtime changes.
54
+
51
55
  If you're looking for additional documentation, in-depth examples, or a live demo, please check out our [Storybook](https://typist.doist.dev/).
52
56
 
53
57
  ## Resources
@@ -69,6 +69,9 @@ type TypistEditorProps = {
69
69
  className?: string;
70
70
  /**
71
71
  * The initial Markdown content for the editor.
72
+ *
73
+ * This value is only used when the editor is first mounted, subsequent changes to this prop are
74
+ * ignored. Use `editor.commands.setContent()` to update content at runtime.
72
75
  */
73
76
  content?: string;
74
77
  /**
@@ -80,61 +83,51 @@ type TypistEditorProps = {
80
83
  */
81
84
  editable?: boolean;
82
85
  /**
83
- * The list of required extensions to initialize the editor.
86
+ * The list of extensions to initialize the editor with.
84
87
  *
85
- * You may consider wrapping this prop with `useMemo` to prevent unnecessary re-renders.
88
+ * This value is only used when the editor is first mounted, subsequent changes to this prop are
89
+ * ignored. Extensions that depend on data that changes over time should read the current value
90
+ * at the moment it's needed from a mutable source you update from outside (a ref or a store),
91
+ * rather than capturing the value when the extension is created.
86
92
  */
87
93
  extensions: Extensions;
88
94
  /**
89
95
  * A short hint that gives users an idea what can be entered in the editor.
96
+ *
97
+ * This value is only used when the editor is first mounted, subsequent changes to this prop are
98
+ * ignored.
90
99
  */
91
100
  placeholder?: string;
92
101
  /**
93
102
  * The event handler that is fired before the editor view is created.
94
- *
95
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
96
103
  */
97
104
  onBeforeCreate?: (props: BeforeCreateProps) => void;
98
105
  /**
99
106
  * The event handler that is fired when the editor view is ready.
100
- *
101
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
102
107
  */
103
108
  onCreate?: (props: CreateProps) => void;
104
109
  /**
105
110
  * The event handler that is fired when the editor content has changed.
106
- *
107
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
108
111
  */
109
112
  onUpdate?: (props: UpdateProps) => void;
110
113
  /**
111
114
  * The event handler that is fired when the editor selection has changed.
112
- *
113
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
114
115
  */
115
116
  onSelectionUpdate?: (props: SelectionUpdateProps) => void;
116
117
  /**
117
118
  * The event handler that is fired when the editor state has changed.
118
- *
119
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
120
119
  */
121
120
  onTransaction?: (props: TransacationProps) => void;
122
121
  /**
123
122
  * The event handler that is fired when the editor view gains focus.
124
- *
125
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
126
123
  */
127
124
  onFocus?: (props: FocusProps) => void;
128
125
  /**
129
126
  * The event handler that is fired when the editor view loses focus.
130
- *
131
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
132
127
  */
133
128
  onBlur?: (props: BlurProps) => void;
134
129
  /**
135
130
  * The event handler that is fired when the editor view is being destroyed.
136
- *
137
- * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.
138
131
  */
139
132
  onDestroy?: (props: DestroyProps) => void;
140
133
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"typist-editor.d.ts","names":[],"sources":["../../src/components/typist-editor.tsx"],"mappings":";;;;;;;;AAgBiD;;KAM5C,eAAA;EAIgB;;;EAAjB,SAAA,QAAiB,MAAA;EAYF;;;EAPf,WAAA;EAKA;;;EAAA,2BAAA,GACI,QAAA,aACC,UAAA,QAAkB,2BAAA;AAAA;AAA2B;AAAA;;AAAA,KAMjD,iBAAA,GAAoB,YAAY;;AAAA;AAAA;KAKhC,WAAA,GAAc,YAAY;;;AAAA;KAK1B,WAAA,GAAc,YAAA,aAAyB,IAAA,CAAK,eAAA;;;;KAK5C,oBAAA,GAAuB,YAAY;;;;KAKnC,iBAAA,GAAoB,YAAY;;;;KAKhC,UAAA,GAAa,YAAY;AAfkC;;;AAAA,KAoB3D,SAAA,GAAY,YAAY;AAfW;AAAA;;AAAA,KAoBnC,YAAA,GAAe,YAAY;;AAfK;AAAA;;KAqBhC,iBAAA;EAhByB;AAAA;AAAA;EAoB1B,SAAA;;;AAfyB;EAoBzB,SAAA;EAfa;;;EAoBb,OAAA;EAdC;;;EAmBD,gBAAA,GAAmB,SAAA;EAYP;;;EAPZ,QAAA;EAwC4B;;;;;EAjC5B,UAAA,EAAY,UAAA;EA2EG;;;EAtEf,WAAA;EAuFoC;;;;;EAhFpC,cAAA,IAAkB,KAAA,EAAO,iBAAA;EAxBN;;;;;EA+BnB,QAAA,IAAY,KAAA,EAAO,WAAA;EAPM;;;;;EAczB,QAAA,IAAY,KAAA,EAAO,WAAA;EAAA;;;;;EAOnB,iBAAA,IAAqB,KAAA,EAAO,oBAAA;EAOJ;;;;;EAAxB,aAAA,IAAiB,KAAA,EAAO,iBAAA;EAcP;;;;;EAPjB,OAAA,IAAW,KAAA,EAAO,UAAA;EAqBG;;;;;EAdrB,MAAA,IAAU,KAAA,EAAO,SAAA;EA4BG;;;;;EArBpB,SAAA,IAAa,KAAA,EAAO,YAAA;EA+BgB;AAAA;AAAA;;;EAxBpC,kBAAA,GAAqB,KAAA,CAAM,cAAA;EA+Bb;;;;;EAxBd,YAAA,GAAe,KAAA,CAAM,cAAA;;;;;;EAOrB,iBAAA,GAAoB,KAAA,CAAM,cAAA;;;;EAK1B,OAAA,GAAU,wBAAA;;;;EAKV,SAAA,GAAY,wBAAA;AAAA;;;;;cAOV,YAAA,kBAAY,yBAAA,CAAA,iBAAA,mBAAA,aAAA,CAAA,eAAA"}
1
+ {"version":3,"file":"typist-editor.d.ts","names":[],"sources":["../../src/components/typist-editor.tsx"],"mappings":";;;;;;;;AAeiD;;KAM5C,eAAA;EAIgB;;;EAAjB,SAAA,QAAiB,MAAA;EAYF;;;EAPf,WAAA;EAKA;;;EAAA,2BAAA,GACI,QAAA,aACC,UAAA,QAAkB,2BAAA;AAAA;AAA2B;AAAA;;AAAA,KAMjD,iBAAA,GAAoB,YAAY;;AAAA;AAAA;KAKhC,WAAA,GAAc,YAAY;;;AAAA;KAK1B,WAAA,GAAc,YAAA,aAAyB,IAAA,CAAK,eAAA;;;;KAK5C,oBAAA,GAAuB,YAAY;;;;KAKnC,iBAAA,GAAoB,YAAY;;;;KAKhC,UAAA,GAAa,YAAY;AAfkC;;;AAAA,KAoB3D,SAAA,GAAY,YAAY;AAfW;AAAA;;AAAA,KAoBnC,YAAA,GAAe,YAAY;;AAfK;AAAA;;KAqBhC,iBAAA;EAhByB;AAAA;AAAA;EAoB1B,SAAA;;;AAfyB;EAoBzB,SAAA;EAfa;;;AAAe;AAAA;;EAuB5B,OAAA;EAKmB;;;EAAnB,gBAAA,GAAmB,SAAA;EAsCA;;;EAjCnB,QAAA;EAqDiB;;;;;;;;EA3CjB,UAAA,EAAY,UAAA;EAjCZ;;;;;;EAyCA,WAAA;EARY;;;EAaZ,cAAA,IAAkB,KAAA,EAAO,iBAAA;EAAP;;;EAKlB,QAAA,IAAY,KAAA,EAAO,WAAA;EAKnB;;;EAAA,QAAA,IAAY,KAAA,EAAO,WAAA;EAKS;;;EAA5B,iBAAA,IAAqB,KAAA,EAAO,oBAAA;EAKX;;;EAAjB,aAAA,IAAiB,KAAA,EAAO,iBAAA;EAUxB;;;EALA,OAAA,IAAW,KAAA,EAAO,UAAA;EAUE;;;EALpB,MAAA,IAAU,KAAA,EAAO,SAAA;EAYU;;;EAP3B,SAAA,IAAa,KAAA,EAAO,YAAA;EAqBpB;;;;;EAdA,kBAAA,GAAqB,KAAA,CAAM,cAAA;EAwBf;;AAAwB;AAAA;;EAjBpC,YAAA,GAAe,KAAA,CAAM,cAAA;EAwBP;;;;;EAjBd,iBAAA,GAAoB,KAAA,CAAM,cAAA;EAiBZ;;;EAZd,OAAA,GAAU,wBAAA;;;;EAKV,SAAA,GAAY,wBAAA;AAAA;;;;;cAOV,YAAA,kBAAY,yBAAA,CAAA,iBAAA,mBAAA,aAAA,CAAA,eAAA"}
@@ -2,13 +2,12 @@ import { isMultilineDocument, isPlainTextDocument } from "../helpers/schema.js";
2
2
  import { getHTMLSerializerInstance } from "../serializers/html/html.js";
3
3
  import { ExtraEditorCommands } from "../extensions/core/extra-editor-commands/extra-editor-commands.js";
4
4
  import { ViewEventHandlers } from "../extensions/core/view-event-handlers.js";
5
- import { useEditor } from "../hooks/use-editor.js";
6
5
  import { getMarkdownSerializerInstance } from "../serializers/markdown/markdown.js";
7
6
  import { getAllNodesAttributesByType, resolveContentSelection } from "./typist-editor.helper.js";
8
- import { forwardRef, useCallback, useImperativeHandle, useMemo } from "react";
7
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from "react";
9
8
  import { getSchema } from "@tiptap/core";
10
9
  import { Placeholder } from "@tiptap/extension-placeholder";
11
- import { EditorContent } from "@tiptap/react";
10
+ import { EditorContent, useEditor } from "@tiptap/react";
12
11
  import { jsx } from "react/jsx-runtime";
13
12
  //#region src/components/typist-editor.tsx
14
13
  /**
@@ -16,21 +15,26 @@ import { jsx } from "react/jsx-runtime";
16
15
  * top of the amazing [Tiptap](https://tiptap.dev/) library.
17
16
  */
18
17
  const TypistEditor = forwardRef(function TypistEditor({ autoFocus, className, content = "", contentSelection, editable = true, extensions, placeholder, onBeforeCreate, onCreate, onUpdate, onSelectionUpdate, onTransaction, onFocus, onBlur, onDestroy, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, onClick, onKeyDown }, ref) {
18
+ const [initialExtensions] = useState(() => extensions);
19
+ const [initialContent] = useState(() => content);
20
+ const [initialPlaceholder] = useState(() => placeholder);
21
+ const [initialOnClick] = useState(() => onClick);
22
+ const [initialOnKeyDown] = useState(() => onKeyDown);
19
23
  const allExtensions = useMemo(function initializeExtensions() {
20
24
  return [
21
- ...placeholder ? [Placeholder.configure({ placeholder })] : [],
25
+ ...initialPlaceholder ? [Placeholder.configure({ placeholder: initialPlaceholder })] : [],
22
26
  ExtraEditorCommands,
23
27
  ViewEventHandlers.configure({
24
- onClick,
25
- onKeyDown
28
+ onClick: initialOnClick,
29
+ onKeyDown: initialOnKeyDown
26
30
  }),
27
- ...extensions
31
+ ...initialExtensions
28
32
  ];
29
33
  }, [
30
- extensions,
31
- onClick,
32
- onKeyDown,
33
- placeholder
34
+ initialExtensions,
35
+ initialPlaceholder,
36
+ initialOnClick,
37
+ initialOnKeyDown
34
38
  ]);
35
39
  const schema = useMemo(function generateProseMirrorSchema() {
36
40
  return getSchema(allExtensions);
@@ -42,23 +46,8 @@ const TypistEditor = forwardRef(function TypistEditor({ autoFocus, className, co
42
46
  return getMarkdownSerializerInstance(schema);
43
47
  }, [schema]);
44
48
  const htmlContent = useMemo(function generateHTMLContent() {
45
- return htmlSerializer.serialize(content);
46
- }, [content, htmlSerializer]);
47
- const ariaAttributes = useMemo(function initializeAriaAttributes() {
48
- return {
49
- "aria-readonly": String(!editable),
50
- "aria-multiline": String(isMultilineDocument(schema)),
51
- ...ariaDescribedBy ? { "aria-describedby": ariaDescribedBy } : {},
52
- ...ariaLabel ? { "aria-label": ariaLabel } : {},
53
- ...ariaLabelledBy ? { "aria-labelledby": ariaLabelledBy } : {}
54
- };
55
- }, [
56
- ariaDescribedBy,
57
- ariaLabel,
58
- ariaLabelledBy,
59
- editable,
60
- schema
61
- ]);
49
+ return htmlSerializer.serialize(initialContent);
50
+ }, [initialContent, htmlSerializer]);
62
51
  const handleCreate = useCallback(function handleCreate(props) {
63
52
  const { view } = props.editor;
64
53
  if (autoFocus && contentSelection) view.dispatch(view.state.tr.setSelection(resolveContentSelection(view.state.doc, contentSelection)).scrollIntoView());
@@ -68,18 +57,36 @@ const TypistEditor = forwardRef(function TypistEditor({ autoFocus, className, co
68
57
  contentSelection,
69
58
  onCreate
70
59
  ]);
60
+ const editorProps = useMemo(function initializeEditorProps() {
61
+ return { attributes: {
62
+ "data-typist-editor": "true",
63
+ ...isPlainTextDocument(schema) ? { "data-plain-text": "true" } : { "data-rich-text": "true" },
64
+ "aria-readonly": String(!editable),
65
+ "aria-multiline": String(isMultilineDocument(schema)),
66
+ ...ariaDescribedBy ? { "aria-describedby": ariaDescribedBy } : {},
67
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
68
+ ...ariaLabelledBy ? { "aria-labelledby": ariaLabelledBy } : {},
69
+ role: "textbox"
70
+ } };
71
+ }, [
72
+ schema,
73
+ editable,
74
+ ariaDescribedBy,
75
+ ariaLabel,
76
+ ariaLabelledBy
77
+ ]);
78
+ const parseOptions = useMemo(function initializeParseOptions() {
79
+ return { preserveWhitespace: isPlainTextDocument(schema) };
80
+ }, [schema]);
71
81
  const editor = useEditor({
72
82
  autofocus: autoFocus ? "end" : false,
73
83
  content: htmlContent,
74
84
  editable,
75
- editorProps: { attributes: {
76
- "data-typist-editor": "true",
77
- ...isPlainTextDocument(schema) ? { "data-plain-text": "true" } : { "data-rich-text": "true" },
78
- role: "textbox",
79
- ...ariaAttributes
80
- } },
85
+ editorProps,
81
86
  extensions: allExtensions,
82
- parseOptions: { preserveWhitespace: isPlainTextDocument(schema) },
87
+ parseOptions,
88
+ immediatelyRender: true,
89
+ shouldRerenderOnTransaction: false,
83
90
  ...onBeforeCreate ? { onBeforeCreate } : {},
84
91
  onCreate: handleCreate,
85
92
  ...onUpdate ? { onUpdate(props) {
@@ -95,22 +102,19 @@ const TypistEditor = forwardRef(function TypistEditor({ autoFocus, className, co
95
102
  ...onFocus ? { onFocus } : {},
96
103
  ...onBlur ? { onBlur } : {},
97
104
  ...onDestroy ? { onDestroy } : {}
105
+ });
106
+ useEffect(function syncEditableState() {
107
+ editor.setEditable(editable);
108
+ }, [editor, editable]);
109
+ useEffect(function syncViewEventHandlers() {
110
+ editor.storage.viewEventHandlers.setHandlers({
111
+ onClick,
112
+ onKeyDown
113
+ });
98
114
  }, [
99
- allExtensions,
100
- ariaAttributes,
101
- autoFocus,
102
- editable,
103
- handleCreate,
104
- htmlContent,
105
- markdownSerializer,
106
- onBeforeCreate,
107
- onBlur,
108
- onDestroy,
109
- onFocus,
110
- onSelectionUpdate,
111
- onTransaction,
112
- onUpdate,
113
- schema
115
+ editor,
116
+ onClick,
117
+ onKeyDown
114
118
  ]);
115
119
  useImperativeHandle(ref, function exposeHelperFunctionsToParent() {
116
120
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"typist-editor.js","names":[],"sources":["../../src/components/typist-editor.tsx"],"sourcesContent":["import { forwardRef, useCallback, useImperativeHandle, useMemo } from 'react'\n\nimport { getSchema } from '@tiptap/core'\nimport { Placeholder } from '@tiptap/extension-placeholder'\nimport { EditorContent } from '@tiptap/react'\n\nimport { ExtraEditorCommands } from '../extensions/core/extra-editor-commands/extra-editor-commands'\nimport { ViewEventHandlers, ViewEventHandlersOptions } from '../extensions/core/view-event-handlers'\nimport { isMultilineDocument, isPlainTextDocument } from '../helpers/schema'\nimport { useEditor } from '../hooks/use-editor'\nimport { getHTMLSerializerInstance } from '../serializers/html/html'\nimport { getMarkdownSerializerInstance } from '../serializers/markdown/markdown'\n\nimport { getAllNodesAttributesByType, resolveContentSelection } from './typist-editor.helper'\n\nimport type { Editor as CoreEditor, EditorEvents, Extensions } from '@tiptap/core'\nimport type { Selection } from '@tiptap/pm/state'\n\n/**\n * The forwarded ref that describes the helper methods that the `TypistEditor` parent component\n * will have access to.\n */\ntype TypistEditorRef = {\n /**\n * Returns the `Editor` instance associated to the `TypistEditor` component.\n */\n getEditor: () => CoreEditor\n\n /**\n * Returns the current editor document output as Markdown.\n */\n getMarkdown: () => string\n\n /**\n * Returns the attributes of a given node type for all the nodes in the editor document.\n */\n getAllNodesAttributesByType: (\n nodeType: string,\n ) => ReturnType<typeof getAllNodesAttributesByType>\n}\n\n/**\n * The properties that describe the `beforeCreate` editor event.\n */\ntype BeforeCreateProps = EditorEvents['beforeCreate']\n\n/**\n * The properties that describe the `create` editor event.\n */\ntype CreateProps = EditorEvents['create']\n\n/**\n * The properties that describe the `update` editor event.\n */\ntype UpdateProps = EditorEvents['update'] & Pick<TypistEditorRef, 'getMarkdown'>\n\n/**\n * The properties that describe the `selectionUpdate` editor event.\n */\ntype SelectionUpdateProps = EditorEvents['selectionUpdate']\n\n/**\n * The properties that describe the `transaction` editor event.\n */\ntype TransacationProps = EditorEvents['transaction']\n\n/**\n * The properties that describe the `focus` editor event.\n */\ntype FocusProps = EditorEvents['focus']\n\n/**\n * The properties that describe the `blur` editor event.\n */\ntype BlurProps = EditorEvents['blur']\n\n/**\n * The properties that describe the `destroy` editor event.\n */\ntype DestroyProps = EditorEvents['destroy']\n\n/**\n * The properties available to represent an instance of the `TypistEditor` component, including\n * the supported WAI-ARIA 1.1 attributes.\n */\ntype TypistEditorProps = {\n /**\n * Auto focus the editor to the end of the document on initialization.\n */\n autoFocus?: boolean\n\n /**\n * The CSS class for the container surrounding the editor DOM element.\n */\n className?: string\n\n /**\n * The initial Markdown content for the editor.\n */\n content?: string\n\n /**\n * The initial content selection (only applied if `autoFocus` is `true`).\n */\n contentSelection?: Selection\n\n /**\n * Determines if users can write into the editor.\n */\n editable?: boolean\n\n /**\n * The list of required extensions to initialize the editor.\n *\n * You may consider wrapping this prop with `useMemo` to prevent unnecessary re-renders.\n */\n extensions: Extensions\n\n /**\n * A short hint that gives users an idea what can be entered in the editor.\n */\n placeholder?: string\n\n /**\n * The event handler that is fired before the editor view is created.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onBeforeCreate?: (props: BeforeCreateProps) => void\n\n /**\n * The event handler that is fired when the editor view is ready.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onCreate?: (props: CreateProps) => void\n\n /**\n * The event handler that is fired when the editor content has changed.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onUpdate?: (props: UpdateProps) => void\n\n /**\n * The event handler that is fired when the editor selection has changed.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onSelectionUpdate?: (props: SelectionUpdateProps) => void\n\n /**\n * The event handler that is fired when the editor state has changed.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onTransaction?: (props: TransacationProps) => void\n\n /**\n * The event handler that is fired when the editor view gains focus.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onFocus?: (props: FocusProps) => void\n\n /**\n * The event handler that is fired when the editor view loses focus.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onBlur?: (props: BlurProps) => void\n\n /**\n * The event handler that is fired when the editor view is being destroyed.\n *\n * You may consider wrapping this prop with `useCallback` to prevent unnecessary re-renders.\n */\n onDestroy?: (props: DestroyProps) => void\n\n /**\n * Identifies the element (or elements) that describes the object.\n *\n * @see aria-labelledby\n */\n 'aria-describedby'?: React.AriaAttributes['aria-describedby']\n\n /**\n * Defines a string value that labels the current element.\n *\n * @see aria-labelledby.\n */\n 'aria-label'?: React.AriaAttributes['aria-label']\n\n /**\n * Identifies the element (or elements) that labels the current element.\n *\n * @see aria-describedby.\n */\n 'aria-labelledby'?: React.AriaAttributes['aria-labelledby']\n\n /**\n * The event handler that processes `click` events in the editor.\n */\n onClick?: ViewEventHandlersOptions['onClick']\n\n /**\n * The event handler that processes `keydown` events in the editor.\n */\n onKeyDown?: ViewEventHandlersOptions['onKeyDown']\n}\n\n/**\n * The `TypistEditor` component represents a plain-text or a rich-text editing control, built on\n * top of the amazing [Tiptap](https://tiptap.dev/) library.\n */\nconst TypistEditor = forwardRef<TypistEditorRef, TypistEditorProps>(function TypistEditor(\n {\n autoFocus,\n className,\n content = '',\n contentSelection,\n editable = true,\n extensions,\n placeholder,\n onBeforeCreate,\n onCreate,\n onUpdate,\n onSelectionUpdate,\n onTransaction,\n onFocus,\n onBlur,\n onDestroy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n onClick,\n onKeyDown,\n },\n ref,\n) {\n const allExtensions = useMemo(\n function initializeExtensions() {\n return [\n ...(placeholder\n ? [\n Placeholder.configure({\n placeholder,\n }),\n ]\n : []),\n ExtraEditorCommands,\n ViewEventHandlers.configure({\n onClick,\n onKeyDown,\n }),\n // Always register external extensions at the end so they get a higher priority and\n // are loaded earlier (necessary to override behaviors from built-in extensions)\n ...extensions,\n ]\n },\n [extensions, onClick, onKeyDown, placeholder],\n )\n\n const schema = useMemo(\n function generateProseMirrorSchema() {\n return getSchema(allExtensions)\n },\n [allExtensions],\n )\n\n const htmlSerializer = useMemo(\n function initializeHTMLSerializer() {\n return getHTMLSerializerInstance(schema)\n },\n [schema],\n )\n\n const markdownSerializer = useMemo(\n function initializeMarkdownSerializer() {\n return getMarkdownSerializerInstance(schema)\n },\n [schema],\n )\n\n const htmlContent = useMemo(\n function generateHTMLContent() {\n return htmlSerializer.serialize(content)\n },\n [content, htmlSerializer],\n )\n\n const ariaAttributes = useMemo(\n function initializeAriaAttributes() {\n return {\n 'aria-readonly': String(!editable),\n 'aria-multiline': String(isMultilineDocument(schema)),\n ...(ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : {}),\n ...(ariaLabel ? { 'aria-label': ariaLabel } : {}),\n ...(ariaLabelledBy ? { 'aria-labelledby': ariaLabelledBy } : {}),\n }\n },\n [ariaDescribedBy, ariaLabel, ariaLabelledBy, editable, schema],\n )\n\n const handleCreate = useCallback(\n function handleCreate(props: CreateProps) {\n const { view } = props.editor\n\n // Apply a selection to the document if one was given and `autoFocus` is `true`\n if (autoFocus && contentSelection) {\n view.dispatch(\n view.state.tr\n .setSelection(resolveContentSelection(view.state.doc, contentSelection))\n .scrollIntoView(),\n )\n }\n\n // Invoke the user `onCreate` handle after all internal initializations\n onCreate?.(props)\n },\n [autoFocus, contentSelection, onCreate],\n )\n\n const editor = useEditor(\n {\n autofocus: autoFocus ? 'end' : false,\n content: htmlContent,\n editable,\n editorProps: {\n attributes: {\n 'data-typist-editor': 'true',\n ...(isPlainTextDocument(schema)\n ? { 'data-plain-text': 'true' }\n : { 'data-rich-text': 'true' }),\n role: 'textbox',\n ...ariaAttributes,\n },\n },\n extensions: allExtensions,\n parseOptions: {\n preserveWhitespace: isPlainTextDocument(schema),\n },\n ...(onBeforeCreate ? { onBeforeCreate } : {}),\n onCreate: handleCreate,\n ...(onUpdate\n ? {\n onUpdate(props) {\n onUpdate({\n ...props,\n getMarkdown() {\n return markdownSerializer.serialize(props.editor.getHTML())\n },\n })\n },\n }\n : {}),\n ...(onSelectionUpdate ? { onSelectionUpdate } : {}),\n ...(onTransaction ? { onTransaction } : {}),\n ...(onFocus ? { onFocus } : {}),\n ...(onBlur ? { onBlur } : {}),\n ...(onDestroy ? { onDestroy } : {}),\n },\n [\n allExtensions,\n ariaAttributes,\n autoFocus,\n editable,\n handleCreate,\n htmlContent,\n markdownSerializer,\n onBeforeCreate,\n onBlur,\n onDestroy,\n onFocus,\n onSelectionUpdate,\n onTransaction,\n onUpdate,\n schema,\n ],\n )\n\n useImperativeHandle(\n ref,\n function exposeHelperFunctionsToParent() {\n return {\n getEditor() {\n return editor\n },\n getMarkdown() {\n return markdownSerializer.serialize(editor.getHTML())\n },\n getAllNodesAttributesByType(nodeType) {\n return getAllNodesAttributesByType(editor.state.doc, nodeType)\n },\n }\n },\n [editor, markdownSerializer],\n )\n\n return <EditorContent className={className} editor={editor} />\n})\n\nexport { TypistEditor }\n\nexport type {\n BeforeCreateProps,\n BlurProps,\n CreateProps,\n DestroyProps,\n FocusProps,\n SelectionUpdateProps,\n TransacationProps,\n TypistEditorProps,\n TypistEditorRef,\n UpdateProps,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"typist-editor.js","names":[],"sources":["../../src/components/typist-editor.tsx"],"sourcesContent":["import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'\n\nimport { getSchema } from '@tiptap/core'\nimport { Placeholder } from '@tiptap/extension-placeholder'\nimport { EditorContent, useEditor } from '@tiptap/react'\n\nimport { ExtraEditorCommands } from '../extensions/core/extra-editor-commands/extra-editor-commands'\nimport { ViewEventHandlers, ViewEventHandlersOptions } from '../extensions/core/view-event-handlers'\nimport { isMultilineDocument, isPlainTextDocument } from '../helpers/schema'\nimport { getHTMLSerializerInstance } from '../serializers/html/html'\nimport { getMarkdownSerializerInstance } from '../serializers/markdown/markdown'\n\nimport { getAllNodesAttributesByType, resolveContentSelection } from './typist-editor.helper'\n\nimport type { Editor as CoreEditor, EditorEvents, Extensions } from '@tiptap/core'\nimport type { Selection } from '@tiptap/pm/state'\n\n/**\n * The forwarded ref that describes the helper methods that the `TypistEditor` parent component\n * will have access to.\n */\ntype TypistEditorRef = {\n /**\n * Returns the `Editor` instance associated to the `TypistEditor` component.\n */\n getEditor: () => CoreEditor\n\n /**\n * Returns the current editor document output as Markdown.\n */\n getMarkdown: () => string\n\n /**\n * Returns the attributes of a given node type for all the nodes in the editor document.\n */\n getAllNodesAttributesByType: (\n nodeType: string,\n ) => ReturnType<typeof getAllNodesAttributesByType>\n}\n\n/**\n * The properties that describe the `beforeCreate` editor event.\n */\ntype BeforeCreateProps = EditorEvents['beforeCreate']\n\n/**\n * The properties that describe the `create` editor event.\n */\ntype CreateProps = EditorEvents['create']\n\n/**\n * The properties that describe the `update` editor event.\n */\ntype UpdateProps = EditorEvents['update'] & Pick<TypistEditorRef, 'getMarkdown'>\n\n/**\n * The properties that describe the `selectionUpdate` editor event.\n */\ntype SelectionUpdateProps = EditorEvents['selectionUpdate']\n\n/**\n * The properties that describe the `transaction` editor event.\n */\ntype TransacationProps = EditorEvents['transaction']\n\n/**\n * The properties that describe the `focus` editor event.\n */\ntype FocusProps = EditorEvents['focus']\n\n/**\n * The properties that describe the `blur` editor event.\n */\ntype BlurProps = EditorEvents['blur']\n\n/**\n * The properties that describe the `destroy` editor event.\n */\ntype DestroyProps = EditorEvents['destroy']\n\n/**\n * The properties available to represent an instance of the `TypistEditor` component, including\n * the supported WAI-ARIA 1.1 attributes.\n */\ntype TypistEditorProps = {\n /**\n * Auto focus the editor to the end of the document on initialization.\n */\n autoFocus?: boolean\n\n /**\n * The CSS class for the container surrounding the editor DOM element.\n */\n className?: string\n\n /**\n * The initial Markdown content for the editor.\n *\n * This value is only used when the editor is first mounted, subsequent changes to this prop are\n * ignored. Use `editor.commands.setContent()` to update content at runtime.\n */\n content?: string\n\n /**\n * The initial content selection (only applied if `autoFocus` is `true`).\n */\n contentSelection?: Selection\n\n /**\n * Determines if users can write into the editor.\n */\n editable?: boolean\n\n /**\n * The list of extensions to initialize the editor with.\n *\n * This value is only used when the editor is first mounted, subsequent changes to this prop are\n * ignored. Extensions that depend on data that changes over time should read the current value\n * at the moment it's needed from a mutable source you update from outside (a ref or a store),\n * rather than capturing the value when the extension is created.\n */\n extensions: Extensions\n\n /**\n * A short hint that gives users an idea what can be entered in the editor.\n *\n * This value is only used when the editor is first mounted, subsequent changes to this prop are\n * ignored.\n */\n placeholder?: string\n\n /**\n * The event handler that is fired before the editor view is created.\n */\n onBeforeCreate?: (props: BeforeCreateProps) => void\n\n /**\n * The event handler that is fired when the editor view is ready.\n */\n onCreate?: (props: CreateProps) => void\n\n /**\n * The event handler that is fired when the editor content has changed.\n */\n onUpdate?: (props: UpdateProps) => void\n\n /**\n * The event handler that is fired when the editor selection has changed.\n */\n onSelectionUpdate?: (props: SelectionUpdateProps) => void\n\n /**\n * The event handler that is fired when the editor state has changed.\n */\n onTransaction?: (props: TransacationProps) => void\n\n /**\n * The event handler that is fired when the editor view gains focus.\n */\n onFocus?: (props: FocusProps) => void\n\n /**\n * The event handler that is fired when the editor view loses focus.\n */\n onBlur?: (props: BlurProps) => void\n\n /**\n * The event handler that is fired when the editor view is being destroyed.\n */\n onDestroy?: (props: DestroyProps) => void\n\n /**\n * Identifies the element (or elements) that describes the object.\n *\n * @see aria-labelledby\n */\n 'aria-describedby'?: React.AriaAttributes['aria-describedby']\n\n /**\n * Defines a string value that labels the current element.\n *\n * @see aria-labelledby.\n */\n 'aria-label'?: React.AriaAttributes['aria-label']\n\n /**\n * Identifies the element (or elements) that labels the current element.\n *\n * @see aria-describedby.\n */\n 'aria-labelledby'?: React.AriaAttributes['aria-labelledby']\n\n /**\n * The event handler that processes `click` events in the editor.\n */\n onClick?: ViewEventHandlersOptions['onClick']\n\n /**\n * The event handler that processes `keydown` events in the editor.\n */\n onKeyDown?: ViewEventHandlersOptions['onKeyDown']\n}\n\n/**\n * The `TypistEditor` component represents a plain-text or a rich-text editing control, built on\n * top of the amazing [Tiptap](https://tiptap.dev/) library.\n */\nconst TypistEditor = forwardRef<TypistEditorRef, TypistEditorProps>(function TypistEditor(\n {\n autoFocus,\n className,\n content = '',\n contentSelection,\n editable = true,\n extensions,\n placeholder,\n onBeforeCreate,\n onCreate,\n onUpdate,\n onSelectionUpdate,\n onTransaction,\n onFocus,\n onBlur,\n onDestroy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n onClick,\n onKeyDown,\n },\n ref,\n) {\n // Extensions and content are captured once at mount. Subsequent changes to these props are\n // ignored because recreating the editor is expensive and unnecessary for runtime updates.\n // Use `editor.commands.setContent()` to update content, and design extensions to handle\n // dynamic data internally (e.g., via stores or refs) rather than requiring reconfiguration.\n const [initialExtensions] = useState(() => extensions)\n const [initialContent] = useState(() => content)\n const [initialPlaceholder] = useState(() => placeholder)\n\n // Capture the initial click and keydown handlers so they are configured on the extension from\n // the first render. Unlike content and extensions, they stay reactive, so later changes are\n // synced at runtime.\n const [initialOnClick] = useState(() => onClick)\n const [initialOnKeyDown] = useState(() => onKeyDown)\n\n const allExtensions = useMemo(\n function initializeExtensions() {\n return [\n ...(initialPlaceholder\n ? [\n Placeholder.configure({\n placeholder: initialPlaceholder,\n }),\n ]\n : []),\n ExtraEditorCommands,\n ViewEventHandlers.configure({\n onClick: initialOnClick,\n onKeyDown: initialOnKeyDown,\n }),\n // Always register external extensions at the end so they get a higher priority and\n // are loaded earlier (necessary to override behaviors from built-in extensions)\n ...initialExtensions,\n ]\n },\n [initialExtensions, initialPlaceholder, initialOnClick, initialOnKeyDown],\n )\n\n const schema = useMemo(\n function generateProseMirrorSchema() {\n return getSchema(allExtensions)\n },\n [allExtensions],\n )\n\n const htmlSerializer = useMemo(\n function initializeHTMLSerializer() {\n return getHTMLSerializerInstance(schema)\n },\n [schema],\n )\n\n const markdownSerializer = useMemo(\n function initializeMarkdownSerializer() {\n return getMarkdownSerializerInstance(schema)\n },\n [schema],\n )\n\n const htmlContent = useMemo(\n function generateHTMLContent() {\n return htmlSerializer.serialize(initialContent)\n },\n [initialContent, htmlSerializer],\n )\n\n const handleCreate = useCallback(\n function handleCreate(props: CreateProps) {\n const { view } = props.editor\n\n // Apply a selection to the document if one was given and `autoFocus` is `true`\n if (autoFocus && contentSelection) {\n view.dispatch(\n view.state.tr\n .setSelection(resolveContentSelection(view.state.doc, contentSelection))\n .scrollIntoView(),\n )\n }\n\n // Invoke the user `onCreate` handle after all internal initializations\n onCreate?.(props)\n },\n [autoFocus, contentSelection, onCreate],\n )\n\n // Keep these option objects memoized so they preserve a stable reference across renders. The\n // built-in `useEditor` compares options by reference and reconfigures the editor in place when\n // they differ, so passing inline objects would trigger that work on every parent re-render.\n const editorProps = useMemo(\n function initializeEditorProps() {\n return {\n attributes: {\n 'data-typist-editor': 'true',\n ...(isPlainTextDocument(schema)\n ? { 'data-plain-text': 'true' }\n : { 'data-rich-text': 'true' }),\n 'aria-readonly': String(!editable),\n 'aria-multiline': String(isMultilineDocument(schema)),\n ...(ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : {}),\n ...(ariaLabel ? { 'aria-label': ariaLabel } : {}),\n ...(ariaLabelledBy ? { 'aria-labelledby': ariaLabelledBy } : {}),\n role: 'textbox',\n },\n }\n },\n [schema, editable, ariaDescribedBy, ariaLabel, ariaLabelledBy],\n )\n\n const parseOptions = useMemo(\n function initializeParseOptions() {\n return {\n preserveWhitespace: isPlainTextDocument(schema),\n }\n },\n [schema],\n )\n\n const editor = useEditor({\n autofocus: autoFocus ? 'end' : false,\n content: htmlContent,\n editable,\n editorProps,\n extensions: allExtensions,\n parseOptions,\n\n // Tiptap's `useEditor` returns `null` by default on the first render to support SSR.\n // Typist has no need for SSR, so we opt into immediate rendering to guarantee a\n // non-null editor instance from the first render.\n immediatelyRender: true,\n\n // Opt-out of the legacy behavior that re-renders the component on every ProseMirror\n // transaction (e.g. keystrokes, selection changes). Typist doesn't read reactive editor\n // state during render, so these re-renders would be wasteful. Consumers that need\n // reactive state (e.g. toolbars) should subscribe directly via `useSyncExternalStore`.\n shouldRerenderOnTransaction: false,\n\n ...(onBeforeCreate ? { onBeforeCreate } : {}),\n onCreate: handleCreate,\n ...(onUpdate\n ? {\n onUpdate(props) {\n onUpdate({\n ...props,\n getMarkdown() {\n return markdownSerializer.serialize(props.editor.getHTML())\n },\n })\n },\n }\n : {}),\n ...(onSelectionUpdate ? { onSelectionUpdate } : {}),\n ...(onTransaction ? { onTransaction } : {}),\n ...(onFocus ? { onFocus } : {}),\n ...(onBlur ? { onBlur } : {}),\n ...(onDestroy ? { onDestroy } : {}),\n })\n\n useEffect(\n function syncEditableState() {\n editor.setEditable(editable)\n },\n [editor, editable],\n )\n\n // The editor is created once, so push the latest handlers into the extension as they change\n useEffect(\n function syncViewEventHandlers() {\n editor.storage.viewEventHandlers.setHandlers({ onClick, onKeyDown })\n },\n [editor, onClick, onKeyDown],\n )\n\n useImperativeHandle(\n ref,\n function exposeHelperFunctionsToParent() {\n return {\n getEditor() {\n return editor\n },\n getMarkdown() {\n return markdownSerializer.serialize(editor.getHTML())\n },\n getAllNodesAttributesByType(nodeType) {\n return getAllNodesAttributesByType(editor.state.doc, nodeType)\n },\n }\n },\n [editor, markdownSerializer],\n )\n\n return <EditorContent className={className} editor={editor} />\n})\n\nexport { TypistEditor }\n\nexport type {\n BeforeCreateProps,\n BlurProps,\n CreateProps,\n DestroyProps,\n FocusProps,\n SelectionUpdateProps,\n TransacationProps,\n TypistEditorProps,\n TypistEditorRef,\n UpdateProps,\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { EditorView } from "@tiptap/pm/view";
3
3
 
4
4
  //#region src/extensions/core/view-event-handlers.d.ts
5
5
  /**
6
- * The options available to customize the `ViewEventHandlers` extension.
6
+ * The set of view event handlers that can be provided to the editor.
7
7
  *
8
8
  * If more view handlers are needed, please look into the available event handlers in
9
9
  * [`prosemirror-view`](https://prosemirror.net/docs/ref/#view.Props), and add them below.
@@ -18,17 +18,6 @@ type ViewEventHandlersOptions = {
18
18
  */
19
19
  onKeyDown?: (event: KeyboardEvent, view: EditorView) => boolean | void;
20
20
  };
21
- /**
22
- * The `ViewEventHandlers` extension allows handling of various ProseMirror view events.
23
- *
24
- * The various event-handling functions may all return `true` to indicate that they handled the
25
- * given event. The view will then take care to call `preventDefault` on the event, except with
26
- * `handleDOMEvents`, where the handler itself is responsible for that. Return `false` or
27
- * `undefined` for the default event handler to be called.
28
- *
29
- * These event handlers should be used sparingly, please consider if a reusable extension would be
30
- * more appropriate for your use case.
31
- */
32
21
  //#endregion
33
22
  export type { ViewEventHandlersOptions };
34
23
  //# sourceMappingURL=view-event-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-event-handlers.d.ts","names":[],"sources":["../../../src/extensions/core/view-event-handlers.ts"],"mappings":";;;;;;AAKiD;;;;KAQ5C,wBAAA;EASmB;;;EALpB,OAAA,IAAW,KAAA,EAAO,UAAA,EAAY,IAAA,EAAM,UAAA,EAAY,GAAA;EAAhD;;;EAKA,SAAA,IAAa,KAAA,EAAO,aAAA,EAAe,IAAA,EAAM,UAAA;AAAA;;;;;;;;AAAU"}
1
+ {"version":3,"file":"view-event-handlers.d.ts","names":[],"sources":["../../../src/extensions/core/view-event-handlers.ts"],"mappings":";;;;;;AAKiD;;;;KAQ5C,wBAAA;EASmB;;;EALpB,OAAA,IAAW,KAAA,EAAO,UAAA,EAAY,IAAA,EAAM,UAAA,EAAY,GAAA;EAAhD;;;EAKA,SAAA,IAAa,KAAA,EAAO,aAAA,EAAe,IAAA,EAAM,UAAA;AAAA"}
@@ -16,16 +16,26 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
16
16
  const ViewEventHandlers = Extension.create({
17
17
  name: "viewEventHandlers",
18
18
  priority: 105,
19
+ addStorage() {
20
+ return {
21
+ onClick: this.options.onClick,
22
+ onKeyDown: this.options.onKeyDown,
23
+ setHandlers(handlers) {
24
+ this.onClick = handlers.onClick;
25
+ this.onKeyDown = handlers.onKeyDown;
26
+ }
27
+ };
28
+ },
19
29
  addProseMirrorPlugins() {
20
- const { options } = this;
30
+ const { editor } = this;
21
31
  return [new Plugin({
22
32
  key: new PluginKey("viewEventHandlers"),
23
33
  props: {
24
34
  handleClick(view, pos, event) {
25
- return options.onClick?.(event, view, pos) || false;
35
+ return editor.storage.viewEventHandlers.onClick?.(event, view, pos) || false;
26
36
  },
27
37
  handleKeyDown(view, event) {
28
- return options.onKeyDown?.(event, view) || false;
38
+ return editor.storage.viewEventHandlers.onKeyDown?.(event, view) || false;
29
39
  }
30
40
  }
31
41
  })];
@@ -1 +1 @@
1
- {"version":3,"file":"view-event-handlers.js","names":[],"sources":["../../../src/extensions/core/view-event-handlers.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { VIEW_EVENT_HANDLERS_PRIORITY } from '../../constants/extension-priorities'\n\nimport type { EditorView } from '@tiptap/pm/view'\n\n/**\n * The options available to customize the `ViewEventHandlers` extension.\n *\n * If more view handlers are needed, please look into the available event handlers in\n * [`prosemirror-view`](https://prosemirror.net/docs/ref/#view.Props), and add them below.\n */\ntype ViewEventHandlersOptions = {\n /**\n * Called when the editor is clicked, after `handleClickOn` handlers have been called.\n */\n onClick?: (event: MouseEvent, view: EditorView, pos: number) => boolean | void\n\n /**\n * Called when the editor receives a `keydown` event.\n */\n onKeyDown?: (event: KeyboardEvent, view: EditorView) => boolean | void\n}\n\n/**\n * The `ViewEventHandlers` extension allows handling of various ProseMirror view events.\n *\n * The various event-handling functions may all return `true` to indicate that they handled the\n * given event. The view will then take care to call `preventDefault` on the event, except with\n * `handleDOMEvents`, where the handler itself is responsible for that. Return `false` or\n * `undefined` for the default event handler to be called.\n *\n * These event handlers should be used sparingly, please consider if a reusable extension would be\n * more appropriate for your use case.\n */\nconst ViewEventHandlers = Extension.create<ViewEventHandlersOptions>({\n name: 'viewEventHandlers',\n priority: VIEW_EVENT_HANDLERS_PRIORITY,\n addProseMirrorPlugins() {\n const { options } = this\n\n return [\n new Plugin({\n key: new PluginKey('viewEventHandlers'),\n props: {\n handleClick(view, pos, event) {\n return options.onClick?.(event, view, pos) || false\n },\n handleKeyDown(view, event) {\n return options.onKeyDown?.(event, view) || false\n },\n },\n }),\n ]\n },\n})\n\nexport { ViewEventHandlers }\n\nexport type { ViewEventHandlersOptions }\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,MAAM,oBAAoB,UAAU,OAAiC;CACjE,MAAM;CACN,UAAA;CACA,wBAAwB;EACpB,MAAM,EAAE,YAAY;EAEpB,OAAO,CACH,IAAI,OAAO;GACP,KAAK,IAAI,UAAU,mBAAmB;GACtC,OAAO;IACH,YAAY,MAAM,KAAK,OAAO;KAC1B,OAAO,QAAQ,UAAU,OAAO,MAAM,GAAG,KAAK;IAClD;IACA,cAAc,MAAM,OAAO;KACvB,OAAO,QAAQ,YAAY,OAAO,IAAI,KAAK;IAC/C;GACJ;EACJ,CAAC,CACL;CACJ;AACJ,CAAC"}
1
+ {"version":3,"file":"view-event-handlers.js","names":[],"sources":["../../../src/extensions/core/view-event-handlers.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { VIEW_EVENT_HANDLERS_PRIORITY } from '../../constants/extension-priorities'\n\nimport type { EditorView } from '@tiptap/pm/view'\n\n/**\n * The set of view event handlers that can be provided to the editor.\n *\n * If more view handlers are needed, please look into the available event handlers in\n * [`prosemirror-view`](https://prosemirror.net/docs/ref/#view.Props), and add them below.\n */\ntype ViewEventHandlersOptions = {\n /**\n * Called when the editor is clicked, after `handleClickOn` handlers have been called.\n */\n onClick?: (event: MouseEvent, view: EditorView, pos: number) => boolean | void\n\n /**\n * Called when the editor receives a `keydown` event.\n */\n onKeyDown?: (event: KeyboardEvent, view: EditorView) => boolean | void\n}\n\ntype ViewEventHandlersStorage = ViewEventHandlersOptions & {\n /**\n * Updates the handlers the plugin invokes. The editor component calls this as its handler\n * props change, since the editor and its plugins are created only once.\n */\n setHandlers: (handlers: ViewEventHandlersOptions) => void\n}\n\n/**\n * The `ViewEventHandlers` extension allows handling of various ProseMirror view events.\n *\n * The various event-handling functions may all return `true` to indicate that they handled the\n * given event. The view will then take care to call `preventDefault` on the event, except with\n * `handleDOMEvents`, where the handler itself is responsible for that. Return `false` or\n * `undefined` for the default event handler to be called.\n *\n * These event handlers should be used sparingly, please consider if a reusable extension would be\n * more appropriate for your use case.\n */\nconst ViewEventHandlers = Extension.create<ViewEventHandlersOptions, ViewEventHandlersStorage>({\n name: 'viewEventHandlers',\n priority: VIEW_EVENT_HANDLERS_PRIORITY,\n addStorage() {\n return {\n onClick: this.options.onClick,\n onKeyDown: this.options.onKeyDown,\n setHandlers(handlers) {\n this.onClick = handlers.onClick\n this.onKeyDown = handlers.onKeyDown\n },\n }\n },\n addProseMirrorPlugins() {\n const { editor } = this\n\n return [\n new Plugin({\n key: new PluginKey('viewEventHandlers'),\n props: {\n handleClick(view, pos, event) {\n return editor.storage.viewEventHandlers.onClick?.(event, view, pos) || false\n },\n handleKeyDown(view, event) {\n return editor.storage.viewEventHandlers.onKeyDown?.(event, view) || false\n },\n },\n }),\n ]\n },\n})\n\nexport { ViewEventHandlers }\n\nexport type { ViewEventHandlersOptions }\n"],"mappings":";;;;;;;;;;;;;;;AA4CA,MAAM,oBAAoB,UAAU,OAA2D;CAC3F,MAAM;CACN,UAAA;CACA,aAAa;EACT,OAAO;GACH,SAAS,KAAK,QAAQ;GACtB,WAAW,KAAK,QAAQ;GACxB,YAAY,UAAU;IAClB,KAAK,UAAU,SAAS;IACxB,KAAK,YAAY,SAAS;GAC9B;EACJ;CACJ;CACA,wBAAwB;EACpB,MAAM,EAAE,WAAW;EAEnB,OAAO,CACH,IAAI,OAAO;GACP,KAAK,IAAI,UAAU,mBAAmB;GACtC,OAAO;IACH,YAAY,MAAM,KAAK,OAAO;KAC1B,OAAO,OAAO,QAAQ,kBAAkB,UAAU,OAAO,MAAM,GAAG,KAAK;IAC3E;IACA,cAAc,MAAM,OAAO;KACvB,OAAO,OAAO,QAAQ,kBAAkB,YAAY,OAAO,IAAI,KAAK;IACxE;GACJ;EACJ,CAAC,CACL;CACJ;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@doist/typist",
3
3
  "description": "The mighty Tiptap-based rich-text editor React component that powers Doist products.",
4
- "version": "14.1.2",
4
+ "version": "15.0.0-next.1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://typist.doist.dev/",
7
7
  "repository": {
@@ -121,6 +121,7 @@
121
121
  "@testing-library/dom": "10.4.1",
122
122
  "@testing-library/jest-dom": "6.9.1",
123
123
  "@testing-library/react": "16.3.2",
124
+ "@testing-library/user-event": "14.6.1",
124
125
  "@types/hast": "3.0.4",
125
126
  "@types/lodash-es": "4.17.12",
126
127
  "@types/react": "18.3.31",
@@ -1,59 +0,0 @@
1
- import { useCallback, useEffect, useState } from "react";
2
- import { Editor } from "@tiptap/react";
3
- //#region src/hooks/use-editor.ts
4
- function stateChanger(state) {
5
- return (state + 1) % Number.MAX_SAFE_INTEGER;
6
- }
7
- /**
8
- * This is a copy of the `useRerender` hook from `@react-hookz/web`, which is a utility hook that
9
- * returns a function that can be called to force a re-render of the component.
10
- *
11
- * Turns out we don't have the need to use any of the other hooks from `@react-hookz/web`, which is
12
- * a peer dependency that often introduces breaking changes, causing upgrade issues across our
13
- * projects.
14
- */
15
- function useRerender() {
16
- const [, setState] = useState(0);
17
- return useCallback(() => {
18
- setState(stateChanger);
19
- }, []);
20
- }
21
- /**
22
- * This is a fork of the official `useEditor` hook with one key difference, which is to prevent a
23
- * `null` Editor object instance from being returned on the first render.
24
- *
25
- * This change was once fixed in the `@tiptap/react` package, but was reverted because it didn't
26
- * have support for server-side rendering ([ref](https://github.com/ueberdosis/tiptap/pull/2282)),
27
- * which is a problem we don't currently have.
28
- *
29
- * @param options The options to configure the editor component with.
30
- * @param dependencies If present, re-create the editor instance if the values in the list change.
31
- *
32
- * @returns A new editor instance with the given options.
33
- */
34
- function useEditor(options = {}, dependencies = []) {
35
- const [editor, setEditor] = useState(() => new Editor(options));
36
- const forceRerender = useRerender();
37
- useEffect(function initializeEditorInstance() {
38
- let instance;
39
- if (editor.isDestroyed) {
40
- instance = new Editor(options);
41
- setEditor(instance);
42
- } else instance = editor;
43
- instance.on("transaction", () => {
44
- requestAnimationFrame(() => {
45
- requestAnimationFrame(() => {
46
- if (!instance.isDestroyed) forceRerender();
47
- });
48
- });
49
- });
50
- return function destroyEditorInstance() {
51
- instance.destroy();
52
- };
53
- }, dependencies);
54
- return editor;
55
- }
56
- //#endregion
57
- export { useEditor };
58
-
59
- //# sourceMappingURL=use-editor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-editor.js","names":[],"sources":["../../src/hooks/use-editor.ts"],"sourcesContent":["import { DependencyList, useCallback, useEffect, useState } from 'react'\n\nimport { Editor } from '@tiptap/react'\n\nimport type { EditorOptions } from '@tiptap/core'\n\nfunction stateChanger(state: number) {\n return (state + 1) % Number.MAX_SAFE_INTEGER\n}\n\n/**\n * This is a copy of the `useRerender` hook from `@react-hookz/web`, which is a utility hook that\n * returns a function that can be called to force a re-render of the component.\n *\n * Turns out we don't have the need to use any of the other hooks from `@react-hookz/web`, which is\n * a peer dependency that often introduces breaking changes, causing upgrade issues across our\n * projects.\n */\nfunction useRerender(): () => void {\n const [, setState] = useState(0)\n\n return useCallback(() => {\n setState(stateChanger)\n }, [])\n}\n\n/**\n * This is a fork of the official `useEditor` hook with one key difference, which is to prevent a\n * `null` Editor object instance from being returned on the first render.\n *\n * This change was once fixed in the `@tiptap/react` package, but was reverted because it didn't\n * have support for server-side rendering ([ref](https://github.com/ueberdosis/tiptap/pull/2282)),\n * which is a problem we don't currently have.\n *\n * @param options The options to configure the editor component with.\n * @param dependencies If present, re-create the editor instance if the values in the list change.\n *\n * @returns A new editor instance with the given options.\n */\nfunction useEditor(\n options: Partial<EditorOptions> = {},\n dependencies: DependencyList = [],\n): Editor {\n const [editor, setEditor] = useState<Editor>(() => new Editor(options))\n\n const forceRerender = useRerender()\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(\n function initializeEditorInstance() {\n let instance: Editor\n\n if (editor.isDestroyed) {\n instance = new Editor(options)\n setEditor(instance)\n } else {\n instance = editor\n }\n\n instance.on('transaction', () => {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (!instance.isDestroyed) {\n forceRerender()\n }\n })\n })\n })\n\n return function destroyEditorInstance() {\n instance.destroy()\n }\n },\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n dependencies,\n )\n\n return editor\n}\n\nexport { useEditor }\n"],"mappings":";;;AAMA,SAAS,aAAa,OAAe;CACjC,QAAQ,QAAQ,KAAK,OAAO;AAChC;;;;;;;;;AAUA,SAAS,cAA0B;CAC/B,MAAM,GAAG,YAAY,SAAS,CAAC;CAE/B,OAAO,kBAAkB;EACrB,SAAS,YAAY;CACzB,GAAG,CAAC,CAAC;AACT;;;;;;;;;;;;;;AAeA,SAAS,UACL,UAAkC,CAAC,GACnC,eAA+B,CAAC,GAC1B;CACN,MAAM,CAAC,QAAQ,aAAa,eAAuB,IAAI,OAAO,OAAO,CAAC;CAEtE,MAAM,gBAAgB,YAAY;CAGlC,UACI,SAAS,2BAA2B;EAChC,IAAI;EAEJ,IAAI,OAAO,aAAa;GACpB,WAAW,IAAI,OAAO,OAAO;GAC7B,UAAU,QAAQ;EACtB,OACI,WAAW;EAGf,SAAS,GAAG,qBAAqB;GAC7B,4BAA4B;IACxB,4BAA4B;KACxB,IAAI,CAAC,SAAS,aACV,cAAc;IAEtB,CAAC;GACL,CAAC;EACL,CAAC;EAED,OAAO,SAAS,wBAAwB;GACpC,SAAS,QAAQ;EACrB;CACJ,GAEA,YACJ;CAEA,OAAO;AACX"}