@lexical/text 0.13.1 → 0.14.2
Sign up to get free protection for your applications and to get access to all the features.
- package/LexicalText.dev.esm.js +288 -0
- package/LexicalText.esm.js +16 -0
- package/LexicalText.js +1 -1
- package/LexicalText.prod.esm.js +7 -0
- package/README.md +2 -0
- package/package.json +5 -3
@@ -0,0 +1,288 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
3
|
+
*
|
4
|
+
* This source code is licensed under the MIT license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
import { $isElementNode, $isTextNode, $getRoot, $isDecoratorNode, $isParagraphNode, TextNode, $createTextNode } from 'lexical';
|
8
|
+
|
9
|
+
/** @module @lexical/text */
|
10
|
+
/**
|
11
|
+
* Finds a TextNode with a size larger than targetCharacters and returns
|
12
|
+
* the node along with the remaining length of the text.
|
13
|
+
* @param root - The RootNode.
|
14
|
+
* @param targetCharacters - The number of characters whose TextNode must be larger than.
|
15
|
+
* @returns The TextNode and the intersections offset, or null if no TextNode is found.
|
16
|
+
*/
|
17
|
+
function $findTextIntersectionFromCharacters(root, targetCharacters) {
|
18
|
+
let node = root.getFirstChild();
|
19
|
+
let currentCharacters = 0;
|
20
|
+
mainLoop: while (node !== null) {
|
21
|
+
if ($isElementNode(node)) {
|
22
|
+
const child = node.getFirstChild();
|
23
|
+
if (child !== null) {
|
24
|
+
node = child;
|
25
|
+
continue;
|
26
|
+
}
|
27
|
+
} else if ($isTextNode(node)) {
|
28
|
+
const characters = node.getTextContentSize();
|
29
|
+
if (currentCharacters + characters > targetCharacters) {
|
30
|
+
return {
|
31
|
+
node,
|
32
|
+
offset: targetCharacters - currentCharacters
|
33
|
+
};
|
34
|
+
}
|
35
|
+
currentCharacters += characters;
|
36
|
+
}
|
37
|
+
const sibling = node.getNextSibling();
|
38
|
+
if (sibling !== null) {
|
39
|
+
node = sibling;
|
40
|
+
continue;
|
41
|
+
}
|
42
|
+
let parent = node.getParent();
|
43
|
+
while (parent !== null) {
|
44
|
+
const parentSibling = parent.getNextSibling();
|
45
|
+
if (parentSibling !== null) {
|
46
|
+
node = parentSibling;
|
47
|
+
continue mainLoop;
|
48
|
+
}
|
49
|
+
parent = parent.getParent();
|
50
|
+
}
|
51
|
+
break;
|
52
|
+
}
|
53
|
+
return null;
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Determines if the root has any text content and can trim any whitespace if it does.
|
58
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
59
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
60
|
+
* @returns true if text content is empty, false if there is text or isEditorComposing is true.
|
61
|
+
*/
|
62
|
+
function $isRootTextContentEmpty(isEditorComposing, trim = true) {
|
63
|
+
if (isEditorComposing) {
|
64
|
+
return false;
|
65
|
+
}
|
66
|
+
let text = $rootTextContent();
|
67
|
+
if (trim) {
|
68
|
+
text = text.trim();
|
69
|
+
}
|
70
|
+
return text === '';
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* Returns a function that executes {@link $isRootTextContentEmpty}
|
75
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
76
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
77
|
+
* @returns A function that executes $isRootTextContentEmpty based on arguments.
|
78
|
+
*/
|
79
|
+
function $isRootTextContentEmptyCurry(isEditorComposing, trim) {
|
80
|
+
return () => $isRootTextContentEmpty(isEditorComposing, trim);
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Returns the root's text content.
|
85
|
+
* @returns The root's text content.
|
86
|
+
*/
|
87
|
+
function $rootTextContent() {
|
88
|
+
const root = $getRoot();
|
89
|
+
return root.getTextContent();
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Determines if the input should show the placeholder. If anything is in
|
94
|
+
* in the root the placeholder should not be shown.
|
95
|
+
* @param isComposing - Is the editor in composition mode due to an active Input Method Editor?
|
96
|
+
* @returns true if the input should show the placeholder, false otherwise.
|
97
|
+
*/
|
98
|
+
function $canShowPlaceholder(isComposing) {
|
99
|
+
if (!$isRootTextContentEmpty(isComposing, false)) {
|
100
|
+
return false;
|
101
|
+
}
|
102
|
+
const root = $getRoot();
|
103
|
+
const children = root.getChildren();
|
104
|
+
const childrenLength = children.length;
|
105
|
+
if (childrenLength > 1) {
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
for (let i = 0; i < childrenLength; i++) {
|
109
|
+
const topBlock = children[i];
|
110
|
+
if ($isDecoratorNode(topBlock)) {
|
111
|
+
return false;
|
112
|
+
}
|
113
|
+
if ($isElementNode(topBlock)) {
|
114
|
+
if (!$isParagraphNode(topBlock)) {
|
115
|
+
return false;
|
116
|
+
}
|
117
|
+
if (topBlock.__indent !== 0) {
|
118
|
+
return false;
|
119
|
+
}
|
120
|
+
const topBlockChildren = topBlock.getChildren();
|
121
|
+
const topBlockChildrenLength = topBlockChildren.length;
|
122
|
+
for (let s = 0; s < topBlockChildrenLength; s++) {
|
123
|
+
const child = topBlockChildren[i];
|
124
|
+
if (!$isTextNode(child)) {
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
return true;
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Returns a function that executes {@link $canShowPlaceholder}
|
135
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
136
|
+
* @returns A function that executes $canShowPlaceholder with arguments.
|
137
|
+
*/
|
138
|
+
function $canShowPlaceholderCurry(isEditorComposing) {
|
139
|
+
return () => $canShowPlaceholder(isEditorComposing);
|
140
|
+
}
|
141
|
+
/**
|
142
|
+
* Returns a tuple that can be rested (...) into mergeRegister to clean up
|
143
|
+
* node transforms listeners that transforms text into another node, eg. a HashtagNode.
|
144
|
+
* @example
|
145
|
+
* ```ts
|
146
|
+
* useEffect(() => {
|
147
|
+
return mergeRegister(
|
148
|
+
...registerLexicalTextEntity(editor, getMatch, targetNode, createNode),
|
149
|
+
);
|
150
|
+
}, [createNode, editor, getMatch, targetNode]);
|
151
|
+
* ```
|
152
|
+
* Where targetNode is the type of node containing the text you want to transform (like a text input),
|
153
|
+
* then getMatch uses a regex to find a matching text and creates the proper node to include the matching text.
|
154
|
+
* @param editor - The lexical editor.
|
155
|
+
* @param getMatch - Finds a matching string that satisfies a regex expression.
|
156
|
+
* @param targetNode - The node type that contains text to match with. eg. HashtagNode
|
157
|
+
* @param createNode - A function that creates a new node to contain the matched text. eg createHashtagNode
|
158
|
+
* @returns An array containing the plain text and reverse node transform listeners.
|
159
|
+
*/
|
160
|
+
function registerLexicalTextEntity(editor, getMatch, targetNode, createNode) {
|
161
|
+
const isTargetNode = node => {
|
162
|
+
return node instanceof targetNode;
|
163
|
+
};
|
164
|
+
const replaceWithSimpleText = node => {
|
165
|
+
const textNode = $createTextNode(node.getTextContent());
|
166
|
+
textNode.setFormat(node.getFormat());
|
167
|
+
node.replace(textNode);
|
168
|
+
};
|
169
|
+
const getMode = node => {
|
170
|
+
return node.getLatest().__mode;
|
171
|
+
};
|
172
|
+
const textNodeTransform = node => {
|
173
|
+
if (!node.isSimpleText()) {
|
174
|
+
return;
|
175
|
+
}
|
176
|
+
const prevSibling = node.getPreviousSibling();
|
177
|
+
let text = node.getTextContent();
|
178
|
+
let currentNode = node;
|
179
|
+
let match;
|
180
|
+
if ($isTextNode(prevSibling)) {
|
181
|
+
const previousText = prevSibling.getTextContent();
|
182
|
+
const combinedText = previousText + text;
|
183
|
+
const prevMatch = getMatch(combinedText);
|
184
|
+
if (isTargetNode(prevSibling)) {
|
185
|
+
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
186
|
+
replaceWithSimpleText(prevSibling);
|
187
|
+
return;
|
188
|
+
} else {
|
189
|
+
const diff = prevMatch.end - previousText.length;
|
190
|
+
if (diff > 0) {
|
191
|
+
const concatText = text.slice(0, diff);
|
192
|
+
const newTextContent = previousText + concatText;
|
193
|
+
prevSibling.select();
|
194
|
+
prevSibling.setTextContent(newTextContent);
|
195
|
+
if (diff === text.length) {
|
196
|
+
node.remove();
|
197
|
+
} else {
|
198
|
+
const remainingText = text.slice(diff);
|
199
|
+
node.setTextContent(remainingText);
|
200
|
+
}
|
201
|
+
return;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
// eslint-disable-next-line no-constant-condition
|
210
|
+
while (true) {
|
211
|
+
match = getMatch(text);
|
212
|
+
let nextText = match === null ? '' : text.slice(match.end);
|
213
|
+
text = nextText;
|
214
|
+
if (nextText === '') {
|
215
|
+
const nextSibling = currentNode.getNextSibling();
|
216
|
+
if ($isTextNode(nextSibling)) {
|
217
|
+
nextText = currentNode.getTextContent() + nextSibling.getTextContent();
|
218
|
+
const nextMatch = getMatch(nextText);
|
219
|
+
if (nextMatch === null) {
|
220
|
+
if (isTargetNode(nextSibling)) {
|
221
|
+
replaceWithSimpleText(nextSibling);
|
222
|
+
} else {
|
223
|
+
nextSibling.markDirty();
|
224
|
+
}
|
225
|
+
return;
|
226
|
+
} else if (nextMatch.start !== 0) {
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
} else {
|
231
|
+
const nextMatch = getMatch(nextText);
|
232
|
+
if (nextMatch !== null && nextMatch.start === 0) {
|
233
|
+
return;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
if (match === null) {
|
237
|
+
return;
|
238
|
+
}
|
239
|
+
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
240
|
+
continue;
|
241
|
+
}
|
242
|
+
let nodeToReplace;
|
243
|
+
if (match.start === 0) {
|
244
|
+
[nodeToReplace, currentNode] = currentNode.splitText(match.end);
|
245
|
+
} else {
|
246
|
+
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end);
|
247
|
+
}
|
248
|
+
const replacementNode = createNode(nodeToReplace);
|
249
|
+
replacementNode.setFormat(nodeToReplace.getFormat());
|
250
|
+
nodeToReplace.replace(replacementNode);
|
251
|
+
if (currentNode == null) {
|
252
|
+
return;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
};
|
256
|
+
const reverseNodeTransform = node => {
|
257
|
+
const text = node.getTextContent();
|
258
|
+
const match = getMatch(text);
|
259
|
+
if (match === null || match.start !== 0) {
|
260
|
+
replaceWithSimpleText(node);
|
261
|
+
return;
|
262
|
+
}
|
263
|
+
if (text.length > match.end) {
|
264
|
+
// This will split out the rest of the text as simple text
|
265
|
+
node.splitText(match.end);
|
266
|
+
return;
|
267
|
+
}
|
268
|
+
const prevSibling = node.getPreviousSibling();
|
269
|
+
if ($isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
270
|
+
replaceWithSimpleText(prevSibling);
|
271
|
+
replaceWithSimpleText(node);
|
272
|
+
}
|
273
|
+
const nextSibling = node.getNextSibling();
|
274
|
+
if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
275
|
+
replaceWithSimpleText(nextSibling);
|
276
|
+
|
277
|
+
// This may have already been converted in the previous block
|
278
|
+
if (isTargetNode(node)) {
|
279
|
+
replaceWithSimpleText(node);
|
280
|
+
}
|
281
|
+
}
|
282
|
+
};
|
283
|
+
const removePlainTextTransform = editor.registerNodeTransform(TextNode, textNodeTransform);
|
284
|
+
const removeReverseNodeTransform = editor.registerNodeTransform(targetNode, reverseNodeTransform);
|
285
|
+
return [removePlainTextTransform, removeReverseNodeTransform];
|
286
|
+
}
|
287
|
+
|
288
|
+
export { $canShowPlaceholder, $canShowPlaceholderCurry, $findTextIntersectionFromCharacters, $isRootTextContentEmpty, $isRootTextContentEmptyCurry, $rootTextContent, registerLexicalTextEntity };
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
3
|
+
*
|
4
|
+
* This source code is licensed under the MIT license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
import * as modDev from './LexicalText.dev.esm.js';
|
8
|
+
import * as modProd from './LexicalText.prod.esm.js';
|
9
|
+
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
10
|
+
export const $canShowPlaceholder = mod.$canShowPlaceholder;
|
11
|
+
export const $canShowPlaceholderCurry = mod.$canShowPlaceholderCurry;
|
12
|
+
export const $findTextIntersectionFromCharacters = mod.$findTextIntersectionFromCharacters;
|
13
|
+
export const $isRootTextContentEmpty = mod.$isRootTextContentEmpty;
|
14
|
+
export const $isRootTextContentEmptyCurry = mod.$isRootTextContentEmptyCurry;
|
15
|
+
export const $rootTextContent = mod.$rootTextContent;
|
16
|
+
export const registerLexicalTextEntity = mod.registerLexicalTextEntity;
|
package/LexicalText.js
CHANGED
@@ -5,5 +5,5 @@
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
6
6
|
*/
|
7
7
|
'use strict'
|
8
|
-
const LexicalText = process.env.NODE_ENV === 'development' ? require('./LexicalText.dev.js') : require('./LexicalText.prod.js')
|
8
|
+
const LexicalText = process.env.NODE_ENV === 'development' ? require('./LexicalText.dev.js') : require('./LexicalText.prod.js');
|
9
9
|
module.exports = LexicalText;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
3
|
+
*
|
4
|
+
* This source code is licensed under the MIT license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
import{$isElementNode as t,$isTextNode as e,$getRoot as n,$isDecoratorNode as r,$isParagraphNode as i,TextNode as o,$createTextNode as l}from"lexical";function s(n,r){let i=n.getFirstChild(),o=0;t:for(;null!==i;){if(t(i)){const t=i.getFirstChild();if(null!==t){i=t;continue}}else if(e(i)){const t=i.getTextContentSize();if(o+t>r)return{node:i,offset:r-o};o+=t}const n=i.getNextSibling();if(null!==n){i=n;continue}let l=i.getParent();for(;null!==l;){const t=l.getNextSibling();if(null!==t){i=t;continue t}l=l.getParent()}break}return null}function u(t,e=!0){if(t)return!1;let n=c();return e&&(n=n.trim()),""===n}function f(t,e){return()=>u(t,e)}function c(){return n().getTextContent()}function g(o){if(!u(o,!1))return!1;const l=n().getChildren(),s=l.length;if(s>1)return!1;for(let n=0;n<s;n++){const o=l[n];if(r(o))return!1;if(t(o)){if(!i(o))return!1;if(0!==o.__indent)return!1;const t=o.getChildren(),r=t.length;for(let i=0;i<r;i++){const r=t[n];if(!e(r))return!1}}}return!0}function x(t){return()=>g(t)}function a(t,n,r,i){const s=t=>t instanceof r,u=t=>{const e=l(t.getTextContent());e.setFormat(t.getFormat()),t.replace(e)};return[t.registerNodeTransform(o,(t=>{if(!t.isSimpleText())return;const r=t.getPreviousSibling();let o,l=t.getTextContent(),f=t;if(e(r)){const e=r.getTextContent(),i=n(e+l);if(s(r)){if(null===i||0!==(t=>t.getLatest().__mode)(r))return void u(r);{const n=i.end-e.length;if(n>0){const i=e+l.slice(0,n);if(r.select(),r.setTextContent(i),n===l.length)t.remove();else{const e=l.slice(n);t.setTextContent(e)}return}}}else if(null===i||i.start<e.length)return}for(;;){o=n(l);let t,c=null===o?"":l.slice(o.end);if(l=c,""===c){const t=f.getNextSibling();if(e(t)){c=f.getTextContent()+t.getTextContent();const e=n(c);if(null===e)return void(s(t)?u(t):t.markDirty());if(0!==e.start)return}}else{const t=n(c);if(null!==t&&0===t.start)return}if(null===o)return;if(0===o.start&&e(r)&&r.isTextEntity())continue;0===o.start?[t,f]=f.splitText(o.end):[,t,f]=f.splitText(o.start,o.end);const g=i(t);if(g.setFormat(t.getFormat()),t.replace(g),null==f)return}})),t.registerNodeTransform(r,(t=>{const r=t.getTextContent(),i=n(r);if(null===i||0!==i.start)return void u(t);if(r.length>i.end)return void t.splitText(i.end);const o=t.getPreviousSibling();e(o)&&o.isTextEntity()&&(u(o),u(t));const l=t.getNextSibling();e(l)&&l.isTextEntity()&&(u(l),s(t)&&u(t))}))]}export{g as $canShowPlaceholder,x as $canShowPlaceholderCurry,s as $findTextIntersectionFromCharacters,u as $isRootTextContentEmpty,f as $isRootTextContentEmptyCurry,c as $rootTextContent,a as registerLexicalTextEntity};
|
package/README.md
CHANGED
package/package.json
CHANGED
@@ -9,14 +9,16 @@
|
|
9
9
|
"text"
|
10
10
|
],
|
11
11
|
"license": "MIT",
|
12
|
-
"version": "0.
|
12
|
+
"version": "0.14.2",
|
13
13
|
"main": "LexicalText.js",
|
14
14
|
"peerDependencies": {
|
15
|
-
"lexical": "0.
|
15
|
+
"lexical": "0.14.2"
|
16
16
|
},
|
17
17
|
"repository": {
|
18
18
|
"type": "git",
|
19
19
|
"url": "https://github.com/facebook/lexical",
|
20
20
|
"directory": "packages/lexical-text"
|
21
|
-
}
|
21
|
+
},
|
22
|
+
"module": "LexicalText.esm.js",
|
23
|
+
"sideEffects": false
|
22
24
|
}
|