@mintplayer/ng-spark 22.19.0 → 22.21.0

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 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-renderers.mjs","sources":["../../renderers/src/renderer-inputs.ts","../../renderers/src/spark-attribute-renderer-registry.ts","../../renderers/mintplayer-ng-spark-renderers.ts"],"sourcesContent":["import { reflectComponentType, Type } from '@angular/core';\nimport { PersistentObjectAttribute, QueryResultItemValue } from '@mintplayer/ng-spark/models';\n\n// Reflection result cached per component type: the input builders are template\n// expressions re-evaluated every CD pass (and query-list virtual-scrolls).\nconst declaredInputs = new Map<Type<any>, Set<string>>();\n\n/**\n * Drops entries the component doesn't declare, so every contract member is\n * genuinely optional — NgComponentOutlet throws on an undeclared input.\n */\nexport function withDeclaredInputs(component: Type<any>, inputs: Record<string, any>): Record<string, any> {\n let declared = declaredInputs.get(component);\n if (!declared) {\n declared = new Set(reflectComponentType(component)?.inputs.map(i => i.templateName) ?? []);\n declaredInputs.set(component, declared);\n }\n return Object.fromEntries(Object.entries(inputs).filter(([k]) => declared!.has(k)));\n}\n\n/**\n * The renderer-facing value of an attribute: the flat value, or for AsDetail\n * attributes (whose flat value the server nulls on purpose) the nested\n * PersistentObject (single) / PersistentObject[] (array).\n *\n * Used by the detail and edit paths, which still work in attributes.\n */\nexport function rendererValue(attr: PersistentObjectAttribute | undefined): any {\n return attr?.value ?? attr?.object ?? attr?.objects;\n}\n\n/**\n * The renderer-facing value of a query-result cell.\n *\n * A cell is a single channel: whatever a renderer needs is already on `value` — for a single-child\n * AsDetail column that is the nested PersistentObject itself, put there by the server so this\n * matches what {@link rendererValue} falls through to on a detail page. There is nothing to fall\n * back to, which is why this is deliberately not the same function: keeping them separate is what\n * stops a renderer silently receiving `undefined` because it was written against the attribute\n * shape's `object` / `objects` fields, which a row does not carry.\n */\nexport function cellValue(value: QueryResultItemValue | undefined): any {\n return value?.value;\n}\n","import { InjectionToken, Provider, Type } from '@angular/core';\n\nexport interface SparkAttributeRendererRegistration {\n /** The renderer name (must match attr.renderer in model JSON) */\n name: string;\n /** Component for the PO detail page. Must implement SparkAttributeDetailRenderer. Omit for a column/edit-only renderer. */\n detailComponent?: Type<any> | null;\n /** Component for query-list column cells. Must implement SparkAttributeColumnRenderer. Omit for a detail/edit-only renderer. */\n columnComponent?: Type<any> | null;\n /** Optional component for create/edit forms. Must implement SparkAttributeEditRenderer. When omitted, the default input is used. */\n editComponent?: Type<any>;\n}\n\nexport const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken<SparkAttributeRendererRegistration[]>(\n 'SparkAttributeRenderers',\n { factory: () => [] }\n);\n\n/**\n * Register custom attribute renderers globally.\n *\n * @example\n * provideSparkAttributeRenderers([\n * { name: 'video-player', detailComponent: VideoDetailComponent, columnComponent: VideoColumnComponent },\n * { name: 'color-swatch', detailComponent: ColorDetailComponent, columnComponent: ColorColumnComponent },\n * ])\n */\nexport function provideSparkAttributeRenderers(\n renderers: SparkAttributeRendererRegistration[]\n): Provider {\n return {\n provide: SPARK_ATTRIBUTE_RENDERERS,\n useValue: renderers,\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAGA;AACA;AACA,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0B;AAExD;;;AAGG;AACG,SAAU,kBAAkB,CAAC,SAAoB,EAAE,MAA2B,EAAA;IAClF,IAAI,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC;IAC5C,IAAI,CAAC,QAAQ,EAAE;QACb,QAAQ,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;AAC1F,QAAA,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;IACzC;AACA,IAAA,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,QAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF;AAEA;;;;;;AAMG;AACG,SAAU,aAAa,CAAC,IAA2C,EAAA;IACvE,OAAO,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,MAAM,IAAI,IAAI,EAAE,OAAO;AACrD;AAEA;;;;;;;;;AASG;AACG,SAAU,SAAS,CAAC,KAAuC,EAAA;IAC/D,OAAO,KAAK,EAAE,KAAK;AACrB;;AC9BO,MAAM,yBAAyB,GAAG,IAAI,cAAc,CACzD,yBAAyB,EACzB,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;AAGvB;;;;;;;;AAQG;AACG,SAAU,8BAA8B,CAC5C,SAA+C,EAAA;IAE/C,OAAO;AACL,QAAA,OAAO,EAAE,yBAAyB;AAClC,QAAA,QAAQ,EAAE,SAAS;KACpB;AACH;;AClCA;;AAEG;;;;"}
1
+ {"version":3,"file":"mintplayer-ng-spark-renderers.mjs","sources":["../../renderers/src/renderer-inputs.ts","../../renderers/src/spark-attribute-renderer-registry.ts","../../renderers/mintplayer-ng-spark-renderers.ts"],"sourcesContent":["import { reflectComponentType, Type } from '@angular/core';\nimport { PersistentObjectAttribute, QueryResultItemValue } from '@mintplayer/ng-spark/models';\n\n// Reflection result cached per component type: the input builders are template\n// expressions re-evaluated every CD pass (and query-list virtual-scrolls).\nconst declaredInputs = new Map<Type<any>, Set<string>>();\n\n/**\n * Drops entries the component doesn't declare, so every contract member is\n * genuinely optional — NgComponentOutlet throws on an undeclared input.\n */\nexport function withDeclaredInputs(component: Type<any>, inputs: Record<string, any>): Record<string, any> {\n let declared = declaredInputs.get(component);\n if (!declared) {\n declared = new Set(reflectComponentType(component)?.inputs.map(i => i.templateName) ?? []);\n declaredInputs.set(component, declared);\n }\n return Object.fromEntries(Object.entries(inputs).filter(([k]) => declared!.has(k)));\n}\n\n/**\n * The renderer-facing value of an attribute: the flat value, or for AsDetail\n * attributes (whose flat value the server nulls on purpose) the nested\n * PersistentObject (single) / PersistentObject[] (array).\n *\n * Used by the detail and edit paths, which still work in attributes.\n */\nexport function rendererValue(attr: PersistentObjectAttribute | undefined): any {\n return attr?.value ?? attr?.object ?? attr?.objects;\n}\n\n/**\n * The renderer-facing value of a query-result cell.\n *\n * A cell is a single channel: whatever a renderer needs is already on `value` — for a single-child\n * AsDetail column that is the nested PersistentObject itself, put there by the server so this\n * matches what {@link rendererValue} falls through to on a detail page. There is nothing to fall\n * back to, which is why this is deliberately not the same function: keeping them separate is what\n * stops a renderer silently receiving `undefined` because it was written against the attribute\n * shape's `object` / `objects` fields, which a row does not carry.\n */\nexport function cellValue(value: QueryResultItemValue | undefined): any {\n return value?.value;\n}\n","import { InjectionToken, Provider, Type } from '@angular/core';\n\nexport interface SparkAttributeRendererRegistration {\n /** The renderer name (must match attr.renderer in model JSON) */\n name: string;\n /** Component for the PO detail page. Must implement SparkAttributeDetailRenderer. Omit for a column/edit-only renderer. */\n detailComponent?: Type<any> | null;\n /** Component for query-list column cells. Must implement SparkAttributeColumnRenderer. Omit for a detail/edit-only renderer. */\n columnComponent?: Type<any> | null;\n /** Optional component for create/edit forms. Must implement SparkAttributeEditRenderer. When omitted, the default input is used. */\n editComponent?: Type<any>;\n /**\n * Relabels this column's values in a filter panel (#431).\n *\n * A renderer is a component that paints arbitrary DOM, and a distinct value has no row, so it\n * cannot be instantiated to produce text for a filter list. The label therefore defaults to the\n * server-computed cell text: a status rendered as a coloured pill lists `Active`, a rating\n * rendered as stars lists `3`. This is the escape for when that is too raw — a value stored as\n * `2` whose cell reads \"High priority\".\n *\n * ⚠️ A **pure function**: no DOM, no component instantiation, no row context. There is no row to\n * give it, which is the whole reason the renderer itself cannot be used here.\n */\n filterLabel?: (value: unknown) => string;\n}\n\nexport const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken<SparkAttributeRendererRegistration[]>(\n 'SparkAttributeRenderers',\n { factory: () => [] }\n);\n\n/**\n * Register custom attribute renderers globally.\n *\n * @example\n * provideSparkAttributeRenderers([\n * { name: 'video-player', detailComponent: VideoDetailComponent, columnComponent: VideoColumnComponent },\n * { name: 'color-swatch', detailComponent: ColorDetailComponent, columnComponent: ColorColumnComponent },\n * ])\n */\nexport function provideSparkAttributeRenderers(\n renderers: SparkAttributeRendererRegistration[]\n): Provider {\n return {\n provide: SPARK_ATTRIBUTE_RENDERERS,\n useValue: renderers,\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAGA;AACA;AACA,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0B;AAExD;;;AAGG;AACG,SAAU,kBAAkB,CAAC,SAAoB,EAAE,MAA2B,EAAA;IAClF,IAAI,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC;IAC5C,IAAI,CAAC,QAAQ,EAAE;QACb,QAAQ,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;AAC1F,QAAA,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;IACzC;AACA,IAAA,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,QAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF;AAEA;;;;;;AAMG;AACG,SAAU,aAAa,CAAC,IAA2C,EAAA;IACvE,OAAO,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,MAAM,IAAI,IAAI,EAAE,OAAO;AACrD;AAEA;;;;;;;;;AASG;AACG,SAAU,SAAS,CAAC,KAAuC,EAAA;IAC/D,OAAO,KAAK,EAAE,KAAK;AACrB;;ACjBO,MAAM,yBAAyB,GAAG,IAAI,cAAc,CACzD,yBAAyB,EACzB,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;AAGvB;;;;;;;;AAQG;AACG,SAAU,8BAA8B,CAC5C,SAA+C,EAAA;IAE/C,OAAO;AACL,QAAA,OAAO,EAAE,yBAAyB;AAClC,QAAA,QAAQ,EAAE,SAAS;KACpB;AACH;;AC/CA;;AAEG;;;;"}
@@ -229,6 +229,29 @@ class SparkService {
229
229
  skip: options?.skip,
230
230
  take: options?.take,
231
231
  search: options?.search,
232
+ // Per-column value filters (#431). The shape that could not be expressed as a query string,
233
+ // and the reason these reads are a POST at all.
234
+ columns: options?.columns?.length ? options.columns : undefined,
235
+ });
236
+ }
237
+ /**
238
+ * The distinct values behind one column's filter panel (#431).
239
+ *
240
+ * `columns` carries the OTHER columns' current filters, so the values returned are the ones still
241
+ * reachable — offering a value that empties the grid the moment it is picked is worse than
242
+ * omitting it.
243
+ *
244
+ * An empty result means either "nothing matches" or "you may not enumerate this column". The two
245
+ * are deliberately indistinguishable, so there is nothing for a caller to branch on.
246
+ */
247
+ async getDistinctValues(queryId, column, options) {
248
+ return this.sendRead(`${this.baseUrl}/queries/distinct-values`, {
249
+ queryId,
250
+ column,
251
+ search: options?.search || undefined,
252
+ columns: options?.columns?.length ? options.columns : undefined,
253
+ parentId: options?.parentId,
254
+ parentType: options?.parentType,
232
255
  });
233
256
  }
