@ethlete/query-devtools 1.0.0-next.14 → 1.0.0-next.15

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,13 +1,184 @@
1
+ import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
2
+ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
1
3
  import * as i0 from '@angular/core';
2
- import { signal, inject, ElementRef, ViewEncapsulation, Component, input, output, computed, viewChild, effect, numberAttribute, linkedSignal, InjectionToken, untracked, NgZone, DestroyRef, booleanAttribute } from '@angular/core';
3
- import { queryDevtoolsAbout, hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsMocks, queryDevtoolsAuthSessions, queryDevtoolsArmedMocks, clearQueryDevtoolsAuthSessions, queryDevtoolsApiEnvs, queryDevtoolsApiEnvValues, setQueryDevtoolsApiEnv, queryDevtoolsSettings, queryDevtoolsAllowsLocalAuthSessions, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, setQueryDevtoolsSettings, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, queryDevtoolsApiEnvIsProduction, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthActive, queryDevtoolsAuthTabLocal, switchQueryDevtoolsAuthSession, renameQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor, logoutQueryDevtoolsAuthSession, setQueryDevtoolsAuthTabLocal, loginQueryDevtoolsAuthAccount, setQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthCredentials, removeQueryDevtoolsAuthAccount, queryDevtoolsAuthFieldsFor, addQueryDevtoolsAuthAccount, setQueryDevtoolsTokenTtl, queryDevtoolsTokenTtls, clearQueryDevtoolsTokenTtl, serializeQueryDevtoolsOverrideTransfer, parseQueryDevtoolsOverrideTransfer, armQueryDevtoolsOverrideTransfer, countUnresolvedQueryDevtoolsOverrides, createQueryDevtoolsOverrides, applyQueryDevtoolsOverrides, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, measureQueryDevtoolsPayload, seedQueryDevtoolsSchemaBody, loadQueryDevtoolsSchema, queryDevtoolsMockId, saveQueryDevtoolsMock, armQueryDevtoolsMock, deleteQueryDevtoolsMock, collectQueryDevtoolsSchemaComponents, seedQueryDevtoolsSchemaRoute, QUERY_DEVTOOLS_FAULT_STATUSES, setQueryDevtoolsFault, clearQueryDevtoolsTombstones, readQueryDevtoolsStore, createQueryKeyLockManager, queryDevtoolsEntries, isQueryDevtoolsRepositoryLive, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, queryDevtoolsFaultsRestored, queryDevtoolsArmedMocksRestored, restoredQueryDevtoolsOverrides, queryDevtoolsRestoredOverridesScope, clearRestoredQueryDevtoolsOverrides, setQueryDevtoolsUiMounted, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, createQueryErrorResponse, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, resolveQueryHeaders, sumQueryDevtoolsStats } from '@ethlete/query';
4
+ import { input, ViewEncapsulation, Component, InjectionToken, inject, signal, output, computed, Directive, ElementRef, viewChild, effect, numberAttribute, linkedSignal, untracked, NgZone, DestroyRef, booleanAttribute } from '@angular/core';
4
5
  import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
6
+ import { injectStyleManager, injectSemanticColorTheme, ProvideColorDirective, clamp, injectRenderer, injectFileDownload, injectViewportSize, injectIsDocumentVisible, injectBreakpointObserver, signalElementDimensions, randomId, reserveOverlayViewportSpace, createObjectUrlHandle, DragHandleDirective, ResizeHandlesComponent } from '@ethlete/core';
7
+ import { setQueryDevtoolsTokenTtl, clearQueryDevtoolsTokenTtl, createQueryKeyLockManager, setQueryDevtoolsUiMounted, createQueryErrorResponse, resolveQueryHeaders } from '@ethlete/query';
8
+ import { hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, queryDevtoolsApiEnvIsProduction, queryDevtoolsSettings, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthActive, queryDevtoolsAuthTabLocal, switchQueryDevtoolsAuthSession, renameQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor, logoutQueryDevtoolsAuthSession, setQueryDevtoolsAuthTabLocal, loginQueryDevtoolsAuthAccount, setQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthCredentials, removeQueryDevtoolsAuthAccount, queryDevtoolsAuthFieldsFor, addQueryDevtoolsAuthAccount, setQueryDevtoolsSettings, clearQueryDevtoolsAuthSessions, queryDevtoolsTokenTtls, serializeQueryDevtoolsOverrideTransfer, parseQueryDevtoolsOverrideTransfer, armQueryDevtoolsOverrideTransfer, countUnresolvedQueryDevtoolsOverrides, queryDevtoolsAbout, createQueryDevtoolsOverrides, applyQueryDevtoolsOverrides, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsMocks, measureQueryDevtoolsPayload, queryDevtoolsArmedMocks, seedQueryDevtoolsSchemaBody, loadQueryDevtoolsSchema, queryDevtoolsMockId, saveQueryDevtoolsMock, armQueryDevtoolsMock, deleteQueryDevtoolsMock, collectQueryDevtoolsSchemaComponents, seedQueryDevtoolsSchemaRoute, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsAuthSessions, queryDevtoolsApiEnvs, queryDevtoolsApiEnvValues, setQueryDevtoolsApiEnv, queryDevtoolsAllowsLocalAuthSessions, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, QUERY_DEVTOOLS_FAULT_STATUSES, setQueryDevtoolsFault, isQueryDevtoolsEnabled, clearQueryDevtoolsTombstones, readQueryDevtoolsStore, queryDevtoolsEntries, isQueryDevtoolsRepositoryLive, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, queryDevtoolsFaultsRestored, queryDevtoolsArmedMocksRestored, restoredQueryDevtoolsOverrides, queryDevtoolsRestoredOverridesScope, clearRestoredQueryDevtoolsOverrides, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, sumQueryDevtoolsStats } from '@ethlete/query/devtools-contract';
5
9
  import { Subject, switchMap, timer, tap, EMPTY, interval, scan, animationFrames, NEVER, takeUntil, finalize as finalize$1, startWith, merge, map, fromEvent, filter, take } from 'rxjs';
6
- import { injectStyleManager, injectErrorTheme, ProvideColorDirective, clamp, injectRenderer, injectFileDownload, injectViewportSize, injectIsDocumentVisible, injectBreakpointObserver, signalElementDimensions, randomId, reserveOverlayViewportSpace, createObjectUrlHandle, DragHandleDirective, ResizeHandlesComponent } from '@ethlete/core';
7
10
  import { MenuDirective, MenuTriggerDirective, MenuSurfaceDirective, MenuComponent, MenuItemComponent, MenuSearchDirective, MenuSeparatorComponent, MenuItemShortcutComponent, SelectComponent, SelectSearchDirective, COMPONENTS_VERSION, MenuCheckboxItemComponent, MenuRadioGroupComponent, MenuRadioItemComponent } from '@ethlete/components';
