lexxy 1.0.2 → 1.0.3

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: f75c0788ef54e0cee25bbb2ef6c037d570b44f9dc40d5cdbb7373b168789dfbc
4
- data.tar.gz: dad6f3a8057d1317a01f67f43081b3121af8562a09f4a2d6ec5f5d45878456f5
3
+ metadata.gz: f90f12597c2b6b34e544760455df98d3c4a8273230b159ff57e6ba693175a72b
4
+ data.tar.gz: 1ed0a3faa6d8ebc6bb810c102817ad281a7e638630d9c2f242d6c85c97276412
5
5
  SHA512:
6
- metadata.gz: 21fec3530465ce58796bdb7c596ef491007d90e76e2991699647de9ede990913a85d2f630696e8e914e48c0a54234b0ea8a806c453dab04a6b5dfd61da75f330
7
- data.tar.gz: '0584ef6c45f3262e8035715d7e48bc5bba837f6abb7d377ce0f80f29ce4f741e4f9a25c0a76f92939d0cd99a643c822a6d1e31eb7ce9a8e85c4f7acbac9daed4'
6
+ metadata.gz: 47ecc7aedf386f5d58e9e72f86eb4d81e4c61a44d858becf2096459fc64c8cfc016c80f68b737d4ecc82307be76e3508e7f3c48557d7177afe229286f1cc36a4
7
+ data.tar.gz: 6b40a63ef8cf45c5451e3190c4feee447358e1c224f259d370d5337e1b2a1f2a70126e98b307e51fa4247289cfecf0c5b4c50cb5267dbaf5d0b56d18c34b9581
@@ -7754,8 +7754,17 @@ class LexicalToolbarElement extends HTMLElement {
7754
7754
 
7755
7755
  #updateButtonStates() {
7756
7756
  const selection = Qr();
7757
- if (!Fr(selection)) return
7758
7757
 
7758
+ this.#setButtonDisabled("link", Ir(selection) && !this.selection.hasLinkableAttachment);
7759
+
7760
+ if (Ir(selection)) {
7761
+ this.#setButtonPressed("link", this.selection.isInLink);
7762
+ } else if (Fr(selection)) {
7763
+ this.#updateFormatButtonStates(selection);
7764
+ }
7765
+ }
7766
+
7767
+ #updateFormatButtonStates(selection) {
7759
7768
  const anchorNode = selection.anchor.getNode();
7760
7769
  if (!anchorNode.getParent()) { return }
7761
7770
 
