lexxy 0.1.10.beta → 0.1.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fffdc9d43469a6366a3d6fa16b6e5b8284c5a19bdcfa3cbffeb79a96cf16970
4
- data.tar.gz: b1e76d677c2c7e9c55ff713eba32c89f04cc0e267153f4e4def7cf48238dc733
3
+ metadata.gz: 72557f2f915d20c07de190adaad818d1900728371f65482d8f01a51b3b4c5f24
4
+ data.tar.gz: 0fabe3de15525414a0e7e548f4d8e5c605bff3e26074bc79541c20bbcb92946c
5
5
  SHA512:
6
- metadata.gz: 0c7752a992efceb4f40f411bb154037a21b7ffeb83db7dc28bf882f68ed2ff57f21ebe4c1d61dc420d6b344fd45fb8d819595a753e33d90134a8e3536d6fd0ba
7
- data.tar.gz: 761ce9eee667c72a4cab67a5572cad3013f468c9248f415477c144b640a26852400c07afcdbe287afe492f858d54a798281ac96c89814fd69683f3ecd4488d77
6
+ metadata.gz: 31d13d76af610866ca92fc340c90f71d3b8a44e6a27f616feb76c2f56cf6f074f22323edd67258054982386f480ab37b9d7b32ff7d7e946be3ec2fc8cb7371bd
7
+ data.tar.gz: 73f85f0ed7d75452cc6d0c28cc3cfd45d0f1bb544068287b25822b060c14926410d09d7923738879c6f15164a644daea3b3097cfff0cd4a34fbbd3a76f0c5c74
@@ -5091,6 +5091,17 @@ function getListType(node) {
5091
5091
  return null
5092
5092
  }
5093
5093
 
