@ethlete/core 5.0.0-next.33 → 5.0.0-next.34

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ethlete/core
2
2
 
3
+ ## 5.0.0-next.34
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ef65231`](https://github.com/ethlete-io/ethdk/commit/ef65231ed04184345200f1e6f2e80d3207778157) Thanks [@TomTomB](https://github.com/TomTomB)! - Auto-surface: content that is grafted into an overlay pane after it first renders (e.g. a select option's `etAutoSurface` avatar) now re-resolves its elevation once it lands in the pane, instead of staying stuck one level too low.
8
+
3
9
  ## 5.0.0-next.33
4
10
 
5
11
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, ElementRef, Directive, assertInInjectionContext, DestroyRef, TemplateRef, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, NgZone, untracked, EnvironmentInjector, runInInjectionContext, Injector, booleanAttribute, numberAttribute, PLATFORM_ID, ApplicationRef, createComponent, model, ViewContainerRef, input, output, Pipe, ViewEncapsulation, Component } from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, Directive, assertInInjectionContext, DestroyRef, TemplateRef, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, NgZone, untracked, EnvironmentInjector, runInInjectionContext, Injector, booleanAttribute, numberAttribute, PLATFORM_ID, ApplicationRef, createComponent, model, ViewContainerRef, input, output, Pipe, ViewEncapsulation, Component, afterEveryRender } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
4
4
  import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, defer, from, catchError, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat, exhaustMap, share, isObservable, firstValueFrom, defaultIfEmpty } from 'rxjs';
5
5
  import { FormGroup, FormArray, FormControl } from '@angular/forms';
@@ -7637,11 +7637,20 @@ class AutoSurfaceDirective {
7637
7637
  // rather than layering one level above a parent surface.
7638
7638
  this.isOverlaySurface = signal(false, /* @ts-ignore */
7639
7639
  ...(ngDevMode ? [{ debugName: "isOverlaySurface" }] : /* istanbul ignore next */ []));
7640
+ // Bumped by the afterEveryRender watcher in the constructor whenever this element's overlay
7641
+ // containment changes. surfaceForElement() below does a non-reactive element.contains() read,
7642
+ // so the computed must be re-triggered when this element is (re)grafted into an overlay pane —
7643
+ // otherwise a value cached from before the graft (one elevation too low) sticks forever.
7644
+ this.domSettleTick = signal(0, /* @ts-ignore */
7645
+ ...(ngDevMode ? [{ debugName: "domSettleTick" }] : /* istanbul ignore next */ []));
7640
7646
  this.resolvedSurface = computed(() => {
7641
7647
  const themes = this.surfaceThemes;
7642
7648
  if (!themes) {
7643
7649
  return null;
7644
7650
  }
7651
+ // Establish a dependency on the DOM-settle tick so the non-reactive containment read below
7652
+ // is re-evaluated after the element is grafted into its final overlay pane.
7653
+ this.domSettleTick();
7645
7654
  const contextProvider = this.surfaceProvider() ?? this.contextSurfaceProvider ?? null;
7646
7655
  const contextElevation = contextProvider?.elevation() ?? null;
7647
7656
  const contextType = contextProvider?.surfaceType() ?? null;
@@ -7688,6 +7697,29 @@ class AutoSurfaceDirective {
7688
7697
  return resolveSurfaceByElevation(themes, parentType, parentElevation + 1)?.name ?? null;
7689
7698
  }, /* @ts-ignore */
7690
7699
  ...(ngDevMode ? [{ debugName: "resolvedSurface" }] : /* istanbul ignore next */ []));
7700
+ // Projected/portaled auto-surface content (e.g. a select option's avatar) can have its
7701
+ // resolvedSurface computed run *before* it is grafted into the overlay pane it visually lives
7702
+ // in — or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
7703
+ // surfaceForElement()'s element.contains() check then returns the wrong (or no) overlay, so it
7704
+ // resolves off the declaration injector (the trigger location) one elevation too low. That check
7705
+ // is a plain DOM read, not a signal, so nothing re-runs the computed once the element reaches its
7706
+ // final pane. Watch the tracker result across renders and re-trigger the computed whenever the
7707
+ // element's overlay containment changes, settling (and unsubscribing) once it stops moving.
7708
+ let lastElevation = this.surfaceContextTracker.surfaceForElement(this.elementRef.nativeElement)?.elevation ?? null;
7709
+ let stableRenders = 0;
7710
+ const settleWatcher = afterEveryRender(() => {
7711
+ const el = this.elementRef.nativeElement;
7712
+ const elevation = this.surfaceContextTracker.surfaceForElement(el)?.elevation ?? null;
7713
+ if (elevation !== lastElevation) {
7714
+ lastElevation = elevation;
7715
+ stableRenders = 0;
7716
+ this.domSettleTick.update((v) => v + 1);
7717
+ }
7718
+ else if (el.isConnected && ++stableRenders >= 2) {
7719
+ // containment has held for a couple of renders and the element is in the DOM — done moving
7720
+ settleWatcher.destroy();
7721
+ }
7722
+ });
7691
7723
  effect(() => {
7692
7724
  const surface = this.resolvedSurface();
7693
7725
  untracked(() => {