lexxy 0.7.2.beta → 0.7.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.
@@ -4716,6 +4716,7 @@ function buildConfig() {
4716
4716
  return {
4717
4717
  ALLOWED_TAGS: ALLOWED_HTML_TAGS.concat(Lexxy.global.get("attachmentTagName")),
4718
4718
  ALLOWED_ATTR: ALLOWED_HTML_ATTRIBUTES,
4719
+ ADD_URI_SAFE_ATTR: [ "caption", "filename" ],
4719
4720
  SAFE_FOR_XML: false // So that it does not strip attributes that contains serialized HTML (like content)
4720
4721
  }
4721
4722
  }
@@ -6622,6 +6623,8 @@ class LexicalToolbarElement extends HTMLElement {
6622
6623
  super();
6623
6624
  this.internals = this.attachInternals();
6624
6625
  this.internals.role = "toolbar";
6626
+
6627
+ this.#createEditorPromise();
6625
6628
  }
6626
6629
 
6627
6630
  connectedCallback() {
@@ -6654,14 +6657,26 @@ class LexicalToolbarElement extends HTMLElement {
6654
6657
  this.#refreshToolbarOverflow();
6655
6658
  this.#bindFocusListeners();
6656
6659
 
6660
+ this.resolveEditorPromise(editorElement);
6661
+
6657
6662
  this.toggleAttribute("connected", true);
6658
6663
  }
6659
6664
 
6665
+ async getEditorElement() {
6666
+ return this.editorElement || await this.editorPromise
6667
+ }
6668
+
6660
6669
  #reconnect() {
6661
6670
  this.disconnectedCallback();
6662
6671
  this.connectedCallback();
6663
6672
  }
6664
6673
 
6674
+ #createEditorPromise() {
6675
+ this.editorPromise = new Promise((resolve) => {
6676
+ this.resolveEditorPromise = resolve;
6677
+ });
6678
+ }
6679
+
6665
6680
  #installResizeObserver() {
