@livepreso/react-plugin-textfield 0.2.1 → 0.3.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/CHANGELOG.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@livepreso/react-plugin-textfield",
3
3
  "entries": [
4
+ {
5
+ "version": "0.3.0",
6
+ "tag": "@livepreso/react-plugin-textfield_v0.3.0",
7
+ "date": "Tue, 25 Nov 2025 06:55:05 GMT",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "comment": "When Token content renders an empty string, remove the node insted of replacing (text nodes cannot be empty)"
12
+ }
13
+ ],
14
+ "minor": [
15
+ {
16
+ "comment": "Support react 19 as well"
17
+ }
18
+ ],
19
+ "dependency": [
20
+ {
21
+ "comment": "Updating dependency \"@livepreso/content-react\" to `2.1.0`"
22
+ }
23
+ ]
24
+ }
25
+ },
26
+ {
27
+ "version": "0.2.2",
28
+ "tag": "@livepreso/react-plugin-textfield_v0.2.2",
29
+ "date": "Tue, 25 Nov 2025 00:07:07 GMT",
30
+ "comments": {
31
+ "patch": [
32
+ {
33
+ "comment": "Change reserveWhitespace to \"true\" to keep token's trailing whitespace, but trim whitespace between paragraphs in Strategy Recs. Add contenteditable: false to Token when not burst. Add leading space to @ when using the token button (when required)."
34
+ }
35
+ ]
36
+ }
37
+ },
4
38
  {
5
39
  "version": "0.2.1",
6
40
  "tag": "@livepreso/react-plugin-textfield_v0.2.1",
package/CHANGELOG.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # Change Log - @livepreso/react-plugin-textfield
2
2
 
3
- This log was last generated on Sun, 23 Nov 2025 23:46:44 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 25 Nov 2025 06:55:05 GMT and should not be manually modified.
4
+
5
+ ## 0.3.0
6
+ Tue, 25 Nov 2025 06:55:05 GMT
7
+
8
+ ### Minor changes
9
+
10
+ - Support react 19 as well
11
+
12
+ ### Patches
13
+
14
+ - When Token content renders an empty string, remove the node insted of replacing (text nodes cannot be empty)
15
+
16
+ ## 0.2.2
17
+ Tue, 25 Nov 2025 00:07:07 GMT
18
+
19
+ ### Patches
20
+
21
+ - Change reserveWhitespace to "true" to keep token's trailing whitespace, but trim whitespace between paragraphs in Strategy Recs. Add contenteditable: false to Token when not burst. Add leading space to @ when using the token button (when required).
4
22
 
5
23
  ## 0.2.1
6
24
  Sun, 23 Nov 2025 23:46:44 GMT
@@ -37,9 +37,13 @@ export const convertTokensToTextNodes = (editor, options) => {
37
37
  : tokenRender();
38
38
 
39
39
  const endPos = pos + editor.state.doc.nodeAt(pos).nodeSize;
40
- const textNode = editor.state.schema.text(content);
41
40
 
42
- tr.replaceWith(pos, endPos, textNode);
41
+ if (content) {
42
+ const textNode = editor.state.schema.text(content);
43
+ tr.replaceWith(pos, endPos, textNode);
44
+ } else {
45
+ tr.delete(pos, endPos);
46
+ }
43
47
  });
44
48
 
45
49
  editor.view.dispatch(tr);
