@blotch/angular-lynx 22.2.10 → 22.2.11

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.
@@ -2590,6 +2590,11 @@ const flushesBefore = (first, second) => {
2590
2590
  const isIOS = () => {
2591
2591
  return ((typeof lynx !== "undefined" ? lynx.SystemInfo : void 0) ?? (typeof SystemInfo !== "undefined" ? SystemInfo : void 0))?.platform === "iOS";
2592
2592
  };
2593
+ const listenForScrollsFromTheStart = (element) => {
2594
+ if (__WEB__ || !isIOS()) return;
2595
+ element.addEventListener("bindscroll", ignoreScroll);
2596
+ };
2597
+ const ignoreScroll = () => {};
2593
2598
  const insertHidesBlurView = (parent, child) => !__WEB__ && child.tagName === "blur-view" && parent.tagName !== "blur-view" && isIOS();
2594
2599
  const isTextish = (el) => el.tagName === "text" || el.tagName === "raw-text";
2595
2600
  var LynxElement = class {
@@ -2615,6 +2620,7 @@ var LynxElement = class {
2615
2620
  #ownTraits = null;
2616
2621
  #addedTraits = [];
2617
2622
  #listeners = [];
2623
+ #heldEvents = /* @__PURE__ */ new Map();
2618
2624
  #webLongPress = null;
2619
2625
  #pointerEvents = null;
2620
2626
  #webPointer = null;
@@ -2630,6 +2636,8 @@ var LynxElement = class {
2630
2636
  static #pendingAttributeRemovals = /* @__PURE__ */ new Set();
2631
2637
  #removedAttributes = /* @__PURE__ */ new Set();
2632
2638
  static #byNativeId = /* @__PURE__ */ new Map();
2639
+ static #pendingReleases = /* @__PURE__ */ new Set();
2640
+ #destroyed = false;
2633
2641
  static #canonicalFor(ref) {
2634
2642
  return _a.#byNativeId.get(__GetElementUniqueID(ref)) ?? new _a(ref);
2635
2643
  }
@@ -2644,6 +2652,15 @@ var LynxElement = class {
2644
2652
  const id = __GetElementUniqueID(this.#nativeRef);
2645
2653
  if (_a.#byNativeId.get(id) === this) _a.#byNativeId.delete(id);
2646
2654
  }
2655
+ markDestroyed() {
2656
+ if (this.isRootPageElement) return;
2657
+ this.#destroyed = true;
2658
+ _a.#pendingReleases.add(this);
2659
+ scheduleSettleFlush();
2660
+ }
2661
+ virtualChildren() {
2662
+ return [];
2663
+ }
2647
2664
  get ownerDocument() {
2648
2665
  return globalEvents().document;
2649
2666
  }
@@ -2762,6 +2779,7 @@ var LynxElement = class {
2762
2779
  insertBefore(newChild, refChild) {
2763
2780
  if (newChild.isRootPageElement) return;
2764
2781
  newChild.#cancelPendingRemoval();
2782
+ newChild.#keepIfDestroyed();
2765
2783
  newChild.#removeIfMoveWouldBeLost(this);
2766
2784
  newChild.#recreateIfDead();
2767
2785
  if (refChild == null) this.appendChild(newChild);
@@ -2783,6 +2801,7 @@ var LynxElement = class {
2783
2801
  appendChild(newChild) {
2784
2802
  if (newChild.isRootPageElement) return;
2785
2803
  newChild.#cancelPendingRemoval();
2804
+ newChild.#keepIfDestroyed();
2786
2805
  newChild.#removeIfMoveWouldBeLost(this);
2787
2806
  newChild.#recreateIfDead();
2788
2807
  if (wouldFormCycle(this.element, newChild.element)) return;
@@ -2841,10 +2860,18 @@ var LynxElement = class {
2841
2860
  }
2842
2861
  }
2843
2862
  static commitPendingRemovals() {
2844
- if (_a.#pendingRemovals.size === 0) return;
2845
- const items = [..._a.#pendingRemovals];
2846
- _a.#pendingRemovals.clear();
2847
- for (const el of items) el.#doRemove();
2863
+ if (_a.#pendingRemovals.size > 0) {
2864
+ const items = [..._a.#pendingRemovals];
2865
+ _a.#pendingRemovals.clear();
2866
+ for (const el of items) el.#doRemove();
2867
+ }
2868
+ _a.#commitPendingReleases();
2869
+ }
2870
+ static #commitPendingReleases() {
2871
+ if (_a.#pendingReleases.size === 0) return;
2872
+ const elements = [..._a.#pendingReleases];
2873
+ _a.#pendingReleases.clear();
2874
+ for (const el of elements) if (el.#destroyed && !el.#jsParent && !el._virtualParent) el.#release();
2848
2875
  }
2849
2876
  static #enqueueTextNormalization(el) {
2850
2877
  _a.#pendingTextNormalization.add(el);
@@ -2896,6 +2923,11 @@ var LynxElement = class {
2896
2923
  #cancelPendingRemoval() {
2897
2924
  _a.#pendingRemovals.delete(this);
2898
2925
  }
2926
+ #keepIfDestroyed() {
2927
+ if (!this.#destroyed) return;
2928
+ this.#destroyed = false;
2929
+ _a.#pendingReleases.delete(this);
2930
+ }
2899
2931
  #removeIfMoveWouldBeLost(newParent) {
2900
2932
  if (__WEB__ || this.#paintingDead || this._virtualParent) return;
2901
2933
  const oldParent = __GetParent(this.element);
@@ -2903,7 +2935,7 @@ var LynxElement = class {
2903
2935
  if (flushesBefore(newParent.element, oldParent)) this.#doRemove();
2904
2936
  }
2905
2937
  #doRemove() {
2906
- const parent = this.parentNode();
2938
+ const parent = __GetParent(this.element);
2907
2939
  if (!parent) return;
2908
2940
  for (const anim of this.#activeAnimations) anim.cancel();
2909
2941
  if (this.#activeAnimations.length > 0) {
@@ -2911,7 +2943,7 @@ var LynxElement = class {
2911
2943
  __AddInlineStyle(this.element, "transform", null);
2912
2944
  this.#activeAnimations.length = 0;
2913
2945
  }
2914
- __RemoveElement(parent.element, this.element);
2946
+ __RemoveElement(parent, this.element);
2915
2947
  const jsParent = this.#jsParent;
2916
2948
  if (jsParent) {
2917
2949
  const i = jsParent.#children.indexOf(this);
@@ -2921,6 +2953,15 @@ var LynxElement = class {
2921
2953
  }
2922
2954
  this.#markPaintingDead();
2923
2955
  if (jsParent && isTextish(this)) _a.#enqueueTextNormalization(jsParent);
2956
+ if (this.#destroyed) this.#release();
2957
+ }
2958
+ #release() {
2959
+ this.#destroyed = false;
2960
+ this.#paintingDead = true;
2961
+ this.deregisterNative();
2962
+ for (const { eventType, eventName } of this.#nativeHandlers.values()) __AddEvent(this.element, eventType, eventName, void 0);
2963
+ for (const child of this.#children) child.#release();
2964
+ for (const child of this.virtualChildren()) child.#release();
2924
2965
  }
2925
2966
  #markPaintingDead() {
2926
2967
  this.#paintingDead = true;
@@ -3132,11 +3173,13 @@ var LynxElement = class {
3132
3173
  }
3133
3174
  #syncNativeHandler(entry) {
3134
3175
  const key = nativeEventKey(entry.eventType, entry.eventName);
3176
+ if (this.#heldEvents.has(key)) return;
3135
3177
  const listeners = this.#listenersLike(entry);
3136
3178
  const current = this.#nativeHandlers.get(key);
3137
3179
  const eventType = listeners.length ? combinedEventType(listeners) : null;
3138
3180
  if (current?.eventType === eventType) return;
3139
3181
  scheduleSettleFlush();
3182
+ this.#sendHandlersWithNextFlush();
3140
3183
  if (current) {
3141
3184
  __AddEvent(this.element, current.eventType, entry.eventName, void 0);
3142
3185
  this.#nativeHandlers.delete(key);
@@ -3153,6 +3196,25 @@ var LynxElement = class {
3153
3196
  value: handler.dispatch
3154
3197
  });
3155
3198
  }
3199
+ holdNativeEvent(eventName) {
3200
+ this.#heldEvents.set(eventName, (this.#heldEvents.get(eventName) ?? 0) + 1);
3201
+ this.#nativeHandlers.delete(eventName);
3202
+ }
3203
+ releaseNativeEvent(eventName) {
3204
+ const held = this.#heldEvents.get(eventName);
3205
+ if (held === void 0) return;
3206
+ if (held > 1) {
3207
+ this.#heldEvents.set(eventName, held - 1);
3208
+ return;
3209
+ }
3210
+ this.#heldEvents.delete(eventName);
3211
+ const entry = this.#listeners.find((listener) => listener.eventName === eventName && listener.eventType !== "global-bindEvent");
3212
+ if (entry) this.#syncNativeHandler(entry);
3213
+ }
3214
+ #sendHandlersWithNextFlush() {
3215
+ if (__WEB__) return;
3216
+ __SetDataset(this.element, { ...this.#dataset });
3217
+ }
3156
3218
  #dispatch(entry, event) {
3157
3219
  if (event && typeof event === "object") {
3158
3220
  if (typeof event.stopPropagation !== "function") event.stopPropagation = () => {};
@@ -3269,6 +3331,11 @@ var LynxListElement = class extends LynxElement {
3269
3331
  }
3270
3332
  return children;
3271
3333
  }
3334
+ virtualChildren() {
3335
+ const children = [];
3336
+ for (let child = this.#firstVirtualChild; child; child = child._virtualNext) children.push(child);
3337
+ return children;
3338
+ }
3272
3339
  getCommittedUIChildren() {
3273
3340
  return this.#committedUIChildren;
3274
3341
  }
@@ -5937,6 +6004,9 @@ const noHandler = () => ({
5937
6004
  });
5938
6005
  var LynxMainThreadEvent = class LynxMainThreadEvent {
5939
6006
  #el = inject(ElementRef).nativeElement;
6007
+ get #host() {
6008
+ return this.#el;
6009
+ }
5940
6010
  #registrations = /* @__PURE__ */ new Map();
5941
6011
  mainThreadBindtap;
5942
6012
  mainThreadCatchtap;
@@ -5981,9 +6051,13 @@ var LynxMainThreadEvent = class LynxMainThreadEvent {
5981
6051
  __workletMap[workletId] = handle;
5982
6052
  }
5983
6053
  if (workletId === void 0) {
5984
- if (this.#registrations.delete(inputName)) __AddEvent(elementRef, eventType, eventName, noHandler());
6054
+ if (this.#registrations.delete(inputName)) {
6055
+ __AddEvent(elementRef, eventType, eventName, noHandler());
6056
+ this.#host.releaseNativeEvent?.(eventName);
6057
+ }
5985
6058
  continue;
5986
6059
  }
6060
+ if (!this.#registrations.has(inputName)) this.#host.holdNativeEvent?.(eventName);
5987
6061
  __AddEvent(elementRef, eventType, eventName, {
5988
6062
  type: "worklet",
5989
6063
  value: {
@@ -5999,6 +6073,7 @@ var LynxMainThreadEvent = class LynxMainThreadEvent {
5999
6073
  if (!elementRef) return;
6000
6074
  for (const [inputName, { eventType, eventName }] of this.#registrations) {
6001
6075
  __AddEvent(elementRef, eventType, eventName, noHandler());
6076
+ this.#host.releaseNativeEvent?.(eventName);
6002
6077
  this.#release(inputName);
6003
6078
  }
6004
6079
  this.#registrations.clear();
@@ -9593,6 +9668,7 @@ const CONTEXT_EVENTS = /* @__PURE__ */ new Set([
9593
9668
  "webglcontextrestored",
9594
9669
  "webglcontextcreationerror"
9595
9670
  ]);
9671
+ const contextFinalizer = typeof FinalizationRegistry === "function" ? new FinalizationRegistry((free) => free()) : null;
9596
9672
  const kebab = (name) => name.startsWith("--") ? name : name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
9597
9673
  const createStyle = (element) => {
9598
9674
  return new Proxy({}, {
@@ -9702,6 +9778,7 @@ var LynxCanvasElement = class extends LynxElement {
9702
9778
  return null;
9703
9779
  }
9704
9780
  this.#context = new LynxWebGL2RenderingContext(bridge, contextId, this, resolved);
9781
+ contextFinalizer?.register(this, () => bridge.destroy(contextId));
9705
9782
  return this.#context;
9706
9783
  }
9707
9784
  addEventListener(name, cb) {
@@ -9840,7 +9917,11 @@ var LynxDocument = class {
9840
9917
  /* v8 ignore next */
9841
9918
  if (__PROFILE__) devStats.elementCreated++;
9842
9919
  scheduleSettleFlush();
9843
- if (tag === "list") return createListElement(this.#pageId);
9920
+ if (tag === "list") {
9921
+ const list = createListElement(this.#pageId);
9922
+ listenForScrollsFromTheStart(list);
9923
+ return list;
9924
+ }
9844
9925
  if (tag === "page") {
9845
9926
  const result = createPageElement(this.page, this.#pageElementRequested);
9846
9927
  this.#pageElementRequested = result.pageElementRequested;
@@ -9851,6 +9932,7 @@ var LynxDocument = class {
9851
9932
  el.tagName = tag;
9852
9933
  if (tag === "raw-text") el.setInitialText(value ?? "");
9853
9934
  if (tag === "blur-view") el.updateBlurMaterial();
9935
+ if (tag === "scroll-view") listenForScrollsFromTheStart(el);
9854
9936
  return el;
9855
9937
  }
9856
9938
  createText(value) {
@@ -9994,7 +10076,7 @@ var LynxRenderer = class {
9994
10076
  return this.#document.createText(collapseWhitespace(value));
9995
10077
  }
9996
10078
  destroyNode = (node) => {
9997
- node.deregisterNative?.();
10079
+ node.markDestroyed?.();
9998
10080
  node.releaseContext?.();
9999
10081
  };
10000
10082
  appendChild(parent, newChild) {
@@ -11407,7 +11489,7 @@ const sizeOf = (width, height) => [{
11407
11489
  inlineSize: width,
11408
11490
  blockSize: height
11409
11491
  }];
11410
- const entryFor$1 = (target, rect) => {
11492
+ const entryFor = (target, rect) => {
11411
11493
  const ratio = pixelRatio();
11412
11494
  return {
11413
11495
  target,
@@ -11436,7 +11518,7 @@ var LynxResizeObserver = class {
11436
11518
  const notify = (rect) => {
11437
11519
  if (last && last.width === rect.width && last.height === rect.height) return;
11438
11520
  last = rect;
11439
- this.#callback([entryFor$1(target, rect)], this);
11521
+ this.#callback([entryFor(target, rect)], this);
11440
11522
  };
11441
11523
  this.#observed.set(target, target.observeLayout(notify));
11442
11524
  const rect = target.layoutRect;
@@ -11491,40 +11573,64 @@ const wrapResizeObserver = (NativeResizeObserver) => {
11491
11573
  };
11492
11574
  };
11493
11575
  const isLynxElement = (value) => typeof value === "object" && value !== null && "element" in value && typeof value.addEventListener === "function" && typeof value.observeLayout === "function" && typeof value.parentNode === "function";
11494
- const SCROLL_CONTAINERS = /* @__PURE__ */ new Set([
11495
- "scroll-view",
11496
- "list",
11497
- "viewpager",
11498
- "scroll-coordinator"
11576
+ const SCROLL_EVENTS = /* @__PURE__ */ new Map([
11577
+ ["scroll-view", "bindscroll"],
11578
+ ["list", "bindscroll"],
11579
+ ["viewpager", "bindoffsetchange"],
11580
+ ["scroll-coordinator", "bindoffset"]
11499
11581
  ]);
11500
- const measure = (element) => new Promise((resolve) => measureLayout(element.element, resolve));
11501
- const measureGeometry = async (target) => {
11582
+ const measurements = /* @__PURE__ */ new Map();
11583
+ let sampleMeasurements = null;
11584
+ const measure = (element) => {
11585
+ const native = element.element;
11586
+ const shared = sampleMeasurements?.get(native) ?? measurements.get(native);
11587
+ if (shared) return shared;
11588
+ let answered = false;
11589
+ const rect = new Promise((resolve) => measureLayout(native, (box) => {
11590
+ answered = true;
11591
+ measurements.delete(native);
11592
+ resolve(box);
11593
+ }));
11594
+ sampleMeasurements?.set(native, rect);
11595
+ if (!answered) measurements.set(native, rect);
11596
+ return rect;
11597
+ };
11598
+ const measureGeometry = async (target, root) => {
11502
11599
  const scrollContainers = [];
11503
11600
  let top = target;
11601
+ let inRoot = false;
11504
11602
  while (!top.isRootPageElement) {
11505
11603
  const parent = top.parentNode();
11506
11604
  if (!parent) break;
11507
- if (SCROLL_CONTAINERS.has(parent.tagName)) scrollContainers.push(parent);
11605
+ if (parent === root) {
11606
+ inRoot = true;
11607
+ break;
11608
+ }
11609
+ if (SCROLL_EVENTS.has(parent.tagName)) scrollContainers.push(parent);
11508
11610
  top = parent;
11509
11611
  }
11510
- const page = top.isRootPageElement ? top : null;
11511
- const [box, root, ...scrollBoxes] = await Promise.all([
11612
+ if (!root) inRoot = top.isRootPageElement === true;
11613
+ const rootElement = root ?? (inRoot ? top : null);
11614
+ const clips = inRoot ? scrollContainers : [];
11615
+ const [box, rootBox, ...scrollBoxes] = await Promise.all([
11512
11616
  measure(target),
11513
- page ? measure(page) : null,
11514
- ...scrollContainers.map(measure)
11617
+ rootElement ? measure(rootElement) : null,
11618
+ ...clips.map(measure)
11515
11619
  ]);
11516
11620
  return {
11517
11621
  box: box ?? null,
11518
- root: root ?? null,
11519
- scrollContainers: scrollBoxes
11622
+ root: rootBox ?? null,
11623
+ inRoot,
11624
+ scrollContainers: scrollBoxes,
11625
+ scrolledBy: root && inRoot && SCROLL_EVENTS.has(root.tagName) ? [...clips, root] : clips
11520
11626
  };
11521
11627
  };
11522
- const intersect = (a, b) => {
11628
+ const intersect = (a, b, touching) => {
11523
11629
  const left = Math.max(a.left, b.left);
11524
11630
  const top = Math.max(a.top, b.top);
11525
11631
  const right = Math.min(a.left + a.width, b.left + b.width);
11526
11632
  const bottom = Math.min(a.top + a.height, b.top + b.height);
11527
- return right > left && bottom > top ? {
11633
+ return (touching ? right >= left && bottom >= top : right > left && bottom > top) ? {
11528
11634
  left,
11529
11635
  top,
11530
11636
  width: right - left,
@@ -11533,11 +11639,11 @@ const intersect = (a, b) => {
11533
11639
  };
11534
11640
  const visiblePart = ({ box, root, scrollContainers }) => {
11535
11641
  let visible = box;
11536
- for (const clip of [root, ...scrollContainers]) visible = visible && clip ? intersect(visible, clip) : null;
11642
+ for (const clip of [root, ...scrollContainers]) visible = visible && clip ? intersect(visible, clip, false) : null;
11537
11643
  return visible;
11538
11644
  };
11539
11645
  const area = (rect) => rect.width * rect.height;
11540
- const entryFor = (target, geometry, exposed) => {
11646
+ const exposureEntryFor = (target, geometry, exposed) => {
11541
11647
  const visible = visiblePart(geometry);
11542
11648
  const isIntersecting = exposed ?? visible !== null;
11543
11649
  const { box } = geometry;
@@ -11553,6 +11659,51 @@ const entryFor = (target, geometry, exposed) => {
11553
11659
  isIntersecting
11554
11660
  };
11555
11661
  };
11662
+ const grow = (rect, margin) => {
11663
+ const [top, right, bottom, left] = margin.map((side, index) => side.percent ? side.value * (index % 2 ? rect.width : rect.height) / 100 : side.value);
11664
+ return {
11665
+ left: rect.left - left,
11666
+ top: rect.top - top,
11667
+ width: Math.max(rect.width + left + right, 0),
11668
+ height: Math.max(rect.height + top + bottom, 0)
11669
+ };
11670
+ };
11671
+ const thresholdIndexOf = (ratio, thresholds) => {
11672
+ let index = 0;
11673
+ while (index < thresholds.length && thresholds[index] <= ratio + 1e-6) index++;
11674
+ return index;
11675
+ };
11676
+ const measuredEntryFor = (target, geometry, margin, thresholds, exposed) => {
11677
+ const { box } = geometry;
11678
+ const rootBounds = geometry.root && grow(geometry.root, margin);
11679
+ let visible = null;
11680
+ let overlaps = false;
11681
+ let ratio = 0;
11682
+ if (!box) {
11683
+ overlaps = exposed === true;
11684
+ ratio = overlaps ? 1 : 0;
11685
+ } else if (geometry.inRoot && rootBounds) {
11686
+ visible = box;
11687
+ for (const clip of [...geometry.scrollContainers, rootBounds]) visible = visible && clip ? intersect(visible, clip, true) : null;
11688
+ if (visible) {
11689
+ overlaps = true;
11690
+ ratio = area(box) === 0 ? 1 : Math.min(area(visible) / area(box), 1);
11691
+ }
11692
+ }
11693
+ const index = overlaps ? thresholdIndexOf(ratio, thresholds) : 0;
11694
+ return {
11695
+ index,
11696
+ entry: {
11697
+ target,
11698
+ time: performance.now(),
11699
+ rootBounds: rootBounds ? toDOMRect(rootBounds) : null,
11700
+ boundingClientRect: toDOMRect(box),
11701
+ intersectionRect: toDOMRect(visible),
11702
+ intersectionRatio: ratio,
11703
+ isIntersecting: index > 0
11704
+ }
11705
+ };
11706
+ };
11556
11707
  const thresholdsOf = (threshold) => {
11557
11708
  const given = threshold === void 0 ? [] : Array.isArray(threshold) ? threshold.map(Number) : [Number(threshold)];
11558
11709
  for (const value of given) if (!(value >= 0 && value <= 1)) throw new RangeError("Failed to construct 'IntersectionObserver': Threshold values must be numbers between 0 and 1");
@@ -11561,26 +11712,53 @@ const thresholdsOf = (threshold) => {
11561
11712
  const rootMarginOf = (margin) => {
11562
11713
  const tokens = String(margin ?? "").trim().split(/\s+/).filter(Boolean);
11563
11714
  if (tokens.length > 4) throw invalidRootMargin();
11564
- const [top = "0px", right = top, bottom = top, left = right] = tokens.map((token) => {
11715
+ const [top = NO_MARGIN, right = top, bottom = top, left = right] = tokens.map((token) => {
11565
11716
  const match = /^(-?(?:\d+\.?\d*|\.\d+))(px|%)?$/.exec(token);
11566
11717
  if (!match || !match[2] && Number(match[1]) !== 0) throw invalidRootMargin();
11567
- return match[2] ? token : "0px";
11718
+ return match[2] ? {
11719
+ text: token,
11720
+ value: Number(match[1]),
11721
+ percent: match[2] === "%"
11722
+ } : NO_MARGIN;
11568
11723
  });
11569
- return `${top} ${right} ${bottom} ${left}`;
11724
+ return [
11725
+ top,
11726
+ right,
11727
+ bottom,
11728
+ left
11729
+ ];
11730
+ };
11731
+ const NO_MARGIN = {
11732
+ text: "0px",
11733
+ value: 0,
11734
+ percent: false
11570
11735
  };
11571
11736
  const invalidRootMargin = () => /* @__PURE__ */ new SyntaxError("Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent.");
11572
- const DEFAULT_ROOT_MARGIN = "0px 0px 0px 0px";
11573
- let warnedOfOptions = false;
11574
- const warnOfOptions = (observer) => {
11575
- if (warnedOfOptions) return;
11576
- const ignored = [
11577
- observer.root !== null && "root",
11578
- observer.rootMargin !== DEFAULT_ROOT_MARGIN && "rootMargin",
11579
- (observer.thresholds.length !== 1 || observer.thresholds[0] !== 0) && "threshold"
11580
- ].filter(Boolean);
11581
- if (!ignored.length) return;
11582
- warnedOfOptions = true;
11583
- console.warn(`[angular-lynx] IntersectionObserver on Lynx reports when an element comes into view and when it leaves, inside its scroll containers and the screen. It ignores ${ignored.join(", ")} for now.`);
11737
+ const SAMPLE_INTERVAL = 50;
11738
+ const SAMPLES_AFTER_SCROLL = 6;
11739
+ const scrolling = /* @__PURE__ */ new Map();
11740
+ let samplingTimer = null;
11741
+ const sampleWhileScrolling = (sample) => {
11742
+ scrolling.set(sample, SAMPLES_AFTER_SCROLL);
11743
+ if (samplingTimer === null) samplingTimer = setTimeout(sampleScrolling, SAMPLE_INTERVAL);
11744
+ };
11745
+ const sampleScrolling = () => {
11746
+ samplingTimer = null;
11747
+ sampleMeasurements = /* @__PURE__ */ new Map();
11748
+ for (const [sample, left] of Array.from(scrolling)) {
11749
+ if (left > 1) scrolling.set(sample, left - 1);
11750
+ else scrolling.delete(sample);
11751
+ sample();
11752
+ }
11753
+ sampleMeasurements = null;
11754
+ if (scrolling.size && samplingTimer === null) samplingTimer = setTimeout(sampleScrolling, SAMPLE_INTERVAL);
11755
+ };
11756
+ const stopSampling = (sample) => {
11757
+ scrolling.delete(sample);
11758
+ if (!scrolling.size && samplingTimer !== null) {
11759
+ clearTimeout(samplingTimer);
11760
+ samplingTimer = null;
11761
+ }
11584
11762
  };
11585
11763
  var LynxIntersectionObserver = class {
11586
11764
  static forLynxElements = true;
@@ -11588,6 +11766,9 @@ var LynxIntersectionObserver = class {
11588
11766
  rootMargin;
11589
11767
  thresholds;
11590
11768
  #callback;
11769
+ #root;
11770
+ #margin;
11771
+ #measures;
11591
11772
  #observed = /* @__PURE__ */ new Map();
11592
11773
  #records = [];
11593
11774
  #delivering = false;
@@ -11595,39 +11776,56 @@ var LynxIntersectionObserver = class {
11595
11776
  if (typeof callback !== "function") throw new TypeError("Failed to construct 'IntersectionObserver': parameter 1 is not of type 'Function'.");
11596
11777
  this.#callback = callback;
11597
11778
  const root = options.root;
11598
- this.root = root === void 0 || root === globalThis.document ? null : root;
11599
- this.rootMargin = rootMarginOf(options.rootMargin);
11779
+ if (root === void 0 || root === null || root === globalThis.document) this.#root = null;
11780
+ else if (isLynxElement(root)) this.#root = root;
11781
+ else throw new TypeError("Failed to construct 'IntersectionObserver': Failed to read the 'root' property from 'IntersectionObserverInit': The provided value is not of type '(Document or Element)'.");
11782
+ this.root = this.#root;
11783
+ this.#margin = rootMarginOf(options.rootMargin);
11784
+ this.rootMargin = this.#margin.map((side) => side.text).join(" ");
11600
11785
  this.thresholds = thresholdsOf(options.threshold);
11601
- warnOfOptions(this);
11786
+ this.#measures = this.#root !== null || this.#margin.some((side) => side.value !== 0) || this.thresholds.length !== 1 || this.thresholds[0] !== 0;
11602
11787
  }
11603
11788
  observe(target) {
11604
11789
  if (!isLynxElement(target)) throw new TypeError("Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.");
11605
11790
  if (this.#observed.has(target)) return;
11606
11791
  const stopAppear = target.addEventListener("binduiappear", () => {
11607
- this.#report(target, observation, true);
11792
+ this.#exposure(target, observation, true);
11608
11793
  });
11609
11794
  const stopDisappear = target.addEventListener("binduidisappear", () => {
11610
- this.#report(target, observation, false);
11795
+ this.#exposure(target, observation, false);
11611
11796
  });
11612
11797
  let laidOut = false;
11613
- const first = () => {
11798
+ const layout = () => {
11799
+ if (this.#measures) {
11800
+ observation.sample();
11801
+ return;
11802
+ }
11614
11803
  if (laidOut) return;
11615
11804
  laidOut = true;
11616
11805
  stopLayout();
11617
11806
  this.#report(target, observation, null);
11618
11807
  };
11619
- const stopLayout = target.observeLayout(first);
11808
+ const stopLayout = target.observeLayout(layout);
11620
11809
  const observation = {
11621
11810
  stop: () => {
11622
11811
  stopAppear();
11623
11812
  stopDisappear();
11624
11813
  stopLayout();
11814
+ for (const stopScroll of observation.scrollListeners.values()) stopScroll();
11815
+ observation.scrollListeners.clear();
11816
+ stopSampling(observation.sample);
11625
11817
  },
11626
- intersecting: null,
11627
- events: 0
11818
+ index: null,
11819
+ exposed: null,
11820
+ events: 0,
11821
+ measuring: false,
11822
+ again: false,
11823
+ measured: false,
11824
+ scrollListeners: /* @__PURE__ */ new Map(),
11825
+ sample: () => this.#sample(target, observation)
11628
11826
  };
11629
11827
  this.#observed.set(target, observation);
11630
- if (target.layoutRect) queueMicrotask(first);
11828
+ if (target.layoutRect) queueMicrotask(layout);
11631
11829
  }
11632
11830
  unobserve(target) {
11633
11831
  const observation = this.#observed.get(target);
@@ -11644,14 +11842,61 @@ var LynxIntersectionObserver = class {
11644
11842
  takeRecords() {
11645
11843
  return this.#records.splice(0);
11646
11844
  }
11845
+ #exposure(target, observation, exposed) {
11846
+ observation.exposed = exposed;
11847
+ if (this.#measures) observation.sample();
11848
+ else this.#report(target, observation, exposed);
11849
+ }
11647
11850
  async #report(target, observation, exposed) {
11648
11851
  const event = exposed === null ? 0 : ++observation.events;
11649
- const geometry = await measureGeometry(target);
11852
+ const geometry = await measureGeometry(target, null);
11650
11853
  if (this.#observed.get(target) !== observation) return;
11651
11854
  if (observation.events !== event) return;
11652
- const entry = entryFor(target, geometry, exposed);
11653
- if (entry.isIntersecting === observation.intersecting) return;
11654
- observation.intersecting = entry.isIntersecting;
11855
+ const entry = exposureEntryFor(target, geometry, exposed);
11856
+ const index = entry.isIntersecting ? 1 : 0;
11857
+ if (index === observation.index) return;
11858
+ observation.index = index;
11859
+ this.#queue(entry);
11860
+ }
11861
+ #sample(target, observation) {
11862
+ if (this.#observed.get(target) !== observation) return;
11863
+ if (observation.measuring) {
11864
+ observation.again = true;
11865
+ return;
11866
+ }
11867
+ observation.measuring = true;
11868
+ measureGeometry(target, this.#root).then((geometry) => {
11869
+ observation.measuring = false;
11870
+ if (this.#observed.get(target) !== observation) return;
11871
+ this.#listenForScrolls(observation, geometry.scrolledBy);
11872
+ if (geometry.box) observation.measured = true;
11873
+ const { entry, index } = measuredEntryFor(target, geometry, this.#margin, this.thresholds, observation.measured ? null : observation.exposed);
11874
+ if (index !== observation.index) {
11875
+ observation.index = index;
11876
+ this.#queue(entry);
11877
+ }
11878
+ if (observation.again) {
11879
+ observation.again = false;
11880
+ observation.sample();
11881
+ }
11882
+ });
11883
+ }
11884
+ #listenForScrolls(observation, scrolledBy) {
11885
+ const current = new Set(scrolledBy);
11886
+ for (const [scroller, stopScroll] of observation.scrollListeners) {
11887
+ if (current.has(scroller)) continue;
11888
+ stopScroll();
11889
+ observation.scrollListeners.delete(scroller);
11890
+ }
11891
+ for (const scroller of scrolledBy) {
11892
+ if (observation.scrollListeners.has(scroller)) continue;
11893
+ const event = SCROLL_EVENTS.get(scroller.tagName);
11894
+ observation.scrollListeners.set(scroller, scroller.addEventListener(event, () => {
11895
+ sampleWhileScrolling(observation.sample);
11896
+ }));
11897
+ }
11898
+ }
11899
+ #queue(entry) {
11655
11900
  this.#records.push(entry);
11656
11901
  this.#deliverSoon();
11657
11902
  }