@ethlete/core 5.0.0-next.62 → 5.0.0-next.63

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,14 @@
1
1
  # @ethlete/core
2
2
 
3
+ ## 5.0.0-next.63
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix modal focus trap tab stops, overlay stacking order for Escape and outside clicks, focus restore to disabled openers, `afterOpened()` on early close, and scroll restoration under a base href.
8
+ - Fix edge cases in `AnimatedLifecycleDirective` classes, `getScrollSnapTarget`, `[etClickOutside]`, `signalElementIntersection` order, `isElementVisible().intersectionRatio` and `createUnsavedChangesTracker` with a `null` default.
9
+ - `htmlToMarkdown` drops empty headings and list items, and normalizes block alignment to `center`, `right` or `justify`; other values serialize as plain Markdown.
10
+ - The modal focus trap treats a native radio group as one Tab stop, so Tab no longer escapes a dialog through it.
11
+
3
12
  ## 5.0.0-next.62
4
13
 
5
14
  ### Major Changes
@@ -1755,8 +1755,17 @@ const breakpointTransformBase = (coerce, defaultValue) => {
1755
1755
  const bp = currentBp();
1756
1756
  const r = raw();
1757
1757
  if (!cachedSig) {
1758
- const instance = injector.get(BREAKPOINT_INSTANCE_TOKEN, null);
1759
- if (!instance) {
1758
+ const instance = injector.get(BREAKPOINT_INSTANCE_TOKEN, null, { self: true });
1759
+ if (instance) {
1760
+ for (const key of Object.keys(instance)) {
1761
+ const val = instance[key];
1762
+ if (val && typeof val === 'function' && val[SIGNAL]?.transformFn === transformFn) {
1763
+ cachedSig = val;
1764
+ break;
1765
+ }
1766
+ }
1767
+ }
1768
+ if (!cachedSig) {
1760
1769
  if (ngDevMode && r !== undefined && !warnedMissingInstance) {
1761
1770
  warnedMissingInstance = true;
1762
1771
  console.warn('[ethlete] A breakpoint map was bound to an input, but its component does not call ' +
@@ -1764,13 +1773,6 @@ const breakpointTransformBase = (coerce, defaultValue) => {
1764
1773
  }
1765
1774
  return;
1766
1775
  }
1767
- for (const key of Object.keys(instance)) {
1768
- const val = instance[key];
1769
- if (val && typeof val === 'function' && val[SIGNAL]?.transformFn === transformFn) {
1770
- cachedSig = val;
1771
- break;
1772
- }
1773
- }
1774
1776
  }
1775
1777
  if (!cachedSig || r === undefined)
1776
1778
  return;
@@ -2213,7 +2215,7 @@ const isElementVisible = (options) => {
2213
2215
  containerRect,
2214
2216
  elementRect,
2215
2217
  // Round the intersection ratio to the nearest 0.01 to avoid floating point errors and system scaling issues.
2216
- intersectionRatio: Math.round(Math.min(inlineIntersectionPercentage, blockIntersectionPercentage) * 100) / 100,
2218
+ intersectionRatio: Math.round(inlineIntersectionPercentage * blockIntersectionPercentage * 100) / 100,
2217
2219
  };
2218
2220
  };
2219
2221
  const nearestAlignment = (elStart, elEnd, conStart, conEnd) => {
@@ -2331,6 +2333,8 @@ const getScrollSnapTarget = (items, container, direction, origin, margin = 0) =>
2331
2333
  };
2332
2334
  for (const item of items) {
2333
2335
  const itemRect = item.getBoundingClientRect();
2336
+ if (!itemRect.width && !itemRect.height)
2337
+ continue;
2334
2338
  const itemSize = direction === 'horizontal' ? itemRect.width : itemRect.height;
2335
2339
  const relativeStart = direction === 'horizontal' ? itemRect.left - containerRect.left : itemRect.top - containerRect.top;
2336
2340
  const relativeEnd = relativeStart + itemSize;
@@ -2512,6 +2516,12 @@ const signalElementIntersection = (el, options) => {
2512
2516
  currentValues = [...currentValues, intersectionEntry];
2513
2517
  }
2514
2518
  }
2519
+ const domOrder = untracked(elements).currentElements;
2520
+ const orderOf = (target) => {
2521
+ const index = domOrder.indexOf(target);
2522
+ return index === -1 ? domOrder.length : index;
2523
+ };
2524
+ currentValues.sort((a, b) => orderOf(a.target) - orderOf(b.target));
2515
2525
  zone.run(() => elementIntersectionSignal.set(currentValues));
2516
2526
  };
2517
2527
  const updateIntersectionObserver = (rendered, enabled, rootEl, margin, thresholdValue) => {
@@ -2954,7 +2964,10 @@ const createInitialRoute = () => {
2954
2964
  if (!isBrowser) {
2955
2965
  return router.url;
2956
2966
  }
2957
- const path = inject(Location).path(true);
2967
+ return readLocationPath(inject(Location));
2968
+ };
2969
+ const readLocationPath = (location) => {
2970
+ const path = location.path(true);
2958
2971
  return path.startsWith('/') ? path : `/${path}`;
2959
2972
  };
2960
2973
  /** Inject the current router event */
@@ -3040,14 +3053,20 @@ const createRouterState = (router) => {
3040
3053
  /**
3041
3054
  * Read the current route (the committed url without query params and fragment) straight from the router.
3042
3055
  * Unlike the `injectRoute` signal this reads synchronously, so it can be used inside a `router.events` handler
3043
- * where `router.url` is already committed. Falls back to the browser location before the first navigation commits.
3056
+ * where `router.url` is already committed. Before the first navigation commits it falls back to `location`
3057
+ * (without the base href) when given, else to the browser location.
3044
3058
  */
3045
- const createRoute = (router) => {
3046
- const browserUrl = typeof window === 'undefined'
3059
+ const createRoute = (router, location) => {
3060
+ const url = router.navigated ? router.url : readUncommittedUrl(router, location);
3061
+ return url.split('?')[0]?.split('#')[0] ?? '';
3062
+ };
3063
+ const readUncommittedUrl = (router, location) => {
3064
+ if (location) {
3065
+ return readLocationPath(location);
3066
+ }
3067
+ return typeof window === 'undefined'
3047
3068
  ? router.url
3048
3069
  : window.location.pathname + window.location.search + window.location.hash;
3049
- const url = router.navigated ? router.url : browserUrl;
3050
- return url.split('?')[0]?.split('#')[0] ?? '';
3051
3070
  };
3052
3071
  /**
3053
3072
  * Inject the complete router state. This includes the current route data, path params, query params, title and fragment.
@@ -3306,6 +3325,7 @@ const setupScrollRestoration = (config = {}) => {
3306
3325
  return;
3307
3326
  }
3308
3327
  const router = inject(Router);
3328
+ const location = inject(Location);
3309
3329
  const document = inject(DOCUMENT);
3310
3330
  const injector = inject(Injector);
3311
3331
  const destroyRef = inject(DestroyRef);
@@ -3334,7 +3354,7 @@ const setupScrollRestoration = (config = {}) => {
3334
3354
  let wantsRestore = false;
3335
3355
  let prev = {
3336
3356
  state: createRouterState(router),
3337
- route: createRoute(router),
3357
+ route: createRoute(router, location),
3338
3358
  routeConfig: getDeepestRouteConfig(router),
3339
3359
  };
3340
3360
  let pendingRestore = null;
@@ -3452,7 +3472,7 @@ const setupScrollRestoration = (config = {}) => {
3452
3472
  const onNavigationEnd = (event) => {
3453
3473
  const curr = {
3454
3474
  state: createRouterState(router),
3455
- route: createRoute(router),
3475
+ route: createRoute(router, location),
3456
3476
  routeConfig: getDeepestRouteConfig(router),
3457
3477
  };
3458
3478
  const prevState = prev.state;
@@ -3613,7 +3633,13 @@ const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
3613
3633
  const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
3614
3634
  const alignClass = (align) => (align ? ` class="et-rte-align-${align}"` : '');
3615
3635
  const alignOf = (attrs) => (/\bclass\s*=\s*["'][^"']*\bet-rte-align-([a-z]+)/i.exec(attrs)?.[1] ??
3616
- /\bstyle\s*=\s*["'][^"']*text-align:\s*([a-z]+)/i.exec(attrs)?.[1])?.toLowerCase() ?? null;
3636
+ /\bstyle\s*=\s*["'][^"']*text-align:\s*(?:-[a-z]+-)?([a-z]+)/i.exec(attrs)?.[1])?.toLowerCase() ?? null;
3637
+ const BLOCK_ALIGNS = /* @__PURE__ */ new Set(['center', 'right', 'justify']);
3638
+ const blockAlignOf = (attrs) => {
3639
+ const align = alignOf(attrs);
3640
+ return align && BLOCK_ALIGNS.has(align) ? align : null;
3641
+ };
3642
+ const isBlankHtml = (html) => !/<img\b/i.test(html) && !stripTags(html.replace(/&nbsp;/gi, ' ')).trim();
3617
3643
  /** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters -
3618
3644
  * CommonMark emphasis must not face whitespace on the inside (`** fett**` doesn't parse), and
3619
3645
  * `&nbsp;` counts as whitespace there too. Whitespace-only content stays unwrapped. */
@@ -3816,16 +3842,20 @@ const listToMarkdown = (inner, ordered, startNumber, depth) => {
3816
3842
  const indent = ' '.repeat(depth);
3817
3843
  let n = startNumber;
3818
3844
  return findListItems(inner)
3819
- .map((itemInner) => {
3845
+ .flatMap((itemInner) => {
3820
3846
  let content = itemInner;
3821
3847
  let nestedMarkdown = '';
3822
3848
  for (let nested = findList(content); nested; nested = findList(content)) {
3823
- nestedMarkdown += `\n${listToMarkdown(nested.inner, nested.ordered, nested.startNumber, depth + 1)}`;
3849
+ const nestedItems = listToMarkdown(nested.inner, nested.ordered, nested.startNumber, depth + 1);
3850
+ if (nestedItems)
3851
+ nestedMarkdown += `\n${nestedItems}`;
3824
3852
  content = content.slice(0, nested.start) + content.slice(nested.end);
3825
3853
  }
3854
+ if (!nestedMarkdown && isBlankHtml(content))
3855
+ return [];
3826
3856
  const marker = ordered ? `${n++}. ` : '- ';
3827
3857
  // list items are single-line in this serializer, so a <br> degrades to a space
3828
- return `${indent}${marker}${stripTags(content.replace(/<br\s*\/?>/gi, ' ')).trim()}${nestedMarkdown}`;
3858
+ return [`${indent}${marker}${stripTags(content.replace(/<br\s*\/?>/gi, ' ')).trim()}${nestedMarkdown}`];
3829
3859
  })
3830
3860
  .join('\n');
3831
3861
  };
@@ -3934,7 +3964,7 @@ const markdownToHtml = (markdown, options = {}) => {
3934
3964
  const align = aligned ? alignOf(aligned[2] ?? '') : null;
3935
3965
  if (aligned && align) {
3936
3966
  const tag = (aligned[1] ?? 'p').toLowerCase();
3937
- return `<${tag}${alignClass(align)}>${sanitizeInlineHtml(aligned[3] ?? '')}</${tag}>`;
3967
+ return `<${tag}${alignClass(BLOCK_ALIGNS.has(align) ? align : null)}>${sanitizeInlineHtml(aligned[3] ?? '')}</${tag}>`;
3938
3968
  }
3939
3969
  // Heading
3940
3970
  const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
@@ -4004,9 +4034,12 @@ const htmlToMarkdown = (html) => {
4004
4034
  // stays HTML) and round-trip via a placeholder - extracted before the block passes below rewrite
4005
4035
  // them, restored after the final tag-strip.
4006
4036
  const alignedBlocks = [];
4007
- md = md.replace(/<(p|h[1-6]|div)\b([^>]*\b(?:style="[^"]*text-align|class="[^"]*\bet-rte-align-)[^>]*)>([\s\S]*?)<\/\1>/gi, (_, tag, attrs, inner) => {
4037
+ md = md.replace(/<(p|h[1-6]|div)\b([^>]*\b(?:style="[^"]*text-align|class="[^"]*\bet-rte-align-)[^>]*)>([\s\S]*?)<\/\1>/gi, (block, tag, attrs, inner) => {
4038
+ const align = blockAlignOf(attrs);
4039
+ if (!align)
4040
+ return block.replace(/^<[a-z0-9]+\b[^>]*>/i, `<${tag}>`);
4008
4041
  const name = tag.toLowerCase();
4009
- return makePlaceholder('ALIGN', alignedBlocks.push(`<${name}${alignClass(alignOf(attrs))}>${inner}</${name}>`) - 1);
4042
+ return makePlaceholder('ALIGN', alignedBlocks.push(`<${name}${alignClass(align)}>${inner}</${name}>`) - 1);
4010
4043
  });
4011
4044
  md = escapeTextNodes(md);
4012
4045
  // Code blocks - process before inline code
@@ -4016,7 +4049,7 @@ const htmlToMarkdown = (html) => {
4016
4049
  // Headings - keep the inner markup so the inline passes below turn any nested
4017
4050
  // bold/italic/link/code into markdown; leftover tags are stripped at the end.
4018
4051
  for (let i = 6; i >= 1; i--) {
4019
- md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => `\n${'#'.repeat(i)} ${content.trim()}\n`);
4052
+ md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => isBlankHtml(content) ? '\n' : `\n${'#'.repeat(i)} ${content.trim()}\n`);
4020
4053
  }
4021
4054
  // Bold + italic (combined before individual). Boundary whitespace is hoisted out of the
4022
4055
  // delimiters (emphasize) - `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
@@ -4856,6 +4889,7 @@ const ANIMATION_CLASSES = {
4856
4889
  leaveDone: 'et-animation-leave-done',
4857
4890
  leaveInterrupt: 'et-animation-leave-interrupt',
4858
4891
  };
4892
+ const ALL_ANIMATION_CLASSES = /* @__PURE__ */ Object.values(ANIMATION_CLASSES);
4859
4893
  const FORCE_INVISIBLE_CLASS = 'et-force-invisible';
4860
4894
  class AnimatedLifecycleDirective {
4861
4895
  constructor() {
@@ -4902,9 +4936,9 @@ class AnimatedLifecycleDirective {
4902
4936
  return;
4903
4937
  if ((currentState === 'init' && !this.isConstructed) || this.skipNextEnter()) {
4904
4938
  this.debugLog('enter: instant (not constructed or skipNextEnter)', { currentState });
4905
- this.updateState('entered');
4939
+ this.cancelCurrentAnimation$.next();
4940
+ this.settleInstantly('entered');
4906
4941
  this.skipNextEnter.set(false);
4907
- this.addClass(ANIMATION_CLASSES.enterDone);
4908
4942
  this.forcedThisFrame = false;
4909
4943
  return;
4910
4944
  }
@@ -4922,9 +4956,7 @@ class AnimatedLifecycleDirective {
4922
4956
  transitionId,
4923
4957
  });
4924
4958
  if (skipAnimation) {
4925
- this.removeClasses(ANIMATION_CLASSES.leaveFrom, ANIMATION_CLASSES.leaveActive, ANIMATION_CLASSES.leaveTo, ANIMATION_CLASSES.leaveInterrupt);
4926
- this.updateState('entered');
4927
- this.addClass(ANIMATION_CLASSES.enterDone);
4959
+ this.settleInstantly('entered');
4928
4960
  previousCancel$.next();
4929
4961
  previousCancel$.complete();
4930
4962
  return;
@@ -4972,8 +5004,7 @@ class AnimatedLifecycleDirective {
4972
5004
  return;
4973
5005
  if (currentState === 'init') {
4974
5006
  this.debugLog('leave: instant (state is init, enter never ran)');
4975
- this.updateState('left');
4976
- this.addClass(ANIMATION_CLASSES.leaveDone);
5007
+ this.settleInstantly('left');
4977
5008
  this.forcedThisFrame = false;
4978
5009
  return;
4979
5010
  }
@@ -4991,9 +5022,7 @@ class AnimatedLifecycleDirective {
4991
5022
  transitionId,
4992
5023
  });
4993
5024
  if (skipAnimation) {
4994
- this.removeClasses(ANIMATION_CLASSES.enterFrom, ANIMATION_CLASSES.enterActive, ANIMATION_CLASSES.enterTo, ANIMATION_CLASSES.enterInterrupt);
4995
- this.updateState('left');
4996
- this.addClass(ANIMATION_CLASSES.leaveDone);
5025
+ this.settleInstantly('left');
4997
5026
  previousCancel$.next();
4998
5027
  previousCancel$.complete();
4999
5028
  return;
@@ -5037,18 +5066,20 @@ class AnimatedLifecycleDirective {
5037
5066
  }
5038
5067
  forceEnteredState() {
5039
5068
  this.cancelCurrentAnimation$.next();
5040
- this.updateState('entered');
5041
- this.removeClasses(ANIMATION_CLASSES.enterFrom, ANIMATION_CLASSES.enterActive, ANIMATION_CLASSES.enterTo, ANIMATION_CLASSES.leaveFrom, ANIMATION_CLASSES.leaveActive, ANIMATION_CLASSES.leaveTo);
5042
- this.addClass(ANIMATION_CLASSES.enterDone);
5069
+ this.settleInstantly('entered');
5043
5070
  this.markForcedThisFrame();
5044
5071
  }
5045
5072
  forceLeftState() {
5046
5073
  this.cancelCurrentAnimation$.next();
5047
- this.updateState('left');
5048
- this.removeClasses(ANIMATION_CLASSES.enterFrom, ANIMATION_CLASSES.enterActive, ANIMATION_CLASSES.enterTo, ANIMATION_CLASSES.leaveFrom, ANIMATION_CLASSES.leaveActive, ANIMATION_CLASSES.leaveTo);
5049
- this.addClass(ANIMATION_CLASSES.leaveDone);
5074
+ this.settleInstantly('left');
5050
5075
  this.markForcedThisFrame();
5051
5076
  }
5077
+ settleInstantly(state) {
5078
+ const doneClass = state === 'entered' ? ANIMATION_CLASSES.enterDone : ANIMATION_CLASSES.leaveDone;
5079
+ this.updateState(state);
5080
+ this.removeClasses(...ALL_ANIMATION_CLASSES.filter((cls) => cls !== doneClass));
5081
+ this.addClass(doneClass);
5082
+ }
5052
5083
  markForcedThisFrame() {
5053
5084
  this.forcedThisFrame = true;
5054
5085
  if (typeof requestAnimationFrame === 'undefined')
@@ -6703,12 +6734,9 @@ const createUnsavedChangesTracker = (config) => {
6703
6734
  throw new RuntimeError(UNSAVED_CHANGES_ERROR_CODES.MISSING_CONFIRM, '[createUnsavedChangesTracker] No confirm: pass `confirm` or call `provideUnsavedChangesConfirm()` in a parent injector.');
6704
6735
  }
6705
6736
  const normalized = normalizeUnsavedChangesSource(config.source);
6706
- const hasExplicitDefault = config.defaultValue !== undefined;
6707
- const initialDefault = hasExplicitDefault
6708
- ? typeof config.defaultValue === 'function'
6709
- ? config.defaultValue()
6710
- : config.defaultValue
6711
- : untracked(normalized.value);
6737
+ const explicitDefault = typeof config.defaultValue === 'function' ? config.defaultValue() : config.defaultValue;
6738
+ const hasExplicitDefault = explicitDefault !== undefined && explicitDefault !== null;
6739
+ const initialDefault = hasExplicitDefault ? explicitDefault : untracked(normalized.value);
6712
6740
  const _defaultValue = signal(initialDefault, /* @ts-ignore */
6713
6741
  ...(ngDevMode ? [{ debugName: "_defaultValue" }] : /* istanbul ignore next */ []));
6714
6742
  // No explicit default → capture the first non-null value the source produces as the baseline.
@@ -7073,6 +7101,9 @@ class ClickOutsideDirective {
7073
7101
  this.pressStartedInside = event.composedPath().includes(this.elementRef.nativeElement);
7074
7102
  }), takeUntilDestroyed())
7075
7103
  .subscribe();
7104
+ fromEvent(this.document.documentElement, 'pointercancel', { capture: true })
7105
+ .pipe(tap(() => (this.pressStartedInside = false)), takeUntilDestroyed())
7106
+ .subscribe();
7076
7107
  fromEvent(this.document.documentElement, 'click')
7077
7108
  .pipe(tap((event) => {
7078
7109
  const hostElement = this.elementRef.nativeElement;
@@ -7639,8 +7670,11 @@ const FOCUSABLE_SELECTOR = /* @__PURE__ */ [
7639
7670
  'select:not([disabled])',
7640
7671
  'textarea:not([disabled])',
7641
7672
  'iframe',
7673
+ 'audio[controls]',
7674
+ 'video[controls]',
7675
+ 'details > summary:first-of-type',
7642
7676
  '[tabindex]:not([tabindex="-1"])',
7643
- '[contenteditable="true"]',
7677
+ '[contenteditable]:not([contenteditable="false"])',
7644
7678
  ].join(',');
7645
7679
  const isHTMLElement = (value) => {
7646
7680
  // `instanceof` is realm-bound: an element adopted by a same-origin pop-up (the `document` mount
@@ -7670,10 +7704,24 @@ const isFocusable = (element, document) => {
7670
7704
  const view = document.defaultView;
7671
7705
  const style = view?.getComputedStyle(element);
7672
7706
  const isVisible = style?.display !== 'none' && style?.visibility !== 'hidden' && element.getClientRects().length > 0;
7673
- return isVisible && !element.hasAttribute('disabled') && element.tabIndex >= 0;
7674
- };
7707
+ return (isVisible &&
7708
+ !element.hasAttribute('disabled') &&
7709
+ !element.matches(':disabled') &&
7710
+ !element.closest('[inert]') &&
7711
+ element.tabIndex >= 0);
7712
+ };
7713
+ const isNamedRadio = (element) => element.tagName === 'INPUT' && element.type === 'radio' && !!element.name;
7714
+ const isSameTabStop = (a, b) => a === b || (isNamedRadio(a) && isNamedRadio(b) && a.name === b.name && a.form === b.form);
7715
+ /** The elements Tab stops on inside `container`, with each named radio group collapsed to its checked (else first) radio. */
7675
7716
  const getFocusableElements = (container, document) => {
7676
- return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => isFocusable(el, document));
7717
+ const elements = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => isFocusable(el, document));
7718
+ return elements.filter((element) => {
7719
+ if (!isNamedRadio(element)) {
7720
+ return true;
7721
+ }
7722
+ const group = elements.filter((other) => isSameTabStop(element, other));
7723
+ return (group.find((radio) => radio.checked) ?? group[0]) === element;
7724
+ });
7677
7725
  };
7678
7726
  const getHeadingElement = (container) => {
7679
7727
  return container.querySelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
@@ -7729,12 +7777,12 @@ const setupFocusTrap = (paneElement, overlayRef, enabled, isTopMost, document) =
7729
7777
  focusElement(event.shiftKey ? (lastElement ?? null) : (firstElement ?? null));
7730
7778
  return;
7731
7779
  }
7732
- if (event.shiftKey && activeElement === firstElement) {
7780
+ if (event.shiftKey && firstElement && isSameTabStop(activeElement, firstElement)) {
7733
7781
  event.preventDefault();
7734
7782
  focusElement(lastElement ?? null);
7735
7783
  return;
7736
7784
  }
7737
- if (!event.shiftKey && activeElement === lastElement) {
7785
+ if (!event.shiftKey && lastElement && isSameTabStop(activeElement, lastElement)) {
7738
7786
  event.preventDefault();
7739
7787
  focusElement(firstElement ?? null);
7740
7788
  }
@@ -8364,6 +8412,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8364
8412
  }
8365
8413
  _state.set('closed');
8366
8414
  _componentInstance.set(null);
8415
+ afterOpenedSubject.complete();
8367
8416
  afterClosedSubject.next(closeEvent);
8368
8417
  afterClosedSubject.complete();
8369
8418
  },
@@ -8443,14 +8492,18 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8443
8492
  }
8444
8493
  };
8445
8494
  const isTopMost = (overlayRef) => {
8446
- const entries = openEntriesState();
8447
- for (let index = entries.length - 1; index >= 0; index--) {
8448
- const entry = entries[index];
8449
- if (!entry || entry.config.passive || entry.state() === 'closing' || entry.state() === 'closed')
8495
+ let topMost = null;
8496
+ let topMostLayer = -Infinity;
8497
+ for (const entry of openEntriesState()) {
8498
+ if (entry.config.passive || entry.state() === 'closing' || entry.state() === 'closed')
8450
8499
  continue;
8451
- return entry === overlayRef;
8500
+ const entryLayer = entry.config.zIndex ?? DEFAULT_OVERLAY_LAYER;
8501
+ if (entryLayer >= topMostLayer) {
8502
+ topMost = entry;
8503
+ topMostLayer = entryLayer;
8504
+ }
8452
8505
  }
8453
- return false;
8506
+ return topMost === overlayRef;
8454
8507
  };
8455
8508
  const getAnimatedLifecycle = (componentRef) => {
8456
8509
  const componentLifecycle = componentRef.instance.animatedLifecycle?.();
@@ -8601,7 +8654,13 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8601
8654
  openEntriesState.update((entries) => entries.filter((entry) => entry !== overlayRef));
8602
8655
  maybeDestroyRootElements(targetDocument);
8603
8656
  if (config.restoreFocus !== false && overlayStillOwnsFocus) {
8604
- focusRestoreChain.find((element) => element.isConnected)?.focus({ preventScroll: true });
8657
+ for (const element of focusRestoreChain) {
8658
+ if (!element.isConnected)
8659
+ continue;
8660
+ element.focus({ preventScroll: true });
8661
+ if (element.contains(targetDocument.activeElement))
8662
+ break;
8663
+ }
8605
8664
  }
8606
8665
  overlayRef.finishClose(closeEvent);
8607
8666
  };
@@ -8725,12 +8784,13 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8725
8784
  else {
8726
8785
  lifecycle.enter();
8727
8786
  }
8728
- lifecycle.state$
8787
+ const enteredSubscription = lifecycle.state$
8729
8788
  .pipe(filter((state) => state === 'entered'), take(1), tap(() => {
8730
8789
  applyInitialFocus(paneElement, autoFocus, targetDocument);
8731
8790
  overlayRef.markOpened();
8732
8791
  }))
8733
8792
  .subscribe();
8793
+ cleanupFns.push(() => enteredSubscription.unsubscribe());
8734
8794
  });
8735
8795
  return overlayRef;
8736
8796
  };
@@ -10519,7 +10579,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
10519
10579
 
10520
10580
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
10521
10581
  /** The version of `@ethlete/core` this build was cut from. */
10522
- const CORE_VERSION = '5.0.0-next.62';
10582
+ const CORE_VERSION = '5.0.0-next.63';
10523
10583
 
10524
10584
  /**
10525
10585
  * Generated bundle index. Do not edit.