@ethlete/components 1.0.0-next.33 → 1.0.0-next.35

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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, input, booleanAttribute, signal, computed, effect, untracked, Directive, inject, afterNextRender, DestroyRef, TemplateRef, ElementRef, model, linkedSignal, ViewEncapsulation, Component, output, numberAttribute, contentChild, NgZone, ApplicationRef, viewChild, ViewContainerRef, createEnvironmentInjector, DOCUMENT, runInInjectionContext, EnvironmentInjector, inputBinding, createComponent, isSignal, Injector, assertInInjectionContext, isDevMode as isDevMode$1, provideEnvironmentInitializer, viewChildren, contentChildren, afterRenderEffect, outputBinding, afterEveryRender, PLATFORM_ID } from '@angular/core';
3
3
  import * as i1 from '@ethlete/core';
4
- import { RuntimeError, createComponentId, injectLocale, defineLabels, toProvideFn, toInjectFn, toToken, injectStyleManager, defineStaticRootProvider, inferMimeType, injectColorThemes, ProvideColorDirective, injectRenderer, SurfaceInteractiveDirective, createCanAnimateSignal, ColorInteractiveDirective, ProvideSurfaceDirective, COLOR_PROVIDER, SURFACE_PROVIDER, injectSurfaceThemes, injectSurfaceThemesPrefix, injectSurfaceContextTracker, injectBoundaryElement, ANIMATED_LIFECYCLE_TOKEN, resolveAppRootColorProvider, resolveSurfaceByElevation, createCssSurfaceName, provideBoundaryElement, AnimatedLifecycleDirective, injectBreakpointObserver, isHTMLElement, equal, animationDebugLog, defineRootProvider, injectOverlayRuntime, anchoredOverlayPosition, enableAnchoredOverlayPositionExtras, randomId, forceReflow, nextFrame, elementCanScroll, matchesReducedMotion, createSwipeTracker, ScrollObserverDirective, signalElementScrollState, ScrollObserverStartDirective, ScrollObserverEndDirective, applyHostListener, injectQueryParam, createUnsavedChangesTracker, createDocumentElementSignal, defineProvider, fromNextFrame, injectRoute, AnimatedIfDirective, signalElementDimensions, applyInitialFocus, syncSignal, AutoSurfaceDirective, setInputSignal, injectTemplateRef, signalHostElementScrollState, applyStructuredDataBinding, typedBreakpointTransform, signalElementChildren, signalElementIntersection, signalHostAttributes, signalHostClasses, signalHostStyles, getScrollContainerTarget, getScrollItemTarget, getElementScrollCoordinates, provideBreakpointInstance, signalClasses, useCursorDragScroll, signalStyles, getScrollSnapTarget, injectPrefersReducedMotion, injectIsDocumentVisible, signalHostElementIntersection, clamp as clamp$1, injectErrorTheme, injectAnimatedBlockSize, injectObserveBreakpoint, AnimatableDirective, ColorInteractiveContainerDirective, ColorInteractiveExcludeDirective, createFlipAnimation, createFlipAnimationGroup, ColorInteractiveHasFocusDirective, htmlToMarkdown, markdownToHtml, injectHasTouchInput, signalHostElementDimensions, DragHandleDirective, ResizeHandlesComponent, copyToClipboard, numberBreakpointTransform, applyLinkBinding, applyHeadTitleBinding, injectViewportSize, injectHostElement, injectFocusVisibleTracker, getFocusableElements, dragGestureFrom, injectRouterEvent, canUseSessionMemory, createAutoSessionMemoryKey, createSessionMemory } from '@ethlete/core';
4
+ import { RuntimeError, createComponentId, injectLocale, defineLabels, toProvideFn, toInjectFn, toToken, injectStyleManager, defineStaticRootProvider, inferMimeType, ProvideColorDirective, injectColorThemes, injectRenderer, SurfaceInteractiveDirective, createCanAnimateSignal, ColorInteractiveDirective, ProvideSurfaceDirective, COLOR_PROVIDER, SURFACE_PROVIDER, injectSurfaceThemes, injectSurfaceThemesPrefix, injectSurfaceContextTracker, injectBoundaryElement, ANIMATED_LIFECYCLE_TOKEN, resolveAppRootColorProvider, resolveSurfaceByElevation, createCssSurfaceName, provideBoundaryElement, AnimatedLifecycleDirective, injectBreakpointObserver, isHTMLElement, equal, animationDebugLog, defineRootProvider, injectOverlayRuntime, anchoredOverlayPosition, enableAnchoredOverlayPositionExtras, randomId, forceReflow, nextFrame, elementCanScroll, matchesReducedMotion, createSwipeTracker, ScrollObserverDirective, signalElementScrollState, ScrollObserverStartDirective, ScrollObserverEndDirective, applyHostListener, injectQueryParam, createUnsavedChangesTracker, createDocumentElementSignal, defineProvider, fromNextFrame, injectRoute, AnimatedIfDirective, signalElementDimensions, applyInitialFocus, syncSignal, AutoSurfaceDirective, setInputSignal, injectTemplateRef, signalHostElementScrollState, applyStructuredDataBinding, typedBreakpointTransform, signalElementChildren, signalElementIntersection, signalHostAttributes, signalHostClasses, signalHostStyles, getScrollContainerTarget, getScrollItemTarget, getElementScrollCoordinates, provideBreakpointInstance, signalClasses, useCursorDragScroll, signalStyles, getScrollSnapTarget, injectPrefersReducedMotion, injectIsDocumentVisible, signalHostElementIntersection, clamp as clamp$1, injectErrorTheme, injectAnimatedBlockSize, injectObserveBreakpoint, AnimatableDirective, ColorInteractiveContainerDirective, ColorInteractiveExcludeDirective, createFlipAnimation, createFlipAnimationGroup, ColorInteractiveHasFocusDirective, htmlToMarkdown, markdownToHtml, injectHasTouchInput, signalHostElementDimensions, DragHandleDirective, ResizeHandlesComponent, copyToClipboard, numberBreakpointTransform, applyLinkBinding, applyHeadTitleBinding, injectViewportSize, injectHostElement, injectFocusVisibleTracker, getFocusableElements, dragGestureFrom, injectRouterEvent, canUseSessionMemory, createAutoSessionMemoryKey, createSessionMemory } from '@ethlete/core';
5
5
  import { NgTemplateOutlet, NgComponentOutlet, Location, DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
6
6
  import { DomSanitizer } from '@angular/platform-browser';
7
7
  import { format, startOfWeek, startOfDay, endOfWeek, isBefore, addDays, endOfMonth, startOfMonth, isSameYear, isSameMonth, isSameDay, startOfYear, isAfter, addYears, addMonths, setMonth, setYear, getDate, setDate, lastDayOfMonth, getWeek, endOfYear, parse, isValid, setSeconds, setMinutes, setHours, setMilliseconds } from 'date-fns';
@@ -9,7 +9,7 @@ import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable, outpu
9
9
  import { Subject, take, tap, timer, takeUntil, fromEvent, filter, combineLatest, switchMap, map, distinctUntilChanged, EMPTY, debounceTime, merge, defer, finalize, of, isObservable, from, catchError, concat, scan, interval, animationFrameScheduler, EmptyError, exhaustMap, Observable, shareReplay, defaultIfEmpty, expand, toArray } from 'rxjs';
10
10
  import * as i1$1 from '@angular/router';
11
11
  import { Router, RouterLink, RouterLinkActive, NavigationEnd } from '@angular/router';
12
- import { withArgs, queryStateSignal, isQueryStateSuccess, isQueryStateFailure, isQueryStateLoading, queryComputed, queryDevtoolsEntries, createQueryErrorResponse, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleEn, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToTitleDe, queryErrorMessages, extractQuery } from '@ethlete/query';
12
+ import { withArgs, queryStateSignal, isQueryStateSuccess, isQueryStateFailure, isQueryStateLoading, queryComputed, queryDevtoolsEntries, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, QUERY_DEVTOOLS_FAULT_STATUSES, clearQueryDevtoolsFaults, createQueryErrorResponse, setQueryDevtoolsFault, sumQueryDevtoolsStats, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleEn, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToTitleDe, queryErrorMessages, extractQuery } from '@ethlete/query';
13
13
  import { createManagedMetadataKey, metadata, validate, FORM_FIELD, form } from '@angular/forms/signals';
14
14
  import { tap as tap$1 } from 'rxjs/operators';
15
15
  import { HttpErrorResponse } from '@angular/common/http';
@@ -2878,7 +2878,14 @@ class PictureComponent {
2878
2878
  * @example [sizes]="['(min-width: 800px) 50vw', '100vw']"
2879
2879
  */
2880
2880
  this.sizes = input(null, { ...(ngDevMode ? { debugName: "sizes" } : /* istanbul ignore next */ {}), transform: normalizePictureSizes });
2881
- /** The image finished loading. */
2881
+ /**
2882
+ * How the image fills its box. Setting it makes the `<img>` fill the host, so the host's own size (a class, an
2883
+ * `aspectRatio`, a grid cell) decides the box and this decides what the image does inside it. Requires the
2884
+ * host to have a definite size in both axes. Omit for the default flow layout, where the image sizes itself.
2885
+ */
2886
+ this.fit = input(null, /* @ts-ignore */
2887
+ ...(ngDevMode ? [{ debugName: "fit" }] : /* istanbul ignore next */ []));
2888
+ /** The image finished loading, with the intrinsic pixel dimensions the browser decoded. */
2882
2889
  this.imgLoad = output();
2883
2890
  /** The image failed to load - a dead URL, a network error, an undecodable file. */
2884
2891
  this.imgError = output();
@@ -2897,36 +2904,61 @@ class PictureComponent {
2897
2904
  this.defaultSrcUrl = computed(() => extractFirstImageUrl(this.resolvedDefaultSource()), /* @ts-ignore */
2898
2905
  ...(ngDevMode ? [{ debugName: "defaultSrcUrl" }] : /* istanbul ignore next */ []));
2899
2906
  /**
2900
- * Reset by the source it describes: a picture pointed at a new URL is loading again, and a state that
2901
- * stayed `'loaded'` would leave the placeholder hidden for the second image.
2907
+ * What everything learnt from the last load resets on: a picture pointed at a new URL - or at new sources,
2908
+ * which decide the image just as much - is loading again, and a state that stayed `'loaded'` would leave the
2909
+ * placeholder hidden for the second image.
2902
2910
  */
2903
- this.loadState = linkedSignal({ ...(ngDevMode ? { debugName: "loadState" } : /* istanbul ignore next */ {}), source: () => this.defaultSrcUrl(),
2911
+ this.loadResetKey = computed(() => [this.defaultSrcUrl(), this.sources()], /* @ts-ignore */
2912
+ ...(ngDevMode ? [{ debugName: "loadResetKey" }] : /* istanbul ignore next */ []));
2913
+ this.loadState = linkedSignal({ ...(ngDevMode ? { debugName: "loadState" } : /* istanbul ignore next */ {}), source: () => this.loadResetKey(),
2904
2914
  computation: () => PICTURE_STATES.LOADING });
2915
+ this.loadedSize = linkedSignal({ ...(ngDevMode ? { debugName: "loadedSize" } : /* istanbul ignore next */ {}), source: () => this.loadResetKey(),
2916
+ computation: () => null });
2905
2917
  /**
2906
2918
  * Where the image has got to, as a signal rather than only a pair of events - so a template can react to it
2907
2919
  * without keeping its own copy. Also on the host as `data-state`, for styling.
2908
2920
  */
2909
2921
  this.state = computed(() => this.loadState(), /* @ts-ignore */
2910
2922
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
2923
+ /**
2924
+ * The image's intrinsic pixel dimensions, once the browser has decoded them. `null` while loading and after a
2925
+ * failure, so a template can wait for a real measurement rather than guess one.
2926
+ */
2927
+ this.naturalSize = computed(() => this.loadedSize(), /* @ts-ignore */
2928
+ ...(ngDevMode ? [{ debugName: "naturalSize" }] : /* istanbul ignore next */ []));
2929
+ /**
2930
+ * The intrinsic ratio as a single number in CSS `aspect-ratio` orientation - width over height. `null`
2931
+ * whenever `naturalSize()` is.
2932
+ */
2933
+ this.naturalAspectRatio = computed(() => {
2934
+ const size = this.naturalSize();
2935
+ return size && size.height ? size.width / size.height : null;
2936
+ }, /* @ts-ignore */
2937
+ ...(ngDevMode ? [{ debugName: "naturalAspectRatio" }] : /* istanbul ignore next */ []));
2911
2938
  }
2912
- markLoaded() {
2939
+ markLoaded(event) {
2940
+ const img = event.target;
2941
+ this.loadedSize.set({ width: img.naturalWidth, height: img.naturalHeight });
2913
2942
  this.loadState.set(PICTURE_STATES.LOADED);
2914
- this.imgLoad.emit();
2943
+ this.imgLoad.emit({ naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight });
2915
2944
  }
2916
2945
  markFailed() {
2946
+ this.loadedSize.set(null);
2917
2947
  this.loadState.set(PICTURE_STATES.ERROR);
2918
2948
  this.imgError.emit();
2919
2949
  }
2920
2950
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: PictureComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2921
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: PictureComponent, isStandalone: true, selector: "et-picture", inputs: { sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, defaultSrc: { classPropertyName: "defaultSrc", publicName: "defaultSrc", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: true, transformFunction: null }, figcaption: { classPropertyName: "figcaption", publicName: "figcaption", isSignal: true, isRequired: false, transformFunction: null }, priority: { classPropertyName: "priority", publicName: "priority", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imgLoad: "imgLoad", imgError: "imgError" }, host: { properties: { "attr.data-state": "state()" }, classAttribute: "et-picture" }, queries: [{ propertyName: "placeholder", first: true, predicate: PicturePlaceholderDirective, descendants: true, isSignal: true }, { propertyName: "errorSlot", first: true, predicate: PictureErrorDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<figure class=\"et-picture-figure\">\n <picture class=\"et-picture-picture\">\n @for (source of resolvedSources(); track source.srcset) {\n <source\n [attr.type]=\"source.type\"\n [attr.srcset]=\"source.srcset\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.media]=\"source.media\"\n />\n }\n\n @if (resolvedDefaultSource(); as source) {\n <img\n [attr.srcset]=\"source.srcset\"\n [attr.src]=\"defaultSrcUrl()\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.alt]=\"alt()\"\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.loading]=\"priority() ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"priority() ? 'high' : 'auto'\"\n [style.aspect-ratio]=\"aspectRatio()\"\n (load)=\"markLoaded()\"\n (error)=\"markFailed()\"\n class=\"et-picture-img\"\n />\n }\n\n <!-- Overlaid rather than swapped in: the img has to stay in the DOM to keep loading, and replacing it\n would restart the request. -->\n @if (state() === 'loading' && placeholder()) {\n <span class=\"et-picture-slot\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"placeholder()!.templateRef\" />\n </span>\n }\n\n @if (state() === 'error' && errorSlot()) {\n <span class=\"et-picture-slot\">\n <ng-container [ngTemplateOutlet]=\"errorSlot()!.templateRef\" />\n </span>\n }\n </picture>\n\n @if (figcaption()) {\n <figcaption class=\"et-picture-figcaption\">{{ figcaption() }}</figcaption>\n }\n</figure>\n", styles: ["@layer components{.et-picture{display:block}.et-picture .et-picture-figure{margin:0;display:block}.et-picture .et-picture-picture{display:block;position:relative}.et-picture .et-picture-img{display:block;max-inline-size:100%;block-size:auto}.et-picture .et-picture-slot{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}.et-picture:where([data-state=error]):has(.et-picture-slot) .et-picture-img{visibility:hidden}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None }); }
2951
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: PictureComponent, isStandalone: true, selector: "et-picture", inputs: { sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, defaultSrc: { classPropertyName: "defaultSrc", publicName: "defaultSrc", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: true, transformFunction: null }, figcaption: { classPropertyName: "figcaption", publicName: "figcaption", isSignal: true, isRequired: false, transformFunction: null }, priority: { classPropertyName: "priority", publicName: "priority", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imgLoad: "imgLoad", imgError: "imgError" }, host: { properties: { "attr.data-state": "state()", "attr.data-fit": "fit()", "style.--_et-picture-fit": "fit()" }, classAttribute: "et-picture" }, queries: [{ propertyName: "placeholder", first: true, predicate: PicturePlaceholderDirective, descendants: true, isSignal: true }, { propertyName: "errorSlot", first: true, predicate: PictureErrorDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<figure class=\"et-picture-figure\">\n <picture class=\"et-picture-picture\">\n @for (source of resolvedSources(); track source.srcset) {\n <source\n [attr.type]=\"source.type\"\n [attr.srcset]=\"source.srcset\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.media]=\"source.media\"\n />\n }\n\n @if (resolvedDefaultSource(); as source) {\n <img\n [attr.srcset]=\"source.srcset\"\n [attr.src]=\"defaultSrcUrl()\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.alt]=\"alt()\"\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.loading]=\"priority() ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"priority() ? 'high' : 'auto'\"\n [style.aspect-ratio]=\"aspectRatio()\"\n (load)=\"markLoaded($event)\"\n (error)=\"markFailed()\"\n class=\"et-picture-img\"\n />\n }\n\n <!-- Overlaid rather than swapped in: the img has to stay in the DOM to keep loading, and replacing it\n would restart the request. -->\n @if (state() === 'loading' && placeholder()) {\n <span class=\"et-picture-slot\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"placeholder()!.templateRef\" />\n </span>\n }\n\n @if (state() === 'error' && errorSlot()) {\n <span class=\"et-picture-slot\">\n <ng-container [ngTemplateOutlet]=\"errorSlot()!.templateRef\" />\n </span>\n }\n </picture>\n\n @if (figcaption()) {\n <figcaption class=\"et-picture-figcaption\">{{ figcaption() }}</figcaption>\n }\n</figure>\n", styles: ["@layer components{.et-picture{display:block}.et-picture .et-picture-figure{margin:0;display:block}.et-picture .et-picture-picture{display:block;position:relative}.et-picture .et-picture-img{display:block;max-inline-size:100%;block-size:auto}.et-picture .et-picture-slot{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}.et-picture:where([data-fit]) .et-picture-figure,.et-picture:where([data-fit]) .et-picture-picture{inline-size:100%;block-size:100%}.et-picture:where([data-fit]) .et-picture-img{inline-size:100%;block-size:100%;object-fit:var(--_et-picture-fit)}.et-picture:where([data-state=error]):has(.et-picture-slot) .et-picture-img{visibility:hidden}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None }); }
2922
2952
  }
2923
2953
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: PictureComponent, decorators: [{
2924
2954
  type: Component,
2925
2955
  args: [{ selector: 'et-picture', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet], host: {
2926
2956
  class: 'et-picture',
2927
2957
  '[attr.data-state]': 'state()',
2928
- }, template: "<figure class=\"et-picture-figure\">\n <picture class=\"et-picture-picture\">\n @for (source of resolvedSources(); track source.srcset) {\n <source\n [attr.type]=\"source.type\"\n [attr.srcset]=\"source.srcset\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.media]=\"source.media\"\n />\n }\n\n @if (resolvedDefaultSource(); as source) {\n <img\n [attr.srcset]=\"source.srcset\"\n [attr.src]=\"defaultSrcUrl()\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.alt]=\"alt()\"\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.loading]=\"priority() ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"priority() ? 'high' : 'auto'\"\n [style.aspect-ratio]=\"aspectRatio()\"\n (load)=\"markLoaded()\"\n (error)=\"markFailed()\"\n class=\"et-picture-img\"\n />\n }\n\n <!-- Overlaid rather than swapped in: the img has to stay in the DOM to keep loading, and replacing it\n would restart the request. -->\n @if (state() === 'loading' && placeholder()) {\n <span class=\"et-picture-slot\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"placeholder()!.templateRef\" />\n </span>\n }\n\n @if (state() === 'error' && errorSlot()) {\n <span class=\"et-picture-slot\">\n <ng-container [ngTemplateOutlet]=\"errorSlot()!.templateRef\" />\n </span>\n }\n </picture>\n\n @if (figcaption()) {\n <figcaption class=\"et-picture-figcaption\">{{ figcaption() }}</figcaption>\n }\n</figure>\n", styles: ["@layer components{.et-picture{display:block}.et-picture .et-picture-figure{margin:0;display:block}.et-picture .et-picture-picture{display:block;position:relative}.et-picture .et-picture-img{display:block;max-inline-size:100%;block-size:auto}.et-picture .et-picture-slot{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}.et-picture:where([data-state=error]):has(.et-picture-slot) .et-picture-img{visibility:hidden}}\n"] }]
2929
- }], propDecorators: { sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], defaultSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSrc", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: true }] }], figcaption: [{ type: i0.Input, args: [{ isSignal: true, alias: "figcaption", required: false }] }], priority: [{ type: i0.Input, args: [{ isSignal: true, alias: "priority", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], sizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizes", required: false }] }], imgLoad: [{ type: i0.Output, args: ["imgLoad"] }], imgError: [{ type: i0.Output, args: ["imgError"] }], placeholder: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PicturePlaceholderDirective), { isSignal: true }] }], errorSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PictureErrorDirective), { isSignal: true }] }] } });
2958
+ '[attr.data-fit]': 'fit()',
2959
+ '[style.--_et-picture-fit]': 'fit()',
2960
+ }, template: "<figure class=\"et-picture-figure\">\n <picture class=\"et-picture-picture\">\n @for (source of resolvedSources(); track source.srcset) {\n <source\n [attr.type]=\"source.type\"\n [attr.srcset]=\"source.srcset\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.media]=\"source.media\"\n />\n }\n\n @if (resolvedDefaultSource(); as source) {\n <img\n [attr.srcset]=\"source.srcset\"\n [attr.src]=\"defaultSrcUrl()\"\n [attr.sizes]=\"source.sizes ?? sizes()\"\n [attr.alt]=\"alt()\"\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.loading]=\"priority() ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"priority() ? 'high' : 'auto'\"\n [style.aspect-ratio]=\"aspectRatio()\"\n (load)=\"markLoaded($event)\"\n (error)=\"markFailed()\"\n class=\"et-picture-img\"\n />\n }\n\n <!-- Overlaid rather than swapped in: the img has to stay in the DOM to keep loading, and replacing it\n would restart the request. -->\n @if (state() === 'loading' && placeholder()) {\n <span class=\"et-picture-slot\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"placeholder()!.templateRef\" />\n </span>\n }\n\n @if (state() === 'error' && errorSlot()) {\n <span class=\"et-picture-slot\">\n <ng-container [ngTemplateOutlet]=\"errorSlot()!.templateRef\" />\n </span>\n }\n </picture>\n\n @if (figcaption()) {\n <figcaption class=\"et-picture-figcaption\">{{ figcaption() }}</figcaption>\n }\n</figure>\n", styles: ["@layer components{.et-picture{display:block}.et-picture .et-picture-figure{margin:0;display:block}.et-picture .et-picture-picture{display:block;position:relative}.et-picture .et-picture-img{display:block;max-inline-size:100%;block-size:auto}.et-picture .et-picture-slot{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}.et-picture:where([data-fit]) .et-picture-figure,.et-picture:where([data-fit]) .et-picture-picture{inline-size:100%;block-size:100%}.et-picture:where([data-fit]) .et-picture-img{inline-size:100%;block-size:100%;object-fit:var(--_et-picture-fit)}.et-picture:where([data-state=error]):has(.et-picture-slot) .et-picture-img{visibility:hidden}}\n"] }]
2961
+ }], propDecorators: { sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], defaultSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSrc", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: true }] }], figcaption: [{ type: i0.Input, args: [{ isSignal: true, alias: "figcaption", required: false }] }], priority: [{ type: i0.Input, args: [{ isSignal: true, alias: "priority", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], sizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizes", required: false }] }], fit: [{ type: i0.Input, args: [{ isSignal: true, alias: "fit", required: false }] }], imgLoad: [{ type: i0.Output, args: ["imgLoad"] }], imgError: [{ type: i0.Output, args: ["imgError"] }], placeholder: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PicturePlaceholderDirective), { isSignal: true }] }], errorSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PictureErrorDirective), { isSignal: true }] }] } });
2930
2962
 
2931
2963
  const PICTURE_IMPORTS = [PictureComponent, PicturePlaceholderDirective, PictureErrorDirective];
2932
2964
 
@@ -3127,11 +3159,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
3127
3159
  const BASE_STROKE_WIDTH = 10;
