lexxy 0.9.9.beta → 0.9.9.beta.preview1

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: 14f60aa212296a9c387271eca7386c9cb5527beadf1641cf8cf5bd9bc5896da9
4
- data.tar.gz: e4274b1696e483831690857f86d80ff3495d4be2d88c383fbf87b28fb286c190
3
+ metadata.gz: be0c22bb945fd56429ced73c583c28bcd0e6caf5ae885d666587ff329e1785cc
4
+ data.tar.gz: ab43a2841e18c6a0b44b788197f6f22f7653584f43214216ba10f875fda46e87
5
5
  SHA512:
6
- metadata.gz: eed04f532148665127494199324a8fb005b3602f4695a583f62730dc0dc546a860b1f647a8abb03810f268f085c8257629ec2278ff149923c8f6c8ba8a44d687
7
- data.tar.gz: 405ebe5f4dfe0e0c62f2daf4370cc2154f90bf4d4fdf08df8d913debc741e87029c2192b3169dc1b380c27745120e9366be7ce505107f6a0c8d67eaa4b0c1156
6
+ metadata.gz: e719a3e55ed0758b455c9ec191aa669b7f467eb7a55479f34d5462cd743d3391e62d93e74f2177074671a1bee82201db96737bd282b9bb675cb29bf99badb8b8
7
+ data.tar.gz: '04495b4335b01e4992b475977566dd7b3065991e3e8584b717306dcd0701754a1242faf0a92261fb6ee2735f18f6e1058e92cb5947feec44ef7675f6d842508a'
@@ -7847,6 +7847,35 @@ function isAttachmentSpacerTextNode(node, previousNode, index, childCount) {
7847
7847
  && previousNode instanceof CustomActionTextAttachmentNode
7848
7848
  }
7849
7849
 
7850
+ // Shared, strictly-contained element used to attach ephemeral nodes when we
7851
+ // need to read computed styles (e.g. canonicalizing style values, resolving
7852
+ // CSS custom properties). The container is created once and attached to
7853
+ // `document.body` once; subsequent child mutations happen *inside* the
7854
+ // contained subtree so they do not invalidate style on the rest of the page.
7855
+ //
7856
+ // Without this, `document.body.appendChild(...)` / `element.remove()` calls
7857
+ // forced the browser to re-evaluate every ancestor-dependent selector (`:has()`,
7858
+ // descendant combinators, universal sibling rules) across the document on each
7859
+ // invocation — a 13,000+ element style recalc per call on a typical Basecamp
7860
+ // page.
7861
+
7862
+ let resolverRoot = null;
7863
+
7864
+ function styleResolverRoot() {
7865
+ if (resolverRoot && resolverRoot.isConnected) return resolverRoot
7866
+
7867
+ resolverRoot = document.createElement("div");
7868
+ resolverRoot.setAttribute("aria-hidden", "true");
7869
+ resolverRoot.setAttribute("data-lexxy-style-resolver", "");
7870
+ // `contain: strict` (size, layout, paint, style) isolates everything.
7871
+ // The root itself paints nothing (visibility hidden), has zero
7872
+ // geometric impact (position fixed, intrinsic size via contain), and
7873
+ // never leaks style invalidation to its ancestors.
7874
+ resolverRoot.style.cssText = "contain: strict; position: fixed; top: 0; left: 0; visibility: hidden; pointer-events: none; width: 0; height: 0;";
7875
+ document.body.appendChild(resolverRoot);
7876
+ return resolverRoot
7877
+ }
7878
+
7850
7879
  function isSelectionHighlighted(selection) {
7851
7880
  if (!wr(selection)) return false
7852
7881
 
@@ -7927,10 +7956,11 @@ class StyleCanonicalizer {
7927
7956
  }
7928
7957
  }
7929
7958
 