8
- import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
9
- import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
10
- import { QUERY_DEVTOOLS_VIEW_STATE_KEY, queryDevtoolsShortcutLabel, isQueryDevtoolsShortcut, QueryDevtoolsToggleComponent } from '@ethlete/query-devtools/toggle';
11
+ import { ɵQUERY_DEVTOOLS_VIEW_STATE_KEY as _QUERY_DEVTOOLS_VIEW_STATE_KEY, queryDevtoolsShortcutLabel, isQueryDevtoolsShortcut, QueryDevtoolsToggleComponent } from '@ethlete/query-devtools/toggle';
12
+
13
+ /** Duck-typed rather than `instanceof HttpHeaders`, so a second copy of `@angular/common/http` still reads. */
14
+ const isHeadersValue = (value) => !!value &&
15
+ typeof value === 'object' &&
16
+ 'keys' in value &&
17
+ typeof value.keys === 'function' &&
18
+ 'getAll' in value &&
19
+ typeof value.getAll === 'function' &&
20
+ 'lazyInit' in value;
21
+ const HEADERS_KEY = 'headers';
22
+ /**
23
+ * How to render `value` if it is one of the built-ins the explorer knows, or `null` for anything it
24
+ * should keep treating as a plain object or array. `key` is the name the value sits under; a
25
+ * `headers` function is called so the row shows the headers it hands the request.
26
+ */
27
+ const exoticOf = (value, key) => {
28
+ if (typeof value === 'function') {
29
+ const provided = key === HEADERS_KEY && value.length === 0 ? providedHeaders(value) : null;
30
+ if (provided)
31
+ return { typeName: 'HttpHeaders', entries: headerEntries(provided) };
32
+ // `String(fn)` would dump the whole source of the function into the row.
33
+ return { typeName: 'fn', display: value.name || 'anonymous' };
34
+ }
35
+ if (!value || typeof value !== 'object')
36
+ return null;
37
+ if (value instanceof Date) {
38
+ return { typeName: 'Date', display: Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString() };
39
+ }
40
+ if (typeof File !== 'undefined' && value instanceof File) {
41
+ return {
42
+ typeName: 'File',
43
+ entries: [
44
+ { k: 'name', v: value.name },
45
+ { k: 'size', v: value.size },
46
+ { k: 'type', v: value.type },
47
+ { k: 'lastModified', v: new Date(value.lastModified) },
48
+ ],
49
+ };
50
+ }
51
+ if (typeof Blob !== 'undefined' && value instanceof Blob) {
52
+ return {
53
+ typeName: 'Blob',
54
+ entries: [
55
+ { k: 'size', v: value.size },
56
+ { k: 'type', v: value.type },
57
+ ],
58
+ };
59
+ }
60
+ if (value instanceof Map) {
61
+ return { typeName: 'Map', entries: [...value].map(([k, v]) => ({ k: String(k), v })) };
62
+ }
63
+ if (value instanceof Set) {
64
+ return { typeName: 'Set', entries: [...value].map((v, i) => ({ k: String(i), v })) };
65
+ }
66
+ if (typeof FormData !== 'undefined' && value instanceof FormData) {
67
+ return { typeName: 'FormData', entries: [...value].map(([k, v]) => ({ k, v })) };
68
+ }
69
+ if (isHeadersValue(value)) {
70
+ return { typeName: 'HttpHeaders', entries: headerEntries(value) };
71
+ }
72
+ return null;
73
+ };
74
+ /**
75
+ * The headers a `headers` provider hands the request, or `null` if it hands anything else. A secure
76
+ * query resolves its access token inside such a provider and throws while no token is available yet -
77
+ * both cases keep the plain `fn` row.
78
+ */
79
+ const providedHeaders = (provider) => {
80
+ try {
81
+ const value = provider();
82
+ return isHeadersValue(value) ? value : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ };
88
+ /**
89
+ * The headers an `HttpHeaders` actually holds, as `name: value` pairs. A header set more than once
90
+ * joins on `, ` the way the wire format does.
91
+ */
92
+ const headerEntries = (headers) => {
93
+ try {
94
+ return headers.keys().map((name) => ({ k: name, v: headers.getAll(name)?.join(', ') ?? '' }));
95
+ }
96
+ catch {
97
+ return [];
98
+ }
99
+ };
100
+ /** Past this a description stops fitting the diff cell and the form-field row it is written for. */
101
+ const MAX_INLINE_LENGTH = 80;
102
+ /**
103
+ * A one-line description of a value {@link exoticOf} knows, for a place with no room to expand it - a
104
+ * diff cell, a form field's value column. `null` for anything the caller should render itself.
105
+ */
106
+ const inlineExoticOf = (value) => {
107
+ const exotic = exoticOf(value);
108
+ if (!exotic)
109
+ return null;
110
+ const inner = exotic.display ?? exotic.entries.map(({ k, v }) => `${k}: ${safeJson(v)}`).join(', ');
111
+ const rendered = `${exotic.typeName}(${inner})`;
112
+ return rendered.length > MAX_INLINE_LENGTH ? `${rendered.slice(0, MAX_INLINE_LENGTH)}…` : rendered;
113
+ };
114
+ const safeJson = (value) => {
115
+ try {
116
+ return JSON.stringify(value) ?? 'undefined';
117
+ }
118
+ catch {
119
+ return '[unserializable]';
120
+ }
121
+ };
122
+
123
+ /** A feature list: the feature's name, followed by the options it was configured with. */
124
+ class QueryDevtoolsFeaturesComponent {
125
+ constructor() {
126
+ this.features = input.required(/* @ts-ignore */
127
+ ...(ngDevMode ? [{ debugName: "features" }] : /* istanbul ignore next */ []));
128
+ }
129
+ featureLabel(type) {
130
+ return type
131
+ .replace(/^WITH_/, '')
132
+ .replace(/_/g, ' ')
133
+ .toLowerCase();
134
+ }
135
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
136
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsFeaturesComponent, isStandalone: true, selector: "et-query-devtools-features", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
137
+ <ul class="et-query-devtools-features">
138
+ @for (feature of features(); track feature.type) {
139
+ <li class="et-query-devtools-feature">
140
+ <span class="et-query-devtools-chip">{{ featureLabel(feature.type) }}</span>
141
+ <!-- Always rendered: the list is one grid, so a feature without options still needs its cell
142
+ for the next row's chip to stay in the same column. -->
143
+ <span class="et-query-devtools-feature-details">
144
+ @for (detail of feature.details; track detail.label) {
145
+ <span class="et-query-devtools-feature-detail">
146
+ <span class="et-query-devtools-feature-label">{{ detail.label }}</span>
147
+ <span class="et-query-devtools-feature-value">{{ detail.value }}</span>
148
+ </span>
149
+ }
150
+ </span>
151
+ </li>
152
+ }
153
+ </ul>
154
+ `, isInline: true, styles: ["@layer components{.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:var(--_et-qdt-radius);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}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
155
+ }
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, decorators: [{
157
+ type: Component,
158
+ args: [{ selector: 'et-query-devtools-features', template: `
159
+ <ul class="et-query-devtools-features">
160
+ @for (feature of features(); track feature.type) {
161
+ <li class="et-query-devtools-feature">
162
+ <span class="et-query-devtools-chip">{{ featureLabel(feature.type) }}</span>
163
+ <!-- Always rendered: the list is one grid, so a feature without options still needs its cell
164
+ for the next row's chip to stay in the same column. -->
165
+ <span class="et-query-devtools-feature-details">
166
+ @for (detail of feature.details; track detail.label) {
167
+ <span class="et-query-devtools-feature-detail">
168
+ <span class="et-query-devtools-feature-label">{{ detail.label }}</span>
169
+ <span class="et-query-devtools-feature-value">{{ detail.value }}</span>
170
+ </span>
171
+ }
172
+ </span>
173
+ </li>
174
+ }
175
+ </ul>
176
+ `, encapsulation: ViewEncapsulation.None, styles: ["@layer components{.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:var(--_et-qdt-radius);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}}\n"] }]
177
+ }], propDecorators: { features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: true }] }] } });
178
+
179
+ const QUERY_DEVTOOLS_HOST = new InjectionToken('QUERY_DEVTOOLS_HOST');
180
+ /** Injects the panel a tab or the detail drawer is rendered inside of. @see QueryDevtoolsHost */
181
+ const injectQueryDevtoolsHost = () => inject(QUERY_DEVTOOLS_HOST);
11
182
 
12
183
  /**
13
184
  * Reads the clipboard, distinguishing "this browser has no `readText`" from "the user or the permission
@@ -145,47 +316,6 @@ const createQueryDevtoolsCopiedTick = (idle) => {
145
316
  };
146
317
  };
147
318
 
148
- const groupsOf = () => {
149
- const about = queryDevtoolsAbout();
150
- const groups = [
151
- {
152
- title: 'Ethlete',
153
- rows: Object.entries(about.ethlete).map(([name, version]) => ({ label: `@ethlete/${name}`, value: version })),
154
- },
155
- { title: 'Runtime', rows: [{ label: 'Angular', value: about.angular }] },
156
- ];
157
- if (about.app) {
158
- groups.push({
159
- title: 'Application',
160
- rows: Object.entries(about.app).map(([label, value]) => ({ label, value: String(value) })),
161
- });
162
- }
163
- return groups;
164
- };
165
- /**
166
- * What is running: the loaded `@ethlete/*` versions, the Angular version and whatever the app handed to
167
- * `provideQueryDevtools({ about })`. A section rather than a tab body, so it can be shown anywhere.
168
- */
169
- class QueryDevtoolsAboutComponent {
170
- constructor() {
171
- this.hostEl = inject(ElementRef);
172
- this.groups = groupsOf();
173
- this.copied = createQueryDevtoolsCopiedTick(false);
174
- }
175
- copy() {
176
- const text = this.groups
177
- .map((group) => [group.title, ...group.rows.map((row) => ` ${row.label}: ${row.value}`)].join('\n'))
178
- .join('\n\n');
179
- writeQueryDevtoolsClipboard({ text }, this.hostEl.nativeElement.ownerDocument).then((result) => this.copied.mark(result.ok));
180
- }
181
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
182
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsAboutComponent, isStandalone: true, selector: "et-query-devtools-about", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied.value() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
183
- }
184
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, decorators: [{
185
- type: Component,
186
- args: [{ selector: 'et-query-devtools-about', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied.value() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"] }]
187
- }] });
188
-
189
319
  /**
190
320
  * The copy menu's caret chrome, as a styles-only component the menu mounts itself - a session that
191
321
  * never opens a value explorer does not inject it.
@@ -474,121 +604,11 @@ const diffQueryDevtoolsResponses = (before, after) => {
474
604
  return acc;
475
605
  };
476
606
 
477
- /** Duck-typed rather than `instanceof HttpHeaders`, so a second copy of `@angular/common/http` still reads. */
478
- const isHeadersValue = (value) => !!value &&
479
- typeof value === 'object' &&
480
- 'keys' in value &&
481
- typeof value.keys === 'function' &&
482
- 'getAll' in value &&
483
- typeof value.getAll === 'function' &&
484
- 'lazyInit' in value;
485
- const HEADERS_KEY = 'headers';
486
607
  /**
487
- * How to render `value` if it is one of the built-ins the explorer knows, or `null` for anything it
488
- * should keep treating as a plain object or array. `key` is the name the value sits under; a
489
- * `headers` function is called so the row shows the headers it hands the request.
490
- */
491
- const exoticOf = (value, key) => {
492
- if (typeof value === 'function') {
493
- const provided = key === HEADERS_KEY && value.length === 0 ? providedHeaders(value) : null;
494
- if (provided)
495
- return { typeName: 'HttpHeaders', entries: headerEntries(provided) };
496
- // `String(fn)` would dump the whole source of the function into the row.
497
- return { typeName: 'fn', display: value.name || 'anonymous' };
498
- }
499
- if (!value || typeof value !== 'object')
500
- return null;
501
- if (value instanceof Date) {
502
- return { typeName: 'Date', display: Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString() };
503
- }
504
- if (typeof File !== 'undefined' && value instanceof File) {
505
- return {
506
- typeName: 'File',
507
- entries: [
508
- { k: 'name', v: value.name },
509
- { k: 'size', v: value.size },
510
- { k: 'type', v: value.type },
511
- { k: 'lastModified', v: new Date(value.lastModified) },
512
- ],
513
- };
514
- }
515
- if (typeof Blob !== 'undefined' && value instanceof Blob) {
516
- return {
517
- typeName: 'Blob',
518
- entries: [
519
- { k: 'size', v: value.size },
520
- { k: 'type', v: value.type },
521
- ],
522
- };
523
- }
524
- if (value instanceof Map) {
525
- return { typeName: 'Map', entries: [...value].map(([k, v]) => ({ k: String(k), v })) };
526
- }
527
- if (value instanceof Set) {
528
- return { typeName: 'Set', entries: [...value].map((v, i) => ({ k: String(i), v })) };
529
- }
530
- if (typeof FormData !== 'undefined' && value instanceof FormData) {
531
- return { typeName: 'FormData', entries: [...value].map(([k, v]) => ({ k, v })) };
532
- }
533
- if (isHeadersValue(value)) {
534
- return { typeName: 'HttpHeaders', entries: headerEntries(value) };
535
- }
536
- return null;
537
- };
538
- /**
539
- * The headers a `headers` provider hands the request, or `null` if it hands anything else. A secure
540
- * query resolves its access token inside such a provider and throws while no token is available yet -
541
- * both cases keep the plain `fn` row.
542
- */
543
- const providedHeaders = (provider) => {
544
- try {
545
- const value = provider();
546
- return isHeadersValue(value) ? value : null;
547
- }
548
- catch {
549
- return null;
550
- }
551
- };
552
- /**
553
- * The headers an `HttpHeaders` actually holds, as `name: value` pairs. A header set more than once
554
- * joins on `, ` the way the wire format does.
555
- */
556
- const headerEntries = (headers) => {
557
- try {
558
- return headers.keys().map((name) => ({ k: name, v: headers.getAll(name)?.join(', ') ?? '' }));
559
- }
560
- catch {
561
- return [];
562
- }
563
- };
564
- /** Past this a description stops fitting the diff cell and the form-field row it is written for. */
565
- const MAX_INLINE_LENGTH = 80;
566
- /**
567
- * A one-line description of a value {@link exoticOf} knows, for a place with no room to expand it - a
568
- * diff cell, a form field's value column. `null` for anything the caller should render itself.
569
- */
570
- const inlineExoticOf = (value) => {
571
- const exotic = exoticOf(value);
572
- if (!exotic)
573
- return null;
574
- const inner = exotic.display ?? exotic.entries.map(({ k, v }) => `${k}: ${safeJson(v)}`).join(', ');
575
- const rendered = `${exotic.typeName}(${inner})`;
576
- return rendered.length > MAX_INLINE_LENGTH ? `${rendered.slice(0, MAX_INLINE_LENGTH)}…` : rendered;
577
- };
578
- const safeJson = (value) => {
579
- try {
580
- return JSON.stringify(value) ?? 'undefined';
581
- }
582
- catch {
583
- return '[unserializable]';
584
- }
585
- };
586
-
587
- /**
588
- * The value explorer's tree chrome, as a styles-only component the tree mounts itself - a session that
589
- * never opens a JSON view does not inject it.
590
- *
591
- * @internal
608
+ * The value explorer's tree chrome, as a styles-only component the tree mounts itself - a session that
609
+ * never opens a JSON view does not inject it.
610
+ *
611
+ * @internal
592
612
  */
593
613
  class QueryDevtoolsJsonStylesComponent {
594
614
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsJsonStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -614,6 +634,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
614
634
  args: [{ selector: 'et-query-devtools-override-menu-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{et-query-devtools-override-menu{display:contents}.et-query-devtools-override-menu{display:contents}.et-query-devtools-override-trigger{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:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-override-trigger:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-override-trigger:focus-visible,.et-query-devtools-override-trigger[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-override-trigger{opacity:1}}@media(hover:none){.et-query-devtools-override-trigger{opacity:1}}.et-query-devtools-override-error,.et-query-devtools-override-note{margin:4px var(--et-menu-item-padding-inline, 10px) 2px;max-inline-size:240px;font-size:12px}.et-query-devtools-override-error{color:var(--_et-qdt-error, rgb(239 68 68))}.et-query-devtools-override-note{color:var(--_et-qdt-subtle, currentColor)}.et-query-devtools-override-set-menu{display:contents}}\n"] }]
615
635
  }] });
616
636
 
637
+ /**
638
+ * The error theme of the surface this element renders on. A menu's content is declared by the component that owns
639
+ * the trigger, so it must read the theme here, inside the detached menu panel, and not from that component.
640
+ *
641
+ * @internal
642
+ */
643
+ class QueryDevtoolsErrorThemeDirective {
644
+ constructor() {
645
+ this.theme = injectSemanticColorTheme('error');
646
+ }
647
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsErrorThemeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
648
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsErrorThemeDirective, isStandalone: true, selector: "[etQueryDevtoolsErrorTheme]", exportAs: ["etQueryDevtoolsErrorTheme"], ngImport: i0 }); }
649
+ }
650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsErrorThemeDirective, decorators: [{
651
+ type: Directive,
652
+ args: [{
653
+ selector: '[etQueryDevtoolsErrorTheme]',
654
+ exportAs: 'etQueryDevtoolsErrorTheme',
655
+ }]
656
+ }] });
657
+
617
658
  /**
618
659
  * The per-value action menu of the query devtools value explorer: arms a path-addressed
619
660
  * {@link OverrideOp} on the query's response so it replays on every future fetch, instead of the
@@ -623,7 +664,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
623
664
  */