@@ -8138,7 +8147,7 @@ class ToolbarDropdown extends HTMLElement {
8138
8147
  #handleTriggerClick = (event) => {
8139
8148
  if (this.isOpen) {
8140
8149
  this.close({ focusEditor: false });
8141
- } else {
8150
+ } else if (this.trigger.ariaDisabled !== "true") {
8142
8151
  this.#shouldReturnFocusToTrigger = this.#isOpenedFromHost(event);
8143
8152
  this.host?.closeDropdowns({ except: this });
8144
8153
  this.open();
@@ -9751,9 +9760,11 @@ function $consecutiveSiblingGroups(blocks) {
9751
9760
  return groups
9752
9761
  }
9753
9762
 
9754
- // Payload: Record<nodeKey, { patch?, replace? }>
9763
+ // Payload: Record<nodeKey, { patch?, replace?, replaceInHistory? }>
9755
9764
  // - patch: plain object, shallow-merged into the existing node's properties
9756
9765
  // - replace: a LexicalNode instance that replaces the node
9766
+ // - replaceInHistory: builds each history entry's replacement from the node
9767
+ // that entry holds, so edits made between entries survive; defaults to replace
9757
9768
  const REWRITE_HISTORY_COMMAND = re$5("REWRITE_HISTORY_COMMAND");
9758
9769
 
9759
9770
  class RewritableHistoryExtension extends LexxyExtension {
@@ -9813,14 +9824,14 @@ class RewritableHistoryExtension extends LexxyExtension {
9813
9824
 
9814
9825
  const editorState = entry.editorState = safeCloneEditorState(entry.editorState);
9815
9826
 
9816
- for (const [ nodeKey, { patch, replace } ] of Object.entries(rewrites)) {
9827
+ for (const [ nodeKey, { patch, replace, replaceInHistory } ] of Object.entries(rewrites)) {
9817
9828
  const node = editorState._nodeMap.get(nodeKey);
9818
9829
  if (!node) continue
9819
9830
 
9820
9831
  if (patch) {
9821
9832
  this.#patchNodeInEditorState(editorState, node, patch);
9822
9833
  } else if (replace) {
9823
- this.#replaceNodeInEditorState(editorState, node, replace);
9834
+ this.#replaceNodeInEditorState(editorState, node, replaceInHistory?.(node) ?? replace);
9824
9835
  }
9825
9836
  }
9826
9837
  }
@@ -9894,6 +9905,7 @@ class ActionTextAttachmentNode extends Ji {
9894
9905
  conversion: (attachment) => ({
9895
9906
  node: new ActionTextAttachmentNode({
9896
9907
  sgid: attachment.getAttribute("sgid"),
9908
+ href: attachment.getAttribute("href"),
9897
9909
  src: attachment.getAttribute("url"),
9898
9910
  previewable: attachment.getAttribute("previewable"),
9899
9911
  altText: attachment.getAttribute("alt"),
@@ -9948,10 +9960,11 @@ class ActionTextAttachmentNode extends Ji {
9948
9960
  return Lexxy.global.get("attachmentTagName")
9949
9961
  }
9950
9962
 
9951
- constructor({ sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError } = {}, key) {
9963
+ constructor({ sgid, href, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError } = {}, key) {
9952
9964
  super(key);
9953
9965
 
9954
9966
  this.sgid = sgid;
9967
+ this.href = href || null;
9955
9968
  this.src = src;
9956
9969
  this.previewSrc = previewSrc;
9957
9970
  this.previewable = parseBoolean(previewable);
@@ -10024,6 +10037,7 @@ class ActionTextAttachmentNode extends Ji {
10024
10037
  exportDOM() {
10025
10038
  const attachment = createElement(ActionTextAttachmentNode.TAG_NAME, {
10026
10039
  sgid: this.sgid,
10040
+ href: this.href,
10027
10041
  previewable: this.previewable || null,
10028
10042
  url: this.src,
10029
10043
  alt: this.altText,
@@ -10044,6 +10058,7 @@ class ActionTextAttachmentNode extends Ji {
10044
10058
  type: "action_text_attachment",
10045
10059
  version: 1,
10046
10060
  sgid: this.sgid,
10061
+ href: this.href,
10047
10062
  src: this.src,
10048
10063
  previewable: this.previewable,
10049
10064
  previewStatusUrl: this.previewStatusUrl,
@@ -10115,9 +10130,9 @@ class ActionTextAttachmentNode extends Ji {
10115
10130
  });
10116
10131
  }
10117
10132
 
10118
- replaceAndRewriteHistory(node) {
10133
+ replaceAndRewriteHistory(node, replaceInHistory) {
10119
10134
  this.editor.dispatchCommand(REWRITE_HISTORY_COMMAND, {
10120
- [this.getKey()]: { replace: node }
10135
+ [this.getKey()]: { replace: node, replaceInHistory }
10121
10136
  });
10122
10137
  }
10123
10138
 
@@ -10313,6 +10328,12 @@ function $isActionTextAttachmentNode(node) {
10313
10328
  return node instanceof ActionTextAttachmentNode
10314
10329
  }
10315
10330
 
10331
+ // Only images can carry a link: Action Text keeps an attachment's `href`, and a
10332
+ // host's blob partial can wrap the image in it.
10333
+ function $isLinkableAttachmentNode(node) {
10334
+ return $isActionTextAttachmentNode(node) && node.isPreviewableImage
10335
+ }
10336
+
10316
10337
  async function loadFileIntoImage(file, image) {
10317
10338
  return new Promise((resolve) => {
10318
10339
  const reader = new FileReader();
@@ -10562,14 +10583,20 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
10562
10583
  $showUploadedAttachment(blob) {
10563
10584
  const previewSrc = this.isPreviewableImage && this.#canPreviewLocally ? URL.createObjectURL(this.file) : null;
10564
10585
 
10565
- const replacementNode = this.#toActionTextAttachmentNodeWith(blob, previewSrc);
10566
- this.replaceAndRewriteHistory(replacementNode);
10586
+ const replacementNode = this.#toActionTextAttachmentNodeWith(this.#latest, blob, previewSrc);
10587
+ this.replaceAndRewriteHistory(replacementNode, (uploadNode) => this.#toActionTextAttachmentNodeWith(uploadNode, blob, previewSrc));
10567
10588
 
10568
10589
  return replacementNode.getKey()
10569
10590
  }
10570
10591
 
10571
- #toActionTextAttachmentNodeWith(blob, previewSrc) {
10572
- const conversion = new AttachmentNodeConversion(this, blob, previewSrc);
10592
+ // The node the upload started from, as edited since. It is gone when the
10593
+ // attachment was removed mid-upload, and only history entries still hold it.
10594
+ get #latest() {
10595
+ return Yo(this.getKey()) ?? this
10596
+ }
10597
+
10598
+ #toActionTextAttachmentNodeWith(uploadNode, blob, previewSrc) {
10599
+ const conversion = new AttachmentNodeConversion(uploadNode, blob, previewSrc);
10573
10600
  return conversion.toAttachmentNode()
10574
10601
  }
10575
10602
 
@@ -12057,17 +12084,11 @@ class CommandDispatcher {
12057
12084
  this.selection.placeCursorInsideAdjacentLink();
12058
12085
 
12059
12086
  const selection = Qr();
12060
- if (!Fr(selection)) return
12061
-
12062
- const anchorNode = selection.anchor.getNode();
12063
12087
 
12064
- if (selection.isCollapsed() && !At$2(anchorNode, F$4)) {
12065
- const autoLinkNode = H$5(url);
12066
- const textNode = kr(url);
12067
- autoLinkNode.append(textNode);
12068
- selection.insertNodes([ autoLinkNode ]);
12069
- } else {
12070
- Q$5(url);
12088
+ if (Ir(selection)) {
12089
+ $setHrefOnSelectedImages(selection, url);
12090
+ } else if (Fr(selection)) {
12091
+ $linkSelectedText(url, selection);
12071
12092
  }
12072
12093
  });
12073
12094
  }
@@ -12081,7 +12102,13 @@ class CommandDispatcher {
12081
12102
  return
12082
12103
  }
12083
12104
 
12084
- Q$5(null);
12105
+ const selection = Qr();
12106
+
12107
+ if (Ir(selection)) {
12108
+ $setHrefOnSelectedImages(selection, null);
12109
+ } else {
12110
+ Q$5(null);
12111
+ }
12085
12112
  });
12086
12113
  }
12087
12114
 
@@ -12429,6 +12456,25 @@ class CommandDispatcher {
12429
12456
 
12430
12457
  }
12431
12458
 
12459
+ function $setHrefOnSelectedImages(selection, href) {
12460
+ for (const node of selection.getNodes()) {
12461
+ if ($isLinkableAttachmentNode(node)) node.getWritable().href = href;
12462
+ }
12463
+ }
12464
+
12465
+ function $linkSelectedText(url, selection) {
12466
+ const anchorNode = selection.anchor.getNode();
12467
+
12468
+ if (selection.isCollapsed() && !At$2(anchorNode, F$4)) {
12469
+ const autoLinkNode = H$5(url);
12470
+ const textNode = kr(url);
12471
+ autoLinkNode.append(textNode);
12472
+ selection.insertNodes([ autoLinkNode ]);
12473
+ } else {
12474
+ Q$5(url);
12475
+ }
12476
+ }
12477
+
12432
12478
  function capitalize(str) {
12433
12479
  return str.charAt(0).toUpperCase() + str.slice(1)
12434
12480
  }
@@ -12525,7 +12571,7 @@ class Selection {
12525
12571
  isStrikethrough: selection.hasFormat("strikethrough"),
12526
12572
  isUnderline: selection.hasFormat("underline"),
12527
12573
  isHighlight: isSelectionHighlighted(selection),
12528
- isInLink: this.linkUrl !== null,
12574
+ isInLink: this.isInLink,
12529
12575
  isInQuote: Mt$2(topLevelElement),
12530
12576
  isInHeading: headingNode !== null,
12531
12577
  isInCode: this.#isInCode(selection, anchorNode),
@@ -12536,8 +12582,18 @@ class Selection {
12536
12582
  }
12537
12583
  }
12538
12584
 
12585
+ get isInLink() {
12586
+ return this.linkUrl !== null
12587
+ }
12588
+
12539
12589
  get linkUrl() {
12540
- return this.linkAtCursor?.getURL() ?? null
12590
+ const { node } = this.selectedNodeWithOffset();
12591
+
12592
+ if ($isActionTextAttachmentNode(node)) {
12593
+ return $isLinkableAttachmentNode(node) ? node.href : null
12594
+ } else {
12595
+ return this.linkAtCursor?.getURL() ?? null
12596
+ }
12541
12597
  }
12542
12598
 
12543
12599
  get linkAtCursor() {
@@ -12545,6 +12601,11 @@ class Selection {
12545
12601
  return At$2(anchorNode, F$4) ?? this.#linkEndingAtCursor()
12546
12602
  }
12547
12603
 
12604
+ get hasLinkableAttachment() {
12605
+ const selection = Qr();
12606
+ return Ir(selection) && selection.getNodes().some($isLinkableAttachmentNode)
12607
+ }
12608
+
12548
12609
  nearestNodeOfType(nodeType) {
12549
12610
  const anchorNode = Qr()?.anchor?.getNode();
12550
12611
  return At$2(anchorNode, nodeType)
@@ -17025,7 +17086,7 @@ function customAttachmentBesideAnchor(selection) {
17025
17086
  }
17026
17087
 
17027
17088
  const ATTACHMENT_ATTRIBUTES = [ "alt", "caption", "content", "content-type", "data-direct-upload-id",
17028
- "data-sgid", "filename", "filesize", "height", "presentation", "previewable", "sgid", "url", "width" ];
17089
+ "data-sgid", "filename", "filesize", "height", "href", "presentation", "previewable", "sgid", "url", "width" ];
17029
17090
 
17030
17091
  class AttachmentsExtension extends LexxyExtension {
17031
17092
  #uploadsCount = 0
@@ -19506,14 +19567,51 @@ class LexicalPromptElement extends HTMLElement {
19506
19567
 
19507
19568
  const forceTop = this.verticalDirection === "top";
19508
19569
  const forceBottom = this.verticalDirection === "bottom";
19509
- const overflowsWindow = popoverRect.bottom > window.innerHeight;
19510
19570
 
19511
- if (!forceBottom && (forceTop || overflowsWindow)) {
19571
+ if (!forceBottom && (forceTop || this.#fitsBetterAbove(popoverRect, fontSize))) {
19512
19572
  this.#setPopoverOffsetY(contentRect.height - y + fontSize);
19513
19573
  this.popoverElement.toggleAttribute("data-clipped-at-bottom", true);
19514
19574
  }
19515
19575
  }
19516
19576
 
19577
+ #fitsBetterAbove(popoverRect, fontSize) {
19578
+ const { top, bottom } = this.#visibleBounds();
19579
+ const overflowsBelow = popoverRect.bottom > bottom;
19580
+ const roomAbove = popoverRect.top - fontSize - top;
19581
+ const roomBelow = bottom - popoverRect.top;
19582
+
19583
+ return overflowsBelow && roomAbove > roomBelow
19584
+ }
19585
+
19586
+ // A scroll container or modal around the editor clips the menu at its scrollport before the
19587
+ // window does, so the visible area is the window narrowed by every clipping ancestor. A fixed
19588
+ // ancestor escapes the clipping of everything up to its containing block: the window, or the
19589
+ // nearest ancestor that a transform, filter or containment makes contain fixed descendants.
19590
+ // A top-layer ancestor, such as a modal dialog, escapes everything outside it.
19591
+ #visibleBounds() {
19592
+ let top = 0;
19593
+ let bottom = window.innerHeight;
19594
+ let escapingToContainingBlock = false;
19595
+
19596
+ for (let node = this.#editorElement; node && node !== document.body; node = node.parentElement) {
19597
+ const style = getComputedStyle(node);
19598
+
19599
+ if (style.display !== "contents" && (!escapingToContainingBlock || containsFixedDescendants(style))) {
19600
+ escapingToContainingBlock = style.position === "fixed";
19601
+
19602
+ if (clipsDescendants(style)) {
19603
+ const scrollport = scrollportBounds(node);
19604
+ top = Math.max(top, scrollport.top);
19605
+ bottom = Math.min(bottom, scrollport.bottom);
19606
+ }
19607
+ }
19608
+
19609
+ if (node.matches(":modal, :popover-open")) break
19610
+ }
19611
+
19612
+ return { top, bottom }
19613
+ }
19614
+
19517
19615
  #setPopoverOffsetX(value) {
19518
19616
  this.popoverElement.style.setProperty("--lexxy-prompt-offset-x", `${value}px`);
19519
19617
  }
@@ -19799,6 +19897,29 @@ class LexicalPromptElement extends HTMLElement {
19799
19897
  }
19800
19898
  }
19801
19899
 
19900
+ function clipsDescendants(style) {
19901
+ return style.overflowY !== "visible" || /paint|strict|content/.test(style.contain) || style.contentVisibility === "auto"
19902
+ }
19903
+
19904
+ // The bounding rect is in viewport coordinates, already scaled by any transform, while
19905
+ // clientTop and clientHeight are untransformed layout sizes, so they're scaled to match.
19906
+ function scrollportBounds(element) {
19907
+ const rect = element.getBoundingClientRect();
19908
+ const scale = rect.height / element.offsetHeight || 1;
19909
+ const top = rect.top + element.clientTop * scale;
19910
+
19911
+ return { top, bottom: top + element.clientHeight * scale }
19912
+ }
19913
+
19914
+ function containsFixedDescendants(style) {
19915
+ return [ style.transform, style.translate, style.rotate, style.scale, style.perspective, style.filter, style.backdropFilter ]
19916
+ .some(value => value && value !== "none") ||
19917
+ /transform|translate|rotate|scale|perspective|filter/.test(style.willChange) ||
19918
+ /paint|layout|strict|content/.test(style.contain) ||
19919
+ /size/.test(style.containerType) ||
19920
+ style.contentVisibility === "auto"
19921
+ }
19922
+
19802
19923
  class CodeLanguagePicker extends HTMLElement {
19803
19924
  #abortController = null
19804
19925
  #listeners = new ListenerBin()
@@ -21862,5 +21983,5 @@ queueMicrotask(() => {
21862
21983
  }
21863
21984
  });
21864
21985
 
21865
- export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, $isCustomActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, EditorSanitizer, LexxyExtension as Extension, HorizontalDividerNode, Lexical_prod as Lexical, NativeAdapter, REWRITE_HISTORY_COMMAND, configure, highlightCode, highlightElement };
21986
+ export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, $isCustomActionTextAttachmentNode, $isLinkableAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, EditorSanitizer, LexxyExtension as Extension, HorizontalDividerNode, Lexical_prod as Lexical, NativeAdapter, REWRITE_HISTORY_COMMAND, configure, highlightCode, highlightElement };
21866
21987
  //# sourceMappingURL=lexxy.js.map
Binary file
Binary file