6666
6681
  this.resizeObserver = new ResizeObserver(() => this.#refreshToolbarOverflow());
6667
6682
  this.resizeObserver.observe(this);
@@ -6730,28 +6745,24 @@ class LexicalToolbarElement extends HTMLElement {
6730
6745
  }
6731
6746
 
6732
6747
  #bindFocusListeners() {
6733
- this.editorElement.addEventListener("lexxy:focus", this.#handleFocus);
6734
- this.editorElement.addEventListener("lexxy:blur", this.#handleFocusOut);
6735
- this.addEventListener("focusout", this.#handleFocusOut);
6748
+ this.editorElement.addEventListener("lexxy:focus", this.#handleEditorFocus);
6749
+ this.editorElement.addEventListener("lexxy:blur", this.#handleEditorBlur);
6736
6750
  this.addEventListener("keydown", this.#handleKeydown);
6737
6751
  }
6738
6752
 
6739
6753
  #unbindFocusListeners() {
6740
- this.editorElement.removeEventListener("lexxy:focus", this.#handleFocus);
6741
- this.editorElement.removeEventListener("lexxy:blur", this.#handleFocusOut);
6742
- this.removeEventListener("focusout", this.#handleFocusOut);
6754
+ this.editorElement.removeEventListener("lexxy:focus", this.#handleEditorFocus);
6755
+ this.editorElement.removeEventListener("lexxy:blur", this.#handleEditorBlur);
6743
6756
  this.removeEventListener("keydown", this.#handleKeydown);
6744
6757
  }
6745
6758
 
6746
- #handleFocus = () => {
6747
- this.#resetTabIndexValues();
6759
+ #handleEditorFocus = () => {
6748
6760
  this.#focusableItems[0].tabIndex = 0;
6749
6761
  }
6750
6762
 
6751
- #handleFocusOut = () => {
6752
- if (!this.contains(document.activeElement)) {
6753
- this.#resetTabIndexValues();
6754
- }
6763
+ #handleEditorBlur = () => {
6764
+ this.#resetTabIndexValues();
6765
+ this.#closeDropdowns();
6755
6766
  }
6756
6767
 
6757
6768
  #handleKeydown = (event) => {
@@ -6765,11 +6776,13 @@ class LexicalToolbarElement extends HTMLElement {
6765
6776
  }
6766
6777
 
6767
6778
  #monitorSelectionChanges() {
6768
- this.editor.registerUpdateListener(() => {
6769
- this.editor.getEditorState().read(() => {
6779
+ this.editor.registerCommand(
6780
+ ie$1,
6781
+ () => {
6782
+ this.#closeDropdowns();
6770
6783
  this.#updateButtonStates();
6771
- });
6772
- });
6784
+ return false
6785
+ }, Bi);
6773
6786
  }
6774
6787
 
6775
6788
  #monitorHistoryChanges() {
@@ -6858,11 +6871,13 @@ class LexicalToolbarElement extends HTMLElement {
6858
6871
  }
6859
6872
 
6860
6873
  #toolbarIsOverflowing() {
6861
- return this.scrollWidth > this.clientWidth
6874
+ // Safari can report inconsistent clientWidth values on more than 100% window zoom level,
6875
+ // that was affecting the toolbar overflow calculation. We're adding +1 to get around this issue.
6876
+ return (this.scrollWidth - this.#overflow.clientWidth) > this.clientWidth + 1
6862
6877
  }
6863
6878
 
6864
6879
  #refreshToolbarOverflow = () => {
6865
- this.#resetToolbar();
6880
+ this.#resetToolbarOverflow();
6866
6881
  this.#compactMenu();
6867
6882
 
6868
6883
  this.#overflow.style.display = this.#overflowMenu.children.length ? "block" : "none";
@@ -6888,7 +6903,7 @@ class LexicalToolbarElement extends HTMLElement {
6888
6903
  }
6889
6904
  }
6890
6905
 
6891
- #resetToolbar() {
6906
+ #resetToolbarOverflow() {
6892
6907
  const items = Array.from(this.#overflowMenu.children);
6893
6908
  items.sort((a, b) => this.#itemPosition(b) - this.#itemPosition(a));
6894
6909
 
@@ -6910,6 +6925,16 @@ class LexicalToolbarElement extends HTMLElement {
6910
6925
  });
6911
6926
  }
6912
6927
 
6928
+ #closeDropdowns() {
6929
+ this.#dropdowns.forEach((details) => {
6930
+ details.open = false;
6931
+ });
6932
+ }
6933
+
6934
+ get #dropdowns() {
6935
+ return this.querySelectorAll("details")
6936
+ }
6937
+
6913
6938
  get #overflow() {
6914
6939
  return this.querySelector(".lexxy-editor__toolbar-overflow")
6915
6940
  }
@@ -6951,9 +6976,8 @@ class LexicalToolbarElement extends HTMLElement {
6951
6976
  <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.65422 0.711575C7.1856 0.242951 6.42579 0.242951 5.95717 0.711575C5.48853 1.18021 5.48853 1.94 5.95717 2.40864L8.70864 5.16011L2.85422 11.0145C1.44834 12.4204 1.44833 14.6998 2.85422 16.1057L7.86011 21.1115C9.26599 22.5174 11.5454 22.5174 12.9513 21.1115L19.6542 14.4087C20.1228 13.94 20.1228 13.1802 19.6542 12.7115L11.8544 4.91171L11.2542 4.31158L7.65422 0.711575ZM4.55127 12.7115L10.4057 6.85716L17.1087 13.56H4.19981C4.19981 13.253 4.31696 12.9459 4.55127 12.7115ZM23.6057 20.76C23.6057 22.0856 22.5311 23.16 21.2057 23.16C19.8802 23.16 18.8057 22.0856 18.8057 20.76C18.8057 19.5408 19.8212 18.5339 20.918 17.4462C21.0135 17.3516 21.1096 17.2563 21.2057 17.16C21.3018 17.2563 21.398 17.3516 21.4935 17.4462C22.5903 18.5339 23.6057 19.5408 23.6057 20.76Z"/></svg>
6952
6977
  </summary>
6953
6978
  <lexxy-highlight-dropdown class="lexxy-editor__toolbar-dropdown-content">
6954
- <div data-button-group="color"></div>
6955
- <div data-button-group="background-color"></div>
6956
- <button data-command="removeHighlight" class="lexxy-editor__toolbar-dropdown-reset">Remove all coloring</button>
6979
+ <div class="lexxy-highlight-colors"></div>
6980
+ <button data-command="removeHighlight" class="lexxy-editor__toolbar-button lexxy-editor__toolbar-dropdown-reset">Remove all coloring</button>
6957
6981
  </lexxy-highlight-dropdown>
6958
6982
  </details>
6959
6983
 
@@ -6965,8 +6989,8 @@ class LexicalToolbarElement extends HTMLElement {
6965
6989
  <form method="dialog">
6966
6990
  <input type="url" placeholder="Enter a URL…" class="input">
6967
6991
  <div class="lexxy-editor__toolbar-dropdown-actions">
6968
- <button type="submit" class="btn" value="link">Link</button>
6969
- <button type="button" class="btn" value="unlink">Unlink</button>
6992
+ <button type="submit" class="lexxy-editor__toolbar-button" value="link">Link</button>
6993
+ <button type="button" class="lexxy-editor__toolbar-button" value="unlink">Unlink</button>
6970
6994
  </div>
6971
6995
  </form>
6972
6996
  </lexxy-link-dropdown>
@@ -7014,9 +7038,9 @@ class LexicalToolbarElement extends HTMLElement {
7014
7038
  <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.2599 8.26531C15.9672 6.56386 13.1237 5.77629 10.2823 6.05535C7.4408 6.33452 4.80455 7.66079 2.88681 9.77605C1.32245 11.5016 0.326407 13.6516 0.0127834 15.9352C-0.105117 16.7939 0.608975 17.4997 1.47567 17.4997C2.34228 17.4997 3.02969 16.7915 3.19149 15.9401C3.47682 14.4379 4.17156 13.0321 5.212 11.8844C6.60637 10.3464 8.52287 9.38139 10.589 9.17839C12.655 8.97546 14.7227 9.54856 16.3897 10.7858C17.5237 11.6275 18.4165 12.7361 18.9991 13.9997H15.4063C14.578 13.9997 13.9066 14.6714 13.9063 15.4997C13.9063 16.3281 14.5779 16.9997 15.4063 16.9997H22.4063C23.2348 16.9997 23.9063 16.3281 23.9063 15.4997V8.49968C23.9061 7.67144 23.2346 6.99968 22.4063 6.99968C21.578 6.99968 20.9066 7.67144 20.9063 8.49968V11.0212C20.1897 9.9704 19.2984 9.03613 18.2599 8.26531Z"/></svg>
7015
7039
  </button>
7016
7040
 
7017
- <details class="lexxy-editor__toolbar-overflow">
7041
+ <details class="lexxy-editor__toolbar-dropdown lexxy-editor__toolbar-overflow" name="lexxy-dropdown">
7018
7042
  <summary class="lexxy-editor__toolbar-button" aria-label="Show more toolbar buttons">•••</summary>
7019
- <div class="lexxy-editor__toolbar-overflow-menu" aria-label="More toolbar buttons"></div>
7043
+ <div class="lexxy-editor__toolbar-dropdown-content lexxy-editor__toolbar-overflow-menu" aria-label="More toolbar buttons"></div>
7020
7044
  </details>
7021
7045
  `
7022
7046
  }
@@ -7066,6 +7090,8 @@ var theme = {
7066
7090
  tableCellSelected: "lexxy-content__table-cell--selected",
7067
7091
  tableSelection: "lexxy-content__table--selection",
7068
7092
  tableScrollableWrapper: "lexxy-content__table-wrapper",
7093
+ tableCellHighlight: "lexxy-content__table-cell--highlight",
7094
+ tableCellFocus: "lexxy-content__table-cell--focus",
7069
7095
  list: {
7070
7096
  nested: {
7071
7097
  listitem: "lexxy-nested-listitem",
@@ -7647,30 +7673,6 @@ class HorizontalDividerNode extends ki {
7647
7673
  }
7648
7674
  }
7649
7675
 
7650
- class WrappedTableNode extends hn {
7651
- static clone(node) {
7652
- return new WrappedTableNode(node.__key)
7653
- }
7654
-
7655
- exportDOM(editor) {
7656
- const superExport = super.exportDOM(editor);
7657
-
7658
- return {
7659
- ...superExport,
7660
- after: (tableElement) => {
7661
- if (superExport.after) {
7662
- tableElement = superExport.after(tableElement);
7663
- const clonedTable = tableElement.cloneNode(true);
7664
- const wrappedTable = createElement("figure", { className: "lexxy-content__table-wrapper" }, clonedTable.outerHTML);
7665
- return wrappedTable
7666
- }
7667
-
7668
- return tableElement
7669
- }
7670
- }
7671
- }
7672
- }
7673
-
7674
7676
  const COMMANDS = [
7675
7677
  "bold",
7676
7678
  "italic",
@@ -7688,13 +7690,6 @@ const COMMANDS = [
7688
7690
  "uploadAttachments",
7689
7691
 
7690
7692
  "insertTable",
7691
- "insertTableRowAbove",
7692
- "insertTableRowBelow",
7693
- "insertTableColumnAfter",
7694
- "insertTableColumnBefore",
7695
- "deleteTableRow",
7696
- "deleteTableColumn",
7697
- "deleteTable",
7698
7693
 
7699
7694
  "undo",
7700
7695
  "redo"
@@ -7803,9 +7798,7 @@ class CommandDispatcher {
7803
7798
  }
7804
7799
 
7805
7800
  dispatchInsertHorizontalDivider() {
7806
- this.editor.update(() => {
7807
- this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
7808
- });
7801
+ this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
7809
7802
 
7810
7803
  this.editor.focus();
7811
7804
  }
@@ -7867,41 +7860,6 @@ class CommandDispatcher {
7867
7860
  this.editor.dispatchCommand(Ae$1, { "rows": 3, "columns": 3, "includeHeaders": true });
7868
7861
  }
7869
7862
 
7870
- dispatchInsertTableRowBelow() {
7871
- je$1(true);
7872
- }
7873
-
7874
- dispatchInsertTableRowAbove() {
7875
- je$1(false);
7876
- }
7877
-
7878
- dispatchInsertTableColumnAfter() {
7879
- Ze$1(true);
7880
- }
7881
-
7882
- dispatchInsertTableColumnBefore() {
7883
- Ze$1(false);
7884
- }
7885
-
7886
- dispatchDeleteTableRow() {
7887
- ot$1();
7888
- }
7889
-
7890
- dispatchDeleteTableColumn() {
7891
- lt$1();
7892
- }
7893
-
7894
- dispatchDeleteTable() {
7895
- this.editor.update(() => {
7896
- const selection = Lr();
7897
- if (!yr(selection)) return
7898
-
7899
- const anchorNode = selection.anchor.getNode();
7900
- const tableNode = Qt(anchorNode);
7901
- tableNode.remove();
7902
- });
7903
- }
7904
-
7905
7863
  dispatchUndo() {
7906
7864
  this.editor.dispatchCommand(pe$1, undefined);
7907
7865
  }
@@ -8195,6 +8153,14 @@ class Selection {
8195
8153
  return wt$5(anchorNode, q$2) !== null
8196
8154
  }
8197
8155
 
8156
+ get isTableCellSelected() {
8157
+ const selection = Lr();
8158
+ if (!yr(selection)) return false
8159
+
8160
+ const anchorNode = selection.anchor.getNode();
8161
+ return wt$5(anchorNode, xe) !== null
8162
+ }
8163
+
8198
8164
  get nodeAfterCursor() {
8199
8165
  const { anchorNode, offset } = this.#getCollapsedSelectionData();
8200
8166
  if (!anchorNode) return null
@@ -8707,17 +8673,6 @@ function sanitize(html) {
8707
8673
  return purify.sanitize(html, buildConfig())
8708
8674
  }
8709
8675
 
8710
- // Prevent the hardcoded background color
8711
- // A background color value is set by Lexical if background is null:
8712
- // https://github.com/facebook/lexical/blob/5bbbe849bd229e1db0e7b536e6a919520ada7bb2/packages/lexical-table/src/LexicalTableCellNode.ts#L187
8713
- function registerHeaderBackgroundTransform(editor) {
8714
- return editor.registerNodeTransform(xe, (node) => {
8715
- if (node.getBackgroundColor() === null) {
8716
- node.setBackgroundColor("");
8717
- }
8718
- })
8719
- }
8720
-
8721
8676
  function dasherize(value) {
8722
8677
  return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`)
8723
8678
  }
@@ -8741,6 +8696,10 @@ function filterMatches(text, potentialMatch) {
8741
8696
  return normalizeFilteredText(text).includes(normalizeFilteredText(potentialMatch))
8742
8697
  }
8743
8698
 
8699
+ function upcaseFirst(string) {
8700
+ return string.charAt(0).toUpperCase() + string.slice(1)
8701
+ }
8702
+
8744
8703
  class EditorConfiguration {
8745
8704
  #editorElement
8746
8705
  #config
@@ -9182,15 +9141,14 @@ class Contents {
9182
9141
  new FormatEscaper(editorElement).monitor();
9183
9142
  }
9184
9143
 
9185
- insertHtml(html) {
9144
+ insertHtml(html, { tag } = {}) {
9186
9145
  this.editor.update(() => {
9187
9146
  const selection = Lr();
9188
-
9189
9147
  if (!yr(selection)) return
9190
9148
 
9191
9149
  const nodes = m$1(this.editor, parseHtml(html));
9192
9150
  selection.insertNodes(nodes);
9193
- });
9151
+ }, { tag });
9194
9152
  }
9195
9153
 
9196
9154
  insertAtCursor(node) {
@@ -10065,14 +10023,14 @@ class Clipboard {
10065
10023
 
10066
10024
  #pasteMarkdown(text) {
10067
10025
  const html = k(text);
10068
- this.contents.insertHtml(html);
10026
+ this.contents.insertHtml(html, { tag: [ Fn ] });
10069
10027
  }
10070
10028
 
10071
10029
  #pasteRichText(clipboardData) {
10072
10030
  this.editor.update(() => {
10073
10031
  const selection = Lr();
10074
10032
  R$3(clipboardData, selection, this.editor);
10075
- });
10033
+ }, { tag: Fn });
10076
10034
  }
10077
10035
 
10078
10036
  #handlePastedFiles(clipboardData) {
@@ -10306,6 +10264,124 @@ function $applyLanguage(conversionOutput, element) {
10306
10264
  conversionOutput.node.setLanguage(language);
10307
10265
  }
10308
10266
 
10267
+ class WrappedTableNode extends hn {
10268
+ static clone(node) {
10269
+ return new WrappedTableNode(node.__key)
10270
+ }
10271
+
10272
+ exportDOM(editor) {
10273
+ const superExport = super.exportDOM(editor);
10274
+
10275
+ return {
10276
+ ...superExport,
10277
+ after: (tableElement) => {
10278
+ if (superExport.after) {
10279
+ tableElement = superExport.after(tableElement);
10280
+ const clonedTable = tableElement.cloneNode(true);
10281
+ const wrappedTable = createElement("figure", { className: "lexxy-content__table-wrapper" }, clonedTable.outerHTML);
10282
+ return wrappedTable
10283
+ }
10284
+
10285
+ return tableElement
10286
+ }
10287
+ }
10288
+ }
10289
+ }
10290
+
10291
+ const TablesLexicalExtension = Kl({
10292
+ name: "lexxy/tables",
10293
+ nodes: [
10294
+ WrappedTableNode,
10295
+ {
10296
+ replace: hn,
10297
+ with: () => new WrappedTableNode()
10298
+ },
10299
+ xe,
10300
+ Ee$1
10301
+ ],
10302
+ register(editor) {
10303
+ // Register Lexical table plugins
10304
+ Nn(editor);
10305
+ yn(editor, true);
10306
+ un(editor);
10307
+
10308
+ // Bug fix: Prevent hardcoded background color (Lexical #8089)
10309
+ editor.registerNodeTransform(xe, (node) => {
10310
+ if (node.getBackgroundColor() === null) {
10311
+ node.setBackgroundColor("");
10312
+ }
10313
+ });
10314
+
10315
+ // Bug fix: Fix column header states (Lexical #8090)
10316
+ editor.registerNodeTransform(xe, (node) => {
10317
+ const headerState = node.getHeaderStyles();
10318
+
10319
+ if (headerState !== ve$1.ROW) return
10320
+
10321
+ const rowParent = node.getParent();
10322
+ const tableNode = rowParent?.getParent();
10323
+ if (!tableNode) return
10324
+
10325
+ const rows = tableNode.getChildren();
10326
+ const cellIndex = rowParent.getChildren().indexOf(node);
10327
+
10328
+ const cellsInRow = rowParent.getChildren();
10329
+ const isHeaderRow = cellsInRow.every(cell =>
10330
+ cell.getHeaderStyles() !== ve$1.NO_STATUS
10331
+ );
10332
+
10333
+ const isHeaderColumn = rows.every(row => {
10334
+ const cell = row.getChildren()[cellIndex];
10335
+ return cell && cell.getHeaderStyles() !== ve$1.NO_STATUS
10336
+ });
10337
+
10338
+ let newHeaderState = ve$1.NO_STATUS;
10339
+
10340
+ if (isHeaderRow) {
10341
+ newHeaderState |= ve$1.ROW;
10342
+ }
10343
+
10344
+ if (isHeaderColumn) {
10345
+ newHeaderState |= ve$1.COLUMN;
10346
+ }
10347
+
10348
+ if (newHeaderState !== headerState) {
10349
+ node.setHeaderStyles(newHeaderState, ve$1.BOTH);
10350
+ }
10351
+ });
10352
+
10353
+ editor.registerCommand("insertTableRowAfter", () => {
10354
+ je$1(true);
10355
+ }, Ri);
10356
+
10357
+ editor.registerCommand("insertTableRowBefore", () => {
10358
+ je$1(false);
10359
+ }, Ri);
10360
+
10361
+ editor.registerCommand("insertTableColumnAfter", () => {
10362
+ Ze$1(true);
10363
+ }, Ri);
10364
+
10365
+ editor.registerCommand("insertTableColumnBefore", () => {
10366
+ Ze$1(false);
10367
+ }, Ri);
10368
+
10369
+ editor.registerCommand("deleteTableRow", () => {
10370
+ ot$1();
10371
+ }, Ri);
10372
+
10373
+ editor.registerCommand("deleteTableColumn", () => {
10374
+ lt$1();
10375
+ }, Ri);
10376
+
10377
+ editor.registerCommand("deleteTable", () => {
10378
+ const selection = Lr();
10379
+ if (!yr(selection)) return false
10380
+ Qt(selection.anchor.getNode())?.remove();
10381
+ }, Ri);
10382
+ }
10383
+ });
10384
+
10309
10385
  class LexicalEditorElement extends HTMLElement {
10310
10386
  static formAssociated = true
10311
10387
  static debug = false
@@ -10497,8 +10573,7 @@ class LexicalEditorElement extends HTMLElement {
10497
10573
  this.#registerComponents();
10498
10574
  this.#listenForInvalidatedNodes();
10499
10575
  this.#handleEnter();
10500
- this.#handleFocus();
10501
- this.#handleTables();
10576
+ this.#registerFocusEvents();
10502
10577
  this.#attachDebugHooks();
10503
10578
  this.#attachToolbar();
10504
10579
  this.#loadInitialValue();
@@ -10526,7 +10601,8 @@ class LexicalEditorElement extends HTMLElement {
10526
10601
  const extensions = [ ];
10527
10602
  const richTextExtensions = [
10528
10603
  this.highlighter.lexicalExtension,
10529
- TrixContentExtension
10604
+ TrixContentExtension,
10605
+ TablesLexicalExtension
10530
10606
  ];
10531
10607
 
10532
10608
  if (this.supportsRichText) {
@@ -10551,14 +10627,7 @@ class LexicalEditorElement extends HTMLElement {
10551
10627
  et$1,
10552
10628
  y$1,
10553
10629
  A,
10554
- HorizontalDividerNode,
10555
- WrappedTableNode,
10556
- {
10557
- replace: hn,
10558
- with: () => { return new WrappedTableNode() }
10559
- },
10560
- xe,
10561
- Ee$1,
10630
+ HorizontalDividerNode
10562
10631
  );
10563
10632
  }
10564
10633
 
@@ -10672,11 +10741,8 @@ class LexicalEditorElement extends HTMLElement {
10672
10741
  }
10673
10742
 
10674
10743
  #registerTableComponents() {
10675
- Nn(this.editor);
10676
- this.tableHandler = createElement("lexxy-table-handler");
10677
- this.append(this.tableHandler);
10678
-
10679
- this.#addUnregisterHandler(registerHeaderBackgroundTransform(this.editor));
10744
+ this.tableTools = createElement("lexxy-table-tools");
10745
+ this.append(this.tableTools);
10680
10746
  }
10681
10747
 
10682
10748
  #registerCodeHiglightingComponents() {
@@ -10723,12 +10789,27 @@ class LexicalEditorElement extends HTMLElement {
10723
10789
  );
10724
10790
  }
10725
10791
 
10726
- #handleFocus() {
10727
- // Lexxy handles focus and blur as commands
10728
- // see https://github.com/facebook/lexical/blob/d1a8e84fe9063a4f817655b346b6ff373aa107f0/packages/lexical/src/LexicalEvents.ts#L35
10729
- // and https://stackoverflow.com/a/72212077
10730
- this.editor.registerCommand(Ye, () => { dispatch(this, "lexxy:blur"); }, Ri);
10731
- this.editor.registerCommand(Ve$1, () => { dispatch(this, "lexxy:focus"); }, Ri);
10792
+ #registerFocusEvents() {
10793
+ this.addEventListener("focusin", this.#handleFocusIn);
10794
+ this.addEventListener("focusout", this.#handleFocusOut);
10795
+ }
10796
+
10797
+ #handleFocusIn(event) {
10798
+ if (this.#elementInEditorOrToolbar(event.target) && !this.currentlyFocused) {
10799
+ dispatch(this, "lexxy:focus");
10800
+ this.currentlyFocused = true;
10801
+ }
10802
+ }
10803
+
10804
+ #handleFocusOut(event) {
10805
+ if (!this.#elementInEditorOrToolbar(event.relatedTarget)) {
10806
+ dispatch(this, "lexxy:blur");
10807
+ this.currentlyFocused = false;
10808
+ }
10809
+ }
10810
+
10811
+ #elementInEditorOrToolbar(element) {
10812
+ return this.contains(element) || this.toolbarElement?.contains(element)
10732
10813
  }