5094
+ function isPrintableCharacter(event) {
5095
+ // Ignore if modifier keys are pressed (except Shift for uppercase)
5096
+ if (event.ctrlKey || event.metaKey || event.altKey) return false
5097
+
5098
+ // Ignore special keys
5099
+ if (event.key.length > 1 && event.key !== 'Enter' && event.key !== 'Space') return false
5100
+
5101
+ // Accept single character keys (letters, numbers, punctuation)
5102
+ return event.key.length === 1
5103
+ }
5104
+
5094
5105
  class LexicalToolbarElement extends HTMLElement {
5095
5106
  constructor() {
5096
5107
  super();
@@ -5950,6 +5961,8 @@ class CommandDispatcher {
5950
5961
 
5951
5962
  dispatchInsertUnorderedList() {
5952
5963
  const selection = Nr();
5964
+ if (!selection) return;
5965
+
5953
5966
  const anchorNode = selection.anchor.getNode();
5954
5967
 
5955
5968
  if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "bullet") {
@@ -5961,6 +5974,8 @@ class CommandDispatcher {
5961
5974
 
5962
5975
  dispatchInsertOrderedList() {
5963
5976
  const selection = Nr();
5977
+ if (!selection) return;
5978
+
5964
5979
  const anchorNode = selection.anchor.getNode();
5965
5980
 
5966
5981
  if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "number") {
@@ -6133,11 +6148,14 @@ function nextFrame() {
6133
6148
  class Selection {
6134
6149
  constructor(editorElement) {
6135
6150
  this.editorElement = editorElement;
6151
+ this.editorContentElement = editorElement.editorContentElement;
6136
6152
  this.editor = this.editorElement.editor;
6137
6153
  this.previouslySelectedKeys = new Set();
6138
6154
 
6139
6155
  this.#listenForNodeSelections();
6140
6156
  this.#processSelectionChangeCommands();
6157
+ this.#handleInputWhenDecoratorNodesSelected();
6158
+ this.#containEditorFocus();
6141
6159
  }
6142
6160
 
6143
6161
  clear() {
@@ -6231,6 +6249,21 @@ class Selection {
6231
6249
  return this.#findNextSiblingUp(anchorNode)
6232
6250
  }
6233
6251
 
6252
+ get topLevelNodeAfterCursor() {
6253
+ const { anchorNode, offset } = this.#getCollapsedSelectionData();
6254
+ if (!anchorNode) return null
6255
+
6256
+ if (Qn(anchorNode)) {
6257
+ return this.#getNextNodeFromTextEnd(anchorNode)
6258
+ }
6259
+
6260
+ if (di(anchorNode)) {
6261
+ return this.#getNodeAfterElementNode(anchorNode, offset)
6262
+ }
6263
+
6264
+ return this.#findNextSiblingUp(anchorNode)
6265
+ }
6266
+
6234
6267
  get nodeBeforeCursor() {
6235
6268
  const { anchorNode, offset } = this.#getCollapsedSelectionData();
6236
6269
  if (!anchorNode) return null
@@ -6246,6 +6279,21 @@ class Selection {
6246
6279
  return this.#findPreviousSiblingUp(anchorNode)
6247
6280
  }
6248
6281
 
6282
+ get topLevelNodeBeforeCursor() {
6283
+ const { anchorNode, offset } = this.#getCollapsedSelectionData();
6284
+ if (!anchorNode) return null
6285
+
6286
+ if (Qn(anchorNode)) {
6287
+ return this.#getPreviousNodeFromTextStart(anchorNode)
6288
+ }
6289
+
6290
+ if (di(anchorNode)) {
6291
+ return this.#getNodeBeforeElementNode(anchorNode, offset)
6292
+ }
6293
+
6294
+ return this.#findPreviousSiblingUp(anchorNode)
6295
+ }
6296
+
6249
6297
  get #contents() {
6250
6298
  return this.editorElement.contents
6251
6299
  }
@@ -6266,9 +6314,9 @@ class Selection {
6266
6314
 
6267
6315
  #processSelectionChangeCommands() {
6268
6316
  this.editor.registerCommand(Te$1, this.#selectPreviousNode.bind(this), Ii);
6269
- this.editor.registerCommand(Ne$1, this.#selectPreviousNode.bind(this), Ii);
6270
6317
  this.editor.registerCommand(ve$1, this.#selectNextNode.bind(this), Ii);
6271
- this.editor.registerCommand(we$1, this.#selectNextNode.bind(this), Ii);
6318
+ this.editor.registerCommand(Ne$1, this.#selectPreviousTopLevelNode.bind(this), Ii);
6319
+ this.editor.registerCommand(we$1, this.#selectNextTopLevelNode.bind(this), Ii);
6272
6320
 
6273
6321
  this.editor.registerCommand(De$1, this.#deleteSelectedOrNext.bind(this), Ii);
6274
6322
  this.editor.registerCommand(Ae$1, this.#deletePreviousOrNext.bind(this), Ii);
@@ -6299,6 +6347,93 @@ class Selection {
6299
6347
  });
6300
6348
  }
6301
6349
 
6350
+ // In Safari, when the only node in the document is an attachment, it won't let you enter text
6351
+ // before/below it. There is probably a better fix here, but this workaround solves the problem until
6352
+ // we find it.
6353
+ #handleInputWhenDecoratorNodesSelected() {
6354
+ this.editor.getRootElement().addEventListener("keydown", (event) => {
6355
+ if (isPrintableCharacter(event)) {
6356
+ this.editor.update(() => {
6357
+ const selection = Nr();
6358
+
6359
+ if (cr(selection) && selection.isCollapsed()) {
6360
+ const anchorNode = selection.anchor.getNode();
6361
+ const offset = selection.anchor.offset;
6362
+
6363
+ const nodeBefore = this.#getNodeBeforePosition(anchorNode, offset);
6364
+ const nodeAfter = this.#getNodeAfterPosition(anchorNode, offset);
6365
+
6366
+ if (nodeBefore instanceof gi && !nodeBefore.isInline()) {
6367
+ event.preventDefault();
6368
+ this.#contents.createParagraphAfterNode(nodeBefore, event.key);
6369
+ return
6370
+ } else if (nodeAfter instanceof gi && !nodeAfter.isInline()) {
6371
+ event.preventDefault();
6372
+ this.#contents.createParagraphBeforeNode(nodeAfter, event.key);
6373
+ return
6374
+ }
6375
+ }
6376
+ });
6377
+ }
6378
+ }, true);
6379
+ }
6380
+
6381
+ #getNodeBeforePosition(node, offset) {
6382
+ if (Qn(node) && offset === 0) {
6383
+ return node.getPreviousSibling()
6384
+ }
6385
+ if (di(node) && offset > 0) {
6386
+ return node.getChildAtIndex(offset - 1)
6387
+ }
6388
+ return null
6389
+ }
6390
+
6391
+ #getNodeAfterPosition(node, offset) {
6392
+ if (Qn(node) && offset === node.getTextContentSize()) {
6393
+ return node.getNextSibling()
6394
+ }
6395
+ if (di(node)) {
6396
+ return node.getChildAtIndex(offset)
6397
+ }
6398
+ return null
6399
+ }
6400
+
6401
+ #containEditorFocus() {
6402
+ // Workaround for a bizarre Chrome bug where the cursor abandons the editor to focus on not-focusable elements
6403
+ // above when navigating UP/DOWN when Lexical shows its fake cursor on custom decorator nodes.
6404
+ this.editorContentElement.addEventListener("keydown", (event) => {
6405
+ if (event.key === "ArrowUp") {
6406
+ const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
6407
+
6408
+ if (lexicalCursor) {
6409
+ let currentElement = lexicalCursor.previousElementSibling;
6410
+ while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
6411
+ currentElement = currentElement.previousElementSibling;
6412
+ }
6413
+
6414
+ if (!currentElement) {
6415
+ event.preventDefault();
6416
+ }
6417
+ }
6418
+ }
6419
+
6420
+ if (event.key === "ArrowDown") {
6421
+ const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
6422
+
6423
+ if (lexicalCursor) {
6424
+ let currentElement = lexicalCursor.nextElementSibling;
6425
+ while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
6426
+ currentElement = currentElement.nextElementSibling;
6427
+ }
6428
+
6429
+ if (!currentElement) {
6430
+ event.preventDefault();
6431
+ }
6432
+ }
6433
+ }
6434
+ }, true);
6435
+ }
6436
+
6302
6437
  #syncSelectedClasses() {
6303
6438
  this.#clearPreviouslyHighlightedItems();
6304
6439
  this.#highlightNewItems();
@@ -6341,6 +6476,22 @@ class Selection {
6341
6476
  }
6342
6477
  }
6343
6478
 
6479
+ async #selectPreviousTopLevelNode() {
6480
+ if (this.current) {
6481
+ await this.#withCurrentNode((currentNode) => currentNode.selectPrevious());
6482
+ } else {
6483
+ this.#selectInLexical(this.topLevelNodeBeforeCursor);
6484
+ }
6485
+ }
6486
+
6487
+ async #selectNextTopLevelNode() {
6488
+ if (this.current) {
6489
+ await this.#withCurrentNode((currentNode) => currentNode.selectNext(0, 0));
6490
+ } else {
6491
+ this.#selectInLexical(this.topLevelNodeAfterCursor);
6492
+ }
6493
+ }
6494
+
6344
6495
  async #withCurrentNode(fn) {
6345
6496
  await nextFrame();
6346
6497
  if (this.current) {
@@ -6895,6 +7046,30 @@ class Contents {
6895
7046
  });
6896
7047
  }
6897
7048
 
7049
+ createParagraphAfterNode(node, text) {
7050
+ const newParagraph = Pi();
7051
+ node.insertAfter(newParagraph);
7052
+ newParagraph.selectStart();
7053
+
7054
+ // Insert the typed text
7055
+ if (text) {
7056
+ newParagraph.append(Xn(text));
7057
+ newParagraph.select(1, 1); // Place cursor after the text
7058
+ }
7059
+ }
7060
+
7061
+ createParagraphBeforeNode(node, text) {
7062
+ const newParagraph = Pi();
7063
+ node.insertBefore(newParagraph);
7064
+ newParagraph.selectStart();
7065
+
7066
+ // Insert the typed text
7067
+ if (text) {
7068
+ newParagraph.append(Xn(text));
7069
+ newParagraph.select(1, 1); // Place cursor after the text
7070
+ }
7071
+ }
7072
+
6898
7073
  uploadFile(file) {
6899
7074
  if (!this.editorElement.supportsAttachments) {
6900
7075
  console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
@@ -6929,25 +7104,34 @@ class Contents {
6929
7104
  deleteSelectedNodes() {
6930
7105
  this.editor.update(() => {
6931
7106
  if (ur(this.#selection.current)) {
6932
- let nodesWereRemoved = false;
6933
- this.#selection.current.getNodes().forEach((node) => {
7107
+ const nodesToRemove = this.#selection.current.getNodes();
7108
+ if (nodesToRemove.length === 0) return
7109
+
7110
+ // Use splice() instead of node.remove() for proper removal and
7111
+ // reconciliation. Would have issues with removing unintended decorator nodes
7112
+ // with node.remove()
7113
+ nodesToRemove.forEach((node) => {
6934
7114
  const parent = node.getParent();
7115
+ if (!di(parent)) return
6935
7116
 
6936
- node.remove();
7117
+ const children = parent.getChildren();
7118
+ const index = children.indexOf(node);
6937
7119
 
6938
- if (parent.getType() === "root" && parent.getChildrenSize() === 0) {
6939
- parent.append(Pi());
7120
+ if (index >= 0) {
7121
+ parent.splice(index, 1, []);
6940
7122
  }
6941
-
6942
- nodesWereRemoved = true;
6943
7123
  });
6944
7124
 
6945
- if (nodesWereRemoved) {
6946
- this.#selection.clear();
6947
- this.editor.focus();
6948
-
6949
- return true
7125
+ // Check if root is empty after all removals
7126
+ const root = ps();
7127
+ if (root.getChildrenSize() === 0) {
7128
+ root.append(Pi());
6950
7129
  }
7130
+
7131
+ this.#selection.clear();
7132
+ this.editor.focus();
7133
+
7134
+ return true
6951
7135
  }
6952
7136
  });
6953
7137
  }
@@ -7435,9 +7619,10 @@ class LexicalEditorElement extends HTMLElement {
7435
7619
  static debug = true
7436
7620
  static commands = [ "bold", "italic", "" ]
7437
7621
 
7438
- static observedAttributes = [ "connected" ]
7622
+ static observedAttributes = [ "connected", "required" ]
7439
7623
 
7440
7624
  #initialValue = ""
7625
+ #validationTextArea = document.createElement("textarea")
7441
7626
 
7442
7627
  constructor() {
7443
7628
  super();
@@ -7470,6 +7655,11 @@ class LexicalEditorElement extends HTMLElement {
7470
7655
  if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
7471
7656
  requestAnimationFrame(() => this.#reconnect());
7472
7657
  }
7658
+
7659
+ if (name === "required" && this.isConnected) {
7660
+ this.#validationTextArea.required = this.hasAttribute("required");
7661
+ this.#setValidity();
7662
+ }
7473
7663
  }
7474
7664
 
7475
7665
  formResetCallback() {
@@ -7523,7 +7713,7 @@ class LexicalEditorElement extends HTMLElement {
7523
7713
  Ys(Mi);
7524
7714
  const root = ps();
7525
7715
  root.clear();
7526
- root.append(...this.#parseHtmlIntoLexicalNodes(html));
7716
+ if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html));
7527
7717
  root.select();
7528
7718
 
7529
7719
  this.#toggleEmptyStatus();
@@ -7636,6 +7826,7 @@ class LexicalEditorElement extends HTMLElement {
7636
7826
 
7637
7827
  this.internals.setFormValue(html);
7638
7828
  this._internalFormValue = html;
7829
+ this.#validationTextArea.value = this.#isEmpty ? "" : html;
7639
7830
 
7640
7831
  if (changed) {
7641
7832
  dispatch(this, "lexxy:change");
@@ -7664,7 +7855,7 @@ class LexicalEditorElement extends HTMLElement {
7664
7855
  this.cachedValue = null;
7665
7856
  this.#internalFormValue = this.value;
7666
7857
  this.#toggleEmptyStatus();
7667
- this.#validateRequired();
7858
+ this.#setValidity();
7668
7859
  }));
7669
7860
  }
7670
7861
 
@@ -7771,11 +7962,11 @@ class LexicalEditorElement extends HTMLElement {
7771
7962
  return !this.editorContentElement.textContent.trim() && !containsVisuallyRelevantChildren(this.editorContentElement)
7772
7963
  }
7773
7964
 
7774
- #validateRequired() {
7775
- if (this.hasAttribute("required") && this.#isEmpty) {
7776
- this.internals.setValidity({ valueMissing: true }, "Please fill out this field.", this.editorContentElement);
7777
- } else {
7965
+ #setValidity() {
7966
+ if (this.#validationTextArea.validity.valid) {
7778
7967
  this.internals.setValidity({});
7968
+ } else {
7969
+ this.internals.setValidity(this.#validationTextArea.validity, this.#validationTextArea.validationMessage, this.editorContentElement);
7779
7970
  }
7780
7971
  }
7781
7972
 
Binary file
Binary file