3128
3160
  class SpinnerComponent {
3129
3161
  constructor() {
3162
+ this.provideColor = inject(ProvideColorDirective);
3130
3163
  this.diameter = input(18, { ...(ngDevMode ? { debugName: "diameter" } : /* istanbul ignore next */ {}), transform: numberAttribute });
3131
3164
  this.strokeWidth = input(2.25, { ...(ngDevMode ? { debugName: "strokeWidth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
3132
3165
  this.track = input(false, { ...(ngDevMode ? { debugName: "track" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3133
3166
  this.value = input(0, { ...(ngDevMode ? { debugName: "value" } : /* istanbul ignore next */ {}), transform: numberAttribute });
3134
3167
  this.determinate = input(false, { ...(ngDevMode ? { debugName: "determinate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3168
+ /**
3169
+ * Whether `color` was set on this spinner. The theme colour is deliberately gated on the input rather than
3170
+ * applied whenever a colour scope resolves: a spinner without `color` must keep inheriting `currentColor`
3171
+ * from its context (a button's label, muted body text), even when it sits inside an ancestor `et-color--*`
3172
+ * scope.
3173
+ */
3174
+ this.hasExplicitColor = computed(() => (this.provideColor.color() ?? null) !== null, /* @ts-ignore */
3175
+ ...(ngDevMode ? [{ debugName: "hasExplicitColor" }] : /* istanbul ignore next */ []));
3135
3176
  this.circleRadius = computed(() => Math.max(1, (this.diameter() - BASE_STROKE_WIDTH) / 2), /* @ts-ignore */
3136
3177
  ...(ngDevMode ? [{ debugName: "circleRadius" }] : /* istanbul ignore next */ []));
3137
3178
  this.normalizedStrokeWidth = computed(() => (this.strokeWidth() / this.diameter()) * 100, /* @ts-ignore */
@@ -3154,7 +3195,7 @@ class SpinnerComponent {
3154
3195
  ...(ngDevMode ? [{ debugName: "determinateDashOffset" }] : /* istanbul ignore next */ []));
3155
3196
  }
3156
3197
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3157
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SpinnerComponent, isStandalone: true, selector: "et-spinner", inputs: { diameter: { classPropertyName: "diameter", publicName: "diameter", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, track: { classPropertyName: "track", publicName: "track", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, determinate: { classPropertyName: "determinate", publicName: "determinate", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.et-spinner--determinate": "determinate()", "style.--et-spinner-size.px": "diameter()", "style.--et-spinner-stroke-width.px": "strokeWidth()", "attr.aria-valuenow": "determinate() ? clampedValue() : null", "attr.aria-valuemin": "determinate() ? 0 : null", "attr.aria-valuemax": "determinate() ? 100 : null" }, classAttribute: "et-spinner" }, ngImport: i0, template: `
3198
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SpinnerComponent, isStandalone: true, selector: "et-spinner", inputs: { diameter: { classPropertyName: "diameter", publicName: "diameter", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, track: { classPropertyName: "track", publicName: "track", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, determinate: { classPropertyName: "determinate", publicName: "determinate", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.et-spinner--determinate": "determinate()", "class.et-spinner--themed": "hasExplicitColor()", "style.--et-spinner-size.px": "diameter()", "style.--et-spinner-stroke-width.px": "strokeWidth()", "attr.aria-valuenow": "determinate() ? clampedValue() : null", "attr.aria-valuemin": "determinate() ? 0 : null", "attr.aria-valuemax": "determinate() ? 100 : null" }, classAttribute: "et-spinner" }, hostDirectives: [{ directive: i1.ProvideColorDirective, inputs: ["etProvideColor", "color"] }], ngImport: i0, template: `
3158
3199
  <ng-template #circle>
3159
3200
  <svg
3160
3201
  [attr.viewBox]="viewBox()"
@@ -3226,7 +3267,7 @@ class SpinnerComponent {
3226
3267
  </div>
3227
3268
  </div>
3228
3269
  }
3229
- `, isInline: true, styles: ["@layer components{@property --et-spinner-size{syntax: \"<length>\"; inherits: false; initial-value: 18px;}@property --et-spinner-stroke-width{syntax: \"<length>\"; inherits: false; initial-value: 2.25px;}@property --et-spinner-color{syntax: \"<color>\"; inherits: true; initial-value: currentColor;}@property --et-spinner-track-color{syntax: \"<color>\"; inherits: true; initial-value: transparent;}@property --et-spinner-duration{syntax: \"<time>\"; inherits: false; initial-value: 1333ms;}@keyframes et-spinner-rotate{to{transform:rotate(360deg)}}@keyframes et-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@keyframes et-spinner-left-spin{0%{transform:rotate(260deg)}50%{transform:rotate(130deg)}to{transform:rotate(260deg)}}@keyframes et-spinner-right-spin{0%{transform:rotate(-260deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-260deg)}}.et-spinner{position:relative;display:inline-block;width:var(--et-spinner-size);height:var(--et-spinner-size);color:var(--et-spinner-color);direction:ltr;vertical-align:middle;line-height:0;overflow:hidden}.et-spinner .et-spinner-track-container,.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-indeterminate-container,.et-spinner .et-spinner-layer,.et-spinner .et-spinner-circle-graphic{position:absolute;inset:0;width:100%;height:100%}.et-spinner .et-spinner-track-container{transform:rotate(-90deg)}.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-circle-graphic{fill:none}.et-spinner .et-spinner-track-circle,.et-spinner .et-spinner-circle-graphic circle{fill:none;stroke:currentColor}.et-spinner .et-spinner-track-circle{stroke:var(--et-spinner-track-color, color-mix(in srgb, currentColor 14%, transparent));stroke-linecap:round}.et-spinner .et-spinner-circle-graphic circle{stroke-linecap:round;stroke-linejoin:round}.et-spinner .et-spinner-indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;animation:et-spinner-rotate 1568ms linear infinite}.et-spinner .et-spinner-layer{animation:et-spinner-layer-rotate 5332ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.et-spinner .et-spinner-gap-patch .et-spinner-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.et-spinner .et-spinner-circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.et-spinner .et-spinner-circle-clipper .et-spinner-circle-graphic{width:200%}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{left:-100%}.et-spinner .et-spinner-circle-left .et-spinner-circle-graphic{animation:et-spinner-left-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{animation:et-spinner-right-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-determinate-graphic{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg);fill:none}.et-spinner .et-spinner-determinate-circle{fill:none;stroke:currentColor;stroke-linecap:round;transition:stroke-dashoffset .2s linear}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None }); }
3270
+ `, isInline: true, styles: ["@layer components{@property --et-spinner-size{syntax: \"<length>\"; inherits: false; initial-value: 18px;}@property --et-spinner-stroke-width{syntax: \"<length>\"; inherits: false; initial-value: 2.25px;}@property --et-spinner-color{syntax: \"<color>\"; inherits: true; initial-value: currentColor;}@property --et-spinner-track-color{syntax: \"<color>\"; inherits: true; initial-value: transparent;}@property --et-spinner-duration{syntax: \"<time>\"; inherits: false; initial-value: 1333ms;}@keyframes et-spinner-rotate{to{transform:rotate(360deg)}}@keyframes et-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@keyframes et-spinner-left-spin{0%{transform:rotate(260deg)}50%{transform:rotate(130deg)}to{transform:rotate(260deg)}}@keyframes et-spinner-right-spin{0%{transform:rotate(-260deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-260deg)}}.et-spinner{position:relative;display:inline-block;width:var(--et-spinner-size);height:var(--et-spinner-size);color:var(--et-spinner-color);direction:ltr;vertical-align:middle;line-height:0;overflow:hidden}.et-spinner .et-spinner-track-container,.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-indeterminate-container,.et-spinner .et-spinner-layer,.et-spinner .et-spinner-circle-graphic{position:absolute;inset:0;width:100%;height:100%}.et-spinner .et-spinner-track-container{transform:rotate(-90deg)}.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-circle-graphic{fill:none}.et-spinner .et-spinner-track-circle,.et-spinner .et-spinner-circle-graphic circle{fill:none;stroke:currentColor}.et-spinner .et-spinner-track-circle{stroke:var(--et-spinner-track-color, color-mix(in srgb, currentColor 14%, transparent));stroke-linecap:round}.et-spinner .et-spinner-circle-graphic circle{stroke-linecap:round;stroke-linejoin:round}.et-spinner .et-spinner-indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;animation:et-spinner-rotate 1568ms linear infinite}.et-spinner .et-spinner-layer{animation:et-spinner-layer-rotate 5332ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.et-spinner .et-spinner-gap-patch .et-spinner-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.et-spinner .et-spinner-circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.et-spinner .et-spinner-circle-clipper .et-spinner-circle-graphic{width:200%}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{left:-100%}.et-spinner .et-spinner-circle-left .et-spinner-circle-graphic{animation:et-spinner-left-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{animation:et-spinner-right-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-determinate-graphic{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg);fill:none}.et-spinner .et-spinner-determinate-circle{fill:none;stroke:currentColor;stroke-linecap:round;transition:stroke-dashoffset .2s linear}.et-spinner:where(.et-spinner--themed){--et-spinner-color: var(--et-theme-color-primary-solid, currentColor)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None }); }
3230
3271
  }
3231
3272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SpinnerComponent, decorators: [{
3232
3273
  type: Component,
@@ -3302,16 +3343,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
3302
3343
  </div>
3303
3344
  </div>
3304
3345
  }
3305
- `, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet], host: {
3346
+ `, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet], hostDirectives: [
3347
+ {
3348
+ directive: ProvideColorDirective,
3349
+ inputs: ['etProvideColor:color'],
3350
+ },
3351
+ ], host: {
3306
3352
  class: 'et-spinner',
3307
3353
  '[class.et-spinner--determinate]': 'determinate()',
3354
+ '[class.et-spinner--themed]': 'hasExplicitColor()',
3308
3355
  '[style.--et-spinner-size.px]': 'diameter()',
3309
3356
  '[style.--et-spinner-stroke-width.px]': 'strokeWidth()',
3310
3357
  role: 'progressbar',
3311
3358
  '[attr.aria-valuenow]': 'determinate() ? clampedValue() : null',
3312
3359
  '[attr.aria-valuemin]': 'determinate() ? 0 : null',
3313
3360
  '[attr.aria-valuemax]': 'determinate() ? 100 : null',
3314
- }, styles: ["@layer components{@property --et-spinner-size{syntax: \"<length>\"; inherits: false; initial-value: 18px;}@property --et-spinner-stroke-width{syntax: \"<length>\"; inherits: false; initial-value: 2.25px;}@property --et-spinner-color{syntax: \"<color>\"; inherits: true; initial-value: currentColor;}@property --et-spinner-track-color{syntax: \"<color>\"; inherits: true; initial-value: transparent;}@property --et-spinner-duration{syntax: \"<time>\"; inherits: false; initial-value: 1333ms;}@keyframes et-spinner-rotate{to{transform:rotate(360deg)}}@keyframes et-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@keyframes et-spinner-left-spin{0%{transform:rotate(260deg)}50%{transform:rotate(130deg)}to{transform:rotate(260deg)}}@keyframes et-spinner-right-spin{0%{transform:rotate(-260deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-260deg)}}.et-spinner{position:relative;display:inline-block;width:var(--et-spinner-size);height:var(--et-spinner-size);color:var(--et-spinner-color);direction:ltr;vertical-align:middle;line-height:0;overflow:hidden}.et-spinner .et-spinner-track-container,.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-indeterminate-container,.et-spinner .et-spinner-layer,.et-spinner .et-spinner-circle-graphic{position:absolute;inset:0;width:100%;height:100%}.et-spinner .et-spinner-track-container{transform:rotate(-90deg)}.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-circle-graphic{fill:none}.et-spinner .et-spinner-track-circle,.et-spinner .et-spinner-circle-graphic circle{fill:none;stroke:currentColor}.et-spinner .et-spinner-track-circle{stroke:var(--et-spinner-track-color, color-mix(in srgb, currentColor 14%, transparent));stroke-linecap:round}.et-spinner .et-spinner-circle-graphic circle{stroke-linecap:round;stroke-linejoin:round}.et-spinner .et-spinner-indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;animation:et-spinner-rotate 1568ms linear infinite}.et-spinner .et-spinner-layer{animation:et-spinner-layer-rotate 5332ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.et-spinner .et-spinner-gap-patch .et-spinner-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.et-spinner .et-spinner-circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.et-spinner .et-spinner-circle-clipper .et-spinner-circle-graphic{width:200%}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{left:-100%}.et-spinner .et-spinner-circle-left .et-spinner-circle-graphic{animation:et-spinner-left-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{animation:et-spinner-right-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-determinate-graphic{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg);fill:none}.et-spinner .et-spinner-determinate-circle{fill:none;stroke:currentColor;stroke-linecap:round;transition:stroke-dashoffset .2s linear}}\n"] }]
3361
+ }, styles: ["@layer components{@property --et-spinner-size{syntax: \"<length>\"; inherits: false; initial-value: 18px;}@property --et-spinner-stroke-width{syntax: \"<length>\"; inherits: false; initial-value: 2.25px;}@property --et-spinner-color{syntax: \"<color>\"; inherits: true; initial-value: currentColor;}@property --et-spinner-track-color{syntax: \"<color>\"; inherits: true; initial-value: transparent;}@property --et-spinner-duration{syntax: \"<time>\"; inherits: false; initial-value: 1333ms;}@keyframes et-spinner-rotate{to{transform:rotate(360deg)}}@keyframes et-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@keyframes et-spinner-left-spin{0%{transform:rotate(260deg)}50%{transform:rotate(130deg)}to{transform:rotate(260deg)}}@keyframes et-spinner-right-spin{0%{transform:rotate(-260deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-260deg)}}.et-spinner{position:relative;display:inline-block;width:var(--et-spinner-size);height:var(--et-spinner-size);color:var(--et-spinner-color);direction:ltr;vertical-align:middle;line-height:0;overflow:hidden}.et-spinner .et-spinner-track-container,.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-indeterminate-container,.et-spinner .et-spinner-layer,.et-spinner .et-spinner-circle-graphic{position:absolute;inset:0;width:100%;height:100%}.et-spinner .et-spinner-track-container{transform:rotate(-90deg)}.et-spinner .et-spinner-track-graphic,.et-spinner .et-spinner-circle-graphic{fill:none}.et-spinner .et-spinner-track-circle,.et-spinner .et-spinner-circle-graphic circle{fill:none;stroke:currentColor}.et-spinner .et-spinner-track-circle{stroke:var(--et-spinner-track-color, color-mix(in srgb, currentColor 14%, transparent));stroke-linecap:round}.et-spinner .et-spinner-circle-graphic circle{stroke-linecap:round;stroke-linejoin:round}.et-spinner .et-spinner-indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;animation:et-spinner-rotate 1568ms linear infinite}.et-spinner .et-spinner-layer{animation:et-spinner-layer-rotate 5332ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.et-spinner .et-spinner-gap-patch .et-spinner-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.et-spinner .et-spinner-circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.et-spinner .et-spinner-circle-clipper .et-spinner-circle-graphic{width:200%}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{left:-100%}.et-spinner .et-spinner-circle-left .et-spinner-circle-graphic{animation:et-spinner-left-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-circle-right .et-spinner-circle-graphic{animation:et-spinner-right-spin 1333ms cubic-bezier(.4,0,.2,1) infinite both}.et-spinner .et-spinner-determinate-graphic{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg);fill:none}.et-spinner .et-spinner-determinate-circle{fill:none;stroke:currentColor;stroke-linecap:round;transition:stroke-dashoffset .2s linear}.et-spinner:where(.et-spinner--themed){--et-spinner-color: var(--et-theme-color-primary-solid, currentColor)}}\n"] }]
3315
3362
  }], propDecorators: { diameter: [{ type: i0.Input, args: [{ isSignal: true, alias: "diameter", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], track: [{ type: i0.Input, args: [{ isSignal: true, alias: "track", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], determinate: [{ type: i0.Input, args: [{ isSignal: true, alias: "determinate", required: false }] }] } });
3316
3363
 
3317
3364
  /**
@@ -3485,7 +3532,7 @@ class MatchParticipantComponent {
3485
3532
  @if (showSeed() && participant()?.seed !== null && participant()?.seed !== undefined) {
3486
3533
  <span [attr.aria-label]="seedLabel()" class="et-match-participant-seed">{{ participant()?.seed }}</span>
3487
3534
  }
3488
- `, isInline: true, styles: ["@layer components{@property --et-match-participant-emblem-size{syntax: \"<length>\"; inherits: true; initial-value: 28px;}@property --et-match-participant-gap{syntax: \"<length>\"; inherits: true; initial-value: 10px;}@property --et-match-participant-font-size{syntax: \"<length>\"; inherits: true; initial-value: 14px;}.et-match-participant{display:flex;align-items:center;gap:var(--et-match-participant-gap);min-inline-size:0;color:var(--et-surface-color-solid, currentColor);text-decoration:none}.et-match-participant:where([data-interactive]){cursor:pointer;background:none;border:0;padding:0;font:inherit;text-align:start}.et-match-participant:where([data-interactive]):hover .et-match-participant-name{text-decoration:underline}.et-match-participant-emblem{display:var(--_et-match-participant-emblem-display, grid);place-items:center;flex:none;inline-size:var(--et-match-participant-emblem-size);block-size:var(--et-match-participant-emblem-size);overflow:hidden;border-radius:50%;background:color-mix(in srgb,var(--et-surface-interaction-solid, gray) 10%,transparent)}.et-match-participant-picture,.et-match-participant-bone{inline-size:100%;block-size:100%}.et-match-participant-names{display:flex;flex-direction:column;min-inline-size:0}.et-match-participant-name{min-inline-size:0;overflow:hidden;font-size:var(--et-match-participant-font-size);line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.et-match-participant-subtitle{min-inline-size:0;overflow:hidden;font-size:max(10px,calc(var(--et-match-participant-font-size) - 4px));line-height:1.3;color:var(--et-surface-color-muted-solid, currentColor);text-overflow:ellipsis;white-space:nowrap}.et-match-participant-name--tbd{color:var(--et-surface-color-muted-solid, currentColor)}.et-match-participant-name-bone{inline-size:96px}.et-match-participant-seed{flex:none;padding-inline:5px;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;color:var(--et-surface-color-muted-solid, currentColor);background:color-mix(in srgb,var(--et-surface-interaction-solid, gray) 12%,transparent);border-radius:4px}.et-match-participant:where([data-compact]){--et-match-participant-emblem-size: 20px;--et-match-participant-gap: 8px;--et-match-participant-font-size: 13px}}\n"], dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes"], outputs: ["imgLoad", "imgError"] }, { kind: "component", type: SkeletonItemComponent, selector: "et-skeleton-item", inputs: ["shape"] }], encapsulation: i0.ViewEncapsulation.None }); }
3535
+ `, isInline: true, styles: ["@layer components{@property --et-match-participant-emblem-size{syntax: \"<length>\"; inherits: true; initial-value: 28px;}@property --et-match-participant-gap{syntax: \"<length>\"; inherits: true; initial-value: 10px;}@property --et-match-participant-font-size{syntax: \"<length>\"; inherits: true; initial-value: 14px;}.et-match-participant{display:flex;align-items:center;gap:var(--et-match-participant-gap);min-inline-size:0;color:var(--et-surface-color-solid, currentColor);text-decoration:none}.et-match-participant:where([data-interactive]){cursor:pointer;background:none;border:0;padding:0;font:inherit;text-align:start}.et-match-participant:where([data-interactive]):hover .et-match-participant-name{text-decoration:underline}.et-match-participant-emblem{display:var(--_et-match-participant-emblem-display, grid);place-items:center;flex:none;inline-size:var(--et-match-participant-emblem-size);block-size:var(--et-match-participant-emblem-size);overflow:hidden;border-radius:50%;background:color-mix(in srgb,var(--et-surface-interaction-solid, gray) 10%,transparent)}.et-match-participant-picture,.et-match-participant-bone{inline-size:100%;block-size:100%}.et-match-participant-names{display:flex;flex-direction:column;min-inline-size:0}.et-match-participant-name{min-inline-size:0;overflow:hidden;font-size:var(--et-match-participant-font-size);line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.et-match-participant-subtitle{min-inline-size:0;overflow:hidden;font-size:max(10px,calc(var(--et-match-participant-font-size) - 4px));line-height:1.3;color:var(--et-surface-color-muted-solid, currentColor);text-overflow:ellipsis;white-space:nowrap}.et-match-participant-name--tbd{color:var(--et-surface-color-muted-solid, currentColor)}.et-match-participant-name-bone{inline-size:96px}.et-match-participant-seed{flex:none;padding-inline:5px;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;color:var(--et-surface-color-muted-solid, currentColor);background:color-mix(in srgb,var(--et-surface-interaction-solid, gray) 12%,transparent);border-radius:4px}.et-match-participant:where([data-compact]){--et-match-participant-emblem-size: 20px;--et-match-participant-gap: 8px;--et-match-participant-font-size: 13px}}\n"], dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes", "fit"], outputs: ["imgLoad", "imgError"] }, { kind: "component", type: SkeletonItemComponent, selector: "et-skeleton-item", inputs: ["shape"] }], encapsulation: i0.ViewEncapsulation.None }); }
3489
3536
  }
3490
3537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MatchParticipantComponent, decorators: [{
3491
3538
  type: Component,
@@ -25954,6 +26001,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
25954
26001
  }, styles: ["et-description{display:block;font-size:var(--et-description-font-size, 12px);color:var(--et-surface-color-muted-solid, currentColor)}\n"] }]
25955
26002
  }] });
25956
26003
 
26004
+ /** The muted `et-description` block for secondary copy next to a control or a label. */
26005
+ const DESCRIPTION_IMPORTS = [DescriptionComponent];
26006
+
25957
26007
  /** The built-in English labels. */
25958
26008
  const DEFAULT_DROPZONE_LABELS = {
25959
26009
  prompt: 'Drag & drop or click to browse',
@@ -27266,12 +27316,14 @@ class InputDirective extends TextFieldControlDirective {
27266
27316
  * split in its own `(input)` handler), it suppresses ours so the two can't clobber each other.
27267
27317
  */
27268
27318
  this.nativeSyncSuppressed = false;
27269
- this.hasValue = computed(() => this.mixed() || this.value().length > 0, /* @ts-ignore */
27319
+ // A bound field may hold `null` for "empty" (a nullable string field, e.g. the query form's
27320
+ // search field), so neither of these may assume a string is there.
27321
+ this.hasValue = computed(() => this.mixed() || !!this.value(), /* @ts-ignore */
27270
27322
  ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
27271
27323
  this.controlType = signal(FORM_FIELD_CONTROL_TYPES.TEXT_INPUT, /* @ts-ignore */
27272
27324
  ...(ngDevMode ? [{ debugName: "controlType" }] : /* istanbul ignore next */ []));
27273
27325
  /** The text the native input renders - empty while mixed so the raw value never reaches the DOM. */
27274
- this.displayValue = computed(() => (this.mixed() ? '' : this.value()), /* @ts-ignore */
27326
+ this.displayValue = computed(() => (this.mixed() ? '' : (this.value() ?? '')), /* @ts-ignore */
27275
27327
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
27276
27328
  /** The placeholder the native input renders - `mixedLabel` overrides the consumer placeholder while mixed. */
27277
27329
  this.effectivePlaceholder = computed(() => (this.mixed() ? this.resolvedMixedLabel() : this.placeholder()), /* @ts-ignore */
@@ -30469,7 +30521,7 @@ const RICH_TEXT_EDITOR_TOKEN_CODEC = new InjectionToken('RICH_TEXT_EDITOR_TOKEN_
30469
30521
  const COALESCE_WINDOW_MS = 500;
30470
30522
  /** How many states are kept. The oldest fall off the bottom - enough depth to be useful, bounded
30471
30523
  * so a long editing session can't grow the stack without limit. */
30472
- const MAX_ENTRIES = 100;
30524
+ const MAX_ENTRIES$1 = 100;
30473
30525
  /**
30474
30526
  * The characters `next` inserted over `prev` - a prefix/suffix diff, which is all that is needed to
30475
30527
  * tell "typed a word character" from "typed a space". A deletion yields `''`.
@@ -30553,7 +30605,7 @@ const createRichTextEditorHistory = () => {
30553
30605
  return;
30554
30606
  }
30555
30607
  entries.push({ value, selection });
30556
- if (entries.length > MAX_ENTRIES)
30608
+ if (entries.length > MAX_ENTRIES$1)
30557
30609
  entries.shift();
30558
30610
  index = entries.length - 1;
30559
30611
  syncFlags();
@@ -40630,6 +40682,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
40630
40682
  }, template: "<div class=\"et-radio-circle\"></div>\n<div class=\"et-radio-content\">\n <span [attr.id]=\"optionDirective.labelId()\" class=\"et-radio-label\">\n <ng-content />\n </span>\n <ng-content select=\"et-description\" />\n</div>\n", styles: ["@layer components{@property --et-radio-size{syntax: \"<length>\"; inherits: true; initial-value: 20px;}@property --et-radio-dot-size{syntax: \"<length>\"; inherits: true; initial-value: 10px;}@property --et-radio-border-width{syntax: \"<length>\"; inherits: true; initial-value: 2px;}@property --et-radio-transition-duration{syntax: \"<time>\"; inherits: true; initial-value: .15s;}@property --et-radio-opacity-disabled{syntax: \"<number>\"; inherits: true; initial-value: .5;}@property --et-radio-gap{syntax: \"<length>\"; inherits: true; initial-value: 10px;}@property --et-radio-card-padding{syntax: \"*\"; inherits: true; initial-value: 16px;}@property --et-radio-card-border-radius{syntax: \"<length>\"; inherits: true; initial-value: 10px;}@property --et-radio-card-border-width{syntax: \"<length>\"; inherits: true; initial-value: 1px;}et-radio{display:flex;align-items:flex-start;gap:var(--et-radio-gap);cursor:pointer;outline:none;-webkit-user-select:none;user-select:none}et-radio[data-readonly]{cursor:default}et-radio .et-radio-circle{flex-shrink:0;inline-size:var(--et-radio-size);block-size:var(--et-radio-size);border-radius:50%;border:var(--et-radio-border-width) solid var(--_et-control-border, var(--et-surface-border-solid, currentColor));background-color:transparent;display:grid;place-items:center}et-radio .et-radio-circle:after{content:\"\";inline-size:var(--et-radio-dot-size);block-size:var(--et-radio-dot-size);border-radius:50%;background-color:var(--et-theme-color-primary-solid, currentColor);transform:scale(0)}et-radio .et-radio-content{display:flex;flex-direction:column;gap:2px;min-inline-size:0}et-radio .et-radio-label{font-size:14px;line-height:20px;color:var(--et-surface-color-solid, currentColor)}@media(hover:hover){et-radio:hover:not([aria-disabled=true]):not([data-readonly]):not([aria-checked=true]) .et-radio-circle{border-color:var(--_et-control-border, var(--et-surface-interaction-hover-solid, currentColor));background-color:color-mix(in srgb,var(--et-surface-interaction-solid, currentColor) 8%,transparent)}}et-radio:focus-visible .et-radio-circle{border-color:var(--et-theme-color-primary-solid);outline:2px solid var(--et-theme-color-primary-solid);outline-offset:2px}et-radio:active:not([aria-disabled=true]):not([data-readonly]) .et-radio-circle{transform:scale(.92)}et-radio[aria-disabled=true]{opacity:var(--et-radio-opacity-disabled);pointer-events:none;cursor:default}et-radio[aria-disabled=true] .et-radio-label{color:var(--et-surface-interaction-disabled-solid, currentColor)}et-radio[aria-checked=true] .et-radio-circle{border-color:var(--et-theme-color-primary-solid, currentColor);background-color:transparent}et-radio[aria-checked=true] .et-radio-circle:after{transform:scale(1)}et-radio[data-can-animate] .et-radio-circle{transition:border-color var(--et-radio-transition-duration) ease,background-color var(--et-radio-transition-duration) ease,transform var(--et-radio-transition-duration) ease}et-radio[data-can-animate] .et-radio-circle:after{transition:transform var(--et-radio-transition-duration) ease}@media(prefers-reduced-motion:reduce){et-radio{--et-radio-transition-duration: 1ms}}et-radio:where([data-variant=card]){flex-direction:row-reverse;justify-content:space-between;align-items:center;padding:var(--et-radio-card-padding);border-radius:var(--et-radio-card-border-radius);background-color:var(--et-surface-background-solid, transparent);border:var(--et-radio-card-border-width) solid var(--et-surface-border-solid, currentColor)}et-radio:where([data-variant=card]) .et-radio-content{flex:1;min-inline-size:0}et-radio:where([data-variant=card]) .et-radio-label{font-weight:500;color:var(--et-surface-color-muted-solid, currentColor)}et-radio:where([data-variant=card]):where([aria-checked=true]) .et-radio-label{color:var(--et-surface-color-solid, currentColor)}@media(hover:hover){et-radio:where([data-variant=card]):where(:hover:not([aria-disabled=true]):not([data-readonly])){border-color:var(--et-surface-interaction-hover-solid, currentColor)}}et-radio:where([data-variant=card]):where(:active:not([aria-disabled=true]):not([data-readonly])){border-color:var(--et-surface-interaction-active-solid, currentColor)}et-radio:where([data-variant=card]):where([aria-checked=true]){border-color:var(--et-theme-color-primary-solid, currentColor)}et-radio:where([data-variant=card]):focus-visible{border-color:var(--et-theme-color-primary-solid, currentColor);outline:2px solid var(--et-theme-color-primary-solid);outline-offset:2px}et-radio:where([data-variant=card]):focus-visible .et-radio-circle{outline:none}et-radio:where([data-variant=card]):active:not([aria-disabled=true]):not([data-readonly]) .et-radio-circle{transform:none}et-radio:where([data-variant=card])[data-can-animate]{transition:border-color var(--et-radio-transition-duration) ease,background-color var(--et-radio-transition-duration) ease}et-radio:where([data-variant=card])[data-can-animate] .et-radio-label{transition:color var(--et-radio-transition-duration) ease}}\n"] }]
40631
40683
  }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
40632
40684
 
40685
+ /**
40686
+ * The headless selection engine only (`etSelectionList`, `etSelectionOption`, `etSelectionListControl`) -
40687
+ * selection state, roving-tabindex keyboard navigation and the form-field wiring, with no visual opinion.
40688
+ * For a ready-made group use {@link CHECKBOX_GROUP_IMPORTS}, {@link RADIO_GROUP_IMPORTS} or
40689
+ * {@link SEGMENTED_BUTTON_IMPORTS} instead - they apply these directives themselves.
40690
+ */
40691
+ const SELECTION_LIST_IMPORTS = [
40692
+ SelectionListDirective,
40693
+ SelectionOptionDirective,
40694
+ SelectionListControlDirective,
40695
+ ];
40696
+ /**
40697
+ * The multi-select group: `et-checkbox-group` with `et-checkbox-option` children, plus the
40698
+ * `et-checkbox-group-select-all` toggle for the mixed-state "select all" row.
40699
+ */
40700
+ const CHECKBOX_GROUP_IMPORTS = [
40701
+ CheckboxGroupComponent,
40702
+ CheckboxOptionComponent,
40703
+ CheckboxGroupSelectAllComponent,
40704
+ ];
40705
+ /** The single-select radio group: `et-radio-group` with `et-radio` children. */
40706
+ const RADIO_GROUP_IMPORTS = [RadioGroupComponent, RadioComponent];
40707
+ /**
40708
+ * The single-select connected button row: `et-segmented-button-group` with `et-segmented-button`
40709
+ * children (`variant="tabs"` for the tabs look).
40710
+ */
40711
+ const SEGMENTED_BUTTON_IMPORTS = [SegmentedButtonGroupComponent, SegmentedButtonComponent];
40712
+
40633
40713
  // codes 3100-3199
40634
40714
  const SLIDER_ERROR_CODES = {
40635
40715
  THUMB_OUTSIDE_SLIDER: 3100,
@@ -46931,7 +47011,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
46931
47011
  }], ctorParameters: () => [], propDecorators: { urlForPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "etPaginationSeo", required: false }] }], pageTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageTitle", required: false }] }] } });
46932
47012
 
46933
47013
  /** How long the copy button stays ticked after a successful write. */
46934
- const COPIED_RESET_MS = 1200;
47014
+ const COPIED_RESET_MS$1 = 1200;
47015
+ /**
47016
+ * Containers with more entries than this are not rendered directly - their children are grouped into
47017
+ * collapsed slices of this size, so a 5000-item array costs 50 rows instead of 5000.
47018
+ */
47019
+ const CHUNK_SIZE = 100;
46935
47020
  const kindOf = (value) => {
46936
47021
  if (value === null)
46937
47022
  return 'null';
@@ -46941,10 +47026,26 @@ const kindOf = (value) => {
46941
47026
  return 'array';
46942
47027
  return typeof value;
46943
47028
  };
47029
+ const entriesOf = (value) => {
47030
+ if (Array.isArray(value))
47031
+ return value.map((v, i) => ({ k: String(i), v }));
47032
+ if (value && typeof value === 'object') {
47033
+ return Object.entries(value).map(([k, v]) => ({ k, v }));
47034
+ }
47035
+ return [];
47036
+ };
47037
+ /** Keeps the number of sibling slices at or below `CHUNK_SIZE` by folding recursively for huge counts. */
47038
+ const chunkSizeFor = (count) => {
47039
+ let size = CHUNK_SIZE;
47040
+ while (count / size > CHUNK_SIZE)
47041
+ size *= CHUNK_SIZE;
47042
+ return size;
47043
+ };
46944
47044
  /**
46945
47045
  * A recursive, collapsible, searchable JSON tree used by the query devtools value explorer. Renders
46946
47046
  * the *transformed* value the app actually sees (post `transformResponse`). Self-recurses via its
46947
- * own selector.
47047
+ * own selector - both for child entries and for the folded slices of an oversized container, which
47048
+ * re-enter as the same component with a `chunk` window over the same value.
46948
47049
  */
46949
47050
  class QueryDevtoolsJsonComponent {
46950
47051
  constructor() {
@@ -46956,6 +47057,12 @@ class QueryDevtoolsJsonComponent {
46956
47057
  /** Lowercased search term; when set, the tree auto-expands and matches are highlighted. */
46957
47058
  this.search = input('', /* @ts-ignore */
46958
47059
  ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
47060
+ /**
47061
+ * When set, this node is not the value itself but a folded window over its entries. It shares the
47062
+ * container's `path` so a child's persisted expansion does not depend on where the slice borders land.
47063
+ */
47064
+ this.chunk = input(null, /* @ts-ignore */
47065
+ ...(ngDevMode ? [{ debugName: "chunk" }] : /* istanbul ignore next */ []));
46959
47066
  /** Full path of this node from the explorer root, used as the key for persisted expansion. */
46960
47067
  this.path = input('', /* @ts-ignore */
46961
47068
  ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
@@ -46972,66 +47079,80 @@ class QueryDevtoolsJsonComponent {
46972
47079
  ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
46973
47080
  this.isContainer = computed(() => this.kind() === 'array' || this.kind() === 'object', /* @ts-ignore */
46974
47081
  ...(ngDevMode ? [{ debugName: "isContainer" }] : /* istanbul ignore next */ []));
46975
- this.entries = computed(() => {
46976
- const value = this.value();
46977
- if (Array.isArray(value))
46978
- return value.map((v, i) => ({ k: String(i), v }));
46979
- if (value && typeof value === 'object') {
46980
- return Object.entries(value).map(([k, v]) => ({ k, v }));
46981
- }
46982
- return [];
47082
+ /** The entries this node covers: the container's own, or just the window a chunk stands for. */
47083
+ this.ownEntries = computed(() => {
47084
+ const entries = entriesOf(this.value());
47085
+ const chunk = this.chunk();
47086
+ return chunk ? entries.slice(chunk.start, chunk.end) : entries;
47087
+ }, /* @ts-ignore */
47088
+ ...(ngDevMode ? [{ debugName: "ownEntries" }] : /* istanbul ignore next */ []));
47089
+ this.childChunks = computed(() => {
47090
+ const entries = this.ownEntries();
47091
+ if (entries.length <= CHUNK_SIZE)
47092
+ return [];
47093
+ return buildChunks(entries, { offset: this.chunk()?.start ?? 0, isArray: this.kind() === 'array' });
47094
+ }, /* @ts-ignore */
47095
+ ...(ngDevMode ? [{ debugName: "childChunks" }] : /* istanbul ignore next */ []));
47096
+ /** Entries rendered as rows - empty while they are folded into slices instead. */
47097
+ this.visibleEntries = computed(() => (this.childChunks().length ? [] : this.ownEntries()), /* @ts-ignore */
47098
+ ...(ngDevMode ? [{ debugName: "visibleEntries" }] : /* istanbul ignore next */ []));
47099
+ /** Slices carry their window in the key so siblings persist independently of the container. */
47100
+ this.togglePath = computed(() => {
47101
+ const chunk = this.chunk();
47102
+ return chunk ? `${this.path()}#${chunk.start}` : this.path();
46983
47103
  }, /* @ts-ignore */
46984
- ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
46985
- this.localExpanded = linkedSignal(() => this.depth() < 1, /* @ts-ignore */
47104
+ ...(ngDevMode ? [{ debugName: "togglePath" }] : /* istanbul ignore next */ []));
47105
+ this.defaultExpanded = computed(() => !this.chunk() && this.depth() < 1, /* @ts-ignore */
47106
+ ...(ngDevMode ? [{ debugName: "defaultExpanded" }] : /* istanbul ignore next */ []));
47107
+ this.localExpanded = linkedSignal(() => this.defaultExpanded(), /* @ts-ignore */
46986
47108
  ...(ngDevMode ? [{ debugName: "localExpanded" }] : /* istanbul ignore next */ []));
46987
47109
  this.expanded = computed(() => {
46988
47110
  // Externally-persisted mode: resolve from the expanded/collapsed path sets, else the depth default.
46989
47111
  if (this.toggleFn()) {
46990
- const path = this.path();
47112
+ const path = this.togglePath();
46991
47113
  if (this.collapsedPaths()?.has(path))
46992
47114
  return false;
46993
47115
  if (this.expandedPaths()?.has(path))
46994
47116
  return true;
46995
- return this.depth() < 1;
47117
+ return this.defaultExpanded();
46996
47118
  }
46997
47119
  return this.localExpanded();
46998
47120
  }, /* @ts-ignore */
46999
47121
  ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
47000
- this.effectiveExpanded = computed(() => (this.search() ? true : this.expanded()), /* @ts-ignore */
47122
+ /** Only slices that actually contain a match unfold while searching, so a filter stays cheap. */
47123
+ this.chunkHasHit = computed(() => {
47124
+ const term = this.search();
47125
+ return !!term && this.ownEntries().some(({ k, v }) => k.toLowerCase().includes(term) || matchesDeep(v, term));
47126
+ }, /* @ts-ignore */
47127
+ ...(ngDevMode ? [{ debugName: "chunkHasHit" }] : /* istanbul ignore next */ []));
47128
+ this.effectiveExpanded = computed(() => {
47129
+ if (!this.search())
47130
+ return this.expanded();
47131
+ return this.chunk() ? this.chunkHasHit() : true;
47132
+ }, /* @ts-ignore */
47001
47133
  ...(ngDevMode ? [{ debugName: "effectiveExpanded" }] : /* istanbul ignore next */ []));
47002
47134
  this.copied = signal(false, /* @ts-ignore */
47003
47135
  ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
47004
47136
  this.copiedReset$ = new Subject();
47005
47137
  this.copyLabel = computed(() => {
47006
- const value = this.value();
47007
- if (Array.isArray(value))
47008
- return `Copy array (${value.length} ${value.length === 1 ? 'item' : 'items'})`;
47009
- if (this.kind() === 'object') {
47010
- const count = Object.keys(value).length;
47138
+ const count = this.ownEntries().length;
47139
+ if (this.chunk())
47140
+ return `Copy slice (${count} ${count === 1 ? 'entry' : 'entries'})`;
47141
+ if (this.kind() === 'array')
47142
+ return `Copy array (${count} ${count === 1 ? 'item' : 'items'})`;
47143
+ if (this.kind() === 'object')
47011
47144
  return `Copy object (${count} ${count === 1 ? 'key' : 'keys'})`;
47012
- }
47013
47145
  return 'Copy value';
47014
47146
  }, /* @ts-ignore */
47015
47147
  ...(ngDevMode ? [{ debugName: "copyLabel" }] : /* istanbul ignore next */ []));
47016
47148
  this.preview = computed(() => {
47017
- const value = this.value();
47018
- if (Array.isArray(value))
47019
- return value.length ? `[…] ${value.length}` : '[]';
47020
- const count = value ? Object.keys(value).length : 0;
47149
+ const count = this.ownEntries().length;
47150
+ if (this.kind() === 'array')
47151
+ return count ? `[…] ${count}` : '[]';
47021
47152
  return count ? `{…} ${count}` : '{}';
47022
47153
  }, /* @ts-ignore */
47023
47154
  ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
47024
- this.display = computed(() => {
47025
- const value = this.value();
47026
- const kind = this.kind();
47027
- if (kind === 'string')
47028
- return `"${value}"`;
47029
- if (kind === 'null')
47030
- return 'null';
47031
- if (kind === 'undefined')
47032
- return 'undefined';
47033
- return String(value);
47034
- }, /* @ts-ignore */
47155
+ this.display = computed(() => displayOf(this.value()), /* @ts-ignore */
47035
47156
  ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
47036
47157
  this.keyHit = computed(() => {
47037
47158
  const term = this.search();
@@ -47046,7 +47167,7 @@ class QueryDevtoolsJsonComponent {
47046
47167
  ...(ngDevMode ? [{ debugName: "valueHit" }] : /* istanbul ignore next */ []));
47047
47168
  // Each copy restarts the tick countdown; switchMap drops the pending reset of the previous one.
47048
47169
  this.copiedReset$
47049
- .pipe(switchMap(() => timer(COPIED_RESET_MS)), tap(() => this.copied.set(false)), takeUntilDestroyed())
47170
+ .pipe(switchMap(() => timer(COPIED_RESET_MS$1)), tap(() => this.copied.set(false)), takeUntilDestroyed())
47050
47171
  .subscribe();
47051
47172
  }
47052
47173
  childPath(key) {
@@ -47056,18 +47177,19 @@ class QueryDevtoolsJsonComponent {
47056
47177
  toggle() {
47057
47178
  const toggleFn = this.toggleFn();
47058
47179
  if (toggleFn) {
47059
- toggleFn(this.path(), !this.expanded());
47180
+ toggleFn(this.togglePath(), !this.expanded());
47060
47181
  return;
47061
47182
  }
47062
47183
  this.localExpanded.set(!this.localExpanded());
47063
47184
  }
47064
47185
  /**
47065
- * Containers copy their whole subtree as JSON; leaves copy something pasteable - a raw string
47066
- * without the display quotes, so an id or url can go straight into a search box.
47186
+ * Containers copy their whole subtree as JSON, slices only the entries they cover; leaves copy
47187
+ * something pasteable - a raw string without the display quotes, so an id or url can go straight
47188
+ * into a search box.
47067
47189
  */
47068
47190
  copyValue() {
47069
- const value = this.value();
47070
47191
  const kind = this.kind();
47192
+ const value = this.chunk() ? this.chunkValue() : this.value();
47071
47193
  let text;
47072
47194
  try {
47073
47195
  if (kind === 'string')
@@ -47086,12 +47208,18 @@ class QueryDevtoolsJsonComponent {
47086
47208
  .then(() => this.flagCopied())
47087
47209
  .catch(() => undefined);
47088
47210
  }
47211
+ chunkValue() {
47212
+ const entries = this.ownEntries();
47213
+ if (this.kind() === 'array')
47214
+ return entries.map((entry) => entry.v);
47215
+ return Object.fromEntries(entries.map((entry) => [entry.k, entry.v]));
47216
+ }
47089
47217
  flagCopied() {
47090
47218
  this.copied.set(true);
47091
47219
  this.copiedReset$.next();
47092
47220
  }
47093
47221
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
47094
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsJsonComponent, isStandalone: true, selector: "et-query-devtools-json", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, expandedPaths: { classPropertyName: "expandedPaths", publicName: "expandedPaths", isSignal: true, isRequired: false, transformFunction: null }, collapsedPaths: { classPropertyName: "collapsedPaths", publicName: "collapsedPaths", isSignal: true, isRequired: false, transformFunction: null }, toggleFn: { classPropertyName: "toggleFn", publicName: "toggleFn", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
47222
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsJsonComponent, isStandalone: true, selector: "et-query-devtools-json", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, chunk: { classPropertyName: "chunk", publicName: "chunk", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, expandedPaths: { classPropertyName: "expandedPaths", publicName: "expandedPaths", isSignal: true, isRequired: false, transformFunction: null }, collapsedPaths: { classPropertyName: "collapsedPaths", publicName: "collapsedPaths", isSignal: true, isRequired: false, transformFunction: null }, toggleFn: { classPropertyName: "toggleFn", publicName: "toggleFn", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
47095
47223
  @if (isContainer()) {
47096
47224
  <div class="et-query-devtools-json-node">
47097
47225
  <div class="et-query-devtools-json-row">
@@ -47102,7 +47230,9 @@ class QueryDevtoolsJsonComponent {
47102
47230
  type="button"
47103
47231
  >
47104
47232
  <span class="et-query-devtools-json-caret">{{ effectiveExpanded() ? '▾' : '▸' }}</span>
47105
- @if (nodeKey() !== null) {
47233
+ @if (chunk(); as chunk) {
47234
+ <span class="et-query-devtools-json-chunk">{{ chunk.label }}</span>
47235
+ } @else if (nodeKey() !== null) {
47106
47236
  <span [class.et-query-devtools-json-hit]="keyHit()" class="et-query-devtools-json-key">{{
47107
47237
  nodeKey()
47108
47238
  }}</span>
@@ -47124,7 +47254,19 @@ class QueryDevtoolsJsonComponent {
47124
47254
 
47125
47255
  @if (effectiveExpanded()) {
47126
47256
  <div class="et-query-devtools-json-children">
47127
- @for (entry of entries(); track entry.k) {
47257
+ @for (childChunk of childChunks(); track childChunk.start) {
47258
+ <et-query-devtools-json
47259
+ [value]="value()"
47260
+ [chunk]="childChunk"
47261
+ [depth]="depth()"
47262
+ [path]="path()"
47263
+ [search]="search()"
47264
+ [expandedPaths]="expandedPaths()"
47265
+ [collapsedPaths]="collapsedPaths()"
47266
+ [toggleFn]="toggleFn()"
47267
+ />
47268
+ }
47269
+ @for (entry of visibleEntries(); track entry.k) {
47128
47270
  <et-query-devtools-json
47129
47271
  [value]="entry.v"
47130
47272
  [nodeKey]="entry.k"
@@ -47160,7 +47302,7 @@ class QueryDevtoolsJsonComponent {
47160
47302
  </button>
47161
47303
  </div>
47162
47304
  }
47163
- `, isInline: true, dependencies: [{ kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "path", "expandedPaths", "collapsedPaths", "toggleFn"] }], encapsulation: i0.ViewEncapsulation.None }); }
47305
+ `, isInline: true, dependencies: [{ kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn"] }], encapsulation: i0.ViewEncapsulation.None }); }
47164
47306
  }
47165
47307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonComponent, decorators: [{
47166
47308
  type: Component,
@@ -47177,7 +47319,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
47177
47319
  type="button"
47178
47320
  >
47179
47321
  <span class="et-query-devtools-json-caret">{{ effectiveExpanded() ? '▾' : '▸' }}</span>
47180
- @if (nodeKey() !== null) {
47322
+ @if (chunk(); as chunk) {
47323
+ <span class="et-query-devtools-json-chunk">{{ chunk.label }}</span>
47324
+ } @else if (nodeKey() !== null) {
47181
47325
  <span [class.et-query-devtools-json-hit]="keyHit()" class="et-query-devtools-json-key">{{
47182
47326
  nodeKey()
47183
47327
  }}</span>
@@ -47199,7 +47343,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
47199
47343
 
47200
47344
  @if (effectiveExpanded()) {
47201
47345
  <div class="et-query-devtools-json-children">
47202
- @for (entry of entries(); track entry.k) {
47346
+ @for (childChunk of childChunks(); track childChunk.start) {
47347
+ <et-query-devtools-json
47348
+ [value]="value()"
47349
+ [chunk]="childChunk"
47350
+ [depth]="depth()"
47351
+ [path]="path()"
47352
+ [search]="search()"
47353
+ [expandedPaths]="expandedPaths()"
47354
+ [collapsedPaths]="collapsedPaths()"
47355
+ [toggleFn]="toggleFn()"
47356
+ />
47357
+ }
47358
+ @for (entry of visibleEntries(); track entry.k) {
47203
47359
  <et-query-devtools-json
47204
47360
  [value]="entry.v"
47205
47361
  [nodeKey]="entry.k"
@@ -47239,7 +47395,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
47239
47395
  encapsulation: ViewEncapsulation.None,
47240
47396
  imports: [QueryDevtoolsJsonComponent],
47241
47397
  }]
47242
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], depth: [{ type: i0.Input, args: [{ isSignal: true, alias: "depth", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], expandedPaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedPaths", required: false }] }], collapsedPaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedPaths", required: false }] }], toggleFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleFn", required: false }] }] } });
47398
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], depth: [{ type: i0.Input, args: [{ isSignal: true, alias: "depth", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], chunk: [{ type: i0.Input, args: [{ isSignal: true, alias: "chunk", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], expandedPaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedPaths", required: false }] }], collapsedPaths: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedPaths", required: false }] }], toggleFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleFn", required: false }] }] } });
47399
+ // Below the component: an interpolated template literal above an inline template breaks the Angular
47400
+ // language service for the whole template (see `ethlete/no-template-literal-before-inline-template`).
47401
+ const displayOf = (value) => {
47402
+ switch (kindOf(value)) {
47403
+ case 'string':
47404
+ return `"${value}"`;
47405
+ case 'null':
47406
+ return 'null';
47407
+ case 'undefined':
47408
+ return 'undefined';
47409
+ default:
47410
+ return String(value);
47411
+ }
47412
+ };
47413
+ const buildChunks = (entries, window) => {
47414
+ const size = chunkSizeFor(entries.length);
47415
+ const chunks = [];
47416
+ for (let i = 0; i < entries.length; i += size) {
47417
+ const slice = entries.slice(i, i + size);
47418
+ const start = window.offset + i;
47419
+ const end = start + slice.length;
47420
+ const first = window.isArray ? String(start) : (slice[0]?.k ?? '');
47421
+ const last = window.isArray ? String(end - 1) : (slice[slice.length - 1]?.k ?? '');
47422
+ chunks.push({ start, end, label: slice.length === 1 ? first : `${first} … ${last}` });
47423
+ }
47424
+ return chunks;
47425
+ };
47426
+ /** Whether a search term appears anywhere in a subtree - used to keep non-matching slices folded. */
47427
+ const matchesDeep = (value, term) => {
47428
+ if (!value || typeof value !== 'object')
47429
+ return displayOf(value).toLowerCase().includes(term);
47430
+ return entriesOf(value).some(({ k, v }) => k.toLowerCase().includes(term) || matchesDeep(v, term));
47431
+ };
47243
47432
 
47244
47433
  /**
47245
47434
  * The global devtools toggle shortcut, rendered for the current platform (`⌘⌥Q` on Apple, where the
@@ -47293,10 +47482,290 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
47293
47482
  `, encapsulation: ViewEncapsulation.ShadowDom, styles: [":host{position:fixed;inset-block-end:16px;inset-inline-end:16px;z-index:2147483001}button{--_accent: var(--et-theme-color-primary-solid, #60a5fa);position:relative;display:inline-flex;align-items:center;gap:8px;padding:10px 12px 10px 14px;border:none;border-radius:999px;background:linear-gradient(135deg,#1f1f23,#2a2a30);color:#fafafa;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;font-weight:600;letter-spacing:.02em;cursor:pointer;box-shadow:0 6px 20px #00000059,inset 0 0 0 1px #ffffff14;transition:transform .15s ease,box-shadow .2s ease}button:before{content:\"\";position:absolute;inset:0;border-radius:inherit;padding:1px;background:linear-gradient(135deg,var(--_accent),transparent 60%);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.7;transition:opacity .2s ease}button:hover{transform:translateY(-2px);box-shadow:0 10px 28px #0006,0 0 0 4px color-mix(in srgb,var(--_accent) 22%,transparent),inset 0 0 0 1px #ffffff1a}button:hover:before{opacity:1}svg{inline-size:16px;block-size:16px;fill:none;stroke:var(--_accent);stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}.label{line-height:1}kbd{padding:3px 6px;border-radius:5px;background:#ffffff12;box-shadow:inset 0 0 0 1px #ffffff1f;color:#d4d4d8;font:inherit;font-size:11px;font-weight:500;line-height:1;white-space:nowrap}\n"] }]
47294
47483
  }], propDecorators: { openChange: [{ type: i0.Output, args: ["openChange"] }] } });
47295
47484
 
47485
+ /** Enough to describe what changed between two responses; past this the list stops being readable. */
47486
+ const MAX_ENTRIES = 200;
47487
+ /**
47488
+ * How deep the walk descends before comparing whole subtrees at once. Guards against a self-referential
47489
+ * response, and keeps a deeply nested change reported as one entry rather than a path per level.
47490
+ */
47491
+ const MAX_DEPTH = 12;
47492
+ /** Stands in for a path one side does not have, so a stored `undefined` is not mistaken for a missing key. */
47493
+ const ABSENT = /* @__PURE__ */ Symbol('absent');
47494
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
47495
+ const valueAt = (record, key) => (key in record ? record[key] : ABSENT);
47496
+ /**
47497
+ * The `id` of every element, when an array holds records keyed by a unique primitive `id` - the shape an
47498
+ * API list almost always has. `null` for anything else, which is then compared by index.
47499
+ */
47500
+ const recordIds = (items) => {
47501
+ const ids = [];
47502
+ for (const item of items) {
47503
+ if (!isRecord(item))
47504
+ return null;
47505
+ const id = item['id'];
47506
+ if (typeof id !== 'string' && typeof id !== 'number')
47507
+ return null;
47508
+ ids.push(id);
47509
+ }
47510
+ return new Set(ids).size === ids.length ? ids : null;
47511
+ };
47512
+ /**
47513
+ * Whether two values are the same, for the leaves and for the subtrees the walk stopped descending into.
47514
+ * The serialized comparison is key-order sensitive, which only ever over-reports a difference.
47515
+ */
47516
+ const isSameValue = (before, after) => {
47517
+ if (Object.is(before, after))
47518
+ return true;
47519
+ if (!isRecord(before) && !Array.isArray(before))
47520
+ return false;
47521
+ if (!isRecord(after) && !Array.isArray(after))
47522
+ return false;
47523
+ try {
47524
+ return JSON.stringify(before) === JSON.stringify(after);
47525
+ }
47526
+ catch {
47527
+ return false;
47528
+ }
47529
+ };
47530
+ const push = (acc, entry) => {
47531
+ if (acc.entries.length >= MAX_ENTRIES) {
47532
+ acc.truncated = true;
47533
+ return;
47534
+ }
47535
+ acc.entries.push(entry);
47536
+ };
47537
+ const walk = ({ before, after, path, depth }, acc) => {
47538
+ if (acc.truncated)
47539
+ return;
47540
+ if (before === ABSENT) {
47541
+ push(acc, { path, kind: 'added', before: null, after });
47542
+ return;
47543
+ }
47544
+ if (after === ABSENT) {
47545
+ push(acc, { path, kind: 'removed', before, after: null });
47546
+ return;
47547
+ }
47548
+ if (depth < MAX_DEPTH) {
47549
+ if (isRecord(before) && isRecord(after)) {
47550
+ for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
47551
+ walk({ before: valueAt(before, key), after: valueAt(after, key), path: `${path}.${key}`, depth: depth + 1 }, acc);
47552
+ }
47553
+ return;
47554
+ }
47555
+ if (Array.isArray(before) && Array.isArray(after)) {
47556
+ walkArrays({ before, after, path, depth }, acc);
47557
+ return;
47558
+ }
47559
+ }
47560
+ if (!isSameValue(before, after))
47561
+ push(acc, { path, kind: 'changed', before, after });
47562
+ };
47563
+ const walkArrays = ({ before, after, path, depth }, acc) => {
47564
+ const beforeIds = recordIds(before);
47565
+ const afterIds = recordIds(after);
47566
+ const nested = depth + 1;
47567
+ // Matching records by their `id` keeps a list that gained or lost an item reported as that one item,
47568
+ // instead of every index after it shifting and reading as changed.
47569
+ if (beforeIds && afterIds) {
47570
+ const afterById = new Map(afterIds.map((id, index) => [id, after[index]]));
47571
+ const beforeIdSet = new Set(beforeIds);
47572
+ beforeIds.forEach((id, index) => {
47573
+ const paired = afterById.has(id) ? afterById.get(id) : ABSENT;
47574
+ walk({ before: before[index], after: paired, path: `${path}[id=${id}]`, depth: nested }, acc);
47575
+ });
47576
+ afterIds.forEach((id, index) => {
47577
+ if (beforeIdSet.has(id))
47578
+ return;
47579
+ walk({ before: ABSENT, after: after[index], path: `${path}[id=${id}]`, depth: nested }, acc);
47580
+ });
47581
+ return;
47582
+ }
47583
+ for (let index = 0; index < Math.max(before.length, after.length); index++) {
47584
+ walk({
47585
+ before: index < before.length ? before[index] : ABSENT,
47586
+ after: index < after.length ? after[index] : ABSENT,
47587
+ path: `${path}[${index}]`,
47588
+ depth: nested,
47589
+ }, acc);
47590
+ }
47591
+ };
47592
+ /**
47593
+ * What changed between two responses of the same query, as a flat list of paths - the shape that answers
47594
+ * "the list re-rendered, what changed?" and "did that poll return anything new?".
47595
+ */
47596
+ const diffQueryDevtoolsResponses = (before, after) => {
47597
+ const acc = { entries: [], truncated: false };
47598
+ walk({ before, after, path: '$', depth: 0 }, acc);
47599
+ return acc;
47600
+ };
47601
+
47602
+ /* eslint-disable @typescript-eslint/naming-convention -- the `__export_*` keys are Insomnia's file format */
47603
+ const WORKSPACE_ID = 'wrk_ethlete_query_devtools';
47604
+ const jsonBody = (body) => {
47605
+ try {
47606
+ return JSON.stringify(body, null, 2);
47607
+ }
47608
+ catch {
47609
+ // A body holding a circular reference or a throwing `toJSON` cannot be exported.
47610
+ return null;
47611
+ }
47612
+ };
47613
+ /**
47614
+ * Insomnia's GraphQL body editor stores `{ query, variables }` as its text, so a GraphQL request is
47615
+ * exported with the document and its variables rather than the serialized POST body.
47616
+ */
47617
+ const graphqlBody = (gqlQuery, body) => {
47618
+ const variables = body?.variables ?? {};
47619
+ return jsonBody({ query: gqlQuery, variables });
47620
+ };
47621
+ const bodyOf = (request) => {
47622
+ if (request.gqlQuery) {
47623
+ const text = graphqlBody(request.gqlQuery, request.body);
47624
+ return text === null ? {} : { mimeType: 'graphql', text };
47625
+ }
47626
+ if (request.body === null || request.body === undefined)
47627
+ return {};
47628
+ const text = jsonBody(request.body);
47629
+ return text === null ? {} : { mimeType: 'application/json', text };
47630
+ };
47631
+ /**
47632
+ * Angular's `HttpClient` labels an object body `application/json` on its own, so a replay outside the
47633
+ * app needs the header spelled out to reach the same endpoint the same way.
47634
+ */
47635
+ const headersOf = (input, hasBody) => {
47636
+ const headers = input.headers.filter((header) => header.value !== '');
47637
+ const hasContentType = headers.some((header) => header.name.toLowerCase() === 'content-type');
47638
+ if (!hasBody || hasContentType)
47639
+ return headers;
47640
+ return [...headers, { name: 'Content-Type', value: 'application/json' }];
47641
+ };
47642
+ const AUTH_HEADER = 'Authorization';
47643
+ /**
47644
+ * Insomnia's template tag for reading a value out of another request's response. `when-expired` plus a
47645
+ * max age is what makes the chain maintain itself: once the stored refresh response is older than
47646
+ * `maxAgeSeconds`, sending a request that reads from it re-sends the refresh first.
47647
+ */
47648
+ const responseTag = (requestId, refresh) => `{% response 'body', '${requestId}', '${refresh.accessTokenPath}', 'when-expired', ${refresh.maxAgeSeconds} %}`;
47649
+ /** Replaces whatever `Authorization` the export resolved with the token the refresh chain yields. */
47650
+ const withChainedAuth = (headers, value) => [
47651
+ { name: AUTH_HEADER, value },
47652
+ ...headers.filter((header) => header.name.toLowerCase() !== AUTH_HEADER.toLowerCase()),
47653
+ ];
47654
+ /**
47655
+ * Builds an Insomnia v4 collection from a set of requests, ready to be imported via Insomnia's
47656
+ * `Import > From File / Clipboard`. Requests are filed into a folder per query client, and a request
47657
+ * naming a token refresh gets its bearer token from that refresh's response rather than from a
47658
+ * literal token frozen at export time.
47659
+ */
47660
+ const buildInsomniaExport = (options) => {
47661
+ const groups = new Map();
47662
+ const resources = [
47663
+ {
47664
+ _id: WORKSPACE_ID,
47665
+ _type: 'workspace',
47666
+ parentId: null,
47667
+ name: options.name,
47668
+ description: 'Exported from the @ethlete/query devtools.',
47669
+ scope: 'collection',
47670
+ created: options.now,
47671
+ modified: options.now,
47672
+ },
47673
+ ];
47674
+ const groupIdFor = (name) => {
47675
+ if (!name)
47676
+ return WORKSPACE_ID;
47677
+ const existing = groups.get(name);
47678
+ if (existing)
47679
+ return existing;
47680
+ const id = `fld_${groups.size}`;
47681
+ groups.set(name, id);
47682
+ resources.push({
47683
+ _id: id,
47684
+ _type: 'request_group',
47685
+ parentId: WORKSPACE_ID,
47686
+ name,
47687
+ environment: {},
47688
+ // Insomnia sorts ascending, so the counter keeps folders in the order they were added.
47689
+ metaSortKey: groups.size,
47690
+ created: options.now,
47691
+ modified: options.now,
47692
+ });
47693
+ return id;
47694
+ };
47695
+ const requestResource = (resource) => {
47696
+ resources.push({
47697
+ _type: 'request',
47698
+ description: '',
47699
+ parameters: [],
47700
+ authentication: {},
47701
+ isPrivate: false,
47702
+ settingStoreCookies: true,
47703
+ settingSendCookies: true,
47704
+ settingEncodeUrl: true,
47705
+ settingRebuildPath: true,
47706
+ settingFollowRedirects: 'global',
47707
+ created: options.now,
47708
+ modified: options.now,
47709
+ ...resource,
47710
+ });
47711
+ };
47712
+ /** The `Authorization` value a request chained to a given token refresh gets, keyed by refresh id. */
47713
+ const chainedAuth = new Map();
47714
+ // Emitted first so every chained `Authorization` below already has a request id to point at.
47715
+ options.tokenRefreshes?.forEach((refresh, index) => {
47716
+ const id = `req_refresh_${index}`;
47717
+ const body = bodyOf(refresh);
47718
+ chainedAuth.set(refresh.id, `Bearer ${responseTag(id, refresh)}`);
47719
+ requestResource({
47720
+ _id: id,
47721
+ parentId: groupIdFor(refresh.group),
47722
+ name: refresh.name,
47723
+ description: 'Mints the access token every secure request in this collection reads. Insomnia re-sends it on ' +
47724
+ `its own once the stored response is older than ${refresh.maxAgeSeconds}s. The refresh token in ` +
47725
+ 'the body is the one the app held at export time - re-export once it is spent.',
47726
+ method: refresh.method,
47727
+ url: refresh.url,
47728
+ body,
47729
+ headers: headersOf(refresh, 'text' in body),
47730
+ // Insomnia sorts ascending and the exported requests start at 0, so the refresh stays on top.
47731
+ metaSortKey: index - (options.tokenRefreshes?.length ?? 0),
47732
+ });
47733
+ });
47734
+ options.requests.forEach((request, index) => {
47735
+ const body = bodyOf(request);
47736
+ const headers = headersOf(request, 'text' in body);
47737
+ const auth = request.secureBy ? chainedAuth.get(request.secureBy) : undefined;
47738
+ requestResource({
47739
+ _id: `req_${index}`,
47740
+ parentId: groupIdFor(request.group),
47741
+ name: request.name,
47742
+ method: request.method,
47743
+ url: request.url,
47744
+ body,
47745
+ headers: auth ? withChainedAuth(headers, auth) : headers,
47746
+ metaSortKey: index,
47747
+ });
47748
+ });
47749
+ return {
47750
+ _type: 'export',
47751
+ __export_format: 4,
47752
+ __export_date: new Date(options.now).toISOString(),
47753
+ __export_source: 'ethlete.query:devtools',
47754
+ resources,
47755
+ };
47756
+ };
47757
+
47758
+ /** How long a copy button stays ticked after a successful write. */
47759
+ const COPIED_RESET_MS = 1200;
47760
+ const noop = () => undefined;
47296
47761
  const STORAGE_KEY = 'ethlete:query:devtools:v4';
47297
47762
  const MAX_EVENTS = 100;
47298
47763
  const DEFAULT_HEIGHT = 360;
47299
47764
  const MIN_HEIGHT = 200;
47765
+ /** How many bars the timeline draws. Past this the newest are kept and the rest are counted instead. */
47766
+ const MAX_TIMELINE_ROWS = 200;
47767
+ /** Where the timeline's axis labels sit, as fractions of the window. */
47768
+ const TIMELINE_TICKS = [0, 0.25, 0.5, 0.75, 1];
47300
47769
  const readPersistedState = () => {
47301
47770
  try {
47302
47771
  const raw = sessionStorage.getItem(STORAGE_KEY);
@@ -47330,6 +47799,27 @@ const slimForReport = (value, depth = 0) => {
47330
47799
  }
47331
47800
  return value;
47332
47801
  };
47802
+ /** How long an exported Insomnia collection reuses a refresh response whose token lifetime is unknown. */
47803
+ const DEFAULT_TOKEN_MAX_AGE_S = 300;
47804
+ /** Upper bound for the same, so a token that claims a year of life still gets refreshed hourly. */
47805
+ const MAX_TOKEN_MAX_AGE_S = 3600;
47806
+ /**
47807
+ * The JSONPath of a string value inside a response, or `null`. Used to locate the access token in an
47808
+ * auth response whose shape only the provider's `extractTokens` knows.
47809
+ */
47810
+ const findValuePath = (value, node) => {
47811
+ if (node.value === value)
47812
+ return node.path;
47813
+ if (node.depth > 5 || !node.value || typeof node.value !== 'object')
47814
+ return null;
47815
+ for (const [key, entry] of Object.entries(node.value)) {
47816
+ const path = Array.isArray(node.value) ? `${node.path}[${key}]` : `${node.path}.${key}`;
47817
+ const found = findValuePath(value, { value: entry, path, depth: node.depth + 1 });
47818
+ if (found)
47819
+ return found;
47820
+ }
47821
+ return null;
47822
+ };
47333
47823
  /** Best-effort decode of a JWT payload for the auth tab. Returns `null` for anything non-decodable. */
47334
47824
  const decodeJwtPayload = (token) => {
47335
47825
  if (!token)
@@ -47361,6 +47851,7 @@ const decodeJwtPayload = (token) => {
47361
47851
  class QueryDevtoolsComponent {
47362
47852
  constructor() {
47363
47853
  this.hostEl = inject(ElementRef);
47854
+ this.renderer = injectRenderer();
47364
47855
  this.document = inject(DOCUMENT$1);
47365
47856
  this.eventIdCounter = 0;
47366
47857
  this.lastSelectionKey = '';
@@ -47369,10 +47860,25 @@ class QueryDevtoolsComponent {
47369
47860
  { id: 'queries', label: 'Queries' },
47370
47861
  { id: 'stacks', label: 'Stacks' },
47371
47862
  { id: 'sequences', label: 'Sequences' },
47863
+ { id: 'forms', label: 'Forms' },
47372
47864
  { id: 'auth', label: 'Auth' },
47373
47865
  { id: 'ws', label: 'Sockets' },
47374
47866
  { id: 'cache', label: 'Cache' },
47867
+ { id: 'timeline', label: 'Timeline' },
47375
47868
  { id: 'events', label: 'Events' },
47869
+ { id: 'faults', label: 'Faults' },
47870
+ ];
47871
+ this.detailTabs = [
47872
+ { id: 'overview', label: 'Overview' },
47873
+ { id: 'history', label: 'History' },
47874
+ { id: 'data', label: 'Data' },
47875
+ ];
47876
+ /** The status chips above the Queries list, in the order a problem is usually looked for. */
47877
+ this.facets = [
47878
+ { id: 'error', label: 'Failing' },
47879
+ { id: 'loading', label: 'Loading' },
47880
+ { id: 'stale', label: 'Stale' },
47881
+ { id: 'idle', label: 'Idle' },
47376
47882
  ];
47377
47883
  this.shortcut = queryDevtoolsShortcutLabel();
47378
47884
  this.open = signal(this.persisted.open ?? false, /* @ts-ignore */
@@ -47383,15 +47889,32 @@ class QueryDevtoolsComponent {
47383
47889
  ...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
47384
47890
  this.activeTab = signal(this.persisted.activeTab ?? 'queries', /* @ts-ignore */
47385
47891
  ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
47892
+ /** Which section of the query detail is showing. Shared by the Queries tab and both drawers. */
47893
+ this.detailTab = signal(this.persisted.detailTab ?? 'overview', /* @ts-ignore */
47894
+ ...(ngDevMode ? [{ debugName: "detailTab" }] : /* istanbul ignore next */ []));
47386
47895
  this.selectedClientName = signal(this.persisted.selectedClientName ?? null, /* @ts-ignore */
47387
47896
  ...(ngDevMode ? [{ debugName: "selectedClientName" }] : /* istanbul ignore next */ []));
47388
47897
  this.selectedQueryId = signal(this.persisted.selectedQueryId ?? null, /* @ts-ignore */
47389
47898
  ...(ngDevMode ? [{ debugName: "selectedQueryId" }] : /* istanbul ignore next */ []));
47390
- // Independent per-drawer selection so the Stacks / Sequences drawers don't share the Queries tab's.
47899
+ /** The form whose detail the Forms tab has expanded. */
47900
+ this.selectedFormId = signal(this.persisted.selectedFormId ?? null, /* @ts-ignore */
47901
+ ...(ngDevMode ? [{ debugName: "selectedFormId" }] : /* istanbul ignore next */ []));
47902
+ // Independent per-drawer selection so no drawer shares the Queries tab's selection - or another
47903
+ // drawer's: opening a query from the Timeline must not also change what the Forms drawer shows.
47391
47904
  this.stackSelectedQueryId = signal(null, /* @ts-ignore */
47392
47905
  ...(ngDevMode ? [{ debugName: "stackSelectedQueryId" }] : /* istanbul ignore next */ []));
47393
47906
  this.sequenceSelectedQueryId = signal(null, /* @ts-ignore */
47394
47907
  ...(ngDevMode ? [{ debugName: "sequenceSelectedQueryId" }] : /* istanbul ignore next */ []));
47908
+ this.formSelectedQueryId = signal(null, /* @ts-ignore */
47909
+ ...(ngDevMode ? [{ debugName: "formSelectedQueryId" }] : /* istanbul ignore next */ []));
47910
+ this.timelineSelectedQueryId = signal(null, /* @ts-ignore */
47911
+ ...(ngDevMode ? [{ debugName: "timelineSelectedQueryId" }] : /* istanbul ignore next */ []));
47912
+ /** Free-text narrowing of the Queries list. Every whitespace-separated term has to match. */
47913
+ this.queryFilter = signal(this.persisted.queryFilter ?? '', /* @ts-ignore */
47914
+ ...(ngDevMode ? [{ debugName: "queryFilter" }] : /* istanbul ignore next */ []));
47915
+ /** The status facets the Queries list is narrowed to. Empty means no status narrowing. */
47916
+ this.queryFacets = signal(new Set(this.persisted.queryFacets ?? []), /* @ts-ignore */
47917
+ ...(ngDevMode ? [{ debugName: "queryFacets" }] : /* istanbul ignore next */ []));
47395
47918
  this.eventLog = signal([], /* @ts-ignore */
47396
47919
  ...(ngDevMode ? [{ debugName: "eventLog" }] : /* istanbul ignore next */ []));
47397
47920
  /** Keys (`<entryId>:<stepIndex>`) of the sequence steps whose in/out detail is expanded. */
@@ -47407,6 +47930,9 @@ class QueryDevtoolsComponent {
47407
47930
  ...(ngDevMode ? [{ debugName: "jsonExpandedPaths" }] : /* istanbul ignore next */ []));
47408
47931
  this.jsonCollapsedPaths = signal(new Set(this.persisted.jsonCollapsed ?? []), /* @ts-ignore */
47409
47932
  ...(ngDevMode ? [{ debugName: "jsonCollapsedPaths" }] : /* istanbul ignore next */ []));
47933
+ /** The run whose response the diff is comparing, by run index, or `null` while no diff is open. */
47934
+ this.diffRunIndex = signal(null, /* @ts-ignore */
47935
+ ...(ngDevMode ? [{ debugName: "diffRunIndex" }] : /* istanbul ignore next */ []));
47410
47936
  /** JIT editor state (response / args editing on the selected query). */
47411
47937
  this.editorMode = signal('none', /* @ts-ignore */
47412
47938
  ...(ngDevMode ? [{ debugName: "editorMode" }] : /* istanbul ignore next */ []));
@@ -47414,11 +47940,22 @@ class QueryDevtoolsComponent {
47414
47940
  ...(ngDevMode ? [{ debugName: "responseDraft" }] : /* istanbul ignore next */ []));
47415
47941
  this.argsDraft = signal('', /* @ts-ignore */
47416
47942
  ...(ngDevMode ? [{ debugName: "argsDraft" }] : /* istanbul ignore next */ []));
47943
+ /**
47944
+ * The text the currently open editor was seeded with. The textareas bind `value` to this and not to
47945
+ * the live draft: a `value` binding fed by the draft is written back on every keystroke, and writing
47946
+ * a textarea's `value` puts the caret at the end.
47947
+ */
47948
+ this.editorSeed = '';
47417
47949
  this.editError = signal(null, /* @ts-ignore */
47418
47950
  ...(ngDevMode ? [{ debugName: "editError" }] : /* istanbul ignore next */ []));
47419
- /** Transient "Copied!" feedback for the copy-report action. */
47951
+ /** Transient "Copied!" feedback for the copy-report, copy-as-Insomnia and copy-document actions. */
47420
47952
  this.copiedReport = signal(false, /* @ts-ignore */
47421
47953
  ...(ngDevMode ? [{ debugName: "copiedReport" }] : /* istanbul ignore next */ []));
47954
+ this.copiedInsomnia = signal(false, /* @ts-ignore */
47955
+ ...(ngDevMode ? [{ debugName: "copiedInsomnia" }] : /* istanbul ignore next */ []));
47956
+ this.copiedGql = signal(false, /* @ts-ignore */
47957
+ ...(ngDevMode ? [{ debugName: "copiedGql" }] : /* istanbul ignore next */ []));
47958
+ this.copiedReset$ = new Subject();
47422
47959
  /** 1-second tick driving the cache freshness countdowns. */
47423
47960
  this.clock = toSignal(interval(1000), { initialValue: 0 });
47424
47961
  /** "Inspect" mode: hover the live UI to find the query that a component created. */
@@ -47435,6 +47972,8 @@ class QueryDevtoolsComponent {
47435
47972
  ...(ngDevMode ? [{ debugName: "stackEntries" }] : /* istanbul ignore next */ []));
47436
47973
  this.sequenceEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'query-sequence'), /* @ts-ignore */
47437
47974
  ...(ngDevMode ? [{ debugName: "sequenceEntries" }] : /* istanbul ignore next */ []));
47975
+ this.formEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'query-form'), /* @ts-ignore */
47976
+ ...(ngDevMode ? [{ debugName: "formEntries" }] : /* istanbul ignore next */ []));
47438
47977
  this.authEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'auth-provider'), /* @ts-ignore */
47439
47978
  ...(ngDevMode ? [{ debugName: "authEntries" }] : /* istanbul ignore next */ []));
47440
47979
  this.wsEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'ws-client'), /* @ts-ignore */
@@ -47465,26 +48004,226 @@ class QueryDevtoolsComponent {
47465
48004
  return Array.from(map, ([repository, info]) => ({ repository, ...info }));
47466
48005
  }, /* @ts-ignore */
47467
48006
  ...(ngDevMode ? [{ debugName: "repositories" }] : /* istanbul ignore next */ []));
47468
- this.filteredQueries = computed(() => {
48007
+ /**
48008
+ * Every client the Faults tab can arm, with the fault it currently carries. Clients with nothing armed
48009
+ * read as {@link EMPTY_QUERY_DEVTOOLS_FAULT} so the inputs always have a value to show.
48010
+ */
48011
+ this.faultClients = computed(() => {
48012
+ const faults = queryDevtoolsFaults();
48013
+ return this.repositories()
48014
+ .map(({ name, baseUrl }) => {
48015
+ const fault = faults[name] ?? EMPTY_QUERY_DEVTOOLS_FAULT;
48016
+ return { name, baseUrl, fault, armed: isQueryDevtoolsFaultArmed(fault) };
48017
+ })
48018
+ .sort((a, b) => a.name.localeCompare(b.name));
48019
+ }, /* @ts-ignore */
48020
+ ...(ngDevMode ? [{ debugName: "faultClients" }] : /* istanbul ignore next */ []));
48021
+ /**
48022
+ * The names of the clients currently carrying a fault, or `null` when none do - so a template can `@if`
48023
+ * on it. Armed faults are the one state where the panel is lying to the app on purpose, and a badge on a
48024
+ * tab that isn't open cannot say that: whichever tab you are reading, a red response has to be
48025
+ * attributable to the injection rather than to the API.
48026
+ */
48027
+ this.armedFaultClients = computed(() => {
48028
+ const armed = this.faultClients()
48029
+ .filter((client) => client.armed)
48030
+ .map((client) => client.name);
48031
+ return armed.length ? armed : null;
48032
+ }, /* @ts-ignore */
48033
+ ...(ngDevMode ? [{ debugName: "armedFaultClients" }] : /* istanbul ignore next */ []));
48034
+ /**
48035
+ * The queries the list is scoped to before the search box and the status chips narrow them further:
48036
+ * either the picked client's, or exactly the inspected element's.
48037
+ */
48038
+ this.scopedQueries = computed(() => {
47469
48039
  const entries = this.queryEntries();
47470
48040
  const inspectIds = this.inspectFilterIds();
47471
- let filtered;
47472
- if (inspectIds) {
47473
- filtered = entries.filter((e) => inspectIds.includes(e.id));
47474
- }
47475
- else {
47476
- const client = this.selectedClientName();
47477
- filtered = client ? entries.filter((e) => e.meta.clientName === client) : entries;
47478
- }
47479
- return filtered.map((entry) => ({ entry, query: entry.handle }));
48041
+ if (inspectIds)
48042
+ return entries.filter((e) => inspectIds.includes(e.id));
48043
+ const client = this.selectedClientName();
48044
+ return client ? entries.filter((e) => e.meta.clientName === client) : entries;
48045
+ }, /* @ts-ignore */
48046
+ ...(ngDevMode ? [{ debugName: "scopedQueries" }] : /* istanbul ignore next */ []));
48047
+ this.searchedQueries = computed(() => {
48048
+ const items = this.scopedQueries().map((entry) => ({ entry, query: entry.handle }));
48049
+ const terms = this.queryFilter().trim().toLowerCase().split(/\s+/).filter(Boolean);
48050
+ if (!terms.length)
48051
+ return items;
48052
+ return items.filter((item) => {
48053
+ const haystack = this.queryHaystack(item.entry, item.query);
48054
+ return terms.every((term) => haystack.includes(term));
48055
+ });
48056
+ }, /* @ts-ignore */
48057
+ ...(ngDevMode ? [{ debugName: "searchedQueries" }] : /* istanbul ignore next */ []));
48058
+ /**
48059
+ * How many queries each status chip would leave. Counted before the active chips are applied, so a
48060
+ * chip always states what picking it yields rather than what the current selection happens to show.
48061
+ */
48062
+ this.facetCounts = computed(() => {
48063
+ // Staleness is a `Date.now()` comparison and deliberately not reactive, so the clock is what makes
48064
+ // the counts age with it - without it a chip would keep the number it happened to be built with.
48065
+ this.clock();
48066
+ const counts = { error: 0, loading: 0, stale: 0, idle: 0 };
48067
+ for (const { query } of this.searchedQueries()) {
48068
+ const status = this.queryStatus(query);
48069
+ if (status === 'error')
48070
+ counts.error++;
48071
+ if (status === 'loading')
48072
+ counts.loading++;
48073
+ if (status === 'idle')
48074
+ counts.idle++;
48075
+ if (this.isStale(query))
48076
+ counts.stale++;
48077
+ }
48078
+ return counts;
48079
+ }, /* @ts-ignore */
48080
+ ...(ngDevMode ? [{ debugName: "facetCounts" }] : /* istanbul ignore next */ []));
48081
+ this.filteredQueries = computed(() => {
48082
+ const facets = this.queryFacets();
48083
+ const items = this.searchedQueries();
48084
+ if (!facets.size)
48085
+ return items;
48086
+ // Same reason as in `facetCounts`: a list narrowed to stale queries has to re-evaluate as they age.
48087
+ if (facets.has('stale'))
48088
+ this.clock();
48089
+ return items.filter(({ query }) => this.matchesFacets(query, facets));
47480
48090
  }, /* @ts-ignore */
47481
48091
  ...(ngDevMode ? [{ debugName: "filteredQueries" }] : /* istanbul ignore next */ []));
48092
+ /** How many queries are in scope, which is what the list would show unfiltered. */
48093
+ this.scopedQueryCount = computed(() => this.scopedQueries().length, /* @ts-ignore */
48094
+ ...(ngDevMode ? [{ debugName: "scopedQueryCount" }] : /* istanbul ignore next */ []));
48095
+ /** Whether the search box or a status chip is narrowing the list beyond its scope. */
48096
+ this.isQueryListNarrowed = computed(() => !!this.queryFilter().trim() || this.queryFacets().size > 0, /* @ts-ignore */
48097
+ ...(ngDevMode ? [{ debugName: "isQueryListNarrowed" }] : /* istanbul ignore next */ []));
48098
+ /**
48099
+ * Every run the scoped queries have recorded, oldest first. The client picker and the inspection
48100
+ * filter narrow the timeline the same way they narrow the Queries list.
48101
+ */
48102
+ this.scopedRuns = computed(() => {
48103
+ const collected = [];
48104
+ for (const entry of this.scopedQueries()) {
48105
+ for (const run of entry.stats?.runs() ?? [])
48106
+ collected.push({ entry, run });
48107
+ }
48108
+ return collected.sort((a, b) => a.run.startedAt - b.run.startedAt);
48109
+ }, /* @ts-ignore */
48110
+ ...(ngDevMode ? [{ debugName: "scopedRuns" }] : /* istanbul ignore next */ []));
48111
+ /**
48112
+ * Every scoped run laid out on one axis, so a stampede reads as overlapping bars and a chain as a
48113
+ * staircase - which is what the Events tab's flat list of wall-clock times cannot show.
48114
+ */
48115
+ this.timeline = computed(() => {
48116
+ const collected = this.scopedRuns();
48117
+ const hidden = Math.max(0, collected.length - MAX_TIMELINE_ROWS);
48118
+ const shown = hidden ? collected.slice(hidden) : collected;
48119
+ const first = shown[0];
48120
+ if (!first)
48121
+ return { rows: [], startedAt: 0, windowMs: 0, hidden: 0 };
48122
+ // A run in flight has no end yet, so the window has to grow with the clock - otherwise its bar would
48123
+ // freeze at the width it happened to be built with, the same trap `isStale` documents.
48124
+ if (shown.some(({ run }) => run.endedAt === null))
48125
+ this.clock();
48126
+ const now = Date.now();
48127
+ const startedAt = first.run.startedAt;
48128
+ const endedAt = shown.reduce((latest, { run }) => Math.max(latest, run.endedAt ?? now), startedAt);
48129
+ const windowMs = Math.max(1, endedAt - startedAt);
48130
+ const rows = shown.map(({ entry, run }) => {
48131
+ const url = run.url ?? this.requestUrl(entry.handle);
48132
+ return {
48133
+ key: `${entry.id}:${run.index}`,
48134
+ entryId: entry.id,
48135
+ method: entry.meta.method ?? '',
48136
+ path: url ? this.requestPath(url) : (entry.meta.route ?? ''),
48137
+ run,
48138
+ leftPct: ((run.startedAt - startedAt) / windowMs) * 100,
48139
+ // An instant run - a cache entry filled by a poll or by another consumer - still needs a sliver
48140
+ // of width to be visible at all.
48141
+ widthPct: Math.max(0.4, (((run.endedAt ?? now) - run.startedAt) / windowMs) * 100),
48142
+ durationMs: run.endedAt === null ? null : run.endedAt - run.startedAt,
48143
+ };
48144
+ });
48145
+ return { rows, startedAt, windowMs, hidden };
48146
+ }, /* @ts-ignore */
48147
+ ...(ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []));
48148
+ /** Axis labels for the timeline, as offsets from the window's start. */
48149
+ this.timelineTicks = computed(() => {
48150
+ const { windowMs, rows } = this.timeline();
48151
+ if (!rows.length)
48152
+ return [];
48153
+ return TIMELINE_TICKS.map((fraction) => ({
48154
+ pct: fraction * 100,
48155
+ label: this.formatDuration(Math.round(windowMs * fraction)),
48156
+ }));
48157
+ }, /* @ts-ignore */
48158
+ ...(ngDevMode ? [{ debugName: "timelineTicks" }] : /* istanbul ignore next */ []));
48159
+ /** How many runs every query has recorded, for the Timeline tab's badge. */
48160
+ this.runTotals = computed(() => {
48161
+ let count = 0;
48162
+ let errors = 0;
48163
+ for (const entry of this.queryEntries()) {
48164
+ for (const run of entry.stats?.runs() ?? []) {
48165
+ count++;
48166
+ if (run.status === 'error')
48167
+ errors++;
48168
+ }
48169
+ }
48170
+ return { count, errors };
48171
+ }, /* @ts-ignore */
48172
+ ...(ngDevMode ? [{ debugName: "runTotals" }] : /* istanbul ignore next */ []));
48173
+ /** Cache entries across every client, for the Cache tab's badge. */
48174
+ this.cacheEntryCount = computed(() => this.repositories().reduce((total, { repository }) => {
48175
+ // Read the version signal so the badge recounts on every cache mutation.
48176
+ repository.subtle.cacheVersion();
48177
+ return total + repository.subtle.cacheEntries().length;
48178
+ }, 0), /* @ts-ignore */
48179
+ ...(ngDevMode ? [{ debugName: "cacheEntryCount" }] : /* istanbul ignore next */ []));
48180
+ /**
48181
+ * What each tab holds, so a failing query in a tab that is not open is still visible. Reading it
48182
+ * subscribes the tab bar to every entry's live state - which is the point of the badges.
48183
+ */
48184
+ this.tabBadges = computed(() => {
48185
+ const queries = this.queryEntries();
48186
+ const stacks = this.stackEntries();
48187
+ const sequences = this.sequenceEntries();
48188
+ const events = this.eventLog();
48189
+ return {
48190
+ queries: {
48191
+ count: queries.length,
48192
+ errors: queries.filter((entry) => this.queryStatus(entry.handle) === 'error').length,
48193
+ },
48194
+ stacks: {
48195
+ count: stacks.length,
48196
+ errors: stacks.filter((entry) => entry.kind === 'paged-query-stack' ? !!this.asPagedStack(entry).error() : !!this.asStack(entry).anyError()).length,
48197
+ },
48198
+ sequences: {
48199
+ count: sequences.length,
48200
+ errors: sequences.filter((entry) => (this.asSequence(entry)?.failedAt() ?? null) !== null).length,
48201
+ },
48202
+ forms: { count: this.formEntries().length, errors: 0 },
48203
+ auth: { count: this.authEntries().length, errors: 0 },
48204
+ ws: { count: this.wsEntries().length, errors: 0 },
48205
+ cache: { count: this.cacheEntryCount(), errors: 0 },
48206
+ timeline: this.runTotals(),
48207
+ events: {
48208
+ count: events.length,
48209
+ errors: events.filter((event) => event.type === 'request-error').length,
48210
+ },
48211
+ // Armed faults are reported as errors rather than as a plain count: the badge is the one reminder
48212
+ // that the app is misbehaving on purpose, and it has to be impossible to read as "all good".
48213
+ faults: { count: 0, errors: Object.keys(queryDevtoolsFaults()).length, errorNoun: 'armed' },
48214
+ };
48215
+ }, /* @ts-ignore */
48216
+ ...(ngDevMode ? [{ debugName: "tabBadges" }] : /* istanbul ignore next */ []));
47482
48217
  this.selectedQuery = computed(() => this.findQuery(this.selectedQueryId()), /* @ts-ignore */
47483
48218
  ...(ngDevMode ? [{ debugName: "selectedQuery" }] : /* istanbul ignore next */ []));
47484
48219
  this.stackSelectedQuery = computed(() => this.findQuery(this.stackSelectedQueryId()), /* @ts-ignore */
47485
48220
  ...(ngDevMode ? [{ debugName: "stackSelectedQuery" }] : /* istanbul ignore next */ []));
47486
48221
  this.sequenceSelectedQuery = computed(() => this.findQuery(this.sequenceSelectedQueryId()), /* @ts-ignore */
47487
48222
  ...(ngDevMode ? [{ debugName: "sequenceSelectedQuery" }] : /* istanbul ignore next */ []));
48223
+ this.formSelectedQuery = computed(() => this.findQuery(this.formSelectedQueryId()), /* @ts-ignore */
48224
+ ...(ngDevMode ? [{ debugName: "formSelectedQuery" }] : /* istanbul ignore next */ []));
48225
+ this.timelineSelectedQuery = computed(() => this.findQuery(this.timelineSelectedQueryId()), /* @ts-ignore */
48226
+ ...(ngDevMode ? [{ debugName: "timelineSelectedQuery" }] : /* istanbul ignore next */ []));
47488
48227
  this.cacheView = computed(() => this.repositories().map(({ repository, name, baseUrl, client }) => {
47489
48228
  // Read the version signal so this recomputes on every cache mutation.
47490
48229
  repository.subtle.cacheVersion();
@@ -47514,6 +48253,15 @@ class QueryDevtoolsComponent {
47514
48253
  return map;
47515
48254
  }, /* @ts-ignore */
47516
48255
  ...(ngDevMode ? [{ debugName: "elementQueryMap" }] : /* istanbul ignore next */ []));
48256
+ // --- Fault injection ---
48257
+ this.FAULT_STATUSES = QUERY_DEVTOOLS_FAULT_STATUSES;
48258
+ this.CLEAR_FAULTS = clearQueryDevtoolsFaults;
48259
+ /** The ceiling each numeric fault field is clamped to. */
48260
+ this.FAULT_LIMITS = {
48261
+ latencyMs: 60_000,
48262
+ failNext: 99,
48263
+ failRate: 100,
48264
+ };
47517
48265
  // Assigned here (not as an arrow property) so `this` is bound for the value-explorer callback.
47518
48266
  this.toggleJsonPath = (path, expand) => {
47519
48267
  const expanded = new Set(this.jsonExpandedPaths());
@@ -47529,6 +48277,14 @@ class QueryDevtoolsComponent {
47529
48277
  this.jsonExpandedPaths.set(expanded);
47530
48278
  this.jsonCollapsedPaths.set(collapsed);
47531
48279
  };
48280
+ // Each copy restarts the countdown; switchMap drops the pending reset of the previous one.
48281
+ this.copiedReset$
48282
+ .pipe(switchMap(() => timer(COPIED_RESET_MS)), tap(() => {
48283
+ this.copiedReport.set(false);
48284
+ this.copiedInsomnia.set(false);
48285
+ this.copiedGql.set(false);
48286
+ }), takeUntilDestroyed())
48287
+ .subscribe();
47532
48288
  // Merge every live repository's event stream into the rolling log, re-subscribing as the set of
47533
48289
  // repositories changes. Composed with RxJS (not a subscribe-in-effect) per the styleguide.
47534
48290
  toObservable(this.repositories)
@@ -47539,9 +48295,13 @@ class QueryDevtoolsComponent {
47539
48295
  open: this.open(),
47540
48296
  height: this.panelHeight(),
47541
48297
  activeTab: this.activeTab(),
48298
+ detailTab: this.detailTab(),
47542
48299
  selectedClientName: this.selectedClientName(),
47543
48300
  selectedQueryId: this.selectedQueryId(),
48301
+ selectedFormId: this.selectedFormId(),
47544
48302
  inspectFilterIds: this.inspectFilterIds(),
48303
+ queryFilter: this.queryFilter(),
48304
+ queryFacets: [...this.queryFacets()],
47545
48305
  jsonSearch: this.jsonSearch(),
47546
48306
  expandedSteps: [...this.expandedSteps()],
47547
48307
  jsonExpanded: [...this.jsonExpandedPaths()],
@@ -47564,6 +48324,8 @@ class QueryDevtoolsComponent {
47564
48324
  this.editorMode.set('none');
47565
48325
  this.editError.set(null);
47566
48326
  this.copiedReport.set(false);
48327
+ this.copiedInsomnia.set(false);
48328
+ this.diffRunIndex.set(null);
47567
48329
  if (key !== this.lastSelectionKey) {
47568
48330
  this.lastSelectionKey = key;
47569
48331
  this.jsonSearch.set('');
@@ -47612,6 +48374,17 @@ class QueryDevtoolsComponent {
47612
48374
  clearInspectFilter() {
47613
48375
  this.inspectFilterIds.set(null);
47614
48376
  }
48377
+ toggleFacet(facet) {
48378
+ const next = new Set(this.queryFacets());
48379
+ if (!next.delete(facet))
48380
+ next.add(facet);
48381
+ this.queryFacets.set(next);
48382
+ }
48383
+ /** Drops the search term and the status chips, keeping the client / inspection scope. */
48384
+ clearQueryFilters() {
48385
+ this.queryFilter.set('');
48386
+ this.queryFacets.set(new Set());
48387
+ }
47615
48388
  toggleInspect() {
47616
48389
  this.inspectActive.update((v) => !v);
47617
48390
  }
@@ -47621,10 +48394,44 @@ class QueryDevtoolsComponent {
47621
48394
  }
47622
48395
  inspectLabel(entries) {
47623
48396
  const first = entries[0];
47624
- if (entries.length === 1 && first)
47625
- return `${first.meta.method ?? ''} ${first.meta.route ?? ''}`.trim();
48397
+ if (entries.length === 1 && first) {
48398
+ return `${first.meta.method ?? ''} ${this.queryRoute(first, first.handle)}`.trim();
48399
+ }
47626
48400
  return `${entries.length} queries`;
47627
48401
  }
48402
+ /**
48403
+ * A query's route, split so the template can tell its static path from its path params (each carrying
48404
+ * the value the query used, or `:<name>` while it has none yet) and from the query string of the
48405
+ * request that ran - which is what tells two requests to the same endpoint apart.
48406
+ */
48407
+ routeSegments(entry, query) {
48408
+ const parts = entry?.meta.routeParts;
48409
+ const search = query.subtle.request()?.url.split('?')[1];
48410
+ const querySegment = search ? [{ text: `?${search}`, kind: 'query' }] : [];
48411
+ if (!parts?.length) {
48412
+ return entry?.meta.route ? [{ text: entry.meta.route, kind: 'static' }, ...querySegment] : [];
48413
+ }
48414
+ const pathParams = this.queryArgs(query)?.['pathParams'];
48415
+ const pathSegments = parts.map(({ text, param }) => {
48416
+ if (!param)
48417
+ return { text, kind: 'static' };
48418
+ const value = pathParams?.[param];
48419
+ return { text: value === undefined || value === null ? `:${param}` : String(value), kind: 'param', name: param };
48420
+ });
48421
+ return [...pathSegments, ...querySegment];
48422
+ }
48423
+ /** The full URL of the request a query last made, or `null` while it has not executed. */
48424
+ requestUrl(query) {
48425
+ return query.subtle.request()?.url ?? null;
48426
+ }
48427
+ /**
48428
+ * The args of a query. A query executed imperatively (`execute({ args })`, a sequence step, an auth
48429
+ * query) never writes them to its own `args` signal - only the `withArgs` feature does - so the args
48430
+ * its current request was built from stand in.
48431
+ */
48432
+ queryArgs(query) {
48433
+ return query.args() ?? query.subtle.request()?.args ?? null;
48434
+ }
47628
48435
  queryStatus(query) {
47629
48436
  const state = query.executionState();
47630
48437
  if (!state)
@@ -47635,14 +48442,164 @@ class QueryDevtoolsComponent {
47635
48442
  return 'error';
47636
48443
  return 'success';
47637
48444
  }
48445
+ /**
48446
+ * A request in flight is already refreshing, so reporting it as stale on top of `loading` is noise -
48447
+ * the same precedence the cache tab's freshness column applies.
48448
+ */
47638
48449
  isStale(query) {
47639
48450
  try {
47640
- return query.subtle.request()?.isStale() ?? false;
48451
+ const request = query.subtle.request();
48452
+ if (!request || request.loading())
48453
+ return false;
48454
+ return request.isStale();
47641
48455
  }
47642
48456
  catch {
47643
48457
  return false;
47644
48458
  }
47645
48459
  }
48460
+ /**
48461
+ * What a query's current request is doing beyond being loading, or `null` when there is nothing beyond
48462
+ * the status dot to say - so the readout only takes up room while it carries something.
48463
+ */
48464
+ requestProgress(query) {
48465
+ const request = query.subtle.request();
48466
+ if (!request)
48467
+ return null;
48468
+ const retry = request.subtle.retryState();
48469
+ const attempts = request.subtle.attempts();
48470
+ // The request's own loading state, not the query's: a forced loading state carries no progress, and
48471
+ // reading the query's would report the forced one as a transfer that never started.
48472
+ const progress = request.loading()?.progress ?? null;
48473
+ if (!retry && !progress && attempts < 2)
48474
+ return null;
48475
+ // The countdown is a `Date.now()` comparison, so the clock is what makes it tick down - the same trap
48476
+ // `isStale` documents.
48477
+ this.clock();
48478
+ return { attempts, retry, retryInMs: retry ? Math.max(0, retry.startsAt - Date.now()) : null, progress };
48479
+ }
48480
+ /** Why a retry was scheduled, as the panel spells it out. A status of 0 never reached the server. */
48481
+ retryCause(status) {
48482
+ return status ? `after ${status}` : 'after a connection failure';
48483
+ }
48484
+ formatPercent(percentage) {
48485
+ return `${Math.round(percentage)}%`;
48486
+ }
48487
+ /** A countdown in whole seconds, spelled out the way the cache freshness column does. */
48488
+ formatCountdown(ms) {
48489
+ return ms === null ? '—' : `${Math.ceil(ms / 1000)}s`;
48490
+ }
48491
+ queryActivity(entry) {
48492
+ return this.activityOf([entry.stats]);
48493
+ }
48494
+ linkActivity(link) {
48495
+ return this.activityOf([link.stats]);
48496
+ }
48497
+ stackActivity(stack) {
48498
+ return this.activityOf(this.queriesForStack(stack).map((link) => link.stats));
48499
+ }
48500
+ sequenceActivity(sequence) {
48501
+ return this.activityOf(this.queriesForSequence(sequence).map((link) => link.stats));
48502
+ }
48503
+ /** Clears an entry's counters and run history, so the next interaction can be measured on its own. */
48504
+ resetStats(entry) {
48505
+ entry.stats?.reset();
48506
+ this.diffRunIndex.set(null);
48507
+ }
48508
+ /** The same, for every query the timeline covers. */
48509
+ resetTimeline() {
48510
+ for (const entry of this.scopedQueries())
48511
+ entry.stats?.reset();
48512
+ this.diffRunIndex.set(null);
48513
+ }
48514
+ /** A query's runs, newest first - the order a history is read in. */
48515
+ queryRuns(entry) {
48516
+ return [...(entry.stats?.runs() ?? [])].reverse();
48517
+ }
48518
+ /**
48519
+ * What a run's status dot and timeline bar colour by. `pending` reuses the panel's loading colour;
48520
+ * `aborted` matches no rule and so falls back to the neutral one.
48521
+ */
48522
+ runStatus(run) {
48523
+ return run.status === 'pending' ? 'loading' : run.status;
48524
+ }
48525
+ /** Whether a run can be diffed: it still holds its body, and so does an older run of the same query. */
48526
+ canDiffRun(entry, run) {
48527
+ if (!run.hasResponse)
48528
+ return false;
48529
+ return (entry.stats?.runs() ?? []).some((other) => other.hasResponse && other.index < run.index);
48530
+ }
48531
+ toggleRunDiff(run) {
48532
+ this.diffRunIndex.update((current) => (current === run.index ? null : run.index));
48533
+ }
48534
+ /**
48535
+ * The response diff of the picked run against the newest older run that still holds a body - which is
48536
+ * not necessarily the run right before it, since a failed run has none to compare.
48537
+ *
48538
+ * `null` unless a run is picked, so a closed diff costs nothing to walk.
48539
+ */
48540
+ responseDiff(entry) {
48541
+ const picked = this.diffRunIndex();
48542
+ if (picked === null)
48543
+ return null;
48544
+ const runs = entry.stats?.runs() ?? [];
48545
+ const at = runs.findIndex((run) => run.index === picked);
48546
+ const after = at === -1 ? null : runs[at];
48547
+ if (!after?.hasResponse)
48548
+ return null;
48549
+ for (let index = at - 1; index >= 0; index--) {
48550
+ const before = runs[index];
48551
+ if (before?.hasResponse) {
48552
+ return { before, after, diff: diffQueryDevtoolsResponses(before.response, after.response) };
48553
+ }
48554
+ }
48555
+ return null;
48556
+ }
48557
+ /** Opens a query in the Queries tab - the Events tab is a way in, not a dead end. */
48558
+ selectQuery(id) {
48559
+ this.activeTab.set('queries');
48560
+ this.selectedQueryId.set(id);
48561
+ }
48562
+ selectTimelineRow(row) {
48563
+ this.timelineSelectedQueryId.set(row.entryId);
48564
+ }
48565
+ selectEventRow(item) {
48566
+ if (item.queryId)
48567
+ this.selectQuery(item.queryId);
48568
+ }
48569
+ /** A value on one line, for a diff row or a form field. The full tree would bury the row it sits in. */
48570
+ inlineValue(value) {
48571
+ if (typeof value === 'string')
48572
+ return value.length > 80 ? `"${value.slice(0, 80)}…"` : `"${value}"`;
48573
+ if (value === undefined)
48574
+ return 'undefined';
48575
+ try {
48576
+ const json = JSON.stringify(value) ?? 'undefined';
48577
+ return json.length > 80 ? `${json.slice(0, 80)}…` : json;
48578
+ }
48579
+ catch {
48580
+ return '[unserializable]';
48581
+ }
48582
+ }
48583
+ /** A byte count the way a network panel spells one out. */
48584
+ formatBytes(bytes) {
48585
+ if (bytes < 1000)
48586
+ return `${bytes} B`;
48587
+ if (bytes < 1_000_000)
48588
+ return `${(bytes / 1000).toFixed(1)} kB`;
48589
+ return `${(bytes / 1_000_000).toFixed(2)} MB`;
48590
+ }
48591
+ /**
48592
+ * A transferred size, marked `≈` when any part of it was measured from a decoded body instead of read
48593
+ * from a `content-length` header - such a size ignores transport compression.
48594
+ */
48595
+ formatTransferred(bytes, isEstimated) {
48596
+ return `${isEstimated ? '≈' : ''}${this.formatBytes(bytes)}`;
48597
+ }
48598
+ formatDuration(ms) {
48599
+ if (ms === null)
48600
+ return '—';
48601
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
48602
+ }
47646
48603
  executeQuery(query, allowCache) {
47647
48604
  query.execute(allowCache ? { options: { allowCache: true } } : undefined);
47648
48605
  }
@@ -47658,11 +48615,18 @@ class QueryDevtoolsComponent {
47658
48615
  const error = query.error();
47659
48616
  const httpStatus = error ? error.raw.status : this.responseStatus(query);
47660
48617
  const method = entry.meta.method ?? '';
47661
- const route = entry.meta.route || '-';
47662
- const client = entry.meta.clientBaseUrl ?? entry.meta.clientName ?? '';
48618
+ // The URL actually requested already contains the base URL and the resolved params, so the client
48619
+ // is only spelled out separately when the query has not run and only its `:param` template is known.
48620
+ const requestUrl = this.requestUrl(query);
48621
+ const route = requestUrl ?? entry.meta.route ?? '-';
48622
+ const client = requestUrl ? '' : (entry.meta.clientBaseUrl ?? entry.meta.clientName ?? '');
47663
48623
  const statusLine = `status: ${this.queryStatus(query)}${httpStatus !== null ? ` (${httpStatus})` : ''} · ${this.formatTime(query.lastTimeExecutedAt())}`;
48624
+ const features = entry.meta.features?.length
48625
+ ? `features: ${entry.meta.features.map((feature) => this.featureSummary(feature)).join(' | ')}`
48626
+ : null;
48627
+ const activity = this.activitySummary(this.queryActivity(entry));
47664
48628
  const gqlDoc = entry.meta.gqlQuery ? this.gqlDocument(entry.meta.gqlQuery) : null;
47665
- const args = query.args();
48629
+ const args = this.queryArgs(query);
47666
48630
  const argsLabel = gqlDoc ? 'Variables' : 'Args';
47667
48631
  const argsJson = args !== null && args !== undefined ? JSON.stringify(args, null, 2) : null;
47668
48632
  const bodyLabel = error ? `Error (${error.raw.status})` : 'Response';
@@ -47672,6 +48636,10 @@ class QueryDevtoolsComponent {
47672
48636
  : error.error.message
47673
48637
  : JSON.stringify(slimForReport(query.response()), null, 2);
47674
48638
  const textParts = [`${method} ${route}${client ? ` - ${client}` : ''}`, statusLine];
48639
+ if (activity)
48640
+ textParts.push(activity);
48641
+ if (features)
48642
+ textParts.push(features);
47675
48643
  if (gqlDoc)
47676
48644
  textParts.push('', 'GraphQL document', gqlDoc);
47677
48645
  if (argsJson)
@@ -47683,22 +48651,64 @@ class QueryDevtoolsComponent {
47683
48651
  `<b>${esc(method)}</b> <code>${esc(route)}</code>${client ? ` - <code>${esc(client)}</code>` : ''}`,
47684
48652
  esc(statusLine),
47685
48653
  ];
48654
+ if (activity)
48655
+ htmlParts.push(esc(activity));
48656
+ if (features)
48657
+ htmlParts.push(esc(features));
47686
48658
  if (gqlDoc)
47687
48659
  htmlParts.push('<b>GraphQL document</b>', `<pre><code>${esc(gqlDoc)}</code></pre>`);
47688
48660
  if (argsJson)
47689
48661
  htmlParts.push(`<b>${argsLabel}</b>`, `<pre><code>${esc(argsJson)}</code></pre>`);
47690
48662
  htmlParts.push(`<b>${esc(bodyLabel)}</b>`, `<pre><code>${esc(bodyContent)}</code></pre>`);
47691
48663
  const html = htmlParts.join('<br>');
47692
- this.writeToClipboard(html, text);
48664
+ this.writeToClipboard({ html, text }, this.copiedReport);
48665
+ }
48666
+ // --- Insomnia export ---
48667
+ /** Copies one query as an Insomnia collection, for `Import > From Clipboard`. */
48668
+ copyInsomniaRequest(entry, query) {
48669
+ const requests = [this.insomniaRequest(entry, query)];
48670
+ const json = JSON.stringify(buildInsomniaExport({
48671
+ name: `${entry.meta.clientName ?? 'query'} · ${this.queryRoute(entry, query) || 'request'}`,
48672
+ requests,
48673
+ tokenRefreshes: this.insomniaTokenRefreshes(requests),
48674
+ now: Date.now(),
48675
+ }), null, 2);
48676
+ this.writeToClipboard({ text: json }, this.copiedInsomnia);
48677
+ }
48678
+ /** Copies the GraphQL document as displayed — dedented, so it pastes straight into a playground. */
48679
+ copyGqlDocument(doc) {
48680
+ this.writeToClipboard({ text: this.gqlDocument(doc) }, this.copiedGql);
48681
+ }
48682
+ /**
48683
+ * Downloads every query currently listed (so the client filter and the inspection filter both apply)
48684
+ * as one Insomnia collection, filed into a folder per query client.
48685
+ */
48686
+ downloadInsomniaCollection() {
48687
+ const items = this.filteredQueries();
48688
+ if (!items.length)
48689
+ return;
48690
+ const client = this.selectedClientName();
48691
+ const requests = items.map(({ entry, query }) => this.insomniaRequest(entry, query));
48692
+ const json = JSON.stringify(buildInsomniaExport({
48693
+ name: `${client ?? 'ethlete'} queries`,
48694
+ requests,
48695
+ tokenRefreshes: this.insomniaTokenRefreshes(requests),
48696
+ now: Date.now(),
48697
+ }), null, 2);
48698
+ this.downloadFile(`insomnia-${client ?? 'ethlete'}-queries.json`, json);
47693
48699
  }
47694
48700
  // --- JIT editing ---
47695
48701
  openResponseEditor(query) {
47696
- this.responseDraft.set(JSON.stringify(query.response() ?? null, null, 2));
48702
+ const draft = JSON.stringify(query.response() ?? null, null, 2);
48703
+ this.editorSeed = draft;
48704
+ this.responseDraft.set(draft);
47697
48705
  this.editError.set(null);
47698
48706
  this.editorMode.set('response');
47699
48707
  }
47700
48708
  openArgsEditor(query) {
47701
- this.argsDraft.set(JSON.stringify(query.args() ?? {}, null, 2));
48709
+ const draft = JSON.stringify(this.queryArgs(query) ?? {}, null, 2);
48710
+ this.editorSeed = draft;
48711
+ this.argsDraft.set(draft);
47702
48712
  this.editError.set(null);
47703
48713
  this.editorMode.set('args');
47704
48714
  }
@@ -47751,6 +48761,22 @@ class QueryDevtoolsComponent {
47751
48761
  query.subtle.setLoading(null);
47752
48762
  query.subtle.setError(null);
47753
48763
  }
48764
+ /**
48765
+ * Arms one numeric field of a client's fault from an input's raw value. Clamped here rather than left
48766
+ * to the input's `min`/`max`, which a typed-in (or pasted) value ignores.
48767
+ */
48768
+ armFaultValue(options) {
48769
+ const { clientName, field, value } = options;
48770
+ const parsed = clamp$1(Math.trunc(Number(value) || 0), 0, this.FAULT_LIMITS[field]);
48771
+ setQueryDevtoolsFault({ clientName, patch: { [field]: parsed } });
48772
+ }
48773
+ armFaultStatus(options) {
48774
+ setQueryDevtoolsFault({ clientName: options.clientName, patch: { status: Number(options.value) } });
48775
+ }
48776
+ /** Whether the default retry policy retries the status a client is armed to fail with. */
48777
+ isFaultStatusRetryable(status) {
48778
+ return QUERY_DEVTOOLS_FAULT_STATUSES.find((entry) => entry.status === status)?.retryable ?? false;
48779
+ }
47754
48780
  // --- Cache actions ---
47755
48781
  refetchCacheEntry(entry) {
47756
48782
  entry.request.execute();
@@ -47828,6 +48854,50 @@ class QueryDevtoolsComponent {
47828
48854
  asWs(entry) {
47829
48855
  return entry.handle;
47830
48856
  }
48857
+ asForm(entry) {
48858
+ return entry.handle;
48859
+ }
48860
+ /**
48861
+ * The queries a form feeds, discovered from the reads its `value()` recorded while their args were
48862
+ * built - so a form that nothing consumes yet reads as exactly that.
48863
+ */
48864
+ queriesDrivenByForm(entry) {
48865
+ return this.queryEntries()
48866
+ .filter((candidate) => candidate.formLinks?.ids().includes(entry.id))
48867
+ .map((candidate) => this.queryLinkFor(candidate));
48868
+ }
48869
+ /** The reverse: the forms whose value a query's args read. */
48870
+ formsDrivingQuery(entry) {
48871
+ const ids = entry.formLinks?.ids() ?? [];
48872
+ return this.formEntries().filter((form) => ids.includes(form.id));
48873
+ }
48874
+ selectForm(id) {
48875
+ this.activeTab.set('forms');
48876
+ this.selectedFormId.set(id);
48877
+ }
48878
+ /**
48879
+ * The refreshes that re-executed a query, newest first - the answer to "why did this refetch?". Read
48880
+ * off the event log, so it goes back exactly as far as the log does.
48881
+ */
48882
+ refreshesFor(entryId) {
48883
+ return this.eventLog()
48884
+ .filter((item) => item.cause && item.refreshed?.some((refreshed) => refreshed.queryIds.includes(entryId)))
48885
+ .map((item) => ({
48886
+ id: item.id,
48887
+ timestamp: item.timestamp,
48888
+ label: this.causeLabel(item.cause),
48889
+ }));
48890
+ }
48891
+ /** What asked for a refresh, on one line. */
48892
+ causeLabel(cause) {
48893
+ const scope = cause.url ? this.requestPath(cause.url) : 'everything in use';
48894
+ const what = cause.type === 'invalidation'
48895
+ ? `invalidated ${scope}`
48896
+ : cause.type === 'mutation'
48897
+ ? `mutation on ${scope}`
48898
+ : `refreshed ${scope}`;
48899
+ return cause.otherTab ? `${what} · another tab` : what;
48900
+ }
47831
48901
  /** Derives the per-step status of a sequence step from its live progress signals. */
47832
48902
  sequenceStepStatus(sequence, index) {
47833
48903
  const failedAt = sequence.failedAt();
@@ -47849,25 +48919,12 @@ class QueryDevtoolsComponent {
47849
48919
  queriesForStack(stack) {
47850
48920
  const inner = stack.queries();
47851
48921
  const queryEntries = this.queryEntries();
47852
- return inner.map((query) => {
47853
- const entry = queryEntries.find((e) => e.handle === query);
47854
- return {
47855
- id: entry?.id ?? '',
47856
- query: query,
47857
- method: entry?.meta.method ?? '',
47858
- route: entry?.meta.route ?? '',
47859
- clientName: entry?.meta.clientName ?? '',
47860
- clientBaseUrl: entry?.meta.clientBaseUrl ?? '',
47861
- };
47862
- });
48922
+ return inner.map((query) => this.queryLinkFor(queryEntries.find((e) => e.handle === query), query));
47863
48923
  }
47864
48924
  /** Identifying info for a stack, derived from its (uniform) inner queries. */
47865
48925
  stackIdentity(stack) {
47866
48926
  const first = this.queriesForStack(stack)[0];
47867
- return { method: first?.method ?? '', route: first?.route ?? '', baseUrl: first?.clientBaseUrl ?? '' };
47868
- }
47869
- authFeatures(auth) {
47870
- return Object.keys(auth.features ?? {});
48927
+ return { method: first?.method ?? '', segments: first?.segments ?? [], baseUrl: first?.clientBaseUrl ?? '' };
47871
48928
  }
47872
48929
  authQueryKeys(auth) {
47873
48930
  return Object.keys(auth.queries ?? {});
@@ -47890,15 +48947,7 @@ class QueryDevtoolsComponent {
47890
48947
  }
47891
48948
  queriesForSequence(sequence) {
47892
48949
  const queryEntries = this.queryEntries();
47893
- return sequence.queries.map((query) => {
47894
- const entry = queryEntries.find((e) => e.handle === query);
47895
- return {
47896
- id: entry?.id ?? '',
47897
- query: query,
47898
- method: entry?.meta.method ?? '',
47899
- route: entry?.meta.route ?? '',
47900
- };
47901
- });
48950
+ return sequence.queries.map((query) => this.queryLinkFor(queryEntries.find((e) => e.handle === query), query));
47902
48951
  }
47903
48952
  /** The snapshot of a sequence step, once it has run (holds the args in and the response/error out). */
47904
48953
  stepSnapshot(sequence, index) {
@@ -47935,6 +48984,16 @@ class QueryDevtoolsComponent {
47935
48984
  .replace(/_/g, ' ')
47936
48985
  .toLowerCase();
47937
48986
  }
48987
+ /** A feature and its options on one line, for a report or a chip's tooltip. */
48988
+ featureSummary(feature) {
48989
+ const details = feature.details.map((detail) => `${detail.label} ${detail.value}`);
48990
+ return [this.featureLabel(feature.type), ...details].join(' · ');
48991
+ }
48992
+ /** The features of the client behind a cache tab card, or `null` for a client without any. */
48993
+ clientFeatures(client) {
48994
+ const features = client?.subtle.devtoolsFeatures ?? [];
48995
+ return features.length ? features : null;
48996
+ }
47938
48997
  formatTime(timestamp) {
47939
48998
  if (!timestamp)
47940
48999
  return '-';
@@ -47943,8 +49002,185 @@ class QueryDevtoolsComponent {
47943
49002
  eventTypeLabel(event) {
47944
49003
  if (event.type === 'unbind-all-secure')
47945
49004
  return 'logout';
49005
+ if (event.type === 'queries-refreshed')
49006
+ return `refetch ×${event.refreshed?.length ?? 0}`;
47946
49007
  return event.type === 'request-error' ? `error ${event.status}` : 'success';
47947
49008
  }
49009
+ /** A registered query as a row that opens the detail drawer. */
49010
+ queryLinkFor(entry, query = entry?.handle) {
49011
+ return {
49012
+ id: entry?.id ?? '',
49013
+ query,
49014
+ method: entry?.meta.method ?? '',
49015
+ segments: this.routeSegments(entry, query),
49016
+ clientBaseUrl: entry?.meta.clientBaseUrl ?? '',
49017
+ stats: entry?.stats,
49018
+ };
49019
+ }
49020
+ /** The activity of one entry, or the total of a group of them (a stack's queries, a whole tab). */
49021
+ activityOf(handles) {
49022
+ const stats = sumQueryDevtoolsStats(handles);
49023
+ return {
49024
+ stats,
49025
+ cacheServed: Math.max(0, stats.executions - stats.requests),
49026
+ avgDurationMs: stats.responses ? Math.round(stats.totalDurationMs / stats.responses) : null,
49027
+ avgResponseBytes: stats.responses ? Math.round(stats.receivedBytes / stats.responses) : null,
49028
+ hasActivity: stats.executions > 0,
49029
+ };
49030
+ }
49031
+ /** An activity summary on one line, for a report. `null` for a query that has not run. */
49032
+ activitySummary(activity) {
49033
+ if (!activity.hasActivity)
49034
+ return null;
49035
+ const { stats } = activity;
49036
+ const parts = [
49037
+ `${stats.executions} execution${stats.executions === 1 ? '' : 's'}`,
49038
+ `${stats.requests} request${stats.requests === 1 ? '' : 's'}`,
49039
+ `↓ ${this.formatTransferred(stats.receivedBytes, stats.hasEstimatedBytes)}`,
49040
+ ];
49041
+ if (stats.sentBytes)
49042
+ parts.push(`↑ ${this.formatTransferred(stats.sentBytes, stats.hasEstimatedBytes)}`);
49043
+ if (stats.errors)
49044
+ parts.push(`${stats.errors} failed`);
49045
+ if (stats.retries)
49046
+ parts.push(`${stats.retries} retr${stats.retries === 1 ? 'y' : 'ies'}`);
49047
+ if (activity.avgDurationMs !== null)
49048
+ parts.push(`avg ${this.formatDuration(activity.avgDurationMs)}`);
49049
+ return `activity: ${parts.join(' · ')}`;
49050
+ }
49051
+ /** A query matches the chips if it is in any of the picked states - chips widen, they don't intersect. */
49052
+ matchesFacets(query, facets) {
49053
+ const status = this.queryStatus(query);
49054
+ return ((facets.has('error') && status === 'error') ||
49055
+ (facets.has('loading') && status === 'loading') ||
49056
+ (facets.has('idle') && status === 'idle') ||
49057
+ (facets.has('stale') && this.isStale(query)));
49058
+ }
49059
+ /**
49060
+ * What the search box matches against: what the row shows, plus the path of the request that ran for a
49061
+ * query whose template differs from it.
49062
+ *
49063
+ * Deliberately not the origin or the client name - both repeat across most entries, so a short term
49064
+ * ("p") would match nearly everything through the host name. Scoping to a client is the picker's job.
49065
+ */
49066
+ queryHaystack(entry, query) {
49067
+ const url = this.requestUrl(query);
49068
+ const parts = [entry.meta.method, this.queryRoute(entry, query), url ? this.requestPath(url) : null];
49069
+ return parts.join(' ').toLowerCase();
49070
+ }
49071
+ /** {@link routeSegments} as a plain string, for the places that cannot render markup. */
49072
+ queryRoute(entry, query) {
49073
+ return this.routeSegments(entry, query)
49074
+ .map((segment) => segment.text)
49075
+ .join('');
49076
+ }
49077
+ /**
49078
+ * Describes a query the way a replay outside the app needs it: the URL, headers and body of the
49079
+ * request it last made, or - for a query that has not run - what its current args would send.
49080
+ */
49081
+ insomniaRequest(entry, query) {
49082
+ const request = query.subtle.request();
49083
+ const args = this.queryArgs(query);
49084
+ const route = this.queryRoute(entry, query);
49085
+ return {
49086
+ // A GraphQL query has no HTTP method of its own until it runs; its transport is POST unless the
49087
+ // creator says otherwise, which is only knowable from the request.
49088
+ method: request?.method ?? (entry.meta.gqlQuery ? 'POST' : (entry.meta.method ?? 'GET')),
49089
+ url: request?.url ?? `${entry.meta.clientBaseUrl ?? ''}${route}`,
49090
+ // Query params are part of the name so that several requests to the same endpoint (the pages of a
49091
+ // stack, a search) stay tellable apart in Insomnia's sidebar.
49092
+ name: `${entry.meta.method ?? ''} ${request ? this.requestPath(request.url) : route || query.id() || 'request'}`.trim(),
49093
+ headers: this.insomniaHeaders(request, args),
49094
+ body: args?.body ?? null,
49095
+ gqlQuery: entry.meta.gqlQuery ? this.gqlDocument(entry.meta.gqlQuery) : null,
49096
+ group: entry.meta.clientName ?? null,
49097
+ secureBy: entry.meta.isSecure ? (entry.meta.authProviderName ?? null) : null,
49098
+ };
49099
+ }
49100
+ /**
49101
+ * The token refreshes the given requests authenticate with - one per auth provider they name, and
49102
+ * only for a provider that is logged in, since a refresh request without a refresh token has
49103
+ * nothing to send.
49104
+ */
49105
+ insomniaTokenRefreshes(requests) {
49106
+ const names = new Set(requests.map((request) => request.secureBy).filter((name) => typeof name === 'string'));
49107
+ return Array.from(names)
49108
+ .map((name) => this.insomniaTokenRefresh(name))
49109
+ .filter((refresh) => refresh !== null);
49110
+ }
49111
+ insomniaTokenRefresh(providerName) {
49112
+ const entry = this.authEntries().find((candidate) => candidate.meta.name === providerName);
49113
+ const refresh = entry?.meta.authQueries?.find((authQuery) => authQuery.kind === 'token-refresh');
49114
+ if (!entry || !refresh)
49115
+ return null;
49116
+ const provider = entry.handle;
49117
+ const refreshToken = provider.refreshToken();
49118
+ if (!refreshToken)
49119
+ return null;
49120
+ return {
49121
+ id: providerName,
49122
+ name: `${refresh.method} ${refresh.route} (token refresh)`,
49123
+ method: refresh.method,
49124
+ url: `${entry.meta.clientBaseUrl ?? ''}${refresh.route}`,
49125
+ headers: [],
49126
+ body: refresh.buildArgs?.(refreshToken).body ?? null,
49127
+ group: entry.meta.clientName ?? null,
49128
+ accessTokenPath: this.accessTokenPath(provider),
49129
+ maxAgeSeconds: this.accessTokenMaxAge(provider),
49130
+ };
49131
+ }
49132
+ /**
49133
+ * Where the access token sits in the refresh response. A provider's `extractTokens` can pull it out
49134
+ * of any shape, so the path is recovered by finding the live token in the last auth response - with
49135
+ * the default extractor's `$.accessToken` as the fallback.
49136
+ */
49137
+ accessTokenPath(provider) {
49138
+ const token = provider.accessToken();
49139
+ const response = provider.latestExecutedQuery()?.snapshot.response();
49140
+ const path = token && response ? findValuePath(token, { value: response, path: '$', depth: 0 }) : null;
49141
+ return path ?? '$.accessToken';
49142
+ }
49143
+ /**
49144
+ * How long Insomnia may reuse a stored refresh response: the access token's own lifetime with a
49145
+ * margin, so the chain refreshes shortly before the token it hands out would expire. Capped at an
49146
+ * hour - a long-lived (or bogus) `exp` should still hand out a token minted this session.
49147
+ */
49148
+ accessTokenMaxAge(provider) {
49149
+ const payload = decodeJwtPayload(provider.accessToken());
49150
+ const exp = payload?.['exp'];
49151
+ const iat = payload?.['iat'];
49152
+ if (typeof exp !== 'number')
49153
+ return DEFAULT_TOKEN_MAX_AGE_S;
49154
+ const lifetime = exp - (typeof iat === 'number' ? iat : Math.floor(Date.now() / 1000));
49155
+ if (lifetime <= 0)
49156
+ return DEFAULT_TOKEN_MAX_AGE_S;
49157
+ return Math.min(MAX_TOKEN_MAX_AGE_S, Math.max(60, Math.round(lifetime * 0.9)));
49158
+ }
49159
+ /**
49160
+ * The headers a replay needs, including the ones the query client adds. Header providers can throw
49161
+ * (a secure query's needs an access token), in which case the request is exported without them.
49162
+ */
49163
+ insomniaHeaders(request, args) {
49164
+ try {
49165
+ const headers = request
49166
+ ? request.subtle.resolveHeaders()
49167
+ : typeof args?.headers === 'function'
49168
+ ? args.headers()
49169
+ : args?.headers;
49170
+ return (headers?.keys() ?? []).map((name) => ({ name, value: headers?.getAll(name)?.join(', ') ?? '' }));
49171
+ }
49172
+ catch {
49173
+ return [];
49174
+ }
49175
+ }
49176
+ downloadFile(fileName, content) {
49177
+ const url = URL.createObjectURL(new Blob([content], { type: 'application/json' }));
49178
+ const anchor = this.renderer.createElement('a');
49179
+ anchor.href = url;
49180
+ anchor.download = fileName;
49181
+ anchor.click();
49182
+ URL.revokeObjectURL(url);
49183
+ }
47948
49184
  stepKey(entryId, index) {
47949
49185
  return `${entryId}:${index}`;
47950
49186
  }
@@ -47991,31 +49227,37 @@ class QueryDevtoolsComponent {
47991
49227
  this.inspectActive.set(false);
47992
49228
  }
47993
49229
  selectionKey() {
47994
- return `${this.selectedQueryId()}|${this.stackSelectedQueryId()}|${this.sequenceSelectedQueryId()}`;
47995
- }
47996
- writeToClipboard(html, text) {
49230
+ return [
49231
+ this.selectedQueryId(),
49232
+ this.stackSelectedQueryId(),
49233
+ this.sequenceSelectedQueryId(),
49234
+ this.formSelectedQueryId(),
49235
+ this.timelineSelectedQueryId(),
49236
+ ].join('|');
49237
+ }
49238
+ /** Writes to the clipboard and ticks `copied` on success. `html` is omitted for plain-text payloads. */
49239
+ writeToClipboard(payload, copied) {
47997
49240
  const clipboard = navigator.clipboard;
49241
+ const { text, html } = payload;
47998
49242
  if (!clipboard)
47999
49243
  return;
49244
+ const flag = () => {
49245
+ copied.set(true);
49246
+ this.copiedReset$.next();
49247
+ };
48000
49248
  // Prefer rich HTML (Slack keeps the formatting on paste); fall back to plain text.
48001
- if ('write' in clipboard && typeof ClipboardItem !== 'undefined') {
49249
+ if (html !== undefined && 'write' in clipboard && typeof ClipboardItem !== 'undefined') {
48002
49250
  const item = new ClipboardItem({
48003
49251
  'text/html': new Blob([html], { type: 'text/html' }),
48004
49252
  'text/plain': new Blob([text], { type: 'text/plain' }),
48005
49253
  });
48006
49254
  clipboard
48007
49255
  .write([item])
48008
- .then(() => this.copiedReport.set(true))
48009
- .catch(() => clipboard
48010
- .writeText(text)
48011
- .then(() => this.copiedReport.set(true))
48012
- .catch(() => undefined));
49256
+ .then(flag)
49257
+ .catch(() => clipboard.writeText(text).then(flag).catch(noop));
48013
49258
  return;
48014
49259
  }
48015
- clipboard
48016
- .writeText(text)
48017
- .then(() => this.copiedReport.set(true))
48018
- .catch(() => undefined);
49260
+ clipboard.writeText(text).then(flag).catch(noop);
48019
49261
  }
48020
49262
  responseStatus(query) {
48021
49263
  const event = query.latestHttpEvent();
@@ -48032,28 +49274,66 @@ class QueryDevtoolsComponent {
48032
49274
  // so a row for it would only ever duplicate the next one.
48033
49275
  if (event.type === 'entry-created')
48034
49276
  return;
48035
- const base = { id: this.eventIdCounter++, timestamp: Date.now(), client, type: event.type };
49277
+ const base = {
49278
+ id: this.eventIdCounter++,
49279
+ timestamp: Date.now(),
49280
+ client,
49281
+ type: event.type,
49282
+ cause: null,
49283
+ refreshed: null,
49284
+ };
48036
49285
  // A logout drops every secure entry at once - worth a row of its own, since the requests that
48037
49286
  // disappear from the cache view are otherwise unexplained.
48038
- const item = event.type === 'unbind-all-secure'
48039
- ? { ...base, method: null, url: null, isSecure: true, status: null }
48040
- : {
49287
+ if (event.type === 'unbind-all-secure') {
49288
+ this.pushEventItem({ ...base, method: null, url: null, isSecure: true, status: null, queryId: null });
49289
+ return;
49290
+ }
49291
+ // An invalidation is one row for the whole fan-out: the six queries it re-executed are listed on it
49292
+ // rather than blurring into the six request rows that follow.
49293
+ if (event.type === 'queries-refreshed') {
49294
+ this.pushEventItem({
48041
49295
  ...base,
48042
- method: event.request.method,
48043
- url: event.request.url,
48044
- isSecure: event.isSecure,
48045
- status: event.type === 'request-error' ? event.error.status : null,
48046
- };
49296
+ method: null,
49297
+ url: null,
49298
+ isSecure: false,
49299
+ status: null,
49300
+ queryId: null,
49301
+ cause: event.cause,
49302
+ refreshed: event.requests.map((request) => this.refreshedRequestOf(request)),
49303
+ });
49304
+ return;
49305
+ }
49306
+ this.pushEventItem({
49307
+ ...base,
49308
+ method: event.request.method,
49309
+ url: event.request.url,
49310
+ isSecure: event.isSecure,
49311
+ status: event.type === 'request-error' ? event.error.status : null,
49312
+ // A request is shared by every query on the same cache key, so the first owner is as good
49313
+ // as any - they all show the same response.
49314
+ queryId: this.queryEntries().find((e) => e.handle.subtle.request() === event.request)?.id ?? null,
49315
+ });
49316
+ }
49317
+ refreshedRequestOf(request) {
49318
+ return {
49319
+ queryIds: this.queryEntries()
49320
+ .filter((e) => e.handle.subtle.request() === request)
49321
+ .map((owner) => owner.id),
49322
+ method: request.method,
49323
+ path: this.requestPath(request.url),
49324
+ };
49325
+ }
49326
+ pushEventItem(item) {
48047
49327
  this.eventLog.update((log) => [item, ...log].slice(0, MAX_EVENTS));
48048
49328
  }
48049
49329
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48050
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools-host" }, ngImport: i0, template: "@if (open()) {\n <section\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [style.height.px]=\"panelHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of tabs; track tab.id) {\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n </button>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n \u2316 Inspect\n </button>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-close-panel\"\n type=\"button\"\n title=\"Close devtools ({{ shortcut }})\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n </button>\n </div>\n </header>\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <div class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #clientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n <span class=\"et-query-devtools-count\">{{ filteredQueries().length }}</span>\n }\n </div>\n\n @for (item of filteredQueries(); track item.entry.id) {\n <button\n [class.et-query-devtools-row--active]=\"selectedQueryId() === item.entry.id\"\n (click)=\"selectedQueryId.set(item.entry.id)\"\n class=\"et-query-devtools-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"queryStatus(item.query)\"\n [title]=\"queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">{{ item.entry.meta.route || '\u2014' }}</span>\n @if (isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n </button>\n } @empty {\n @if (inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n }\n </div>\n\n <div class=\"et-query-devtools-detail\">\n @if (selectedQuery(); as sel) {\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n </div>\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">{{ pagedId.route }}</span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.featureTypes?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (feature of entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">{{ stackId.route }}</span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.featureTypes?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (feature of entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n @if (stackSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"stackSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of sequenceEntries(); track entry.id) {\n @let sequence = asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\"\n >step {{ sequence.currentStep() }} / {{ sequence.total }}</span\n >\n </div>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">{{ link.route || 'step ' + (i + 1) }}</span>\n </button>\n <button\n [attr.aria-expanded]=\"isStepExpanded(entry.id, i)\"\n (click)=\"toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (isStepExpanded(entry.id, i)) {\n @let snap = stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n @if (sequenceSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"sequenceSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of authEntries(); track entry.id) {\n @let auth = asAuth(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (authTokenExpiry(auth); as expiry) {\n <dt>Token expires in</dt>\n <dd>{{ expiry }}</dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (authFeatures(auth).length) {\n <dt>Features</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (feature of authFeatures(auth); track feature) {\n <span class=\"et-query-devtools-chip\">{{ feature }}</span>\n }\n </div>\n </dd>\n }\n </dl>\n\n @if (authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n </div>\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of wsEntries(); track entry.id) {\n @let ws = asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <h5 class=\"et-query-devtools-section\">Messages ({{ ws.messages().length }})</h5>\n @for (message of ws.messages(); track message.id) {\n <div class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n <et-query-devtools-json [value]=\"message.data\" />\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No messages received yet.</p>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n </div>\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <div class=\"et-query-devtools-scroll\">\n @for (client of cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.entries.length }} entries</span>\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ persistedCount(client.client!) }} on disk</span>\n <button (click)=\"clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n </div>\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (cacheEntry of client.entries; track cacheEntry.key) {\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>{{ cacheEntry.consumerCount }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ cacheFreshness(cacheEntry) }}</td>\n <td>{{ cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ cachePersistence(cacheEntry) }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n <button (click)=\"refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"7\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n </div>\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <span class=\"et-query-devtools-count\">{{ eventLog().length }} events</span>\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n </tr>\n </thead>\n <tbody>\n @for (event of eventLog(); track event.id) {\n <tr [attr.data-status]=\"event.type === 'request-error' ? 'error' : 'success'\">\n <td>{{ formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.url; as url) {\n <td [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">No events captured yet.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n </div>\n\n <!-- Reusable inner-query list (stacks) \u2014 clicking opens the split-view drawer, not a tab switch. -->\n <ng-template #innerQueriesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">{{ link.route || link.query.id() || 'query ' + ($index + 1) }}</span>\n </button>\n }\n </div>\n </ng-template>\n\n <!-- Reusable query detail \u2014 used by the Queries tab and the stacks split-view drawer. -->\n <ng-template #queryDetailTpl let-sel=\"sel\">\n <div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ sel.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">{{ sel.entry.meta.route || '\u2014' }}</span>\n </div>\n\n <div class=\"et-query-devtools-actions\">\n <span class=\"et-query-devtools-actions-label\">Run</span>\n <button (click)=\"executeQuery(sel.query, false)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"executeQuery(sel.query, true)\" class=\"et-query-devtools-btn\" type=\"button\">Cached</button>\n <button (click)=\"resetQuery(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Reset</button>\n <button (click)=\"copyReport(sel.entry, sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ copiedReport() ? '\u2713 Copied' : 'Copy report' }}\n </button>\n <span class=\"et-query-devtools-actions-label\">Edit</span>\n <button (click)=\"openResponseEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Response</button>\n <button (click)=\"openArgsEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Replay args</button>\n <span class=\"et-query-devtools-actions-label\">Force</span>\n <button (click)=\"forceLoading(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Loading</button>\n <button (click)=\"forceError(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Error</button>\n <button (click)=\"forceEmpty(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Empty</button>\n <button (click)=\"clearForced(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n\n @if (editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"responseDraft()\"\n (input)=\"responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyResponse(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"argsDraft()\"\n (input)=\"argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyArgs(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.entry.meta.clientBaseUrl || sel.entry.meta.clientName || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ queryStatus(sel.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ formatTime(sel.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ sel.query.triggeredBy() || 'user' }}</dd>\n @if (sel.entry.meta.featureTypes?.length) {\n <dt>Features</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (feature of sel.entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n </dd>\n }\n </dl>\n\n @if (sel.entry.meta.gqlQuery; as gqlQuery) {\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <pre class=\"et-query-devtools-code\">{{ gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"jsonSearch()\"\n (input)=\"jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"sel.query.args()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"args\"\n />\n\n @if (sel.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <h5 class=\"et-query-devtools-section\">Response</h5>\n <et-query-devtools-json\n [value]=\"sel.query.response()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"response\"\n />\n }\n </ng-template>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.4rem;flex:0 0 auto}.et-query-devtools-inspect-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.4rem .8rem;font:inherit}.et-query-devtools-inspect-btn:hover{color:var(--_et-qdt-fg)}.et-query-devtools-inspect-btn--active{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-close-panel{appearance:none;display:inline-flex;align-items:center;gap:.6rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:var(--_et-qdt-hover);color:var(--_et-qdt-fg);cursor:pointer;padding:.4rem .6rem .4rem .8rem;font:inherit}.et-query-devtools-close-panel:hover{border-color:var(--_et-qdt-error);color:var(--_et-qdt-error)}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 34%;min-inline-size:22rem;max-inline-size:40rem;overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-drawer{flex:0 0 42%;min-inline-size:26rem;max-inline-size:48rem;overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:center;gap:.8rem;inline-size:100%;text-align:start;appearance:none;border:none;border-block-end:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-fg);padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;margin-block-end:.8rem}.et-query-devtools-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.3rem;background:transparent;color:var(--_et-qdt-fg);padding:.25rem .55rem;font-size:1.15rem;line-height:1.4;cursor:pointer}.et-query-devtools-btn:hover{background:var(--_et-qdt-hover)}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;margin-block-end:.4rem}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-actions-label{color:var(--_et-qdt-subtle);align-self:center;text-transform:uppercase;font-size:1rem;letter-spacing:.05em}.et-query-devtools-actions-label:not(:first-child){margin-inline-start:.7rem}.et-query-devtools-cell-actions{display:flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:0 1 20rem;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon,.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:.2rem}.et-query-devtools-json-copy{appearance:none;flex:0 0 auto;border:none;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit}.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){color:var(--_et-qdt-fg)}.et-query-devtools-json-row:hover .et-query-devtools-json-copy,.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "path", "expandedPaths", "collapsedPaths", "toggleFn"] }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", outputs: ["openChange"] }], encapsulation: i0.ViewEncapsulation.None }); }
49330
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools-host" }, ngImport: i0, template: "@if (open()) {\n <section\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [style.height.px]=\"panelHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of tabs; track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-close-panel\"\n type=\"button\"\n title=\"Close devtools ({{ shortcut }})\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n </button>\n </div>\n </header>\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <div class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #clientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n \u2913 Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"queryFilter()\"\n (input)=\"queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @for (facet of facets; track facet.id) {\n @let count = facetCounts()[facet.id];\n @let isActive = queryFacets().has(facet.id);\n <button\n [class.et-query-devtools-facet--active]=\"isActive\"\n [attr.aria-pressed]=\"isActive\"\n [disabled]=\"!count && !isActive\"\n (click)=\"toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ count }}</span>\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @for (item of filteredQueries(); track item.entry.id) {\n <button\n [class.et-query-devtools-row--active]=\"selectedQueryId() === item.entry.id\"\n (click)=\"selectedQueryId.set(item.entry.id)\"\n class=\"et-query-devtools-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"queryStatus(item.query)\"\n [title]=\"queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: routeSegments(item.entry, item.query), fallback: '\u2014' }\n \"\n />\n </span>\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n </button>\n } @empty {\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.\n </p>\n } @else if (inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n }\n </div>\n\n <div class=\"et-query-devtools-detail\">\n @if (selectedQuery(); as sel) {\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n </div>\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: pagedId.segments, fallback: '\u2014' }\"\n />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRowTpl; context: { activity: stackActivity(paged) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: stackId.segments, fallback: '\u2014' }\"\n />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRowTpl; context: { activity: stackActivity(stack) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n @if (stackSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"stackSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of sequenceEntries(); track entry.id) {\n @let sequence = asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\"\n >step {{ sequence.currentStep() }} / {{ sequence.total }}</span\n >\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <ng-container\n *ngTemplateOutlet=\"activityRowTpl; context: { activity: sequenceActivity(sequence) }\"\n />\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: link.segments, fallback: 'step ' + (i + 1) }\n \"\n />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"isStepExpanded(entry.id, i)\"\n (click)=\"toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (isStepExpanded(entry.id, i)) {\n @let snap = stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n @if (sequenceSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"sequenceSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of formEntries(); track entry.id) {\n @let form = asForm(entry);\n @let expanded = selectedFormId() === entry.id;\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"selectedFormId.set(expanded ? null : entry.id)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-caret\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n <strong>{{ entry.meta.name || 'query form' }}</strong>\n </button>\n @if (form.activeFilterCount(); as count) {\n <span class=\"et-query-devtools-chip\">{{ count }} active</span>\n } @else {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">at defaults</span>\n }\n @if (form.isCommitPending()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">debouncing</span>\n }\n @if (!form.isObserving()) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"observe() has not been called, so nothing syncs with the URL\"\n >\n not observing\n </span>\n }\n <button\n [disabled]=\"form.isAtDefaults()\"\n (click)=\"form.resetAll()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2 Reset all\n </button>\n </div>\n\n @let driven = queriesDrivenByForm(entry);\n <dl class=\"et-query-devtools-meta\">\n <dt>Drives</dt>\n <dd>\n @if (driven.length) {\n <ng-container *ngTemplateOutlet=\"formDrivesTpl; context: { links: driven }\" />\n } @else {\n <span class=\"et-query-devtools-hint\">\n No query has read this form's value yet. A query picks it up the first time its\n <code>withArgs</code> reads <code>value()</code>.\n </span>\n }\n </dd>\n </dl>\n\n @if (expanded) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Value</th>\n <th>Default</th>\n <th>URL param</th>\n <th>Rules</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (field of form.fields(); track field.key) {\n <tr [attr.data-status]=\"field.isDefault ? null : 'success'\">\n <td class=\"et-query-devtools-mono\">{{ field.key }}</td>\n <td class=\"et-query-devtools-mono\">\n {{ inlineValue(field.value) }}\n <!-- The live value only differs while a debounce is pending, which is exactly\n when \"the input says X but the query asked for Y\" needs explaining. -->\n @if (field.liveValue !== field.value) {\n <span class=\"et-query-devtools-stat-sub\"\n >typing {{ inlineValue(field.liveValue) }}</span\n >\n }\n </td>\n <td class=\"et-query-devtools-mono\">{{ inlineValue(field.defaultValue) }}</td>\n <td class=\"et-query-devtools-mono\">\n @if (field.queryParam === undefined) {\n <span class=\"et-query-devtools-hint\">\u2014</span>\n } @else {\n {{ field.paramKey }}={{ inlineValue(field.queryParam) }}\n }\n </td>\n <td>\n <div class=\"et-query-devtools-chips\">\n @if (field.debounceMs !== null) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ field.debounceMs }}ms\n </span>\n }\n @if (field.isResetBy.length) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Reset to its default whenever one of these changes\"\n >\n reset by {{ field.isResetBy.join(', ') }}\n </span>\n }\n @if (!field.countsAsFilter) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Not counted by activeFilterCount\"\n >\n not a filter\n </span>\n }\n </div>\n </td>\n <td>\n <button\n [disabled]=\"field.isDefault\"\n (click)=\"form.resetField(field.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <h4 class=\"et-query-devtools-section\">Committed value</h4>\n <et-query-devtools-json [value]=\"form.value()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n No query forms registered. A form created with <code>createQueryForm()</code> registers itself.\n </p>\n }\n </div>\n\n @if (formSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"formSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of authEntries(); track entry.id) {\n @let auth = asAuth(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (authTokenExpiry(auth); as expiry) {\n <dt>Token expires in</dt>\n <dd>{{ expiry }}</dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n </dd>\n }\n </dl>\n\n @if (authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n </div>\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of wsEntries(); track entry.id) {\n @let ws = asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <h5 class=\"et-query-devtools-section\">Messages ({{ ws.messages().length }})</h5>\n @for (message of ws.messages(); track message.id) {\n <div class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n <et-query-devtools-json [value]=\"message.data\" />\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No messages received yet.</p>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n </div>\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <div class=\"et-query-devtools-scroll\">\n @for (client of cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.entries.length }} entries</span>\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ persistedCount(client.client!) }} on disk</span>\n <button (click)=\"clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n </div>\n @if (clientFeatures(client.client); as features) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features }\" />\n }\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (cacheEntry of client.entries; track cacheEntry.key) {\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>{{ cacheEntry.consumerCount }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ cacheFreshness(cacheEntry) }}</td>\n <td>{{ cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ cachePersistence(cacheEntry) }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n <button (click)=\"refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"7\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n </div>\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n @let tl = timeline();\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #timelineClientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(timelineClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n <span class=\"et-query-devtools-count\">\n {{ tl.rows.length }} run{{ tl.rows.length === 1 ? '' : 's' }}\n @if (tl.hidden) {\n \u00B7 {{ tl.hidden }} older not shown\n }\n @if (tl.rows.length) {\n \u00B7 over {{ formatDuration(tl.windowMs) }} from {{ formatTime(tl.startedAt) }}\n }\n </span>\n <button\n [disabled]=\"!tl.rows.length\"\n (click)=\"resetTimeline()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters of every listed query\"\n >\n \u27F2 Reset\n </button>\n </div>\n </div>\n\n @if (tl.rows.length) {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-timeline\">\n <div class=\"et-query-devtools-timeline-axis\">\n <span class=\"et-query-devtools-timeline-ticks\">\n @for (tick of timelineTicks(); track tick.pct) {\n <span [style.left.%]=\"tick.pct\" class=\"et-query-devtools-timeline-tick\">{{\n tick.label\n }}</span>\n }\n </span>\n </div>\n\n @for (row of tl.rows; track row.key) {\n <button\n [class.et-query-devtools-timeline-row--active]=\"timelineSelectedQueryId() === row.entryId\"\n (click)=\"selectTimelineRow(row)\"\n class=\"et-query-devtools-timeline-row\"\n type=\"button\"\n title=\"{{ row.method }} {{ row.path }} \u00B7 run {{ row.run.index }} \u00B7 {{ row.run.status }}\"\n >\n <span class=\"et-query-devtools-timeline-label\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <span class=\"et-query-devtools-url\">{{ row.path }}</span>\n </span>\n <span class=\"et-query-devtools-timeline-track\">\n <span\n [attr.data-status]=\"runStatus(row.run)\"\n [style.left.%]=\"row.leftPct\"\n [style.width.%]=\"row.widthPct\"\n class=\"et-query-devtools-timeline-bar\"\n ></span>\n </span>\n <span class=\"et-query-devtools-timeline-duration\">\n @if (row.durationMs === null) {\n in flight\n } @else {\n {{ formatDuration(row.durationMs) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-size\">\n @if (row.run.receivedBytes) {\n {{ formatBytes(row.run.receivedBytes) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-flags\">\n @if (row.run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n row.run.attempts\n }} attempts - so this bar is mostly retry backoff, not one slow round trip\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ row.run.attempts }}\n </span>\n }\n @if (!row.run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (row.run.status === 'aborted') {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The query requested again before this run's response arrived\"\n >\n aborted\n </span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n } @else {\n <p class=\"et-query-devtools-empty\">\n No requests recorded yet. Every request a listed query makes shows up here as a bar.\n </p>\n }\n </div>\n\n @if (timelineSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"timelineSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <span class=\"et-query-devtools-count\">{{ eventLog().length }} events</span>\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n </tr>\n </thead>\n <tbody>\n @for (event of eventLog(); track event.id) {\n <tr\n [attr.data-status]=\"\n event.type === 'request-error' ? 'error' : event.type === 'queries-refreshed' ? null : 'success'\n \"\n >\n <td>{{ formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (refreshed.queryIds[0]; as queryId) {\n <button\n (click)=\"selectQuery(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (event.queryId) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">No events captured yet.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see\n them exactly as they would see a real one. Unlike the rest of the panel, what you arm here is not\n persisted - a page reload disarms every client.\n </p>\n </div>\n\n @for (client of faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm\n </button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n </div>\n }\n }\n </div>\n\n <!-- A route with its path params and query string picked out from the static path. The segments must\n stay element-only: a text node between them would render as a space inside the route. -->\n <ng-template #routeTpl let-segments=\"segments\" let-fallback=\"fallback\">\n @for (segment of segments; track $index) {\n <span [class]=\"'et-query-devtools-route-' + segment.kind\" [attr.title]=\"segment.name\">{{ segment.text }}</span>\n } @empty {\n {{ fallback }}\n }\n </ng-template>\n\n <!-- Reusable dt/dd pair for a card's `et-query-devtools-meta` list: what its queries ran and moved. -->\n <ng-template #activityRowTpl let-activity=\"activity\">\n <dt>Traffic</dt>\n <dd>\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </ng-template>\n\n <!-- Reusable feature list: the feature's name, followed by the options it was configured with. -->\n <ng-template #featuresTpl let-features=\"features\">\n <ul class=\"et-query-devtools-features\">\n @for (feature of features; track feature.type) {\n <li class=\"et-query-devtools-feature\">\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature.type) }}</span>\n <!-- Always rendered: the list is one grid, so a feature without options still needs its cell\n for the next row's chip to stay in the same column. -->\n <span class=\"et-query-devtools-feature-details\">\n @for (detail of feature.details; track detail.label) {\n <span class=\"et-query-devtools-feature-detail\">\n <span class=\"et-query-devtools-feature-label\">{{ detail.label }}</span>\n <span class=\"et-query-devtools-feature-value\">{{ detail.value }}</span>\n </span>\n }\n </span>\n </li>\n }\n </ul>\n </ng-template>\n\n <!-- Reusable inner-query list (stacks) \u2014 clicking opens the split-view drawer, not a tab switch. -->\n <ng-template #innerQueriesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: link.segments, fallback: link.query.id() || 'query ' + ($index + 1) }\n \"\n />\n </span>\n @let linkStats = linkActivity(link);\n @if (linkStats.hasActivity) {\n <span class=\"et-query-devtools-traffic\">\n {{ formatTransferred(linkStats.stats.receivedBytes, linkStats.stats.hasEstimatedBytes) }}\n </span>\n }\n </button>\n }\n </div>\n </ng-template>\n\n <!-- The queries a form feeds \u2014 clicking one opens the split-view drawer, not a tab switch. -->\n <ng-template #formDrivesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track link.id) {\n <button\n [class.et-query-devtools-inner-row--active]=\"formSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"formSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: link.segments, fallback: link.query.id() || 'query' }\"\n />\n </span>\n </button>\n }\n </div>\n </ng-template>\n\n <!-- Reusable query detail \u2014 used by the Queries tab and by every split-view drawer. -->\n <ng-template #queryDetailTpl let-sel=\"sel\">\n <div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ sel.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: routeSegments(sel.entry, sel.query), fallback: '\u2014' }\"\n />\n </span>\n </div>\n\n @if (requestProgress(sel.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ retryCause(retry.status) }} \u00B7 backing off {{ formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ formatPercent(progress.percentage) }} \u00B7 {{ formatBytes(progress.loaded) }} of\n {{ formatBytes(progress.total) }}\n @if (progress.remainingTime !== null) {\n \u00B7 {{ formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-actions\">\n <span class=\"et-query-devtools-actions-label\">Run</span>\n <button (click)=\"executeQuery(sel.query, false)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"executeQuery(sel.query, true)\" class=\"et-query-devtools-btn\" type=\"button\">Cached</button>\n <button (click)=\"resetQuery(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Reset</button>\n <button (click)=\"copyReport(sel.entry, sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ copiedReport() ? '\u2713 Copied' : 'Copy report' }}\n </button>\n <button\n (click)=\"copyInsomniaRequest(sel.entry, sel.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy as an Insomnia collection (Import \u2192 From Clipboard). Includes the request headers, and the token refresh for a secure query.\"\n >\n {{ copiedInsomnia() ? '\u2713 Copied' : 'Insomnia' }}\n </button>\n <span class=\"et-query-devtools-actions-label\">Edit</span>\n <button (click)=\"openResponseEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Response</button>\n <button (click)=\"openArgsEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Replay args</button>\n <span class=\"et-query-devtools-actions-label\">Force</span>\n <button (click)=\"forceLoading(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Loading</button>\n <button (click)=\"forceError(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Error</button>\n <button (click)=\"forceEmpty(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Empty</button>\n <button (click)=\"clearForced(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n\n @if (editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"editorSeed\"\n (input)=\"responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyResponse(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"editorSeed\"\n (input)=\"argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyArgs(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n }\n\n @let detailRuns = queryRuns(sel.entry);\n <nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"detailTab() === tab.id\"\n [attr.aria-selected]=\"detailTab() === tab.id\"\n (click)=\"detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && sel.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n </nav>\n\n @switch (detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ sel.entry.meta.clientBaseUrl || sel.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ requestUrl(sel.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ queryStatus(sel.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ formatTime(sel.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ sel.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = refreshesFor(sel.entry.id);\n @if (refreshes.length) {\n <dt\n title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\"\n >\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = formsDrivingQuery(sel.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (sel.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: sel.entry.meta.features }\" />\n </dd>\n }\n </dl>\n\n @let activity = queryActivity(sel.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"resetStats(sel.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >{{ formatBytes(activity.avgResponseBytes) }} per response</span\n >\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"resetStats(sel.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Diff</th>\n </tr>\n </thead>\n <tbody>\n @for (run of detailRuns; track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n </td>\n <td>{{ formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n @if (canDiffRun(sel.entry, run)) {\n <button\n [class.et-query-devtools-btn--active]=\"diffRunIndex() === run.index\"\n (click)=\"toggleRunDiff(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare this response against the previous one still held\"\n >\n Diff\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">nothing earlier to diff</span>\n } @else if (run.status === 'success') {\n <span class=\"et-query-devtools-count\">body no longer held</span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n @if (responseDiff(sel.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-count\">\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (sel.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"jsonSearch()\"\n (input)=\"jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"queryArgs(sel.query)\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"args\"\n />\n\n @if (sel.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <h5 class=\"et-query-devtools-section\">Response</h5>\n <et-query-devtools-json\n [value]=\"sel.query.response()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"response\"\n />\n }\n }\n }\n </ng-template>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;margin-inline-start:.5rem;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.4rem;flex:0 0 auto}.et-query-devtools-inspect-btn{appearance:none;position:relative;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.4rem .8rem;font:inherit;transition:color .12s ease,background-color .12s ease,border-color .12s ease}.et-query-devtools-inspect-btn:hover{color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-inspect-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:2px}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-inspect-btn--active,.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:.6rem;pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn{transition:none}.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-close-panel{appearance:none;display:inline-flex;align-items:center;gap:.6rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:var(--_et-qdt-hover);color:var(--_et-qdt-fg);cursor:pointer;padding:.4rem .6rem .4rem .8rem;font:inherit}.et-query-devtools-close-panel:hover{border-color:var(--_et-qdt-error);color:var(--_et-qdt-error)}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 34%;min-inline-size:22rem;max-inline-size:40rem;overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-drawer{flex:0 0 42%;min-inline-size:26rem;max-inline-size:48rem;overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.8rem;padding:.8rem;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.8rem}.et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:center;gap:.8rem;inline-size:100%;min-block-size:3.6rem;text-align:start;appearance:none;border:none;border-block-end:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-fg);padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;margin-block-end:.8rem}.et-query-devtools-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.3rem;background:transparent;color:var(--_et-qdt-fg);padding:.25rem .55rem;font-size:1.15rem;line-height:1.4;cursor:pointer}.et-query-devtools-btn:hover{background:var(--_et-qdt-hover)}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-traffic{margin-inline-start:auto;flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;margin-block-end:.4rem}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-features{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:.4rem .8rem;margin:0 0 .8rem;padding:0;list-style:none}.et-query-devtools-meta .et-query-devtools-features{margin-block-end:0}.et-query-devtools-feature{display:contents}.et-query-devtools-feature>.et-query-devtools-chip{justify-self:start}.et-query-devtools-feature-details{display:flex;flex-wrap:wrap;gap:.3rem}.et-query-devtools-feature-detail{display:inline-flex;align-items:baseline;gap:.4rem;padding:.2rem .5rem;border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);font-size:1.1rem}.et-query-devtools-feature-label{color:var(--_et-qdt-muted)}.et-query-devtools-feature-value{color:var(--_et-qdt-fg);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}.et-query-devtools-caret{display:inline-block;width:1.2rem;color:var(--_et-qdt-muted)}.et-query-devtools-facet{appearance:none;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-actions-label{color:var(--_et-qdt-subtle);align-self:center;text-transform:uppercase;font-size:1rem;letter-spacing:.05em}.et-query-devtools-actions-label:not(:first-child){margin-inline-start:.7rem}.et-query-devtools-cell-actions{display:flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:0 1 20rem;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:hover .et-query-devtools-url,.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon,.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-chunk{color:var(--_et-qdt-fg);opacity:.75;font-variant-numeric:tabular-nums}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:.2rem}.et-query-devtools-json-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}.et-query-devtools-json-row:hover .et-query-devtools-json-copy,.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-timeline{display:grid;grid-template-columns:minmax(10rem,26rem) minmax(14rem,1fr) max-content max-content max-content;gap:.1rem .8rem;padding-inline:.4rem 1rem}.et-query-devtools-timeline-axis,.et-query-devtools-timeline-row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center}.et-query-devtools-timeline-axis{position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border);padding-block-end:.4rem;margin-block-end:.4rem}.et-query-devtools-timeline-ticks{grid-column:2;position:relative;block-size:1.5rem}.et-query-devtools-timeline-tick{position:absolute;inset-block-start:0;transform:translate(-50%);color:var(--_et-qdt-subtle);font-size:1.1rem;white-space:nowrap}.et-query-devtools-timeline-tick:first-child{transform:none}.et-query-devtools-timeline-tick:last-child{transform:translate(-100%)}.et-query-devtools-timeline-row{appearance:none;border:0;background:transparent;color:inherit;font:inherit;text-align:start;padding:.15rem 0;cursor:pointer}.et-query-devtools-timeline-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-timeline-row--active{background:var(--_et-qdt-active)}.et-query-devtools-timeline-label{display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;overflow:hidden}.et-query-devtools-timeline-label .et-query-devtools-url{max-inline-size:none;margin-inline-start:0}.et-query-devtools-timeline-track{position:relative;block-size:1.8rem;border-radius:.2rem;overflow:hidden;background-color:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);background-image:repeating-linear-gradient(to right,var(--_et-qdt-border) 0 1px,transparent 1px 25%)}.et-query-devtools-timeline-bar{position:absolute;inset-block:.4rem;min-inline-size:.2rem;border-radius:.2rem;background:var(--_et-qdt-subtle)}.et-query-devtools-timeline-bar[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-timeline-bar[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-timeline-bar[data-status=loading]{background:var(--_et-qdt-loading)}.et-query-devtools-timeline-bar[data-status=aborted]{min-inline-size:.5rem;background:color-mix(in srgb,var(--_et-qdt-subtle) 20%,transparent);outline:1px solid color-mix(in srgb,var(--_et-qdt-subtle) 70%,transparent);outline-offset:-1px}.et-query-devtools-timeline-duration,.et-query-devtools-timeline-size{text-align:end;color:var(--_et-qdt-muted);font-size:1.1rem;font-variant-numeric:tabular-nums;white-space:nowrap}.et-query-devtools-timeline-duration{color:var(--_et-qdt-fg)}.et-query-devtools-timeline-flags{display:flex;align-items:center;gap:.3rem;white-space:nowrap}.et-query-devtools-timeline-flags .et-query-devtools-chip{padding:0 .4rem;border-color:transparent;border-radius:.3rem;background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:flex;align-items:center;gap:.5rem}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn"] }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", outputs: ["openChange"] }], encapsulation: i0.ViewEncapsulation.None }); }
48051
49331
  }
48052
49332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
48053
49333
  type: Component,
48054
49334
  args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsJsonComponent, QueryDevtoolsToggleComponent], host: {
48055
49335
  class: 'et-query-devtools-host',
48056
- }, template: "@if (open()) {\n <section\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [style.height.px]=\"panelHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of tabs; track tab.id) {\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n </button>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n \u2316 Inspect\n </button>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-close-panel\"\n type=\"button\"\n title=\"Close devtools ({{ shortcut }})\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n </button>\n </div>\n </header>\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <div class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #clientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n <span class=\"et-query-devtools-count\">{{ filteredQueries().length }}</span>\n }\n </div>\n\n @for (item of filteredQueries(); track item.entry.id) {\n <button\n [class.et-query-devtools-row--active]=\"selectedQueryId() === item.entry.id\"\n (click)=\"selectedQueryId.set(item.entry.id)\"\n class=\"et-query-devtools-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"queryStatus(item.query)\"\n [title]=\"queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">{{ item.entry.meta.route || '\u2014' }}</span>\n @if (isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n </button>\n } @empty {\n @if (inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n }\n </div>\n\n <div class=\"et-query-devtools-detail\">\n @if (selectedQuery(); as sel) {\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n </div>\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">{{ pagedId.route }}</span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.featureTypes?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (feature of entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">{{ stackId.route }}</span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.featureTypes?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (feature of entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n @if (stackSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"stackSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of sequenceEntries(); track entry.id) {\n @let sequence = asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\"\n >step {{ sequence.currentStep() }} / {{ sequence.total }}</span\n >\n </div>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">{{ link.route || 'step ' + (i + 1) }}</span>\n </button>\n <button\n [attr.aria-expanded]=\"isStepExpanded(entry.id, i)\"\n (click)=\"toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (isStepExpanded(entry.id, i)) {\n @let snap = stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n @if (sequenceSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"sequenceSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of authEntries(); track entry.id) {\n @let auth = asAuth(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (authTokenExpiry(auth); as expiry) {\n <dt>Token expires in</dt>\n <dd>{{ expiry }}</dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (authFeatures(auth).length) {\n <dt>Features</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (feature of authFeatures(auth); track feature) {\n <span class=\"et-query-devtools-chip\">{{ feature }}</span>\n }\n </div>\n </dd>\n }\n </dl>\n\n @if (authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n </div>\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of wsEntries(); track entry.id) {\n @let ws = asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <h5 class=\"et-query-devtools-section\">Messages ({{ ws.messages().length }})</h5>\n @for (message of ws.messages(); track message.id) {\n <div class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n <et-query-devtools-json [value]=\"message.data\" />\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No messages received yet.</p>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n </div>\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <div class=\"et-query-devtools-scroll\">\n @for (client of cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.entries.length }} entries</span>\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ persistedCount(client.client!) }} on disk</span>\n <button (click)=\"clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n </div>\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (cacheEntry of client.entries; track cacheEntry.key) {\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>{{ cacheEntry.consumerCount }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ cacheFreshness(cacheEntry) }}</td>\n <td>{{ cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ cachePersistence(cacheEntry) }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n <button (click)=\"refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"7\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n </div>\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <span class=\"et-query-devtools-count\">{{ eventLog().length }} events</span>\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n </tr>\n </thead>\n <tbody>\n @for (event of eventLog(); track event.id) {\n <tr [attr.data-status]=\"event.type === 'request-error' ? 'error' : 'success'\">\n <td>{{ formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.url; as url) {\n <td [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">No events captured yet.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n </div>\n\n <!-- Reusable inner-query list (stacks) \u2014 clicking opens the split-view drawer, not a tab switch. -->\n <ng-template #innerQueriesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">{{ link.route || link.query.id() || 'query ' + ($index + 1) }}</span>\n </button>\n }\n </div>\n </ng-template>\n\n <!-- Reusable query detail \u2014 used by the Queries tab and the stacks split-view drawer. -->\n <ng-template #queryDetailTpl let-sel=\"sel\">\n <div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ sel.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">{{ sel.entry.meta.route || '\u2014' }}</span>\n </div>\n\n <div class=\"et-query-devtools-actions\">\n <span class=\"et-query-devtools-actions-label\">Run</span>\n <button (click)=\"executeQuery(sel.query, false)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"executeQuery(sel.query, true)\" class=\"et-query-devtools-btn\" type=\"button\">Cached</button>\n <button (click)=\"resetQuery(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Reset</button>\n <button (click)=\"copyReport(sel.entry, sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ copiedReport() ? '\u2713 Copied' : 'Copy report' }}\n </button>\n <span class=\"et-query-devtools-actions-label\">Edit</span>\n <button (click)=\"openResponseEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Response</button>\n <button (click)=\"openArgsEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Replay args</button>\n <span class=\"et-query-devtools-actions-label\">Force</span>\n <button (click)=\"forceLoading(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Loading</button>\n <button (click)=\"forceError(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Error</button>\n <button (click)=\"forceEmpty(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Empty</button>\n <button (click)=\"clearForced(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n\n @if (editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"responseDraft()\"\n (input)=\"responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyResponse(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"argsDraft()\"\n (input)=\"argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyArgs(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.entry.meta.clientBaseUrl || sel.entry.meta.clientName || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ queryStatus(sel.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ formatTime(sel.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ sel.query.triggeredBy() || 'user' }}</dd>\n @if (sel.entry.meta.featureTypes?.length) {\n <dt>Features</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (feature of sel.entry.meta.featureTypes; track feature) {\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature) }}</span>\n }\n </div>\n </dd>\n }\n </dl>\n\n @if (sel.entry.meta.gqlQuery; as gqlQuery) {\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <pre class=\"et-query-devtools-code\">{{ gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"jsonSearch()\"\n (input)=\"jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"sel.query.args()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"args\"\n />\n\n @if (sel.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <h5 class=\"et-query-devtools-section\">Response</h5>\n <et-query-devtools-json\n [value]=\"sel.query.response()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"response\"\n />\n }\n </ng-template>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.4rem;flex:0 0 auto}.et-query-devtools-inspect-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.4rem .8rem;font:inherit}.et-query-devtools-inspect-btn:hover{color:var(--_et-qdt-fg)}.et-query-devtools-inspect-btn--active{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-close-panel{appearance:none;display:inline-flex;align-items:center;gap:.6rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:var(--_et-qdt-hover);color:var(--_et-qdt-fg);cursor:pointer;padding:.4rem .6rem .4rem .8rem;font:inherit}.et-query-devtools-close-panel:hover{border-color:var(--_et-qdt-error);color:var(--_et-qdt-error)}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 34%;min-inline-size:22rem;max-inline-size:40rem;overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-drawer{flex:0 0 42%;min-inline-size:26rem;max-inline-size:48rem;overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:center;gap:.8rem;inline-size:100%;text-align:start;appearance:none;border:none;border-block-end:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-fg);padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;margin-block-end:.8rem}.et-query-devtools-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.3rem;background:transparent;color:var(--_et-qdt-fg);padding:.25rem .55rem;font-size:1.15rem;line-height:1.4;cursor:pointer}.et-query-devtools-btn:hover{background:var(--_et-qdt-hover)}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;margin-block-end:.4rem}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-actions-label{color:var(--_et-qdt-subtle);align-self:center;text-transform:uppercase;font-size:1rem;letter-spacing:.05em}.et-query-devtools-actions-label:not(:first-child){margin-inline-start:.7rem}.et-query-devtools-cell-actions{display:flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:0 1 20rem;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon,.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:.2rem}.et-query-devtools-json-copy{appearance:none;flex:0 0 auto;border:none;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit}.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){color:var(--_et-qdt-fg)}.et-query-devtools-json-row:hover .et-query-devtools-json-copy,.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}}\n"] }]
49336
+ }, template: "@if (open()) {\n <section\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [style.height.px]=\"panelHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of tabs; track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-close-panel\"\n type=\"button\"\n title=\"Close devtools ({{ shortcut }})\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n </button>\n </div>\n </header>\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <div class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #clientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n \u2913 Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"queryFilter()\"\n (input)=\"queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @for (facet of facets; track facet.id) {\n @let count = facetCounts()[facet.id];\n @let isActive = queryFacets().has(facet.id);\n <button\n [class.et-query-devtools-facet--active]=\"isActive\"\n [attr.aria-pressed]=\"isActive\"\n [disabled]=\"!count && !isActive\"\n (click)=\"toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ count }}</span>\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @for (item of filteredQueries(); track item.entry.id) {\n <button\n [class.et-query-devtools-row--active]=\"selectedQueryId() === item.entry.id\"\n (click)=\"selectedQueryId.set(item.entry.id)\"\n class=\"et-query-devtools-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"queryStatus(item.query)\"\n [title]=\"queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: routeSegments(item.entry, item.query), fallback: '\u2014' }\n \"\n />\n </span>\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n </button>\n } @empty {\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.\n </p>\n } @else if (inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n }\n </div>\n\n <div class=\"et-query-devtools-detail\">\n @if (selectedQuery(); as sel) {\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n </div>\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: pagedId.segments, fallback: '\u2014' }\"\n />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRowTpl; context: { activity: stackActivity(paged) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: stackId.segments, fallback: '\u2014' }\"\n />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRowTpl; context: { activity: stackActivity(stack) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueriesTpl; context: { links: queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n @if (stackSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"stackSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of sequenceEntries(); track entry.id) {\n @let sequence = asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\"\n >step {{ sequence.currentStep() }} / {{ sequence.total }}</span\n >\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <ng-container\n *ngTemplateOutlet=\"activityRowTpl; context: { activity: sequenceActivity(sequence) }\"\n />\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: link.segments, fallback: 'step ' + (i + 1) }\n \"\n />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"isStepExpanded(entry.id, i)\"\n (click)=\"toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (isStepExpanded(entry.id, i)) {\n @let snap = stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n @if (sequenceSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"sequenceSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of formEntries(); track entry.id) {\n @let form = asForm(entry);\n @let expanded = selectedFormId() === entry.id;\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"selectedFormId.set(expanded ? null : entry.id)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-caret\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n <strong>{{ entry.meta.name || 'query form' }}</strong>\n </button>\n @if (form.activeFilterCount(); as count) {\n <span class=\"et-query-devtools-chip\">{{ count }} active</span>\n } @else {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">at defaults</span>\n }\n @if (form.isCommitPending()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">debouncing</span>\n }\n @if (!form.isObserving()) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"observe() has not been called, so nothing syncs with the URL\"\n >\n not observing\n </span>\n }\n <button\n [disabled]=\"form.isAtDefaults()\"\n (click)=\"form.resetAll()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2 Reset all\n </button>\n </div>\n\n @let driven = queriesDrivenByForm(entry);\n <dl class=\"et-query-devtools-meta\">\n <dt>Drives</dt>\n <dd>\n @if (driven.length) {\n <ng-container *ngTemplateOutlet=\"formDrivesTpl; context: { links: driven }\" />\n } @else {\n <span class=\"et-query-devtools-hint\">\n No query has read this form's value yet. A query picks it up the first time its\n <code>withArgs</code> reads <code>value()</code>.\n </span>\n }\n </dd>\n </dl>\n\n @if (expanded) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Value</th>\n <th>Default</th>\n <th>URL param</th>\n <th>Rules</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (field of form.fields(); track field.key) {\n <tr [attr.data-status]=\"field.isDefault ? null : 'success'\">\n <td class=\"et-query-devtools-mono\">{{ field.key }}</td>\n <td class=\"et-query-devtools-mono\">\n {{ inlineValue(field.value) }}\n <!-- The live value only differs while a debounce is pending, which is exactly\n when \"the input says X but the query asked for Y\" needs explaining. -->\n @if (field.liveValue !== field.value) {\n <span class=\"et-query-devtools-stat-sub\"\n >typing {{ inlineValue(field.liveValue) }}</span\n >\n }\n </td>\n <td class=\"et-query-devtools-mono\">{{ inlineValue(field.defaultValue) }}</td>\n <td class=\"et-query-devtools-mono\">\n @if (field.queryParam === undefined) {\n <span class=\"et-query-devtools-hint\">\u2014</span>\n } @else {\n {{ field.paramKey }}={{ inlineValue(field.queryParam) }}\n }\n </td>\n <td>\n <div class=\"et-query-devtools-chips\">\n @if (field.debounceMs !== null) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ field.debounceMs }}ms\n </span>\n }\n @if (field.isResetBy.length) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Reset to its default whenever one of these changes\"\n >\n reset by {{ field.isResetBy.join(', ') }}\n </span>\n }\n @if (!field.countsAsFilter) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Not counted by activeFilterCount\"\n >\n not a filter\n </span>\n }\n </div>\n </td>\n <td>\n <button\n [disabled]=\"field.isDefault\"\n (click)=\"form.resetField(field.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <h4 class=\"et-query-devtools-section\">Committed value</h4>\n <et-query-devtools-json [value]=\"form.value()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n No query forms registered. A form created with <code>createQueryForm()</code> registers itself.\n </p>\n }\n </div>\n\n @if (formSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"formSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of authEntries(); track entry.id) {\n @let auth = asAuth(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (authTokenExpiry(auth); as expiry) {\n <dt>Token expires in</dt>\n <dd>{{ expiry }}</dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: entry.meta.features }\" />\n </dd>\n }\n </dl>\n\n @if (authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n </div>\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <div class=\"et-query-devtools-scroll\">\n @for (entry of wsEntries(); track entry.id) {\n @let ws = asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <h5 class=\"et-query-devtools-section\">Messages ({{ ws.messages().length }})</h5>\n @for (message of ws.messages(); track message.id) {\n <div class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n <et-query-devtools-json [value]=\"message.data\" />\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No messages received yet.</p>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n </div>\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <div class=\"et-query-devtools-scroll\">\n @for (client of cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.entries.length }} entries</span>\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ persistedCount(client.client!) }} on disk</span>\n <button (click)=\"clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n </div>\n @if (clientFeatures(client.client); as features) {\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features }\" />\n }\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (cacheEntry of client.entries; track cacheEntry.key) {\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>{{ cacheEntry.consumerCount }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ cacheFreshness(cacheEntry) }}</td>\n <td>{{ cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ cachePersistence(cacheEntry) }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n <button (click)=\"refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"7\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n </div>\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n @let tl = timeline();\n <div class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #timelineClientSelect\n [value]=\"selectedClientName() ?? ''\"\n (change)=\"selectClient(timelineClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n <span class=\"et-query-devtools-count\">\n {{ tl.rows.length }} run{{ tl.rows.length === 1 ? '' : 's' }}\n @if (tl.hidden) {\n \u00B7 {{ tl.hidden }} older not shown\n }\n @if (tl.rows.length) {\n \u00B7 over {{ formatDuration(tl.windowMs) }} from {{ formatTime(tl.startedAt) }}\n }\n </span>\n <button\n [disabled]=\"!tl.rows.length\"\n (click)=\"resetTimeline()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters of every listed query\"\n >\n \u27F2 Reset\n </button>\n </div>\n </div>\n\n @if (tl.rows.length) {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-timeline\">\n <div class=\"et-query-devtools-timeline-axis\">\n <span class=\"et-query-devtools-timeline-ticks\">\n @for (tick of timelineTicks(); track tick.pct) {\n <span [style.left.%]=\"tick.pct\" class=\"et-query-devtools-timeline-tick\">{{\n tick.label\n }}</span>\n }\n </span>\n </div>\n\n @for (row of tl.rows; track row.key) {\n <button\n [class.et-query-devtools-timeline-row--active]=\"timelineSelectedQueryId() === row.entryId\"\n (click)=\"selectTimelineRow(row)\"\n class=\"et-query-devtools-timeline-row\"\n type=\"button\"\n title=\"{{ row.method }} {{ row.path }} \u00B7 run {{ row.run.index }} \u00B7 {{ row.run.status }}\"\n >\n <span class=\"et-query-devtools-timeline-label\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <span class=\"et-query-devtools-url\">{{ row.path }}</span>\n </span>\n <span class=\"et-query-devtools-timeline-track\">\n <span\n [attr.data-status]=\"runStatus(row.run)\"\n [style.left.%]=\"row.leftPct\"\n [style.width.%]=\"row.widthPct\"\n class=\"et-query-devtools-timeline-bar\"\n ></span>\n </span>\n <span class=\"et-query-devtools-timeline-duration\">\n @if (row.durationMs === null) {\n in flight\n } @else {\n {{ formatDuration(row.durationMs) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-size\">\n @if (row.run.receivedBytes) {\n {{ formatBytes(row.run.receivedBytes) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-flags\">\n @if (row.run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n row.run.attempts\n }} attempts - so this bar is mostly retry backoff, not one slow round trip\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ row.run.attempts }}\n </span>\n }\n @if (!row.run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (row.run.status === 'aborted') {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The query requested again before this run's response arrived\"\n >\n aborted\n </span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n } @else {\n <p class=\"et-query-devtools-empty\">\n No requests recorded yet. Every request a listed query makes shows up here as a bar.\n </p>\n }\n </div>\n\n @if (timelineSelectedQuery(); as sel) {\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"timelineSelectedQueryId.set(null)\"\n class=\"et-query-devtools-close\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <ng-container *ngTemplateOutlet=\"queryDetailTpl; context: { sel }\" />\n </aside>\n }\n </div>\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <span class=\"et-query-devtools-count\">{{ eventLog().length }} events</span>\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n </tr>\n </thead>\n <tbody>\n @for (event of eventLog(); track event.id) {\n <tr\n [attr.data-status]=\"\n event.type === 'request-error' ? 'error' : event.type === 'queries-refreshed' ? null : 'success'\n \"\n >\n <td>{{ formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (refreshed.queryIds[0]; as queryId) {\n <button\n (click)=\"selectQuery(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (event.queryId) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">No events captured yet.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see\n them exactly as they would see a real one. Unlike the rest of the panel, what you arm here is not\n persisted - a page reload disarms every client.\n </p>\n </div>\n\n @for (client of faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm\n </button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"\n armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\n \"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n </div>\n }\n }\n </div>\n\n <!-- A route with its path params and query string picked out from the static path. The segments must\n stay element-only: a text node between them would render as a space inside the route. -->\n <ng-template #routeTpl let-segments=\"segments\" let-fallback=\"fallback\">\n @for (segment of segments; track $index) {\n <span [class]=\"'et-query-devtools-route-' + segment.kind\" [attr.title]=\"segment.name\">{{ segment.text }}</span>\n } @empty {\n {{ fallback }}\n }\n </ng-template>\n\n <!-- Reusable dt/dd pair for a card's `et-query-devtools-meta` list: what its queries ran and moved. -->\n <ng-template #activityRowTpl let-activity=\"activity\">\n <dt>Traffic</dt>\n <dd>\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </ng-template>\n\n <!-- Reusable feature list: the feature's name, followed by the options it was configured with. -->\n <ng-template #featuresTpl let-features=\"features\">\n <ul class=\"et-query-devtools-features\">\n @for (feature of features; track feature.type) {\n <li class=\"et-query-devtools-feature\">\n <span class=\"et-query-devtools-chip\">{{ featureLabel(feature.type) }}</span>\n <!-- Always rendered: the list is one grid, so a feature without options still needs its cell\n for the next row's chip to stay in the same column. -->\n <span class=\"et-query-devtools-feature-details\">\n @for (detail of feature.details; track detail.label) {\n <span class=\"et-query-devtools-feature-detail\">\n <span class=\"et-query-devtools-feature-label\">{{ detail.label }}</span>\n <span class=\"et-query-devtools-feature-value\">{{ detail.value }}</span>\n </span>\n }\n </span>\n </li>\n }\n </ul>\n </ng-template>\n\n <!-- Reusable inner-query list (stacks) \u2014 clicking opens the split-view drawer, not a tab switch. -->\n <ng-template #innerQueriesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"\n routeTpl;\n context: { segments: link.segments, fallback: link.query.id() || 'query ' + ($index + 1) }\n \"\n />\n </span>\n @let linkStats = linkActivity(link);\n @if (linkStats.hasActivity) {\n <span class=\"et-query-devtools-traffic\">\n {{ formatTransferred(linkStats.stats.receivedBytes, linkStats.stats.hasEstimatedBytes) }}\n </span>\n }\n </button>\n }\n </div>\n </ng-template>\n\n <!-- The queries a form feeds \u2014 clicking one opens the split-view drawer, not a tab switch. -->\n <ng-template #formDrivesTpl let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track link.id) {\n <button\n [class.et-query-devtools-inner-row--active]=\"formSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"formSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: link.segments, fallback: link.query.id() || 'query' }\"\n />\n </span>\n </button>\n }\n </div>\n </ng-template>\n\n <!-- Reusable query detail \u2014 used by the Queries tab and by every split-view drawer. -->\n <ng-template #queryDetailTpl let-sel=\"sel\">\n <div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ sel.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <ng-container\n *ngTemplateOutlet=\"routeTpl; context: { segments: routeSegments(sel.entry, sel.query), fallback: '\u2014' }\"\n />\n </span>\n </div>\n\n @if (requestProgress(sel.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ retryCause(retry.status) }} \u00B7 backing off {{ formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ formatPercent(progress.percentage) }} \u00B7 {{ formatBytes(progress.loaded) }} of\n {{ formatBytes(progress.total) }}\n @if (progress.remainingTime !== null) {\n \u00B7 {{ formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-actions\">\n <span class=\"et-query-devtools-actions-label\">Run</span>\n <button (click)=\"executeQuery(sel.query, false)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"executeQuery(sel.query, true)\" class=\"et-query-devtools-btn\" type=\"button\">Cached</button>\n <button (click)=\"resetQuery(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Reset</button>\n <button (click)=\"copyReport(sel.entry, sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ copiedReport() ? '\u2713 Copied' : 'Copy report' }}\n </button>\n <button\n (click)=\"copyInsomniaRequest(sel.entry, sel.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy as an Insomnia collection (Import \u2192 From Clipboard). Includes the request headers, and the token refresh for a secure query.\"\n >\n {{ copiedInsomnia() ? '\u2713 Copied' : 'Insomnia' }}\n </button>\n <span class=\"et-query-devtools-actions-label\">Edit</span>\n <button (click)=\"openResponseEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Response</button>\n <button (click)=\"openArgsEditor(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Replay args</button>\n <span class=\"et-query-devtools-actions-label\">Force</span>\n <button (click)=\"forceLoading(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Loading</button>\n <button (click)=\"forceError(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Error</button>\n <button (click)=\"forceEmpty(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Empty</button>\n <button (click)=\"clearForced(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n </div>\n\n @if (editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"editorSeed\"\n (input)=\"responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyResponse(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"editorSeed\"\n (input)=\"argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"applyArgs(sel.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n }\n\n @let detailRuns = queryRuns(sel.entry);\n <nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"detailTab() === tab.id\"\n [attr.aria-selected]=\"detailTab() === tab.id\"\n (click)=\"detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && sel.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n </nav>\n\n @switch (detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ sel.entry.meta.clientBaseUrl || sel.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ requestUrl(sel.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ queryStatus(sel.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ sel.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ formatTime(sel.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ sel.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = refreshesFor(sel.entry.id);\n @if (refreshes.length) {\n <dt\n title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\"\n >\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = formsDrivingQuery(sel.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (sel.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <ng-container *ngTemplateOutlet=\"featuresTpl; context: { features: sel.entry.meta.features }\" />\n </dd>\n }\n </dl>\n\n @let activity = queryActivity(sel.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"resetStats(sel.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >{{ formatBytes(activity.avgResponseBytes) }} per response</span\n >\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"resetStats(sel.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Diff</th>\n </tr>\n </thead>\n <tbody>\n @for (run of detailRuns; track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n </td>\n <td>{{ formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td class=\"et-query-devtools-cell-actions\">\n @if (canDiffRun(sel.entry, run)) {\n <button\n [class.et-query-devtools-btn--active]=\"diffRunIndex() === run.index\"\n (click)=\"toggleRunDiff(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare this response against the previous one still held\"\n >\n Diff\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">nothing earlier to diff</span>\n } @else if (run.status === 'success') {\n <span class=\"et-query-devtools-count\">body no longer held</span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n @if (responseDiff(sel.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-count\">\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (sel.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"jsonSearch()\"\n (input)=\"jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"queryArgs(sel.query)\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"args\"\n />\n\n @if (sel.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <h5 class=\"et-query-devtools-section\">Response</h5>\n <et-query-devtools-json\n [value]=\"sel.query.response()\"\n [search]=\"jsonSearchTerm()\"\n [expandedPaths]=\"jsonExpandedPaths()\"\n [collapsedPaths]=\"jsonCollapsedPaths()\"\n [toggleFn]=\"toggleJsonPath\"\n path=\"response\"\n />\n }\n }\n }\n </ng-template>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;margin-inline-start:.5rem;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.4rem;flex:0 0 auto}.et-query-devtools-inspect-btn{appearance:none;position:relative;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.4rem .8rem;font:inherit;transition:color .12s ease,background-color .12s ease,border-color .12s ease}.et-query-devtools-inspect-btn:hover{color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-inspect-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:2px}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-inspect-btn--active,.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:.6rem;pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn{transition:none}.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-close-panel{appearance:none;display:inline-flex;align-items:center;gap:.6rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:var(--_et-qdt-hover);color:var(--_et-qdt-fg);cursor:pointer;padding:.4rem .6rem .4rem .8rem;font:inherit}.et-query-devtools-close-panel:hover{border-color:var(--_et-qdt-error);color:var(--_et-qdt-error)}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 34%;min-inline-size:22rem;max-inline-size:40rem;overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-drawer{flex:0 0 42%;min-inline-size:26rem;max-inline-size:48rem;overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.8rem;padding:.8rem;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.8rem}.et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:center;gap:.8rem;inline-size:100%;min-block-size:3.6rem;text-align:start;appearance:none;border:none;border-block-end:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-fg);padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;margin-block-end:.8rem}.et-query-devtools-btn{appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.3rem;background:transparent;color:var(--_et-qdt-fg);padding:.25rem .55rem;font-size:1.15rem;line-height:1.4;cursor:pointer}.et-query-devtools-btn:hover{background:var(--_et-qdt-hover)}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-traffic{margin-inline-start:auto;flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;margin-block-end:.4rem}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-features{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:.4rem .8rem;margin:0 0 .8rem;padding:0;list-style:none}.et-query-devtools-meta .et-query-devtools-features{margin-block-end:0}.et-query-devtools-feature{display:contents}.et-query-devtools-feature>.et-query-devtools-chip{justify-self:start}.et-query-devtools-feature-details{display:flex;flex-wrap:wrap;gap:.3rem}.et-query-devtools-feature-detail{display:inline-flex;align-items:baseline;gap:.4rem;padding:.2rem .5rem;border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);font-size:1.1rem}.et-query-devtools-feature-label{color:var(--_et-qdt-muted)}.et-query-devtools-feature-value{color:var(--_et-qdt-fg);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}.et-query-devtools-caret{display:inline-block;width:1.2rem;color:var(--_et-qdt-muted)}.et-query-devtools-facet{appearance:none;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-actions-label{color:var(--_et-qdt-subtle);align-self:center;text-transform:uppercase;font-size:1rem;letter-spacing:.05em}.et-query-devtools-actions-label:not(:first-child){margin-inline-start:.7rem}.et-query-devtools-cell-actions{display:flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:0 1 20rem;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:hover .et-query-devtools-url,.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon,.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-chunk{color:var(--_et-qdt-fg);opacity:.75;font-variant-numeric:tabular-nums}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:.2rem}.et-query-devtools-json-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}.et-query-devtools-json-row:hover .et-query-devtools-json-copy,.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-timeline{display:grid;grid-template-columns:minmax(10rem,26rem) minmax(14rem,1fr) max-content max-content max-content;gap:.1rem .8rem;padding-inline:.4rem 1rem}.et-query-devtools-timeline-axis,.et-query-devtools-timeline-row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center}.et-query-devtools-timeline-axis{position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border);padding-block-end:.4rem;margin-block-end:.4rem}.et-query-devtools-timeline-ticks{grid-column:2;position:relative;block-size:1.5rem}.et-query-devtools-timeline-tick{position:absolute;inset-block-start:0;transform:translate(-50%);color:var(--_et-qdt-subtle);font-size:1.1rem;white-space:nowrap}.et-query-devtools-timeline-tick:first-child{transform:none}.et-query-devtools-timeline-tick:last-child{transform:translate(-100%)}.et-query-devtools-timeline-row{appearance:none;border:0;background:transparent;color:inherit;font:inherit;text-align:start;padding:.15rem 0;cursor:pointer}.et-query-devtools-timeline-row:hover{background:var(--_et-qdt-hover)}.et-query-devtools-timeline-row--active{background:var(--_et-qdt-active)}.et-query-devtools-timeline-label{display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;overflow:hidden}.et-query-devtools-timeline-label .et-query-devtools-url{max-inline-size:none;margin-inline-start:0}.et-query-devtools-timeline-track{position:relative;block-size:1.8rem;border-radius:.2rem;overflow:hidden;background-color:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);background-image:repeating-linear-gradient(to right,var(--_et-qdt-border) 0 1px,transparent 1px 25%)}.et-query-devtools-timeline-bar{position:absolute;inset-block:.4rem;min-inline-size:.2rem;border-radius:.2rem;background:var(--_et-qdt-subtle)}.et-query-devtools-timeline-bar[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-timeline-bar[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-timeline-bar[data-status=loading]{background:var(--_et-qdt-loading)}.et-query-devtools-timeline-bar[data-status=aborted]{min-inline-size:.5rem;background:color-mix(in srgb,var(--_et-qdt-subtle) 20%,transparent);outline:1px solid color-mix(in srgb,var(--_et-qdt-subtle) 70%,transparent);outline-offset:-1px}.et-query-devtools-timeline-duration,.et-query-devtools-timeline-size{text-align:end;color:var(--_et-qdt-muted);font-size:1.1rem;font-variant-numeric:tabular-nums;white-space:nowrap}.et-query-devtools-timeline-duration{color:var(--_et-qdt-fg)}.et-query-devtools-timeline-flags{display:flex;align-items:center;gap:.3rem;white-space:nowrap}.et-query-devtools-timeline-flags .et-query-devtools-chip{padding:0 .4rem;border-color:transparent;border-radius:.3rem;background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:flex;align-items:center;gap:.5rem}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"] }]
48057
49337
  }], ctorParameters: () => [] });
48058
49338
 
48059
49339
  const QUERY_DEVTOOLS_IMPORTS = [QueryDevtoolsComponent];
@@ -58770,5 +60050,5 @@ const TOOLTIP_IMPORTS = [TooltipDirective, TooltipComponent];
58770
60050
  * Generated bundle index. Do not edit.
58771
60051
  */
58772
60052
 
58773
- export { ACCORDION_ERROR_CODES, ACCORDION_GROUP_TOKEN, ACCORDION_IMPORTS, ACCORDION_TOKEN, ALIGN_CENTER_ICON, ALIGN_JUSTIFY_ICON, ALIGN_LEFT_ICON, ALIGN_RIGHT_ICON, ARROWS_LEFT_RIGHT_ICON, ARROW_OUT_UP_RIGHT_ICON, ARROW_RIGHT_ICON, ARROW_UP_ICON, AccordionComponent, AccordionContentDirective, AccordionDirective, AccordionGroupComponent, AccordionGroupDirective, AccordionHintDirective, AccordionLabelDirective, AccordionPanelDirective, AccordionTriggerDirective, BOLD_ICON, BRACKET_CARD_CONTEXT, BRACKET_DATA_LAYOUT, BRACKET_DEFAULTS, BRACKET_DENSITY, BRACKET_DENSITY_PRESETS, BRACKET_ERROR_CODES, BRACKET_IMPORTS, BRACKET_LABELS, BRACKET_ROUND_MIRROR_TYPE, BRACKET_SWISS_GROUP_COLOR_TYPE, BREADCRUMB_COLLAPSE_IMPORTS, BREADCRUMB_COLLAPSE_TOKEN, BREADCRUMB_ERROR_CODES, BREADCRUMB_IMPORTS, BREADCRUMB_LABELS, BREADCRUMB_SEGMENT_TOKEN, BREADCRUMB_SEO_IMPORTS, BREADCRUMB_TOKEN, BUTTON_ICON_ALIGNMENTS, BUTTON_IMPORTS, BUTTON_SIZES, BUTTON_SPINNER_CONFIG, BUTTON_TYPES, BUTTON_VARIANTS, BracketComponent, BracketDefaultContinueComponent, BracketDefaultFinalMatchComponent, BracketDefaultMatchComponent, BracketDefaultRoundHeaderComponent, BracketMap, BracketRoundsListComponent, BrandLoaderComponent, BreadcrumbCollapseDirective, BreadcrumbComponent, BreadcrumbDirective, BreadcrumbItemDirective, BreadcrumbItemTemplateDirective, BreadcrumbOutletComponent, BreadcrumbOverflowComponent, BreadcrumbSegmentDirective, BreadcrumbSeoDirective, BreadcrumbSeparatorDirective, ButtonComponent, ButtonDirective, ButtonStylesDirective, CALENDAR_ERROR_CODES, CALENDAR_ICON, CALENDAR_IMPORTS, CALENDAR_LABELS, CAROUSEL_AUTOPLAY_TOKEN, CAROUSEL_ERROR_CODES, CAROUSEL_IMPORTS, CAROUSEL_LABELS, CAROUSEL_SLIDE_ALIGNMENTS, CAROUSEL_TOKEN, CAROUSEL_TRANSITIONS, CAROUSEL_TRANSITION_DRIVERS, CASCADER_ERROR_CODES, CASCADER_IMPORTS, CASCADER_LABELS, CASCADER_SELECTABLE_LEVELS, CHECKBOX_IMPORTS, CHECKBOX_OPTION_VARIANTS, CHECK_ICON, CHEVRON_ICON, CHIP_ERROR_CODES, CHIP_IMPORTS, CHIP_LABELS, CHOICE_FIELD_IMPORTS, CHOICE_FIELD_VARIANTS, CIRCLE_CHECK_ICON, CIRCLE_INFO_ICON, CLIPBOARD_CHECK_ICON, CLOCK_ICON, CODE_BLOCK_ICON, CODE_ICON, COLOR_INPUT_IMPORTS, COMMON_BRACKET_ROUND_TYPE, CalendarCellDirective, CalendarComponent, CalendarDirective, CalendarGridDirective, CalendarHeaderDirective, CarouselAutoplayDirective, CarouselComponent, CarouselDirective, CarouselItemDirective, CarouselNextDirective, CarouselPlayToggleDirective, CarouselPreviousDirective, CarouselSlideDirective, CascaderColumnDirective, CascaderComponent, CascaderDirective, CascaderNodeDirective, CascaderPanelComponent, CascaderSearchDirective, CascaderSearchOptionDirective, CascaderSurfaceDirective, CascaderTriggerDirective, CheckboxComponent, CheckboxDirective, CheckboxGroupComponent, CheckboxGroupSelectAllComponent, CheckboxOptionComponent, ChipComponent, ChipDirective, ChipRemoveDirective, ChoiceFieldComponent, ColorInputComponent, ColorInputDirective, CounterComponent, DAILYMOTION_PLAYER_TOKEN, DATE_FORMAT, DATE_INPUT_ERROR_CODES, DATE_INPUT_IMPORTS, DATE_LOCALE, DATE_PICKER_HOST, DATE_RANGE_INPUT_ERROR_CODES, DATE_RANGE_INPUT_IMPORTS, DATE_TIME_INPUT_ERROR_CODES, DATE_TIME_INPUT_IMPORTS, DATE_TIME_LABELS, DEFAULT_BRACKET_LABELS, DEFAULT_BREADCRUMB_LABELS, DEFAULT_BREAKPOINTS, DEFAULT_CALENDAR_LABELS, DEFAULT_CALENDAR_RANGE_STRATEGY, DEFAULT_CAROUSEL_LABELS, DEFAULT_CASCADER_LABELS, DEFAULT_CHIP_LABELS, DEFAULT_DATE_TIME_LABELS, DEFAULT_DROPZONE_LABELS, DEFAULT_FILTER_OVERLAY_LABELS, DEFAULT_FORM_FIELD_LABELS, DEFAULT_GRID_LABELS, DEFAULT_ICON_VARIANT, DEFAULT_INPUT_LABELS, DEFAULT_LOADER_LABELS, DEFAULT_MATCH_CARD_START_TIME_FORMAT, DEFAULT_MATCH_LABELS, DEFAULT_NOTIFICATION_LABELS, DEFAULT_NOTIFICATION_MANAGER_CONFIG, DEFAULT_NOTIFICATION_STATUS_ICONS, DEFAULT_PAGINATION_LABELS, DEFAULT_PHONE_INPUT_LABELS, DEFAULT_PIP_WINDOW_CONFIG, DEFAULT_QUERY_ERROR_LABELS, DEFAULT_RICH_TEXT_EDITOR_LABELS, DEFAULT_RICH_TEXT_EDITOR_TOOLS, DEFAULT_SELECT_LABELS, DEFAULT_SLIDER_LABELS, DEFAULT_STANDINGS_LABELS, DEFAULT_STREAM_LABELS, DEFAULT_STREAM_PLAYER_STATE, DEFAULT_TABLE_LABELS, DEFAULT_TIME_PICKER_LABELS, DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE, DROPZONE_ENTRY_STATUSES, DROPZONE_ERROR_CODES, DROPZONE_FILES_ERROR_KIND, DROPZONE_FILE_CONSTRAINTS, DROPZONE_FILE_REJECTION_REASONS, DROPZONE_IMPORTS, DROPZONE_LABELS, DURATION_INPUT_ERROR_CODES, DURATION_INPUT_IMPORTS, DailymotionPlayerComponent, DailymotionPlayerDirective, DailymotionPlayerParamsDirective, DailymotionPlayerSlotComponent, DateInputComponent, DateInputDirective, DateInputFieldDirective, DatePickerPanelComponent, DatePickerSurfaceDirective, DatePickerTriggerDirective, DateRangeInputComponent, DateRangeInputDirective, DateRangeInputFieldDirective, DateTimeInputComponent, DateTimeInputDirective, DateTimeInputFieldDirective, DescriptionComponent, DropzoneComponent, DropzoneDirective, DurationInputComponent, DurationInputDirective, DurationInputFieldDirective, ELLIPSIS_ICON, ELLIPSIS_VERTICAL_ICON, ET_BUILT_IN_ICON_NAMES, EYE_ICON, EYE_SLASH_ICON, FACEBOOK_PLAYER_TOKEN, FILE_ICON, FILTER_ICON, FILTER_OVERLAY_ERROR_CODES, FILTER_OVERLAY_IMPORTS, FILTER_OVERLAY_LABELS, FILTER_OVERLAY_TOKEN, FLOATING_ACTION_ERROR_CODES, FLOATING_ACTION_IMPORTS, FLOATING_ACTION_STATES, FLOATING_ACTION_TOKEN, FLOPPY_DISK_ICON, FOCUS_FRAME_ICON, FORM_ERROR_MESSAGE_RESOLVER, FORM_FIELD_APPEARANCES, FORM_FIELD_CONTROL_TYPES, FORM_FIELD_ERROR_CODES, FORM_FIELD_FILLS, FORM_FIELD_IMPORTS, FORM_FIELD_LABELS, FORM_FIELD_LABEL_MODES, FORM_FIELD_SIZES, FORM_FIELD_TOKEN, FabComponent, FacebookPlayerComponent, FacebookPlayerDirective, FacebookPlayerParamsDirective, FacebookPlayerSlotComponent, FilterOverlayResetDirective, FilterOverlaySubmitDirective, FloatingActionAnchorDirective, FloatingActionDirective, FloatingActionScopeDirective, FloatingActionTopDirective, FloatingActionTriggerDirective, FocusRingDirective, FormErrorComponent, FormFieldComponent, FormFieldDirective, GERMAN_FILTER_OVERLAY_LABELS, GERMAN_QUERY_ERROR_LABELS, GRID_2X2_ICON, GRID_DEBUG_IMPORTS, GRID_ERROR_CODES, GRID_IMPORTS, GRID_LABELS, GRID_TOKEN, GROUP_BRACKET_ROUND_TYPE, GridComponent, GridDebugComponent, GridDirective, GridDragDirective, GridItemComponent, GridItemDefaultActionsComponent, GridItemDirective, GridItemRef, GridItemToolbarComponent, GridResizeDirective, HEADING_1_ICON, HEADING_2_ICON, HEADING_3_ICON, HintComponent, ICONS_TOKEN, ICON_DIRECTIVE_TOKEN, ICON_ERROR_CODES, ICON_IMPORTS, ICON_OVERRIDES_TOKEN, IMAGE_ICON, INITIAL_SUPPORT_PRESENTATION_STATE, INPUT_IMPORTS, INPUT_LABELS, INPUT_MASK_HOST, INPUT_TEXT_ALIGNMENTS, INPUT_TYPES, INTERACTIVE_TAGS, ITALIC_ICON, IconButtonComponent, IconDirective, InputComponent, InputDirective, InputMaskDirective, InputPrefixDirective, InputSuffixDirective, KICK_PLAYER_TOKEN, KickPlayerComponent, KickPlayerDirective, KickPlayerParamsDirective, KickPlayerSlotComponent, LINK_ICON, LIST_BULLETED_ICON, LIST_NUMBERED_ICON, LOADER_LABELS, LOCK_ICON, LabelDirective, MASKED_INPUT_ERROR_CODES, MASKED_INPUT_IMPORTS, MASK_VALUE_MODES, MASONRY_ERROR_CODES, MASONRY_IMPORTS, MASONRY_TOKEN, MATCH_CARD_IMPORTS, MATCH_CARD_SIZES, MATCH_CARD_TOKEN, MATCH_ERROR_CODES, MATCH_LABELS, MATCH_PARTICIPANT_IMPORTS, MENU_ERROR_CODES, MENU_IMPORTS, MENU_ITEM_VARIANTS, MENU_SELECTION_GROUP_MULTIPLE, MENU_SELECTION_GROUP_TOKEN, MENU_SELECTION_ITEM_KIND, MINUS_ICON, MULTI_LANGUAGE_RICH_TEXT_EDITOR_ERROR_CODES, MULTI_LANGUAGE_RICH_TEXT_EDITOR_IMPORTS, MasonryDirective, MasonryItemDirective, MatchCardComponent, MatchCardDirective, MatchCardGameScoresDirective, MatchCardMetaDirective, MatchCardScoreDirective, MatchParticipantComponent, MatchScoreComponent, MenuCheckboxGroupComponent, MenuCheckboxItemComponent, MenuComponent, MenuContextTriggerDirective, MenuDirective, MenuGroupLabelComponent, MenuItemComponent, MenuItemDirective, MenuItemShortcutComponent, MenuPanelDirective, MenuRadioGroupComponent, MenuRadioItemComponent, MenuSearchDirective, MenuSelectionGroupDirective, MenuSelectionItemDirective, MenuSeparatorComponent, MenuSurfaceDirective, MenuTriggerDirective, MultiLanguageRichTextEditorComponent, MultiLanguageRichTextEditorDirective, MultiLanguageRichTextEditorLanguageToolComponent, NAV_TABS_TOKEN, NAV_TAB_IMPORTS, NOTIFICATION_ACTION_SLOTS, NOTIFICATION_ERROR_CODES, NOTIFICATION_IMPORTS, NOTIFICATION_LABELS, NOTIFICATION_STACK_CONTEXT_TOKEN, NOTIFICATION_STATUS, NUMBER_INPUT_IMPORTS, NavTabLinkComponent, NavTabLinkDirective, NavTabsComponent, NavTabsDirective, NavTabsOutletComponent, NavTabsOutletDirective, NotificationActionDirective, NotificationComponent, NotificationDirective, NotificationDismissDirective, NotificationItemDirective, NotificationStackDirective, NotificationSwipeToDismissDirective, NumberInputComponent, NumberInputDirective, OTP_INPUT_IMPORTS, OVERLAY_BACK_OR_CLOSE_TOKEN, OVERLAY_BODY_TOKEN, OVERLAY_CONFIG_CLASS_KEYS, OVERLAY_CONTENT_IMPORTS, OVERLAY_ERROR_CODES, OVERLAY_FOOTER_TOKEN, OVERLAY_HEADER_TEMPLATE_TOKEN, OVERLAY_HEADER_TOKEN, OVERLAY_IMPORTS, OVERLAY_MAIN_TOKEN, OVERLAY_QUERY_PARAM_INPUT_NAME, OVERLAY_REF, OVERLAY_ROUTER_CONFIG_TOKEN, OVERLAY_ROUTER_LINK_TOKEN, OVERLAY_ROUTER_OUTLET_DISABLED_TEMPLATE_TOKEN, OVERLAY_ROUTER_OUTLET_TOKEN, OVERLAY_ROUTER_TOKEN, OVERLAY_SHARED_ROUTE_TEMPLATE_TOKEN, OtpInputComponent, OtpInputDirective, OverlayAnchorDirective, OverlayBackOrCloseDirective, OverlayBodyComponent, OverlayCloseDirective, OverlayContainerComponent, OverlayDirective, OverlayFooterDirective, OverlayHeaderDirective, OverlayHeaderTemplateDirective, OverlayMainDirective, OverlayOriginCloneComponent, OverlayRouteHeaderTemplateOutletComponent, OverlayRouterLinkDirective, OverlayRouterOutletComponent, OverlayRouterOutletDisabledTemplateDirective, OverlaySharedRouteTemplateDirective, OverlaySharedRouteTemplateOutletComponent, OverlaySidebarComponent, OverlaySidebarPageComponent, OverlaySurfaceDirective, OverlayTitleDirective, OverlayTriggerDirective, PAGE_SIZE_SELECT_IMPORTS, PAGINATION_IMPORTS, PAGINATION_LABELS, PARAGRAPH_ICON, PASSWORD_INPUT_IMPORTS, PAUSE_ICON, PENCIL_ICON, PHONE_COUNTRIES, PHONE_INPUT_ERROR_CODES, PHONE_INPUT_IMPORTS, PHONE_INPUT_LABELS, PICTURE_IMPORTS, PICTURE_STATES, PIP_CHROME_REF_TOKEN, PIP_ENTRY_TOKEN, PIP_WINDOW_ASPECT_RATIO_TOKEN, PLAY_ICON, PLUS_ICON, PageSizeSelectComponent, PaginationComponent, PaginationDirective, PaginationSeoDirective, PasswordInputComponent, PasswordInputDirective, PhoneInputComponent, PhoneInputDirective, PhoneInputFieldDirective, PhoneInputFlagDirective, PictureComponent, PictureErrorDirective, PicturePlaceholderDirective, PipBackDirective, PipBringBackDirective, PipCellDirective, PipCloseDirective, PipCollapseOverlayDirective, PipGridToggleDirective, PipPlayerComponent, PipSlotPlaceholderComponent, PipStageDirective, PipTitleBarDirective, PipTitleBarTemplateDirective, PipWindowComponent, PipWindowParamsDirective, ProgressBarComponent, QUERY_DEVTOOLS_IMPORTS, QUERY_ERROR_ERROR_CODES, QUERY_ERROR_IMPORTS, QUERY_ERROR_LABELS, QUERY_ERROR_TOKEN, QUOTE_ICON, QueryDevtoolsComponent, QueryDevtoolsJsonComponent, QueryDevtoolsToggleComponent, QueryErrorActionsDirective, QueryErrorComponent, QueryErrorDirective, QueryErrorTitleDirective, QueryParamOverlayLinkDirective, RADIO_VARIANTS, RATING_IMPORTS, REDO_ICON, RICH_TEXT_EDITOR_HEADING_OPTIONS, RICH_TEXT_EDITOR_IMPORTS, RICH_TEXT_EDITOR_INLINE_TOOLS, RICH_TEXT_EDITOR_LABELS, RICH_TEXT_EDITOR_LANGUAGE_TOOL, RICH_TEXT_EDITOR_LINK_EDITOR, RICH_TEXT_EDITOR_TOKEN_CODEC, RICH_TEXT_EDITOR_TOKEN_PALETTE_IMPORTS, RICH_TEXT_EDITOR_TOOL, RICH_TEXT_EDITOR_TOOLS, RICH_TEXT_EDITOR_TOOL_BUTTONS, RICH_TEXT_EDITOR_TRIGGERS_IMPORTS, ROTATE_RIGHT_ICON, RadioComponent, RadioGroupComponent, RangeSliderComponent, RangeSliderDirective, RatingComponent, RatingDirective, RatingIconDirective, RichTextEditorAlignToolComponent, RichTextEditorComponent, RichTextEditorDirective, RichTextEditorFloatingToolbarComponent, RichTextEditorFloatingToolbarDirective, RichTextEditorHeadingToolComponent, RichTextEditorImageEditorComponent, RichTextEditorImageStylesComponent, RichTextEditorImageToolComponent, RichTextEditorLinkEditorComponent, RichTextEditorLinkEditorDirective, RichTextEditorTableStylesComponent, RichTextEditorTableToolComponent, RichTextEditorTokenPaletteComponent, RichTextEditorTokenPopupComponent, RichTextEditorTriggersDirective, SCROLLABLE_DARKEN_IMPORTS, SCROLLABLE_DRAG_IMPORTS, SCROLLABLE_ERROR_CODES, SCROLLABLE_IMPORTS, SCROLLABLE_NAVIGATION_IMPORTS, SEGMENTED_BUTTON_GROUP_VARIANTS, SELECTION_LIST_MULTIPLE, SELECTION_LIST_TOKEN, SELECT_ERROR_CODES, SELECT_FILTER_MODES, SELECT_IMPORTS, SELECT_LABELS, SIDEBAR_OVERLAY_CONFIG, SIDEBAR_OVERLAY_TOKEN, SINGLE_ELIMINATION_BRACKET_ROUND_TYPE, SKELETON_IMPORTS, SLIDER_ERROR_CODES, SLIDER_IMPORTS, SLIDER_LABELS, SLIDER_MARK_VALUE_ATTRIBUTE, SLIDER_TOKEN, SNAP_HYSTERESIS, SOOP_PLAYER_TOKEN, SPLIT_BUTTON_ERROR_CODES, STANDINGS_ERROR_CODES, STANDINGS_IMPORTS, STANDINGS_LABELS, STAR_ICON, STREAM_ALL_IMPORTS, STREAM_CONSENT_TOKEN, STREAM_DAILYMOTION_IMPORTS, STREAM_FACEBOOK_IMPORTS, STREAM_IMPORTS, STREAM_KICK_IMPORTS, STREAM_LABELS, STREAM_PIP_IMPORTS, STREAM_PLAYER_COMPONENT_TOKEN, STREAM_PLAYER_ERROR_CONTEXT_TOKEN, STREAM_PLAYER_ERROR_TOKEN, STREAM_PLAYER_PARAMS_TOKEN, STREAM_PLAYER_SLOT_TOKEN, STREAM_PLAYER_TOKEN, STREAM_SLOT_PLAYER_ID_TOKEN, STREAM_SOOP_IMPORTS, STREAM_TIKTOK_IMPORTS, STREAM_TWITCH_IMPORTS, STREAM_USER_CONSENT_PROVIDER_TOKEN, STREAM_VIMEO_IMPORTS, STREAM_YOUTUBE_IMPORTS, STRIKETHROUGH_ICON, SUPPORT_CONTENT_STATE, SUPPORT_TRANSITION_DIRECTION, SWISS_ADVANCE_WINS, SWISS_BRACKET_ROUND_TYPE, SWISS_ELIMINATE_LOSSES, SWITCH_IMPORTS, ScrollableActiveChildDirective, ScrollableButtonsComponent, ScrollableButtonsDirective, ScrollableComponent, ScrollableDarkenDirective, ScrollableDirective, ScrollableDragDirective, ScrollableIgnoreChildDirective, ScrollableLoadingTemplateDirective, ScrollableMasksComponent, ScrollableNavigationComponent, ScrollableNavigationDirective, ScrollableSnapDirective, SegmentedButtonComponent, SegmentedButtonGroupComponent, SelectComponent, SelectDirective, SelectEmptyDirective, SelectErrorDirective, SelectListboxDirective, SelectLoadingDirective, SelectOptionComponent, SelectOptionDirective, SelectOptionGroupComponent, SelectOptionGroupDirective, SelectOptionTemplateDirective, SelectOptionsDirective, SelectPanelComponent, SelectSearchDirective, SelectSurfaceDirective, SelectTriggerDirective, SelectValueDirective, SelectViewportDirective, SelectVirtualOptionComponent, SelectVirtualOptionDirective, SelectionListControlDirective, SelectionListDirective, SelectionOptionDirective, SkeletonComponent, SkeletonItemComponent, SkeletonTextComponent, SliderComponent, SliderDirective, SliderThumbDirective, SliderThumbLabelDirective, SliderTrackDirective, SoopPlayerComponent, SoopPlayerDirective, SoopPlayerParamsDirective, SoopPlayerSlotComponent, SpinnerComponent, SplitButtonActionDirective, SplitButtonComponent, SplitButtonDirective, SplitButtonTriggerDirective, StandingsComponent, StandingsDirective, StreamConsentAcceptDirective, StreamConsentComponent, StreamConsentDirective, StreamPipChromeComponent, StreamPlayerErrorComponent, StreamPlayerErrorDirective, StreamPlayerLoadingComponent, StreamPlayerSlotDirective, SwitchComponent, SwitchDirective, TABLE_CELL_ERROR_TOOLTIP_IMPORTS, TABLE_COLUMN_CHOOSER_IMPORTS, TABLE_COLUMN_MENU_IMPORTS, TABLE_CSV_EXPORT_IMPORTS, TABLE_ERROR_CODES, TABLE_FEATURE_HOST, TABLE_FILTER_IMPORTS, TABLE_ICON, TABLE_IMPORTS, TABLE_INLINE_EDIT_IMPORTS, TABLE_KEYBOARD_NAV_IMPORTS, TABLE_LABELS, TABLE_REORDER_IMPORTS, TABLE_RESIZE_IMPORTS, TABLE_SELECTION_IMPORTS, TABLE_STATE_PERSISTENCE_IMPORTS, TABLE_VIRTUAL_SCROLL_IMPORTS, TAB_BAR_FITS, TAB_BAR_ORIENTATIONS, TAB_BAR_TOKEN, TAB_BAR_TRIGGER_TOKEN, TAB_BAR_VARIANTS, TAB_ERROR_CODES, TAB_GROUP_TOKEN, TAB_IMPORTS, TAB_PANEL_TOKEN, TAB_SIZES, TAG_INPUT_ERROR_CODES, TAG_INPUT_IMPORTS, TEXTAREA_IMPORTS, TEXTAREA_RESIZE_MODES, TEXT_ALIGNS, TIKTOK_PLAYER_TOKEN, TIMES_ICON, TIME_FORMAT, TIME_INPUT_ERROR_CODES, TIME_INPUT_IMPORTS, TIME_PICKER_ERROR_CODES, TIME_PICKER_IMPORTS, TIME_PICKER_LABELS, TOGGLETIP_ERROR_CODES, TOGGLETIP_IMPORTS, TOOLTIP_ERROR_CODES, TOOLTIP_IMPORTS, TOURNAMENT_MODE, TRIANGLE_EXCLAMATION_ICON, TROPHY_ICON, TWITCH_PLAYER_TOKEN, TabBarDirective, TabBarTriggerDirective, TabBarUnderlineDirective, TabComponent, TabGroupComponent, TabGroupDirective, TabLabelDirective, TabPanelDirective, TabTriggerDirective, TableCellDirective, TableCellEditDirective, TableCellErrorMarkComponent, TableCellErrorTooltipDirective, TableCellSkeletonDirective, TableColumnChooserComponent, TableColumnMenuDirective, TableColumnMenuTriggerComponent, TableComponent, TableCsvExportDirective, TableDetailStylesComponent, TableExpanderCellComponent, TableFilterOptionDirective, TableFilterTriggerComponent, TableFiltersDirective, TableFooterCellDirective, TableFooterDirective, TableHeaderCellDirective, TableInlineEditDirective, TableInlineEditStylesComponent, TableKeyboardNavDirective, TableReorderDirective, TableReorderOverlayComponent, TableResizeDirective, TableResizeGripComponent, TableSelectAllCellComponent, TableSelectCellComponent, TableSelectionDirective, TableStatePersistenceDirective, TableVirtualScrollDirective, TableVirtualScrollStylesComponent, TagInputComponent, TagInputDirective, TagInputFieldDirective, TextButtonComponent, TextFieldControlDirective, TextareaComponent, TextareaDirective, TikTokPlayerComponent, TikTokPlayerDirective, TikTokPlayerParamsDirective, TikTokPlayerSlotComponent, TimeInputComponent, TimeInputDirective, TimeInputFieldDirective, TimePickerColumnDirective, TimePickerComponent, TimePickerDirective, TimePickerOptionDirective, ToggletipCloseDirective, ToggletipComponent, ToggletipDirective, ToggletipTriggerDirective, TooltipComponent, TooltipDirective, TwitchPlayerComponent, TwitchPlayerDirective, TwitchPlayerParamsDirective, TwitchPlayerSlotComponent, UNDERLINE_ICON, UNDO_ICON, UNIT_MS, UPLOAD_ICON, VIMEO_PLAYER_TOKEN, VimeoPlayerComponent, VimeoPlayerDirective, VimeoPlayerParamsDirective, VimeoPlayerSlotComponent, WINDOW_CONTROL_BUTTON_KINDS, WINDOW_CONTROL_BUTTON_SIZES, WindowControlButtonComponent, YOUTUBE_PLAYER_SLOT_TOKEN, YOUTUBE_PLAYER_TOKEN, YoutubePlayerComponent, YoutubePlayerDirective, YoutubePlayerParamsDirective, YoutubePlayerSlotComponent, YoutubePlayerSlotDirective, abortFullscreenAnimation, anchoredDialogOverlayStrategy, anchoredOverlayStrategy, autoPlace, bottomSheetOverlayStrategy, bracketFitsWidth, bracketNaturalWidth, buildAnchoredRuntimePositionStrategy, canHaveChildren, cascaderFromQuery, centeredOverlayStrategy, clampPosition, clampResizeRect, cleanupFullscreenAnimation, cleanupFullscreenAnimationStyles, compactLayout, computeGeometry, computeGridHeight, createAnchoredPanelController, createAutoScroller, createBracket, createBracketBase, createCardMask, createCurrencyMask, createDefaultDropzoneArgs, createDropzoneUpload, createExistingDropzoneEntry, createFileDropzoneEntry, createFixedLengthRangeStrategy, createGridAdapter, createIbanMask, createMatchesMapBase, createNewMatchParticipantBase, createNormalizedBracketMatch, createNotificationPromiseFn, createNotificationRef, createOverlayOpener, createOverlayRef, createOverlayStrategyController, createOverlayUnsavedChangesGuard, createParticipantsMapBase, createPipChromeAnimations, createPipChromeState, createRichTextEditorTrigger, createRichTextEditorTriggerWithQuery, createRoundsMapBase, createStreamConfig, createStreamPlayerSlot, createTableRowsSource, createTableStateStorage, createV2DropzoneUpload, createWeekRangeStrategy, defaultCompareWith, defaultDropzoneRejectionMessage, defineOverlay, defineQueryParamOverlay, deriveDurationFormatSpec, deserializeGridLayout, deserializeTableState, dialogOverlayStrategy, disposeDropzoneEntry, doubleEliminationBracketLayout, dropzoneFiles, enableDragToDismiss, extractFirstImageUrl, filterOverlayLabelsForLocale, filterOverlayPreviewFromQuery, filterRows, findCollision, findNextRelevantHtmlElement, findScrollableAncestor, formatDuration, formatFileSize, fromGridPosition, fullScreenDialogOverlayStrategy, generateBracketDataForEthlete, generateBracketRoundSwissGroupMaps, generateMatchPosition, generateMatchRelationPositions, generateMatchRelationsNew, generateRoundRelationsNew, generateRoundTypeFromEthleteRoundType, generateTournamentModeFormEthleteRounds, getAvailableSwissGroupsForRound, getClosestOverlay, getOriginCoordinatesAndDimensions, getSwissGroupColorType, hysteresisRound, iconRegistryKey, indexOfNode, injectAnchoredDialogStrategy, injectAnchoredDialogStrategyDefaults, injectBottomSheetStrategy, injectBottomSheetStrategyDefaults, injectBracketConfig, injectBracketLabels, injectBreadcrumbLabels, injectBreadcrumbManager, injectCalendarLabels, injectCarouselLabels, injectCascaderLabels, injectChipLabels, injectDateFormat, injectDateLocale, injectDateTimeLabels, injectDialogStrategy, injectDialogStrategyDefaults, injectDropzoneLabels, injectFilterOverlay, injectFilterOverlayLabels, injectFormFieldLabels, injectFormSupport, injectFullscreenDialogStrategy, injectFullscreenDialogStrategyDefaults, injectGridConfig, injectGridLabels, injectInputLabels, injectLeftSheetStrategy, injectLeftSheetStrategyDefaults, injectLoaderLabels, injectMatchLabels, injectNotificationLabels, injectNotificationManager, injectNotificationManagerConfig, injectOverlayManager, injectOverlayRouter, injectOverlayScrollBlocker, injectOverlaySurfaceContext, injectPaginationLabels, injectPhoneInputLabels, injectPictureConfig, injectPipChromeManager, injectPipManager, injectPipSlotPlaceholderConfig, injectQueryErrorLabels, injectRichTextEditorLabels, injectRichTextEditorTools, injectRightSheetStrategy, injectRightSheetStrategyDefaults, injectSelectLabels, injectSidebarOverlay, injectSliderLabels, injectStandingsLabels, injectStreamConfig, injectStreamConsentConfig, injectStreamLabels, injectStreamManager, injectStreamPlayerErrorConfig, injectStreamPlayerLoadingConfig, injectStreamScriptLoader, injectStreamUserConsentProvider, injectTableCsvExport, injectTableFeatureHost, injectTableLabels, injectTimeFormat, injectTimePickerLabels, injectTopSheetStrategy, injectTopSheetStrategyDefaults, isFileAccepted, isHtmlElement, isInteractiveElement, isPointerEvent, isTargetInsideOverlayTree, isTouchEvent, itemsCollide, leftSheetOverlayStrategy, legacyQueryErrorSource, mapLayoutToBreakpoint, matchCountryByDialCode, matchParticipantDisplayName, mergeOverlayBreakpointConfigs, mergeOverlayConfigs, mirroredDoubleEliminationBracketLayout, mirroredSingleEliminationBracketLayout, mountRichTextEditorImageStyles, mountRichTextEditorTableStyles, nodesEqual, normalizeEthleteBracketMatch, normalizeEthleteGroupRanking, normalizeEthleteMatch, normalizeEthleteMatchStatus, normalizeEthleteMedia, normalizeEthleteParticipant, normalizeEthletePlacement, normalizePictureSizes, normalizePictureSource, paginate, parseDuration, phoneCountryFlag, phoneCountryName, pixelRectsEqual, positionToPixelRect, positionsEqual, projectDragCell, provideAnchoredDialogStrategy, provideAnchoredDialogStrategyDefaults, provideBottomSheetStrategy, provideBottomSheetStrategyDefaults, provideBracketConfig, provideBracketLabels, provideBreadcrumbLabels, provideBreadcrumbManager, provideCalendarLabels, provideCarouselLabels, provideCascaderLabels, provideChipLabels, provideDateFormat, provideDateLocale, provideDateTimeLabels, provideDialogStrategy, provideDialogStrategyDefaults, provideDropzoneLabels, provideFilterOverlay, provideFilterOverlayLabels, provideFormErrorMessageResolver, provideFormFieldLabels, provideFormSupport, provideFullscreenDialogStrategy, provideFullscreenDialogStrategyDefaults, provideGridConfig, provideGridLabels, provideIconOverrides, provideIcons, provideInputLabels, provideLeftSheetStrategy, provideLeftSheetStrategyDefaults, provideLoaderLabels, provideMatchLabels, provideNotificationLabels, provideNotificationManager, provideNotificationManagerConfig, provideNotificationManagerInstance, provideOverlay, provideOverlayManager, provideOverlayRouter, provideOverlayRouterConfig, provideOverlayRouterService, provideOverlayScrollBlocker, providePaginationLabels, providePhoneInputLabels, providePictureConfig, providePipChromeManager, providePipManager, providePipSlotPlaceholderConfig, provideQueryErrorLabels, provideRichTextEditorAlignmentTool, provideRichTextEditorHeadingTool, provideRichTextEditorImageTool, provideRichTextEditorLabels, provideRichTextEditorLanguageTool, provideRichTextEditorLinkEditor, provideRichTextEditorTableTool, provideRichTextEditorTokenRendering, provideRichTextEditorTools, provideRightSheetStrategy, provideRightSheetStrategyDefaults, provideSelectLabels, provideSidebarOverlay, provideSidebarOverlayConfig, provideSidebarOverlayService, provideSliderLabels, provideStandingsLabels, provideStreamConfig, provideStreamConsentConfig, provideStreamLabels, provideStreamManager, provideStreamPlayerErrorConfig, provideStreamPlayerLoadingConfig, provideTableLabels, provideTimeFormat, provideTimePickerLabels, provideTopSheetStrategy, provideTopSheetStrategyDefaults, queryErrorLabelsForLocale, queryErrorResponseFromLegacyError, reconcileColumnOrder, reconcileColumnWidths, reconcileHiddenColumns, reduceSupportPresentation, registerSingleton, requiredLanguages, resizeSpanBounds, resolveBracketLayout, resolveBreakpoint, resolveClosestOverlay, resolveCollisions, resolveFilterOverlaySubmitButton, resolveNotificationStatusIcon, resolveTableCsvRows, richTextEditorToolLabel, rightSheetOverlayStrategy, rowsToPixelHeight, selectOptionsFromQuery, selectOptionsFromV2Query, serializeGridLayout, serializeTableState, setupRichTextEditorLinkEditor, singleEliminationBracketLayout, snapResizeSpan, sortRows, spanHeight, spanWidth, startFullscreenEnterAnimation, startFullscreenLeaveAnimation, startImageUpload, startOfCalendarUnit, stripTrunkZero, supportPresentationIncludesState, swissBracketLayout, tableCsvRowsFromPages, tableFeatureConfig, tableRowsFromQuery, tableRowsFromV2Query, tableToCsv, toChildrenObservable, toGridPosition, toNotificationContent, toPathObservable, toSearchObservable, topSheetOverlayStrategy, transformingBottomSheetToDialogOverlayStrategy, transformingFullScreenDialogToDialogOverlayStrategy, transformingFullScreenDialogToRightSheetOverlayStrategy, wireFormSupport, withPictureBaseUrl };
60053
+ export { ACCORDION_ERROR_CODES, ACCORDION_GROUP_TOKEN, ACCORDION_IMPORTS, ACCORDION_TOKEN, ALIGN_CENTER_ICON, ALIGN_JUSTIFY_ICON, ALIGN_LEFT_ICON, ALIGN_RIGHT_ICON, ARROWS_LEFT_RIGHT_ICON, ARROW_OUT_UP_RIGHT_ICON, ARROW_RIGHT_ICON, ARROW_UP_ICON, AccordionComponent, AccordionContentDirective, AccordionDirective, AccordionGroupComponent, AccordionGroupDirective, AccordionHintDirective, AccordionLabelDirective, AccordionPanelDirective, AccordionTriggerDirective, BOLD_ICON, BRACKET_CARD_CONTEXT, BRACKET_DATA_LAYOUT, BRACKET_DEFAULTS, BRACKET_DENSITY, BRACKET_DENSITY_PRESETS, BRACKET_ERROR_CODES, BRACKET_IMPORTS, BRACKET_LABELS, BRACKET_ROUND_MIRROR_TYPE, BRACKET_SWISS_GROUP_COLOR_TYPE, BREADCRUMB_COLLAPSE_IMPORTS, BREADCRUMB_COLLAPSE_TOKEN, BREADCRUMB_ERROR_CODES, BREADCRUMB_IMPORTS, BREADCRUMB_LABELS, BREADCRUMB_SEGMENT_TOKEN, BREADCRUMB_SEO_IMPORTS, BREADCRUMB_TOKEN, BUTTON_ICON_ALIGNMENTS, BUTTON_IMPORTS, BUTTON_SIZES, BUTTON_SPINNER_CONFIG, BUTTON_TYPES, BUTTON_VARIANTS, BracketComponent, BracketDefaultContinueComponent, BracketDefaultFinalMatchComponent, BracketDefaultMatchComponent, BracketDefaultRoundHeaderComponent, BracketMap, BracketRoundsListComponent, BrandLoaderComponent, BreadcrumbCollapseDirective, BreadcrumbComponent, BreadcrumbDirective, BreadcrumbItemDirective, BreadcrumbItemTemplateDirective, BreadcrumbOutletComponent, BreadcrumbOverflowComponent, BreadcrumbSegmentDirective, BreadcrumbSeoDirective, BreadcrumbSeparatorDirective, ButtonComponent, ButtonDirective, ButtonStylesDirective, CALENDAR_ERROR_CODES, CALENDAR_ICON, CALENDAR_IMPORTS, CALENDAR_LABELS, CAROUSEL_AUTOPLAY_TOKEN, CAROUSEL_ERROR_CODES, CAROUSEL_IMPORTS, CAROUSEL_LABELS, CAROUSEL_SLIDE_ALIGNMENTS, CAROUSEL_TOKEN, CAROUSEL_TRANSITIONS, CAROUSEL_TRANSITION_DRIVERS, CASCADER_ERROR_CODES, CASCADER_IMPORTS, CASCADER_LABELS, CASCADER_SELECTABLE_LEVELS, CHECKBOX_GROUP_IMPORTS, CHECKBOX_IMPORTS, CHECKBOX_OPTION_VARIANTS, CHECK_ICON, CHEVRON_ICON, CHIP_ERROR_CODES, CHIP_IMPORTS, CHIP_LABELS, CHOICE_FIELD_IMPORTS, CHOICE_FIELD_VARIANTS, CIRCLE_CHECK_ICON, CIRCLE_INFO_ICON, CLIPBOARD_CHECK_ICON, CLOCK_ICON, CODE_BLOCK_ICON, CODE_ICON, COLOR_INPUT_IMPORTS, COMMON_BRACKET_ROUND_TYPE, CalendarCellDirective, CalendarComponent, CalendarDirective, CalendarGridDirective, CalendarHeaderDirective, CarouselAutoplayDirective, CarouselComponent, CarouselDirective, CarouselItemDirective, CarouselNextDirective, CarouselPlayToggleDirective, CarouselPreviousDirective, CarouselSlideDirective, CascaderColumnDirective, CascaderComponent, CascaderDirective, CascaderNodeDirective, CascaderPanelComponent, CascaderSearchDirective, CascaderSearchOptionDirective, CascaderSurfaceDirective, CascaderTriggerDirective, CheckboxComponent, CheckboxDirective, CheckboxGroupComponent, CheckboxGroupSelectAllComponent, CheckboxOptionComponent, ChipComponent, ChipDirective, ChipRemoveDirective, ChoiceFieldComponent, ColorInputComponent, ColorInputDirective, CounterComponent, DAILYMOTION_PLAYER_TOKEN, DATE_FORMAT, DATE_INPUT_ERROR_CODES, DATE_INPUT_IMPORTS, DATE_LOCALE, DATE_PICKER_HOST, DATE_RANGE_INPUT_ERROR_CODES, DATE_RANGE_INPUT_IMPORTS, DATE_TIME_INPUT_ERROR_CODES, DATE_TIME_INPUT_IMPORTS, DATE_TIME_LABELS, DEFAULT_BRACKET_LABELS, DEFAULT_BREADCRUMB_LABELS, DEFAULT_BREAKPOINTS, DEFAULT_CALENDAR_LABELS, DEFAULT_CALENDAR_RANGE_STRATEGY, DEFAULT_CAROUSEL_LABELS, DEFAULT_CASCADER_LABELS, DEFAULT_CHIP_LABELS, DEFAULT_DATE_TIME_LABELS, DEFAULT_DROPZONE_LABELS, DEFAULT_FILTER_OVERLAY_LABELS, DEFAULT_FORM_FIELD_LABELS, DEFAULT_GRID_LABELS, DEFAULT_ICON_VARIANT, DEFAULT_INPUT_LABELS, DEFAULT_LOADER_LABELS, DEFAULT_MATCH_CARD_START_TIME_FORMAT, DEFAULT_MATCH_LABELS, DEFAULT_NOTIFICATION_LABELS, DEFAULT_NOTIFICATION_MANAGER_CONFIG, DEFAULT_NOTIFICATION_STATUS_ICONS, DEFAULT_PAGINATION_LABELS, DEFAULT_PHONE_INPUT_LABELS, DEFAULT_PIP_WINDOW_CONFIG, DEFAULT_QUERY_ERROR_LABELS, DEFAULT_RICH_TEXT_EDITOR_LABELS, DEFAULT_RICH_TEXT_EDITOR_TOOLS, DEFAULT_SELECT_LABELS, DEFAULT_SLIDER_LABELS, DEFAULT_STANDINGS_LABELS, DEFAULT_STREAM_LABELS, DEFAULT_STREAM_PLAYER_STATE, DEFAULT_TABLE_LABELS, DEFAULT_TIME_PICKER_LABELS, DESCRIPTION_IMPORTS, DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE, DROPZONE_ENTRY_STATUSES, DROPZONE_ERROR_CODES, DROPZONE_FILES_ERROR_KIND, DROPZONE_FILE_CONSTRAINTS, DROPZONE_FILE_REJECTION_REASONS, DROPZONE_IMPORTS, DROPZONE_LABELS, DURATION_INPUT_ERROR_CODES, DURATION_INPUT_IMPORTS, DailymotionPlayerComponent, DailymotionPlayerDirective, DailymotionPlayerParamsDirective, DailymotionPlayerSlotComponent, DateInputComponent, DateInputDirective, DateInputFieldDirective, DatePickerPanelComponent, DatePickerSurfaceDirective, DatePickerTriggerDirective, DateRangeInputComponent, DateRangeInputDirective, DateRangeInputFieldDirective, DateTimeInputComponent, DateTimeInputDirective, DateTimeInputFieldDirective, DescriptionComponent, DropzoneComponent, DropzoneDirective, DurationInputComponent, DurationInputDirective, DurationInputFieldDirective, ELLIPSIS_ICON, ELLIPSIS_VERTICAL_ICON, ET_BUILT_IN_ICON_NAMES, EYE_ICON, EYE_SLASH_ICON, FACEBOOK_PLAYER_TOKEN, FILE_ICON, FILTER_ICON, FILTER_OVERLAY_ERROR_CODES, FILTER_OVERLAY_IMPORTS, FILTER_OVERLAY_LABELS, FILTER_OVERLAY_TOKEN, FLOATING_ACTION_ERROR_CODES, FLOATING_ACTION_IMPORTS, FLOATING_ACTION_STATES, FLOATING_ACTION_TOKEN, FLOPPY_DISK_ICON, FOCUS_FRAME_ICON, FORM_ERROR_MESSAGE_RESOLVER, FORM_FIELD_APPEARANCES, FORM_FIELD_CONTROL_TYPES, FORM_FIELD_ERROR_CODES, FORM_FIELD_FILLS, FORM_FIELD_IMPORTS, FORM_FIELD_LABELS, FORM_FIELD_LABEL_MODES, FORM_FIELD_SIZES, FORM_FIELD_TOKEN, FabComponent, FacebookPlayerComponent, FacebookPlayerDirective, FacebookPlayerParamsDirective, FacebookPlayerSlotComponent, FilterOverlayResetDirective, FilterOverlaySubmitDirective, FloatingActionAnchorDirective, FloatingActionDirective, FloatingActionScopeDirective, FloatingActionTopDirective, FloatingActionTriggerDirective, FocusRingDirective, FormErrorComponent, FormFieldComponent, FormFieldDirective, GERMAN_FILTER_OVERLAY_LABELS, GERMAN_QUERY_ERROR_LABELS, GRID_2X2_ICON, GRID_DEBUG_IMPORTS, GRID_ERROR_CODES, GRID_IMPORTS, GRID_LABELS, GRID_TOKEN, GROUP_BRACKET_ROUND_TYPE, GridComponent, GridDebugComponent, GridDirective, GridDragDirective, GridItemComponent, GridItemDefaultActionsComponent, GridItemDirective, GridItemRef, GridItemToolbarComponent, GridResizeDirective, HEADING_1_ICON, HEADING_2_ICON, HEADING_3_ICON, HintComponent, ICONS_TOKEN, ICON_DIRECTIVE_TOKEN, ICON_ERROR_CODES, ICON_IMPORTS, ICON_OVERRIDES_TOKEN, IMAGE_ICON, INITIAL_SUPPORT_PRESENTATION_STATE, INPUT_IMPORTS, INPUT_LABELS, INPUT_MASK_HOST, INPUT_TEXT_ALIGNMENTS, INPUT_TYPES, INTERACTIVE_TAGS, ITALIC_ICON, IconButtonComponent, IconDirective, InputComponent, InputDirective, InputMaskDirective, InputPrefixDirective, InputSuffixDirective, KICK_PLAYER_TOKEN, KickPlayerComponent, KickPlayerDirective, KickPlayerParamsDirective, KickPlayerSlotComponent, LINK_ICON, LIST_BULLETED_ICON, LIST_NUMBERED_ICON, LOADER_LABELS, LOCK_ICON, LabelDirective, MASKED_INPUT_ERROR_CODES, MASKED_INPUT_IMPORTS, MASK_VALUE_MODES, MASONRY_ERROR_CODES, MASONRY_IMPORTS, MASONRY_TOKEN, MATCH_CARD_IMPORTS, MATCH_CARD_SIZES, MATCH_CARD_TOKEN, MATCH_ERROR_CODES, MATCH_LABELS, MATCH_PARTICIPANT_IMPORTS, MENU_ERROR_CODES, MENU_IMPORTS, MENU_ITEM_VARIANTS, MENU_SELECTION_GROUP_MULTIPLE, MENU_SELECTION_GROUP_TOKEN, MENU_SELECTION_ITEM_KIND, MINUS_ICON, MULTI_LANGUAGE_RICH_TEXT_EDITOR_ERROR_CODES, MULTI_LANGUAGE_RICH_TEXT_EDITOR_IMPORTS, MasonryDirective, MasonryItemDirective, MatchCardComponent, MatchCardDirective, MatchCardGameScoresDirective, MatchCardMetaDirective, MatchCardScoreDirective, MatchParticipantComponent, MatchScoreComponent, MenuCheckboxGroupComponent, MenuCheckboxItemComponent, MenuComponent, MenuContextTriggerDirective, MenuDirective, MenuGroupLabelComponent, MenuItemComponent, MenuItemDirective, MenuItemShortcutComponent, MenuPanelDirective, MenuRadioGroupComponent, MenuRadioItemComponent, MenuSearchDirective, MenuSelectionGroupDirective, MenuSelectionItemDirective, MenuSeparatorComponent, MenuSurfaceDirective, MenuTriggerDirective, MultiLanguageRichTextEditorComponent, MultiLanguageRichTextEditorDirective, MultiLanguageRichTextEditorLanguageToolComponent, NAV_TABS_TOKEN, NAV_TAB_IMPORTS, NOTIFICATION_ACTION_SLOTS, NOTIFICATION_ERROR_CODES, NOTIFICATION_IMPORTS, NOTIFICATION_LABELS, NOTIFICATION_STACK_CONTEXT_TOKEN, NOTIFICATION_STATUS, NUMBER_INPUT_IMPORTS, NavTabLinkComponent, NavTabLinkDirective, NavTabsComponent, NavTabsDirective, NavTabsOutletComponent, NavTabsOutletDirective, NotificationActionDirective, NotificationComponent, NotificationDirective, NotificationDismissDirective, NotificationItemDirective, NotificationStackDirective, NotificationSwipeToDismissDirective, NumberInputComponent, NumberInputDirective, OTP_INPUT_IMPORTS, OVERLAY_BACK_OR_CLOSE_TOKEN, OVERLAY_BODY_TOKEN, OVERLAY_CONFIG_CLASS_KEYS, OVERLAY_CONTENT_IMPORTS, OVERLAY_ERROR_CODES, OVERLAY_FOOTER_TOKEN, OVERLAY_HEADER_TEMPLATE_TOKEN, OVERLAY_HEADER_TOKEN, OVERLAY_IMPORTS, OVERLAY_MAIN_TOKEN, OVERLAY_QUERY_PARAM_INPUT_NAME, OVERLAY_REF, OVERLAY_ROUTER_CONFIG_TOKEN, OVERLAY_ROUTER_LINK_TOKEN, OVERLAY_ROUTER_OUTLET_DISABLED_TEMPLATE_TOKEN, OVERLAY_ROUTER_OUTLET_TOKEN, OVERLAY_ROUTER_TOKEN, OVERLAY_SHARED_ROUTE_TEMPLATE_TOKEN, OtpInputComponent, OtpInputDirective, OverlayAnchorDirective, OverlayBackOrCloseDirective, OverlayBodyComponent, OverlayCloseDirective, OverlayContainerComponent, OverlayDirective, OverlayFooterDirective, OverlayHeaderDirective, OverlayHeaderTemplateDirective, OverlayMainDirective, OverlayOriginCloneComponent, OverlayRouteHeaderTemplateOutletComponent, OverlayRouterLinkDirective, OverlayRouterOutletComponent, OverlayRouterOutletDisabledTemplateDirective, OverlaySharedRouteTemplateDirective, OverlaySharedRouteTemplateOutletComponent, OverlaySidebarComponent, OverlaySidebarPageComponent, OverlaySurfaceDirective, OverlayTitleDirective, OverlayTriggerDirective, PAGE_SIZE_SELECT_IMPORTS, PAGINATION_IMPORTS, PAGINATION_LABELS, PARAGRAPH_ICON, PASSWORD_INPUT_IMPORTS, PAUSE_ICON, PENCIL_ICON, PHONE_COUNTRIES, PHONE_INPUT_ERROR_CODES, PHONE_INPUT_IMPORTS, PHONE_INPUT_LABELS, PICTURE_IMPORTS, PICTURE_STATES, PIP_CHROME_REF_TOKEN, PIP_ENTRY_TOKEN, PIP_WINDOW_ASPECT_RATIO_TOKEN, PLAY_ICON, PLUS_ICON, PageSizeSelectComponent, PaginationComponent, PaginationDirective, PaginationSeoDirective, PasswordInputComponent, PasswordInputDirective, PhoneInputComponent, PhoneInputDirective, PhoneInputFieldDirective, PhoneInputFlagDirective, PictureComponent, PictureErrorDirective, PicturePlaceholderDirective, PipBackDirective, PipBringBackDirective, PipCellDirective, PipCloseDirective, PipCollapseOverlayDirective, PipGridToggleDirective, PipPlayerComponent, PipSlotPlaceholderComponent, PipStageDirective, PipTitleBarDirective, PipTitleBarTemplateDirective, PipWindowComponent, PipWindowParamsDirective, ProgressBarComponent, QUERY_DEVTOOLS_IMPORTS, QUERY_ERROR_ERROR_CODES, QUERY_ERROR_IMPORTS, QUERY_ERROR_LABELS, QUERY_ERROR_TOKEN, QUOTE_ICON, QueryDevtoolsComponent, QueryDevtoolsJsonComponent, QueryDevtoolsToggleComponent, QueryErrorActionsDirective, QueryErrorComponent, QueryErrorDirective, QueryErrorTitleDirective, QueryParamOverlayLinkDirective, RADIO_GROUP_IMPORTS, RADIO_VARIANTS, RATING_IMPORTS, REDO_ICON, RICH_TEXT_EDITOR_HEADING_OPTIONS, RICH_TEXT_EDITOR_IMPORTS, RICH_TEXT_EDITOR_INLINE_TOOLS, RICH_TEXT_EDITOR_LABELS, RICH_TEXT_EDITOR_LANGUAGE_TOOL, RICH_TEXT_EDITOR_LINK_EDITOR, RICH_TEXT_EDITOR_TOKEN_CODEC, RICH_TEXT_EDITOR_TOKEN_PALETTE_IMPORTS, RICH_TEXT_EDITOR_TOOL, RICH_TEXT_EDITOR_TOOLS, RICH_TEXT_EDITOR_TOOL_BUTTONS, RICH_TEXT_EDITOR_TRIGGERS_IMPORTS, ROTATE_RIGHT_ICON, RadioComponent, RadioGroupComponent, RangeSliderComponent, RangeSliderDirective, RatingComponent, RatingDirective, RatingIconDirective, RichTextEditorAlignToolComponent, RichTextEditorComponent, RichTextEditorDirective, RichTextEditorFloatingToolbarComponent, RichTextEditorFloatingToolbarDirective, RichTextEditorHeadingToolComponent, RichTextEditorImageEditorComponent, RichTextEditorImageStylesComponent, RichTextEditorImageToolComponent, RichTextEditorLinkEditorComponent, RichTextEditorLinkEditorDirective, RichTextEditorTableStylesComponent, RichTextEditorTableToolComponent, RichTextEditorTokenPaletteComponent, RichTextEditorTokenPopupComponent, RichTextEditorTriggersDirective, SCROLLABLE_DARKEN_IMPORTS, SCROLLABLE_DRAG_IMPORTS, SCROLLABLE_ERROR_CODES, SCROLLABLE_IMPORTS, SCROLLABLE_NAVIGATION_IMPORTS, SEGMENTED_BUTTON_GROUP_VARIANTS, SEGMENTED_BUTTON_IMPORTS, SELECTION_LIST_IMPORTS, SELECTION_LIST_MULTIPLE, SELECTION_LIST_TOKEN, SELECT_ERROR_CODES, SELECT_FILTER_MODES, SELECT_IMPORTS, SELECT_LABELS, SIDEBAR_OVERLAY_CONFIG, SIDEBAR_OVERLAY_TOKEN, SINGLE_ELIMINATION_BRACKET_ROUND_TYPE, SKELETON_IMPORTS, SLIDER_ERROR_CODES, SLIDER_IMPORTS, SLIDER_LABELS, SLIDER_MARK_VALUE_ATTRIBUTE, SLIDER_TOKEN, SNAP_HYSTERESIS, SOOP_PLAYER_TOKEN, SPLIT_BUTTON_ERROR_CODES, STANDINGS_ERROR_CODES, STANDINGS_IMPORTS, STANDINGS_LABELS, STAR_ICON, STREAM_ALL_IMPORTS, STREAM_CONSENT_TOKEN, STREAM_DAILYMOTION_IMPORTS, STREAM_FACEBOOK_IMPORTS, STREAM_IMPORTS, STREAM_KICK_IMPORTS, STREAM_LABELS, STREAM_PIP_IMPORTS, STREAM_PLAYER_COMPONENT_TOKEN, STREAM_PLAYER_ERROR_CONTEXT_TOKEN, STREAM_PLAYER_ERROR_TOKEN, STREAM_PLAYER_PARAMS_TOKEN, STREAM_PLAYER_SLOT_TOKEN, STREAM_PLAYER_TOKEN, STREAM_SLOT_PLAYER_ID_TOKEN, STREAM_SOOP_IMPORTS, STREAM_TIKTOK_IMPORTS, STREAM_TWITCH_IMPORTS, STREAM_USER_CONSENT_PROVIDER_TOKEN, STREAM_VIMEO_IMPORTS, STREAM_YOUTUBE_IMPORTS, STRIKETHROUGH_ICON, SUPPORT_CONTENT_STATE, SUPPORT_TRANSITION_DIRECTION, SWISS_ADVANCE_WINS, SWISS_BRACKET_ROUND_TYPE, SWISS_ELIMINATE_LOSSES, SWITCH_IMPORTS, ScrollableActiveChildDirective, ScrollableButtonsComponent, ScrollableButtonsDirective, ScrollableComponent, ScrollableDarkenDirective, ScrollableDirective, ScrollableDragDirective, ScrollableIgnoreChildDirective, ScrollableLoadingTemplateDirective, ScrollableMasksComponent, ScrollableNavigationComponent, ScrollableNavigationDirective, ScrollableSnapDirective, SegmentedButtonComponent, SegmentedButtonGroupComponent, SelectComponent, SelectDirective, SelectEmptyDirective, SelectErrorDirective, SelectListboxDirective, SelectLoadingDirective, SelectOptionComponent, SelectOptionDirective, SelectOptionGroupComponent, SelectOptionGroupDirective, SelectOptionTemplateDirective, SelectOptionsDirective, SelectPanelComponent, SelectSearchDirective, SelectSurfaceDirective, SelectTriggerDirective, SelectValueDirective, SelectViewportDirective, SelectVirtualOptionComponent, SelectVirtualOptionDirective, SelectionListControlDirective, SelectionListDirective, SelectionOptionDirective, SkeletonComponent, SkeletonItemComponent, SkeletonTextComponent, SliderComponent, SliderDirective, SliderThumbDirective, SliderThumbLabelDirective, SliderTrackDirective, SoopPlayerComponent, SoopPlayerDirective, SoopPlayerParamsDirective, SoopPlayerSlotComponent, SpinnerComponent, SplitButtonActionDirective, SplitButtonComponent, SplitButtonDirective, SplitButtonTriggerDirective, StandingsComponent, StandingsDirective, StreamConsentAcceptDirective, StreamConsentComponent, StreamConsentDirective, StreamPipChromeComponent, StreamPlayerErrorComponent, StreamPlayerErrorDirective, StreamPlayerLoadingComponent, StreamPlayerSlotDirective, SwitchComponent, SwitchDirective, TABLE_CELL_ERROR_TOOLTIP_IMPORTS, TABLE_COLUMN_CHOOSER_IMPORTS, TABLE_COLUMN_MENU_IMPORTS, TABLE_CSV_EXPORT_IMPORTS, TABLE_ERROR_CODES, TABLE_FEATURE_HOST, TABLE_FILTER_IMPORTS, TABLE_ICON, TABLE_IMPORTS, TABLE_INLINE_EDIT_IMPORTS, TABLE_KEYBOARD_NAV_IMPORTS, TABLE_LABELS, TABLE_REORDER_IMPORTS, TABLE_RESIZE_IMPORTS, TABLE_SELECTION_IMPORTS, TABLE_STATE_PERSISTENCE_IMPORTS, TABLE_VIRTUAL_SCROLL_IMPORTS, TAB_BAR_FITS, TAB_BAR_ORIENTATIONS, TAB_BAR_TOKEN, TAB_BAR_TRIGGER_TOKEN, TAB_BAR_VARIANTS, TAB_ERROR_CODES, TAB_GROUP_TOKEN, TAB_IMPORTS, TAB_PANEL_TOKEN, TAB_SIZES, TAG_INPUT_ERROR_CODES, TAG_INPUT_IMPORTS, TEXTAREA_IMPORTS, TEXTAREA_RESIZE_MODES, TEXT_ALIGNS, TIKTOK_PLAYER_TOKEN, TIMES_ICON, TIME_FORMAT, TIME_INPUT_ERROR_CODES, TIME_INPUT_IMPORTS, TIME_PICKER_ERROR_CODES, TIME_PICKER_IMPORTS, TIME_PICKER_LABELS, TOGGLETIP_ERROR_CODES, TOGGLETIP_IMPORTS, TOOLTIP_ERROR_CODES, TOOLTIP_IMPORTS, TOURNAMENT_MODE, TRIANGLE_EXCLAMATION_ICON, TROPHY_ICON, TWITCH_PLAYER_TOKEN, TabBarDirective, TabBarTriggerDirective, TabBarUnderlineDirective, TabComponent, TabGroupComponent, TabGroupDirective, TabLabelDirective, TabPanelDirective, TabTriggerDirective, TableCellDirective, TableCellEditDirective, TableCellErrorMarkComponent, TableCellErrorTooltipDirective, TableCellSkeletonDirective, TableColumnChooserComponent, TableColumnMenuDirective, TableColumnMenuTriggerComponent, TableComponent, TableCsvExportDirective, TableDetailStylesComponent, TableExpanderCellComponent, TableFilterOptionDirective, TableFilterTriggerComponent, TableFiltersDirective, TableFooterCellDirective, TableFooterDirective, TableHeaderCellDirective, TableInlineEditDirective, TableInlineEditStylesComponent, TableKeyboardNavDirective, TableReorderDirective, TableReorderOverlayComponent, TableResizeDirective, TableResizeGripComponent, TableSelectAllCellComponent, TableSelectCellComponent, TableSelectionDirective, TableStatePersistenceDirective, TableVirtualScrollDirective, TableVirtualScrollStylesComponent, TagInputComponent, TagInputDirective, TagInputFieldDirective, TextButtonComponent, TextFieldControlDirective, TextareaComponent, TextareaDirective, TikTokPlayerComponent, TikTokPlayerDirective, TikTokPlayerParamsDirective, TikTokPlayerSlotComponent, TimeInputComponent, TimeInputDirective, TimeInputFieldDirective, TimePickerColumnDirective, TimePickerComponent, TimePickerDirective, TimePickerOptionDirective, ToggletipCloseDirective, ToggletipComponent, ToggletipDirective, ToggletipTriggerDirective, TooltipComponent, TooltipDirective, TwitchPlayerComponent, TwitchPlayerDirective, TwitchPlayerParamsDirective, TwitchPlayerSlotComponent, UNDERLINE_ICON, UNDO_ICON, UNIT_MS, UPLOAD_ICON, VIMEO_PLAYER_TOKEN, VimeoPlayerComponent, VimeoPlayerDirective, VimeoPlayerParamsDirective, VimeoPlayerSlotComponent, WINDOW_CONTROL_BUTTON_KINDS, WINDOW_CONTROL_BUTTON_SIZES, WindowControlButtonComponent, YOUTUBE_PLAYER_SLOT_TOKEN, YOUTUBE_PLAYER_TOKEN, YoutubePlayerComponent, YoutubePlayerDirective, YoutubePlayerParamsDirective, YoutubePlayerSlotComponent, YoutubePlayerSlotDirective, abortFullscreenAnimation, anchoredDialogOverlayStrategy, anchoredOverlayStrategy, autoPlace, bottomSheetOverlayStrategy, bracketFitsWidth, bracketNaturalWidth, buildAnchoredRuntimePositionStrategy, canHaveChildren, cascaderFromQuery, centeredOverlayStrategy, clampPosition, clampResizeRect, cleanupFullscreenAnimation, cleanupFullscreenAnimationStyles, compactLayout, computeGeometry, computeGridHeight, createAnchoredPanelController, createAutoScroller, createBracket, createBracketBase, createCardMask, createCurrencyMask, createDefaultDropzoneArgs, createDropzoneUpload, createExistingDropzoneEntry, createFileDropzoneEntry, createFixedLengthRangeStrategy, createGridAdapter, createIbanMask, createMatchesMapBase, createNewMatchParticipantBase, createNormalizedBracketMatch, createNotificationPromiseFn, createNotificationRef, createOverlayOpener, createOverlayRef, createOverlayStrategyController, createOverlayUnsavedChangesGuard, createParticipantsMapBase, createPipChromeAnimations, createPipChromeState, createRichTextEditorTrigger, createRichTextEditorTriggerWithQuery, createRoundsMapBase, createStreamConfig, createStreamPlayerSlot, createTableRowsSource, createTableStateStorage, createV2DropzoneUpload, createWeekRangeStrategy, defaultCompareWith, defaultDropzoneRejectionMessage, defineOverlay, defineQueryParamOverlay, deriveDurationFormatSpec, deserializeGridLayout, deserializeTableState, dialogOverlayStrategy, disposeDropzoneEntry, doubleEliminationBracketLayout, dropzoneFiles, enableDragToDismiss, extractFirstImageUrl, filterOverlayLabelsForLocale, filterOverlayPreviewFromQuery, filterRows, findCollision, findNextRelevantHtmlElement, findScrollableAncestor, formatDuration, formatFileSize, fromGridPosition, fullScreenDialogOverlayStrategy, generateBracketDataForEthlete, generateBracketRoundSwissGroupMaps, generateMatchPosition, generateMatchRelationPositions, generateMatchRelationsNew, generateRoundRelationsNew, generateRoundTypeFromEthleteRoundType, generateTournamentModeFormEthleteRounds, getAvailableSwissGroupsForRound, getClosestOverlay, getOriginCoordinatesAndDimensions, getSwissGroupColorType, hysteresisRound, iconRegistryKey, indexOfNode, injectAnchoredDialogStrategy, injectAnchoredDialogStrategyDefaults, injectBottomSheetStrategy, injectBottomSheetStrategyDefaults, injectBracketConfig, injectBracketLabels, injectBreadcrumbLabels, injectBreadcrumbManager, injectCalendarLabels, injectCarouselLabels, injectCascaderLabels, injectChipLabels, injectDateFormat, injectDateLocale, injectDateTimeLabels, injectDialogStrategy, injectDialogStrategyDefaults, injectDropzoneLabels, injectFilterOverlay, injectFilterOverlayLabels, injectFormFieldLabels, injectFormSupport, injectFullscreenDialogStrategy, injectFullscreenDialogStrategyDefaults, injectGridConfig, injectGridLabels, injectInputLabels, injectLeftSheetStrategy, injectLeftSheetStrategyDefaults, injectLoaderLabels, injectMatchLabels, injectNotificationLabels, injectNotificationManager, injectNotificationManagerConfig, injectOverlayManager, injectOverlayRouter, injectOverlayScrollBlocker, injectOverlaySurfaceContext, injectPaginationLabels, injectPhoneInputLabels, injectPictureConfig, injectPipChromeManager, injectPipManager, injectPipSlotPlaceholderConfig, injectQueryErrorLabels, injectRichTextEditorLabels, injectRichTextEditorTools, injectRightSheetStrategy, injectRightSheetStrategyDefaults, injectSelectLabels, injectSidebarOverlay, injectSliderLabels, injectStandingsLabels, injectStreamConfig, injectStreamConsentConfig, injectStreamLabels, injectStreamManager, injectStreamPlayerErrorConfig, injectStreamPlayerLoadingConfig, injectStreamScriptLoader, injectStreamUserConsentProvider, injectTableCsvExport, injectTableFeatureHost, injectTableLabels, injectTimeFormat, injectTimePickerLabels, injectTopSheetStrategy, injectTopSheetStrategyDefaults, isFileAccepted, isHtmlElement, isInteractiveElement, isPointerEvent, isTargetInsideOverlayTree, isTouchEvent, itemsCollide, leftSheetOverlayStrategy, legacyQueryErrorSource, mapLayoutToBreakpoint, matchCountryByDialCode, matchParticipantDisplayName, mergeOverlayBreakpointConfigs, mergeOverlayConfigs, mirroredDoubleEliminationBracketLayout, mirroredSingleEliminationBracketLayout, mountRichTextEditorImageStyles, mountRichTextEditorTableStyles, nodesEqual, normalizeEthleteBracketMatch, normalizeEthleteGroupRanking, normalizeEthleteMatch, normalizeEthleteMatchStatus, normalizeEthleteMedia, normalizeEthleteParticipant, normalizeEthletePlacement, normalizePictureSizes, normalizePictureSource, paginate, parseDuration, phoneCountryFlag, phoneCountryName, pixelRectsEqual, positionToPixelRect, positionsEqual, projectDragCell, provideAnchoredDialogStrategy, provideAnchoredDialogStrategyDefaults, provideBottomSheetStrategy, provideBottomSheetStrategyDefaults, provideBracketConfig, provideBracketLabels, provideBreadcrumbLabels, provideBreadcrumbManager, provideCalendarLabels, provideCarouselLabels, provideCascaderLabels, provideChipLabels, provideDateFormat, provideDateLocale, provideDateTimeLabels, provideDialogStrategy, provideDialogStrategyDefaults, provideDropzoneLabels, provideFilterOverlay, provideFilterOverlayLabels, provideFormErrorMessageResolver, provideFormFieldLabels, provideFormSupport, provideFullscreenDialogStrategy, provideFullscreenDialogStrategyDefaults, provideGridConfig, provideGridLabels, provideIconOverrides, provideIcons, provideInputLabels, provideLeftSheetStrategy, provideLeftSheetStrategyDefaults, provideLoaderLabels, provideMatchLabels, provideNotificationLabels, provideNotificationManager, provideNotificationManagerConfig, provideNotificationManagerInstance, provideOverlay, provideOverlayManager, provideOverlayRouter, provideOverlayRouterConfig, provideOverlayRouterService, provideOverlayScrollBlocker, providePaginationLabels, providePhoneInputLabels, providePictureConfig, providePipChromeManager, providePipManager, providePipSlotPlaceholderConfig, provideQueryErrorLabels, provideRichTextEditorAlignmentTool, provideRichTextEditorHeadingTool, provideRichTextEditorImageTool, provideRichTextEditorLabels, provideRichTextEditorLanguageTool, provideRichTextEditorLinkEditor, provideRichTextEditorTableTool, provideRichTextEditorTokenRendering, provideRichTextEditorTools, provideRightSheetStrategy, provideRightSheetStrategyDefaults, provideSelectLabels, provideSidebarOverlay, provideSidebarOverlayConfig, provideSidebarOverlayService, provideSliderLabels, provideStandingsLabels, provideStreamConfig, provideStreamConsentConfig, provideStreamLabels, provideStreamManager, provideStreamPlayerErrorConfig, provideStreamPlayerLoadingConfig, provideTableLabels, provideTimeFormat, provideTimePickerLabels, provideTopSheetStrategy, provideTopSheetStrategyDefaults, queryErrorLabelsForLocale, queryErrorResponseFromLegacyError, reconcileColumnOrder, reconcileColumnWidths, reconcileHiddenColumns, reduceSupportPresentation, registerSingleton, requiredLanguages, resizeSpanBounds, resolveBracketLayout, resolveBreakpoint, resolveClosestOverlay, resolveCollisions, resolveFilterOverlaySubmitButton, resolveNotificationStatusIcon, resolveTableCsvRows, richTextEditorToolLabel, rightSheetOverlayStrategy, rowsToPixelHeight, selectOptionsFromQuery, selectOptionsFromV2Query, serializeGridLayout, serializeTableState, setupRichTextEditorLinkEditor, singleEliminationBracketLayout, snapResizeSpan, sortRows, spanHeight, spanWidth, startFullscreenEnterAnimation, startFullscreenLeaveAnimation, startImageUpload, startOfCalendarUnit, stripTrunkZero, supportPresentationIncludesState, swissBracketLayout, tableCsvRowsFromPages, tableFeatureConfig, tableRowsFromQuery, tableRowsFromV2Query, tableToCsv, toChildrenObservable, toGridPosition, toNotificationContent, toPathObservable, toSearchObservable, topSheetOverlayStrategy, transformingBottomSheetToDialogOverlayStrategy, transformingFullScreenDialogToDialogOverlayStrategy, transformingFullScreenDialogToRightSheetOverlayStrategy, wireFormSupport, withPictureBaseUrl };
58774
60054
  //# sourceMappingURL=ethlete-components.mjs.map