624
665
  class QueryDevtoolsOverrideMenuComponent {
625
666
  constructor() {
626
- this.errorColorTheme = injectErrorTheme();
627
667
  this.hostEl = inject(ElementRef);
628
668
  this.value = input(/* @ts-ignore */
629
669
  ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
@@ -888,7 +928,7 @@ class QueryDevtoolsOverrideMenuComponent {
888
928
  this.rootMenu().closeAll();
889
929
  }
890
930
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
891
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideMenuComponent, isStandalone: true, selector: "et-query-devtools-override-menu", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "rootMenu", first: true, predicate: ["rootMenu"], descendants: true, isSignal: true }, { propertyName: "customInput", first: true, predicate: ["customValueInput"], descendants: true, isSignal: true }, { propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
931
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideMenuComponent, isStandalone: true, selector: "et-query-devtools-override-menu", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "rootMenu", first: true, predicate: ["rootMenu"], descendants: true, isSignal: true }, { propertyName: "customInput", first: true, predicate: ["customValueInput"], descendants: true, isSignal: true }, { propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p\n #errorTheme=\"etQueryDevtoolsErrorTheme\"\n [etProvideColor]=\"errorTheme.theme()\"\n class=\"et-query-devtools-override-error\"\n role=\"alert\"\n etQueryDevtoolsErrorTheme\n >\n {{ err }}\n </p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }, { kind: "directive", type: QueryDevtoolsErrorThemeDirective, selector: "[etQueryDevtoolsErrorTheme]", exportAs: ["etQueryDevtoolsErrorTheme"] }], encapsulation: i0.ViewEncapsulation.None }); }
892
932
  }
893
933
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, decorators: [{
894
934
  type: Component,
@@ -902,7 +942,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
902
942
  MenuItemShortcutComponent,
903
943
  MenuSeparatorComponent,
904
944
  ProvideColorDirective,
905
- ], template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
945
+ QueryDevtoolsErrorThemeDirective,
946
+ ], template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p\n #errorTheme=\"etQueryDevtoolsErrorTheme\"\n [etProvideColor]=\"errorTheme.theme()\"\n class=\"et-query-devtools-override-error\"\n role=\"alert\"\n etQueryDevtoolsErrorTheme\n >\n {{ err }}\n </p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
906
947
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], parentKind: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKind", required: false }] }], overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: true }] }], rootMenu: [{ type: i0.ViewChild, args: ['rootMenu', { isSignal: true }] }], customInput: [{ type: i0.ViewChild, args: ['customValueInput', { isSignal: true }] }], pasteInput: [{ type: i0.ViewChild, args: ['pasteBox', { isSignal: true }] }] } });
907
948
  const parseLooseJson = (raw) => {
908
949
  try {
@@ -1367,455 +1408,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
1367
1408
  {{ copied.value() ? '✓' : '⧉' }}
1368
1409
  </button>
1369
1410
  @if (addressable()) {
1370
- <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1371
- }
1372
- @if (!chunk()) {
1373
- @if (overrides(); as overrides) {
1374
- <et-query-devtools-override-menu
1375
- [value]="value()"
1376
- [path]="jsonPath()"
1377
- [parentKind]="parentKind()"
1378
- [overrides]="overrides"
1379
- />
1380
- }
1381
- }
1382
- </div>
1383
-
1384
- @if (effectiveExpanded()) {
1385
- <div class="et-query-devtools-json-children">
1386
- @for (childChunk of childChunks(); track childChunk.start) {
1387
- <et-query-devtools-json
1388
- [value]="value()"
1389
- [chunk]="childChunk"
1390
- [depth]="depth()"
1391
- [path]="path()"
1392
- [jsonPath]="jsonPath()"
1393
- [parentKind]="parentKind()"
1394
- [overrides]="overrides()"
1395
- [annotations]="annotations()"
1396
- [search]="search()"
1397
- [expandedPaths]="expandedPaths()"
1398
- [collapsedPaths]="collapsedPaths()"
1399
- [toggleFn]="toggleFn()"
1400
- [ancestors]="ancestors()"
1401
- />
1402
- }
1403
- @for (entry of visibleEntries(); track entry.k) {
1404
- <et-query-devtools-json
1405
- [value]="entry.v"
1406
- [nodeKey]="entry.k"
1407
- [depth]="depth() + 1"
1408
- [path]="childPath(entry.k)"
1409
- [jsonPath]="childJsonPath(entry.k)"
1410
- [parentKind]="kind()"
1411
- [overrides]="overrides()"
1412
- [annotations]="annotations()"
1413
- [search]="search()"
1414
- [expandedPaths]="expandedPaths()"
1415
- [collapsedPaths]="collapsedPaths()"
1416
- [toggleFn]="toggleFn()"
1417
- [ancestors]="childAncestors()"
1418
- />
1419
- }
1420
- </div>
1421
- }
1422
- </div>
1423
- } @else {
1424
- <div [attr.data-kind]="kind()" class="et-query-devtools-json-row et-query-devtools-json-leaf">
1425
- @if (nodeKey() !== null) {
1426
- <span [class.et-query-devtools-json-hit]="keyHit()" class="et-query-devtools-json-key">{{ nodeKey() }}</span>
1427
- <span class="et-query-devtools-json-colon">:</span>
1428
- }
1429
- <span [class.et-query-devtools-json-hit]="valueHit()" class="et-query-devtools-json-value">{{
1430
- display()
1431
- }}</span>
1432
- @if (annotation(); as annotation) {
1433
- <span [title]="annotationTitle()" class="et-query-devtools-json-type">{{ annotation }}</span>
1434
- }
1435
- <button
1436
- [attr.aria-label]="copyLabel()"
1437
- [title]="copyLabel()"
1438
- [class.et-query-devtools-json-copy--copied]="copied.value()"
1439
- (click)="copy('value')"
1440
- class="et-query-devtools-json-copy"
1441
- type="button"
1442
- >
1443
- {{ copied.value() ? '✓' : '⧉' }}
1444
- </button>
1445
- @if (addressable()) {
1446
- <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1447
- }
1448
- @if (overrides(); as overrides) {
1449
- <et-query-devtools-override-menu
1450
- [value]="value()"
1451
- [path]="jsonPath()"
1452
- [parentKind]="parentKind()"
1453
- [overrides]="overrides"
1454
- />
1455
- }
1456
- </div>
1457
- }
1458
- `,
1459
- encapsulation: ViewEncapsulation.None,
1460
- imports: [QueryDevtoolsJsonComponent, QueryDevtoolsCopyMenuComponent, QueryDevtoolsOverrideMenuComponent],
1461
- }]
1462
- }], 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 }] }], jsonPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "jsonPath", required: false }] }], parentKind: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKind", required: false }] }], overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], ancestors: [{ type: i0.Input, args: [{ isSignal: true, alias: "ancestors", required: false }] }] } });
1463
- // Below the component: an interpolated template literal above an inline template breaks the Angular
1464
- // language service for the whole template (see `ethlete/no-template-literal-before-inline-template`).
1465
- const displayOf = (value, key) => {
1466
- const exotic = exoticOf(value, key);
1467
- if (exotic?.display)
1468
- return `${exotic.typeName}(${exotic.display})`;
1469
- switch (kindOf(value)) {
1470
- case 'string':
1471
- return `"${value}"`;
1472
- case 'null':
1473
- return 'null';
1474
- case 'undefined':
1475
- return 'undefined';
1476
- default:
1477
- return String(value);
1478
- }
1479
- };
1480
- const buildChunks = (entries, window) => {
1481
- const size = chunkSizeFor(entries.length);
1482
- const chunks = [];
1483
- for (let i = 0; i < entries.length; i += size) {
1484
- const slice = entries.slice(i, i + size);
1485
- const start = window.offset + i;
1486
- const end = start + slice.length;
1487
- const first = window.isArray ? String(start) : (slice[0]?.k ?? '');
1488
- const last = window.isArray ? String(end - 1) : (slice[slice.length - 1]?.k ?? '');
1489
- chunks.push({ start, end, label: slice.length === 1 ? first : `${first} … ${last}` });
1490
- }
1491
- return chunks;
1492
- };
1493
- /**
1494
- * Whether a search term appears anywhere in a subtree - used to keep non-matching slices folded. The
1495
- * values a query holds are the app's own, so a self-referential one has to terminate here.
1496
- *
1497
- * `seen` is shared across the whole walk rather than being a per-path set: the search stops at the
1498
- * first match, so anything already in it was walked to the end and did not match.
1499
- */
1500
- const matchesDeep = (entry, term) => {
1501
- const seen = new Set();
1502
- const walk = ({ k, v }) => {
1503
- const exotic = exoticOf(v, k);
1504
- if (exotic ? !exotic.entries : !v || typeof v !== 'object') {
1505
- return displayOf(v, k).toLowerCase().includes(term);
1506
- }
1507
- if (typeof v === 'object' && v !== null) {
1508
- if (seen.has(v))
1509
- return false;
1510
- seen.add(v);
1511
- }
1512
- return entriesOf(v, k).some((child) => child.k.toLowerCase().includes(term) || walk(child));
1513
- };
1514
- return walk(entry);
1515
- };
1516
-
1517
- const QUERY_DEVTOOLS_HOST = new InjectionToken('QUERY_DEVTOOLS_HOST');
1518
- /** Injects the panel a tab or the detail drawer is rendered inside of. @see QueryDevtoolsHost */
1519
- const injectQueryDevtoolsHost = () => inject(QUERY_DEVTOOLS_HOST);
1520
-
1521
- const isAbsoluteHttpUrl = (value) => {
1522
- try {
1523
- return ['http:', 'https:'].includes(new URL(value).protocol);
1524
- }
1525
- catch {
1526
- return false;
1527
- }
1528
- };
1529
- const SCOPE_ROWS = [
1530
- {
1531
- key: 'viewState',
1532
- label: 'Panel view state',
1533
- hint: 'Where the panel sits, how big it is, the open tab, every filter and the selected query. Set none to forget all of it on each reload.',
1534
- },
1535
- {
1536
- key: 'pins',
1537
- label: 'Pinned queries',
1538
- hint: 'Which queries sort to the top of the list. Local outlives the tab, because a pin says what you are working on rather than what you are looking at.',
1539
- },
1540
- {
1541
- key: 'overrides',
1542
- label: 'Response overrides',
1543
- hint: 'Armed edits, replayed as queries register - before their first fetch. None is the default: a reload is how the app stops being lied to.',
1544
- warn: {
1545
- scopes: ['local'],
1546
- text: 'Armed overrides now outlive closing the tab. An app that stays tampered with across days is hours of debugging the wrong thing - the bar above the tabs says when they came back.',
1547
- },
1548
- },
1549
- {
1550
- key: 'mocks',
1551
- label: 'Designed mocks',
1552
- hint: 'The library you authored, not whether any of it is armed - arming has its own scope below.',
1553
- },
1554
- {
1555
- key: 'armedMocks',
1556
- label: 'Armed mocks',
1557
- hint: 'Which of the library is served. None is the default: a reload is how the app goes back to talking to the API.',
1558
- warn: {
1559
- scopes: ['session', 'local'],
1560
- text: 'The next page load starts serving these routes from the panel, before anyone opens it. The bar above the tabs says when they came back.',
1561
- },
1562
- },
1563
- {
1564
- key: 'authSessions',
1565
- label: 'Sessions and accounts',
1566
- hint: 'The token pairs the Auth tab switches between, and the credentials it logs in with. Local is the default in a development build: a vault that forgets the other user on every reload is not one. None keeps nothing at all.',
1567
- warn: {
1568
- scopes: ['local'],
1569
- text: 'Access and refresh tokens for every user you logged in as, plus what you typed into the account fields, stay in this browser until you forget them. They are never kept for an API env marked production.',
1570
- },
1571
- },
1572
- {
1573
- key: 'armedFaults',
1574
- label: 'Armed faults',
1575
- hint: 'The latency and failures injected per client, budgets included. None is the default, for the same reason as armed mocks.',
1576
- warn: {
1577
- scopes: ['session', 'local'],
1578
- text: 'The next page load starts injecting these failures on its own, so an app that looks broken may only be armed. The bar above the tabs says when they came back.',
1579
- },
1580
- },
1581
- ];
1582
- const SCOPES = [
1583
- { value: 'none', label: 'none' },
1584
- { value: 'session', label: 'session' },
1585
- { value: 'local', label: 'local' },
1586
- ];
1587
- /**
1588
- * Why IndexedDB - the one store with the quota for a large library of designed data - is not on offer.
1589
- * These are read synchronously: view state and pins in a field initializer of the panel, and overrides
1590
- * inside query registration, before the first request. An async store cannot answer either in time, and a
1591
- * scope that silently arrives late is worse than one that is missing. The mock library is the one that
1592
- * could tolerate arriving late, since nothing is served until a mock is armed by hand.
1593
- */
1594
- const INDEXED_DB_TITLE = 'Unavailable: these are read synchronously - view state before the first render, overrides before the first fetch - and IndexedDB cannot answer in time. Only the mock library could tolerate an async store, and it is not wired to one yet.';
1595
- const LIMIT_ROWS = [
1596
- {
1597
- key: 'maxEvents',
1598
- label: 'Events log',
1599
- min: 10,
1600
- max: 1000,
1601
- step: 10,
1602
- hint: 'rows the Events tab keeps. Lowering it trims the log now.',
1603
- },
1604
- {
1605
- key: 'maxDroppedCacheEntries',
1606
- label: 'Dropped cache entries',
1607
- min: 0,
1608
- max: 200,
1609
- step: 5,
1610
- hint: 'per client, remembered so the Cache tab can say why an entry went away. Applies as entries are dropped.',
1611
- },
1612
- ];
1613
- /**
1614
- * Every panel-wide switch in one place - the storage each kind of state uses, the limits the panel would
1615
- * otherwise hold as constants, and the toggles their own tabs also carry.
1616
- */
1617
- class QueryDevtoolsSettingsComponent {
1618
- constructor() {
1619
- this.document = inject(DOCUMENT);
1620
- this.host = injectQueryDevtoolsHost();
1621
- this.SCOPE_ROWS = SCOPE_ROWS;
1622
- this.SCOPES = SCOPES;
1623
- this.INDEXED_DB_TITLE = INDEXED_DB_TITLE;
1624
- this.LIMIT_ROWS = LIMIT_ROWS;
1625
- this.ARM_ALL_MOCKS = armAllQueryDevtoolsMocks;
1626
- this.DISARM_ALL_MOCKS = clearQueryDevtoolsArmedMocks;
1627
- this.DISARM_ALL_FAULTS = clearQueryDevtoolsFaults;
1628
- /** What queries actually retain, which is the application's value unless this panel raised it. */
1629
- this.responseHistory = computed(queryDevtoolsResponseHistory, /* @ts-ignore */
1630
- ...(ngDevMode ? [{ debugName: "responseHistory" }] : /* istanbul ignore next */ []));
1631
- this.mockCount = computed(() => queryDevtoolsMocks().length, /* @ts-ignore */
1632
- ...(ngDevMode ? [{ debugName: "mockCount" }] : /* istanbul ignore next */ []));
1633
- this.sessionCount = computed(() => queryDevtoolsAuthSessions().length, /* @ts-ignore */
1634
- ...(ngDevMode ? [{ debugName: "sessionCount" }] : /* istanbul ignore next */ []));
1635
- this.armedMockCount = computed(() => queryDevtoolsArmedMocks().size, /* @ts-ignore */
1636
- ...(ngDevMode ? [{ debugName: "armedMockCount" }] : /* istanbul ignore next */ []));
1637
- this.armedFaultCount = computed(() => this.host.faultClients().filter((client) => client.armed).length, /* @ts-ignore */
1638
- ...(ngDevMode ? [{ debugName: "armedFaultCount" }] : /* istanbul ignore next */ []));
1639
- /** The same keys the Events tab's own picker offers - see {@link queryDevtoolsEventClients}. */
1640
- this.eventClients = computed(() => queryDevtoolsEventClients(this.host.eventLog(), this.host.eventClient()), /* @ts-ignore */
1641
- ...(ngDevMode ? [{ debugName: "eventClients" }] : /* istanbul ignore next */ []));
1642
- this.resetConfirming = signal(false, /* @ts-ignore */
1643
- ...(ngDevMode ? [{ debugName: "resetConfirming" }] : /* istanbul ignore next */ []));
1644
- this.FORGET_SESSIONS = clearQueryDevtoolsAuthSessions;
1645
- /** The backends the application declared, or an empty list - the card renders only for a declared one. */
1646
- this.apiEnvs = computed(queryDevtoolsApiEnvs, /* @ts-ignore */
1647
- ...(ngDevMode ? [{ debugName: "apiEnvs" }] : /* istanbul ignore next */ []));
1648
- }
1649
- /** The env id (or typed URL) a switch's key holds, or `null` while the application picks for itself. */
1650
- apiEnvValue(apiSwitch) {
1651
- return queryDevtoolsApiEnvValues()[apiSwitch.storageKey] ?? null;
1652
- }
1653
- /** The env a switch falls back to with nothing stored, described for the default button. */
1654
- apiEnvFallback(apiSwitch) {
1655
- const fallback = apiSwitch.envs.find((env) => env.id === apiSwitch.fallback);
1656
- return {
1657
- label: fallback ? `default (${fallback.label ?? fallback.id})` : 'default',
1658
- production: fallback?.production === true,
1659
- };
1660
- }
1661
- /** Whether the pick in force - the stored env, or the fallback behind an unwritten key - is production. */
1662
- apiEnvIsProduction(apiSwitch) {
1663
- const stored = this.apiEnvValue(apiSwitch);
1664
- return apiSwitch.envs.find((env) => env.id === (stored ?? apiSwitch.fallback))?.production === true;
1665
- }
1666
- /** A stored value no declared env carries, which is a URL somebody typed in below. */
1667
- apiEnvCustomUrl(apiSwitch) {
1668
- const stored = this.apiEnvValue(apiSwitch);
1669
- return stored && !apiSwitch.envs.some((env) => env.id === stored) ? stored : '';
1670
- }
1671
- /** Writes the application's own key and reloads - it is read before Angular boots, so nothing else can. */
1672
- pickApiEnv(apiSwitch, value) {
1673
- setQueryDevtoolsApiEnv(apiSwitch.storageKey, value);
1674
- this.document.defaultView?.location.reload();
1675
- }
1676
- setCustomApiUrl(apiSwitch, value) {
1677
- const url = value.trim();
1678
- if (url === this.apiEnvCustomUrl(apiSwitch))
1679
- return;
1680
- if (url && !isAbsoluteHttpUrl(url))
1681
- return;
1682
- this.pickApiEnv(apiSwitch, url || null);
1683
- }
1684
- settings() {
1685
- return queryDevtoolsSettings();
1686
- }
1687
- scopeOf(key) {
1688
- return this.settings()[key];
1689
- }
1690
- limitOf(key) {
1691
- return this.settings()[key];
1692
- }
1693
- /** What the picked scope means beyond where the state is kept, or `null` while it means nothing extra. */
1694
- warningFor(row, scope) {
1695
- return row.warn?.scopes.includes(scope) ? row.warn.text : null;
1696
- }
1697
- /** Why one scope is not on offer for a row, for the button's title, or `null` while it is. */
1698
- unavailableReason(row, scope) {
1699
- if (row.key !== 'authSessions' || scope !== 'local' || queryDevtoolsAllowsLocalAuthSessions())
1700
- return null;
1701
- return 'Unavailable outside a development build: live access and refresh tokens, and the credentials you typed in, must not outlive this tab on a deployed origin. The vault keeps nothing here unless you pick Session, which keeps it for as long as the tab.';
1702
- }
1703
- setScope(key, scope) {
1704
- // Everything armed goes through its own module: changing the scope moves an existing store and
1705
- // captures whatever is armed right now, so the choice reads as "keep these".
1706
- if (key === 'overrides')
1707
- return setQueryDevtoolsOverridesScope(scope);
1708
- if (key === 'armedMocks')
1709
- return setQueryDevtoolsArmedMocksScope(scope);
1710
- if (key === 'armedFaults')
1711
- return setQueryDevtoolsFaultsScope(scope);
1712
- setQueryDevtoolsSettings(key === 'viewState'
1713
- ? { viewState: scope }
1714
- : key === 'pins'
1715
- ? { pins: scope }
1716
- : key === 'authSessions'
1717
- ? { authSessions: scope }
1718
- : { mocks: scope });
1719
- }
1720
- setLimit(key, value) {
1721
- const count = Number(value);
1722
- setQueryDevtoolsSettings(key === 'maxEvents' ? { maxEvents: count } : { maxDroppedCacheEntries: count });
1723
- }
1724
- setResponseHistory(value) {
1725
- setQueryDevtoolsSettings({ responseHistory: Number(value) });
1726
- }
1727
- /** Hands retention back to `provideQueryDevtools({ responseHistory })`. */
1728
- clearResponseHistory() {
1729
- setQueryDevtoolsSettings({ responseHistory: null });
1730
- }
1731
- reset() {
1732
- this.host.resetDevtools();
1733
- this.resetConfirming.set(false);
1411
+ <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1412
+ }
1413
+ @if (!chunk()) {
1414
+ @if (overrides(); as overrides) {
1415
+ <et-query-devtools-override-menu
1416
+ [value]="value()"
1417
+ [path]="jsonPath()"
1418
+ [parentKind]="parentKind()"
1419
+ [overrides]="overrides"
1420
+ />
1421
+ }
1422
+ }
1423
+ </div>
1424
+
1425
+ @if (effectiveExpanded()) {
1426
+ <div class="et-query-devtools-json-children">
1427
+ @for (childChunk of childChunks(); track childChunk.start) {
1428
+ <et-query-devtools-json
1429
+ [value]="value()"
1430
+ [chunk]="childChunk"
1431
+ [depth]="depth()"
1432
+ [path]="path()"
1433
+ [jsonPath]="jsonPath()"
1434
+ [parentKind]="parentKind()"
1435
+ [overrides]="overrides()"
1436
+ [annotations]="annotations()"
1437
+ [search]="search()"
1438
+ [expandedPaths]="expandedPaths()"
1439
+ [collapsedPaths]="collapsedPaths()"
1440
+ [toggleFn]="toggleFn()"
1441
+ [ancestors]="ancestors()"
1442
+ />
1443
+ }
1444
+ @for (entry of visibleEntries(); track entry.k) {
1445
+ <et-query-devtools-json
1446
+ [value]="entry.v"
1447
+ [nodeKey]="entry.k"
1448
+ [depth]="depth() + 1"
1449
+ [path]="childPath(entry.k)"
1450
+ [jsonPath]="childJsonPath(entry.k)"
1451
+ [parentKind]="kind()"
1452
+ [overrides]="overrides()"
1453
+ [annotations]="annotations()"
1454
+ [search]="search()"
1455
+ [expandedPaths]="expandedPaths()"
1456
+ [collapsedPaths]="collapsedPaths()"
1457
+ [toggleFn]="toggleFn()"
1458
+ [ancestors]="childAncestors()"
1459
+ />
1460
+ }
1461
+ </div>
1462
+ }
1463
+ </div>
1464
+ } @else {
1465
+ <div [attr.data-kind]="kind()" class="et-query-devtools-json-row et-query-devtools-json-leaf">
1466
+ @if (nodeKey() !== null) {
1467
+ <span [class.et-query-devtools-json-hit]="keyHit()" class="et-query-devtools-json-key">{{ nodeKey() }}</span>
1468
+ <span class="et-query-devtools-json-colon">:</span>
1469
+ }
1470
+ <span [class.et-query-devtools-json-hit]="valueHit()" class="et-query-devtools-json-value">{{
1471
+ display()
1472
+ }}</span>
1473
+ @if (annotation(); as annotation) {
1474
+ <span [title]="annotationTitle()" class="et-query-devtools-json-type">{{ annotation }}</span>
1475
+ }
1476
+ <button
1477
+ [attr.aria-label]="copyLabel()"
1478
+ [title]="copyLabel()"
1479
+ [class.et-query-devtools-json-copy--copied]="copied.value()"
1480
+ (click)="copy('value')"
1481
+ class="et-query-devtools-json-copy"
1482
+ type="button"
1483
+ >
1484
+ {{ copied.value() ? '✓' : '⧉' }}
1485
+ </button>
1486
+ @if (addressable()) {
1487
+ <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1488
+ }
1489
+ @if (overrides(); as overrides) {
1490
+ <et-query-devtools-override-menu
1491
+ [value]="value()"
1492
+ [path]="jsonPath()"
1493
+ [parentKind]="parentKind()"
1494
+ [overrides]="overrides"
1495
+ />
1496
+ }
1497
+ </div>
1734
1498
  }
1735
- toggleGoneQueries() {
1736
- this.host.toggleFacet('gone');
1499
+ `,
1500
+ encapsulation: ViewEncapsulation.None,
1501
+ imports: [QueryDevtoolsJsonComponent, QueryDevtoolsCopyMenuComponent, QueryDevtoolsOverrideMenuComponent],
1502
+ }]
1503
+ }], 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 }] }], jsonPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "jsonPath", required: false }] }], parentKind: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKind", required: false }] }], overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], ancestors: [{ type: i0.Input, args: [{ isSignal: true, alias: "ancestors", required: false }] }] } });
1504
+ // Below the component: an interpolated template literal above an inline template breaks the Angular
1505
+ // language service for the whole template (see `ethlete/no-template-literal-before-inline-template`).
1506
+ const displayOf = (value, key) => {
1507
+ const exotic = exoticOf(value, key);
1508
+ if (exotic?.display)
1509
+ return `${exotic.typeName}(${exotic.display})`;
1510
+ switch (kindOf(value)) {
1511
+ case 'string':
1512
+ return `"${value}"`;
1513
+ case 'null':
1514
+ return 'null';
1515
+ case 'undefined':
1516
+ return 'undefined';
1517
+ default:
1518
+ return String(value);
1737
1519
  }
1738
- showsGoneQueries() {
1739
- return this.host.queryFacets().has('gone');
1520
+ };
1521
+ const buildChunks = (entries, window) => {
1522
+ const size = chunkSizeFor(entries.length);
1523
+ const chunks = [];
1524
+ for (let i = 0; i < entries.length; i += size) {
1525
+ const slice = entries.slice(i, i + size);
1526
+ const start = window.offset + i;
1527
+ const end = start + slice.length;
1528
+ const first = window.isArray ? String(start) : (slice[0]?.k ?? '');
1529
+ const last = window.isArray ? String(end - 1) : (slice[slice.length - 1]?.k ?? '');
1530
+ chunks.push({ start, end, label: slice.length === 1 ? first : `${first} … ${last}` });
1740
1531
  }
1741
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1742
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsSettingsComponent, isStandalone: true, selector: "et-query-devtools-settings", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:var(--_et-qdt-fg)}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1743
- }
1744
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, decorators: [{
1745
- type: Component,
1746
- args: [{ selector: 'et-query-devtools-settings', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:var(--_et-qdt-fg)}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
1747
- }] });
1748
-
1532
+ return chunks;
1533
+ };
1749
1534
  /**
1750
- * The waterfall's grid, axis and bars, as a styles-only component the panel mounts when the Timeline tab
1751
- * is first opened.
1535
+ * Whether a search term appears anywhere in a subtree - used to keep non-matching slices folded. The
1536
+ * values a query holds are the app's own, so a self-referential one has to terminate here.
1752
1537
  *
1753
- * @internal
1538
+ * `seen` is shared across the whole walk rather than being a per-path set: the search stops at the
1539
+ * first match, so anything already in it was walked to the end and did not match.
1754
1540
  */
