lexxy 0.9.1.beta → 0.9.3.beta

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.
@@ -4952,9 +4952,22 @@ class LexicalToolbarElement extends HTMLElement {
4952
4952
  }
4953
4953
 
4954
4954
  disconnectedCallback() {
4955
+ this.dispose();
4956
+ }
4957
+
4958
+ dispose() {
4955
4959
  this.#uninstallResizeObserver();
4960
+ this.#unbindButtons();
4956
4961
  this.#unbindHotkeys();
4957
4962
  this.#unbindFocusListeners();
4963
+ this.unregisterSelectionListener?.();
4964
+ this.unregisterHistoryListener?.();
4965
+
4966
+ this.editorElement = null;
4967
+ this.editor = null;
4968
+ this.selection = null;
4969
+
4970
+ this.#createEditorPromise();
4958
4971
  }
4959
4972
 
4960
4973
  attributeChangedCallback(name, oldValue, newValue) {
@@ -4998,10 +5011,12 @@ class LexicalToolbarElement extends HTMLElement {
4998
5011
  this.connectedCallback();
4999
5012
  }
5000
5013
 
5001
- #createEditorPromise() {
5014
+ async #createEditorPromise() {
5002
5015
  this.editorPromise = new Promise((resolve) => {
5003
5016
  this.resolveEditorPromise = resolve;
5004
5017
  });
5018
+
5019
+ this.editorElement = await this.editorPromise;
5005
5020
  }
5006
5021
 
5007
5022
  #installResizeObserver() {
@@ -5017,10 +5032,14 @@ class LexicalToolbarElement extends HTMLElement {
5017
5032
  }
5018
5033
 
5019
5034
  #bindButtons() {
5020
- this.addEventListener("click", this.#handleButtonClicked.bind(this));
5035
+ this.addEventListener("click", this.#handleButtonClicked);
5036
+ }
5037
+
5038
+ #unbindButtons() {
5039
+ this.removeEventListener("click", this.#handleButtonClicked);
5021
5040
  }
5022
5041
 
5023
- #handleButtonClicked(event) {
5042
+ #handleButtonClicked = (event) => {
5024
5043
  this.#handleTargetClicked(event, "[data-command]", this.#dispatchButtonCommand.bind(this));
5025
5044
  }
5026
5045
 
@@ -5080,8 +5099,8 @@ class LexicalToolbarElement extends HTMLElement {
5080
5099
  }
5081
5100
 
5082
5101
  #unbindFocusListeners() {
