@lexical/extension 0.44.1-nightly.20260519.0 → 0.45.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.
Files changed (59) hide show
  1. package/dist/ClickAfterLastBlockExtension.d.ts +61 -0
  2. package/{LexicalExtension.dev.js → dist/LexicalExtension.dev.js} +412 -1
  3. package/{LexicalExtension.dev.mjs → dist/LexicalExtension.dev.mjs} +408 -3
  4. package/{LexicalExtension.js.flow → dist/LexicalExtension.js.flow} +1 -1
  5. package/{LexicalExtension.mjs → dist/LexicalExtension.mjs} +6 -0
  6. package/{LexicalExtension.node.mjs → dist/LexicalExtension.node.mjs} +6 -0
  7. package/dist/LexicalExtension.prod.js +9 -0
  8. package/dist/LexicalExtension.prod.mjs +9 -0
  9. package/dist/NormalizeTripleClickSelectionExtension.d.ts +45 -0
  10. package/dist/getExtensionDependency.d.ts +80 -0
  11. package/{getExtensionDependencyFromEditor.d.ts → dist/getExtensionDependencyFromEditor.d.ts} +4 -0
  12. package/{getPeerDependencyFromEditor.d.ts → dist/getPeerDependencyFromEditor.d.ts} +8 -0
  13. package/{index.d.ts → dist/index.d.ts} +3 -0
  14. package/package.json +31 -16
  15. package/src/AutoFocusExtension.ts +67 -0
  16. package/src/ClearEditorExtension.ts +70 -0
  17. package/src/ClickAfterLastBlockExtension.ts +227 -0
  18. package/src/DecoratorTextExtension.ts +216 -0
  19. package/src/EditorStateExtension.ts +26 -0
  20. package/src/ExtensionRep.ts +488 -0
  21. package/src/HorizontalRuleExtension.ts +240 -0
  22. package/src/InitialStateExtension.ts +94 -0
  23. package/src/LexicalBuilder.ts +493 -0
  24. package/src/NestedEditorExtension.ts +51 -0
  25. package/src/NodeSelectionExtension.ts +101 -0
  26. package/src/NormalizeInlineElementsExtension.ts +69 -0
  27. package/src/NormalizeTripleClickSelectionExtension.ts +210 -0
  28. package/src/SelectionAlwaysOnDisplayExtension.ts +39 -0
  29. package/src/TabIndentationExtension.ts +167 -0
  30. package/src/config.ts +52 -0
  31. package/src/deepThemeMergeInPlace.ts +41 -0
  32. package/src/getExtensionDependency.ts +104 -0
  33. package/src/getExtensionDependencyFromEditor.ts +49 -0
  34. package/src/getPeerDependencyFromEditor.ts +113 -0
  35. package/src/index.ts +110 -0
  36. package/src/namedSignals.ts +41 -0
  37. package/src/signals.ts +17 -0
  38. package/src/watchedSignal.ts +35 -0
  39. package/LexicalExtension.prod.js +0 -9
  40. package/LexicalExtension.prod.mjs +0 -9
  41. /package/{AutoFocusExtension.d.ts → dist/AutoFocusExtension.d.ts} +0 -0
  42. /package/{ClearEditorExtension.d.ts → dist/ClearEditorExtension.d.ts} +0 -0
  43. /package/{DecoratorTextExtension.d.ts → dist/DecoratorTextExtension.d.ts} +0 -0
  44. /package/{EditorStateExtension.d.ts → dist/EditorStateExtension.d.ts} +0 -0
  45. /package/{ExtensionRep.d.ts → dist/ExtensionRep.d.ts} +0 -0
  46. /package/{HorizontalRuleExtension.d.ts → dist/HorizontalRuleExtension.d.ts} +0 -0
  47. /package/{InitialStateExtension.d.ts → dist/InitialStateExtension.d.ts} +0 -0
  48. /package/{LexicalBuilder.d.ts → dist/LexicalBuilder.d.ts} +0 -0
  49. /package/{LexicalExtension.js → dist/LexicalExtension.js} +0 -0
  50. /package/{NestedEditorExtension.d.ts → dist/NestedEditorExtension.d.ts} +0 -0
  51. /package/{NodeSelectionExtension.d.ts → dist/NodeSelectionExtension.d.ts} +0 -0
  52. /package/{NormalizeInlineElementsExtension.d.ts → dist/NormalizeInlineElementsExtension.d.ts} +0 -0
  53. /package/{SelectionAlwaysOnDisplayExtension.d.ts → dist/SelectionAlwaysOnDisplayExtension.d.ts} +0 -0
  54. /package/{TabIndentationExtension.d.ts → dist/TabIndentationExtension.d.ts} +0 -0
  55. /package/{config.d.ts → dist/config.d.ts} +0 -0
  56. /package/{deepThemeMergeInPlace.d.ts → dist/deepThemeMergeInPlace.d.ts} +0 -0
  57. /package/{namedSignals.d.ts → dist/namedSignals.d.ts} +0 -0
  58. /package/{signals.d.ts → dist/signals.d.ts} +0 -0
  59. /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.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.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.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.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 = "0.44.1-nightly.20260519.0+dev.cjs";
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
  *
@@ -1700,10 +2105,15 @@ exports.defineExtension = lexical.defineExtension;
1700
2105
  exports.safeCast = lexical.safeCast;
1701
2106
  exports.shallowMergeConfig = lexical.shallowMergeConfig;
1702
2107
  exports.$createHorizontalRuleNode = $createHorizontalRuleNode;
2108
+ exports.$defaultShouldInsertAfter = $defaultShouldInsertAfter;
2109
+ exports.$getExtensionDependency = $getExtensionDependency;
2110
+ exports.$getExtensionOutput = $getExtensionOutput;
2111
+ exports.$getPeerDependency = $getPeerDependency;
1703
2112
  exports.$isDecoratorTextNode = $isDecoratorTextNode;
1704
2113
  exports.$isHorizontalRuleNode = $isHorizontalRuleNode;
1705
2114
  exports.AutoFocusExtension = AutoFocusExtension;
1706
2115
  exports.ClearEditorExtension = ClearEditorExtension;
2116
+ exports.ClickAfterLastBlockExtension = ClickAfterLastBlockExtension;
1707
2117
  exports.DecoratorTextExtension = DecoratorTextExtension;
1708
2118
  exports.DecoratorTextNode = DecoratorTextNode;
1709
2119
  exports.EditorStateExtension = EditorStateExtension;
@@ -1715,6 +2125,7 @@ exports.LexicalBuilder = LexicalBuilder;
1715
2125
  exports.NestedEditorExtension = NestedEditorExtension;
1716
2126
  exports.NodeSelectionExtension = NodeSelectionExtension;
1717
2127
  exports.NormalizeInlineElementsExtension = NormalizeInlineElementsExtension;
2128
+ exports.NormalizeTripleClickSelectionExtension = NormalizeTripleClickSelectionExtension;
1718
2129
  exports.SelectionAlwaysOnDisplayExtension = SelectionAlwaysOnDisplayExtension;
1719
2130
  exports.TabIndentationExtension = TabIndentationExtension;
1720
2131
  exports.applyFormatFromStyle = applyFormatFromStyle;