10733
10814
 
10734
10815
  #onFocus() {
@@ -10745,12 +10826,6 @@ class LexicalEditorElement extends HTMLElement {
10745
10826
  }
10746
10827
  }
10747
10828
 
10748
- #handleTables() {
10749
- if (this.supportsRichText) {
10750
- this.removeTableSelectionObserver = yn(this.editor, true);
10751
- un(this.editor);
10752
- }
10753
- }
10754
10829
 
10755
10830
  #attachDebugHooks() {
10756
10831
  if (!LexicalEditorElement.debug) return
@@ -10848,10 +10923,11 @@ class ToolbarDropdown extends HTMLElement {
10848
10923
 
10849
10924
  this.container.addEventListener("toggle", this.#handleToggle.bind(this));
10850
10925
  this.container.addEventListener("keydown", this.#handleKeyDown.bind(this));
10926
+
10927
+ this.#onToolbarEditor(this.initialize.bind(this));
10851
10928
  }
10852
10929
 
10853
10930
  disconnectedCallback() {
10854
- this.#removeClickOutsideHandler();
10855
10931
  this.container.removeEventListener("keydown", this.#handleKeyDown.bind(this));
10856
10932
  }
10857
10933
 
@@ -10867,46 +10943,29 @@ class ToolbarDropdown extends HTMLElement {
10867
10943
  return this.toolbar.editor
10868
10944
  }
10869
10945
 
10870
- close() {
10871
- this.container.removeAttribute("open");
10872
- }
10873
-
10874
- #handleToggle(event) {
10875
- if (this.container.open) {
10876
- this.#handleOpen(event.target);
10877
- } else {
10878
- this.#handleClose();
10879
- }
10880
- }
10881
-
10882
- #handleOpen() {
10883
- this.#interactiveElements[0].focus();
10884
- this.#setupClickOutsideHandler();
10885
-
10886
- this.#resetTabIndexValues();
10946
+ initialize() {
10947
+ // Any post-editor initialization
10887
10948
  }
10888
10949
 
10889
- #handleClose() {
10890
- this.#removeClickOutsideHandler();
10950
+ close() {
10891
10951
  this.editor.focus();
10952
+ this.container.open = false;
10892
10953
  }
10893
10954
 
10894
- #setupClickOutsideHandler() {
10895
- if (this.clickOutsideHandler) return
10896
-
10897
- this.clickOutsideHandler = this.#handleClickOutside.bind(this);
10898
- document.addEventListener("click", this.clickOutsideHandler, true);
10955
+ async #onToolbarEditor(callback) {
10956
+ await this.toolbar.editorConnected;
10957
+ callback();
10899
10958
  }
10900
10959
 
10901
- #removeClickOutsideHandler() {
10902
- if (!this.clickOutsideHandler) return
10903
-
10904
- document.removeEventListener("click", this.clickOutsideHandler, true);
10905
- this.clickOutsideHandler = null;
10960
+ #handleToggle() {
10961
+ if (this.container.open) {
10962
+ this.#handleOpen();
10963
+ }
10906
10964
  }
10907
10965
 
10908
- #handleClickOutside({ target }) {
10909
- if (this.container.open && !this.container.contains(target)) this.close();
10966
+ async #handleOpen() {
10967
+ this.#interactiveElements[0].focus();
10968
+ this.#resetTabIndexValues();
10910
10969
  }
10911
10970
 
10912
10971
  #handleKeyDown(event) {
@@ -10994,19 +11053,14 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
10994
11053
  const NO_STYLE = Symbol("no_style");
10995
11054
 
10996
11055
  class HighlightDropdown extends ToolbarDropdown {
10997
- #initialized = false
10998
-
10999
11056
  connectedCallback() {
11000
11057
  super.connectedCallback();
11001
11058
  this.#registerToggleHandler();
11002
11059
  }
11003
11060
 
11004
- #ensureInitialized() {
11005
- if (this.#initialized) return
11006
-
11061
+ initialize() {
11007
11062
  this.#setUpButtons();
11008
11063
  this.#registerButtonHandlers();
11009
- this.#initialized = true;
11010
11064
  }
11011
11065
 
11012
11066
  #registerToggleHandler() {
@@ -11019,16 +11073,18 @@ class HighlightDropdown extends ToolbarDropdown {
11019
11073
  }
11020
11074
 
11021
11075
  #setUpButtons() {
11022
- this.#buttonGroups.forEach(buttonGroup => {
11023
- this.#populateButtonGroup(buttonGroup);
11024
- });
11076
+ const colorGroups = this.editorElement.config.get("highlight.buttons");
11077
+
11078
+ this.#populateButtonGroup("color", colorGroups.color);
11079
+ this.#populateButtonGroup("background-color", colorGroups["background-color"]);
11080
+
11081
+ const maxNumberOfColors = Math.max(colorGroups.color.length, colorGroups["background-color"].length);
11082
+ this.style.setProperty("--max-colors", maxNumberOfColors);
11025
11083
  }
11026
11084
 
11027
- #populateButtonGroup(buttonGroup) {
11028
- const attribute = buttonGroup.dataset.buttonGroup;
11029
- const values = this.editorElement.config.get(`highlight.buttons.${attribute}`) || [];
11085
+ #populateButtonGroup(attribute, values) {
11030
11086
  values.forEach((value, index) => {
11031
- buttonGroup.appendChild(this.#createButton(attribute, value, index));
11087
+ this.#buttonContainer.appendChild(this.#createButton(attribute, value, index));
11032
11088
  });
11033
11089
  }
11034
11090
 
@@ -11037,15 +11093,13 @@ class HighlightDropdown extends ToolbarDropdown {
11037
11093
  button.dataset.style = attribute;
11038
11094
  button.style.setProperty(attribute, value);
11039
11095
  button.dataset.value = value;
11040
- button.classList.add("lexxy-highlight-button");
11096
+ button.classList.add("lexxy-editor__toolbar-button", "lexxy-highlight-button");
11041
11097
  button.name = attribute + "-" + index;
11042
11098
  return button
11043
11099
  }
11044
11100
 
11045
11101
  #handleToggle({ newState }) {
11046
11102
  if (newState === "open") {
11047
- this.#ensureInitialized();
11048
-
11049
11103
  this.editor.getEditorState().read(() => {
11050
11104
  this.#updateColorButtonStates(Lr());
11051
11105
  });
@@ -11088,8 +11142,8 @@ class HighlightDropdown extends ToolbarDropdown {
11088
11142
  this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).disabled = !hasHighlight;
11089
11143
  }
11090
11144
 
11091
- get #buttonGroups() {
11092
- return this.querySelectorAll("[data-button-group]")
11145
+ get #buttonContainer() {
11146
+ return this.querySelector(".lexxy-highlight-colors")
11093
11147
  }
11094
11148
 
11095
11149
  get #colorButtons() {