1755
- class QueryDevtoolsTimelineStylesComponent {
1756
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1757
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsTimelineStylesComponent, isStandalone: true, selector: "et-query-devtools-timeline-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.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}@media(hover:hover){.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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1758
- }
1759
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, decorators: [{
1760
- type: Component,
1761
- args: [{ selector: 'et-query-devtools-timeline-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.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}@media(hover:hover){.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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"] }]
1762
- }] });
1763
-
1764
- /** A feature list: the feature's name, followed by the options it was configured with. */
1765
- class QueryDevtoolsFeaturesComponent {
1766
- constructor() {
1767
- this.features = input.required(/* @ts-ignore */
1768
- ...(ngDevMode ? [{ debugName: "features" }] : /* istanbul ignore next */ []));
1769
- }
1770
- featureLabel(type) {
1771
- return type
1772
- .replace(/^WITH_/, '')
1773
- .replace(/_/g, ' ')
1774
- .toLowerCase();
1775
- }
1776
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1777
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsFeaturesComponent, isStandalone: true, selector: "et-query-devtools-features", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1778
- <ul class="et-query-devtools-features">
1779
- @for (feature of features(); track feature.type) {
1780
- <li class="et-query-devtools-feature">
1781
- <span class="et-query-devtools-chip">{{ featureLabel(feature.type) }}</span>
1782
- <!-- Always rendered: the list is one grid, so a feature without options still needs its cell
1783
- for the next row's chip to stay in the same column. -->
1784
- <span class="et-query-devtools-feature-details">
1785
- @for (detail of feature.details; track detail.label) {
1786
- <span class="et-query-devtools-feature-detail">
1787
- <span class="et-query-devtools-feature-label">{{ detail.label }}</span>
1788
- <span class="et-query-devtools-feature-value">{{ detail.value }}</span>
1789
- </span>
1790
- }
1791
- </span>
1792
- </li>
1793
- }
1794
- </ul>
1795
- `, isInline: true, styles: ["@layer components{.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:var(--_et-qdt-radius);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}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1796
- }
1797
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, decorators: [{
1798
- type: Component,
1799
- args: [{ selector: 'et-query-devtools-features', template: `
1800
- <ul class="et-query-devtools-features">
1801
- @for (feature of features(); track feature.type) {
1802
- <li class="et-query-devtools-feature">
1803
- <span class="et-query-devtools-chip">{{ featureLabel(feature.type) }}</span>
1804
- <!-- Always rendered: the list is one grid, so a feature without options still needs its cell
1805
- for the next row's chip to stay in the same column. -->
1806
- <span class="et-query-devtools-feature-details">
1807
- @for (detail of feature.details; track detail.label) {
1808
- <span class="et-query-devtools-feature-detail">
1809
- <span class="et-query-devtools-feature-label">{{ detail.label }}</span>
1810
- <span class="et-query-devtools-feature-value">{{ detail.value }}</span>
1811
- </span>
1812
- }
1813
- </span>
1814
- </li>
1815
- }
1816
- </ul>
1817
- `, encapsulation: ViewEncapsulation.None, styles: ["@layer components{.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:var(--_et-qdt-radius);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}}\n"] }]
1818
- }], propDecorators: { features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: true }] }] } });
1541
+ const matchesDeep = (entry, term) => {
1542
+ const seen = new Set();
1543
+ const walk = ({ k, v }) => {
1544
+ const exotic = exoticOf(v, k);
1545
+ if (exotic ? !exotic.entries : !v || typeof v !== 'object') {
1546
+ return displayOf(v, k).toLowerCase().includes(term);
1547
+ }
1548
+ if (typeof v === 'object' && v !== null) {
1549
+ if (seen.has(v))
1550
+ return false;
1551
+ seen.add(v);
1552
+ }
1553
+ return entriesOf(v, k).some((child) => child.k.toLowerCase().includes(term) || walk(child));
1554
+ };
1555
+ return walk(entry);
1556
+ };
1819
1557
 
1820
1558
  /** `username, password` as typed in the add row, into the fields the login args are built from. */
1821
1559
  const parseAuthFields = (value) => [...new Set(value.split(/[\s,]+/).filter(Boolean))].map((name) => ({
@@ -2101,7 +1839,6 @@ const buildCurlCommand = (request) => {
2101
1839
  */
2102
1840
  class QueryDevtoolsOverrideSetMenuComponent {
2103
1841
  constructor() {
2104
- this.errorColorTheme = injectErrorTheme();
2105
1842
  this.hostEl = inject(ElementRef);
2106
1843
  this.overrides = input.required(/* @ts-ignore */
2107
1844
  ...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
@@ -2192,7 +1929,7 @@ class QueryDevtoolsOverrideSetMenuComponent {
2192
1929
  this.note.set(`${parts.join('; ')}.`);
2193
1930
  }
2194
1931
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2195
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
1932
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p\n #errorTheme=\"etQueryDevtoolsErrorTheme\"\n [etProvideColor]=\"errorTheme.theme()\"\n class=\"et-query-devtools-override-error\"\n role=\"alert\"\n etQueryDevtoolsErrorTheme\n >\n {{ err }}\n </p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }, { kind: "directive", type: QueryDevtoolsErrorThemeDirective, selector: "[etQueryDevtoolsErrorTheme]", exportAs: ["etQueryDevtoolsErrorTheme"] }], encapsulation: i0.ViewEncapsulation.None }); }
2196
1933
  }
2197
1934
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, decorators: [{
2198
1935
  type: Component,
@@ -2205,7 +1942,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
2205
1942
  MenuItemComponent,
2206
1943
  MenuItemShortcutComponent,
2207
1944
  ProvideColorDirective,
2208
- ], template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
1945
+ QueryDevtoolsErrorThemeDirective,
1946
+ ], template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p\n #errorTheme=\"etQueryDevtoolsErrorTheme\"\n [etProvideColor]=\"errorTheme.theme()\"\n class=\"et-query-devtools-override-error\"\n role=\"alert\"\n etQueryDevtoolsErrorTheme\n >\n {{ err }}\n </p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
2209
1947
  }], ctorParameters: () => [], propDecorators: { overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: true }] }], response: [{ type: i0.Input, args: [{ isSignal: true, alias: "response", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], pasteInput: [{ type: i0.ViewChild, args: ['pasteBox', { isSignal: true }] }] } });
2210
1948
 
2211
1949
  /**
@@ -2462,12 +2200,53 @@ class QueryDevtoolsEventsTabComponent {
2462
2200
  return `dropped · ${DESTROY_CAUSE_LABELS[event.destroyCause ?? 'unbind']}`;
2463
2201
  return event.type === 'request-error' ? `error ${event.status}` : 'success';
2464
2202
  }
2465
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2466
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div #panes 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 (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\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 class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n @let eventQueryId = host.eventQueryId(event);\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!eventQueryId && host.eventSelectedQueryId() === eventQueryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </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\">{{ host.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 (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(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 @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 (canOpenEventRow(eventQueryId)) {\n <button\n (click)=\"host.eventSelectedQueryId.set(eventQueryId)\"\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\">{{ host.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\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
2203
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2204
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div #panes 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 (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\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 class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n @let eventQueryId = host.eventQueryId(event);\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!eventQueryId && host.eventSelectedQueryId() === eventQueryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </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\">{{ host.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 (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(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 @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 (canOpenEventRow(eventQueryId)) {\n <button\n (click)=\"host.eventSelectedQueryId.set(eventQueryId)\"\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\">{{ host.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\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
2205
+ }
2206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, decorators: [{
2207
+ type: Component,
2208
+ args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "<div #panes 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 (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\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 class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n @let eventQueryId = host.eventQueryId(event);\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!eventQueryId && host.eventSelectedQueryId() === eventQueryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </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\">{{ host.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 (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(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 @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 (canOpenEventRow(eventQueryId)) {\n <button\n (click)=\"host.eventSelectedQueryId.set(eventQueryId)\"\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\">{{ host.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\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n" }]
2209
+ }] });
2210
+
2211
+ const groupsOf = () => {
2212
+ const about = queryDevtoolsAbout();
2213
+ const groups = [
2214
+ {
2215
+ title: 'Ethlete',
2216
+ rows: Object.entries(about.ethlete).map(([name, version]) => ({ label: `@ethlete/${name}`, value: version })),
2217
+ },
2218
+ { title: 'Runtime', rows: [{ label: 'Angular', value: about.angular }] },
2219
+ ];
2220
+ if (about.app) {
2221
+ groups.push({
2222
+ title: 'Application',
2223
+ rows: Object.entries(about.app).map(([label, value]) => ({ label, value: String(value) })),
2224
+ });
2225
+ }
2226
+ return groups;
2227
+ };
2228
+ /**
2229
+ * What is running: the loaded `@ethlete/*` versions, the Angular version and whatever the app handed to
2230
+ * `provideQueryDevtools({ about })`. A section rather than a tab body, so it can be shown anywhere.
2231
+ */
2232
+ class QueryDevtoolsAboutComponent {
2233
+ constructor() {
2234
+ this.hostEl = inject(ElementRef);
2235
+ this.groups = groupsOf();
2236
+ this.copied = createQueryDevtoolsCopiedTick(false);
2237
+ }
2238
+ copy() {
2239
+ const text = this.groups
2240
+ .map((group) => [group.title, ...group.rows.map((row) => ` ${row.label}: ${row.value}`)].join('\n'))
2241
+ .join('\n\n');
2242
+ writeQueryDevtoolsClipboard({ text }, this.hostEl.nativeElement.ownerDocument).then((result) => this.copied.mark(result.ok));
2243
+ }
2244
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2245
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsAboutComponent, isStandalone: true, selector: "et-query-devtools-about", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied.value() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
2467
2246
  }
2468
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, decorators: [{
2247
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, decorators: [{
2469
2248
  type: Component,
2470
- args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "<div #panes 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 (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\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 class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n @let eventQueryId = host.eventQueryId(event);\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!eventQueryId && host.eventSelectedQueryId() === eventQueryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </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\">{{ host.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 (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(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 @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 (canOpenEventRow(eventQueryId)) {\n <button\n (click)=\"host.eventSelectedQueryId.set(eventQueryId)\"\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\">{{ host.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\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n" }]
2249
+ args: [{ selector: 'et-query-devtools-about', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied.value() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"] }]
2471
2250
  }] });
2472
2251
 
2473
2252
  /**
@@ -3583,6 +3362,234 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
3583
3362
  args: [{ selector: 'et-query-devtools-mocks-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsMockDesignerComponent, SelectComponent, SelectSearchDirective], template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\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 it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedDefinition.value() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedPathItem.value() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\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 it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"] }]
3584
3363
  }], ctorParameters: () => [] });
3585
3364
 
3365
+ const isAbsoluteHttpUrl = (value) => {
3366
+ try {
3367
+ return ['http:', 'https:'].includes(new URL(value).protocol);
3368
+ }
3369
+ catch {
3370
+ return false;
3371
+ }
3372
+ };
3373
+ const SCOPE_ROWS = [
3374
+ {
3375
+ key: 'viewState',
3376
+ label: 'Panel view state',
3377
+ hint: 'Where the panel sits, how big it is, the open tab, every filter and the selected query. Set none to forget all of it on each reload.',
3378
+ },
3379
+ {
3380
+ key: 'pins',
3381
+ label: 'Pinned queries',
3382
+ hint: 'Which queries sort to the top of the list. Local outlives the tab, because a pin says what you are working on rather than what you are looking at.',
3383
+ },
3384
+ {
3385
+ key: 'overrides',
3386
+ label: 'Response overrides',
3387
+ hint: 'Armed edits, replayed as queries register - before their first fetch. None is the default: a reload is how the app stops being lied to.',
3388
+ warn: {
3389
+ scopes: ['local'],
3390
+ text: 'Armed overrides now outlive closing the tab. An app that stays tampered with across days is hours of debugging the wrong thing - the bar above the tabs says when they came back.',
3391
+ },
3392
+ },
3393
+ {
3394
+ key: 'mocks',
3395
+ label: 'Designed mocks',
3396
+ hint: 'The library you authored, not whether any of it is armed - arming has its own scope below.',
3397
+ },
3398
+ {
3399
+ key: 'armedMocks',
3400
+ label: 'Armed mocks',
3401
+ hint: 'Which of the library is served. None is the default: a reload is how the app goes back to talking to the API.',
3402
+ warn: {
3403
+ scopes: ['session', 'local'],
3404
+ text: 'The next page load starts serving these routes from the panel, before anyone opens it. The bar above the tabs says when they came back.',
3405
+ },
3406
+ },
3407
+ {
3408
+ key: 'authSessions',
3409
+ label: 'Sessions and accounts',
3410
+ hint: 'The token pairs the Auth tab switches between, and the credentials it logs in with. Local is the default in a development build: a vault that forgets the other user on every reload is not one. None keeps nothing at all.',
3411
+ warn: {
3412
+ scopes: ['local'],
3413
+ text: 'Access and refresh tokens for every user you logged in as, plus what you typed into the account fields, stay in this browser until you forget them. They are never kept for an API env marked production.',
3414
+ },
3415
+ },
3416
+ {
3417
+ key: 'armedFaults',
3418
+ label: 'Armed faults',
3419
+ hint: 'The latency and failures injected per client, budgets included. None is the default, for the same reason as armed mocks.',
3420
+ warn: {
3421
+ scopes: ['session', 'local'],
3422
+ text: 'The next page load starts injecting these failures on its own, so an app that looks broken may only be armed. The bar above the tabs says when they came back.',
3423
+ },
3424
+ },
3425
+ ];
3426
+ const SCOPES = [
3427
+ { value: 'none', label: 'none' },
3428
+ { value: 'session', label: 'session' },
3429
+ { value: 'local', label: 'local' },
3430
+ ];
3431
+ /**
3432
+ * Why IndexedDB - the one store with the quota for a large library of designed data - is not on offer.
3433
+ * These are read synchronously: view state and pins in a field initializer of the panel, and overrides
3434
+ * inside query registration, before the first request. An async store cannot answer either in time, and a
3435
+ * scope that silently arrives late is worse than one that is missing. The mock library is the one that
3436
+ * could tolerate arriving late, since nothing is served until a mock is armed by hand.
3437
+ */
3438
+ const INDEXED_DB_TITLE = 'Unavailable: these are read synchronously - view state before the first render, overrides before the first fetch - and IndexedDB cannot answer in time. Only the mock library could tolerate an async store, and it is not wired to one yet.';
3439
+ const LIMIT_ROWS = [
3440
+ {
3441
+ key: 'maxEvents',
3442
+ label: 'Events log',
3443
+ min: 10,
3444
+ max: 1000,
3445
+ step: 10,
3446
+ hint: 'rows the Events tab keeps. Lowering it trims the log now.',
3447
+ },
3448
+ {
3449
+ key: 'maxDroppedCacheEntries',
3450
+ label: 'Dropped cache entries',
3451
+ min: 0,
3452
+ max: 200,
3453
+ step: 5,
3454
+ hint: 'per client, remembered so the Cache tab can say why an entry went away. Applies as entries are dropped.',
3455
+ },
3456
+ ];
3457
+ /**
3458
+ * Every panel-wide switch in one place - the storage each kind of state uses, the limits the panel would
3459
+ * otherwise hold as constants, and the toggles their own tabs also carry.
3460
+ */
3461
+ class QueryDevtoolsSettingsComponent {
3462
+ constructor() {
3463
+ this.document = inject(DOCUMENT);
3464
+ this.host = injectQueryDevtoolsHost();
3465
+ this.SCOPE_ROWS = SCOPE_ROWS;
3466
+ this.SCOPES = SCOPES;
3467
+ this.INDEXED_DB_TITLE = INDEXED_DB_TITLE;
3468
+ this.LIMIT_ROWS = LIMIT_ROWS;
3469
+ this.ARM_ALL_MOCKS = armAllQueryDevtoolsMocks;
3470
+ this.DISARM_ALL_MOCKS = clearQueryDevtoolsArmedMocks;
3471
+ this.DISARM_ALL_FAULTS = clearQueryDevtoolsFaults;
3472
+ /** What queries actually retain, which is the application's value unless this panel raised it. */
3473
+ this.responseHistory = computed(queryDevtoolsResponseHistory, /* @ts-ignore */
3474
+ ...(ngDevMode ? [{ debugName: "responseHistory" }] : /* istanbul ignore next */ []));
3475
+ this.mockCount = computed(() => queryDevtoolsMocks().length, /* @ts-ignore */
3476
+ ...(ngDevMode ? [{ debugName: "mockCount" }] : /* istanbul ignore next */ []));
3477
+ this.sessionCount = computed(() => queryDevtoolsAuthSessions().length, /* @ts-ignore */
3478
+ ...(ngDevMode ? [{ debugName: "sessionCount" }] : /* istanbul ignore next */ []));
3479
+ this.armedMockCount = computed(() => queryDevtoolsArmedMocks().size, /* @ts-ignore */
3480
+ ...(ngDevMode ? [{ debugName: "armedMockCount" }] : /* istanbul ignore next */ []));
3481
+ this.armedFaultCount = computed(() => this.host.faultClients().filter((client) => client.armed).length, /* @ts-ignore */
3482
+ ...(ngDevMode ? [{ debugName: "armedFaultCount" }] : /* istanbul ignore next */ []));
3483
+ /** The same keys the Events tab's own picker offers - see {@link queryDevtoolsEventClients}. */
3484
+ this.eventClients = computed(() => queryDevtoolsEventClients(this.host.eventLog(), this.host.eventClient()), /* @ts-ignore */
3485
+ ...(ngDevMode ? [{ debugName: "eventClients" }] : /* istanbul ignore next */ []));
3486
+ this.resetConfirming = signal(false, /* @ts-ignore */
3487
+ ...(ngDevMode ? [{ debugName: "resetConfirming" }] : /* istanbul ignore next */ []));
3488
+ this.FORGET_SESSIONS = clearQueryDevtoolsAuthSessions;
3489
+ /** The backends the application declared, or an empty list - the card renders only for a declared one. */
3490
+ this.apiEnvs = computed(queryDevtoolsApiEnvs, /* @ts-ignore */
3491
+ ...(ngDevMode ? [{ debugName: "apiEnvs" }] : /* istanbul ignore next */ []));
3492
+ }
3493
+ /** The env id (or typed URL) a switch's key holds, or `null` while the application picks for itself. */
3494
+ apiEnvValue(apiSwitch) {
3495
+ return queryDevtoolsApiEnvValues()[apiSwitch.storageKey] ?? null;
3496
+ }
3497
+ /** The env a switch falls back to with nothing stored, described for the default button. */
3498
+ apiEnvFallback(apiSwitch) {
3499
+ const fallback = apiSwitch.envs.find((env) => env.id === apiSwitch.fallback);
3500
+ return {
3501
+ label: fallback ? `default (${fallback.label ?? fallback.id})` : 'default',
3502
+ production: fallback?.production === true,
3503
+ };
3504
+ }
3505
+ /** Whether the pick in force - the stored env, or the fallback behind an unwritten key - is production. */
3506
+ apiEnvIsProduction(apiSwitch) {
3507
+ const stored = this.apiEnvValue(apiSwitch);
3508
+ return apiSwitch.envs.find((env) => env.id === (stored ?? apiSwitch.fallback))?.production === true;
3509
+ }
3510
+ /** A stored value no declared env carries, which is a URL somebody typed in below. */
3511
+ apiEnvCustomUrl(apiSwitch) {
3512
+ const stored = this.apiEnvValue(apiSwitch);
3513
+ return stored && !apiSwitch.envs.some((env) => env.id === stored) ? stored : '';
3514
+ }
3515
+ /** Writes the application's own key and reloads - it is read before Angular boots, so nothing else can. */
3516
+ pickApiEnv(apiSwitch, value) {
3517
+ setQueryDevtoolsApiEnv(apiSwitch.storageKey, value);
3518
+ this.document.defaultView?.location.reload();
3519
+ }
3520
+ setCustomApiUrl(apiSwitch, value) {
3521
+ const url = value.trim();
3522
+ if (url === this.apiEnvCustomUrl(apiSwitch))
3523
+ return;
3524
+ if (url && !isAbsoluteHttpUrl(url))
3525
+ return;
3526
+ this.pickApiEnv(apiSwitch, url || null);
3527
+ }
3528
+ settings() {
3529
+ return queryDevtoolsSettings();
3530
+ }
3531
+ scopeOf(key) {
3532
+ return this.settings()[key];
3533
+ }
3534
+ limitOf(key) {
3535
+ return this.settings()[key];
3536
+ }
3537
+ /** What the picked scope means beyond where the state is kept, or `null` while it means nothing extra. */
3538
+ warningFor(row, scope) {
3539
+ return row.warn?.scopes.includes(scope) ? row.warn.text : null;
3540
+ }
3541
+ /** Why one scope is not on offer for a row, for the button's title, or `null` while it is. */
3542
+ unavailableReason(row, scope) {
3543
+ if (row.key !== 'authSessions' || scope !== 'local' || queryDevtoolsAllowsLocalAuthSessions())
3544
+ return null;
3545
+ return 'Unavailable outside a development build: live access and refresh tokens, and the credentials you typed in, must not outlive this tab on a deployed origin. The vault keeps nothing here unless you pick Session, which keeps it for as long as the tab.';
3546
+ }
3547
+ setScope(key, scope) {
3548
+ // Everything armed goes through its own module: changing the scope moves an existing store and
3549
+ // captures whatever is armed right now, so the choice reads as "keep these".
3550
+ if (key === 'overrides')
3551
+ return setQueryDevtoolsOverridesScope(scope);
3552
+ if (key === 'armedMocks')
3553
+ return setQueryDevtoolsArmedMocksScope(scope);
3554
+ if (key === 'armedFaults')
3555
+ return setQueryDevtoolsFaultsScope(scope);
3556
+ setQueryDevtoolsSettings(key === 'viewState'
3557
+ ? { viewState: scope }
3558
+ : key === 'pins'
3559
+ ? { pins: scope }
3560
+ : key === 'authSessions'
3561
+ ? { authSessions: scope }
3562
+ : { mocks: scope });
3563
+ }
3564
+ setLimit(key, value) {
3565
+ const count = Number(value);
3566
+ setQueryDevtoolsSettings(key === 'maxEvents' ? { maxEvents: count } : { maxDroppedCacheEntries: count });
3567
+ }
3568
+ setResponseHistory(value) {
3569
+ setQueryDevtoolsSettings({ responseHistory: Number(value) });
3570
+ }
3571
+ /** Hands retention back to `provideQueryDevtools({ responseHistory })`. */
3572
+ clearResponseHistory() {
3573
+ setQueryDevtoolsSettings({ responseHistory: null });
3574
+ }
3575
+ reset() {
3576
+ this.host.resetDevtools();
3577
+ this.resetConfirming.set(false);
3578
+ }
3579
+ toggleGoneQueries() {
3580
+ this.host.toggleFacet('gone');
3581
+ }
3582
+ showsGoneQueries() {
3583
+ return this.host.queryFacets().has('gone');
3584
+ }
3585
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3586
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsSettingsComponent, isStandalone: true, selector: "et-query-devtools-settings", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:var(--_et-qdt-fg)}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3587
+ }
3588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, decorators: [{
3589
+ type: Component,
3590
+ args: [{ selector: 'et-query-devtools-settings', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:var(--_et-qdt-fg)}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
3591
+ }] });
3592
+
3586
3593
  /** The Faults tab: arms latency/failure injection per client - in-memory only, not persisted. */
