prosemirror-math 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,8 @@ Math editing extensions for [ProseMirror](https://prosemirror.net/). Provides no
8
8
  npm install prosemirror-math
9
9
  ```
10
10
 
11
+ <!-- TODO: Add live demo link -->
12
+
11
13
  ## Usage
12
14
 
13
15
  ### Node specs
@@ -23,8 +25,8 @@ const schema = new Schema({
23
25
  doc: { content: 'block+' },
24
26
  paragraph: { content: 'inline*', group: 'block', parseDOM: [{ tag: 'p' }], toDOM: () => ['p', 0] },
25
27
  text: { group: 'inline' },
26
- mathBlock: { ...mathBlockSpec, },
27
- mathInline: { ...mathInlineSpec, },
28
+ mathBlock: { ...mathBlockSpec },
29
+ mathInline: { ...mathInlineSpec },
28
30
  },
29
31
  })
30
32
  ```
@@ -41,12 +43,12 @@ import Temml from 'temml'
41
43
  const view = new EditorView(document.body, {
42
44
  state,
43
45
  nodeViews: {
44
- mathBlock: (node) => createMathBlockView(node, (text, element) => {
46
+ mathBlock: (node, view, getPos, decorations) => createMathBlockView((text, element) => {
45
47
  Temml.render(text, element, { displayMode: true })
46
- }),
47
- mathInline: (node) => createMathInlineView(node, (text, element) => {
48
+ }, node, decorations),
49
+ mathInline: (node, view, getPos, decorations) => createMathInlineView((text, element) => {
48
50
  Temml.render(text, element, { displayMode: false })
49
- }),
51
+ }, node, decorations),
50
52
  },
51
53
  })
52
54
  ```
@@ -66,7 +68,7 @@ const plugin = inputRules({
66
68
 
67
69
  ### Enter rule
68
70
 
69
- `mathBlockEnterRule` is an [EnterRule](https://github.com/prosekit/prosekit/tree/master/packages/prosemirror-enter-rules) that converts a paragraph containing `$$` into a math block when Enter is pressed.
71
+ `mathBlockEnterRule` is an [EnterRule](https://www.npmjs.com/package/prosemirror-enter-rules) that converts a paragraph containing `$$` into a math block when Enter is pressed.
70
72
 
71
73
  ```ts
72
74
  import { createEnterRulePlugin } from 'prosemirror-enter-rules'
@@ -79,39 +81,17 @@ const plugin = createEnterRulePlugin({
79
81
 
80
82
  ### Cursor inside plugin
81
83
 
82
- `createCursorInsidePlugin` adds a `prosekit-head-inside` CSS class to math nodes when the cursor is inside them, useful for styling the active math node.
84
+ `createCursorInsidePlugin` adds a `prosemirror-math-head-inside` CSS class to math nodes when the cursor is inside them, useful for styling the active math node.
83
85
 
84
86
  ```ts
85
87
  import { createCursorInsidePlugin } from 'prosemirror-math'
86
88
 
87
- const plugin = createCursorInsidePlugin(['mathBlock', 'mathInline'])
89
+ const plugin = createCursorInsidePlugin()
88
90
  ```
89
91
 
90
92
  ## API
91
93
 
92
- ### Node specs
93
-
94
- - **`mathBlockSpec`** — NodeSpec for block math (`div.prosekit-math-block > pre > code`)
95
- - **`mathInlineSpec`** — NodeSpec for inline math (`span.prosekit-math-inline > code`)
96
-
97
- ### Node views
98
-
99
- - **`createMathBlockView(node, renderMath)`** — Creates a block math NodeView
100
- - **`createMathInlineView(node, renderMath)`** — Creates an inline math NodeView
101
-
102
- The `renderMath` callback receives `(text: string, element: HTMLElement)` and should render the math into the element.
103
-
104
- ### Input rules
105
-
106
- - **`createMathInlineInputRule(nodeType)`** — Creates an InputRule for inline math
107
-
108
- ### Enter rules
109
-
110
- - **`mathBlockEnterRule`** — EnterRule for converting `$$` into a math block
111
-
112
- ### Plugins
113
-
114
- - **`createCursorInsidePlugin(nodeTypes)`** — Plugin that decorates math nodes containing the cursor
94
+ [API Reference](https://npmx.dev/package-docs/prosemirror-math)
115
95
 
116
96
  ## License
117
97
 
@@ -1,42 +1,99 @@
1
1
  import { Plugin } from "prosemirror-state";
2
- import { NodeView } from "prosemirror-view";
2
+ import { Decoration, NodeView } from "prosemirror-view";
3
3
  import { EnterRule } from "prosemirror-enter-rules";
4
4
  import { InputRule } from "prosemirror-inputrules";
5
5
  import { Node, NodeSpec } from "prosemirror-model";
6
6
 
7
7
  //#region src/cursor-inside-plugin.d.ts
8
- declare function createCursorInsidePlugin(nodeTypes: string[]): Plugin;
8
+ /**
9
+ * Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
10
+ * nodes when the text selection head is inside them. This is useful for styling
11
+ * math nodes differently while they are being edited.
12
+ *
13
+ * The plugin automatically detects nodes in the `math` group.
14
+ *
15
+ * @public
16
+ */
17
+ declare function createCursorInsidePlugin(): Plugin;
9
18
  //#endregion
10
19
  //#region src/math-block-enter-rule.d.ts
20
+ /**
21
+ * An {@link EnterRule} that converts a textblock node that only contains `$$` into a math
22
+ * block node when Enter is pressed.
23
+ *
24
+ * @public
25
+ */
11
26
  declare const mathBlockEnterRule: EnterRule;
12
27
  //#endregion
13
28
  //#region src/math-block-spec.d.ts
29
+ /**
30
+ * A {@link NodeSpec} for a block-level math node.
31
+ *
32
+ * @public
33
+ */
14
34
  declare const mathBlockSpec: NodeSpec;
15
35
  //#endregion
16
36
  //#region src/math-block-view.d.ts
17
37
  /**
18
38
  * The function to render a math block.
19
39
  *
20
- * @param code - The code of the math block. For example, a TeX expression.
40
+ * @param text - The text of the math block. For example, a TeX expression.
21
41
  * @param element - A `<div>` element to render the math block.
22
42
  */
23
- type RenderMathBlock = (code: string, element: HTMLElement) => void;
24
- declare function createMathBlockView(node: Node, renderMathBlock: RenderMathBlock): NodeView;
43
+ type RenderMathBlock = (text: string, element: HTMLElement) => void;
44
+ /**
45
+ * Creates a {@link NodeView} for a block-level math node. The view will show a
46
+ * source editor or a rendered display area based on the text cursor position.
47
+ *
48
+ * @param renderMathBlock - A function that renders math text (e.g. TeX) into
49
+ * the display element. You can use libraries like
50
+ * [Temml](https://temml.org/) or [KaTeX](https://katex.org/).
51
+ * @param node - The ProseMirror node to render.
52
+ * @param decorations - The decorations applied to the node.
53
+ *
54
+ * @public
55
+ */
56
+ declare function createMathBlockView(renderMathBlock: RenderMathBlock, node: Node, decorations: readonly Decoration[]): NodeView;
25
57
  //#endregion
26
58
  //#region src/math-inline-input-rule.d.ts
59
+ /**
60
+ * Creates a ProseMirror {@link InputRule} that converts text wrapped in `$` or
61
+ * `$$` (e.g. `$x^2$`) into an inline math node.
62
+ *
63
+ * @param nodeType - The name of the inline math node type in your schema.
64
+ *
65
+ * @public
66
+ */
27
67
  declare function createMathInlineInputRule(nodeType: string): InputRule;
28
68
  //#endregion
29
69
  //#region src/math-inline-spec.d.ts
70
+ /**
71
+ * A {@link NodeSpec} for an inline math node.
72
+ *
73
+ * @public
74
+ */
30
75
  declare const mathInlineSpec: NodeSpec;
31
76
  //#endregion
32
77
  //#region src/math-inline-view.d.ts
33
78
  /**
34
79
  * The function to render a math inline.
35
80
  *
36
- * @param code - The code of the math inline. For example, a TeX expression.
81
+ * @param text - The text of the math inline. For example, a TeX expression.
37
82
  * @param element - A `<span>` element to render the math inline.
38
83
  */
39
- type RenderMathInline = (code: string, element: HTMLElement) => void;
40
- declare function createMathInlineView(node: Node, renderMathInline: RenderMathInline): NodeView;
84
+ type RenderMathInline = (text: string, element: HTMLElement) => void;
85
+ /**
86
+ * Creates a {@link NodeView} for an inline math node. The view will show a
87
+ * source editor or a rendered display area based on the text cursor position.
88
+ *
89
+ * @param renderMathInline - A function that renders math text (e.g. TeX) into
90
+ * the display element. You can use libraries like [Temml](https://temml.org/)
91
+ * or [KaTeX](https://katex.org/).
92
+ * @param node - The ProseMirror node to render.
93
+ * @param decorations - The decorations applied to the node.
94
+ *
95
+ * @public
96
+ */
97
+ declare function createMathInlineView(renderMathInline: RenderMathInline, node: Node, decorations: readonly Decoration[]): NodeView;
41
98
  //#endregion
42
99
  export { type RenderMathBlock, type RenderMathInline, createCursorInsidePlugin, createMathBlockView, createMathInlineInputRule, createMathInlineView, mathBlockEnterRule, mathBlockSpec, mathInlineSpec };
@@ -5,23 +5,39 @@ import { supportsRegexLookbehind } from "@ocavue/utils";
5
5
  import { InputRule } from "prosemirror-inputrules";
6
6
 
7
7
  //#region src/cursor-inside-plugin.ts
8
- function createCursorInsideDecoration(state, nodeTypes) {
8
+ const DECORATION_SPEC = "MATH_CURSOR_INSIDE";
9
+ function createCursorInsideDecoration(state) {
9
10
  const { $head } = state.selection;
10
11
  const node = $head.parent;
11
- if (!nodeTypes.includes(node.type.name)) return;
12
+ if (!node.type.isInGroup("math")) return;
12
13
  const before = $head.before();
13
- const deco = Decoration.node(before, before + node.nodeSize, { class: "prosekit-head-inside" });
14
+ const deco = Decoration.node(before, before + node.nodeSize, { class: "prosemirror-math-head-inside" }, DECORATION_SPEC);
14
15
  return DecorationSet.create(state.doc, [deco]);
15
16
  }
16
- const key = new PluginKey("prosemirror-math-cursor-inside");
17
- function createCursorInsidePlugin(nodeTypes) {
17
+ /**
18
+ * @internal
19
+ */
20
+ function hasCursorInsideDecoration(decorations) {
21
+ return decorations.some((deco) => deco.spec === DECORATION_SPEC);
22
+ }
23
+ /**
24
+ * Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
25
+ * nodes when the text selection head is inside them. This is useful for styling
26
+ * math nodes differently while they are being edited.
27
+ *
28
+ * The plugin automatically detects nodes in the `math` group.
29
+ *
30
+ * @public
31
+ */
32
+ function createCursorInsidePlugin() {
33
+ const key = new PluginKey("prosemirror-math-cursor-inside");
18
34
  return new Plugin({
19
35
  key,
20
36
  state: {
21
37
  init() {},
22
38
  apply(tr, oldValue, oldState, newState) {
23
- if (oldState.selection.eq(newState.selection) && tr.docChanged === false) return oldValue;
24
- return createCursorInsideDecoration(newState, nodeTypes);
39
+ if (oldState.selection.head === newState.selection.head && !tr.docChanged) return oldValue;
40
+ return createCursorInsideDecoration(newState);
25
41
  }
26
42
  },
27
43
  props: { decorations(state) {
@@ -33,6 +49,12 @@ function createCursorInsidePlugin(nodeTypes) {
33
49
  //#endregion
34
50
  //#region src/math-block-enter-rule.ts
35
51
  const MATH_BLOCK_ENTER_REGEXP = /^\$\$$/;
52
+ /**
53
+ * An {@link EnterRule} that converts a textblock node that only contains `$$` into a math
54
+ * block node when Enter is pressed.
55
+ *
56
+ * @public
57
+ */
36
58
  const mathBlockEnterRule = /* @__PURE__ */ createTextBlockEnterRule({
37
59
  regex: MATH_BLOCK_ENTER_REGEXP,
38
60
  type: "mathBlock"
@@ -40,19 +62,27 @@ const mathBlockEnterRule = /* @__PURE__ */ createTextBlockEnterRule({
40
62
 
41
63
  //#endregion
42
64
  //#region src/math-block-spec.ts
65
+ /**
66
+ * A {@link NodeSpec} for a block-level math node.
67
+ *
68
+ * @public
69
+ */
43
70
  const mathBlockSpec = {
44
71
  atom: false,
45
- group: "block",
72
+ group: "block math",
46
73
  content: "text*",
47
74
  code: true,
48
75
  toDOM() {
49
76
  return [
50
77
  "div",
51
- { class: "prosekit-math-block" },
78
+ { class: "prosemirror-math-block" },
52
79
  ["pre", ["code", 0]]
53
80
  ];
54
81
  },
55
- parseDOM: [{ tag: "div.prosekit-math-block" }]
82
+ parseDOM: [{
83
+ tag: "div.prosemirror-math-block",
84
+ contentElement: "code"
85
+ }]
56
86
  };
57
87
 
58
88
  //#endregion
@@ -64,27 +94,76 @@ function createElement(tag, className, ...children) {
64
94
  return element;
65
95
  }
66
96
 
97
+ //#endregion
98
+ //#region src/math-view-render.ts
99
+ function createMathViewRender(renderMath, source, display, inline) {
100
+ let prevNode;
101
+ let prevText;
102
+ let prevSelected;
103
+ function updateDisplay(node) {
104
+ if (node === prevNode) return;
105
+ prevNode = node;
106
+ const text = node.textContent;
107
+ if (text === prevText) return;
108
+ prevText = text;
109
+ renderMath(text, display);
110
+ }
111
+ function updateStyle(decorations) {
112
+ const selected = hasCursorInsideDecoration(decorations);
113
+ if (selected === prevSelected) return;
114
+ prevSelected = selected;
115
+ display.style.display = selected ? "none" : "";
116
+ if (!inline) source.style.display = selected ? "" : "none";
117
+ else Object.assign(source.style, selected ? visibleInlineSourceStyle : hiddenInlineSourceStyle);
118
+ }
119
+ return function updateMathView(node, decorations) {
120
+ updateDisplay(node);
121
+ updateStyle(decorations);
122
+ };
123
+ }
124
+ const hiddenInlineSourceStyle = {
125
+ display: "inline-flex",
126
+ opacity: "0",
127
+ pointerEvents: "none",
128
+ maxWidth: "0",
129
+ maxHeight: "0",
130
+ overflow: "hidden"
131
+ };
132
+ const visibleInlineSourceStyle = {
133
+ display: "inline-flex",
134
+ opacity: "1",
135
+ pointerEvents: "",
136
+ maxWidth: "",
137
+ maxHeight: "",
138
+ overflow: ""
139
+ };
140
+
67
141
  //#endregion
68
142
  //#region src/math-block-view.ts
69
- function createMathBlockView(node, renderMathBlock) {
143
+ /**
144
+ * Creates a {@link NodeView} for a block-level math node. The view will show a
145
+ * source editor or a rendered display area based on the text cursor position.
146
+ *
147
+ * @param renderMathBlock - A function that renders math text (e.g. TeX) into
148
+ * the display element. You can use libraries like
149
+ * [Temml](https://temml.org/) or [KaTeX](https://katex.org/).
150
+ * @param node - The ProseMirror node to render.
151
+ * @param decorations - The decorations applied to the node.
152
+ *
153
+ * @public
154
+ */
155
+ function createMathBlockView(renderMathBlock, node, decorations) {
70
156
  const code = createElement("code");
71
- const source = createElement("pre", "prosekit-math-source", code);
72
- const display = createElement("div", "prosekit-math-display", source);
73
- const dom = createElement("div", "prosekit-math-block", source, display);
74
- let prevText = "";
75
- const render = (node) => {
76
- const nodeText = node.textContent;
77
- if (prevText !== nodeText) {
78
- prevText = nodeText;
79
- renderMathBlock(nodeText, display);
80
- }
81
- };
82
- render(node);
157
+ const source = createElement("pre", "prosemirror-math-source", code);
158
+ const display = createElement("div", "prosemirror-math-display");
159
+ const dom = createElement("div", "prosemirror-math-block", source, display);
160
+ const render = createMathViewRender(renderMathBlock, source, display, false);
161
+ render(node, decorations);
83
162
  return {
84
163
  dom,
85
164
  contentDOM: code,
86
- update: (node) => {
87
- render(node);
165
+ update: (node, decorations) => {
166
+ render(node, decorations);
88
167
  return true;
89
168
  }
90
169
  };
@@ -93,6 +172,14 @@ function createMathBlockView(node, renderMathBlock) {
93
172
  //#endregion
94
173
  //#region src/math-inline-input-rule.ts
95
174
  const MATH_INPUT_REGEXP = (supportsRegexLookbehind() ? "(?<!\\$)" : "") + "(\\$\\$?)([^\\s$](?:[^$]*[^\\s$])?)\\1$";
175
+ /**
176
+ * Creates a ProseMirror {@link InputRule} that converts text wrapped in `$` or
177
+ * `$$` (e.g. `$x^2$`) into an inline math node.
178
+ *
179
+ * @param nodeType - The name of the inline math node type in your schema.
180
+ *
181
+ * @public
182
+ */
96
183
  function createMathInlineInputRule(nodeType) {
97
184
  return new InputRule(new RegExp(MATH_INPUT_REGEXP), (state, match, start, end) => {
98
185
  const { tr, schema } = state;
@@ -108,43 +195,57 @@ function createMathInlineInputRule(nodeType) {
108
195
 
109
196
  //#endregion
110
197
  //#region src/math-inline-spec.ts
198
+ /**
199
+ * A {@link NodeSpec} for an inline math node.
200
+ *
201
+ * @public
202
+ */
111
203
  const mathInlineSpec = {
112
204
  atom: false,
113
205
  inline: true,
114
- group: "inline",
206
+ group: "inline math",
115
207
  content: "text*",
116
208
  selectable: false,
117
209
  code: true,
118
210
  toDOM() {
119
211
  return [
120
212
  "span",
121
- { class: "prosekit-math-inline" },
213
+ { class: "prosemirror-math-inline" },
122
214
  ["code", 0]
123
215
  ];
124
216
  },
125
- parseDOM: [{ tag: "span.prosekit-math-inline" }]
217
+ parseDOM: [{
218
+ tag: "span.prosemirror-math-inline",
219
+ contentElement: "code"
220
+ }]
126
221
  };
127
222
 
128
223
  //#endregion
129
224
  //#region src/math-inline-view.ts
130
- function createMathInlineView(node, renderMathInline) {
131
- const source = createElement("code", "prosekit-math-source");
132
- const display = createElement("span", "prosekit-math-display", source);
133
- const dom = createElement("span", "prosekit-math-inline", source, display);
134
- let prevText = "";
135
- const render = (node) => {
136
- const nodeText = node.textContent;
137
- if (prevText !== nodeText) {
138
- prevText = nodeText;
139
- renderMathInline(nodeText, display);
140
- }
141
- };
142
- render(node);
225
+ /**
226
+ * Creates a {@link NodeView} for an inline math node. The view will show a
227
+ * source editor or a rendered display area based on the text cursor position.
228
+ *
229
+ * @param renderMathInline - A function that renders math text (e.g. TeX) into
230
+ * the display element. You can use libraries like [Temml](https://temml.org/)
231
+ * or [KaTeX](https://katex.org/).
232
+ * @param node - The ProseMirror node to render.
233
+ * @param decorations - The decorations applied to the node.
234
+ *
235
+ * @public
236
+ */
237
+ function createMathInlineView(renderMathInline, node, decorations) {
238
+ const code = createElement("code");
239
+ const source = createElement("span", "prosemirror-math-source", code);
240
+ const display = createElement("span", "prosemirror-math-display");
241
+ const dom = createElement("span", "prosemirror-math-inline", source, display);
242
+ const render = createMathViewRender(renderMathInline, source, display, true);
243
+ render(node, decorations);
143
244
  return {
144
245
  dom,
145
- contentDOM: source,
146
- update: (node) => {
147
- render(node);
246
+ contentDOM: code,
247
+ update: (node, decorations) => {
248
+ render(node, decorations);
148
249
  return true;
149
250
  }
150
251
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prosemirror-math",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.2.0",
5
5
  "private": false,
6
6
  "description": "Math extensions for ProseMirror",
7
7
  "author": {
@@ -37,22 +37,24 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@ocavue/utils": "^1.5.0",
40
- "prosemirror-inputrules": "^1.4.0",
40
+ "prosemirror-inputrules": "^1.5.1",
41
41
  "prosemirror-model": "^1.25.4",
42
42
  "prosemirror-state": "^1.4.4",
43
43
  "prosemirror-view": "^1.41.6",
44
- "prosemirror-enter-rules": "^0.1.2"
44
+ "prosemirror-enter-rules": "^0.1.3"
45
45
  },
46
46
  "devDependencies": {
47
+ "@mathjax/src": "^4.1.0",
48
+ "@ocavue/tsconfig": "^0.6.3",
47
49
  "diffable-html-snapshot": "^0.2.0",
50
+ "katex": "^0.16.28",
48
51
  "temml": "^0.13.1",
49
52
  "tsdown": "^0.20.3",
50
53
  "typescript": "~5.9.3",
51
54
  "vitest": "^4.0.18",
52
55
  "vitest-browser-commands": "^0.2.0",
53
56
  "@prosekit/config-vitest": "0.0.0",
54
- "@prosekit/core": "^0.10.0",
55
- "@prosekit/pm": "^0.1.15"
57
+ "@prosekit/core": "^0.10.0"
56
58
  },
57
59
  "publishConfig": {
58
60
  "dev": {}
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
- import { createElement } from './create-element'
3
+ import { createElement } from './create-element.ts'
4
4
 
5
5
  describe('createElement', () => {
6
6
  it('creates an element with the given tag', () => {
@@ -1,7 +1,7 @@
1
- import { TextSelection } from '@prosekit/pm/state'
1
+ import { TextSelection } from 'prosemirror-state'
2
2
  import { describe, expect, it } from 'vitest'
3
3
 
4
- import { setupTest } from './testing'
4
+ import { setupTest } from './testing.ts'
5
5
 
6
6
  describe('cursorInsidePlugin', () => {
7
7
  it('applies decoration when cursor is inside mathBlock', () => {
@@ -15,8 +15,8 @@ describe('cursorInsidePlugin', () => {
15
15
  )
16
16
  editor.view.dispatch(tr)
17
17
 
18
- const mathBlock = editor.view.dom.querySelector('.prosekit-math-block')
19
- expect(mathBlock?.classList.contains('prosekit-head-inside')).toBe(true)
18
+ const mathBlock = editor.view.dom.querySelector('.prosemirror-math-block')
19
+ expect(mathBlock?.classList.contains('prosemirror-math-head-inside')).toBe(true)
20
20
  })
21
21
 
22
22
  it('applies decoration when cursor is inside mathInline', () => {
@@ -30,8 +30,8 @@ describe('cursorInsidePlugin', () => {
30
30
  )
31
31
  editor.view.dispatch(tr)
32
32
 
33
- const mathInline = editor.view.dom.querySelector('.prosekit-math-inline')
34
- expect(mathInline?.classList.contains('prosekit-head-inside')).toBe(true)
33
+ const mathInline = editor.view.dom.querySelector('.prosemirror-math-inline')
34
+ expect(mathInline?.classList.contains('prosemirror-math-head-inside')).toBe(true)
35
35
  })
36
36
 
37
37
  it('does not apply decoration when cursor is outside math nodes', () => {
@@ -45,7 +45,7 @@ describe('cursorInsidePlugin', () => {
45
45
  )
46
46
  editor.view.dispatch(tr)
47
47
 
48
- const mathBlock = editor.view.dom.querySelector('.prosekit-math-block')
49
- expect(mathBlock?.classList.contains('prosekit-head-inside')).toBe(false)
48
+ const mathBlock = editor.view.dom.querySelector('.prosemirror-math-block')
49
+ expect(mathBlock?.classList.contains('prosemirror-math-head-inside')).toBe(false)
50
50
  })
51
51
  })
@@ -1,24 +1,46 @@
1
1
  import { Plugin, PluginKey, type EditorState } from 'prosemirror-state'
2
2
  import { Decoration, DecorationSet } from 'prosemirror-view'
3
3
 
4
+ const DECORATION_SPEC = 'MATH_CURSOR_INSIDE'
5
+
4
6
  function createCursorInsideDecoration(
5
7
  state: EditorState,
6
- nodeTypes: string[],
7
8
  ): DecorationSet | undefined {
8
9
  const { $head } = state.selection
9
10
  const node = $head.parent
10
11
 
11
- if (!nodeTypes.includes(node.type.name)) return
12
+ if (!node.type.isInGroup('math')) return
13
+
12
14
  const before = $head.before()
13
- const deco = Decoration.node(before, before + node.nodeSize, { class: 'prosekit-head-inside' })
15
+ const deco = Decoration.node(
16
+ before,
17
+ before + node.nodeSize,
18
+ { class: 'prosemirror-math-head-inside' },
19
+ DECORATION_SPEC,
20
+ )
14
21
  return DecorationSet.create(state.doc, [deco])
15
22
  }
16
23
 
17
- type PluginState = DecorationSet | undefined
24
+ /**
25
+ * @internal
26
+ */
27
+ export function hasCursorInsideDecoration(decorations: readonly Decoration[]): boolean {
28
+ return decorations.some(deco => deco.spec === DECORATION_SPEC)
29
+ }
18
30
 
19
- const key = new PluginKey<PluginState>('prosemirror-math-cursor-inside')
31
+ type PluginState = DecorationSet | undefined
20
32
 
21
- export function createCursorInsidePlugin(nodeTypes: string[]): Plugin {
33
+ /**
34
+ * Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
35
+ * nodes when the text selection head is inside them. This is useful for styling
36
+ * math nodes differently while they are being edited.
37
+ *
38
+ * The plugin automatically detects nodes in the `math` group.
39
+ *
40
+ * @public
41
+ */
42
+ export function createCursorInsidePlugin(): Plugin {
43
+ const key = new PluginKey<PluginState>('prosemirror-math-cursor-inside')
22
44
  return new Plugin<PluginState>({
23
45
  key,
24
46
  state: {
@@ -26,8 +48,13 @@ export function createCursorInsidePlugin(nodeTypes: string[]): Plugin {
26
48
  return undefined
27
49
  },
28
50
  apply(tr, oldValue, oldState, newState): PluginState {
29
- if (oldState.selection.eq(newState.selection) && tr.docChanged === false) return oldValue
30
- return createCursorInsideDecoration(newState, nodeTypes)
51
+ if (
52
+ oldState.selection.head === newState.selection.head
53
+ && !tr.docChanged
54
+ ) {
55
+ return oldValue
56
+ }
57
+ return createCursorInsideDecoration(newState)
31
58
  },
32
59
  },
33
60
  props: {
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { createCursorInsidePlugin } from './cursor-inside-plugin'
2
- export { mathBlockEnterRule } from './math-block-enter-rule'
3
- export { mathBlockSpec } from './math-block-spec'
4
- export { createMathBlockView, type RenderMathBlock } from './math-block-view'
5
- export { createMathInlineInputRule } from './math-inline-input-rule'
6
- export { mathInlineSpec } from './math-inline-spec'
7
- export { createMathInlineView, type RenderMathInline } from './math-inline-view'
1
+ export { createCursorInsidePlugin } from './cursor-inside-plugin.ts'
2
+ export { mathBlockEnterRule } from './math-block-enter-rule.ts'
3
+ export { mathBlockSpec } from './math-block-spec.ts'
4
+ export { createMathBlockView, type RenderMathBlock } from './math-block-view.ts'
5
+ export { createMathInlineInputRule } from './math-inline-input-rule.ts'
6
+ export { mathInlineSpec } from './math-inline-spec.ts'
7
+ export { createMathInlineView, type RenderMathInline } from './math-inline-view.ts'
package/src/katex.ts ADDED
@@ -0,0 +1,9 @@
1
+ import katex from 'katex'
2
+
3
+ export function renderKaTeXMathBlock(text: string, element: HTMLElement) {
4
+ katex.render(text, element, { displayMode: true, throwOnError: false })
5
+ }
6
+
7
+ export function renderKaTeXMathInline(text: string, element: HTMLElement) {
8
+ katex.render(text, element, { displayMode: false, throwOnError: false })
9
+ }
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { userEvent } from 'vitest/browser'
3
3
 
4
- import { MATH_BLOCK_ENTER_REGEXP } from './math-block-enter-rule'
5
- import { setupTest } from './testing'
4
+ import { MATH_BLOCK_ENTER_REGEXP } from './math-block-enter-rule.ts'
5
+ import { setupTest } from './testing.ts'
6
6
 
7
7
  describe('MATH_BLOCK_ENTER_REGEXP', () => {
8
8
  const cases: Array<[input: string, matched: boolean]> = [