@@ -11099,501 +11153,714 @@ class HighlightDropdown extends ToolbarDropdown {
11099
11153
 
11100
11154
  customElements.define("lexxy-highlight-dropdown", HighlightDropdown);
11101
11155
 
11102
- class TableHandler extends HTMLElement {
11103
- connectedCallback() {
11104
- this.#setUpButtons();
11105
- this.#monitorForTableSelection();
11106
- this.#registerKeyboardShortcuts();
11156
+ class TableController {
11157
+ constructor(editorElement) {
11158
+ this.editor = editorElement.editor;
11159
+ this.contents = editorElement.contents;
11160
+ this.selection = editorElement.selection;
11161
+
11162
+ this.currentTableNodeKey = null;
11163
+ this.currentCellKey = null;
11164
+
11165
+ this.#registerKeyHandlers();
11107
11166
  }
11108
11167
 
11109
- disconnectedCallback() {
11110
- this.#unregisterKeyboardShortcuts();
11168
+ destroy() {
11169
+ this.currentTableNodeKey = null;
11170
+ this.currentCellKey = null;
11171
+
11172
+ this.#unregisterKeyHandlers();
11111
11173
  }
11112
11174
 
11113
- get #editor() {
11114
- return this.#editorElement.editor
11175
+ get currentCell() {
11176
+ if (!this.currentCellKey) return null
11177
+
11178
+ return this.editor.getEditorState().read(() => {
11179
+ const cell = xo(this.currentCellKey);
11180
+ return (cell instanceof xe) ? cell : null
11181
+ })
11115
11182
  }
11116
11183
 
11117
- get #editorElement() {
11118
- return this.closest("lexxy-editor")
11184
+ get currentTableNode() {
11185
+ if (!this.currentTableNodeKey) return null
11186
+
11187
+ return this.editor.getEditorState().read(() => {
11188
+ const tableNode = xo(this.currentTableNodeKey);
11189
+ return (tableNode instanceof hn) ? tableNode : null
11190
+ })
11119
11191
  }
11120
11192
 
11121
- get #currentCell() {
11122
- const selection = Lr();
11123
- if (!yr(selection)) return null
11193
+ get currentRowCells() {
11194
+ const currentRowIndex = this.currentRowIndex;
11124
11195
 
11125
- const anchorNode = selection.anchor.getNode();
11126
- return Be$1(anchorNode)
11196
+ const rows = this.tableRows;
11197
+ if (!rows) return null
11198
+
11199
+ return this.editor.getEditorState().read(() => {
11200
+ return rows[currentRowIndex]?.getChildren() ?? null
11201
+ }) ?? null
11127
11202
  }
11128
11203
 
11129
- get #currentRow() {
11130
- const currentCell = this.#currentCell;
11204
+ get currentRowIndex() {
11205
+ const currentCell = this.currentCell;
11131
11206
  if (!currentCell) return 0
11132
- return Ie$1(currentCell)
11207
+
11208
+ return this.editor.getEditorState().read(() => {
11209
+ return Ie$1(currentCell)
11210
+ }) ?? 0
11133
11211
  }
11134
11212
 
11135
- get #currentColumn() {
11136
- const currentCell = this.#currentCell;
11137
- if (!currentCell) return 0
11138
- return Ue$1(currentCell)
11213
+ get currentColumnCells() {
11214
+ const columnIndex = this.currentColumnIndex;
11215
+
11216
+ const rows = this.tableRows;
11217
+ if (!rows) return null
11218
+
11219
+ return this.editor.getEditorState().read(() => {
11220
+ return rows.map(row => row.getChildAtIndex(columnIndex))
11221
+ }) ?? null
11139
11222
  }
11140
11223
 
11141
- get #tableHandlerButtons() {
11142
- return Array.from(this.querySelectorAll("button, details > summary"))
11224
+ get currentColumnIndex() {
11225
+ const currentCell = this.currentCell;
11226
+ if (!currentCell) return 0
11227
+
11228
+ return this.editor.getEditorState().read(() => {
11229
+ return Ue$1(currentCell)
11230
+ }) ?? 0
11143
11231
  }
11144
11232
 
11145
- #registerKeyboardShortcuts() {
11146
- this.unregisterKeyboardShortcuts = this.#editor.registerCommand(me$1, this.#handleKeyDown, Bi);
11233
+ get tableRows() {
11234
+ return this.editor.getEditorState().read(() => {
11235
+ return this.currentTableNode?.getChildren()
11236
+ }) ?? null
11147
11237
  }
11148
11238
 
11149
- #unregisterKeyboardShortcuts() {
11150
- this.unregisterKeyboardShortcuts();
11239
+ updateSelectedTable() {
11240
+ let cellNode = null;
11241
+ let tableNode = null;
11242
+
11243
+ this.editor.getEditorState().read(() => {
11244
+ const selection = Lr();
11245
+ if (!selection || !this.selection.isTableCellSelected) return
11246
+
11247
+ const node = selection.getNodes()[0];
11248
+
11249
+ cellNode = Gt(node);
11250
+ tableNode = Qt(node);
11251
+ });
11252
+
11253
+ this.currentCellKey = cellNode?.getKey() ?? null;
11254
+ this.currentTableNodeKey = tableNode?.getKey() ?? null;
11151
11255
  }
11152
11256
 
11153
- #handleKeyDown = (event) => {
11154
- if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key === "F10") {
11155
- const firstButton = this.querySelector("button, [tabindex]:not([tabindex='-1'])");
11156
- this.#setFocusStateOnSelectedCell();
11157
- firstButton?.focus();
11158
- } else if (event.key === "Escape") {
11159
- this.#editor.getEditorState().read(() => {
11160
- const cell = this.#currentCell;
11161
- if (!cell) return
11257
+ executeTableCommand(command, customIndex = null) {
11258
+ if (command.action === "delete" && command.childType === "table") {
11259
+ this.#deleteTable();
11260
+ return
11261
+ }
11162
11262
 
11163
- this.#editor.update(() => {
11164
- cell.select();
11165
- });
11166
- });
11167
- this.#closeMoreMenu();
11263
+ if (command.action === "toggle") {
11264
+ this.#executeToggleStyle(command);
11265
+ return
11168
11266
  }
11267
+
11268
+ this.#executeCommand(command, customIndex);
11169
11269
  }
11170
11270
 
11171
- #handleTableHandlerKeydown = (event) => {
11172
- if (event.key === "Escape") {
11173
- this.#editor.focus();
11174
- } else {
11175
- handleRollingTabIndex(this.#tableHandlerButtons, event);
11176
- }
11271
+ #executeCommand(command, customIndex = null) {
11272
+ this.#selectCellAtSelection();
11273
+ this.editor.dispatchCommand(this.#commandName(command));
11274
+ this.#selectNextBestCell(command, customIndex);
11177
11275
  }
11178
11276
 
11179
- #setUpButtons() {
11180
- this.appendChild(this.#createRowButtonsContainer());
11181
- this.appendChild(this.#createColumnButtonsContainer());
11277
+ #executeToggleStyle(command) {
11278
+ const childType = command.childType;
11182
11279
 
11183
- this.moreMenu = this.#createMoreMenu();
11184
- this.appendChild(this.moreMenu);
11185
- this.addEventListener("keydown", this.#handleTableHandlerKeydown);
11186
- }
11280
+ let cells = null;
11281
+ let headerState = null;
11187
11282
 
11188
- #showTableHandlerButtons() {
11189
- this.style.display = "flex";
11190
- this.#closeMoreMenu();
11283
+ if (childType === "row") {
11284
+ cells = this.currentRowCells;
11285
+ headerState = ve$1.ROW;
11286
+ } else if (childType === "column") {
11287
+ cells = this.currentColumnCells;
11288
+ headerState = ve$1.COLUMN;
11289
+ }
11191
11290
 
11192
- this.#updateRowColumnCount();
11193
- this.#setTableFocusState(true);
11291
+ if (!cells || cells.length === 0) return
11292
+
11293
+ this.editor.update(() => {
11294
+ const firstCell = Be$1(cells[0]);
11295
+ if (!firstCell) return
11296
+
11297
+ const currentStyle = firstCell.getHeaderStyles();
11298
+ const newStyle = currentStyle ^ headerState;
11299
+
11300
+ cells.forEach(cell => {
11301
+ this.#setHeaderStyle(cell, newStyle, headerState);
11302
+ });
11303
+ });
11194
11304
  }
11195
11305
 
11196
- #hideTableHandlerButtons() {
11197
- this.style.display = "none";
11198
- this.#closeMoreMenu();
11306
+ #deleteTable() {
11307
+ this.#selectCellAtSelection();
11308
+ this.editor.dispatchCommand("deleteTable");
11309
+ }
11310
+
11311
+ #selectCellAtSelection() {
11312
+ this.editor.update(() => {
11313
+ const selection = Lr();
11314
+ if (!selection) return
11199
11315
 
11200
- this.#setTableFocusState(false);
11201
- this.currentTableNode = null;
11316
+ const node = selection.getNodes()[0];
11317
+
11318
+ Gt(node)?.selectEnd();
11319
+ });
11202
11320
  }
11203
11321
 
11204
- #updateButtonsPosition(tableNode) {
11205
- const tableElement = this.#editor.getElementByKey(tableNode.getKey());
11206
- if (!tableElement) return
11322
+ #commandName(command) {
11323
+ const { action, childType, direction } = command;
11207
11324
 
11208
- const tableRect = tableElement.getBoundingClientRect();
11209
- const editorRect = this.#editorElement.getBoundingClientRect();
11325
+ const childTypeSuffix = upcaseFirst(childType);
11326
+ const directionSuffix = action == "insert" ? upcaseFirst(direction) : "";
11327
+ return `${action}Table${childTypeSuffix}${directionSuffix}`
11328
+ }
11210
11329
 
11211
- const relativeTop = tableRect.top - editorRect.top;
11212
- const relativeCenter = (tableRect.left + tableRect.right) / 2 - editorRect.left;
11213
- this.style.top = `${relativeTop}px`;
11214
- this.style.left = `${relativeCenter}px`;
11330
+ #setHeaderStyle(cell, newStyle, headerState) {
11331
+ const tableCellNode = Be$1(cell);
11332
+ tableCellNode?.setHeaderStyles(newStyle, headerState);
11215
11333
  }
11216
11334
 
11217
- #updateRowColumnCount() {
11218
- if (!this.currentTableNode) return
11335
+ async #selectCellAtIndex(rowIndex, columnIndex) {
11336
+ // We wait for next frame, otherwise table operations might not have completed yet.
11337
+ await nextFrame();
11219
11338
 
11220
- const tableElement = dn(this.#editor, this.currentTableNode);
11221
- if (!tableElement) return
11339
+ if (!this.currentTableNode) return
11222
11340
 
11223
- const rowCount = tableElement.rows;
11224
- const columnCount = tableElement.columns;
11341
+ const rows = this.tableRows;
11342
+ if (!rows) return
11225
11343
 
11226
- this.rowCount.textContent = `${rowCount} row${rowCount === 1 ? "" : "s"}`;
11227
- this.columnCount.textContent = `${columnCount} column${columnCount === 1 ? "" : "s"}`;
11228
- }
11344
+ const row = rows[rowIndex];
11345
+ if (!row) return
11229
11346
 
11230
- #createButton(icon, label, onClick) {
11231
- const button = createElement("button", {
11232
- className: "lexxy-table-control__button",
11233
- "aria-label": label,
11234
- type: "button"
11347
+ this.editor.update(() => {
11348
+ const cell = Be$1(row.getChildAtIndex(columnIndex));
11349
+ cell?.selectEnd();
11235
11350
  });
11236
- button.tabIndex = -1;
11237
- button.innerHTML = `${icon} <span>${label}</span>`;
11238
- button.addEventListener("click", onClick.bind(this));
11239
-
11240
- return button
11241
11351
  }
11242
11352
 
11243
- #createRowButtonsContainer() {
11244
- const container = createElement("div", { className: "lexxy-table-control" });
11353
+ #selectNextBestCell(command, customIndex = null) {
11354
+ const { childType, direction } = command;
11245
11355
 
11246
- const plusButton = this.#createButton("+", "Add row", () => this.#insertTableRow("end"));
11247
- const minusButton = this.#createButton("−", "Remove row", () => this.#deleteTableRow("end"));
11356
+ let rowIndex = this.currentRowIndex;
11357
+ let columnIndex = customIndex !== null ? customIndex : this.currentColumnIndex;
11248
11358
 
11249
- this.rowCount = createElement("span");
11250
- this.rowCount.textContent = "_ rows";
11359
+ const deleteOffset = command.action === "delete" ? -1 : 0;
11360
+ const offset = direction === "after" ? 1 : deleteOffset;
11251
11361
 
11252
- container.appendChild(minusButton);
11253
- container.appendChild(this.rowCount);
11254
- container.appendChild(plusButton);
11362
+ if (childType === "row") {
11363
+ rowIndex += offset;
11364
+ } else if (childType === "column") {
11365
+ columnIndex += offset;
11366
+ }
11255
11367
 
11256
- return container
11368
+ this.#selectCellAtIndex(rowIndex, columnIndex);
11257
11369
  }
11258
11370
 
11259
- #createColumnButtonsContainer() {
11260
- const container = createElement("div", { className: "lexxy-table-control" });
11371
+ #selectNextRow() {
11372
+ const rows = this.tableRows;
11373
+ if (!rows) return
11261
11374
 