5083
- this.editorElement.removeEventListener("lexxy:focus", this.#handleEditorFocus);
5084
- this.editorElement.removeEventListener("lexxy:blur", this.#handleEditorBlur);
5102
+ this.editorElement?.removeEventListener("lexxy:focus", this.#handleEditorFocus);
5103
+ this.editorElement?.removeEventListener("lexxy:blur", this.#handleEditorBlur);
5085
5104
  this.removeEventListener("keydown", this.#handleKeydown);
5086
5105
  }
5087
5106
 
@@ -5105,7 +5124,7 @@ class LexicalToolbarElement extends HTMLElement {
5105
5124
  }
5106
5125
 
5107
5126
  #monitorSelectionChanges() {
5108
- this.editor.registerUpdateListener(() => {
5127
+ this.unregisterSelectionListener = this.editor.registerUpdateListener(() => {
5109
5128
  this.editor.getEditorState().read(() => {
5110
5129
  this.#updateButtonStates();
5111
5130
  this.#closeDropdowns();
@@ -5114,7 +5133,7 @@ class LexicalToolbarElement extends HTMLElement {
5114
5133
  }
5115
5134
 
5116
5135
  #monitorHistoryChanges() {
5117
- this.editor.registerUpdateListener(() => {
5136
+ this.unregisterHistoryListener = this.editor.registerUpdateListener(() => {
5118
5137
  this.#updateUndoRedoButtonStates();
5119
5138
  });
5120
5139
  }
@@ -7669,6 +7688,24 @@ function isSelectionHighlighted(selection) {
7669
7688
  }
7670
7689
  }
7671
7690
 
7691
+ function getHighlightStyles(selection) {
7692
+ if (!wr(selection)) return null
7693
+
7694
+ let styles = b$3(selection.style);
7695
+ if (!styles.color && !styles["background-color"]) {
7696
+ const anchorNode = selection.anchor.getNode();
7697
+ if (yr(anchorNode)) {
7698
+ styles = b$3(anchorNode.getStyle());
7699
+ }
7700
+ }
7701
+
7702
+ const color = styles.color || null;
7703
+ const backgroundColor = styles["background-color"] || null;
7704
+ if (!color && !backgroundColor) return null
7705
+
7706
+ return { color, backgroundColor }
7707
+ }
7708
+
7672
7709
  function hasHighlightStyles(cssOrStyles) {
7673
7710
  const styles = typeof cssOrStyles === "string" ? b$3(cssOrStyles) : cssOrStyles;
7674
7711
  return !!(styles.color || styles["background-color"])
@@ -8231,6 +8268,7 @@ const COMMANDS = [
8231
8268
  "insertOrderedList",
8232
8269
  "insertQuoteBlock",
8233
8270
  "insertCodeBlock",
8271
+ "setCodeLanguage",
8234
8272
  "insertHorizontalDivider",
8235
8273
  "uploadImage",
8236
8274
  "uploadFile",
@@ -8243,9 +8281,10 @@ const COMMANDS = [
8243
8281
 
8244
8282
  class CommandDispatcher {
8245
8283
  #selectionBeforeDrag = null
8284
+ #unregister = []
8246
8285
 
8247
8286
  static configureFor(editorElement) {
8248
- new CommandDispatcher(editorElement);
8287
+ return new CommandDispatcher(editorElement)
8249
8288
  }
8250
8289
 
8251
8290
  constructor(editorElement) {
@@ -8305,7 +8344,14 @@ class CommandDispatcher {
8305
8344
  }
8306
8345
 
8307
8346
  dispatchUnlink() {
8308
- this.#toggleLink(null);
8347
+ this.editor.update(() => {
8348
+ // Let adapters signal whether unlink should target a frozen link key.
8349
+ if (this.editorElement.adapter.unlinkFrozenNode?.()) {
8350
+ return
8351
+ }
8352
+
8353
+ Z$1(null);
8354
+ });
8309
8355
  }
8310
8356
 
8311
8357
  dispatchInsertUnorderedList() {
@@ -8396,6 +8442,17 @@ class CommandDispatcher {
8396
8442
  }
8397
8443
  }
8398
8444
 
8445
+ dispatchSetCodeLanguage(language) {
8446
+ this.editor.update(() => {
8447
+ if (!this.selection.isInsideCodeBlock) return
8448
+
8449
+ const codeNode = this.selection.nearestNodeOfType(U$1);
8450
+ if (!codeNode) return
8451
+
8452
+ codeNode.setLanguage(language);
8453
+ });
8454
+ }
8455
+
8399
8456
  dispatchInsertHorizontalDivider() {
8400
8457
  this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
8401
8458
  this.editor.focus();
@@ -8457,6 +8514,13 @@ class CommandDispatcher {
8457
8514
  this.editor.dispatchCommand(Ce$1, undefined);
8458
8515
  }
8459
8516
 
8517
+ dispose() {
8518
+ while (this.#unregister.length) {
8519
+ const unregister = this.#unregister.pop();
8520
+ unregister();
8521
+ }
8522
+ }
8523
+
8460
8524
  #registerCommands() {
8461
8525
  for (const command of COMMANDS) {
8462
8526
  const methodName = `dispatch${capitalize(command)}`;
@@ -8467,12 +8531,12 @@ class CommandDispatcher {
8467
8531
  }
8468
8532
 
8469
8533
  #registerCommandHandler(command, priority, handler) {
8470
- this.editor.registerCommand(command, handler, priority);
8534
+ this.#unregister.push(this.editor.registerCommand(command, handler, priority));
8471
8535
  }
8472
8536
 
8473
8537
  #registerKeyboardCommands() {
8474
- this.editor.registerCommand(ve$1, this.#handleArrowRightKey.bind(this), Gi);
8475
- this.editor.registerCommand(De$2, this.#handleTabKey.bind(this), Gi);
8538
+ this.#registerCommandHandler(ve$1, Gi, this.#handleArrowRightKey.bind(this));
8539
+ this.#registerCommandHandler(De$2, Gi, this.#handleTabKey.bind(this));
8476
8540
  }
8477
8541
 
8478
8542
  #handleArrowRightKey(event) {
@@ -8587,16 +8651,6 @@ class CommandDispatcher {
8587
8651
  return wr(selection) && selection.isCollapsed()
8588
8652
  }
8589
8653
 
8590
- // Not using TOGGLE_LINK_COMMAND because it's not handled unless you use React/LinkPlugin
8591
- #toggleLink(url) {
8592
- this.editor.update(() => {
8593
- if (url === null) {
8594
- Z$1(null);
8595
- } else {
8596
- Z$1(url);
8597
- }
8598
- });
8599
- }
8600
8654
  }
8601
8655
 
8602
8656
  function capitalize(str) {
@@ -8823,8 +8877,8 @@ class ActionTextAttachmentNode extends Fi {
8823
8877
  return null
8824
8878
  }
8825
8879
 
8826
- createAttachmentFigure() {
8827
- const figure = createAttachmentFigure(this.contentType, this.isPreviewableAttachment, this.fileName);
8880
+ createAttachmentFigure(previewable = this.isPreviewableAttachment) {
8881
+ const figure = createAttachmentFigure(this.contentType, previewable, this.fileName);
8828
8882
  figure.draggable = true;
8829
8883
  figure.dataset.lexicalNodeKey = this.__key;
8830
8884
 
@@ -8958,6 +9012,8 @@ function $isActionTextAttachmentNode(node) {
8958
9012
  }
8959
9013
 
8960
9014
  class Selection {
9015
+ #unregister = []
9016
+
8961
9017
  constructor(editorElement) {
8962
9018
  this.editorElement = editorElement;
8963
9019
  this.editorContentElement = editorElement.editorContentElement;
@@ -9214,6 +9270,18 @@ class Selection {
9214
9270
  return this.#findPreviousSiblingUp(anchorNode)
9215
9271
  }
9216
9272
 
9273
+ dispose() {
9274
+ this.editorElement = null;
9275
+ this.editorContentElement = null;
9276
+ this.editor = null;
9277
+ this.previouslySelectedKeys = null;
9278
+
9279
+ while (this.#unregister.length) {
9280
+ const unregister = this.#unregister.pop();
9281
+ unregister();
9282
+ }
9283
+ }
9284
+
9217
9285
  // When all inline code text is deleted, Lexical's selection retains the stale
9218
9286
  // code format flag. Verify the flag is backed by actual code-formatted content:
9219
9287
  // a code block ancestor or a text node that carries the code format.
@@ -9229,7 +9297,7 @@ class Selection {
9229
9297
  // detects that stale state and clears it so newly typed text won't be
9230
9298
  // code-formatted.
9231
9299
  #clearStaleInlineCodeFormat() {
9232
- this.editor.registerUpdateListener(({ editorState, tags }) => {
9300
+ this.#unregister.push(this.editor.registerUpdateListener(({ editorState, tags }) => {
9233
9301
  if (tags.has("history-merge") || tags.has("skip-dom-selection")) return
9234
9302
 
9235
9303
  let isStale = false;
@@ -9258,7 +9326,7 @@ class Selection {
9258
9326
  });
9259
9327
  }, 0);
9260
9328
  }
9261
- });
9329
+ }));
9262
9330
  }
9263
9331
 
9264
9332
  get #currentlySelectedKeys() {
@@ -9277,29 +9345,32 @@ class Selection {
9277
9345
  }
9278
9346
 
9279
9347
  #processSelectionChangeCommands() {
9280
- this.editor.registerCommand(ke$3, this.#selectPreviousNode.bind(this), Hi);
9281
- this.editor.registerCommand(ve$1, this.#selectNextNode.bind(this), Hi);
9282
- this.editor.registerCommand(be$2, this.#selectPreviousTopLevelNode.bind(this), Hi);
9283
- this.editor.registerCommand(we$1, this.#selectNextTopLevelNode.bind(this), Hi);
9348
+ this.#unregister.push(ec(
9349
+ this.editor.registerCommand(ke$3, this.#selectPreviousNode.bind(this), Hi),
9350
+ this.editor.registerCommand(ve$1, this.#selectNextNode.bind(this), Hi),
9351
+ this.editor.registerCommand(be$2, this.#selectPreviousTopLevelNode.bind(this), Hi),
9352
+ this.editor.registerCommand(we$1, this.#selectNextTopLevelNode.bind(this), Hi),
9284
9353
 
9285
- this.editor.registerCommand(ue$2, this.#selectDecoratorNodeBeforeDeletion.bind(this), Hi);
9354
+ this.editor.registerCommand(ue$2, this.#selectDecoratorNodeBeforeDeletion.bind(this), Hi),
9286
9355
 
9287
- this.editor.registerCommand(re$2, () => {
9288
- this.current = $r();
9289
- }, Hi);
9356
+ this.editor.registerCommand(re$2, () => {
9357
+ this.current = $r();
9358
+ }, Hi)
9359
+ ));
9290
9360
  }
9291
9361
 
9292
9362
  #listenForNodeSelections() {
9293
- this.editor.registerCommand(oe$4, ({ target }) => {
9363
+ this.#unregister.push(this.editor.registerCommand(oe$4, ({ target }) => {
9294
9364
  if (!As(target)) return false
9295
9365
 
9296
9366
  const targetNode = Do(target);
9297
9367
  return Li(targetNode) && this.#selectInLexical(targetNode)
9298
- }, Hi);
9368
+ }, Hi));
9299
9369
 
9300
- this.editor.getRootElement().addEventListener("lexxy:internal:move-to-next-line", (event) => {
9301
- this.#selectOrAppendNextLine();
9302
- });
9370
+ const moveNextLineHandler = () => this.#selectOrAppendNextLine();
9371
+ const rootElement = this.editor.getRootElement();
9372
+ rootElement.addEventListener("lexxy:internal:move-to-next-line", moveNextLineHandler);
9373
+ this.#unregister.push(() => rootElement.removeEventListener("lexxy:internal:move-to-next-line", moveNextLineHandler));
9303
9374
  }
9304
9375
 
9305
9376
  #containEditorFocus() {
@@ -9892,9 +9963,12 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
9892
9963
  // node is reloaded from saved state such as from history.
9893
9964
  this.#startUploadIfNeeded();
9894
9965
 
9895
- const figure = this.createAttachmentFigure();
9966
+ // Bridge-managed uploads (uploadUrl is null) don't have file data to show
9967
+ // an image preview, so always show the file icon during upload.
9968
+ const canPreviewFile = this.isPreviewableAttachment && this.uploadUrl != null;
9969
+ const figure = this.createAttachmentFigure(canPreviewFile);
9896
9970
 
9897
- if (this.isPreviewableAttachment) {
9971
+ if (canPreviewFile) {
9898
9972
  const img = figure.appendChild(this.#createDOMForImage());
9899
9973
 
9900
9974
  // load file locally to set dimensions and prevent vertical shifting
@@ -9994,6 +10068,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
9994
10068
 
9995
10069
  async #startUploadIfNeeded() {
9996
10070
  if (this.#uploadStarted) return
10071
+ if (!this.uploadUrl) return // Bridge-managed upload — skip DirectUpload
9997
10072
 
9998
10073
  this.#setUploadStarted();
9999
10074
 
@@ -10010,7 +10085,9 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
10010
10085
  this.#handleUploadError(error);
10011
10086
  } else {
10012
10087
  this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
10013
- this.#showUploadedAttachment(blob);
10088
+ this.editor.update(() => {
10089
+ this.showUploadedAttachment(blob);
10090
+ }, { tag: this.#backgroundUpdateTags });
10014
10091
  }
10015
10092
  });
10016
10093
  }
@@ -10054,17 +10131,15 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
10054
10131
  }, { tag: this.#backgroundUpdateTags });
10055
10132
  }
10056
10133
 
10057
- #showUploadedAttachment(blob) {
10058
- const editorHasFocus = this.#editorHasFocus;
10134
+ showUploadedAttachment(blob) {
10135
+ const replacementNode = this.#toActionTextAttachmentNodeWith(blob);
10136
+ this.replace(replacementNode);
10059
10137
 
10060
- this.editor.update(() => {
10061
- const replacementNode = this.#toActionTextAttachmentNodeWith(blob);
10062
- this.replace(replacementNode);
10138
+ if (xs(replacementNode.getParent())) {
10139
+ replacementNode.selectNext();
10140
+ }
10063
10141
 
10064
- if (editorHasFocus && xs(replacementNode.getParent())) {
10065
- replacementNode.selectNext();
10066
- }
10067
- }, { tag: this.#backgroundUpdateTags });
10142
+ return replacementNode.getKey()
10068
10143
  }
10069
10144
 
10070
10145
  // Upload lifecycle methods (progress, completion, errors) run asynchronously and may
@@ -10434,7 +10509,11 @@ class Contents {
10434
10509
  constructor(editorElement) {
10435
10510
  this.editorElement = editorElement;
10436
10511
  this.editor = editorElement.editor;
10512
+ }
10437
10513
 
10514
+ dispose() {
10515
+ this.editorElement = null;
10516
+ this.editor = null;
10438
10517
  }
10439
10518
 
10440
10519
  insertHtml(html, { tag } = {}) {
@@ -10662,6 +10741,53 @@ class Contents {
10662
10741
  });
10663
10742
  }
10664
10743
 
10744
+ insertPendingAttachment(file) {
10745
+ if (!this.editorElement.supportsAttachments) return null
10746
+
10747
+ let nodeKey = null;
10748
+ this.editor.update(() => {
10749
+ const uploadNode = new ActionTextAttachmentUploadNode({
10750
+ file,
10751
+ uploadUrl: null,
10752
+ blobUrlTemplate: this.editorElement.blobUrlTemplate,
10753
+ editor: this.editor
10754
+ });
10755
+ this.insertAtCursor(uploadNode);
10756
+ nodeKey = uploadNode.getKey();
10757
+ }, { tag: Wn });
10758
+
10759
+ if (!nodeKey) return null
10760
+
10761
+ const editor = this.editor;
10762
+ return {
10763
+ setAttributes(blob) {
10764
+ editor.update(() => {
10765
+ const node = Mo(nodeKey);
10766
+ if (!(node instanceof ActionTextAttachmentUploadNode)) return
10767
+
10768
+ const replacementNodeKey = node.showUploadedAttachment(blob);
10769
+ if (replacementNodeKey) {
10770
+ nodeKey = replacementNodeKey;
10771
+ }
10772
+ }, { tag: Wn });
10773
+ },
10774
+ setUploadProgress(progress) {
10775
+ editor.update(() => {
10776
+ const node = Mo(nodeKey);
10777
+ if (!(node instanceof ActionTextAttachmentUploadNode)) return
10778
+
10779
+ node.getWritable().progress = progress;
10780
+ }, { tag: Wn });
10781
+ },
10782
+ remove() {
10783
+ editor.update(() => {
10784
+ const node = Mo(nodeKey);
10785
+ if (node) node.remove();
10786
+ });
10787
+ }
10788
+ }
10789
+ }
10790
+
10665
10791
  replaceNodeWithHTML(nodeKey, html, options = {}) {
10666
10792
  this.editor.update(() => {
10667
10793
  const node = Mo(nodeKey);
@@ -11218,6 +11344,18 @@ class Extensions {
11218
11344
  }
11219
11345
  }
11220
11346
 
11347
+ class BrowserAdapter {
11348
+ frozenLinkKey = null
11349
+
11350
+ dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {}
11351
+ dispatchEditorInitialized(detail) {}
11352
+ freeze() {}
11353
+ thaw() {}
11354
+ unlinkFrozenNode() {
11355
+ return false
11356
+ }
11357
+ }
11358
+
11221
11359
  // Custom TextNode exportDOM that avoids redundant bold/italic wrapping.
11222
11360
  //
11223
11361
  // Lexical's built-in TextNode.exportDOM() calls createDOM() which produces semantic tags
@@ -11537,11 +11675,12 @@ class TablesExtension extends LexxyExtension {
11537
11675
  ze$1
11538
11676
  ],
11539
11677
  register(editor) {
11678
+ Cn(editor);
11679
+
11540
11680
  return ec(
11541
11681
  // Register Lexical table plugins
11542
11682
  Kn(editor),
11543
11683
  An(editor, true),
11544
- Cn(editor),
11545
11684
 
11546
11685
  // Bug fix: Prevent hardcoded background color (Lexical #8089)
11547
11686
  editor.registerNodeTransform(Ke$1, (node) => {
@@ -12268,6 +12407,8 @@ class LexicalEditorElement extends HTMLElement {
12268
12407
 
12269
12408
  #initialValue = ""
12270
12409
  #validationTextArea = document.createElement("textarea")
12410
+ #editorInitializedRafId = null
12411
+ #disposables = []
12271
12412
 
12272
12413
  constructor() {
12273
12414
  super();
@@ -12276,20 +12417,28 @@ class LexicalEditorElement extends HTMLElement {
12276
12417
  }
12277
12418
 
12278
12419
  connectedCallback() {
12279
- this.id ??= generateDomId("lexxy-editor");
12420
+ this.id ||= generateDomId("lexxy-editor");
12280
12421
  this.config = new EditorConfiguration(this);
12281
12422
  this.extensions = new Extensions(this);
12282
12423
 
12283
12424
  this.editor = this.#createEditor();
12425
+ this.#disposables.push(this.editor);
12284
12426
 
12285
12427
  this.contents = new Contents(this);
12428
+ this.#disposables.push(this.contents);
12429
+
12286
12430
  this.selection = new Selection(this);
12431
+ this.#disposables.push(this.selection);
12432
+
12287
12433
  this.clipboard = new Clipboard(this);
12434
+ this.adapter = new BrowserAdapter();
12435
+
12436
+ const commandDispatcher = CommandDispatcher.configureFor(this);
12437
+ this.#disposables.push(commandDispatcher);
12288
12438
 
12289
- CommandDispatcher.configureFor(this);
12290
12439
  this.#initialize();
12291
12440
 
12292
- requestAnimationFrame(() => dispatch(this, "lexxy:initialize"));
12441
+ this.#scheduleEditorInitializedDispatch();
12293
12442
  this.toggleAttribute("connected", true);
12294
12443
 
12295
12444
  this.#handleAutofocus();
@@ -12298,6 +12447,7 @@ class LexicalEditorElement extends HTMLElement {
12298
12447
  }
12299
12448
 
12300
12449
  disconnectedCallback() {
12450
+ this.#cancelEditorInitializedDispatch();
12301
12451
  this.valueBeforeDisconnect = this.value;
12302
12452
  this.#reset(); // Prevent hangs with Safari when morphing
12303
12453
  }
@@ -12339,7 +12489,7 @@ class LexicalEditorElement extends HTMLElement {
12339
12489
  get toolbarElement() {
12340
12490
  if (!this.#hasToolbar) return null
12341
12491
 
12342
- this.toolbar = this.toolbar || this.#findOrCreateDefaultToolbar();
12492
+ this.toolbar ??= this.#findOrCreateDefaultToolbar();
12343
12493
  return this.toolbar
12344
12494
  }
12345
12495
 
@@ -12394,6 +12544,32 @@ class LexicalEditorElement extends HTMLElement {
12394
12544
  return this.config.get("richText")
12395
12545
  }
12396
12546
 
12547
+ registerAdapter(adapter) {
12548
+ this.adapter = adapter;
12549
+
12550
+ if (!this.editor) return
12551
+
12552
+ this.#cancelEditorInitializedDispatch();
12553
+ this.#dispatchEditorInitialized();
12554
+ this.#dispatchAttributesChange();
12555
+ }
12556
+
12557
+ freezeSelection() {
12558
+ this.adapter.freeze();
12559
+ }
12560
+
12561
+ thawSelection() {
12562
+ this.adapter.thaw();
12563
+ }
12564
+
12565
+ dispatchAttributesChange() {
12566
+ this.#dispatchAttributesChange();
12567
+ }
12568
+
12569
+ dispatchEditorInitialized() {
12570
+ this.#dispatchEditorInitialized();
12571
+ }
12572
+
12397
12573
  // TODO: Deprecate `single-line` attribute
12398
12574
  get isSingleLineMode() {
12399
12575
  return this.hasAttribute("single-line")
@@ -12475,6 +12651,7 @@ class LexicalEditorElement extends HTMLElement {
12475
12651
 
12476
12652
  #createEditor() {
12477
12653
  this.editorContentElement ||= this.#createEditorContentElement();
12654
+ this.appendChild(this.editorContentElement);
12478
12655
 
12479
12656
  const editor = Qt$2({
12480
12657
  name: "lexxy/core",
@@ -12517,6 +12694,7 @@ class LexicalEditorElement extends HTMLElement {
12517
12694
  const editorContentElement = createElement("div", {
12518
12695
  classList: "lexxy-editor__content",
12519
12696
  contenteditable: true,
12697
+ autocapitalize: "none",
12520
12698
  role: "textbox",
12521
12699
  "aria-multiline": true,
12522
12700
  "aria-label": this.#labelText,
@@ -12524,7 +12702,6 @@ class LexicalEditorElement extends HTMLElement {
12524
12702
  });
12525
12703
  editorContentElement.id = `${this.id}-content`;
12526
12704
  this.#ariaAttributes.forEach(attribute => editorContentElement.setAttribute(attribute.name, attribute.value));
12527
- this.appendChild(editorContentElement);
12528
12705
 
12529
12706
  if (this.getAttribute("tabindex")) {
12530
12707
  editorContentElement.setAttribute("tabindex", this.getAttribute("tabindex"));
@@ -12579,6 +12756,7 @@ class LexicalEditorElement extends HTMLElement {
12579
12756
  this.#internalFormValue = this.value;
12580
12757
  this.#toggleEmptyStatus();
12581
12758
  this.#setValidity();
12759
+ this.#dispatchAttributesChange();
12582
12760
  }));
12583
12761
  }
12584
12762
 
@@ -12600,36 +12778,48 @@ class LexicalEditorElement extends HTMLElement {
12600
12778
  }
12601
12779
 
12602
12780
  #registerComponents() {
12781
+ const registered = [];
12782
+
12603
12783
  if (this.supportsRichText) {
12604
- Jt$2(this.editor);
12605
- Le$2(this.editor);
12784
+ registered.push(
12785
+ Jt$2(this.editor),
12786
+ Le$2(this.editor)
12787
+ );
12606
12788
  this.#registerTableComponents();
12607
12789
  this.#registerCodeHiglightingComponents();
12608
12790
  if (this.supportsMarkdown) {
12609
- Yt$1(this.editor, Jt$1);
12610
- registerMarkdownLeadingTagHandler(this.editor, Jt$1);
12791
+ registered.push(
12792
+ Yt$1(this.editor, Jt$1),
12793
+ registerMarkdownLeadingTagHandler(this.editor, Jt$1)
12794
+ );
12611
12795
  }
12612
12796
  } else {
12613
- z$1(this.editor);
12797
+ registered.push(z$1(this.editor));
12614
12798
  }
12615
12799
  this.historyState = H();
12616
- E$1(this.editor, this.historyState, 20);
12800
+ registered.push(E$1(this.editor, this.historyState, 20));
12801
+
12802
+ this.#addUnregisterHandler(ec(...registered));
12617
12803
  }
12618
12804
 
12619
12805
  #registerTableComponents() {
12620
- this.tableTools = createElement("lexxy-table-tools");
12621
- this.append(this.tableTools);
12806
+ let tableTools = this.querySelector("lexxy-table-tools");
12807
+ tableTools ??= createElement("lexxy-table-tools");
12808
+ this.append(tableTools);
12809
+ this.#disposables.push(tableTools);
12622
12810
  }
12623
12811
 
12624
12812
  #registerCodeHiglightingComponents() {
12625
12813
  zt$2(this.editor);
12626
- this.codeLanguagePicker = createElement("lexxy-code-language-picker");
12627
- this.append(this.codeLanguagePicker);
12814
+ let codeLanguagePicker = this.querySelector("lexxy-code-language-picker");
12815
+ codeLanguagePicker ??= createElement("lexxy-code-language-picker");
12816
+ this.append(codeLanguagePicker);
12817
+ this.#disposables.push(codeLanguagePicker);
12628
12818
  }
12629
12819
 
12630
12820
  #handleEnter() {
12631
12821
  // We can't prevent these externally using regular keydown because Lexical handles it first.
12632
- this.editor.registerCommand(
12822
+ this.#addUnregisterHandler(this.editor.registerCommand(
12633
12823
  Ee$2,
12634
12824
  (event) => {
12635
12825
  // Prevent CTRL+ENTER
@@ -12647,16 +12837,22 @@ class LexicalEditorElement extends HTMLElement {
12647
12837
  return false
12648
12838
  },
12649
12839
  Gi
12650
- );
12840
+ ));
12651
12841
  }
12652
12842
 
12653
12843
  #registerFocusEvents() {
12654
12844
  this.addEventListener("focusin", this.#handleFocusIn);
12655
12845
  this.addEventListener("focusout", this.#handleFocusOut);
12846
+
12847
+ this.#addUnregisterHandler(() => {
12848
+ this.removeEventListener("focusin", this.#handleFocusIn);
12849
+ this.removeEventListener("focusout", this.#handleFocusOut);
12850
+ });
12656
12851
  }
12657
12852
 
12658
12853
  #handleFocusIn(event) {
12659
12854
  if (this.#elementInEditorOrToolbar(event.target) && !this.currentlyFocused) {
12855
+ this.#dispatchAttributesChange();
12660
12856
  dispatch(this, "lexxy:focus");
12661
12857
  this.currentlyFocused = true;
12662
12858
  }
@@ -12695,6 +12891,10 @@ class LexicalEditorElement extends HTMLElement {
12695
12891
  #attachToolbar() {
12696
12892
  if (this.#hasToolbar) {
12697
12893
  this.toolbarElement.setEditor(this);
12894
+ if (typeof this.toolbarElement.dispose === "function") {
12895
+ this.#disposables.push(this.toolbarElement);
12896
+ }
12897
+
12698
12898
  this.extensions.initializeToolbars();
12699
12899
  }
12700
12900
  }
@@ -12704,7 +12904,7 @@ class LexicalEditorElement extends HTMLElement {
12704
12904
  if (typeof toolbarConfig === "string") {
12705
12905
  return document.getElementById(toolbarConfig)
12706
12906
  } else {
12707
- return this.#createDefaultToolbar()
12907
+ return this.querySelector("lexxy-toolbar") ?? this.#createDefaultToolbar()
12708
12908
  }
12709
12909
  }
12710
12910
 
@@ -12733,35 +12933,129 @@ class LexicalEditorElement extends HTMLElement {
12733
12933
  }
12734
12934
  }
12735
12935
 
12736
- #reset() {
12737
- this.#unregisterHandlers();
12936
+ #dispatchAttributesChange() {
12937
+ let attributes = null;
12938
+ let linkHref = null;
12939
+ let highlight = null;
12940
+ let headingTag = null;
12738
12941
 
12739
- if (this.editorContentElement) {
12740
- this.editorContentElement.remove();
12741
- this.editorContentElement = null;
12742
- }
12942
+ this.editor.getEditorState().read(() => {
12943
+ const selection = $r();
12944
+ if (!wr(selection)) return
12743
12945
 
12744
- this.contents = null;
12745
- this.editor = null;
12946
+ const format = this.selection.getFormat();
12947
+ if (Object.keys(format).length === 0) return
12746
12948
 
12747
- if (this.toolbar) {
12748
- if (!this.getAttribute("toolbar")) { this.toolbar.remove(); }
12749
- this.toolbar = null;
12750
- }
12949
+ const anchorNode = selection.anchor.getNode();
12950
+ const linkNode = vt$4(anchorNode, E$3);
12951
+
12952
+ attributes = {
12953
+ bold: { active: format.isBold, enabled: true },
12954
+ italic: { active: format.isItalic, enabled: true },
12955
+ strikethrough: { active: format.isStrikethrough, enabled: true },
12956
+ code: { active: format.isInCode, enabled: true },
12957
+ highlight: { active: format.isHighlight, enabled: true },
12958
+ link: { active: format.isInLink, enabled: true },
12959
+ quote: { active: format.isInQuote, enabled: true },
12960
+ heading: { active: format.isInHeading, enabled: true },
12961
+ "unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
12962
+ "ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
12963
+ undo: { active: false, enabled: this.historyState?.undoStack.length > 0 },
12964
+ redo: { active: false, enabled: this.historyState?.redoStack.length > 0 }
12965
+ };
12751
12966
 
12752
- if (this.codeLanguagePicker) {
12753
- this.codeLanguagePicker.remove();
12754
- this.codeLanguagePicker = null;
12755
- }
12967
+ linkHref = linkNode ? linkNode.getURL() : null;
12968
+ highlight = format.isHighlight ? getHighlightStyles(selection) : null;
12969
+ headingTag = format.headingTag ?? null;
12970
+ });
12756
12971
 
12757
- if (this.tableHandler) {
12758
- this.tableHandler.remove();
12759
- this.tableHandler = null;
12972
+ if (attributes) {
12973
+ this.adapter.dispatchAttributesChange(attributes, linkHref, highlight, headingTag);
12760
12974
  }
12975
+ }
12761
12976
 
12762
- this.selection = null;
12977
+ #dispatchEditorInitialized() {
12978
+ if (!this.adapter) return
12979
+
12980
+ this.adapter.dispatchEditorInitialized({
12981
+ highlightColors: this.#resolvedHighlightColors,
12982
+ headingFormats: this.#supportedHeadingFormats
12983
+ });
12984
+ }
12985
+
12986
+ #scheduleEditorInitializedDispatch() {
12987
+ this.#cancelEditorInitializedDispatch();
12988
+ this.#editorInitializedRafId = requestAnimationFrame(() => {
12989
+ this.#editorInitializedRafId = null;
12990
+ if (!this.isConnected || !this.adapter) return
12991
+
12992
+ dispatch(this, "lexxy:initialize");
12993
+ this.#dispatchEditorInitialized();
12994
+ });
12995
+ }
12996
+
12997
+ #cancelEditorInitializedDispatch() {
12998
+ if (this.#editorInitializedRafId == null) return
12999
+
13000
+ cancelAnimationFrame(this.#editorInitializedRafId);
13001
+ this.#editorInitializedRafId = null;
13002
+ }
13003
+
13004
+ get #resolvedHighlightColors() {
13005
+ const buttons = this.config.get("highlight.buttons");
13006
+ if (!buttons) return null
13007
+
13008
+ const colors = this.#resolveColors("color", buttons.color || []);
13009
+ const backgroundColors = this.#resolveColors("background-color", buttons["background-color"] || []);
13010
+ return { colors, backgroundColors }
13011
+ }
13012
+
13013
+ get #supportedHeadingFormats() {
13014
+ if (!this.supportsRichText) return []
13015
+
13016
+ return [
13017
+ { label: "Normal", command: "setFormatParagraph", tag: null },
13018
+ { label: "Large heading", command: "setFormatHeadingLarge", tag: "h2" },
13019
+ { label: "Medium heading", command: "setFormatHeadingMedium", tag: "h3" },
13020
+ { label: "Small heading", command: "setFormatHeadingSmall", tag: "h4" },
13021
+ ]
13022
+ }
13023
+
13024
+ #resolveColors(property, cssValues) {
13025
+ const resolver = document.createElement("span");
13026
+ resolver.style.display = "none";
13027
+ this.appendChild(resolver);
13028
+
13029
+ const resolved = cssValues.map(cssValue => {
13030
+ resolver.style.setProperty(property, cssValue);
13031
+ const value = window.getComputedStyle(resolver).getPropertyValue(property);
13032
+ resolver.style.removeProperty(property);
13033
+ return { name: cssValue, value }
13034
+ });
13035
+
13036
+ resolver.remove();
13037
+ return resolved
13038
+ }
13039
+
13040
+ #reset() {
13041
+ this.#cancelEditorInitializedDispatch();
13042
+ this.#dispose();
13043
+ this.editorContentElement?.remove();
13044
+ this.editorContentElement = null;
12763
13045
 
13046
+ // Prevents issues with turbo morphing receiving an empty <lexxy-editor> which wipes
13047
+ // out the DOM for the tools, and the old toolbar reference will cause issues
13048
+ this.toolbar = null;
13049
+ }
13050
+
13051
+ #dispose() {
13052
+ this.#unregisterHandlers();
13053
+ this.adapter = null;
12764
13054
  document.removeEventListener("turbo:before-cache", this.#handleTurboBeforeCache);
13055
+
13056
+ while (this.#disposables.length) {
13057
+ this.#disposables.pop().dispose();
13058
+ }
12765
13059
  }
12766
13060
 
12767
13061
  #reconnect() {
@@ -12802,14 +13096,15 @@ class ToolbarDropdown extends HTMLElement {
12802
13096
  connectedCallback() {
12803
13097
  this.container = this.closest("details");
12804
13098
 
12805
- this.container.addEventListener("toggle", this.#handleToggle.bind(this));
12806
- this.container.addEventListener("keydown", this.#handleKeyDown.bind(this));
13099
+ this.container.addEventListener("toggle", this.#handleToggle);
13100
+ this.container.addEventListener("keydown", this.#handleKeyDown);
12807
13101
 
12808
13102
  this.#onToolbarEditor(this.initialize.bind(this));
12809
13103
  }
12810
13104
 
12811
13105
  disconnectedCallback() {
12812
- this.container.removeEventListener("keydown", this.#handleKeyDown.bind(this));
13106
+ this.container?.removeEventListener("toggle", this.#handleToggle);
13107
+ this.container?.removeEventListener("keydown", this.#handleKeyDown);
12813
13108
  }
12814
13109
 
12815
13110
  get toolbar() {
@@ -12834,11 +13129,11 @@ class ToolbarDropdown extends HTMLElement {
12834
13129
  }
12835
13130
 
12836
13131
  async #onToolbarEditor(callback) {
12837
- await this.toolbar.editorConnected;
13132
+ await this.toolbar.editorElement;
12838
13133
  callback();
12839
13134
  }
12840
13135
 
12841
- #handleToggle() {
13136
+ #handleToggle = () => {
12842
13137
  if (this.container.open) {
12843
13138
  this.#handleOpen();
12844
13139
  }
@@ -12849,7 +13144,7 @@ class ToolbarDropdown extends HTMLElement {
12849
13144
  this.#resetTabIndexValues();
12850
13145
  }
12851
13146
 
12852
- #handleKeyDown(event) {
13147
+ #handleKeyDown = (event) => {
12853
13148
  if (event.key === "Escape") {
12854
13149
  event.stopPropagation();
12855
13150
  this.close();
@@ -12877,27 +13172,30 @@ class LinkDropdown extends ToolbarDropdown {
12877
13172
  super.connectedCallback();
12878
13173
  this.input = this.querySelector("input");
12879
13174
 
12880
- this.#registerHandlers();
13175
+ this.container.addEventListener("toggle", this.#handleToggle);
13176
+ this.addEventListener("submit", this.#handleSubmit);
13177
+ this.querySelector("[value='unlink']").addEventListener("click", this.#handleUnlink);
12881
13178
  }
12882
13179
 
12883
- #registerHandlers() {
12884
- this.container.addEventListener("toggle", this.#handleToggle.bind(this));
12885
- this.addEventListener("submit", this.#handleSubmit.bind(this));
12886
- this.querySelector("[value='unlink']").addEventListener("click", this.#handleUnlink.bind(this));
13180
+ disconnectedCallback() {
13181
+ this.container?.removeEventListener("toggle", this.#handleToggle);
13182
+ this.removeEventListener("submit", this.#handleSubmit);
13183
+ this.querySelector("[value='unlink']")?.removeEventListener("click", this.#handleUnlink);
13184
+ super.disconnectedCallback();
12887
13185
  }
12888
13186
 
12889
- #handleToggle({ newState }) {
13187
+ #handleToggle = ({ newState }) => {
12890
13188
  this.input.value = this.#selectedLinkUrl;
12891
13189
  this.input.required = newState === "open";
12892
13190
  }
12893
13191
 
12894
- #handleSubmit(event) {
13192
+ #handleSubmit = (event) => {
12895
13193
  const command = event.submitter?.value;
12896
13194
  this.editor.dispatchCommand(command, this.input.value);
12897
13195
  this.close();
12898
13196
  }
12899
13197
 
12900
- #handleUnlink() {
13198
+ #handleUnlink = () => {
12901
13199
  this.editor.dispatchCommand("unlink");
12902
13200
  this.close();
12903
13201
  }
@@ -12932,26 +13230,35 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
12932
13230
  const NO_STYLE = Symbol("no_style");
12933
13231
 
12934
13232
  class HighlightDropdown extends ToolbarDropdown {
12935
- connectedCallback() {
12936
- super.connectedCallback();
12937
- this.#registerToggleHandler();
12938
- }
12939
-
12940
13233
  initialize() {
12941
13234
  this.#setUpButtons();
12942
13235
  this.#registerButtonHandlers();
12943
13236
  }
12944
13237
 
12945
- #registerToggleHandler() {
12946
- this.container.addEventListener("toggle", this.#handleToggle.bind(this));
13238
+ connectedCallback() {
13239
+ super.connectedCallback();
13240
+ this.container.addEventListener("toggle", this.#handleToggle);
13241
+ }
13242
+
13243
+ disconnectedCallback() {
13244
+ this.container?.removeEventListener("toggle", this.#handleToggle);
13245
+ this.#removeButtonHandlers();
13246
+ super.disconnectedCallback();
12947
13247
  }
12948
13248
 
12949
13249
  #registerButtonHandlers() {
12950
- this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick.bind(this)));
12951
- this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick.bind(this));
13250
+ this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick));
13251
+ this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick);
13252
+ }
13253
+
13254
+ #removeButtonHandlers() {
13255
+ this.#colorButtons.forEach(button => button.removeEventListener("click", this.#handleColorButtonClick));
13256
+ this.querySelector(REMOVE_HIGHLIGHT_SELECTOR)?.removeEventListener("click", this.#handleRemoveHighlightClick);
12952
13257
  }
12953
13258
 
12954
13259
  #setUpButtons() {
13260
+ this.#buttonContainer.innerHTML = "";
13261
+
12955
13262
  const colorGroups = this.editorElement.config.get("highlight.buttons");
12956
13263
 
12957
13264
  this.#populateButtonGroup("color", colorGroups.color);
@@ -12977,7 +13284,7 @@ class HighlightDropdown extends ToolbarDropdown {
12977
13284
  return button
12978
13285
  }
12979
13286
 
12980
- #handleToggle({ newState }) {
13287
+ #handleToggle = ({ newState }) => {
12981
13288
  if (newState === "open") {
12982
13289
  this.editor.getEditorState().read(() => {
12983
13290
  this.#updateColorButtonStates($r());
@@ -12985,7 +13292,7 @@ class HighlightDropdown extends ToolbarDropdown {
12985
13292
  }
12986
13293
  }
12987
13294
 
12988
- #handleColorButtonClick(event) {
13295
+ #handleColorButtonClick = (event) => {
12989
13296
  event.preventDefault();
12990
13297
 
12991
13298
  const button = event.target.closest(APPLY_HIGHLIGHT_SELECTOR);
@@ -12998,7 +13305,7 @@ class HighlightDropdown extends ToolbarDropdown {
12998
13305
  this.close();
12999
13306
  }
13000
13307
 
13001
- #handleRemoveHighlightClick(event) {
13308
+ #handleRemoveHighlightClick = (event) => {
13002
13309
  event.preventDefault();
13003
13310
 
13004
13311
  this.editor.dispatchCommand("removeHighlight");
@@ -13649,10 +13956,13 @@ class LexicalPromptElement extends HTMLElement {
13649
13956
  }
13650
13957
 
13651
13958
  class CodeLanguagePicker extends HTMLElement {
13959
+ #abortController = null
13960
+
13652
13961
  connectedCallback() {
13653
13962
  this.editorElement = this.closest("lexxy-editor");
13654
13963
  this.editor = this.editorElement.editor;
13655
13964
  this.classList.add("lexxy-floating-controls");
13965
+ this.#abortController = new AbortController();
13656
13966
 
13657
13967
  this.#attachLanguagePicker();
13658
13968
  this.#hide();
@@ -13660,21 +13970,37 @@ class CodeLanguagePicker extends HTMLElement {
13660
13970
  }
13661
13971
 
13662
13972
  disconnectedCallback() {
13973
+ this.dispose();
13974
+ }
13975
+
13976
+ dispose() {
13977
+ this.#abortController?.abort();
13978
+ this.#abortController = null;
13663
13979
  this.unregisterUpdateListener?.();
13664
13980
  this.unregisterUpdateListener = null;
13665
13981
  }
13666
13982
 
13667
13983
  #attachLanguagePicker() {
13668
- this.languagePickerElement = this.#createLanguagePicker();
13984
+ this.languagePickerElement = this.#findLanguagePicker() ?? this.#createLanguagePicker();
13985
+
13986
+ const signal = this.#abortController.signal;
13669
13987
 
13670
13988
  this.languagePickerElement.addEventListener("change", () => {
13671
13989
  this.#updateCodeBlockLanguage(this.languagePickerElement.value);
13672
- });
13990
+ }, { signal });
13991
+
13992
+ this.languagePickerElement.addEventListener("mousedown", (event) => {
13993
+ this.#dispatchOpenEvent(event);
13994
+ }, { signal });
13673
13995
 
13674
13996
  this.languagePickerElement.setAttribute("nonce", getNonce());
13675
13997
  this.appendChild(this.languagePickerElement);
13676
13998
  }
13677
13999
 
14000
+ #findLanguagePicker() {
14001
+ return this.querySelector("select")
14002
+ }
14003
+
13678
14004
  #createLanguagePicker() {
13679
14005
  const selectElement = createElement("select", { className: "lexxy-code-language-picker", "aria-label": "Pick a language…", name: "lexxy-code-language" });
13680
14006
 
@@ -13707,6 +14033,21 @@ class CodeLanguagePicker extends HTMLElement {
13707
14033
  return Object.fromEntries([ plainEntry, ...sortedEntries ])
13708
14034
  }
13709
14035
 
14036
+ #dispatchOpenEvent(event) {
14037
+ const handled = !dispatch(this.editorElement, "lexxy:code-language-picker-open", {
14038
+ languages: this.#bridgeLanguages,
14039
+ currentLanguage: this.languagePickerElement.value
14040
+ }, true);
14041
+
14042
+ if (handled) {
14043
+ event.preventDefault();
14044
+ }
14045
+ }
14046
+
14047
+ get #bridgeLanguages() {
14048
+ return Object.entries(this.#languages).map(([ key, name ]) => ({ key, name }))
14049
+ }
14050
+
13710
14051
  #updateCodeBlockLanguage(language) {
13711
14052
  this.editor.update(() => {
13712
14053
  const codeNode = this.#getCurrentCodeNode();
@@ -14245,6 +14586,10 @@ class TableTools extends HTMLElement {
14245
14586
  }
14246
14587
 
14247
14588
  disconnectedCallback() {
14589
+ this.dispose();
14590
+ }
14591
+
14592
+ dispose() {
14248
14593
  this.#unregisterKeyboardShortcuts();
14249
14594
 
14250
14595
  this.unregisterUpdateListener?.();
@@ -14269,6 +14614,8 @@ class TableTools extends HTMLElement {
14269
14614
  }
14270
14615
 
14271
14616
  #setUpButtons() {
14617
+ this.innerHTML = "";
14618
+
14272
14619
  this.appendChild(this.#createRowButtonsContainer());
14273
14620
  this.appendChild(this.#createColumnButtonsContainer());
14274
14621
 
@@ -14696,10 +15043,108 @@ function collectTextNodes(root) {
14696
15043
  return nodes
14697
15044
  }
14698
15045
 
15046
+ class NativeAdapter {
15047
+ frozenLinkKey = null
15048
+
15049
+ constructor(editorElement) {
15050
+ this.editorElement = editorElement;
15051
+ this.editorContentElement = editorElement.editorContentElement;
15052
+ }
15053
+
15054
+ dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {
15055
+ dispatch(this.editorElement, "lexxy:attributes-change", {
15056
+ attributes,
15057
+ link: linkHref ? { href: linkHref } : null,
15058
+ highlight,
15059
+ headingTag
15060
+ });
15061
+ }
15062
+
15063
+ dispatchEditorInitialized(detail) {
15064
+ dispatch(this.editorElement, "lexxy:editor-initialized", detail);
15065
+ }
15066
+
15067
+ freeze() {
15068
+ let frozenLinkKey = null;
15069
+ this.editorElement.editor?.getEditorState().read(() => {
15070
+ const selection = $r();
15071
+ if (!wr(selection)) return
15072
+
15073
+ const linkNode = vt$4(selection.anchor.getNode(), E$3);
15074
+ if (linkNode) {
15075
+ frozenLinkKey = linkNode.getKey();
15076
+ }
15077
+ });
15078
+
15079
+ this.frozenLinkKey = frozenLinkKey;
15080
+ this.editorContentElement.contentEditable = "false";
15081
+ }
15082
+
15083
+ thaw() {
15084
+ this.editorContentElement.contentEditable = "true";
15085
+ }
15086
+
15087
+ unlinkFrozenNode() {
15088
+ const key = this.frozenLinkKey;
15089
+ if (!key) return false
15090
+
15091
+ const linkNode = Mo(key);
15092
+ if (!B$2(linkNode)) {
15093
+ this.frozenLinkKey = null;
15094
+ return false
15095
+ }
15096
+
15097
+ const children = linkNode.getChildren();
15098
+ for (const child of children) {
15099
+ linkNode.insertBefore(child);
15100
+ }
15101
+ linkNode.remove();
15102
+
15103
+ // Select the former link text so a follow-up createLink can re-wrap it.
15104
+ const firstText = this.#findFirstTextDescendant(children);
15105
+ const lastText = this.#findLastTextDescendant(children);
15106
+ if (firstText && lastText) {
15107
+ const selection = $r();
15108
+ if (wr(selection)) {
15109
+ selection.anchor.set(firstText.getKey(), 0, "text");
15110
+ selection.focus.set(lastText.getKey(), lastText.getTextContent().length, "text");
15111
+ }
15112
+ }
15113
+
15114
+ this.frozenLinkKey = null;
15115
+ return true
15116
+ }
15117
+
15118
+ #findFirstTextDescendant(nodes) {
15119
+ for (const node of nodes) {
15120
+ if (yr(node)) return node
15121
+ if (Pi(node)) {
15122
+ const nestedTextNode = this.#findFirstTextDescendant(node.getChildren());
15123
+ if (nestedTextNode) return nestedTextNode
15124
+ }
15125
+ }
15126
+
15127
+ return null
15128
+ }
15129
+
15130
+ #findLastTextDescendant(nodes) {
15131
+ for (let index = nodes.length - 1; index >= 0; index--) {
15132
+ const node = nodes[index];
15133
+ if (yr(node)) return node
15134
+ if (Pi(node)) {
15135
+ const nestedTextNode = this.#findLastTextDescendant(node.getChildren());
15136
+ if (nestedTextNode) return nestedTextNode
15137
+ }
15138
+ }
15139
+
15140
+ return null
15141
+ }
15142
+ }
15143
+
14699
15144
  const configure = Lexxy.configure;
14700
15145
 
14701
15146
  // Pushing elements definition to after the current call stack to allow global configuration to take place first
14702
15147
  setTimeout(defineElements, 0);
14703
15148
 
14704
- export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, configure, highlightCode as highlightAll, highlightCode };
15149
+ export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure, highlightCode as highlightAll, highlightCode };
14705
15150
  //# sourceMappingURL=lexxy.js.map