@@ -98,10 +102,11 @@ const TokenBase = Mention.extend({
98
102
  const content =
99
103
  options.fieldIsEditable && options.usePlaceholder
100
104
  ? tokenSummary
101
- : render();
105
+ : render() || "";
102
106
 
103
107
  const editableNode = document.createElement("span");
104
108
  editableNode.setAttribute("contenteditable", options.fieldIsEditable);
109
+ tokenDOM.setAttribute("contenteditable", false);
105
110
 
106
111
  const dom = options.burstOnCreate ? editableNode : tokenDOM;
107
112
  const contentDOM = dom.appendChild(document.createTextNode(content));
@@ -109,17 +114,25 @@ const TokenBase = Mention.extend({
109
114
  const convertToTextNode = () => {
110
115
  // To reference this specific node in a command, get its position.
111
116
  const pos = getPos();
112
- const endPos = pos + node.nodeSize;
113
-
114
- // convert the node to a text node and insert the content
115
- // replacing the token in the process
116
- editor
117
- .chain()
118
- .focus()
119
- .setNodeSelection(pos)
120
- .insertContentAt({ from: pos, to: endPos }, content)
121
- .toggleNode(NODE_TYPES.TOKEN, "text")
122
- .run();
117
+
118
+ if (pos && editor.state.doc.nodeAt(pos)) {
119
+ const endPos = pos + node.nodeSize;
120
+
121
+ if (content) {
122
+ // convert the node to a text node and insert the content
123
+ // replacing the token in the process
124
+ editor
125
+ .chain()
126
+ .focus()
127
+ .setNodeSelection(pos)
128
+ .insertContentAt({ from: pos, to: endPos }, content)
129
+ .toggleNode(NODE_TYPES.TOKEN, "text")
130
+ .run();
131
+ } else {
132
+ // delete node if no renderable content
133
+ editor.chain().focus().setNodeSelection(pos).deleteNode().run();
134
+ }
135
+ }
123
136
  };
124
137
 
125
138
  // Attach the double-click event listener in all modes where content is available.
@@ -133,10 +146,7 @@ const TokenBase = Mention.extend({
133
146
  if (options.burstOnCreate) {
134
147
  // this will be run, once, after the node is created
135
148
  // after which, the Token will no longer exist.
136
- // Only run if currently editing and adding tokens (ie. not on editor load)
137
- if (editor.isFocused) {
138
- setTimeout(() => convertToTextNode(), 50);
139
- }
149
+ setTimeout(() => convertToTextNode(), 50);
140
150
  }
141
151
  return {
142
152
  dom,
@@ -293,8 +293,27 @@ const insertTokenButton = ({ tokens = [] }) =>
293
293
  icon: <icons.WandStars />,
294
294
  // Token extension is always enabled, so nothing to load here
295
295
  extensions: [],
296
- command: (editor) =>
297
- editor.chain().focus().insertContent(TOKEN_TRIGGER_CHAR).run(),
296
+ command: (editor) => {
297
+ const { $from } = editor.view.state.selection;
298
+ const prevPos = $from.pos - 1;
299
+
300
+ let tokenTriggerStr = TOKEN_TRIGGER_CHAR;
301
+
302
+ // Add a space before the token trigger if necessary.
303
+ // Without a leading space the token functionality won't run.
304
+ if (prevPos && editor.view.state.doc.nodeAt(prevPos)) {
305
+ const charBefore = editor.view.state.doc.textBetween(
306
+ prevPos,
307
+ $from.pos,
308
+ );
309
+
310
+ if (charBefore !== " ") {
311
+ tokenTriggerStr = ` ${TOKEN_TRIGGER_CHAR}`;
312
+ }
313
+ }
314
+
315
+ return editor.chain().focus().insertContent(tokenTriggerStr).run();
316
+ },
298
317
  }
299
318
  : null;
300
319
 
package/index.js CHANGED
@@ -157,7 +157,7 @@ function EditableTextField({
157
157
  onUpdate: debounce(({ editor }) => {
158
158
  onChange?.(editor.getHTML());
159
159
  }, 100),
160
- parseOptions: { preserveWhitespace: "full" },
160
+ parseOptions: { preserveWhitespace: true },
161
161
  });
162
162
 
163
163
  const { refs } = useFloating({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepreso/react-plugin-textfield",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -12,8 +12,8 @@
12
12
  "devDependencies": {
13
13
  "eslint": "~9.15.0",
14
14
  "@babel/preset-react": "~7.25.9",
15
- "react": "18.x",
16
- "react-dom": "18.x",
15
+ "react": "~19.2.0",
16
+ "react-dom": "~19.2.0",
17
17
  "@babel/parser": "^7.26.10",
18
18
  "@babel/traverse": "^7.26.10",
19
19
  "@svgr/core": "^8.1.0",
@@ -21,8 +21,8 @@
21
21
  "@livepreso/eslint-config": "1.0.0"
22
22
  },
23
23
  "peerDependencies": {
24
- "react": "18.x",
25
- "react-dom": "18.x"
24
+ "react": ">= 18.0.0 <= 19",
25
+ "react-dom": ">= 19.0.0 <= 19"
26
26
  },
27
27
  "dependencies": {
28
28
  "@floating-ui/react-dom": "~2.1.6",
@@ -58,7 +58,7 @@
58
58
  "@tiptap/extension-mention": "~3.10.1",
59
59
  "@tiptap/suggestion": "~3.10.1",
60
60
  "fuse.js": "~7.1.0",
61
- "@livepreso/content-react": "2.0.2"
61
+ "@livepreso/content-react": "2.1.0"
62
62
  },
63
63
  "scripts": {
64
64
  "build": ""