@lexical/extension 0.44.1-nightly.20260519.0 → 0.45.1-dev.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/dist/ClickAfterLastBlockExtension.d.ts +61 -0
- package/{LexicalExtension.dev.js → dist/LexicalExtension.dev.js} +438 -1
- package/{LexicalExtension.dev.mjs → dist/LexicalExtension.dev.mjs} +433 -3
- package/{LexicalExtension.js.flow → dist/LexicalExtension.js.flow} +1 -1
- package/{LexicalExtension.mjs → dist/LexicalExtension.mjs} +7 -0
- package/{LexicalExtension.node.mjs → dist/LexicalExtension.node.mjs} +7 -0
- package/dist/LexicalExtension.prod.js +9 -0
- package/dist/LexicalExtension.prod.mjs +9 -0
- package/dist/NormalizeTripleClickSelectionExtension.d.ts +45 -0
- package/dist/WatchEditableExtension.d.ts +9 -0
- package/dist/getExtensionDependency.d.ts +80 -0
- package/{getExtensionDependencyFromEditor.d.ts → dist/getExtensionDependencyFromEditor.d.ts} +4 -0
- package/{getPeerDependencyFromEditor.d.ts → dist/getPeerDependencyFromEditor.d.ts} +8 -0
- package/{index.d.ts → dist/index.d.ts} +4 -0
- package/package.json +31 -16
- package/src/AutoFocusExtension.ts +67 -0
- package/src/ClearEditorExtension.ts +70 -0
- package/src/ClickAfterLastBlockExtension.ts +227 -0
- package/src/DecoratorTextExtension.ts +216 -0
- package/src/EditorStateExtension.ts +26 -0
- package/src/ExtensionRep.ts +488 -0
- package/src/HorizontalRuleExtension.ts +240 -0
- package/src/InitialStateExtension.ts +94 -0
- package/src/LexicalBuilder.ts +493 -0
- package/src/NestedEditorExtension.ts +51 -0
- package/src/NodeSelectionExtension.ts +101 -0
- package/src/NormalizeInlineElementsExtension.ts +69 -0
- package/src/NormalizeTripleClickSelectionExtension.ts +210 -0
- package/src/SelectionAlwaysOnDisplayExtension.ts +39 -0
- package/src/TabIndentationExtension.ts +167 -0
- package/src/WatchEditableExtension.ts +31 -0
- package/src/config.ts +52 -0
- package/src/deepThemeMergeInPlace.ts +41 -0
- package/src/getExtensionDependency.ts +104 -0
- package/src/getExtensionDependencyFromEditor.ts +49 -0
- package/src/getPeerDependencyFromEditor.ts +113 -0
- package/src/index.ts +111 -0
- package/src/namedSignals.ts +41 -0
- package/src/signals.ts +17 -0
- package/src/watchedSignal.ts +35 -0
- package/LexicalExtension.prod.js +0 -9
- package/LexicalExtension.prod.mjs +0 -9
- /package/{AutoFocusExtension.d.ts → dist/AutoFocusExtension.d.ts} +0 -0
- /package/{ClearEditorExtension.d.ts → dist/ClearEditorExtension.d.ts} +0 -0
- /package/{DecoratorTextExtension.d.ts → dist/DecoratorTextExtension.d.ts} +0 -0
- /package/{EditorStateExtension.d.ts → dist/EditorStateExtension.d.ts} +0 -0
- /package/{ExtensionRep.d.ts → dist/ExtensionRep.d.ts} +0 -0
- /package/{HorizontalRuleExtension.d.ts → dist/HorizontalRuleExtension.d.ts} +0 -0
- /package/{InitialStateExtension.d.ts → dist/InitialStateExtension.d.ts} +0 -0
- /package/{LexicalBuilder.d.ts → dist/LexicalBuilder.d.ts} +0 -0
- /package/{LexicalExtension.js → dist/LexicalExtension.js} +0 -0
- /package/{NestedEditorExtension.d.ts → dist/NestedEditorExtension.d.ts} +0 -0
- /package/{NodeSelectionExtension.d.ts → dist/NodeSelectionExtension.d.ts} +0 -0
- /package/{NormalizeInlineElementsExtension.d.ts → dist/NormalizeInlineElementsExtension.d.ts} +0 -0
- /package/{SelectionAlwaysOnDisplayExtension.d.ts → dist/SelectionAlwaysOnDisplayExtension.d.ts} +0 -0
- /package/{TabIndentationExtension.d.ts → dist/TabIndentationExtension.d.ts} +0 -0
- /package/{config.d.ts → dist/config.d.ts} +0 -0
- /package/{deepThemeMergeInPlace.d.ts → dist/deepThemeMergeInPlace.d.ts} +0 -0
- /package/{namedSignals.d.ts → dist/namedSignals.d.ts} +0 -0
- /package/{signals.d.ts → dist/signals.d.ts} +0 -0
- /package/{watchedSignal.d.ts → dist/watchedSignal.d.ts} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
*/
|
|
8
|
+
import { LexicalNode } from 'lexical';
|
|
9
|
+
import { type Signal } from './signals';
|
|
10
|
+
/**
|
|
11
|
+
* @experimental
|
|
12
|
+
*
|
|
13
|
+
* Default predicate matches {@link DecoratorNode} and shadow-root
|
|
14
|
+
* `ElementNode`s (e.g. `TableNode`). Apps that want to also trigger on
|
|
15
|
+
* other node types — `CodeNode`, custom non-editable blocks — should
|
|
16
|
+
* compose this default in their own predicate rather than re-deriving
|
|
17
|
+
* the check:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* configExtension(ClickAfterLastBlockExtension, {
|
|
21
|
+
* $shouldInsertAfter: (node) =>
|
|
22
|
+
* $defaultShouldInsertAfter(node) || $isCodeNode(node),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function $defaultShouldInsertAfter(node: LexicalNode): boolean;
|
|
27
|
+
export interface ClickAfterLastBlockConfig {
|
|
28
|
+
/** Set to `true` to disable this extension. */
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Called inside the editor update with the last child of the root when
|
|
32
|
+
* the user clicks the empty area below it. Return `true` to insert a
|
|
33
|
+
* new paragraph after that node and select it; return `false` to leave
|
|
34
|
+
* the click alone. Default is {@link $defaultShouldInsertAfter} — see
|
|
35
|
+
* its docs for composition patterns.
|
|
36
|
+
*/
|
|
37
|
+
$shouldInsertAfter: (node: LexicalNode) => boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ClickAfterLastBlockOutput {
|
|
40
|
+
/** Set to `true` to disable this extension. */
|
|
41
|
+
disabled: Signal<boolean>;
|
|
42
|
+
/** Predicate signal — see {@link ClickAfterLastBlockConfig.$shouldInsertAfter}. */
|
|
43
|
+
$shouldInsertAfter: Signal<(node: LexicalNode) => boolean>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Click handling for the empty area below the last block of the document.
|
|
47
|
+
*
|
|
48
|
+
* Without this extension, clicking the area below the last block when that
|
|
49
|
+
* block is a {@link DecoratorNode}, a shadow-root ElementNode (e.g.
|
|
50
|
+
* `TableNode`), or any other block that doesn't accept the click naturally
|
|
51
|
+
* leaves the selection in an awkward place — `null` for a bare decorator,
|
|
52
|
+
* or at the end of a table cell. Users typically expect a new paragraph
|
|
53
|
+
* to appear below the block with the caret in it, matching the behavior
|
|
54
|
+
* of editors like Notion.
|
|
55
|
+
*
|
|
56
|
+
* This extension intercepts clicks under those conditions, inserts a new
|
|
57
|
+
* empty paragraph after the last block, and selects it.
|
|
58
|
+
*
|
|
59
|
+
* Closes #8544.
|
|
60
|
+
*/
|
|
61
|
+
export declare const ClickAfterLastBlockExtension: import("lexical").LexicalExtension<ClickAfterLastBlockConfig, "@lexical/ClickAfterLastBlock", ClickAfterLastBlockOutput, unknown>;
|
|
@@ -133,6 +133,177 @@ const ClearEditorExtension = lexical.defineExtension({
|
|
|
133
133
|
}
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
138
|
+
*
|
|
139
|
+
* This source code is licensed under the MIT license found in the
|
|
140
|
+
* LICENSE file in the root directory of this source tree.
|
|
141
|
+
*
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @experimental
|
|
147
|
+
*
|
|
148
|
+
* Default predicate matches {@link DecoratorNode} and shadow-root
|
|
149
|
+
* `ElementNode`s (e.g. `TableNode`). Apps that want to also trigger on
|
|
150
|
+
* other node types — `CodeNode`, custom non-editable blocks — should
|
|
151
|
+
* compose this default in their own predicate rather than re-deriving
|
|
152
|
+
* the check:
|
|
153
|
+
*
|
|
154
|
+
* ```ts
|
|
155
|
+
* configExtension(ClickAfterLastBlockExtension, {
|
|
156
|
+
* $shouldInsertAfter: (node) =>
|
|
157
|
+
* $defaultShouldInsertAfter(node) || $isCodeNode(node),
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
function $defaultShouldInsertAfter(node) {
|
|
162
|
+
if (lexical.$isDecoratorNode(node)) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (lexical.$isElementNode(node) && node.isShadowRoot()) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Decide whether a click at `event` should be claimed by this extension.
|
|
172
|
+
* Used by both the mousedown listener (to call preventDefault on the
|
|
173
|
+
* browser's native caret pick) and the click listener (to actually
|
|
174
|
+
* insert the paragraph). Factored out so the two handlers stay in sync
|
|
175
|
+
* — they would otherwise share ~22 lines of byte-for-byte identical
|
|
176
|
+
* logic and have to be maintained together.
|
|
177
|
+
*
|
|
178
|
+
* Read-only because it is called outside an editor.update; mutation
|
|
179
|
+
* happens in the click handler's editor.update below.
|
|
180
|
+
*/
|
|
181
|
+
function shouldClaimClick(editor, rootElement, event, $shouldInsertAfter) {
|
|
182
|
+
if (!editor.isEditable()) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
// Only react to clicks on the root container itself. A click inside
|
|
186
|
+
// an existing block is handled by the normal caret placement path.
|
|
187
|
+
if (event.target !== rootElement) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return editor.getEditorState().read(() => {
|
|
191
|
+
const lastChild = lexical.$getRoot().getLastChild();
|
|
192
|
+
if (lastChild === null) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const lastChildDOM = editor.getElementByKey(lastChild.getKey());
|
|
196
|
+
if (lastChildDOM === null) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
// Exclusive lower edge — clicks at exactly the bottom pixel fall
|
|
200
|
+
// through to native handling, which is what users expect when
|
|
201
|
+
// they click on a block's visible bottom border.
|
|
202
|
+
if (event.clientY <= lastChildDOM.getBoundingClientRect().bottom) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return $shouldInsertAfter(lastChild);
|
|
206
|
+
}, {
|
|
207
|
+
editor
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Click handling for the empty area below the last block of the document.
|
|
213
|
+
*
|
|
214
|
+
* Without this extension, clicking the area below the last block when that
|
|
215
|
+
* block is a {@link DecoratorNode}, a shadow-root ElementNode (e.g.
|
|
216
|
+
* `TableNode`), or any other block that doesn't accept the click naturally
|
|
217
|
+
* leaves the selection in an awkward place — `null` for a bare decorator,
|
|
218
|
+
* or at the end of a table cell. Users typically expect a new paragraph
|
|
219
|
+
* to appear below the block with the caret in it, matching the behavior
|
|
220
|
+
* of editors like Notion.
|
|
221
|
+
*
|
|
222
|
+
* This extension intercepts clicks under those conditions, inserts a new
|
|
223
|
+
* empty paragraph after the last block, and selects it.
|
|
224
|
+
*
|
|
225
|
+
* Closes #8544.
|
|
226
|
+
*/
|
|
227
|
+
const ClickAfterLastBlockExtension = lexical.defineExtension({
|
|
228
|
+
build: (_editor, config) => namedSignals(config),
|
|
229
|
+
config: lexical.safeCast({
|
|
230
|
+
$shouldInsertAfter: $defaultShouldInsertAfter,
|
|
231
|
+
disabled: false
|
|
232
|
+
}),
|
|
233
|
+
name: '@lexical/ClickAfterLastBlock',
|
|
234
|
+
register: (editor, _config, _state) => j(() => {
|
|
235
|
+
const output = _state.getOutput();
|
|
236
|
+
if (output.disabled.value) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
return editor.registerRootListener(rootElement => {
|
|
240
|
+
if (rootElement === null) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Two-phase: cancel native caret-pick at the earliest browser
|
|
244
|
+
// event (mousedown), then claim the click for our own paragraph
|
|
245
|
+
// insert. Without the mousedown leg the browser places a caret
|
|
246
|
+
// on the previous text block before our editor.update lands,
|
|
247
|
+
// visible as a one-frame cursor flicker.
|
|
248
|
+
//
|
|
249
|
+
// Side effect to be aware of: cancelling mousedown also cancels
|
|
250
|
+
// native focus on that click. The subsequent paragraph.select()
|
|
251
|
+
// inside editor.update DOM-focuses the root through the
|
|
252
|
+
// reconciler, so the net effect is the same focused root, but
|
|
253
|
+
// we are now responsible for the focus transition rather than
|
|
254
|
+
// the browser.
|
|
255
|
+
//
|
|
256
|
+
// lexical core has a `pointerdown` listener that flips a
|
|
257
|
+
// module-level `isSelectionChangeFromMouseDown` flag consumed
|
|
258
|
+
// on the next selectionchange (LexicalEvents.ts). preventing
|
|
259
|
+
// mousedown means no native selectionchange fires for this
|
|
260
|
+
// click, so the flag never gets a chance to be consumed in the
|
|
261
|
+
// wrong cycle. If you swap mousedown for pointerdown here you
|
|
262
|
+
// also have to revisit that interaction.
|
|
263
|
+
const onMouseDown = event => {
|
|
264
|
+
if (shouldClaimClick(editor, rootElement, event, output.$shouldInsertAfter.peek())) {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const onClick = event => {
|
|
269
|
+
if (!shouldClaimClick(editor, rootElement, event, output.$shouldInsertAfter.peek())) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
// Tell lexical's root click handler to skip this event so the
|
|
274
|
+
// default caret-placement logic in LexicalEvents.onClick exits
|
|
275
|
+
// early. Without this the previous text block briefly receives
|
|
276
|
+
// the caret before our paragraph insert lands.
|
|
277
|
+
lexical.stopLexicalPropagation(event);
|
|
278
|
+
editor.update(() => {
|
|
279
|
+
const lastChild = lexical.$getRoot().getLastChild();
|
|
280
|
+
if (lastChild === null) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// Re-check inside the update — predicate may flip between
|
|
284
|
+
// read and update if external transforms run.
|
|
285
|
+
if (!output.$shouldInsertAfter.peek()(lastChild)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const paragraph = lexical.$createParagraphNode();
|
|
289
|
+
lastChild.insertAfter(paragraph);
|
|
290
|
+
paragraph.select();
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Capture phase so the mousedown preventDefault runs before any
|
|
295
|
+
// bubble-phase handler can react, and so the click flag is set
|
|
296
|
+
// before lexical core's bubble-phase onClick reads it.
|
|
297
|
+
rootElement.addEventListener('mousedown', onMouseDown, true);
|
|
298
|
+
rootElement.addEventListener('click', onClick, true);
|
|
299
|
+
return () => {
|
|
300
|
+
rootElement.removeEventListener('mousedown', onMouseDown, true);
|
|
301
|
+
rootElement.removeEventListener('click', onClick, true);
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
})
|
|
305
|
+
});
|
|
306
|
+
|
|
136
307
|
/**
|
|
137
308
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
138
309
|
*
|
|
@@ -390,6 +561,31 @@ function formatDevErrorMessage(message) {
|
|
|
390
561
|
throw new Error(message);
|
|
391
562
|
}
|
|
392
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
566
|
+
*
|
|
567
|
+
* This source code is licensed under the MIT license found in the
|
|
568
|
+
* LICENSE file in the root directory of this source tree.
|
|
569
|
+
*
|
|
570
|
+
*/
|
|
571
|
+
|
|
572
|
+
// `"0.45.1-dev.0+dev.cjs"` is statically replaced with the build-specific
|
|
573
|
+
// version string in a Rollup build, and a consumer's bundler `define` can
|
|
574
|
+
// inject it the same way — so the exact `"0.45.1-dev.0+dev.cjs"` member
|
|
575
|
+
// expression must be preserved for that substitution to match. Reading it
|
|
576
|
+
// inside a try/catch lets the source be consumed directly (via the `source`
|
|
577
|
+
// export condition) in a browser bundle, where `process` is undefined and
|
|
578
|
+
// nothing replaced the reference, without throwing a ReferenceError; it falls
|
|
579
|
+
// back to the literal below instead. The literal is regenerated by
|
|
580
|
+
// `pnpm run update-version`.
|
|
581
|
+
let envLexicalVersion;
|
|
582
|
+
try {
|
|
583
|
+
envLexicalVersion = "0.45.1-dev.0+dev.cjs";
|
|
584
|
+
} catch (_unused) {
|
|
585
|
+
// `process` is not defined in some browser bundles; use the fallback.
|
|
586
|
+
}
|
|
587
|
+
const LEXICAL_VERSION = envLexicalVersion ?? '0.45.1-dev.0+source';
|
|
588
|
+
|
|
393
589
|
/**
|
|
394
590
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
395
591
|
*
|
|
@@ -823,7 +1019,7 @@ function maybeWithBuilder(editor) {
|
|
|
823
1019
|
function normalizeExtensionArgument(arg) {
|
|
824
1020
|
return Array.isArray(arg) ? arg : [arg];
|
|
825
1021
|
}
|
|
826
|
-
const PACKAGE_VERSION =
|
|
1022
|
+
const PACKAGE_VERSION = LEXICAL_VERSION;
|
|
827
1023
|
|
|
828
1024
|
/** @internal */
|
|
829
1025
|
class LexicalBuilder {
|
|
@@ -1151,6 +1347,10 @@ class LexicalBuilder {
|
|
|
1151
1347
|
*
|
|
1152
1348
|
* It will throw if the Editor was not built using this Extension.
|
|
1153
1349
|
*
|
|
1350
|
+
* Inside an editor read/update, prefer {@link $getExtensionDependency} or
|
|
1351
|
+
* {@link $getExtensionOutput} — they resolve the editor via `$getEditor()`
|
|
1352
|
+
* so you don't have to thread it through.
|
|
1353
|
+
*
|
|
1154
1354
|
* @param editor - The editor that was built using extension
|
|
1155
1355
|
* @param extension - The concrete reference to an Extension used to build this editor
|
|
1156
1356
|
* @returns The config and output for that Extension
|
|
@@ -1176,6 +1376,10 @@ function getExtensionDependencyFromEditor(editor, extension) {
|
|
|
1176
1376
|
*
|
|
1177
1377
|
* Both the explicit Extension type and the name are required.
|
|
1178
1378
|
*
|
|
1379
|
+
* Inside an editor read/update, prefer {@link $getPeerDependency} — it
|
|
1380
|
+
* resolves the editor via `$getEditor()` so you don't have to thread it
|
|
1381
|
+
* through.
|
|
1382
|
+
*
|
|
1179
1383
|
* @example
|
|
1180
1384
|
* ```tsx
|
|
1181
1385
|
* import type { HistoryExtension } from "@lexical/history";
|
|
@@ -1205,6 +1409,10 @@ function getPeerDependencyFromEditor(editor, extensionName) {
|
|
|
1205
1409
|
*
|
|
1206
1410
|
* Both the explicit Extension type and the name are required.
|
|
1207
1411
|
*
|
|
1412
|
+
* Inside an editor read/update, prefer {@link $getPeerDependency} (which
|
|
1413
|
+
* resolves the editor via `$getEditor()`) and add your own invariant if
|
|
1414
|
+
* the peer is required.
|
|
1415
|
+
*
|
|
1208
1416
|
* @example
|
|
1209
1417
|
* ```tsx
|
|
1210
1418
|
* import type { EmojiExtension } from "./EmojiExtension";
|
|
@@ -1239,6 +1447,96 @@ function getPeerDependencyFromEditorOrThrow(editor, extensionName) {
|
|
|
1239
1447
|
return dep;
|
|
1240
1448
|
}
|
|
1241
1449
|
|
|
1450
|
+
/**
|
|
1451
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1452
|
+
*
|
|
1453
|
+
* This source code is licensed under the MIT license found in the
|
|
1454
|
+
* LICENSE file in the root directory of this source tree.
|
|
1455
|
+
*
|
|
1456
|
+
*/
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Get the finalized config and output for `extension` from the editor
|
|
1461
|
+
* currently in scope. A `$`-flavored shorthand for
|
|
1462
|
+
* `getExtensionDependencyFromEditor($getEditor(), extension)`.
|
|
1463
|
+
*
|
|
1464
|
+
* Throws if the editor was not built with `extension` as a dependency.
|
|
1465
|
+
*
|
|
1466
|
+
* @example
|
|
1467
|
+
* ```ts
|
|
1468
|
+
* import {$getExtensionDependency} from '@lexical/extension';
|
|
1469
|
+
* import {KeywordsExtension} from './KeywordsExtension';
|
|
1470
|
+
*
|
|
1471
|
+
* class KeywordNode extends TextNode {
|
|
1472
|
+
* createDOM(config: EditorConfig): HTMLElement {
|
|
1473
|
+
* const dom = super.createDOM(config);
|
|
1474
|
+
* dom.className =
|
|
1475
|
+
* $getExtensionDependency(KeywordsExtension).config.className;
|
|
1476
|
+
* return dom;
|
|
1477
|
+
* }
|
|
1478
|
+
* }
|
|
1479
|
+
* ```
|
|
1480
|
+
*
|
|
1481
|
+
* @see {@link getExtensionDependencyFromEditor} when you have an explicit
|
|
1482
|
+
* editor reference (e.g. outside a read/update).
|
|
1483
|
+
*/
|
|
1484
|
+
function $getExtensionDependency(extension) {
|
|
1485
|
+
return getExtensionDependencyFromEditor(lexical.$getEditor(), extension);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* Shorthand for `$getExtensionDependency(extension).output` — the most
|
|
1490
|
+
* common reason to look up an extension dependency. Throws if the editor
|
|
1491
|
+
* was not built with `extension` as a dependency.
|
|
1492
|
+
*
|
|
1493
|
+
* @example
|
|
1494
|
+
* ```ts
|
|
1495
|
+
* import {$getExtensionOutput} from '@lexical/extension';
|
|
1496
|
+
* import {DOMImportExtension} from '@lexical/html';
|
|
1497
|
+
*
|
|
1498
|
+
* const nodes = $getExtensionOutput(DOMImportExtension).$generateNodesFromDOM(
|
|
1499
|
+
* dom,
|
|
1500
|
+
* );
|
|
1501
|
+
* ```
|
|
1502
|
+
*
|
|
1503
|
+
* @see {@link $getExtensionDependency} when you need both `.config` and
|
|
1504
|
+
* `.output` (or want to mirror the shape of
|
|
1505
|
+
* {@link getExtensionDependencyFromEditor}).
|
|
1506
|
+
*/
|
|
1507
|
+
function $getExtensionOutput(extension) {
|
|
1508
|
+
return $getExtensionDependency(extension).output;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* Get the finalized config and output for an optional peer extension by
|
|
1513
|
+
* name, from the editor currently in scope. A `$`-flavored shorthand for
|
|
1514
|
+
* `getPeerDependencyFromEditor($getEditor(), extensionName)`.
|
|
1515
|
+
*
|
|
1516
|
+
* Returns `undefined` if the editor was not built with the named
|
|
1517
|
+
* extension. Both the explicit `Extension` type and the name are
|
|
1518
|
+
* required so the returned `config` / `output` types are correct.
|
|
1519
|
+
*
|
|
1520
|
+
* @example
|
|
1521
|
+
* ```ts
|
|
1522
|
+
* import {$getPeerDependency} from '@lexical/extension';
|
|
1523
|
+
* import type {HistoryExtension} from '@lexical/history';
|
|
1524
|
+
*
|
|
1525
|
+
* const dep = $getPeerDependency<typeof HistoryExtension>(
|
|
1526
|
+
* '@lexical/history/History',
|
|
1527
|
+
* );
|
|
1528
|
+
* if (dep) {
|
|
1529
|
+
* // …read dep.config / dep.output…
|
|
1530
|
+
* }
|
|
1531
|
+
* ```
|
|
1532
|
+
*
|
|
1533
|
+
* @see {@link getPeerDependencyFromEditor} when you have an explicit
|
|
1534
|
+
* editor reference.
|
|
1535
|
+
*/
|
|
1536
|
+
function $getPeerDependency(extensionName) {
|
|
1537
|
+
return getPeerDependencyFromEditor(lexical.$getEditor(), extensionName);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1242
1540
|
/**
|
|
1243
1541
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1244
1542
|
*
|
|
@@ -1572,6 +1870,113 @@ const NormalizeInlineElementsExtension = lexical.defineExtension({
|
|
|
1572
1870
|
}
|
|
1573
1871
|
});
|
|
1574
1872
|
|
|
1873
|
+
/**
|
|
1874
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1875
|
+
*
|
|
1876
|
+
* This source code is licensed under the MIT license found in the
|
|
1877
|
+
* LICENSE file in the root directory of this source tree.
|
|
1878
|
+
*
|
|
1879
|
+
*/
|
|
1880
|
+
|
|
1881
|
+
const SKIP_TAGS = new Set([lexical.SKIP_SELECTION_FOCUS_TAG, lexical.SKIP_SCROLL_INTO_VIEW_TAG]);
|
|
1882
|
+
function $fixFocusOverselection() {
|
|
1883
|
+
const selection = lexical.$getSelection();
|
|
1884
|
+
if (!lexical.$isRangeSelection(selection)) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
if (!selection.isCollapsed()) {
|
|
1888
|
+
// Triple click causing selection to overflow into the nearest element. In that
|
|
1889
|
+
// case visually it looks like a single element content is selected, focus node
|
|
1890
|
+
// is actually at the beginning of the next element (if present) and any manipulations
|
|
1891
|
+
// with selection (formatting) are affecting second element as well
|
|
1892
|
+
const range = lexical.$getCaretRangeInDirection(lexical.$caretRangeFromSelection(selection), 'next');
|
|
1893
|
+
let focusCaret = range.focus;
|
|
1894
|
+
// Move it out of the next TextNode if none of it is selected
|
|
1895
|
+
if (lexical.$isTextPointCaret(focusCaret) && range.anchor.origin !== focusCaret.origin && focusCaret.offset === 0) {
|
|
1896
|
+
focusCaret = lexical.$rewindSiblingCaret(focusCaret.getSiblingCaret());
|
|
1897
|
+
}
|
|
1898
|
+
// Move it behind a single LineBreakNode
|
|
1899
|
+
if (lexical.$isSiblingCaret(focusCaret) && range.anchor.origin !== focusCaret.origin && lexical.$isLineBreakNode(focusCaret.origin)) {
|
|
1900
|
+
focusCaret = lexical.$rewindSiblingCaret(focusCaret);
|
|
1901
|
+
}
|
|
1902
|
+
// Move the focus out of the start of any elements
|
|
1903
|
+
while (lexical.$isChildCaret(focusCaret) && range.anchor.origin !== focusCaret.origin) {
|
|
1904
|
+
focusCaret = lexical.$rewindSiblingCaret(lexical.$getSiblingCaret(focusCaret.origin, 'next'));
|
|
1905
|
+
}
|
|
1906
|
+
// Move it inside the containing element
|
|
1907
|
+
if (lexical.$isSiblingCaret(focusCaret) && lexical.$isElementNode(focusCaret.origin)) {
|
|
1908
|
+
focusCaret = lexical.$normalizeCaret(lexical.$getChildCaret(focusCaret.origin, 'previous')).getFlipped();
|
|
1909
|
+
}
|
|
1910
|
+
focusCaret = lexical.$normalizeCaret(focusCaret);
|
|
1911
|
+
if (!focusCaret.isSamePointCaret(range.focus)) {
|
|
1912
|
+
const sel = lexical.$setSelectionFromCaretRange(lexical.$getCaretRange(range.anchor, focusCaret));
|
|
1913
|
+
const editor = lexical.$getEditor();
|
|
1914
|
+
const rootElement = editor.getRootElement();
|
|
1915
|
+
const domSelection = rootElement && lexical.getDOMSelection(rootElement.ownerDocument.defaultView);
|
|
1916
|
+
if (domSelection) {
|
|
1917
|
+
lexical.$updateDOMSelection(lexical.$getPreviousSelection(), sel, lexical.$getEditor(), domSelection, SKIP_TAGS, rootElement);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/**
|
|
1924
|
+
* This extension handles triple-click events and will move the focus
|
|
1925
|
+
* towards the anchor in certain conditions to meet expectations.
|
|
1926
|
+
* Simply speaking, the focus should prefer to land at the end of a node
|
|
1927
|
+
* rather than the beginning of its next sibling, and it should not skip
|
|
1928
|
+
* over a LineBreakNode.
|
|
1929
|
+
*
|
|
1930
|
+
* In order to fix the result visually and avoid a flash of over-selection
|
|
1931
|
+
* it will also eagerly manipulate the DOM selection directly.
|
|
1932
|
+
*
|
|
1933
|
+
* It is conservative in that it only fires this
|
|
1934
|
+
* `$fixFocusOverselection` callback when it has detected a triple click,
|
|
1935
|
+
* but it provides the function as an output signal so that it can both
|
|
1936
|
+
* be called from other places and it can be replaced or wrapped with
|
|
1937
|
+
* different functionality.
|
|
1938
|
+
*/
|
|
1939
|
+
const NormalizeTripleClickSelectionExtension = lexical.defineExtension({
|
|
1940
|
+
build: (editor, config, state) => namedSignals(config),
|
|
1941
|
+
config: lexical.safeCast({
|
|
1942
|
+
$fixFocusOverselection,
|
|
1943
|
+
dateNow: Date.now,
|
|
1944
|
+
disabled: false,
|
|
1945
|
+
thresholdMsec: 100
|
|
1946
|
+
}),
|
|
1947
|
+
name: '@lexical/NormalizeTripleClickSelection',
|
|
1948
|
+
register: (editor, config, state) => j(() => {
|
|
1949
|
+
const stores = state.getOutput();
|
|
1950
|
+
if (stores.disabled.value) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
return editor.registerRootListener(rootElement => {
|
|
1954
|
+
if (!rootElement) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
let lastTripleClick = 0;
|
|
1958
|
+
const refreshTripleClick = event => {
|
|
1959
|
+
if (event ? event.detail === 3 : lastTripleClick > 0) {
|
|
1960
|
+
const now = stores.dateNow.peek()();
|
|
1961
|
+
lastTripleClick = event && event.type === 'mousedown' || now - lastTripleClick <= stores.thresholdMsec.peek() ? now : 0;
|
|
1962
|
+
}
|
|
1963
|
+
return lastTripleClick;
|
|
1964
|
+
};
|
|
1965
|
+
return lexical.mergeRegister(editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, () => {
|
|
1966
|
+
if (refreshTripleClick(null)) {
|
|
1967
|
+
lastTripleClick = 0;
|
|
1968
|
+
stores.$fixFocusOverselection.peek()();
|
|
1969
|
+
}
|
|
1970
|
+
return false;
|
|
1971
|
+
}, lexical.COMMAND_PRIORITY_BEFORE_CRITICAL), (() => {
|
|
1972
|
+
const events = ['mouseup', 'mousedown'];
|
|
1973
|
+
events.forEach(v => rootElement.addEventListener(v, refreshTripleClick, true));
|
|
1974
|
+
return () => events.forEach(v => rootElement.removeEventListener(v, refreshTripleClick, true));
|
|
1975
|
+
})());
|
|
1976
|
+
});
|
|
1977
|
+
})
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1575
1980
|
/**
|
|
1576
1981
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1577
1982
|
*
|
|
@@ -1694,16 +2099,46 @@ const TabIndentationExtension = lexical.defineExtension({
|
|
|
1694
2099
|
}
|
|
1695
2100
|
});
|
|
1696
2101
|
|
|
2102
|
+
/**
|
|
2103
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2104
|
+
*
|
|
2105
|
+
* This source code is licensed under the MIT license found in the
|
|
2106
|
+
* LICENSE file in the root directory of this source tree.
|
|
2107
|
+
*
|
|
2108
|
+
*/
|
|
2109
|
+
|
|
2110
|
+
/**
|
|
2111
|
+
* Exposes the editor's editable state as a reactive `Signal<boolean>` that
|
|
2112
|
+
* mirrors `editor.isEditable()` via an editable listener.
|
|
2113
|
+
*
|
|
2114
|
+
* Depend on this extension and read its output `Signal` from a signals
|
|
2115
|
+
* `effect`/`computed` to react to editability changes without subscribing
|
|
2116
|
+
* through React (or any other framework).
|
|
2117
|
+
*/
|
|
2118
|
+
const WatchEditableExtension = lexical.defineExtension({
|
|
2119
|
+
build(editor) {
|
|
2120
|
+
return watchedSignal(() => editor.isEditable(), signal => editor.registerEditableListener(editable => {
|
|
2121
|
+
signal.value = editable;
|
|
2122
|
+
}));
|
|
2123
|
+
},
|
|
2124
|
+
name: '@lexical/extension/WatchEditable'
|
|
2125
|
+
});
|
|
2126
|
+
|
|
1697
2127
|
exports.configExtension = lexical.configExtension;
|
|
1698
2128
|
exports.declarePeerDependency = lexical.declarePeerDependency;
|
|
1699
2129
|
exports.defineExtension = lexical.defineExtension;
|
|
1700
2130
|
exports.safeCast = lexical.safeCast;
|
|
1701
2131
|
exports.shallowMergeConfig = lexical.shallowMergeConfig;
|
|
1702
2132
|
exports.$createHorizontalRuleNode = $createHorizontalRuleNode;
|
|
2133
|
+
exports.$defaultShouldInsertAfter = $defaultShouldInsertAfter;
|
|
2134
|
+
exports.$getExtensionDependency = $getExtensionDependency;
|
|
2135
|
+
exports.$getExtensionOutput = $getExtensionOutput;
|
|
2136
|
+
exports.$getPeerDependency = $getPeerDependency;
|
|
1703
2137
|
exports.$isDecoratorTextNode = $isDecoratorTextNode;
|
|
1704
2138
|
exports.$isHorizontalRuleNode = $isHorizontalRuleNode;
|
|
1705
2139
|
exports.AutoFocusExtension = AutoFocusExtension;
|
|
1706
2140
|
exports.ClearEditorExtension = ClearEditorExtension;
|
|
2141
|
+
exports.ClickAfterLastBlockExtension = ClickAfterLastBlockExtension;
|
|
1707
2142
|
exports.DecoratorTextExtension = DecoratorTextExtension;
|
|
1708
2143
|
exports.DecoratorTextNode = DecoratorTextNode;
|
|
1709
2144
|
exports.EditorStateExtension = EditorStateExtension;
|
|
@@ -1715,8 +2150,10 @@ exports.LexicalBuilder = LexicalBuilder;
|
|
|
1715
2150
|
exports.NestedEditorExtension = NestedEditorExtension;
|
|
1716
2151
|
exports.NodeSelectionExtension = NodeSelectionExtension;
|
|
1717
2152
|
exports.NormalizeInlineElementsExtension = NormalizeInlineElementsExtension;
|
|
2153
|
+
exports.NormalizeTripleClickSelectionExtension = NormalizeTripleClickSelectionExtension;
|
|
1718
2154
|
exports.SelectionAlwaysOnDisplayExtension = SelectionAlwaysOnDisplayExtension;
|
|
1719
2155
|
exports.TabIndentationExtension = TabIndentationExtension;
|
|
2156
|
+
exports.WatchEditableExtension = WatchEditableExtension;
|
|
1720
2157
|
exports.applyFormatFromStyle = applyFormatFromStyle;
|
|
1721
2158
|
exports.applyFormatToDom = applyFormatToDom;
|
|
1722
2159
|
exports.batch = n;
|