7930
- // Separates DOM writes from layout reads to avoid forced reflows. All resolver
7931
- // elements are built inside a fragment, attached once, then read in a single pass.
7932
- // Reading `getComputedStyle` after a write forces the browser to recompute layout,
7933
- // so interleaving writes and reads inside a loop turns one reflow into N.
7959
+ // Separates DOM writes from layout reads to avoid forced reflows, and attaches
7960
+ // resolver elements to a strictly-contained root (outside the normal document
7961
+ // flow) so neither the attach nor the detach invalidate styles on the rest of
7962
+ // the page. Without containment, appending to `document.body` triggered a
7963
+ // page-wide style recalc on every canonicalization pass.
7934
7964
  function computeStyleValues(property, values) {
7935
7965
  const fragment = document.createDocumentFragment();
7936
7966
 
@@ -7940,7 +7970,7 @@ function computeStyleValues(property, values) {
7940
7970
  return element
7941
7971
  });
7942
7972
 
7943
- document.body.appendChild(fragment);
7973
+ styleResolverRoot().appendChild(fragment);
7944
7974
 
7945
7975
  const computed = elements.map(element =>
7946
7976
  window.getComputedStyle(element).getPropertyValue(property)
@@ -13099,6 +13129,7 @@ class LexicalEditorElement extends HTMLElement {
13099
13129
  static observedAttributes = [ "connected", "required" ]
13100
13130
 
13101
13131
  #initialValue = ""
13132
+ #initialValueLoaded = false
13102
13133
  #validationTextArea = document.createElement("textarea")
13103
13134
  #editorInitializedRafId = null
13104
13135
  #listeners = new ListenerBin()
@@ -13276,9 +13307,19 @@ class LexicalEditorElement extends HTMLElement {
13276
13307
  }
13277
13308
 
13278
13309
  focus() {
13310
+ // `editor.focus()` commits a reconciler update to position the cursor.
13311
+ // Skip if the contenteditable already owns focus — the update would be a
13312
+ // no-op but still triggers a full style/layout pass on pages with large
13313
+ // DOMs.
13314
+ if (this.#isContentFocused) return
13315
+
13279
13316
  this.editor.focus(() => this.#onFocus());
13280
13317
  }
13281
13318
 
13319
+ get #isContentFocused() {
13320
+ return !!this.editorContentElement && this.editorContentElement.contains(document.activeElement)
13321
+ }
13322
+
13282
13323
  get value() {
13283
13324
  if (!this.cachedValue) {
13284
13325
  this.editor?.getEditorState().read(() => {
@@ -13290,6 +13331,8 @@ class LexicalEditorElement extends HTMLElement {
13290
13331
  }
13291
13332
 
13292
13333
  set value(html) {
13334
+ const wasEmpty = !this.#initialValueLoaded;
13335
+
13293
13336
  this.editor.update(() => {
13294
13337
  gs(Vn);
13295
13338
  const root = zo();
@@ -13299,11 +13342,17 @@ class LexicalEditorElement extends HTMLElement {
13299
13342
 
13300
13343
  this.#toggleEmptyStatus();
13301
13344
 
13302
- // The first time you set the value, when the editor is empty, it seems to leave Lexical
13303
- // in an inconsistent state until, at least, you focus. You can type but adding attachments
13304
- // fails because no root node detected. This is a workaround to deal with the issue.
13305
- requestAnimationFrame(() => this.editor?.update(() => { }));
13345
+ // The first time you set the value on an empty editor, Lexical can be
13346
+ // left in an inconsistent state until the next update (adding attachments
13347
+ // fails because no root node is detected). A no-op update works around
13348
+ // it. Only fire on the first load — subsequent set value calls don't hit
13349
+ // the inconsistent state and the extra reconciler cycle is pure overhead.
13350
+ if (wasEmpty) {
13351
+ requestAnimationFrame(() => this.editor?.update(() => { }));
13352
+ }
13306
13353
  });
13354
+
13355
+ this.#initialValueLoaded = true;
13307
13356
  }
13308
13357
 
13309
13358
  #parseHtmlIntoLexicalNodes(html) {
@@ -13737,7 +13786,7 @@ class LexicalEditorElement extends HTMLElement {
13737
13786
  return { element, name: cssValue }
13738
13787
  });
13739
13788
 
13740
- this.appendChild(container);
13789
+ styleResolverRoot().appendChild(container);
13741
13790
 
13742
13791
  const resolved = resolvers.map(({ element, name }) => ({
13743
13792
  name,
Binary file
Binary file