11262
- const plusButton = this.#createButton("+", "Add column", () => this.#insertTableColumn("end"));
11263
- const minusButton = this.#createButton("−", "Remove column", () => this.#deleteTableColumn("end"));
11375
+ const nextRow = rows.at(this.currentRowIndex + 1);
11376
+ if (!nextRow) return
11264
11377
 
11265
- this.columnCount = createElement("span");
11266
- this.columnCount.textContent = "_ columns";
11378
+ this.editor.update(() => {
11379
+ nextRow.getChildAtIndex(this.currentColumnIndex)?.selectEnd();
11380
+ });
11381
+ }
11267
11382
 
11268
- container.appendChild(minusButton);
11269
- container.appendChild(this.columnCount);
11270
- container.appendChild(plusButton);
11383
+ #selectPreviousCell() {
11384
+ const cell = this.currentCell;
11385
+ if (!cell) return
11271
11386
 
11272
- return container
11387
+ this.editor.update(() => {
11388
+ cell.selectPrevious();
11389
+ });
11273
11390
  }
11274
11391
 
11275
- #createMoreMenu() {
11276
- const container = createElement("details", {
11277
- className: "lexxy-table-control lexxy-table-control__more-menu"
11278
- });
11279
- container.setAttribute("name", "lexxy-dropdown");
11392
+ #insertRowAndSelectFirstCell() {
11393
+ this.executeTableCommand({ action: "insert", childType: "row", direction: "after" }, 0);
11394
+ }
11280
11395
 
11281
- container.tabIndex = -1;
11396
+ #deleteRowAndSelectLastCell() {
11397
+ this.executeTableCommand({ action: "delete", childType: "row" }, -1);
11398
+ }
11282
11399
 
11283
- const summary = createElement("summary", {}, "•••");
11284
- container.appendChild(summary);
11400
+ #deleteRowAndSelectNextNode() {
11401
+ const tableNode = this.currentTableNode;
11402
+ this.executeTableCommand({ action: "delete", childType: "row" });
11285
11403
 
11286
- const details = createElement("div", { className: "lexxy-table-control__more-menu-details" });
11287
- container.appendChild(details);
11404
+ this.editor.update(() => {
11405
+ const next = tableNode?.getNextSibling();
11406
+ if (Ii(next)) {
11407
+ next.selectStart();
11408
+ } else {
11409
+ const newParagraph = Li();
11410
+ this.currentTableNode.insertAfter(newParagraph);
11411
+ newParagraph.selectStart();
11412
+ }
11413
+ });
11414
+ }
11288
11415
 