3587
3594
  class QueryDevtoolsFaultsTabComponent {
3588
3595
  constructor() {
@@ -3739,7 +3746,7 @@ const summarizeLocks = (options) => {
3739
3746
 
3740
3747
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
3741
3748
  /** The version of `@ethlete/query-devtools` this build was cut from. */
3742
- const QUERY_DEVTOOLS_VERSION = '1.0.0-next.14';
3749
+ const QUERY_DEVTOOLS_VERSION = '1.0.0-next.15';
3743
3750
 
3744
3751
  /**
3745
3752
  * How deep a value is walked before it is replaced by a marker, and how much of a long string or a long
@@ -4223,6 +4230,7 @@ const flattenQueryPathTree = (tree, options) => {
4223
4230
  class QueryDevtoolsQueriesTabComponent {
4224
4231
  constructor() {
4225
4232
  this.host = injectQueryDevtoolsHost();
4233
+ this.devtoolsEnabled = isQueryDevtoolsEnabled();
4226
4234
  /** The status chips above the list, in the order a problem is usually looked for. */
4227
4235
  this.facets = [
4228
4236
  { id: 'error', label: 'Failing' },
@@ -4534,11 +4542,11 @@ class QueryDevtoolsQueriesTabComponent {
4534
4542
  return parts.join(' ').toLowerCase();
4535
4543
  }
4536
4544
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4537
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.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 @if (host.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)=\"host.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 <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\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\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.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\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.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 @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.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 </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n @if (!isGone && host.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 (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .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-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4545
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.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 @if (host.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)=\"host.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 <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\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\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.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\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.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 @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.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 if (!devtoolsEnabled) {\n <p class=\"et-query-devtools-empty\">\n No queries registered: provideQueryDevtools() is not in the application providers.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n @if (!isGone && host.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 (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .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-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4538
4546
  }
4539
4547
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, decorators: [{
4540
4548
  type: Component,
4541
- args: [{ selector: 'et-query-devtools-queries-tab', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsDetailComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.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 @if (host.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)=\"host.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 <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\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\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.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\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.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 @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.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 </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n @if (!isGone && host.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 (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .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-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"] }]
4549
+ args: [{ selector: 'et-query-devtools-queries-tab', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsDetailComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.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 @if (host.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)=\"host.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 <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\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\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.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\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.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 @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.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 if (!devtoolsEnabled) {\n <p class=\"et-query-devtools-empty\">\n No queries registered: provideQueryDevtools() is not in the application providers.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n @if (!isGone && host.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 (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .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-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"] }]
4542
4550
  }], ctorParameters: () => [] });