234
257
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-services.mjs","sources":["../../services/src/retry-action.service.ts","../../services/src/spark-language.service.ts","../../services/src/spark-streaming.service.ts","../../services/src/spark.service.ts","../../services/src/spark-built-in-icons.ts","../../services/src/spark-icon-registry.ts","../../services/src/spark-query-actions.service.ts","../../services/src/spark-timezone.interceptor.ts","../../services/mintplayer-ng-spark-services.ts"],"sourcesContent":["import { Injectable, signal } from '@angular/core';\nimport { RetryActionPayload, RetryActionResult } from '@mintplayer/ng-spark/models';\n\n@Injectable({ providedIn: 'root' })\nexport class RetryActionService {\n private resolveRetry: ((result: RetryActionResult) => void) | null = null;\n\n payload = signal<RetryActionPayload | null>(null);\n\n show(payload: RetryActionPayload): Promise<RetryActionResult> {\n this.payload.set(payload);\n return new Promise(resolve => { this.resolveRetry = resolve; });\n }\n\n respond(result: RetryActionResult): void {\n this.payload.set(null);\n this.resolveRetry?.(result);\n this.resolveRetry = null;\n }\n}\n","import { Injectable, inject, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { TranslatedString, currentLanguage } from '@mintplayer/ng-spark/models';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\ninterface CultureConfiguration {\n languages: Record<string, TranslatedString>;\n defaultLanguage: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SparkLanguageService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly currentLang = signal('en');\n private readonly translationsMap = signal<Record<string, TranslatedString>>({});\n\n readonly language = this.currentLang.asReadonly();\n readonly languages = signal<Record<string, TranslatedString>>({});\n\n constructor() {\n this.loadCulture();\n this.loadTranslations();\n }\n\n private async loadCulture(): Promise<void> {\n const config = await firstValueFrom(this.http.get<CultureConfiguration>(`${this.baseUrl}/culture`));\n this.languages.set(config.languages);\n const saved = localStorage.getItem('spark-lang');\n const lang = saved ?? config.defaultLanguage;\n this.currentLang.set(lang);\n currentLanguage.set(lang);\n }\n\n private async loadTranslations(): Promise<void> {\n const t = await firstValueFrom(this.http.get<Record<string, TranslatedString>>(`${this.baseUrl}/translations`));\n this.translationsMap.set(t);\n }\n\n setLanguage(lang: string) {\n this.currentLang.set(lang);\n currentLanguage.set(lang);\n localStorage.setItem('spark-lang', lang);\n }\n\n resolve(ts: TranslatedString | undefined): string {\n if (!ts) return '';\n const lang = this.currentLang();\n return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n }\n\n t(key: string): string {\n const ts = this.translationsMap()[key];\n return this.resolve(ts) || key;\n }\n}\n","import { inject, Injectable, NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { StreamingMessage } from '@mintplayer/ng-spark/models';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\n@Injectable({ providedIn: 'root' })\nexport class SparkStreamingService {\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly ngZone = inject(NgZone);\n\n connectToStreamingQuery(queryId: string): Observable<StreamingMessage> {\n return new Observable<StreamingMessage>(subscriber => {\n let ws: WebSocket | null = null;\n let retryCount = 0;\n let retryTimeout: ReturnType<typeof setTimeout> | null = null;\n let closed = false;\n const maxRetries = 10;\n const maxDelay = 30000;\n\n const connect = () => {\n if (closed) return;\n\n const url = this.buildWebSocketUrl(queryId);\n ws = new WebSocket(url);\n\n ws.onopen = () => {\n retryCount = 0; // Reset on successful connect\n };\n\n ws.onmessage = (event) => {\n try {\n const message: StreamingMessage = JSON.parse(event.data);\n this.ngZone.run(() => subscriber.next(message));\n } catch {\n // Ignore malformed messages\n }\n };\n\n ws.onerror = () => {\n // Error will trigger onclose\n };\n\n ws.onclose = (event) => {\n if (closed) return;\n\n // Don't reconnect on normal closure\n if (event.code === 1000) {\n this.ngZone.run(() => subscriber.complete());\n return;\n }\n\n // Reconnect with exponential backoff\n if (retryCount < maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, retryCount), maxDelay);\n retryCount++;\n retryTimeout = setTimeout(() => connect(), delay);\n } else {\n this.ngZone.run(() => subscriber.error(new Error('WebSocket connection failed after maximum retries')));\n }\n };\n };\n\n connect();\n\n // Teardown: close WebSocket on unsubscribe\n return () => {\n closed = true;\n if (retryTimeout) {\n clearTimeout(retryTimeout);\n }\n if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {\n ws.close(1000, 'Client unsubscribed');\n }\n };\n });\n }\n\n private buildWebSocketUrl(queryId: string): string {\n const encodedId = encodeURIComponent(queryId);\n const path = `${this.baseUrl}/queries/${encodedId}/stream`;\n\n // If baseUrl is absolute (starts with http/https), replace protocol\n if (this.baseUrl.startsWith('http://')) {\n return path.replace(/^http:\\/\\//, 'ws://');\n }\n if (this.baseUrl.startsWith('https://')) {\n return path.replace(/^https:\\/\\//, 'wss://');\n }\n\n // Relative URL — construct from window.location\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n return `${protocol}//${window.location.host}${path}`;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { CustomActionDefinition, EntityPermissions, EntityType, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, ProgramUnitsConfiguration, QueryResult, SparkQuery, RetryActionPayload, RetryActionResult } from '@mintplayer/ng-spark/models';\nimport { ClientOperationEnvelope, RetryOperation, SparkClientOperationDispatcher } from '@mintplayer/ng-spark/client-operations';\nimport { SortColumn } from '@mintplayer/pagination';\nimport { RetryActionService } from './retry-action.service';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\n/**\n * Context for {@link SparkService.newObject}. Everything is optional: a standalone New sends an\n * empty body, and an AsDetail row on a parent that has never been saved sends no `parentId` —\n * which is the honest answer, not a gap. The server hands the hook a null parent rather than\n * trusting the client's copy of one.\n */\nexport interface NewObjectOptions {\n /** Name of the parent's AsDetail attribute the row is for; absent for a standalone New. */\n asDetailAttribute?: string;\n /** The parent's entity type — required whenever `asDetailAttribute` is set. */\n parentType?: string;\n /** The parent's id, absent while the parent is itself unsaved. */\n parentId?: string;\n /** Free-form arguments, e.g. which variant a New menu chose. */\n parameters?: Record<string, string>;\n}\n\n/** Context for {@link SparkService.deleteRow}. Every field is required — see `DeleteRow.cs`. */\nexport interface DeleteRowOptions {\n asDetailAttribute: string;\n parentType: string;\n parentId: string;\n /** The row's `__sparkRowKey`, as it arrived from the server. */\n rowKey: string;\n parameters?: Record<string, string>;\n}\n\n/**\n * The union of every body the envelope helpers send.\n *\n * Named rather than inlined per helper because the New and delete-row calls carry neither a\n * `persistentObject` nor a `triggeredBy`: an inline type that did not list their fields would have\n * rejected them at the call site, and spreading them through `any` would have hidden a typo in a\n * field name the server matches by exact spelling.\n */\ntype EnvelopeRequestBody = {\n /** The entity type the request is about — the request parameter, not the submitted object's claim. */\n objectTypeId?: string;\n /** The target object's id, for update and delete. */\n id?: string;\n persistentObject?: any;\n triggeredBy?: string;\n retryResults?: RetryActionResult[];\n} & Partial<NewObjectOptions> & Partial<Omit<DeleteRowOptions, 'asDetailAttribute' | 'parentType' | 'parentId'>>;\n\n@Injectable({ providedIn: 'root' })\nexport class SparkService {\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly http = inject(HttpClient);\n private readonly retryActionService = inject(RetryActionService);\n\n /**\n * How many prompts one request will answer before giving up. Matches the .NET client's\n * `SparkClient.MaxRetryDepth`, and for the same reason: this is a termination bound, not a\n * capacity one. Far above any real conversation (Fleet's longest is two) and far below anything\n * that could hide a spin.\n */\n private static readonly MAX_RETRY_DEPTH = 16;\n private readonly dispatcher = inject(SparkClientOperationDispatcher);\n\n // Entity Types\n async getEntityTypes(): Promise<EntityType[]> {\n return firstValueFrom(this.http.get<EntityType[]>(`${this.baseUrl}/types`));\n }\n\n async getEntityType(id: string): Promise<EntityType> {\n return firstValueFrom(this.http.get<EntityType>(`${this.baseUrl}/types/${encodeURIComponent(id)}`));\n }\n\n async getEntityTypeByClrType(clrType: string): Promise<EntityType | undefined> {\n const types = await this.getEntityTypes();\n return types.find(t => t.clrType === clrType);\n }\n\n // Permissions\n async getPermissions(entityTypeId: string): Promise<EntityPermissions> {\n return firstValueFrom(this.http.get<EntityPermissions>(`${this.baseUrl}/permissions/${encodeURIComponent(entityTypeId)}`));\n }\n\n /** Resolved at most once; see getQueryByName. */\n #queryCatalogue?: Promise<SparkQuery[]>;\n\n // Queries\n async getQueries(): Promise<SparkQuery[]> {\n return firstValueFrom(this.http.get<SparkQuery[]>(`${this.baseUrl}/queries`));\n }\n\n async getQuery(id: string): Promise<SparkQuery> {\n return firstValueFrom(this.http.post<SparkQuery>(`${this.baseUrl}/queries/get`, { queryId: id }));\n }\n\n /**\n * Resolves a query by name, fetching the catalogue at most once per service instance.\n *\n * Without the cache this costs a full `GET /spark/queries` per call, and a form with several\n * reference attributes resolves them in parallel — so opening one page fetched the whole query\n * catalogue N times over. The catalogue is per-caller and effectively static for a page's\n * lifetime; the service is scoped to the app, so a permission change lands on the next load.\n */\n async getQueryByName(name: string): Promise<SparkQuery | undefined> {\n this.#queryCatalogue ??= this.getQueries();\n const queries = await this.#queryCatalogue;\n return queries.find(q => q.name === name);\n }\n\n async executeQuery(queryId: string, options?: {\n sortColumns?: SortColumn[];\n parentId?: string;\n parentType?: string;\n skip?: number;\n take?: number;\n search?: string;\n }): Promise<QueryResult> {\n // A POST with a typed body, not a GET with a query string. `sortColumns` used to be encoded as\n // `prop:asc,other:desc` — an encoding invented because a query string has no arrays. Column\n // filtering (several columns, several selected values each) would need a second such encoding,\n // which is the reason the reads moved to POST at all.\n return this.sendRead<QueryResult>(`${this.baseUrl}/queries/execute`, {\n queryId,\n sortColumns: options?.sortColumns?.length\n ? options.sortColumns.map(c => ({ property: c.property, direction: c.direction === 'descending' ? 'desc' : 'asc' }))\n : undefined,\n parentId: options?.parentId,\n parentType: options?.parentType,\n skip: options?.skip,\n take: options?.take,\n search: options?.search,\n });\n }\n\n /**\n * Runs a query identified by name.\n *\n * It used to forward only the parent, dropping skip/take/search — so every reference picker and\n * AsDetail reference column silently saw the server's default of 50 candidates, with no indication\n * that the list was cut. A picker that omits the option you are looking for, and says nothing, is\n * worse than one that fails.\n */\n async executeQueryByName(queryName: string, options?: {\n parentId?: string;\n parentType?: string;\n skip?: number;\n take?: number;\n search?: string;\n sortColumns?: SortColumn[];\n }): Promise<QueryResult> {\n const query = await this.getQueryByName(queryName);\n if (!query) return { columns: [], items: [], totalItems: 0, skip: 0, take: options?.take ?? 50 };\n return this.executeQuery(query.id, options);\n }\n\n // Program Units\n async getProgramUnits(): Promise<ProgramUnitsConfiguration> {\n return firstValueFrom(this.http.get<ProgramUnitsConfiguration>(`${this.baseUrl}/program-units`));\n }\n\n // Persistent Objects\n async get(type: string, id: string): Promise<PersistentObject> {\n return this.sendRead<PersistentObject>(`${this.baseUrl}/po/load`, { objectTypeId: type, id });\n }\n\n async create(type: string, data: Partial<PersistentObject>): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/create`,\n { objectTypeId: type, persistentObject: data }\n );\n }\n\n async update(type: string, id: string, data: Partial<PersistentObject>): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/update`,\n { objectTypeId: type, id, persistentObject: data }\n );\n }\n\n /**\n * Asks the server to reshape an in-progress object after `triggeredBy`'s value changed.\n *\n * Writes nothing, but goes through the envelope like every other mutating call: a refresh may\n * legitimately emit notifications, and may open the retry-action prompt.\n *\n * `triggeredBy` is the attribute's name. For a trigger inside an AsDetail row it is the same\n * path form the inline validation errors use — `Jobs[2].ProfessionId`.\n */\n async refresh(type: string, data: Partial<PersistentObject>, triggeredBy: string): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/refresh`,\n { objectTypeId: type, persistentObject: data, triggeredBy }\n );\n }\n\n /**\n * Asks the server to construct a new object of `type`, so `OnNewAsync` can default it.\n *\n * Writes nothing — the object comes back unsaved, and for an AsDetail row the parent still owns\n * the save. Only called for a row type whose `serverSideRowLifecycle` is on; every other type\n * keeps building its blank row locally, which is why switching the flag off costs no request.\n *\n * ⚠️ The row comes back **keyed**: the server constructs the CLR instance, so the row-key field\n * initializer runs and `po.id` carries the key. Flattening it with `nestedPoToDict` therefore\n * populates `__sparkRowKey`, and the row is matchable on save from the moment it is added — the\n * whole reason this round-trip is worth a request.\n */\n async newObject(type: string, options?: NewObjectOptions): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/new`,\n { objectTypeId: type, ...(options ?? {}) }\n );\n }\n\n /**\n * Asks whether a stored row may leave its parent's AsDetail collection.\n *\n * Deletes nothing: a resolved promise means the caller may splice the row out of the collection\n * it is editing, and the removal is persisted with the parent. A hook that refuses rejects with a\n * 400 whose `error.error.result.errors` carries the readable reason, exactly like a save.\n *\n * A POST — as everything is now. It used to be worth explaining: the framework's real delete route\n * was a catch-all that would have swallowed a sibling DELETE. The catch-all is gone with the rest\n * of the route variables.\n */\n async deleteRow(type: string, options: DeleteRowOptions): Promise<void> {\n await this.postWithEnvelope<{ removed: boolean }>(\n `${this.baseUrl}/po/delete-row`,\n { objectTypeId: type, ...options }\n );\n }\n\n async delete(type: string, id: string): Promise<void> {\n return this.postWithEnvelope<void>(\n `${this.baseUrl}/po/delete`,\n { objectTypeId: type, id }\n );\n }\n\n // Custom Actions\n async getCustomActions(objectTypeId: string): Promise<CustomActionDefinition[]> {\n return firstValueFrom(this.http.post<CustomActionDefinition[]>(`${this.baseUrl}/actions/list`, { objectTypeId }));\n }\n\n /**\n * @param parent The object of THIS type the action is operating on — the detail-page invocation.\n * @param selectedItemIds Row ids from a query. Ids, never row objects: a row is a projection.\n * @param queryParent When invoked from a sub-query, the object whose detail page it was rendered\n * on — a DIFFERENT type (the cars listed on a company's page are Cars, the page is a Company).\n * Sent as id + type, exactly as the query endpoint names it, and resolved server-side under its\n * own type with its own Read gate.\n * @param queryId The query the selection came from, so the server can re-run it narrowed to those\n * ids and hand the action the rows the grid actually had -- index-computed columns included.\n */\n async executeCustomAction(\n objectTypeId: string,\n actionName: string,\n parent?: PersistentObject,\n selectedItemIds?: string[],\n queryParent?: { id: string; type: string },\n queryId?: string,\n ): Promise<void> {\n const body: {\n objectTypeId: string; actionName: string;\n parent?: PersistentObject; selectedItemIds?: string[];\n parentId?: string; parentType?: string; queryId?: string;\n retryResults?: RetryActionResult[];\n } = { objectTypeId, actionName, parent, selectedItemIds, parentId: queryParent?.id, parentType: queryParent?.type, queryId };\n return this.postWithEnvelope<void>(\n `${this.baseUrl}/actions/execute`,\n body as any\n );\n }\n\n // LookupReferences\n async getLookupReferences(): Promise<LookupReferenceListItem[]> {\n return firstValueFrom(this.http.get<LookupReferenceListItem[]>(`${this.baseUrl}/lookupref`));\n }\n\n async getLookupReference(name: string): Promise<LookupReference> {\n return firstValueFrom(this.http.get<LookupReference>(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`));\n }\n\n async addLookupReferenceValue(name: string, value: LookupReferenceValue): Promise<LookupReferenceValue> {\n return firstValueFrom(this.http.post<LookupReferenceValue>(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`, value));\n }\n\n async updateLookupReferenceValue(name: string, key: string, value: LookupReferenceValue): Promise<LookupReferenceValue> {\n return firstValueFrom(this.http.put<LookupReferenceValue>(\n `${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`,\n value\n ));\n }\n\n async deleteLookupReferenceValue(name: string, key: string): Promise<void> {\n return firstValueFrom(this.http.delete<void>(\n `${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`\n ));\n }\n\n // Envelope-aware HTTP helpers.\n // All mutation endpoints (Create / Update / Delete / Execute custom action) emit the\n // ClientOperationEnvelope { result, operations } shape. These helpers unwrap the envelope,\n // dispatch any non-retry operations, and translate 449 retry-operations into the existing\n // RetryActionService modal flow.\n\n private postWithEnvelope<T>(url: string, body: EnvelopeRequestBody): Promise<T> {\n return this.sendWithEnvelope<T>(\n () => firstValueFrom(this.http.post<ClientOperationEnvelope<T>>(url, body)),\n body,\n () => this.postWithEnvelope<T>(url, body),\n );\n }\n\n /**\n * A read: posts the request, and returns the response **as-is** rather than unwrapping an\n * envelope.\n *\n * The read endpoints moved from GET to POST so their hooks could prompt, but their success\n * responses are still bare — a `PersistentObject`, a `QueryResult`. Only the 449 is enveloped,\n * because a retry has nowhere else to live. So this shares the retry loop with the mutating calls\n * and nothing else.\n */\n private async sendRead<T>(url: string, body: Record<string, unknown>): Promise<T> {\n try {\n return await firstValueFrom(this.http.post<T>(url, body));\n } catch (error) {\n return this.handleEnvelopeRetryError<T>(\n error as HttpErrorResponse,\n () => this.sendRead<T>(url, body),\n body as { retryResults?: RetryActionResult[] },\n );\n }\n }\n\n private async sendWithEnvelope<T>(\n send: () => Promise<ClientOperationEnvelope<T>>,\n body: { retryResults?: RetryActionResult[] },\n retryFn: () => Promise<T>,\n ): Promise<T> {\n try {\n const envelope = await send();\n if (envelope?.operations?.length) {\n this.dispatcher.dispatch(envelope.operations);\n }\n return envelope?.result as T;\n } catch (error) {\n return this.handleEnvelopeRetryError<T>(error as HttpErrorResponse, retryFn, body);\n }\n }\n\n private async handleEnvelopeRetryError<T>(\n error: HttpErrorResponse,\n retryFn: () => Promise<T>,\n body: { retryResults?: RetryActionResult[] }\n ): Promise<T> {\n if (error.status !== 449) throw error;\n const envelope = error.error as ClientOperationEnvelope<T> | undefined;\n if (!envelope?.operations?.length) throw error;\n\n // Dispatch any non-retry operations accumulated before the retry throw\n // so notify/refresh/etc. fire BEFORE the retry modal opens.\n const nonRetry = envelope.operations.filter(o => o.type !== 'retry');\n if (nonRetry.length) this.dispatcher.dispatch(nonRetry);\n\n const retryOp = envelope.operations.find(o => o.type === 'retry') as RetryOperation | undefined;\n if (!retryOp) throw error;\n\n // ⚠️ A hook that raises a retry WITHOUT first checking `Retry.Result` re-raises on every\n // resubmission, and this loop has no natural end: the modal reopens, the user answers, the\n // server asks again. Each round trip looks individually reasonable, so the tab spins until it is\n // closed and nothing is logged. The trap is documented in RetryFromEveryHookTests, and the .NET\n // client has had a bound since M3; this is the browser's.\n //\n // The bound is on ANSWERS ALREADY GIVEN, which is what `retryResults` is — so it counts the\n // conversation and not the component's lifetime, and two unrelated conversations do not add up.\n if ((body.retryResults?.length ?? 0) >= SparkService.MAX_RETRY_DEPTH) {\n throw new Error(\n `Gave up after answering ${SparkService.MAX_RETRY_DEPTH} retry prompts; the last was step ` +\n `${retryOp.step} (\"${retryOp.title}\"). A hook that raises a retry without checking ` +\n `Retry.Result first will do this — it re-raises on every resubmission.`);\n }\n\n const payload: RetryActionPayload = {\n type: 'retry-action',\n step: retryOp.step,\n title: retryOp.title,\n options: retryOp.options,\n defaultOption: retryOp.defaultOption ?? undefined,\n persistentObject: retryOp.persistentObject ?? undefined,\n message: retryOp.message ?? undefined,\n };\n const result = await this.retryActionService.show(payload);\n if (result.option === 'Cancel' && !payload.options.includes('Cancel')) throw error;\n\n body.retryResults = [...(body.retryResults || []), result];\n return retryFn();\n }\n}\n","/** Built-in SVG icons used by ng-spark library templates. */\nexport const SPARK_BUILT_IN_ICONS: Record<string, string> = {\n 'arrow-left': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"/></svg>',\n 'pencil': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-pencil\" viewBox=\"0 0 16 16\"><path d=\"M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325\"/></svg>',\n 'plus': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-plus\" viewBox=\"0 0 16 16\"><path d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4\"/></svg>',\n 'plus-lg': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-plus-lg\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2\"/></svg>',\n 'search': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-search\" viewBox=\"0 0 16 16\"><path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0\"/></svg>',\n 'trash': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-trash\" viewBox=\"0 0 16 16\"><path d=\"M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z\"/><path d=\"M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z\"/></svg>',\n 'x-lg': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-x-lg\" viewBox=\"0 0 16 16\"><path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z\"/></svg>',\n};\n","import { Injectable, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { SPARK_BUILT_IN_ICONS } from './spark-built-in-icons';\n\n/**\n * R2-M12: SVG icon registry. The registry no longer accepts a raw string —\n * apps must pre-sanitize via `DomSanitizer.bypassSecurityTrustHtml` (or\n * `parseSvg` to validate) before calling `register`. Treating the parameter\n * as `SafeHtml` puts the explicit bypass decision in the caller's hands so a\n * developer wiring server-supplied SVG (per-tenant branding fetched from a\n * JSON endpoint) doesn't accidentally trust a `<svg><script>...</script>`\n * payload.\n *\n * The built-in icons baked into the package are still bypass-trusted internally\n * because they're known-safe at build time.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkIconRegistry {\n private sanitizer = inject(DomSanitizer);\n private icons = new Map<string, SafeHtml>();\n\n constructor() {\n for (const [name, svg] of Object.entries(SPARK_BUILT_IN_ICONS)) {\n this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));\n }\n }\n\n /**\n * Registers a pre-sanitized SVG icon. Callers MUST validate or explicitly\n * trust the SVG before passing — for example:\n *\n * ```ts\n * const safe = sanitizer.bypassSecurityTrustHtml(svgFromBuildTimeConstant);\n * registry.register('app-logo', safe);\n * ```\n *\n * For server-supplied SVG, prefer a strict allow-list parser (no `<script>`,\n * no `on*` attributes, no `href=\"javascript:...\"`) before trusting.\n */\n register(name: string, svg: SafeHtml): void {\n this.icons.set(name, svg);\n }\n\n get(name: string): SafeHtml | undefined {\n return this.icons.get(name);\n }\n\n has(name: string): boolean {\n return this.icons.has(name);\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { CustomActionDefinition, PersistentObject, SparkQuery, EntityType, filterQueryActions } from '@mintplayer/ng-spark/models';\nimport { SparkService } from './spark.service';\n\n/**\n * A query's custom actions, reachable **without the grid that usually renders them**.\n *\n * ## Why this exists\n *\n * A query's actions were only ever obtainable by rendering `<spark-query-grid>`, which loads the\n * query, resolves its entity type, fetches the actions and filters them to the ones marked for a\n * query surface — all privately. A page that wanted the same buttons somewhere else (a toolbar\n * above the card, the shell's topbar) had three bad options: duplicate the four-step resolution\n * and let it drift, reach into the grid's internals, or put the button in the wrong place.\n *\n * This exposes the resolution itself. The grid keeps rendering the default placement; a host that\n * wants a different one asks here and renders its own control.\n *\n * ## What it does not do\n *\n * It does not authorize. `/spark/actions/{type}` returns only the actions this caller may see —\n * the server filters against `security.json`, and executing re-checks — so nothing here is a gate,\n * and a host must not treat \"the list came back empty\" as anything other than a display fact.\n *\n * It also does not cache. Actions depend on the caller, and a memo keyed by type id would survive\n * a sign-out.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkQueryActionsService {\n private readonly sparkService = inject(SparkService);\n\n /**\n * The custom actions of the query named by id or alias, already filtered to those that belong on\n * a query surface.\n *\n * Returns an empty list — rather than throwing — when the query resolves to no entity type,\n * because a caller rendering a toolbar wants \"no buttons\", not a broken page.\n */\n async actionsFor(queryIdOrAlias: string): Promise<CustomActionDefinition[]> {\n const context = await this.contextFor(queryIdOrAlias);\n if (!context) return [];\n\n // filterQueryActions, not a local predicate: the grid uses it too, and the `showedOn` values\n // are exactly the kind of thing a second copy gets subtly wrong — both grids once tested for\n // \"list\", a value nothing emits, and every correctly authored action rendered nowhere.\n return filterQueryActions(await this.sparkService.getCustomActions(context.entityType.id));\n }\n\n /**\n * Runs one of those actions. `selectedItemIds` are row ids, exactly as the grid posts them —\n * the server re-materializes each one through the same load path a detail page uses, so an id\n * from anywhere is treated as caller input rather than as a verified row.\n */\n async execute(\n queryIdOrAlias: string,\n actionName: string,\n options?: {\n parent?: PersistentObject;\n selectedItemIds?: string[];\n /** For a sub-query: the object whose detail page it was rendered on. See SparkService. */\n queryParent?: { id: string; type: string };\n },\n ): Promise<void> {\n const context = await this.contextFor(queryIdOrAlias);\n if (!context) {\n throw new Error(\n `Cannot execute '${actionName}': query '${queryIdOrAlias}' resolves to no entity type, so there ` +\n `is nothing to execute it against.`);\n }\n\n await this.sparkService.executeCustomAction(\n context.entityType.id, actionName, options?.parent, options?.selectedItemIds, options?.queryParent,\n context.query.id);\n }\n\n /**\n * The query and the entity type its rows are mapped against — the two-step resolution both\n * methods need, kept in one place so they cannot disagree about which type an action runs on.\n */\n private async contextFor(\n queryIdOrAlias: string,\n ): Promise<{ query: SparkQuery; entityType: EntityType } | null> {\n const [query, entityTypes] = await Promise.all([\n this.sparkService.getQuery(queryIdOrAlias),\n this.sparkService.getEntityTypes(),\n ]);\n if (!query) return null;\n\n const entityType = entityTypes.find(t => t.name === query.entityType)\n ?? entityTypes.find(t => t.id === query.entityType);\n return entityType ? { query, entityType } : null;\n }\n}\n","import { HttpFeature, HttpFeatureKind, HttpInterceptorFn, withInterceptors } from '@angular/common/http';\n\n/**\n * The header carrying the viewer's IANA timezone id, e.g. `Europe/Brussels`.\n * Must stay in lockstep with `RequestTimeZoneResolver.HeaderName` on the server.\n */\nexport const SPARK_TIMEZONE_HEADER = 'X-Spark-Timezone';\n\n/**\n * Returns the browser's IANA zone id, or `null` when the environment cannot name one.\n *\n * An id is deliberately sent rather than a numeric offset: an offset is only valid at one instant,\n * so a server asked to render \"next Tuesday at 09:00\" from an offset captured today gets it wrong\n * across a DST boundary. A zone id carries the rules, not one sample of them.\n */\nexport function resolveBrowserTimeZone(): string | null {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Attaches {@link SPARK_TIMEZONE_HEADER} to same-origin requests.\n *\n * **This does not participate in reading or writing timestamps.** A `DateTimeOffset` means an\n * instant, and the browser converts in both directions on those paths. The header exists for work\n * the server starts on its own — a scheduled export, a notification email — where there is no\n * browser in the loop to do the converting but the output still has to read as local time.\n */\nexport const sparkTimezoneInterceptor: HttpInterceptorFn = (req, next) => {\n // Never annotate a cross-origin call: the viewer's timezone is a (small) fingerprinting signal and\n // third-party hosts have no business receiving it.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(req.url)) {\n try {\n if (new URL(req.url).origin !== window.location.origin) return next(req);\n } catch {\n return next(req);\n }\n }\n\n const zone = resolveBrowserTimeZone();\n if (!zone) return next(req);\n\n return next(req.clone({ setHeaders: { [SPARK_TIMEZONE_HEADER]: zone } }));\n};\n\n/**\n * `provideHttpClient(...withSparkTimezone())` — registers {@link sparkTimezoneInterceptor}.\n *\n * Spreadable alongside `withSparkAuth()`:\n * `provideHttpClient(...withSparkAuth(), ...withSparkTimezone())`.\n */\nexport function withSparkTimezone(): HttpFeature<HttpFeatureKind>[] {\n return [withInterceptors([sparkTimezoneInterceptor])];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-services.mjs","sources":["../../services/src/retry-action.service.ts","../../services/src/spark-language.service.ts","../../services/src/spark-streaming.service.ts","../../services/src/spark.service.ts","../../services/src/spark-built-in-icons.ts","../../services/src/spark-icon-registry.ts","../../services/src/spark-query-actions.service.ts","../../services/src/spark-timezone.interceptor.ts","../../services/mintplayer-ng-spark-services.ts"],"sourcesContent":["import { Injectable, signal } from '@angular/core';\nimport { RetryActionPayload, RetryActionResult } from '@mintplayer/ng-spark/models';\n\n@Injectable({ providedIn: 'root' })\nexport class RetryActionService {\n private resolveRetry: ((result: RetryActionResult) => void) | null = null;\n\n payload = signal<RetryActionPayload | null>(null);\n\n show(payload: RetryActionPayload): Promise<RetryActionResult> {\n this.payload.set(payload);\n return new Promise(resolve => { this.resolveRetry = resolve; });\n }\n\n respond(result: RetryActionResult): void {\n this.payload.set(null);\n this.resolveRetry?.(result);\n this.resolveRetry = null;\n }\n}\n","import { Injectable, inject, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { TranslatedString, currentLanguage } from '@mintplayer/ng-spark/models';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\ninterface CultureConfiguration {\n languages: Record<string, TranslatedString>;\n defaultLanguage: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SparkLanguageService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly currentLang = signal('en');\n private readonly translationsMap = signal<Record<string, TranslatedString>>({});\n\n readonly language = this.currentLang.asReadonly();\n readonly languages = signal<Record<string, TranslatedString>>({});\n\n constructor() {\n this.loadCulture();\n this.loadTranslations();\n }\n\n private async loadCulture(): Promise<void> {\n const config = await firstValueFrom(this.http.get<CultureConfiguration>(`${this.baseUrl}/culture`));\n this.languages.set(config.languages);\n const saved = localStorage.getItem('spark-lang');\n const lang = saved ?? config.defaultLanguage;\n this.currentLang.set(lang);\n currentLanguage.set(lang);\n }\n\n private async loadTranslations(): Promise<void> {\n const t = await firstValueFrom(this.http.get<Record<string, TranslatedString>>(`${this.baseUrl}/translations`));\n this.translationsMap.set(t);\n }\n\n setLanguage(lang: string) {\n this.currentLang.set(lang);\n currentLanguage.set(lang);\n localStorage.setItem('spark-lang', lang);\n }\n\n resolve(ts: TranslatedString | undefined): string {\n if (!ts) return '';\n const lang = this.currentLang();\n return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n }\n\n t(key: string): string {\n const ts = this.translationsMap()[key];\n return this.resolve(ts) || key;\n }\n}\n","import { inject, Injectable, NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { StreamingMessage } from '@mintplayer/ng-spark/models';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\n@Injectable({ providedIn: 'root' })\nexport class SparkStreamingService {\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly ngZone = inject(NgZone);\n\n connectToStreamingQuery(queryId: string): Observable<StreamingMessage> {\n return new Observable<StreamingMessage>(subscriber => {\n let ws: WebSocket | null = null;\n let retryCount = 0;\n let retryTimeout: ReturnType<typeof setTimeout> | null = null;\n let closed = false;\n const maxRetries = 10;\n const maxDelay = 30000;\n\n const connect = () => {\n if (closed) return;\n\n const url = this.buildWebSocketUrl(queryId);\n ws = new WebSocket(url);\n\n ws.onopen = () => {\n retryCount = 0; // Reset on successful connect\n };\n\n ws.onmessage = (event) => {\n try {\n const message: StreamingMessage = JSON.parse(event.data);\n this.ngZone.run(() => subscriber.next(message));\n } catch {\n // Ignore malformed messages\n }\n };\n\n ws.onerror = () => {\n // Error will trigger onclose\n };\n\n ws.onclose = (event) => {\n if (closed) return;\n\n // Don't reconnect on normal closure\n if (event.code === 1000) {\n this.ngZone.run(() => subscriber.complete());\n return;\n }\n\n // Reconnect with exponential backoff\n if (retryCount < maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, retryCount), maxDelay);\n retryCount++;\n retryTimeout = setTimeout(() => connect(), delay);\n } else {\n this.ngZone.run(() => subscriber.error(new Error('WebSocket connection failed after maximum retries')));\n }\n };\n };\n\n connect();\n\n // Teardown: close WebSocket on unsubscribe\n return () => {\n closed = true;\n if (retryTimeout) {\n clearTimeout(retryTimeout);\n }\n if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {\n ws.close(1000, 'Client unsubscribed');\n }\n };\n });\n }\n\n private buildWebSocketUrl(queryId: string): string {\n const encodedId = encodeURIComponent(queryId);\n const path = `${this.baseUrl}/queries/${encodedId}/stream`;\n\n // If baseUrl is absolute (starts with http/https), replace protocol\n if (this.baseUrl.startsWith('http://')) {\n return path.replace(/^http:\\/\\//, 'ws://');\n }\n if (this.baseUrl.startsWith('https://')) {\n return path.replace(/^https:\\/\\//, 'wss://');\n }\n\n // Relative URL — construct from window.location\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n return `${protocol}//${window.location.host}${path}`;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { CustomActionDefinition, DistinctValuesResult, EntityPermissions, EntityType, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, ProgramUnitsConfiguration, QueryColumnFilter, QueryResult, SparkQuery, RetryActionPayload, RetryActionResult } from '@mintplayer/ng-spark/models';\nimport { ClientOperationEnvelope, RetryOperation, SparkClientOperationDispatcher } from '@mintplayer/ng-spark/client-operations';\nimport { SortColumn } from '@mintplayer/pagination';\nimport { RetryActionService } from './retry-action.service';\nimport { SPARK_CONFIG } from '@mintplayer/ng-spark';\n\n/**\n * Context for {@link SparkService.newObject}. Everything is optional: a standalone New sends an\n * empty body, and an AsDetail row on a parent that has never been saved sends no `parentId` —\n * which is the honest answer, not a gap. The server hands the hook a null parent rather than\n * trusting the client's copy of one.\n */\nexport interface NewObjectOptions {\n /** Name of the parent's AsDetail attribute the row is for; absent for a standalone New. */\n asDetailAttribute?: string;\n /** The parent's entity type — required whenever `asDetailAttribute` is set. */\n parentType?: string;\n /** The parent's id, absent while the parent is itself unsaved. */\n parentId?: string;\n /** Free-form arguments, e.g. which variant a New menu chose. */\n parameters?: Record<string, string>;\n}\n\n/** Context for {@link SparkService.deleteRow}. Every field is required — see `DeleteRow.cs`. */\nexport interface DeleteRowOptions {\n asDetailAttribute: string;\n parentType: string;\n parentId: string;\n /** The row's `__sparkRowKey`, as it arrived from the server. */\n rowKey: string;\n parameters?: Record<string, string>;\n}\n\n/**\n * The union of every body the envelope helpers send.\n *\n * Named rather than inlined per helper because the New and delete-row calls carry neither a\n * `persistentObject` nor a `triggeredBy`: an inline type that did not list their fields would have\n * rejected them at the call site, and spreading them through `any` would have hidden a typo in a\n * field name the server matches by exact spelling.\n */\ntype EnvelopeRequestBody = {\n /** The entity type the request is about — the request parameter, not the submitted object's claim. */\n objectTypeId?: string;\n /** The target object's id, for update and delete. */\n id?: string;\n persistentObject?: any;\n triggeredBy?: string;\n retryResults?: RetryActionResult[];\n} & Partial<NewObjectOptions> & Partial<Omit<DeleteRowOptions, 'asDetailAttribute' | 'parentType' | 'parentId'>>;\n\n@Injectable({ providedIn: 'root' })\nexport class SparkService {\n private readonly config = inject(SPARK_CONFIG, { optional: true });\n private readonly baseUrl = this.config?.baseUrl ?? '/spark';\n private readonly http = inject(HttpClient);\n private readonly retryActionService = inject(RetryActionService);\n\n /**\n * How many prompts one request will answer before giving up. Matches the .NET client's\n * `SparkClient.MaxRetryDepth`, and for the same reason: this is a termination bound, not a\n * capacity one. Far above any real conversation (Fleet's longest is two) and far below anything\n * that could hide a spin.\n */\n private static readonly MAX_RETRY_DEPTH = 16;\n private readonly dispatcher = inject(SparkClientOperationDispatcher);\n\n // Entity Types\n async getEntityTypes(): Promise<EntityType[]> {\n return firstValueFrom(this.http.get<EntityType[]>(`${this.baseUrl}/types`));\n }\n\n async getEntityType(id: string): Promise<EntityType> {\n return firstValueFrom(this.http.get<EntityType>(`${this.baseUrl}/types/${encodeURIComponent(id)}`));\n }\n\n async getEntityTypeByClrType(clrType: string): Promise<EntityType | undefined> {\n const types = await this.getEntityTypes();\n return types.find(t => t.clrType === clrType);\n }\n\n // Permissions\n async getPermissions(entityTypeId: string): Promise<EntityPermissions> {\n return firstValueFrom(this.http.get<EntityPermissions>(`${this.baseUrl}/permissions/${encodeURIComponent(entityTypeId)}`));\n }\n\n /** Resolved at most once; see getQueryByName. */\n #queryCatalogue?: Promise<SparkQuery[]>;\n\n // Queries\n async getQueries(): Promise<SparkQuery[]> {\n return firstValueFrom(this.http.get<SparkQuery[]>(`${this.baseUrl}/queries`));\n }\n\n async getQuery(id: string): Promise<SparkQuery> {\n return firstValueFrom(this.http.post<SparkQuery>(`${this.baseUrl}/queries/get`, { queryId: id }));\n }\n\n /**\n * Resolves a query by name, fetching the catalogue at most once per service instance.\n *\n * Without the cache this costs a full `GET /spark/queries` per call, and a form with several\n * reference attributes resolves them in parallel — so opening one page fetched the whole query\n * catalogue N times over. The catalogue is per-caller and effectively static for a page's\n * lifetime; the service is scoped to the app, so a permission change lands on the next load.\n */\n async getQueryByName(name: string): Promise<SparkQuery | undefined> {\n this.#queryCatalogue ??= this.getQueries();\n const queries = await this.#queryCatalogue;\n return queries.find(q => q.name === name);\n }\n\n async executeQuery(queryId: string, options?: ExecuteQueryOptions): Promise<QueryResult> {\n // A POST with a typed body, not a GET with a query string. `sortColumns` used to be encoded as\n // `prop:asc,other:desc` — an encoding invented because a query string has no arrays. Column\n // filtering (several columns, several selected values each) would need a second such encoding,\n // which is the reason the reads moved to POST at all.\n return this.sendRead<QueryResult>(`${this.baseUrl}/queries/execute`, {\n queryId,\n sortColumns: options?.sortColumns?.length\n ? options.sortColumns.map(c => ({ property: c.property, direction: c.direction === 'descending' ? 'desc' : 'asc' }))\n : undefined,\n parentId: options?.parentId,\n parentType: options?.parentType,\n skip: options?.skip,\n take: options?.take,\n search: options?.search,\n // Per-column value filters (#431). The shape that could not be expressed as a query string,\n // and the reason these reads are a POST at all.\n columns: options?.columns?.length ? options.columns : undefined,\n });\n }\n\n /**\n * The distinct values behind one column's filter panel (#431).\n *\n * `columns` carries the OTHER columns' current filters, so the values returned are the ones still\n * reachable — offering a value that empties the grid the moment it is picked is worse than\n * omitting it.\n *\n * An empty result means either \"nothing matches\" or \"you may not enumerate this column\". The two\n * are deliberately indistinguishable, so there is nothing for a caller to branch on.\n */\n async getDistinctValues(queryId: string, column: string, options?: {\n search?: string;\n columns?: QueryColumnFilter[];\n parentId?: string;\n parentType?: string;\n }): Promise<DistinctValuesResult> {\n return this.sendRead<DistinctValuesResult>(`${this.baseUrl}/queries/distinct-values`, {\n queryId,\n column,\n search: options?.search || undefined,\n columns: options?.columns?.length ? options.columns : undefined,\n parentId: options?.parentId,\n parentType: options?.parentType,\n });\n }\n\n /**\n * Runs a query identified by name.\n *\n * It used to forward only the parent, dropping skip/take/search — so every reference picker and\n * AsDetail reference column silently saw the server's default of 50 candidates, with no indication\n * that the list was cut. A picker that omits the option you are looking for, and says nothing, is\n * worse than one that fails.\n */\n async executeQueryByName(queryName: string, options?: ExecuteQueryOptions): Promise<QueryResult> {\n const query = await this.getQueryByName(queryName);\n if (!query) return { columns: [], items: [], totalItems: 0, skip: 0, take: options?.take ?? 50 };\n return this.executeQuery(query.id, options);\n }\n\n // Program Units\n async getProgramUnits(): Promise<ProgramUnitsConfiguration> {\n return firstValueFrom(this.http.get<ProgramUnitsConfiguration>(`${this.baseUrl}/program-units`));\n }\n\n // Persistent Objects\n async get(type: string, id: string): Promise<PersistentObject> {\n return this.sendRead<PersistentObject>(`${this.baseUrl}/po/load`, { objectTypeId: type, id });\n }\n\n async create(type: string, data: Partial<PersistentObject>): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/create`,\n { objectTypeId: type, persistentObject: data }\n );\n }\n\n async update(type: string, id: string, data: Partial<PersistentObject>): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/update`,\n { objectTypeId: type, id, persistentObject: data }\n );\n }\n\n /**\n * Asks the server to reshape an in-progress object after `triggeredBy`'s value changed.\n *\n * Writes nothing, but goes through the envelope like every other mutating call: a refresh may\n * legitimately emit notifications, and may open the retry-action prompt.\n *\n * `triggeredBy` is the attribute's name. For a trigger inside an AsDetail row it is the same\n * path form the inline validation errors use — `Jobs[2].ProfessionId`.\n */\n async refresh(type: string, data: Partial<PersistentObject>, triggeredBy: string): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/refresh`,\n { objectTypeId: type, persistentObject: data, triggeredBy }\n );\n }\n\n /**\n * Asks the server to construct a new object of `type`, so `OnNewAsync` can default it.\n *\n * Writes nothing — the object comes back unsaved, and for an AsDetail row the parent still owns\n * the save. Only called for a row type whose `serverSideRowLifecycle` is on; every other type\n * keeps building its blank row locally, which is why switching the flag off costs no request.\n *\n * ⚠️ The row comes back **keyed**: the server constructs the CLR instance, so the row-key field\n * initializer runs and `po.id` carries the key. Flattening it with `nestedPoToDict` therefore\n * populates `__sparkRowKey`, and the row is matchable on save from the moment it is added — the\n * whole reason this round-trip is worth a request.\n */\n async newObject(type: string, options?: NewObjectOptions): Promise<PersistentObject> {\n return this.postWithEnvelope<PersistentObject>(\n `${this.baseUrl}/po/new`,\n { objectTypeId: type, ...(options ?? {}) }\n );\n }\n\n /**\n * Asks whether a stored row may leave its parent's AsDetail collection.\n *\n * Deletes nothing: a resolved promise means the caller may splice the row out of the collection\n * it is editing, and the removal is persisted with the parent. A hook that refuses rejects with a\n * 400 whose `error.error.result.errors` carries the readable reason, exactly like a save.\n *\n * A POST — as everything is now. It used to be worth explaining: the framework's real delete route\n * was a catch-all that would have swallowed a sibling DELETE. The catch-all is gone with the rest\n * of the route variables.\n */\n async deleteRow(type: string, options: DeleteRowOptions): Promise<void> {\n await this.postWithEnvelope<{ removed: boolean }>(\n `${this.baseUrl}/po/delete-row`,\n { objectTypeId: type, ...options }\n );\n }\n\n async delete(type: string, id: string): Promise<void> {\n return this.postWithEnvelope<void>(\n `${this.baseUrl}/po/delete`,\n { objectTypeId: type, id }\n );\n }\n\n // Custom Actions\n async getCustomActions(objectTypeId: string): Promise<CustomActionDefinition[]> {\n return firstValueFrom(this.http.post<CustomActionDefinition[]>(`${this.baseUrl}/actions/list`, { objectTypeId }));\n }\n\n /**\n * @param parent The object of THIS type the action is operating on — the detail-page invocation.\n * @param selectedItemIds Row ids from a query. Ids, never row objects: a row is a projection.\n * @param queryParent When invoked from a sub-query, the object whose detail page it was rendered\n * on — a DIFFERENT type (the cars listed on a company's page are Cars, the page is a Company).\n * Sent as id + type, exactly as the query endpoint names it, and resolved server-side under its\n * own type with its own Read gate.\n * @param queryId The query the selection came from, so the server can re-run it narrowed to those\n * ids and hand the action the rows the grid actually had -- index-computed columns included.\n */\n async executeCustomAction(\n objectTypeId: string,\n actionName: string,\n parent?: PersistentObject,\n selectedItemIds?: string[],\n queryParent?: { id: string; type: string },\n queryId?: string,\n ): Promise<void> {\n const body: {\n objectTypeId: string; actionName: string;\n parent?: PersistentObject; selectedItemIds?: string[];\n parentId?: string; parentType?: string; queryId?: string;\n retryResults?: RetryActionResult[];\n } = { objectTypeId, actionName, parent, selectedItemIds, parentId: queryParent?.id, parentType: queryParent?.type, queryId };\n return this.postWithEnvelope<void>(\n `${this.baseUrl}/actions/execute`,\n body as any\n );\n }\n\n // LookupReferences\n async getLookupReferences(): Promise<LookupReferenceListItem[]> {\n return firstValueFrom(this.http.get<LookupReferenceListItem[]>(`${this.baseUrl}/lookupref`));\n }\n\n async getLookupReference(name: string): Promise<LookupReference> {\n return firstValueFrom(this.http.get<LookupReference>(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`));\n }\n\n async addLookupReferenceValue(name: string, value: LookupReferenceValue): Promise<LookupReferenceValue> {\n return firstValueFrom(this.http.post<LookupReferenceValue>(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`, value));\n }\n\n async updateLookupReferenceValue(name: string, key: string, value: LookupReferenceValue): Promise<LookupReferenceValue> {\n return firstValueFrom(this.http.put<LookupReferenceValue>(\n `${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`,\n value\n ));\n }\n\n async deleteLookupReferenceValue(name: string, key: string): Promise<void> {\n return firstValueFrom(this.http.delete<void>(\n `${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`\n ));\n }\n\n // Envelope-aware HTTP helpers.\n // All mutation endpoints (Create / Update / Delete / Execute custom action) emit the\n // ClientOperationEnvelope { result, operations } shape. These helpers unwrap the envelope,\n // dispatch any non-retry operations, and translate 449 retry-operations into the existing\n // RetryActionService modal flow.\n\n private postWithEnvelope<T>(url: string, body: EnvelopeRequestBody): Promise<T> {\n return this.sendWithEnvelope<T>(\n () => firstValueFrom(this.http.post<ClientOperationEnvelope<T>>(url, body)),\n body,\n () => this.postWithEnvelope<T>(url, body),\n );\n }\n\n /**\n * A read: posts the request, and returns the response **as-is** rather than unwrapping an\n * envelope.\n *\n * The read endpoints moved from GET to POST so their hooks could prompt, but their success\n * responses are still bare — a `PersistentObject`, a `QueryResult`. Only the 449 is enveloped,\n * because a retry has nowhere else to live. So this shares the retry loop with the mutating calls\n * and nothing else.\n */\n private async sendRead<T>(url: string, body: Record<string, unknown>): Promise<T> {\n try {\n return await firstValueFrom(this.http.post<T>(url, body));\n } catch (error) {\n return this.handleEnvelopeRetryError<T>(\n error as HttpErrorResponse,\n () => this.sendRead<T>(url, body),\n body as { retryResults?: RetryActionResult[] },\n );\n }\n }\n\n private async sendWithEnvelope<T>(\n send: () => Promise<ClientOperationEnvelope<T>>,\n body: { retryResults?: RetryActionResult[] },\n retryFn: () => Promise<T>,\n ): Promise<T> {\n try {\n const envelope = await send();\n if (envelope?.operations?.length) {\n this.dispatcher.dispatch(envelope.operations);\n }\n return envelope?.result as T;\n } catch (error) {\n return this.handleEnvelopeRetryError<T>(error as HttpErrorResponse, retryFn, body);\n }\n }\n\n private async handleEnvelopeRetryError<T>(\n error: HttpErrorResponse,\n retryFn: () => Promise<T>,\n body: { retryResults?: RetryActionResult[] }\n ): Promise<T> {\n if (error.status !== 449) throw error;\n const envelope = error.error as ClientOperationEnvelope<T> | undefined;\n if (!envelope?.operations?.length) throw error;\n\n // Dispatch any non-retry operations accumulated before the retry throw\n // so notify/refresh/etc. fire BEFORE the retry modal opens.\n const nonRetry = envelope.operations.filter(o => o.type !== 'retry');\n if (nonRetry.length) this.dispatcher.dispatch(nonRetry);\n\n const retryOp = envelope.operations.find(o => o.type === 'retry') as RetryOperation | undefined;\n if (!retryOp) throw error;\n\n // ⚠️ A hook that raises a retry WITHOUT first checking `Retry.Result` re-raises on every\n // resubmission, and this loop has no natural end: the modal reopens, the user answers, the\n // server asks again. Each round trip looks individually reasonable, so the tab spins until it is\n // closed and nothing is logged. The trap is documented in RetryFromEveryHookTests, and the .NET\n // client has had a bound since M3; this is the browser's.\n //\n // The bound is on ANSWERS ALREADY GIVEN, which is what `retryResults` is — so it counts the\n // conversation and not the component's lifetime, and two unrelated conversations do not add up.\n if ((body.retryResults?.length ?? 0) >= SparkService.MAX_RETRY_DEPTH) {\n throw new Error(\n `Gave up after answering ${SparkService.MAX_RETRY_DEPTH} retry prompts; the last was step ` +\n `${retryOp.step} (\"${retryOp.title}\"). A hook that raises a retry without checking ` +\n `Retry.Result first will do this — it re-raises on every resubmission.`);\n }\n\n const payload: RetryActionPayload = {\n type: 'retry-action',\n step: retryOp.step,\n title: retryOp.title,\n options: retryOp.options,\n defaultOption: retryOp.defaultOption ?? undefined,\n persistentObject: retryOp.persistentObject ?? undefined,\n message: retryOp.message ?? undefined,\n };\n const result = await this.retryActionService.show(payload);\n if (result.option === 'Cancel' && !payload.options.includes('Cancel')) throw error;\n\n body.retryResults = [...(body.retryResults || []), result];\n return retryFn();\n }\n}\n\n/**\n * The options every query execution takes.\n *\n * Extracted because `executeQueryByName` forwards to `executeQuery`, and while both spelled their\n * options as separate inline literals a field added to one silently failed to forward from the\n * other — which is how skip/take/search were once dropped for every reference picker.\n */\nexport interface ExecuteQueryOptions {\n sortColumns?: SortColumn[];\n parentId?: string;\n parentType?: string;\n skip?: number;\n take?: number;\n search?: string;\n /** Per-column value filters (#431). Columns AND together; values within a column OR. */\n columns?: QueryColumnFilter[];\n}\n","/** Built-in SVG icons used by ng-spark library templates. */\nexport const SPARK_BUILT_IN_ICONS: Record<string, string> = {\n 'arrow-left': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"/></svg>',\n 'pencil': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-pencil\" viewBox=\"0 0 16 16\"><path d=\"M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325\"/></svg>',\n 'plus': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-plus\" viewBox=\"0 0 16 16\"><path d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4\"/></svg>',\n 'plus-lg': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-plus-lg\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2\"/></svg>',\n 'search': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-search\" viewBox=\"0 0 16 16\"><path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0\"/></svg>',\n 'trash': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-trash\" viewBox=\"0 0 16 16\"><path d=\"M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z\"/><path d=\"M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z\"/></svg>',\n 'x-lg': '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-x-lg\" viewBox=\"0 0 16 16\"><path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z\"/></svg>',\n};\n","import { Injectable, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { SPARK_BUILT_IN_ICONS } from './spark-built-in-icons';\n\n/**\n * R2-M12: SVG icon registry. The registry no longer accepts a raw string —\n * apps must pre-sanitize via `DomSanitizer.bypassSecurityTrustHtml` (or\n * `parseSvg` to validate) before calling `register`. Treating the parameter\n * as `SafeHtml` puts the explicit bypass decision in the caller's hands so a\n * developer wiring server-supplied SVG (per-tenant branding fetched from a\n * JSON endpoint) doesn't accidentally trust a `<svg><script>...</script>`\n * payload.\n *\n * The built-in icons baked into the package are still bypass-trusted internally\n * because they're known-safe at build time.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkIconRegistry {\n private sanitizer = inject(DomSanitizer);\n private icons = new Map<string, SafeHtml>();\n\n constructor() {\n for (const [name, svg] of Object.entries(SPARK_BUILT_IN_ICONS)) {\n this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));\n }\n }\n\n /**\n * Registers a pre-sanitized SVG icon. Callers MUST validate or explicitly\n * trust the SVG before passing — for example:\n *\n * ```ts\n * const safe = sanitizer.bypassSecurityTrustHtml(svgFromBuildTimeConstant);\n * registry.register('app-logo', safe);\n * ```\n *\n * For server-supplied SVG, prefer a strict allow-list parser (no `<script>`,\n * no `on*` attributes, no `href=\"javascript:...\"`) before trusting.\n */\n register(name: string, svg: SafeHtml): void {\n this.icons.set(name, svg);\n }\n\n get(name: string): SafeHtml | undefined {\n return this.icons.get(name);\n }\n\n has(name: string): boolean {\n return this.icons.has(name);\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { CustomActionDefinition, PersistentObject, SparkQuery, EntityType, filterQueryActions } from '@mintplayer/ng-spark/models';\nimport { SparkService } from './spark.service';\n\n/**\n * A query's custom actions, reachable **without the grid that usually renders them**.\n *\n * ## Why this exists\n *\n * A query's actions were only ever obtainable by rendering `<spark-query-grid>`, which loads the\n * query, resolves its entity type, fetches the actions and filters them to the ones marked for a\n * query surface — all privately. A page that wanted the same buttons somewhere else (a toolbar\n * above the card, the shell's topbar) had three bad options: duplicate the four-step resolution\n * and let it drift, reach into the grid's internals, or put the button in the wrong place.\n *\n * This exposes the resolution itself. The grid keeps rendering the default placement; a host that\n * wants a different one asks here and renders its own control.\n *\n * ## What it does not do\n *\n * It does not authorize. `/spark/actions/{type}` returns only the actions this caller may see —\n * the server filters against `security.json`, and executing re-checks — so nothing here is a gate,\n * and a host must not treat \"the list came back empty\" as anything other than a display fact.\n *\n * It also does not cache. Actions depend on the caller, and a memo keyed by type id would survive\n * a sign-out.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkQueryActionsService {\n private readonly sparkService = inject(SparkService);\n\n /**\n * The custom actions of the query named by id or alias, already filtered to those that belong on\n * a query surface.\n *\n * Returns an empty list — rather than throwing — when the query resolves to no entity type,\n * because a caller rendering a toolbar wants \"no buttons\", not a broken page.\n */\n async actionsFor(queryIdOrAlias: string): Promise<CustomActionDefinition[]> {\n const context = await this.contextFor(queryIdOrAlias);\n if (!context) return [];\n\n // filterQueryActions, not a local predicate: the grid uses it too, and the `showedOn` values\n // are exactly the kind of thing a second copy gets subtly wrong — both grids once tested for\n // \"list\", a value nothing emits, and every correctly authored action rendered nowhere.\n return filterQueryActions(await this.sparkService.getCustomActions(context.entityType.id));\n }\n\n /**\n * Runs one of those actions. `selectedItemIds` are row ids, exactly as the grid posts them —\n * the server re-materializes each one through the same load path a detail page uses, so an id\n * from anywhere is treated as caller input rather than as a verified row.\n */\n async execute(\n queryIdOrAlias: string,\n actionName: string,\n options?: {\n parent?: PersistentObject;\n selectedItemIds?: string[];\n /** For a sub-query: the object whose detail page it was rendered on. See SparkService. */\n queryParent?: { id: string; type: string };\n },\n ): Promise<void> {\n const context = await this.contextFor(queryIdOrAlias);\n if (!context) {\n throw new Error(\n `Cannot execute '${actionName}': query '${queryIdOrAlias}' resolves to no entity type, so there ` +\n `is nothing to execute it against.`);\n }\n\n await this.sparkService.executeCustomAction(\n context.entityType.id, actionName, options?.parent, options?.selectedItemIds, options?.queryParent,\n context.query.id);\n }\n\n /**\n * The query and the entity type its rows are mapped against — the two-step resolution both\n * methods need, kept in one place so they cannot disagree about which type an action runs on.\n */\n private async contextFor(\n queryIdOrAlias: string,\n ): Promise<{ query: SparkQuery; entityType: EntityType } | null> {\n const [query, entityTypes] = await Promise.all([\n this.sparkService.getQuery(queryIdOrAlias),\n this.sparkService.getEntityTypes(),\n ]);\n if (!query) return null;\n\n const entityType = entityTypes.find(t => t.name === query.entityType)\n ?? entityTypes.find(t => t.id === query.entityType);\n return entityType ? { query, entityType } : null;\n }\n}\n","import { HttpFeature, HttpFeatureKind, HttpInterceptorFn, withInterceptors } from '@angular/common/http';\n\n/**\n * The header carrying the viewer's IANA timezone id, e.g. `Europe/Brussels`.\n * Must stay in lockstep with `RequestTimeZoneResolver.HeaderName` on the server.\n */\nexport const SPARK_TIMEZONE_HEADER = 'X-Spark-Timezone';\n\n/**\n * Returns the browser's IANA zone id, or `null` when the environment cannot name one.\n *\n * An id is deliberately sent rather than a numeric offset: an offset is only valid at one instant,\n * so a server asked to render \"next Tuesday at 09:00\" from an offset captured today gets it wrong\n * across a DST boundary. A zone id carries the rules, not one sample of them.\n */\nexport function resolveBrowserTimeZone(): string | null {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Attaches {@link SPARK_TIMEZONE_HEADER} to same-origin requests.\n *\n * **This does not participate in reading or writing timestamps.** A `DateTimeOffset` means an\n * instant, and the browser converts in both directions on those paths. The header exists for work\n * the server starts on its own — a scheduled export, a notification email — where there is no\n * browser in the loop to do the converting but the output still has to read as local time.\n */\nexport const sparkTimezoneInterceptor: HttpInterceptorFn = (req, next) => {\n // Never annotate a cross-origin call: the viewer's timezone is a (small) fingerprinting signal and\n // third-party hosts have no business receiving it.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(req.url)) {\n try {\n if (new URL(req.url).origin !== window.location.origin) return next(req);\n } catch {\n return next(req);\n }\n }\n\n const zone = resolveBrowserTimeZone();\n if (!zone) return next(req);\n\n return next(req.clone({ setHeaders: { [SPARK_TIMEZONE_HEADER]: zone } }));\n};\n\n/**\n * `provideHttpClient(...withSparkTimezone())` — registers {@link sparkTimezoneInterceptor}.\n *\n * Spreadable alongside `withSparkAuth()`:\n * `provideHttpClient(...withSparkAuth(), ...withSparkTimezone())`.\n */\nexport function withSparkTimezone(): HttpFeature<HttpFeatureKind>[] {\n return [withInterceptors([sparkTimezoneInterceptor])];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mintplayer/ng-spark",
3
3
  "private": false,
4
- "version": "22.19.0",
4
+ "version": "22.21.0",
5
5
  "description": "Angular component library for MintPlayer.Spark CRUD applications",
6
6
  "repository": {
7
7
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "@angular/common": "^22.0.0",
15
15
  "@angular/router": "^22.0.0",
16
16
  "@angular/forms": "^22.0.0",
17
- "@mintplayer/ng-bootstrap": "^22.18.0",
17
+ "@mintplayer/ng-bootstrap": "^22.19.0",
18
18
  "@mintplayer/pagination": "*",
19
19
  "rxjs": "~7.8.0"
20
20
  },
@@ -37,6 +37,10 @@
37
37
  "types": "./types/mintplayer-ng-spark-client-operations.d.ts",
38
38
  "default": "./fesm2022/mintplayer-ng-spark-client-operations.mjs"
39
39
  },
40
+ "./column-filter": {
41
+ "types": "./types/mintplayer-ng-spark-column-filter.d.ts",
42
+ "default": "./fesm2022/mintplayer-ng-spark-column-filter.mjs"
43
+ },
40
44
  "./grid": {
41
45
  "types": "./types/mintplayer-ng-spark-grid.d.ts",
42
46
  "default": "./fesm2022/mintplayer-ng-spark-grid.mjs"
@@ -0,0 +1,77 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { DistinctValues, FilterContext, DistinctValue } from '@mintplayer/ng-bootstrap/datatable';
3
+ import { QueryColumn } from '@mintplayer/ng-spark/models';
4
+
5
+ /**
6
+ * The contents of one column's filter panel (#431).
7
+ *
8
+ * Spark supplies its own panel on **every** filterable column rather than using the datatable's
9
+ * built-in one. Two reasons, and the first forces the second:
10
+ *
11
+ * - A `canListDistincts: false` column has no built-in mode that fits. `FilterMode` is
12
+ * `'values' | 'comparison'` and there is no `contains` operator, so a free-text filter cannot be
13
+ * expressed — and resolving `null` from the distinct source is not an escape either, because that
14
+ * means "fall back to the local pass", which is disabled for a `[fetch]`-bound grid. Such a column
15
+ * would render the built-in panel showing its "no values" string: a dead panel.
16
+ * - Since some columns must nest regardless, mixing would put two visually different panels in one
17
+ * grid.
18
+ *
19
+ * What the datatable still owns, and this component must not reimplement: the async distinct source
20
+ * and its debounce, request cancellation, the matching/remaining bucketing, the change event, the
21
+ * document-root overlay, the focus trap and Escape. This is markup over {@link FilterContext}.
22
+ *
23
+ * ⚠️ **Styling is ours.** The panel is portalled out of the grid's subtree, so Bootstrap's
24
+ * `.form-control` and `.btn` do not reach it — they are only styled inside `bs-*` components. Every
25
+ * visual here is explicit.
26
+ */
27
+ declare class SparkColumnFilterPanelComponent {
28
+ readonly column: _angular_core.InputSignal<QueryColumn>;
29
+ /** The datatable's bucketed list, or null while nothing has loaded. */
30
+ readonly values: _angular_core.InputSignal<DistinctValues | null>;
31
+ readonly ctx: _angular_core.InputSignal<FilterContext>;
32
+ /**
33
+ * Optional relabelling for a column with a custom cell renderer.
34
+ *
35
+ * A renderer is a component that paints arbitrary DOM, and a distinct value has no row, so it
36
+ * cannot be instantiated to produce text. The label therefore defaults to the server-computed cell
37
+ * text — a status rendered as a coloured pill lists `Active`, a rating rendered as stars lists `3`.
38
+ * This hook is the escape for the case where that is too raw: a pure function, no DOM, no row.
39
+ */
40
+ readonly filterLabel: _angular_core.InputSignal<((value: unknown) => string) | null>;
41
+ /** Whether this column offers a value list at all, or only free text. */
42
+ protected readonly listsDistincts: _angular_core.Signal<boolean>;
43
+ /** Selected values, by value. Identity is the value, never the label — two rows may share text. */
44
+ private readonly selected;
45
+ protected readonly inverse: _angular_core.WritableSignal<boolean>;
46
+ protected readonly freeText: _angular_core.WritableSignal<string>;
47
+ protected readonly hasSelection: _angular_core.Signal<boolean>;
48
+ protected readonly matching: _angular_core.Signal<DistinctValue[]>;
49
+ protected readonly remaining: _angular_core.Signal<DistinctValue[]>;
50
+ protected readonly hasMore: _angular_core.Signal<boolean>;
51
+ /**
52
+ * Whether to show the empty state.
53
+ *
54
+ * Covers three states on purpose: nothing loaded yet, nothing matched, and the distinct request
55
+ * failed. The datatable swallows a rejected promise into a null list with no error, so the third
56
+ * is not distinguishable here - and the server already refuses to distinguish "no values" from
57
+ * "you may not list them".
58
+ */
59
+ protected readonly isEmpty: _angular_core.Signal<boolean>;
60
+ protected labelFor(value: DistinctValue): string;
61
+ protected isChecked(value: DistinctValue): boolean;
62
+ protected toggle(value: DistinctValue, checked: boolean): void;
63
+ protected toggleInverse(): void;
64
+ protected onSearch(term: string): void;
65
+ /**
66
+ * Applies the free-text filter of a column whose values may not be enumerated.
67
+ *
68
+ * It goes through the same `apply` as a checkbox list, carrying one synthetic entry, so the grid
69
+ * has exactly one event shape to translate and no second wire form to maintain.
70
+ */
71
+ protected applyFreeText(): void;
72
+ protected clear(): void;
73
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkColumnFilterPanelComponent, never>;
74
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkColumnFilterPanelComponent, "spark-column-filter-panel", never, { "column": { "alias": "column"; "required": true; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; "filterLabel": { "alias": "filterLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
75
+ }
76
+
77
+ export { SparkColumnFilterPanelComponent };
@@ -1,4 +1,4 @@
1
- import { DatatableSettings, BsDatatableFetch } from '@mintplayer/ng-bootstrap/datatable';
1
+ import { DatatableSettings, BsDatatableFetch, DatatableDistincts, FilterChangeDetail } from '@mintplayer/ng-bootstrap/datatable';
2
2
  import * as _mintplayer_ng_spark_models from '@mintplayer/ng-spark/models';
3
3
  import { SparkQuery, EntityType, EntityAttributeDefinition, SparkCellColumn, QueryResultItem, QueryColumn, LookupReference, CustomActionDefinition, PersistentObject } from '@mintplayer/ng-spark/models';
4
4
  import * as _angular_core from '@angular/core';
@@ -46,6 +46,8 @@ declare class SparkGridRenderers {
46
46
  private readonly sparkService;
47
47
  /** The registered column component for a column, or null to fall back to the default cell. */
48
48
  columnComponentFor(column: SparkCellColumn): Type<any> | null;
49
+ /** The registered filter-label function for a column, or null to use the server-computed text. */
50
+ filterLabelFor(column: SparkCellColumn): ((value: unknown) => string) | null;
49
51
  /**
50
52
  * Inputs for a query-grid cell renderer, filtered to what the component actually declares —
51
53
  * `NgComponentOutlet` throws on an input the target does not have, which is what lets every
@@ -371,6 +373,74 @@ declare class SparkQueryGridComponent {
371
373
  */
372
374
  reload(): void;
373
375
  private onSearchChanged;
376
+ /**
377
+ * Replaces the fetched columns only when they actually differ.
378
+ *
379
+ * Columns ship **once per result**, so every page fetch returns an identical set. Assigning it
380
+ * unconditionally wrote a new array of new objects into the signal each time, re-rendering every
381
+ * header and re-evaluating every `*bsDatatableColumn` input for no change in content.
382
+ *
383
+ * Compared by serialized value rather than by reference: the objects are freshly deserialized from
384
+ * JSON on every response, so reference equality is always false and would defeat the check. A
385
+ * dozen small objects per fetch is nothing beside the request that produced them.
386
+ *
387
+ * ⚠️ This was written believing it fixed a panel-teardown bug (#431 F7). Measured in a browser: it
388
+ * does not. The columns were already identical on every fetch, so the guard hits every time and the
389
+ * teardown had another cause entirely. Kept because avoiding a pointless re-render of every header
390
+ * on every page is worth having on its own — but it is an optimization, not a fix, and the F7
391
+ * question remains open.
392
+ */
393
+ private setFetchedColumns;
394
+ /**
395
+ * The per-column filters currently applied (#431).
396
+ *
397
+ * Read inside `makeFetch`'s closure, like `search`, so a change refetches by producing a new fetch
398
+ * identity rather than by being captured.
399
+ */
400
+ private readonly filters;
401
+ /**
402
+ * The value source behind every column's panel.
403
+ *
404
+ * ⚠️ **Referentially stable, and that is the whole point.** It reads `filters()` when the datatable
405
+ * *calls* it, not when it is built — so it always sees the current filters while never changing
406
+ * identity.
407
+ *
408
+ * This was a `computed` first, on the reasoning that it had to close over the current filters
409
+ * because `DistinctsRequest` carries only `{ column, search, signal }`. That was wrong, and the way
410
+ * it was wrong is worth keeping: a new identity reassigns `[distincts]` on the element, and the
411
+ * component aborts the in-flight request and bumps a generation counter — so a response already on
412
+ * its way is discarded as stale and the list renders empty over a perfectly good answer.
413
+ *
414
+ * Reading the signal at call time removes the race entirely and is simpler besides.
415
+ *
416
+ * The asked-for column's own filter is excluded: a panel must offer the values you could still
417
+ * pick, not only the ones you already picked.
418
+ */
419
+ /**
420
+ * The last complete distinct list seen per column, for translating a `< none >` selection.
421
+ *
422
+ * ⚠️ Only stored when the server reported the list was NOT truncated (`hasMore === false`). The
423
+ * translation below is the complement of what is listed, so a partial list would exclude the wrong
424
+ * set — and silently, which is the failure mode this whole feature exists to remove.
425
+ */
426
+ private readonly completeDistincts;
427
+ protected readonly distinctsFn: DatatableDistincts;
428
+ /**
429
+ * Translates one filter change into the wire shape and refetches.
430
+ *
431
+ * The event always arrives as the `'values'` shape because the panel only ever calls
432
+ * `ctx.apply(...)`; comparison mode is never declared. `inverse` is a flag on the event and two
433
+ * arrays on the wire — the translation is this method's whole job.
434
+ */
435
+ protected onFilterChange(detail: FilterChangeDetail): void;
436
+ /**
437
+ * Back to page 1 and a fresh fetch identity, exactly as a new search term does.
438
+ *
439
+ * Both halves matter: the old page number means nothing against a different result set, and the
440
+ * datatable dedupes reloads by `(page, perPage, sort)` — none of which a filter changes — so
441
+ * without a new fetch identity the request is silently never made.
442
+ */
443
+ private onFilterChanged;
374
444
  onCustomAction(action: CustomActionDefinition): Promise<void>;
375
445
  private reportError;
376
446
  /**
@@ -403,6 +473,8 @@ declare class SparkQueryGridComponent {
403
473
  */
404
474
  rendererValueFor(item: QueryResultItem, column: QueryColumn): unknown;
405
475
  getColumnRendererComponent(column: QueryColumn): Type<any> | null;
476
+ /** The renderer's optional filter-label override for this column, or null for the server text. */
477
+ filterLabelFor(column: QueryColumn): ((value: unknown) => string) | null;
406
478
  getColumnRendererInputs(component: Type<any>, item: QueryResultItem, column: QueryColumn): Record<string, any>;
407
479
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkQueryGridComponent, never>;
408
480
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryGridComponent, "spark-query-grid", never, { "queryId": { "alias": "queryId"; "required": true; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "reloadToken": { "alias": "reloadToken"; "required": false; "isSignal": true; }; "settings": { "alias": "settings"; "required": false; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; "rowRoute": { "alias": "rowRoute"; "required": false; "isSignal": true; }; }, { "settings": "settingsChange"; "selection": "selectionChange"; "error": "error"; "rowClicked": "rowClicked"; "customActionExecuted": "customActionExecuted"; }, never, never, true, never>;