11289
- details.appendChild(this.#createRowSection());
11290
- details.appendChild(this.#createColumnSection());
11291
- details.appendChild(this.#createDeleteTableSection());
11416
+ #isCurrentCellEmpty() {
11417
+ if (!this.currentTableNode) return false
11292
11418
 
11293
- container.addEventListener("toggle", this.#handleMoreMenuToggle.bind(this));
11419
+ const cell = this.currentCell;
11420
+ if (!cell) return false
11294
11421
 
11295
- return container
11422
+ return cell.getTextContent().trim() === ""
11296
11423
  }
11297
11424
 
11298
- #createColumnSection() {
11299
- const columnSection = createElement("section", { className: "lexxy-table-control__more-menu-section" });
11425
+ #isCurrentRowLast() {
11426
+ if (!this.currentTableNode) return false
11300
11427
 
11301
- const columnButtons = [
11302
- { icon: this.#icon("add-column-before"), label: "Add column before", onClick: () => this.#insertTableColumn("left") },
11303
- { icon: this.#icon("add-column-after"), label: "Add column after", onClick: () => this.#insertTableColumn("right") },
11304
- { icon: this.#icon("remove-column"), label: "Remove column", onClick: this.#deleteTableColumn },
11305
- { icon: this.#icon("toggle-column-style"), label: "Toggle column style", onClick: this.#toggleColumnHeaderStyle },
11306
- ];
11428
+ const rows = this.tableRows;
11429
+ if (!rows) return false
11307
11430
 
11308
- columnButtons.forEach(button => {
11309
- const buttonElement = this.#createButton(button.icon, button.label, button.onClick);
11310
- columnSection.appendChild(buttonElement);
11311
- });
11431
+ return rows.length === this.currentRowIndex + 1
11432
+ }
11433
+
11434
+ #isCurrentRowEmpty() {
11435
+ if (!this.currentTableNode) return false
11312
11436
 
11313
- return columnSection
11437
+ const cells = this.currentRowCells;
11438
+ if (!cells) return false
11439
+
11440
+ return cells.every(cell => cell.getTextContent().trim() === "")
11314
11441
  }
11315
11442
 
11316
- #createRowSection() {
11317
- const rowSection = createElement("section", { className: "lexxy-table-control__more-menu-section" });
11443
+ #isFirstCellInRow() {
11444
+ if (!this.currentTableNode) return false
11318
11445
 
11319
- const rowButtons = [
11320
- { icon: this.#icon("add-row-above"), label: "Add row above", onClick: () => this.#insertTableRow("above") },
11321
- { icon: this.#icon("add-row-below"), label: "Add row below", onClick: () => this.#insertTableRow("below") },
11322
- { icon: this.#icon("remove-row"), label: "Remove row", onClick: this.#deleteTableRow },
11323
- { icon: this.#icon("toggle-row-style"), label: "Toggle row style", onClick: this.#toggleRowHeaderStyle }
11324
- ];
11446
+ const cells = this.currentRowCells;
11447
+ if (!cells) return false
11325
11448
 
11326
- rowButtons.forEach(button => {
11327
- const buttonElement = this.#createButton(button.icon, button.label, button.onClick);
11328
- rowSection.appendChild(buttonElement);
11329
- });
11449
+ return cells.indexOf(this.currentCell) === 0
11450
+ }
11451
+
11452
+ #registerKeyHandlers() {
11453
+ // We can't prevent these externally using regular keydown because Lexical handles it first.
11454
+ this.unregisterBackspaceKeyHandler = this.editor.registerCommand(we$1, (event) => this.#handleBackspaceKey(event), Bi);
11455
+ this.unregisterEnterKeyHandler = this.editor.registerCommand(Ne$2, (event) => this.#handleEnterKey(event), Bi);
11456
+ }
11457
+
11458
+ #unregisterKeyHandlers() {
11459
+ this.unregisterBackspaceKeyHandler?.();
11460
+ this.unregisterEnterKeyHandler?.();
11330
11461
 
11331
- return rowSection
11462
+ this.unregisterBackspaceKeyHandler = null;
11463
+ this.unregisterEnterKeyHandler = null;
11332
11464
  }
11333
11465
 
11334
- #createDeleteTableSection() {
11335
- const deleteSection = createElement("section", { className: "lexxy-table-control__more-menu-section" });
11466
+ #handleBackspaceKey(event) {
11467
+ if (!this.currentTableNode) return false
11336
11468
 
11337
- const deleteButton = { icon: this.#icon("delete-table"), label: "Delete table", onClick: this.#deleteTable };
11469
+ if (this.#isCurrentRowEmpty() && this.#isFirstCellInRow()) {
11470
+ event.preventDefault();
11471
+ this.#deleteRowAndSelectLastCell();
11472
+ return true
11473
+ }
11338
11474
 
11339
- const buttonElement = this.#createButton(deleteButton.icon, deleteButton.label, deleteButton.onClick);
11340
- deleteSection.appendChild(buttonElement);
11475
+ if (this.#isCurrentCellEmpty() && !this.#isFirstCellInRow()) {
11476
+ event.preventDefault();
11477
+ this.#selectPreviousCell();
11478
+ return true
11479
+ }
11341
11480
 
11342
- return deleteSection
11481
+ return false
11343
11482
  }
11344
11483
 
11345
- #handleMoreMenuToggle() {
11346
- if (this.moreMenu.open) {
11347
- this.#setFocusStateOnSelectedCell();
11484
+ #handleEnterKey(event) {
11485
+ if ((event.ctrlKey || event.metaKey) || event.shiftKey || !this.currentTableNode) return false
11486
+
11487
+ if (this.selection.isInsideList || this.selection.isInsideCodeBlock) return false
11488
+
11489
+ event.preventDefault();
11490
+
11491
+ if (this.#isCurrentRowLast() && this.#isCurrentRowEmpty()) {
11492
+ this.#deleteRowAndSelectNextNode();
11493
+ } else if (this.#isCurrentRowLast()) {
11494
+ this.#insertRowAndSelectFirstCell();
11348
11495
  } else {
11349
- this.#removeFocusStateFromSelectedCell();
11496
+ this.#selectNextRow();
11350
11497
  }
11498
+
11499
+ return true
11351
11500
  }
11501
+ }
11352
11502
 
11353
- #closeMoreMenu() {
11354
- this.#removeFocusStateFromSelectedCell();
11355
- this.moreMenu.removeAttribute("open");
11503
+ var TableIcons = {
11504
+ "insert-row-before":
11505
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11506
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7.86804e-07 15C8.29055e-07 15.8284 0.671574 16.5 1.5 16.5H15L15.1533 16.4922C15.8593 16.4205 16.4205 15.8593 16.4922 15.1533L16.5 15V4.5L16.4922 4.34668C16.4154 3.59028 15.7767 3 15 3H13.5L13.5 4.5H15V9H1.5L1.5 4.5L3 4.5V3H1.5C0.671574 3 1.20956e-06 3.67157 1.24577e-06 4.5L7.86804e-07 15ZM15 10.5V15H1.5L1.5 10.5H15Z"/>
11507
+ <path d="M4.5 4.5H7.5V7.5H9V4.5H12L12 3L9 3V6.55671e-08L7.5 0V3L4.5 3V4.5Z"/>
11508
+ </svg>`,
11509
+
11510
+ "insert-row-after":
11511
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11512
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7.86804e-07 13.5C7.50592e-07 14.3284 0.671574 15 1.5 15H3V13.5H1.5L1.5 9L15 9V13.5H13.5V15H15C15.7767 15 16.4154 14.4097 16.4922 13.6533L16.5 13.5V3L16.4922 2.84668C16.4205 2.14069 15.8593 1.57949 15.1533 1.50781L15 1.5L1.5 1.5C0.671574 1.5 1.28803e-06 2.17157 1.24577e-06 3L7.86804e-07 13.5ZM15 3V7.5L1.5 7.5L1.5 3L15 3Z"/>
11513
+ <path d="M7.5 15V18H9V15H12V13.5H9V10.5H7.5V13.5H4.5V15H7.5Z"/>
11514
+ </svg>`,
11515
+
11516
+ "delete-row":
11517
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11518
+ <path d="M16.4922 12.1533C16.4154 12.9097 15.7767 13.5 15 13.5L12 13.5V12H15V6L1.5 6L1.5 12H4.5V13.5H1.5C0.723337 13.5 0.0846104 12.9097 0.00781328 12.1533L7.86804e-07 12L1.04907e-06 6C1.17362e-06 5.22334 0.590278 4.58461 1.34668 4.50781L1.5 4.5L15 4.5C15.8284 4.5 16.5 5.17157 16.5 6V12L16.4922 12.1533Z"/>
11519
+ <path d="M10.3711 15.9316L8.25 13.8096L6.12793 15.9316L5.06738 14.8711L7.18945 12.75L5.06738 10.6289L6.12793 9.56836L8.25 11.6895L10.3711 9.56836L11.4316 10.6289L9.31055 12.75L11.4316 14.8711L10.3711 15.9316Z"/>
11520
+ </svg>`,
11521
+
11522
+ "toggle-row":
11523
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11524
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0.00781328 13.6533C0.0846108 14.4097 0.723337 15 1.5 15L15 15L15.1533 14.9922C15.8593 14.9205 16.4205 14.3593 16.4922 13.6533L16.5 13.5V4.5L16.4922 4.34668C16.4205 3.64069 15.8593 3.07949 15.1533 3.00781L15 3L1.5 3C0.671574 3 1.24863e-06 3.67157 1.18021e-06 4.5L7.86804e-07 13.5L0.00781328 13.6533ZM15 9V13.5L1.5 13.5L1.5 9L15 9Z"/>
11525
+ </svg>`,
11526
+
11527
+ "insert-column-before":
11528
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11529
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 0C3.67157 0 3 0.671573 3 1.5V3H4.5V1.5H9V15H4.5V13.5H3V15C3 15.7767 3.59028 16.4154 4.34668 16.4922L4.5 16.5H15L15.1533 16.4922C15.8593 16.4205 16.4205 15.8593 16.4922 15.1533L16.5 15V1.5C16.5 0.671573 15.8284 6.03989e-09 15 0H4.5ZM15 15H10.5V1.5H15V15Z"/>
11530
+ <path d="M3 7.5H0V9H3V12H4.5V9H7.5V7.5H4.5V4.5H3V7.5Z"/>
11531
+ </svg>`,
11532
+
11533
+ "insert-column-after":
11534
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11535
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 0C14.3284 0 15 0.671573 15 1.5V3H13.5V1.5H9V15H13.5V13.5H15V15C15 15.7767 14.4097 16.4154 13.6533 16.4922L13.5 16.5H3L2.84668 16.4922C2.14069 16.4205 1.57949 15.8593 1.50781 15.1533L1.5 15V1.5C1.5 0.671573 2.17157 6.03989e-09 3 0H13.5ZM3 15H7.5V1.5H3V15Z"/>
11536
+ <path d="M15 7.5H18V9H15V12H13.5V9H10.5V7.5H13.5V4.5H15V7.5Z"/>
11537
+ </svg>`,
11538
+
11539
+ "delete-column":
11540
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11541
+ <path d="M12.1533 0.0078125C12.9097 0.0846097 13.5 0.723336 13.5 1.5V4.5H12V1.5H6V15H12V12H13.5V15C13.5 15.7767 12.9097 16.4154 12.1533 16.4922L12 16.5H6C5.22334 16.5 4.58461 15.9097 4.50781 15.1533L4.5 15V1.5C4.5 0.671573 5.17157 2.41596e-08 6 0H12L12.1533 0.0078125Z"/>
11542
+ <path d="M15.9316 6.12891L13.8105 8.24902L15.9326 10.3711L14.8711 11.4316L12.75 9.31055L10.6289 11.4316L9.56738 10.3711L11.6885 8.24902L9.56836 6.12891L10.6289 5.06836L12.75 7.18848L14.8711 5.06836L15.9316 6.12891Z"/>
11543
+ </svg>`,
11544
+
11545
+ "toggle-column":
11546
+ `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11547
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.6533 17.9922C14.4097 17.9154 15 17.2767 15 16.5L15 3L14.9922 2.84668C14.9205 2.14069 14.3593 1.57949 13.6533 1.50781L13.5 1.5L4.5 1.5L4.34668 1.50781C3.59028 1.58461 3 2.22334 3 3L3 16.5C3 17.2767 3.59028 17.9154 4.34668 17.9922L4.5 18L13.5 18L13.6533 17.9922ZM9 3L13.5 3L13.5 16.5L9 16.5L9 3Z" />
11548
+ </svg>`,
11549
+
11550
+ "delete-table":
11551
+ `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
11552
+ <path d="M18.2129 19.2305C18.0925 20.7933 16.7892 22 15.2217 22H7.77832C6.21084 22 4.90753 20.7933 4.78711 19.2305L4 9H19L18.2129 19.2305Z"/><path d="M13 2C14.1046 2 15 2.89543 15 4H19C19.5523 4 20 4.44772 20 5V6C20 6.55228 19.5523 7 19 7H4C3.44772 7 3 6.55228 3 6V5C3 4.44772 3.44772 4 4 4H8C8 2.89543 8.89543 2 10 2H13Z"/>
11553
+ </svg>`
11554
+ };
11555
+
11556
+ class TableTools extends HTMLElement {
11557
+ connectedCallback() {
11558
+ this.tableController = new TableController(this.#editorElement);
11559
+
11560
+ this.#setUpButtons();
11561
+ this.#monitorForTableSelection();
11562
+ this.#registerKeyboardShortcuts();
11356
11563
  }
11357
11564
 
11358
- #monitorForTableSelection() {
11359
- this.#editor.registerUpdateListener(() => {
11360
- this.#editor.getEditorState().read(() => {
11361
- const selection = Lr();
11362
- if (!yr(selection)) return
11565
+ disconnectedCallback() {
11566
+ this.#unregisterKeyboardShortcuts();
11363
11567
 
11364
- const anchorNode = selection.anchor.getNode();
11365
- const tableNode = Qt(anchorNode);
11568
+ this.unregisterUpdateListener?.();
11569
+ this.unregisterUpdateListener = null;
11366
11570
 
11367
- if (tableNode) {
11368
- this.#tableCellWasSelected(tableNode);
11369
- } else {
11370
- this.#hideTableHandlerButtons();
11371
- }
11372
- });
11373
- });
11571
+ this.removeEventListener("keydown", this.#handleToolsKeydown);
11572
+
11573
+ this.tableController?.destroy();
11574
+ this.tableController = null;
11374
11575
  }
11375
11576
 
11376
- #setTableFocusState(focused) {
11377
- this.#editorElement.querySelector("div.node--selected:has(table)")?.classList.remove("node--selected");
11577
+ get #editor() {
11578
+ return this.#editorElement.editor
11579
+ }
11378
11580
 
11379
- if (focused && this.currentTableNode) {
11380
- const tableParent = this.#editor.getElementByKey(this.currentTableNode.getKey());
11381
- if (!tableParent) return
11382
- tableParent.classList.add("node--selected");
11383
- }
11581
+ get #editorElement() {
11582
+ return this.closest("lexxy-editor")
11384
11583
  }
11385
11584
 
11386
- #tableCellWasSelected(tableNode) {
11387
- this.currentTableNode = tableNode;
11388
- this.#updateButtonsPosition(tableNode);
11389
- this.#showTableHandlerButtons();
11585
+ get #tableToolsButtons() {
11586
+ return Array.from(this.querySelectorAll("button, details > summary"))
11390
11587
  }
11391
11588
 
11392
- #setFocusStateOnSelectedCell() {
11393
- this.#editor.getEditorState().read(() => {
11394
- const currentCell = this.#currentCell;
11395
- if (!currentCell) return
11589
+ #setUpButtons() {
11590
+ this.appendChild(this.#createRowButtonsContainer());
11591
+ this.appendChild(this.#createColumnButtonsContainer());
11396
11592
 
11397
- const cellElement = this.#editor.getElementByKey(currentCell.getKey());
11398
- if (!cellElement) return
11593
+ this.appendChild(this.#createDeleteTableButton());
11594
+ this.addEventListener("keydown", this.#handleToolsKeydown);
11595
+ }
11399
11596
 
11400
- cellElement.classList.add("table-cell--selected");
11401
- });
11597
+ #createButtonsContainer(childType, setCountProperty, moreMenu) {
11598
+ const container = createElement("div", { className: `lexxy-table-control lexxy-table-control--${childType}` });
11599
+
11600
+ const plusButton = this.#createButton(`Add ${childType}`, { action: "insert", childType, direction: "after" }, "+");
11601
+ const minusButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType }, "−");
11602
+
11603
+ const dropdown = createElement("details", { className: "lexxy-table-control__more-menu" });
11604
+ dropdown.setAttribute("name", "lexxy-dropdown");
11605
+ dropdown.tabIndex = -1;
11606
+
11607
+ const count = createElement("summary", {}, `_ ${childType}s`);
11608
+ setCountProperty(count);
11609
+ dropdown.appendChild(count);
11610
+
11611
+ dropdown.appendChild(moreMenu);
11612
+
11613
+ container.appendChild(minusButton);
11614
+ container.appendChild(dropdown);
11615
+ container.appendChild(plusButton);
11616
+
11617
+ return container
11402
11618
  }
11403
11619
 
11404
- #removeFocusStateFromSelectedCell() {
11405
- this.#editorElement.querySelector(".table-cell--selected")?.classList.remove("table-cell--selected");
11620
+ #createRowButtonsContainer() {
11621
+ return this.#createButtonsContainer(
11622
+ "row",
11623
+ (count) => { this.rowCount = count; },
11624
+ this.#createMoreMenuSection("row")
11625
+ )
11406
11626
  }
11407
11627
 
11408
- #selectLastTableCell() {
11409
- if (!this.currentTableNode) return
11628
+ #createColumnButtonsContainer() {
11629
+ return this.#createButtonsContainer(
11630
+ "column",
11631
+ (count) => { this.columnCount = count; },
11632
+ this.#createMoreMenuSection("column")
11633
+ )
11634
+ }
11410
11635
 
11411
- const last = this.currentTableNode.getLastChild().getLastChild();
11412
- if (!Oe$1(last)) return
11636
+ #createMoreMenuSection(childType) {
11637
+ const section = createElement("div", { className: "lexxy-table-control__more-menu-details" });
11638
+ const addBeforeButton = this.#createButton(`Add ${childType} before`, { action: "insert", childType, direction: "before" });
11639
+ const addAfterButton = this.#createButton(`Add ${childType} after`, { action: "insert", childType, direction: "after" });
11640
+ const toggleStyleButton = this.#createButton(`Toggle ${childType} style`, { action: "toggle", childType });
11641
+ const deleteButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType });
11413
11642
 
11414
- last.selectEnd();
11643
+ section.appendChild(addBeforeButton);
11644
+ section.appendChild(addAfterButton);
11645
+ section.appendChild(toggleStyleButton);
11646
+ section.appendChild(deleteButton);
11647
+
11648
+ return section
11415
11649
  }
11416
11650
 
11417
- #deleteTable() {
11418
- this.#editor.dispatchCommand("deleteTable");
11651
+ #createDeleteTableButton() {
11652
+ const container = createElement("div", { className: "lexxy-table-control" });
11419
11653
 
11420
- this.#closeMoreMenu();
11421
- this.#updateRowColumnCount();
11422
- }
11654
+ const deleteTableButton = this.#createButton("Delete this table?", { action: "delete", childType: "table" });
11655
+ deleteTableButton.classList.add("lexxy-table-control__button--delete-table");
11656
+
11657
+ container.appendChild(deleteTableButton);
11658
+
11659
+ this.deleteContainer = container;
11423
11660
 
11424
- #insertTableRow(direction) {
11425
- this.#executeTableCommand("insert", "row", direction);
11661
+ return container
11426
11662
  }
11427
11663
 
11428
- #insertTableColumn(direction) {
11429
- this.#executeTableCommand("insert", "column", direction);
11664
+ #createButton(label, command = {}, icon = this.#icon(command)) {
11665
+ const button = createElement("button", {
11666
+ className: "lexxy-table-control__button",
11667
+ "aria-label": label,
11668
+ type: "button"
11669
+ });
11670
+ button.tabIndex = -1;
11671
+ button.innerHTML = `${icon} <span>${label}</span>`;
11672
+
11673
+ button.dataset.action = command.action;
11674
+ button.dataset.childType = command.childType;
11675
+ button.dataset.direction = command.direction;
11676
+
11677
+ button.addEventListener("click", () => this.#executeTableCommand(command));
11678
+
11679
+ button.addEventListener("mouseover", () => this.#handleCommandButtonHover());
11680
+ button.addEventListener("focus", () => this.#handleCommandButtonHover());
11681
+ button.addEventListener("mouseout", () => this.#handleCommandButtonHover());
11682
+
11683
+ return button
11430
11684
  }
11431
11685
 
11432
- #deleteTableRow(direction) {
11433
- this.#executeTableCommand("delete", "row", direction);
11686
+ #registerKeyboardShortcuts() {
11687
+ this.unregisterKeyboardShortcuts = this.#editor.registerCommand(me$1, this.#handleAccessibilityShortcutKey, Bi);
11434
11688
  }
11435
11689
 
11436
- #deleteTableColumn(direction) {
11437
- this.#executeTableCommand("delete", "column", direction);
11690
+ #unregisterKeyboardShortcuts() {
11691
+ this.unregisterKeyboardShortcuts?.();
11692
+ this.unregisterKeyboardShortcuts = null;
11438
11693
  }
11439
11694
 
11440
- #executeTableCommand(action = "insert", childType = "row", direction) {
11441
- this.#editor.update(() => {
11442
- const currentCell = this.#currentCell;
11443
- if (!currentCell) return
11695
+ #handleAccessibilityShortcutKey = (event) => {
11696
+ if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key === "F10") {
11697
+ const firstButton = this.querySelector("button, [tabindex]:not([tabindex='-1'])");
11698
+ firstButton?.focus();
11699
+ }
11700
+ }
11444
11701
 
11445
- if (direction === "end") {
11446
- this.#selectLastTableCell();
11447
- }
11702
+ #handleToolsKeydown = (event) => {
11703
+ if (event.key === "Escape") {
11704
+ this.#handleEscapeKey();
11705
+ } else {
11706
+ handleRollingTabIndex(this.#tableToolsButtons, event);
11707
+ }
11708
+ }
11448
11709
 
11449
- this.#dispatchTableCommand(action, childType, direction);
11710
+ #handleEscapeKey() {
11711
+ const cell = this.tableController.currentCell;
11712
+ if (!cell) return
11450
11713
 
11451
- if (currentCell.isAttached()) {
11452
- currentCell.selectEnd();
11453
- }
11714
+ this.#editor.update(() => {
11715
+ cell.select();
11716
+ this.#editor.focus();
11454
11717
  });
11455
11718
 
11456
- this.#closeMoreMenu();
11457
- this.#updateRowColumnCount();
11719
+ this.#update();
11458
11720
  }
11459
11721
 
11460
- #dispatchTableCommand(action, childType, direction) {
11461
- switch (action) {
11462
- case "insert":
11463
- switch (childType) {
11464
- case "row":
11465
- if (direction === "above") {
11466
- this.#editor.dispatchCommand("insertTableRowAbove");
11467
- } else {
11468
- this.#editor.dispatchCommand("insertTableRowBelow");
11469
- }
11470
- break
11471
- case "column":
11472
- if (direction === "left") {
11473
- this.#editor.dispatchCommand("insertTableColumnBefore");
11474
- } else {
11475
- this.#editor.dispatchCommand("insertTableColumnAfter");
11476
- }
11477
- break
11478
- }
11722
+ async #handleCommandButtonHover() {
11723
+ await nextFrame();
11724
+
11725
+ this.#clearCellStyles();
11726
+
11727
+ const activeElement = this.querySelector("button:hover, button:focus");
11728
+ if (!activeElement) return
11729
+
11730
+ const command = {
11731
+ action: activeElement.dataset.action,
11732
+ childType: activeElement.dataset.childType,
11733
+ direction: activeElement.dataset.direction
11734
+ };
11735
+
11736
+ let cellsToHighlight = null;
11737
+
11738
+ switch (command.childType) {
11739
+ case "row":
11740
+ cellsToHighlight = this.tableController.currentRowCells;
11479
11741
  break
11480
- case "delete":
11481
- switch (childType) {
11482
- case "row":
11483
- this.#editor.dispatchCommand("deleteTableRow");
11484
- break
11485
- case "column":
11486
- this.#editor.dispatchCommand("deleteTableColumn");
11487
- break
11488
- }
11742
+ case "column":
11743
+ cellsToHighlight = this.tableController.currentColumnCells;
11744
+ break
11745
+ case "table":
11746
+ cellsToHighlight = this.tableController.tableRows;
11489
11747
  break
11490
11748
  }
11491
- }
11492
11749
 
11493
- #toggleRowHeaderStyle() {
11494
- this.#editor.update(() => {
11495
- const rows = this.currentTableNode.getChildren();
11750
+ if (!cellsToHighlight) return
11496
11751
 
11497
- const row = rows[this.#currentRow];
11498
- if (!row) return
11752
+ cellsToHighlight.forEach(cell => {
11753
+ const cellElement = this.#editor.getElementByKey(cell.getKey());
11754
+ if (!cellElement) return
11499
11755
 
11500
- const cells = row.getChildren();
11501
- const firstCell = Be$1(cells[0]);
11502
- if (!firstCell) return
11756
+ cellElement.classList.toggle(theme.tableCellHighlight, true);
11757
+ Object.assign(cellElement.dataset, command);
11758
+ });
11759
+ }
11503
11760
 
11504
- const currentStyle = firstCell.getHeaderStyles();
11505
- const newStyle = currentStyle ^ ve$1.ROW;
11761
+ #monitorForTableSelection() {
11762
+ this.unregisterUpdateListener = this.#editor.registerUpdateListener(() => {
11763
+ this.tableController.updateSelectedTable();
11506
11764
 
11507
- cells.forEach(cell => {
11508
- this.#setHeaderStyle(cell, newStyle, ve$1.ROW);
11509
- });
11765
+ const tableNode = this.tableController.currentTableNode;
11766
+ if (tableNode) {
11767
+ this.#show();
11768
+ } else {
11769
+ this.#hide();
11770
+ }
11510
11771
  });
11511
11772
  }
11512
11773
 
11513
- #toggleColumnHeaderStyle() {
11514
- this.#editor.update(() => {
11515
- const rows = this.currentTableNode.getChildren();
11774
+ #executeTableCommand(command) {
11775
+ this.tableController.executeTableCommand(command);
11776
+ this.#update();
11777
+ }
11516
11778
 
11517
- const row = rows[this.#currentRow];
11518
- if (!row) return
11779
+ #show() {
11780
+ this.style.display = "flex";
11781
+ this.#update();
11782
+ }
11519
11783
 
11520
- const cells = row.getChildren();
11521
- const selectedCell = Be$1(cells[this.#currentColumn]);
11522
- if (!selectedCell) return
11784
+ #hide() {
11785
+ this.style.display = "none";
11786
+ this.#clearCellStyles();
11787
+ }
11523
11788
 
11524
- const currentStyle = selectedCell.getHeaderStyles();
11525
- const newStyle = currentStyle ^ ve$1.COLUMN;
11789
+ #update() {
11790
+ this.#updateButtonsPosition();
11791
+ this.#updateRowColumnCount();
11792
+ this.#closeMoreMenu();
11793
+ this.#handleCommandButtonHover();
11794
+ }
11526
11795
 
11527
- rows.forEach(row => {
11528
- const cell = row.getChildren()[this.#currentColumn];
11529
- if (!cell) return
11530
- this.#setHeaderStyle(cell, newStyle, ve$1.COLUMN);
11531
- });
11532
- });
11796
+ #closeMoreMenu() {
11797
+ this.querySelector("details[open]")?.removeAttribute("open");
11533
11798
  }
11534
11799
 
11535
- #setHeaderStyle(cell, newStyle, headerState) {
11536
- const tableCellNode = Be$1(cell);
11800
+ #updateButtonsPosition() {
11801
+ const tableNode = this.tableController.currentTableNode;
11802
+ if (!tableNode) return
11537
11803
 
11538
- if (tableCellNode) {
11539
- tableCellNode.setHeaderStyles(newStyle, headerState);
11540
- }
11541
- }
11542
-
11543
- #icon(name) {
11544
- const icons =
11545
- {
11546
- "add-row-above":
11547
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11548
- <path d="M4 7L0 10V4L4 7ZM6.5 7.5H16.5V6.5H6.5V7.5ZM18 8C18 8.55228 17.5523 9 17 9H6C5.44772 9 5 8.55228 5 8V6C5 5.44772 5.44772 5 6 5H17C17.5523 5 18 5.44772 18 6V8Z"/><path d="M2 2C2 1.44772 2.44772 1 3 1H15C15.5523 1 16 1.44772 16 2C16 2.55228 15.5523 3 15 3H3C2.44772 3 2 2.55228 2 2Z"/><path d="M2 12C2 11.4477 2.44772 11 3 11H15C15.5523 11 16 11.4477 16 12C16 12.5523 15.5523 13 15 13H3C2.44772 13 2 12.5523 2 12Z"/><path d="M2 16C2 15.4477 2.44772 15 3 15H15C15.5523 15 16 15.4477 16 16C16 16.5523 15.5523 17 15 17H3C2.44772 17 2 16.5523 2 16Z"/>
11549
- </svg>`,
11550
-
11551
- "add-row-below":
11552
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11553
- <path d="M4 11L0 8V14L4 11ZM6.5 10.5H16.5V11.5H6.5V10.5ZM18 10C18 9.44772 17.5523 9 17 9H6C5.44772 9 5 9.44772 5 10V12C5 12.5523 5.44772 13 6 13H17C17.5523 13 18 12.5523 18 12V10Z"/><path d="M2 16C2 16.5523 2.44772 17 3 17H15C15.5523 17 16 16.5523 16 16C16 15.4477 15.5523 15 15 15H3C2.44772 15 2 15.4477 2 16Z"/><path d="M2 6C2 6.55228 2.44772 7 3 7H15C15.5523 7 16 6.55228 16 6C16 5.44772 15.5523 5 15 5H3C2.44772 5 2 5.44772 2 6Z"/><path d="M2 2C2 2.55228 2.44772 3 3 3H15C15.5523 3 16 2.55228 16 2C16 1.44772 15.5523 1 15 1H3C2.44772 1 2 1.44772 2 2Z"/>
11554
- </svg>`,
11555
-
11556
- "remove-row":
11557
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11558
- <path d="M17.9951 10.1025C17.9438 10.6067 17.5177 11 17 11H12.4922L13.9922 9.5H16.5V5.5L1.5 5.5L1.5 9.5H4.00586L5.50586 11H1L0.897461 10.9951C0.427034 10.9472 0.0527828 10.573 0.00488281 10.1025L0 10L1.78814e-07 5C2.61831e-07 4.48232 0.393332 4.05621 0.897461 4.00488L1 4L17 4C17.5523 4 18 4.44772 18 5V10L17.9951 10.1025Z"/><path d="M11.2969 15.0146L8.99902 12.7168L6.7002 15.0146L5.63965 13.9541L7.93848 11.6562L5.63965 9.3584L6.7002 8.29785L8.99902 10.5957L11.2969 8.29785L12.3574 9.3584L10.0596 11.6562L12.3574 13.9541L11.2969 15.0146Z"/>
11559
- </svg>`,
11560
-
11561
- "toggle-row-style":
11562
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11563
- <path d="M1 2C1 1.44772 1.44772 1 2 1H7C7.55228 1 8 1.44772 8 2V7C8 7.55228 7.55228 8 7 8H2C1.44772 8 1 7.55228 1 7V2Z"/><path d="M2.5 15.5H6.5V11.5H2.5V15.5ZM8 16C8 16.5177 7.60667 16.9438 7.10254 16.9951L7 17H2L1.89746 16.9951C1.42703 16.9472 1.05278 16.573 1.00488 16.1025L1 16V11C1 10.4477 1.44772 10 2 10H7C7.55228 10 8 10.4477 8 11V16Z"/><path d="M10 2C10 1.44772 10.4477 1 11 1H16C16.5523 1 17 1.44772 17 2V7C17 7.55228 16.5523 8 16 8H11C10.4477 8 10 7.55228 10 7V2Z"/><path d="M11.5 15.5H15.5V11.5H11.5V15.5ZM17 16C17 16.5177 16.6067 16.9438 16.1025 16.9951L16 17H11L10.8975 16.9951C10.427 16.9472 10.0528 16.573 10.0049 16.1025L10 16V11C10 10.4477 10.4477 10 11 10H16C16.5523 10 17 10.4477 17 11V16Z"/>
11564
- </svg>`,
11565
-
11566
- "add-column-before":
11567
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11568
- <path d="M7 4L10 2.62268e-07L4 0L7 4ZM7.5 6.5L7.5 16.5H6.5L6.5 6.5H7.5ZM8 18C8.55228 18 9 17.5523 9 17V6C9 5.44772 8.55229 5 8 5H6C5.44772 5 5 5.44772 5 6L5 17C5 17.5523 5.44772 18 6 18H8Z"/><path d="M2 2C1.44772 2 1 2.44772 1 3L1 15C1 15.5523 1.44772 16 2 16C2.55228 16 3 15.5523 3 15L3 3C3 2.44772 2.55229 2 2 2Z"/><path d="M12 2C11.4477 2 11 2.44772 11 3L11 15C11 15.5523 11.4477 16 12 16C12.5523 16 13 15.5523 13 15L13 3C13 2.44772 12.5523 2 12 2Z"/><path d="M16 2C15.4477 2 15 2.44772 15 3L15 15C15 15.5523 15.4477 16 16 16C16.5523 16 17 15.5523 17 15V3C17 2.44772 16.5523 2 16 2Z"/>
11569
- </svg>`,
11570
-
11571
- "add-column-after":
11572
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11573
- <path d="M11 4L8 2.62268e-07L14 0L11 4ZM10.5 6.5V16.5H11.5V6.5H10.5ZM10 18C9.44772 18 9 17.5523 9 17V6C9 5.44772 9.44772 5 10 5H12C12.5523 5 13 5.44772 13 6V17C13 17.5523 12.5523 18 12 18H10Z"/><path d="M16 2C16.5523 2 17 2.44772 17 3L17 15C17 15.5523 16.5523 16 16 16C15.4477 16 15 15.5523 15 15V3C15 2.44772 15.4477 2 16 2Z"/><path d="M6 2C6.55228 2 7 2.44772 7 3L7 15C7 15.5523 6.55228 16 6 16C5.44772 16 5 15.5523 5 15L5 3C5 2.44772 5.44771 2 6 2Z"/><path d="M2 2C2.55228 2 3 2.44772 3 3L3 15C3 15.5523 2.55228 16 2 16C1.44772 16 1 15.5523 1 15V3C1 2.44772 1.44771 2 2 2Z"/>
11574
- </svg>`,
11575
-
11576
- "remove-column":
11577
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11578
- <path d="M10.1025 0.00488281C10.6067 0.0562145 11 0.482323 11 1V5.50781L9.5 4.00781V1.5H5.5V16.5H9.5V13.9941L11 12.4941V17L10.9951 17.1025C10.9472 17.573 10.573 17.9472 10.1025 17.9951L10 18H5C4.48232 18 4.05621 17.6067 4.00488 17.1025L4 17V1C4 0.447715 4.44772 1.61064e-08 5 0H10L10.1025 0.00488281Z"/><path d="M12.7169 8.99999L15.015 11.2981L13.9543 12.3588L11.6562 10.0607L9.35815 12.3588L8.29749 11.2981L10.5956 8.99999L8.29749 6.7019L9.35815 5.64124L11.6562 7.93933L13.9543 5.64124L15.015 6.7019L12.7169 8.99999Z"/>
11579
- </svg>`,
11580
-
11581
- "toggle-column-style":
11582
- `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
11583
- <path d="M1 2C1 1.44772 1.44772 1 2 1H7C7.55228 1 8 1.44772 8 2V7C8 7.55228 7.55228 8 7 8H2C1.44772 8 1 7.55228 1 7V2Z"/><path d="M1 11C1 10.4477 1.44772 10 2 10H7C7.55228 10 8 10.4477 8 11V16C8 16.5523 7.55228 17 7 17H2C1.44772 17 1 16.5523 1 16V11Z"/><path d="M11.5 6.5H15.5V2.5H11.5V6.5ZM17 7C17 7.51768 16.6067 7.94379 16.1025 7.99512L16 8H11L10.8975 7.99512C10.427 7.94722 10.0528 7.57297 10.0049 7.10254L10 7V2C10 1.44772 10.4477 1 11 1H16C16.5523 1 17 1.44772 17 2V7Z"/><path d="M11.5 15.5H15.5V11.5H11.5V15.5ZM17 16C17 16.5177 16.6067 16.9438 16.1025 16.9951L16 17H11L10.8975 16.9951C10.427 16.9472 10.0528 16.573 10.0049 16.1025L10 16V11C10 10.4477 10.4477 10 11 10H16C16.5523 10 17 10.4477 17 11V16Z"/>
11584
- </svg>`,
11585
-
11586
- "delete-table":
11587
- `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
11588
- <path d="M18.2129 19.2305C18.0925 20.7933 16.7892 22 15.2217 22H7.77832C6.21084 22 4.90753 20.7933 4.78711 19.2305L4 9H19L18.2129 19.2305Z"/><path d="M13 2C14.1046 2 15 2.89543 15 4H19C19.5523 4 20 4.44772 20 5V6C20 6.55228 19.5523 7 19 7H4C3.44772 7 3 6.55228 3 6V5C3 4.44772 3.44772 4 4 4H8C8 2.89543 8.89543 2 10 2H13Z"/>
11589
- </svg>`
11590
- };
11804
+ const tableElement = this.#editor.getElementByKey(tableNode.getKey());
11805
+ if (!tableElement) return
11806
+
11807
+ const tableRect = tableElement.getBoundingClientRect();
11808
+ const editorRect = this.#editorElement.getBoundingClientRect();
11809
+
11810
+ const relativeTop = tableRect.top - editorRect.top;
11811
+ const relativeCenter = (tableRect.left + tableRect.right) / 2 - editorRect.left;
11812
+ this.style.top = `${relativeTop}px`;
11813
+ this.style.left = `${relativeCenter}px`;
11814
+ }
11815
+
11816
+ #updateRowColumnCount() {
11817
+ const tableNode = this.tableController.currentTableNode;
11818
+ if (!tableNode) return
11819
+
11820
+ const tableElement = dn(this.#editor, tableNode);
11821
+ if (!tableElement) return
11822
+
11823
+ const rowCount = tableElement.rows;
11824
+ const columnCount = tableElement.columns;
11591
11825
 
11592
- return icons[name]
11826
+ this.rowCount.textContent = `${rowCount} row${rowCount === 1 ? "" : "s"}`;
11827
+ this.columnCount.textContent = `${columnCount} column${columnCount === 1 ? "" : "s"}`;
11828
+ }
11829
+
11830
+ #setTableCellFocus() {
11831
+ const cell = this.tableController.currentCell;
11832
+ if (!cell) return
11833
+
11834
+ const cellElement = this.#editor.getElementByKey(cell.getKey());
11835
+ if (!cellElement) return
11836
+
11837
+ cellElement.classList.add(theme.tableCellFocus);
11838
+ }
11839
+
11840
+ #clearCellStyles() {
11841
+ this.#editorElement.querySelectorAll(`.${theme.tableCellFocus}`)?.forEach(cell => {
11842
+ cell.classList.remove(theme.tableCellFocus);
11843
+ });
11844
+
11845
+ this.#editorElement.querySelectorAll(`.${theme.tableCellHighlight}`)?.forEach(cell => {
11846
+ cell.classList.remove(theme.tableCellHighlight);
11847
+ cell.removeAttribute("data-action");
11848
+ cell.removeAttribute("data-child-type");
11849
+ cell.removeAttribute("data-direction");
11850
+ });
11851
+
11852
+ this.#setTableCellFocus();
11853
+ }
11854
+
11855
+ #icon(command) {
11856
+ const { action, childType } = command;
11857
+ const direction = (action == "insert" ? command.direction : null);
11858
+ const iconId = [ action, childType, direction ].filter(Boolean).join("-");
11859
+ return TableIcons[iconId]
11593
11860
  }