4543
4551
 
4544
4552
  /**
@@ -4644,6 +4652,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
4644
4652
  ], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = host.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 <et-query-devtools-route [segments]=\"pagedId.segments\" />\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 <et-query-devtools-features [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=\"activityRow; context: { activity: host.stackActivity(paged) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = host.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 <et-query-devtools-route [segments]=\"stackId.segments\" />\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 <et-query-devtools-features [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=\"activityRow; context: { activity: host.stackActivity(stack) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.stackSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.stackSelectedQueryId.set(null)\"\n />\n</div>\n\n<ng-template #activityRow 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 {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.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<ng-template #innerQueries 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]=\"host.stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.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 <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query ' + ($index + 1)\" />\n </span>\n @let linkStats = host.linkActivity(link);\n @if (linkStats.hasActivity) {\n <span class=\"et-query-devtools-traffic\">\n {{ host.formatTransferred(linkStats.stats.receivedBytes, linkStats.stats.hasEstimatedBytes) }}\n </span>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@layer components{.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-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}}\n"] }]
4645
4653
  }] });
4646
4654
 
4655
+ /**
4656
+ * The waterfall's grid, axis and bars, as a styles-only component the panel mounts when the Timeline tab
4657
+ * is first opened.
4658
+ *
4659
+ * @internal
4660
+ */
4661
+ class QueryDevtoolsTimelineStylesComponent {
4662
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4663
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsTimelineStylesComponent, isStandalone: true, selector: "et-query-devtools-timeline-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.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}@media(hover:hover){.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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
4664
+ }
4665
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, decorators: [{
4666
+ type: Component,
4667
+ args: [{ selector: 'et-query-devtools-timeline-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.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}@media(hover:hover){.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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"] }]
4668
+ }] });
4669
+
4647
4670
  /** Past this many rows, the timeline keeps the newest and counts the rest instead of drawing them. */