11594
11861
  }
11595
11862
 
11596
- customElements.define("lexxy-table-handler", TableHandler);
11863
+ customElements.define("lexxy-table-tools", TableTools);
11597
11864
 
11598
11865
  class BaseSource {
11599
11866
  // Template method to override
@@ -11797,25 +12064,30 @@ class LexicalPromptElement extends HTMLElement {
11797
12064
  }
11798
12065
 
11799
12066
  #addTriggerListener() {
11800
- const unregister = this.#editor.registerUpdateListener(() => {
11801
- this.#editor.read(() => {
12067
+ const unregister = this.#editor.registerUpdateListener(({ editorState }) => {
12068
+ editorState.read(() => {
11802
12069
  const { node, offset } = this.#selection.selectedNodeWithOffset();
11803
12070
  if (!node) return
11804
12071
 
11805
- if (lr(node) && offset > 0) {
12072
+ if (lr(node)) {
11806
12073
  const fullText = node.getTextContent();
11807
- const charBeforeCursor = fullText[offset - 1];
12074
+ const triggerLength = this.trigger.length;
12075
+
12076
+ // Check if we have enough characters for the trigger
12077
+ if (offset >= triggerLength) {
12078
+ const textBeforeCursor = fullText.slice(offset - triggerLength, offset);
11808
12079
 
11809
- // Check if trigger is at the start of the text node (new line case) or preceded by space or newline
11810
- if (charBeforeCursor === this.trigger) {
11811
- const isAtStart = offset === 1;
12080
+ // Check if trigger is at the start of the text node (new line case) or preceded by space or newline
12081
+ if (textBeforeCursor === this.trigger) {
12082
+ const isAtStart = offset === triggerLength;
11812
12083
 
11813
- const charBeforeTrigger = offset > 1 ? fullText[offset - 2] : null;
11814
- const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n";
12084
+ const charBeforeTrigger = offset > triggerLength ? fullText[offset - triggerLength - 1] : null;
12085
+ const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n";
11815
12086
 
11816
- if (isAtStart || isPrecededBySpaceOrNewline) {
11817
- unregister();
11818
- this.#showPopover();
12087
+ if (isAtStart || isPrecededBySpaceOrNewline) {
12088
+ unregister();
12089
+ this.#showPopover();
12090
+ }
11819
12091
  }
11820
12092
  }
11821
12093
  }
@@ -11835,9 +12107,10 @@ class LexicalPromptElement extends HTMLElement {
11835
12107
  const fullText = node.getTextContent();
11836
12108
  const textBeforeCursor = fullText.slice(0, offset);
11837
12109
  const lastTriggerIndex = textBeforeCursor.lastIndexOf(this.trigger);
12110
+ const triggerEndIndex = lastTriggerIndex + this.trigger.length - 1;
11838
12111
 
11839
- // If trigger is not found, or cursor is at or before the trigger position, hide popover
11840
- if (lastTriggerIndex === -1 || offset <= lastTriggerIndex) {
12112
+ // If trigger is not found, or cursor is at or before the trigger end position, hide popover
12113
+ if (lastTriggerIndex === -1 || offset <= triggerEndIndex) {
11841
12114
  this.#hidePopover();
11842
12115
  }
11843
12116
  } else {
@@ -12281,8 +12554,10 @@ class CodeLanguagePicker extends HTMLElement {
12281
12554
  const codeRect = codeElement.getBoundingClientRect();
12282
12555
  const editorRect = this.editorElement.getBoundingClientRect();
12283
12556
  const relativeTop = codeRect.top - editorRect.top;
12557
+ const relativeRight = editorRect.right - codeRect.right;
12284
12558
 
12285
12559
  this.style.top = `${relativeTop}px`;
12560
+ this.style.right = `${relativeRight}px`;
12286
12561
  }
12287
12562
 
12288
12563
  #showLanguagePicker() {