4648
4671
  const MAX_TIMELINE_ROWS = 200;
4649
4672
  /** Where the timeline's axis labels sit, as fractions of the window. */
@@ -4796,7 +4819,7 @@ const describeEntryId = (id) => {
4796
4819
  return id;
4797
4820
  return `${method} ${route}${client ? ` · ${client}` : ''}${seq && seq !== '0' ? ` #${seq}` : ''}`;
4798
4821
  };
4799
- const STORAGE_KEY = QUERY_DEVTOOLS_VIEW_STATE_KEY;
4822
+ const STORAGE_KEY = _QUERY_DEVTOOLS_VIEW_STATE_KEY;
4800
4823
  /**
4801
4824
  * Pinned queries, held apart from {@link STORAGE_KEY} and defaulting to `localStorage`: everything under
4802
4825
  * that key is view state that should die with the tab, while a pin says which query is being worked on
@@ -4861,7 +4884,7 @@ const settledFloatRect = (rect, viewport) => {
4861
4884
  };
4862
4885
  };
4863
4886
  const resizedFloatRect = (base, { move, viewport }) => {
4864
- const { edge, dx, dy } = move;
4887
+ const { edge, totalDx: dx, totalDy: dy } = move;
4865
4888
  const maxWidth = edge.includes('w') ? base.x + base.width : viewport.width - base.x;
4866
4889
  const maxHeight = edge.includes('n') ? base.y + base.height : viewport.height - base.y;
4867
4890
  const requestedWidth = edge.includes('e') ? base.width + dx : edge.includes('w') ? base.width - dx : base.width;
@@ -5005,6 +5028,7 @@ const decodeJwtPayload = (token) => {
5005
5028
  return null;
5006
5029
  }
5007
5030
  };
5031
+ let warnedMissingProvider = false;
5008
5032
  /**
5009
5033
  * A floating, dockable panel that inspects the live state of the signals-first `@ethlete/query`
5010
5034
  * system: queries, stacks, sequences, bearer auth providers, the repository cache and a rolling
@@ -5541,6 +5565,10 @@ class QueryDevtoolsComponent {
5541
5565
  ...(ngDevMode ? [{ debugName: "restoredOverrides" }] : /* istanbul ignore next */ []));
5542
5566
  this.DROP_RESTORED_OVERRIDES = clearRestoredQueryDevtoolsOverrides;
5543
5567
  this.FLOAT_RESIZE_EDGES = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
5568
+ if (ngDevMode && !warnedMissingProvider && !isQueryDevtoolsEnabled()) {
5569
+ warnedMissingProvider = true;
5570
+ console.warn("<et-query-devtools>: provideQueryDevtools() is not in the application providers, so the panel stays empty. Add it to bootstrapApplication's providers.");
5571
+ }
5544
5572
  setQueryDevtoolsUiMounted(true);
5545
5573
  this.destroyRef.onDestroy(() => setQueryDevtoolsUiMounted(false));
5546
5574
  this.toggleJsonPath = (path, expand) => {
@@ -6505,7 +6533,9 @@ class QueryDevtoolsComponent {
6505
6533
  }));
6506
6534
  }
6507
6535
  causeLabel(cause) {
6508
- const scope = cause.url ? this.requestPath(cause.url) : 'everything in use';
6536
+ const scope = [cause.url ? this.requestPath(cause.url) : null, cause.tag ? `tag ${cause.tag}` : null]
6537
+ .filter((part) => part !== null)
6538
+ .join(' · ') || 'everything in use';
6509
6539
  const what = cause.type === 'invalidation'
6510
6540
  ? `invalidated ${scope}`
6511
6541
  : cause.type === 'mutation'
@@ -7490,5 +7520,5 @@ const QUERY_DEVTOOLS_IMPORTS = [QueryDevtoolsComponent];
7490
7520
  * Generated bundle index. Do not edit.
7491
7521
  */
7492
7522
 
7493
- export { QUERY_DEVTOOLS_IMPORTS, QUERY_DEVTOOLS_VERSION, QueryDevtoolsAboutComponent, QueryDevtoolsComponent, QueryDevtoolsJsonComponent, QueryDevtoolsJsonStylesComponent, QueryDevtoolsSettingsComponent, QueryDevtoolsTimelineStylesComponent, clampFloatToPeek, kindOf, resizedFloatRect, settledFloatRect };
7523
+ export { QUERY_DEVTOOLS_IMPORTS, QUERY_DEVTOOLS_VERSION, QueryDevtoolsComponent };
7494
7524
  //# sourceMappingURL=ethlete-query-devtools.mjs.map