@mintplayer/ng-spark 22.32.0 → 22.33.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.
Files changed (75) hide show
  1. package/README.md +101 -1
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
  4. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
  7. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
  8. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
  9. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
  10. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
  11. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
  12. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
  13. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
  14. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
  15. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
  16. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
  17. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
  18. package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
  19. package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
  20. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  21. package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
  22. package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
  23. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
  24. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
  25. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
  26. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
  27. package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
  28. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  29. package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
  30. package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
  31. package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
  32. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
  33. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
  34. package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
  35. package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
  36. package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
  37. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
  39. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +87 -28
  40. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
  42. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  43. package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
  44. package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
  45. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
  46. package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
  47. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  48. package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
  49. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
  50. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
  51. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
  52. package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
  53. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  54. package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
  55. package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
  56. package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
  57. package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
  58. package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
  59. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  60. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
  61. package/package.json +25 -1
  62. package/types/mintplayer-ng-spark-grid.d.ts +77 -26
  63. package/types/mintplayer-ng-spark-models.d.ts +150 -17
  64. package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
  65. package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
  66. package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
  67. package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
  68. package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
  69. package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
  70. package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
  71. package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
  72. package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
  73. package/types/mintplayer-ng-spark-services.d.ts +146 -6
  74. package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
  75. package/types/mintplayer-ng-spark-share.d.ts +26 -0
@@ -3,13 +3,14 @@ import * as i0 from "@angular/core";
3
3
  import { ChangeDetectionStrategy, Component, DestroyRef, Directive, Injectable, Injector, TemplateRef, computed, contentChildren, effect, inject, input, linkedSignal, model, output, runInInjectionContext, signal, untracked, viewChild } from "@angular/core";
4
4
  import { ShowedOn, confirmationText, defaultQueryActions, filterQueryActions, hasShowedOnFlag, parseSelectionRule, selectionModeFor, valueFor } from "@mintplayer/ng-spark/models";
5
5
  import { SPARK_ATTRIBUTE_RENDERERS, cellValue, withDeclaredInputs } from "@mintplayer/ng-spark/renderers";
6
- import { SPARK_RETURN_URL_STATE_KEY, SparkLanguageService, SparkService, sparkDatatableLabels } from "@mintplayer/ng-spark/services";
6
+ import { SPARK_RETURN_URL_STATE_KEY, SparkLanguageService, SparkLiveService, SparkService, canonicalJson, describeSparkError, sparkDatatableLabels } from "@mintplayer/ng-spark/services";
7
7
  import * as i1$1 from "@angular/common";
8
8
  import { CommonModule, NgComponentOutlet } from "@angular/common";
9
+ import { HttpErrorResponse } from "@angular/common/http";
9
10
  import { toSignal } from "@angular/core/rxjs-interop";
10
11
  import * as i2 from "@angular/router";
11
12
  import { NavigationEnd, Router, RouterModule } from "@angular/router";
12
- import { filter, map } from "rxjs";
13
+ import { BehaviorSubject, filter, map } from "rxjs";
13
14
  import { Color } from "@mintplayer/ng-bootstrap";
14
15
  import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
15
16
  import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
@@ -86,7 +87,7 @@ var SparkGridRenderers = class SparkGridRenderers {
86
87
  }
87
88
  static ɵfac = i0.ɵɵngDeclareFactory({
88
89
  minVersion: "12.0.0",
89
- version: "22.2.0",
90
+ version: "22.2.2",
90
91
  ngImport: i0,
91
92
  type: SparkGridRenderers,
92
93
  deps: [],
@@ -94,7 +95,7 @@ var SparkGridRenderers = class SparkGridRenderers {
94
95
  });
95
96
  static ɵprov = i0.ɵɵngDeclareInjectable({
96
97
  minVersion: "12.0.0",
97
- version: "22.2.0",
98
+ version: "22.2.2",
98
99
  ngImport: i0,
99
100
  type: SparkGridRenderers,
100
101
  providedIn: "root"
@@ -102,7 +103,7 @@ var SparkGridRenderers = class SparkGridRenderers {
102
103
  };
103
104
  i0.ɵɵngDeclareClassMetadata({
104
105
  minVersion: "12.0.0",
105
- version: "22.2.0",
106
+ version: "22.2.2",
106
107
  ngImport: i0,
107
108
  type: SparkGridRenderers,
108
109
  decorators: [{
@@ -126,7 +127,7 @@ var SparkQueryIconDirective = class SparkQueryIconDirective {
126
127
  }
127
128
  static ɵfac = i0.ɵɵngDeclareFactory({
128
129
  minVersion: "12.0.0",
129
- version: "22.2.0",
130
+ version: "22.2.2",
130
131
  ngImport: i0,
131
132
  type: SparkQueryIconDirective,
132
133
  deps: [],
@@ -134,7 +135,7 @@ var SparkQueryIconDirective = class SparkQueryIconDirective {
134
135
  });
135
136
  static ɵdir = i0.ɵɵngDeclareDirective({
136
137
  minVersion: "17.1.0",
137
- version: "22.2.0",
138
+ version: "22.2.2",
138
139
  type: SparkQueryIconDirective,
139
140
  isStandalone: true,
140
141
  selector: "[sparkQueryIcon]",
@@ -150,7 +151,7 @@ var SparkQueryIconDirective = class SparkQueryIconDirective {
150
151
  };
151
152
  i0.ɵɵngDeclareClassMetadata({
152
153
  minVersion: "12.0.0",
153
- version: "22.2.0",
154
+ version: "22.2.2",
154
155
  ngImport: i0,
155
156
  type: SparkQueryIconDirective,
156
157
  decorators: [{
@@ -177,7 +178,7 @@ var SparkQueryCaptionDirective = class SparkQueryCaptionDirective {
177
178
  }
178
179
  static ɵfac = i0.ɵɵngDeclareFactory({
179
180
  minVersion: "12.0.0",
180
- version: "22.2.0",
181
+ version: "22.2.2",
181
182
  ngImport: i0,
182
183
  type: SparkQueryCaptionDirective,
183
184
  deps: [],
@@ -185,7 +186,7 @@ var SparkQueryCaptionDirective = class SparkQueryCaptionDirective {
185
186
  });
186
187
  static ɵdir = i0.ɵɵngDeclareDirective({
187
188
  minVersion: "17.1.0",
188
- version: "22.2.0",
189
+ version: "22.2.2",
189
190
  type: SparkQueryCaptionDirective,
190
191
  isStandalone: true,
191
192
  selector: "[sparkQueryCaption]",
@@ -201,7 +202,7 @@ var SparkQueryCaptionDirective = class SparkQueryCaptionDirective {
201
202
  };
202
203
  i0.ɵɵngDeclareClassMetadata({
203
204
  minVersion: "12.0.0",
204
- version: "22.2.0",
205
+ version: "22.2.2",
205
206
  ngImport: i0,
206
207
  type: SparkQueryCaptionDirective,
207
208
  decorators: [{
@@ -228,7 +229,7 @@ var SparkQueryActionsDirective = class SparkQueryActionsDirective {
228
229
  }
229
230
  static ɵfac = i0.ɵɵngDeclareFactory({
230
231
  minVersion: "12.0.0",
231
- version: "22.2.0",
232
+ version: "22.2.2",
232
233
  ngImport: i0,
233
234
  type: SparkQueryActionsDirective,
234
235
  deps: [],
@@ -236,7 +237,7 @@ var SparkQueryActionsDirective = class SparkQueryActionsDirective {
236
237
  });
237
238
  static ɵdir = i0.ɵɵngDeclareDirective({
238
239
  minVersion: "17.1.0",
239
- version: "22.2.0",
240
+ version: "22.2.2",
240
241
  type: SparkQueryActionsDirective,
241
242
  isStandalone: true,
242
243
  selector: "[sparkQueryActions]",
@@ -252,7 +253,7 @@ var SparkQueryActionsDirective = class SparkQueryActionsDirective {
252
253
  };
253
254
  i0.ɵɵngDeclareClassMetadata({
254
255
  minVersion: "12.0.0",
255
- version: "22.2.0",
256
+ version: "22.2.2",
256
257
  ngImport: i0,
257
258
  type: SparkQueryActionsDirective,
258
259
  decorators: [{
@@ -335,7 +336,7 @@ var SparkGridCellComponent = class SparkGridCellComponent {
335
336
  }, ...ngDevMode ? [{ debugName: "dateValue" }] : /* istanbul ignore next */ []);
336
337
  static ɵfac = i0.ɵɵngDeclareFactory({
337
338
  minVersion: "12.0.0",
338
- version: "22.2.0",
339
+ version: "22.2.2",
339
340
  ngImport: i0,
340
341
  type: SparkGridCellComponent,
341
342
  deps: [],
@@ -343,7 +344,7 @@ var SparkGridCellComponent = class SparkGridCellComponent {
343
344
  });
344
345
  static ɵcmp = i0.ɵɵngDeclareComponent({
345
346
  minVersion: "17.0.0",
346
- version: "22.2.0",
347
+ version: "22.2.2",
347
348
  type: SparkGridCellComponent,
348
349
  isStandalone: true,
349
350
  selector: "spark-grid-cell",
@@ -447,7 +448,7 @@ var SparkGridCellComponent = class SparkGridCellComponent {
447
448
  };
448
449
  i0.ɵɵngDeclareClassMetadata({
449
450
  minVersion: "12.0.0",
450
- version: "22.2.0",
451
+ version: "22.2.2",
451
452
  ngImport: i0,
452
453
  type: SparkGridCellComponent,
453
454
  decorators: [{
@@ -515,8 +516,74 @@ i0.ɵɵngDeclareClassMetadata({
515
516
  }]
516
517
  }
517
518
  });
519
+ const LIVE_GRID_SETTLE_MS = 1e3;
520
+ var LiveGridSession = class {
521
+ onPush;
522
+ onError;
523
+ settleMs;
524
+ requests = new BehaviorSubject(null);
525
+ subscription;
526
+ currentKey = null;
527
+ latest = null;
528
+ waiters = [];
529
+ failed = null;
530
+ constructor(live, onPush, onError, settleMs = LIVE_GRID_SETTLE_MS) {
531
+ this.onPush = onPush;
532
+ this.onError = onError;
533
+ this.settleMs = settleMs;
534
+ this.subscription = live.watchQuery(this.requests).subscribe((event) => {
535
+ if (event.type === "result") {
536
+ this.latest = event.result;
537
+ if (this.waiters.length) this.resolveWaiters(event.result);
538
+ else this.onPush();
539
+ } else {
540
+ this.failed = event;
541
+ this.latest = null;
542
+ this.resolveWaiters(null);
543
+ this.onError(event);
544
+ }
545
+ });
546
+ }
547
+ fetch(request) {
548
+ if (this.failed) return Promise.resolve(null);
549
+ const key = canonicalJson(request);
550
+ if (key === this.currentKey) {
551
+ if (this.waiters.length === 0 && this.latest) return Promise.resolve(this.latest);
552
+ return new Promise((resolve) => this.waiters.push(resolve));
553
+ }
554
+ const answered = this.currentKey === null || this.latest === null;
555
+ this.currentKey = key;
556
+ this.requests.next(request);
557
+ return new Promise((resolve) => {
558
+ let timer = null;
559
+ const waiter = (result) => {
560
+ if (timer) clearTimeout(timer);
561
+ resolve(result);
562
+ };
563
+ this.waiters.push(waiter);
564
+ if (!answered) timer = setTimeout(() => this.resolveWaiters(this.latest), this.settleMs);
565
+ });
566
+ }
567
+ dispose() {
568
+ this.subscription.unsubscribe();
569
+ this.requests.complete();
570
+ this.resolveWaiters(null);
571
+ }
572
+ resolveWaiters(result) {
573
+ const waiters = this.waiters;
574
+ this.waiters = [];
575
+ for (const waiter of waiters) waiter(result);
576
+ }
577
+ };
578
+ const LIVE_ERROR_STATUS = {
579
+ unavailable: 404,
580
+ invalid: 400,
581
+ limit: 429,
582
+ failed: 500
583
+ };
518
584
  var SparkQueryGridComponent = class SparkQueryGridComponent {
519
585
  sparkService = inject(SparkService);
586
+ liveService = inject(SparkLiveService);
520
587
  gridRenderers = inject(SparkGridRenderers);
521
588
  queryRefresh = inject(SparkQueryRefreshService);
522
589
  injector = inject(Injector);
@@ -529,6 +596,13 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
529
596
  presetFilters = input([], ...ngDevMode ? [{ debugName: "presetFilters" }] : /* istanbul ignore next */ []);
530
597
  appliedPresetFilters = signal([], ...ngDevMode ? [{ debugName: "appliedPresetFilters" }] : /* istanbul ignore next */ []);
531
598
  data = input(null, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
599
+ live = input(void 0, ...ngDevMode ? [{ debugName: "live" }] : /* istanbul ignore next */ []);
600
+ liveFailed = signal(false, ...ngDevMode ? [{ debugName: "liveFailed" }] : /* istanbul ignore next */ []);
601
+ isLive = computed(() => this.data() === null && this.liveWanted(this.query()) && !this.liveFailed(), ...ngDevMode ? [{ debugName: "isLive" }] : /* istanbul ignore next */ []);
602
+ liveWanted(query) {
603
+ if (!query || isVirtualScrollingQuery(query)) return false;
604
+ return this.live() ?? query.live === true;
605
+ }
532
606
  search = input("", ...ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []);
533
607
  columns = input(null, ...ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []);
534
608
  reloadToken = input(null, ...ngDevMode ? [{ debugName: "reloadToken" }] : /* istanbul ignore next */ []);
@@ -846,7 +920,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
846
920
  allColumns = computed(() => this.columns() ?? this.fetchedColumns(), ...ngDevMode ? [{ debugName: "allColumns" }] : /* istanbul ignore next */ []);
847
921
  rowActionsColumn = "__sparkRowActions";
848
922
  datatableLabels = computed(() => sparkDatatableLabels(this.lang), ...ngDevMode ? [{ debugName: "datatableLabels" }] : /* istanbul ignore next */ []);
849
- hasExternalData = computed(() => this.data() !== null || this.query()?.isStreamingQuery === true, ...ngDevMode ? [{ debugName: "hasExternalData" }] : /* istanbul ignore next */ []);
923
+ hasExternalData = computed(() => this.data() !== null, ...ngDevMode ? [{ debugName: "hasExternalData" }] : /* istanbul ignore next */ []);
850
924
  isActionEnabled(action) {
851
925
  return this.clientMethods.unavailableReason(action.requiresClient) === null && parseSelectionRule(action.selectionRule)(this.selection().map((r) => r.id).length);
852
926
  }
@@ -929,6 +1003,46 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
929
1003
  this.onFilterChanged();
930
1004
  });
931
1005
  });
1006
+ let firstLive = true;
1007
+ effect(() => {
1008
+ this.live();
1009
+ if (firstLive) {
1010
+ firstLive = false;
1011
+ return;
1012
+ }
1013
+ untracked(() => {
1014
+ const query = this.query();
1015
+ if (!query || this.hasExternalData()) return;
1016
+ this.disposeLive();
1017
+ this.fetchFn.set(this.makeFetch(query, this.parentId(), this.parentType()));
1018
+ });
1019
+ });
1020
+ inject(DestroyRef).onDestroy(() => this.disposeLive());
1021
+ }
1022
+ liveSession = null;
1023
+ disposeLive() {
1024
+ this.liveSession?.dispose();
1025
+ this.liveSession = null;
1026
+ this.liveFailed.set(false);
1027
+ }
1028
+ onLivePush() {
1029
+ const datatable = this.datatable();
1030
+ if (datatable && this.fetchFn()) datatable.reload();
1031
+ }
1032
+ onLiveError(error) {
1033
+ this.liveFailed.set(true);
1034
+ this.errorMessage.set(error.code === "unavailable" ? this.lang.t("spark.query.unavailable", "This list is not available.") : error.message || this.lang.t("common.unexpectedError", "An unexpected error occurred"));
1035
+ this.resultCount.set(0);
1036
+ this.loadedIds.set(/* @__PURE__ */ new Set());
1037
+ this.onLivePush();
1038
+ this.error.emit(new HttpErrorResponse({
1039
+ status: LIVE_ERROR_STATUS[error.code] ?? 500,
1040
+ statusText: error.code,
1041
+ error: {
1042
+ error: error.message,
1043
+ code: error.code
1044
+ }
1045
+ }));
932
1046
  }
933
1047
  reload(options) {
934
1048
  if (this.hasExternalData()) return;
@@ -1034,8 +1148,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1034
1148
  this.customActionExecuted.emit({ action });
1035
1149
  if (action.refreshOnCompleted) this.reload();
1036
1150
  } catch (e) {
1037
- const err = e;
1038
- this.errorMessage.set(err.error?.error || err.message || this.lang.t("common.actionFailed") || "Action failed");
1151
+ this.errorMessage.set(describeSparkError(e, this.lang, e?.message || this.lang.t("common.actionFailed", "Action failed")));
1039
1152
  }
1040
1153
  }
1041
1154
  reportError(err) {
@@ -1043,13 +1156,14 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1043
1156
  this.error.emit(err);
1044
1157
  }
1045
1158
  describe(err) {
1046
- if (err?.status === 404) return this.lang.t("spark.query.unavailable") || "This list is not available.";
1047
- return err?.error?.error || err?.message || this.lang.t("common.unexpectedError") || "An unexpected error occurred";
1159
+ if (err?.status === 404) return this.lang.t("spark.query.unavailable", "This list is not available.");
1160
+ return err?.error?.error || err?.message || this.lang.t("common.unexpectedError", "An unexpected error occurred");
1048
1161
  }
1049
1162
  async loadData(queryId, parentId, parentType) {
1050
1163
  this.loading.set(true);
1051
1164
  this.errorMessage.set(null);
1052
1165
  this.fetchFn.set(null);
1166
+ this.disposeLive();
1053
1167
  this.query.set(null);
1054
1168
  this.entityType.set(null);
1055
1169
  this.canRead.set(false);
@@ -1079,7 +1193,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1079
1193
  this.defaultActions.set(defaultQueryActions(actions));
1080
1194
  } else if (resolvedQuery) console.warn(`[spark] Query '${resolvedQuery.name}' names entity type '${resolvedQuery.entityType ?? resolvedQuery.source}', which resolves to no type in /spark/types; the grid offers no actions and no row links.`);
1081
1195
  this.settings.set(initialGridSettings(resolvedQuery));
1082
- if (this.data() === null && !resolvedQuery?.isStreamingQuery) this.fetchFn.set(this.makeFetch(resolvedQuery, parentId, parentType));
1196
+ if (this.data() === null) this.fetchFn.set(this.makeFetch(resolvedQuery, parentId, parentType));
1083
1197
  this.loadLookupReferenceOptions();
1084
1198
  } catch (e) {
1085
1199
  this.fetchFn.set(null);
@@ -1096,7 +1210,10 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1096
1210
  return entityTypes.find((t) => t.name === sourceName || t.name === singular || t.name + "s" === sourceName || t.clrType?.endsWith(singular)) ?? null;
1097
1211
  }
1098
1212
  makeFetch(query, parentId, parentType) {
1099
- return (req) => this.sparkService.executeQuery(query.id, {
1213
+ return this.liveWanted(query) && this.data() === null ? this.makeLiveFetch(query, parentId, parentType) : this.makeHttpFetch(query, parentId, parentType);
1214
+ }
1215
+ requestFor(req, parentId, parentType) {
1216
+ return {
1100
1217
  sortColumns: req.sortColumns,
1101
1218
  skip: (req.page - 1) * req.perPage,
1102
1219
  take: req.perPage,
@@ -1106,21 +1223,59 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1106
1223
  parentType,
1107
1224
  ...this.effectiveDeleted() ? { deleted: this.effectiveDeleted() } : {},
1108
1225
  ...this.parentDeleted() && parentId && parentType ? { parentDeleted: "include" } : {}
1109
- }).then((r) => {
1110
- this.errorMessage.set(null);
1111
- this.resultCount.set(r.totalItems);
1112
- this.setFetchedColumns(r.columns ?? []);
1113
- this.disabledActions.set(r.disabledActions ?? []);
1114
- const ids = r.items.map((item) => item.id);
1115
- this.loadedIds.set(this.isVirtualScrolling() ? /* @__PURE__ */ new Set([...this.loadedIds(), ...ids]) : new Set(ids));
1116
- return {
1117
- data: r.items,
1118
- totalRecords: r.totalItems,
1119
- totalPages: Math.ceil(r.totalItems / req.perPage) || 1,
1120
- perPage: req.perPage,
1121
- page: req.page
1226
+ };
1227
+ }
1228
+ toPage(r, req) {
1229
+ this.errorMessage.set(null);
1230
+ this.resultCount.set(r.totalItems);
1231
+ this.setFetchedColumns(r.columns ?? []);
1232
+ this.disabledActions.set(r.disabledActions ?? []);
1233
+ const ids = (r.items ?? []).map((item) => item.id);
1234
+ this.loadedIds.set(this.isVirtualScrolling() ? /* @__PURE__ */ new Set([...this.loadedIds(), ...ids]) : new Set(ids));
1235
+ return {
1236
+ data: r.items ?? [],
1237
+ totalRecords: r.totalItems,
1238
+ totalPages: Math.ceil(r.totalItems / req.perPage) || 1,
1239
+ perPage: req.perPage,
1240
+ page: req.page
1241
+ };
1242
+ }
1243
+ makeLiveFetch(query, parentId, parentType) {
1244
+ const session = this.liveSession ??= new LiveGridSession(this.liveService, () => this.onLivePush(), (e) => this.onLiveError(e));
1245
+ return async (req) => {
1246
+ const options = this.requestFor(req, parentId, parentType);
1247
+ const request = {
1248
+ queryId: query.id,
1249
+ sortColumns: options.sortColumns?.length ? options.sortColumns.map((c) => ({
1250
+ property: c.property,
1251
+ direction: c.direction === "descending" ? "desc" : "asc"
1252
+ })) : void 0,
1253
+ skip: options.skip,
1254
+ take: options.take,
1255
+ search: options.search,
1256
+ columns: options.columns.length ? options.columns : void 0,
1257
+ parentId: options.parentId || void 0,
1258
+ parentType: options.parentType || void 0,
1259
+ deleted: options.deleted,
1260
+ parentDeleted: options.parentDeleted
1122
1261
  };
1123
- }).catch((e) => {
1262
+ const result = await session.fetch(request);
1263
+ if (!result) {
1264
+ this.resultCount.set(0);
1265
+ this.loadedIds.set(/* @__PURE__ */ new Set());
1266
+ return {
1267
+ data: [],
1268
+ totalRecords: 0,
1269
+ totalPages: 1,
1270
+ perPage: req.perPage,
1271
+ page: req.page
1272
+ };
1273
+ }
1274
+ return this.toPage(result, req);
1275
+ };
1276
+ }
1277
+ makeHttpFetch(query, parentId, parentType) {
1278
+ return (req) => this.sparkService.executeQuery(query.id, this.requestFor(req, parentId, parentType)).then((r) => this.toPage(r, req)).catch((e) => {
1124
1279
  this.reportError(e);
1125
1280
  this.resultCount.set(0);
1126
1281
  return {
@@ -1149,7 +1304,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1149
1304
  }
1150
1305
  static ɵfac = i0.ɵɵngDeclareFactory({
1151
1306
  minVersion: "12.0.0",
1152
- version: "22.2.0",
1307
+ version: "22.2.2",
1153
1308
  ngImport: i0,
1154
1309
  type: SparkQueryGridComponent,
1155
1310
  deps: [],
@@ -1157,7 +1312,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1157
1312
  });
1158
1313
  static ɵcmp = i0.ɵɵngDeclareComponent({
1159
1314
  minVersion: "17.0.0",
1160
- version: "22.2.0",
1315
+ version: "22.2.2",
1161
1316
  type: SparkQueryGridComponent,
1162
1317
  isStandalone: true,
1163
1318
  selector: "spark-query-grid",
@@ -1197,6 +1352,13 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1197
1352
  isRequired: false,
1198
1353
  transformFunction: null
1199
1354
  },
1355
+ live: {
1356
+ classPropertyName: "live",
1357
+ publicName: "live",
1358
+ isSignal: true,
1359
+ isRequired: false,
1360
+ transformFunction: null
1361
+ },
1200
1362
  search: {
1201
1363
  classPropertyName: "search",
1202
1364
  publicName: "search",
@@ -1277,7 +1439,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1277
1439
  isSignal: true
1278
1440
  }],
1279
1441
  ngImport: i0,
1280
- template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. See the class comment for why rows can\n arrive from outside at all (streaming must not put a websocket in every detail page's bundle).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. The selection survives paging\n (#467, D19), so the chip counts every ticked row — exactly what an action receives — and says how\n many are on other pages; its ⊗ drops them all, since the header checkbox only sees this page.\n The bar keeps its height while empty, so ticking the first row does not push the table down\n under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (offPageCount()) {\n <span class=\"spark-selection-off-page\">· {{ offPageCount() }} {{ 'common.onOtherPages' | t }}</span>\n }\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n </div>\n }\n\n <!-- 'checkbox' (#467, D9): only the checkbox cell selects; a row click opens the row. -->\n <bs-datatable\n [selectionMode]=\"selectionMode() === 'multiple' ? 'checkbox' : 'none'\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [distincts]=\"distinctsFn\"\n [labels]=\"datatableLabels()\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n [(settings)]=\"settings\">\n @for (col of allColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false; label: (col.label | resolveTranslation) || col.name\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n <!--\n The per-row ⋮ menu's column: no header, never sorted or filtered.\n\n ⚠️ Only once the data columns exist. Those ship WITH the first page, so the rows reach the\n datatable one render before the column templates do. The datatable sizes its columns ONCE,\n from the first render that has both columns and rows, then switches to `table-layout: fixed`\n and never measures again. If this column were already registered it would be the only one\n measured; the data columns would share what it left over equally, and at phone width their\n unclipped headers ran into each other. With no column at all the datatable waits.\n -->\n @if (rowActions().length && allColumns().length) {\n <div *bsDatatableColumn=\"rowActionsColumn; sortable: false; filterable: false; resizable: false; label: 'common.actions' | t\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of allColumns(); track col.name; let first = $first) {\n <td>\n @if (row) {\n <!-- The first column links to the row's detail page. Two things about this are\n routinely misread:\n\n 1. `cellContent` is projected INSIDE the anchor, so a custom `renderer` does NOT\n suppress this link — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n @let unavailable = actionUnavailableReason(action.definition);\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n [class.disabled]=\"unavailable !== null\" [attr.aria-disabled]=\"unavailable !== null ? 'true' : null\"\n [attr.title]=\"unavailable\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.label | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
1442
+ template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. A live grid still uses `[fetch]`: its pages\n come from /spark/live instead of /execute (see the class comment).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. The selection survives paging\n (#467, D19), so the chip counts every ticked row — exactly what an action receives — and says how\n many are on other pages; its ⊗ drops them all, since the header checkbox only sees this page.\n The bar keeps its height while empty, so ticking the first row does not push the table down\n under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (offPageCount()) {\n <span class=\"spark-selection-off-page\">· {{ offPageCount() }} {{ 'common.onOtherPages' | t }}</span>\n }\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n </div>\n }\n\n <!-- 'checkbox' (#467, D9): only the checkbox cell selects; a row click opens the row. -->\n <bs-datatable\n [selectionMode]=\"selectionMode() === 'multiple' ? 'checkbox' : 'none'\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [distincts]=\"distinctsFn\"\n [labels]=\"datatableLabels()\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n [(settings)]=\"settings\">\n @for (col of allColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false; label: (col.label | resolveTranslation) || col.name\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n <!--\n The per-row ⋮ menu's column: no header, never sorted or filtered.\n\n ⚠️ Only once the data columns exist. Those ship WITH the first page, so the rows reach the\n datatable one render before the column templates do. The datatable sizes its columns ONCE,\n from the first render that has both columns and rows, then switches to `table-layout: fixed`\n and never measures again. If this column were already registered it would be the only one\n measured; the data columns would share what it left over equally, and at phone width their\n unclipped headers ran into each other. With no column at all the datatable waits.\n -->\n @if (rowActions().length && allColumns().length) {\n <div *bsDatatableColumn=\"rowActionsColumn; sortable: false; filterable: false; resizable: false; label: 'common.actions' | t\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of allColumns(); track col.name; let first = $first) {\n <td>\n @if (row) {\n <!-- The first column links to the row's detail page. Two things about this are\n routinely misread:\n\n 1. `cellContent` is projected INSIDE the anchor, so a custom `renderer` does NOT\n suppress this link — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n @let unavailable = actionUnavailableReason(action.definition);\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n [class.disabled]=\"unavailable !== null\" [attr.aria-disabled]=\"unavailable !== null ? 'true' : null\"\n [attr.title]=\"unavailable\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.label | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
1281
1443
  styles: [":host ::ng-deep tbody tr[data-selected=true] a:not(.btn),:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{color:var(--mp-datatable-row-selected-color)}:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{--bs-btn-color: var(--mp-datatable-row-selected-color);--bs-btn-hover-color: var(--mp-datatable-row-selected-color);--bs-btn-active-color: var(--mp-datatable-row-selected-color)}\n"],
1282
1444
  dependencies: [
1283
1445
  {
@@ -1539,7 +1701,7 @@ var SparkQueryGridComponent = class SparkQueryGridComponent {
1539
1701
  };
1540
1702
  i0.ɵɵngDeclareClassMetadata({
1541
1703
  minVersion: "12.0.0",
1542
- version: "22.2.0",
1704
+ version: "22.2.2",
1543
1705
  ngImport: i0,
1544
1706
  type: SparkQueryGridComponent,
1545
1707
  decorators: [{
@@ -1570,7 +1732,7 @@ i0.ɵɵngDeclareClassMetadata({
1570
1732
  SparkIconComponent
1571
1733
  ],
1572
1734
  changeDetection: ChangeDetectionStrategy.OnPush,
1573
- template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. See the class comment for why rows can\n arrive from outside at all (streaming must not put a websocket in every detail page's bundle).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. The selection survives paging\n (#467, D19), so the chip counts every ticked row — exactly what an action receives — and says how\n many are on other pages; its ⊗ drops them all, since the header checkbox only sees this page.\n The bar keeps its height while empty, so ticking the first row does not push the table down\n under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (offPageCount()) {\n <span class=\"spark-selection-off-page\">· {{ offPageCount() }} {{ 'common.onOtherPages' | t }}</span>\n }\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n </div>\n }\n\n <!-- 'checkbox' (#467, D9): only the checkbox cell selects; a row click opens the row. -->\n <bs-datatable\n [selectionMode]=\"selectionMode() === 'multiple' ? 'checkbox' : 'none'\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [distincts]=\"distinctsFn\"\n [labels]=\"datatableLabels()\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n [(settings)]=\"settings\">\n @for (col of allColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false; label: (col.label | resolveTranslation) || col.name\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n <!--\n The per-row ⋮ menu's column: no header, never sorted or filtered.\n\n ⚠️ Only once the data columns exist. Those ship WITH the first page, so the rows reach the\n datatable one render before the column templates do. The datatable sizes its columns ONCE,\n from the first render that has both columns and rows, then switches to `table-layout: fixed`\n and never measures again. If this column were already registered it would be the only one\n measured; the data columns would share what it left over equally, and at phone width their\n unclipped headers ran into each other. With no column at all the datatable waits.\n -->\n @if (rowActions().length && allColumns().length) {\n <div *bsDatatableColumn=\"rowActionsColumn; sortable: false; filterable: false; resizable: false; label: 'common.actions' | t\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of allColumns(); track col.name; let first = $first) {\n <td>\n @if (row) {\n <!-- The first column links to the row's detail page. Two things about this are\n routinely misread:\n\n 1. `cellContent` is projected INSIDE the anchor, so a custom `renderer` does NOT\n suppress this link — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n @let unavailable = actionUnavailableReason(action.definition);\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n [class.disabled]=\"unavailable !== null\" [attr.aria-disabled]=\"unavailable !== null ? 'true' : null\"\n [attr.title]=\"unavailable\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.label | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
1735
+ template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. A live grid still uses `[fetch]`: its pages\n come from /spark/live instead of /execute (see the class comment).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. The selection survives paging\n (#467, D19), so the chip counts every ticked row — exactly what an action receives — and says how\n many are on other pages; its ⊗ drops them all, since the header checkbox only sees this page.\n The bar keeps its height while empty, so ticking the first row does not push the table down\n under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (offPageCount()) {\n <span class=\"spark-selection-off-page\">· {{ offPageCount() }} {{ 'common.onOtherPages' | t }}</span>\n }\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n </div>\n }\n\n <!-- 'checkbox' (#467, D9): only the checkbox cell selects; a row click opens the row. -->\n <bs-datatable\n [selectionMode]=\"selectionMode() === 'multiple' ? 'checkbox' : 'none'\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [distincts]=\"distinctsFn\"\n [labels]=\"datatableLabels()\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n [(settings)]=\"settings\">\n @for (col of allColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false; label: (col.label | resolveTranslation) || col.name\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n <!--\n The per-row ⋮ menu's column: no header, never sorted or filtered.\n\n ⚠️ Only once the data columns exist. Those ship WITH the first page, so the rows reach the\n datatable one render before the column templates do. The datatable sizes its columns ONCE,\n from the first render that has both columns and rows, then switches to `table-layout: fixed`\n and never measures again. If this column were already registered it would be the only one\n measured; the data columns would share what it left over equally, and at phone width their\n unclipped headers ran into each other. With no column at all the datatable waits.\n -->\n @if (rowActions().length && allColumns().length) {\n <div *bsDatatableColumn=\"rowActionsColumn; sortable: false; filterable: false; resizable: false; label: 'common.actions' | t\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of allColumns(); track col.name; let first = $first) {\n <td>\n @if (row) {\n <!-- The first column links to the row's detail page. Two things about this are\n routinely misread:\n\n 1. `cellContent` is projected INSIDE the anchor, so a custom `renderer` does NOT\n suppress this link — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n @let unavailable = actionUnavailableReason(action.definition);\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n [class.disabled]=\"unavailable !== null\" [attr.aria-disabled]=\"unavailable !== null ? 'true' : null\"\n [attr.title]=\"unavailable\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.label | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
1574
1736
  styles: [":host ::ng-deep tbody tr[data-selected=true] a:not(.btn),:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{color:var(--mp-datatable-row-selected-color)}:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{--bs-btn-color: var(--mp-datatable-row-selected-color);--bs-btn-hover-color: var(--mp-datatable-row-selected-color);--bs-btn-active-color: var(--mp-datatable-row-selected-color)}\n"]
1575
1737
  }]
1576
1738
  }],
@@ -1616,6 +1778,14 @@ i0.ɵɵngDeclareClassMetadata({
1616
1778
  required: false
1617
1779
  }]
1618
1780
  }],
1781
+ live: [{
1782
+ type: i0.Input,
1783
+ args: [{
1784
+ isSignal: true,
1785
+ alias: "live",
1786
+ required: false
1787
+ }]
1788
+ }],
1619
1789
  search: [{
1620
1790
  type: i0.Input,
1621
1791
  args: [{
@@ -1790,7 +1960,7 @@ var SparkSearchBoxComponent = class SparkSearchBoxComponent {
1790
1960
  }
1791
1961
  static ɵfac = i0.ɵɵngDeclareFactory({
1792
1962
  minVersion: "12.0.0",
1793
- version: "22.2.0",
1963
+ version: "22.2.2",
1794
1964
  ngImport: i0,
1795
1965
  type: SparkSearchBoxComponent,
1796
1966
  deps: [],
@@ -1798,7 +1968,7 @@ var SparkSearchBoxComponent = class SparkSearchBoxComponent {
1798
1968
  });
1799
1969
  static ɵcmp = i0.ɵɵngDeclareComponent({
1800
1970
  minVersion: "17.0.0",
1801
- version: "22.2.0",
1971
+ version: "22.2.2",
1802
1972
  type: SparkSearchBoxComponent,
1803
1973
  isStandalone: true,
1804
1974
  selector: "spark-search-box",
@@ -1918,7 +2088,7 @@ var SparkSearchBoxComponent = class SparkSearchBoxComponent {
1918
2088
  };
1919
2089
  i0.ɵɵngDeclareClassMetadata({
1920
2090
  minVersion: "12.0.0",
1921
- version: "22.2.0",
2091
+ version: "22.2.2",
1922
2092
  ngImport: i0,
1923
2093
  type: SparkSearchBoxComponent,
1924
2094
  decorators: [{
@@ -2045,7 +2215,7 @@ var SparkQueryCardComponent = class SparkQueryCardComponent {
2045
2215
  selection = computed(() => this.grid()?.selection() ?? [], ...ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []);
2046
2216
  static ɵfac = i0.ɵɵngDeclareFactory({
2047
2217
  minVersion: "12.0.0",
2048
- version: "22.2.0",
2218
+ version: "22.2.2",
2049
2219
  ngImport: i0,
2050
2220
  type: SparkQueryCardComponent,
2051
2221
  deps: [],
@@ -2053,7 +2223,7 @@ var SparkQueryCardComponent = class SparkQueryCardComponent {
2053
2223
  });
2054
2224
  static ɵcmp = i0.ɵɵngDeclareComponent({
2055
2225
  minVersion: "17.0.0",
2056
- version: "22.2.0",
2226
+ version: "22.2.2",
2057
2227
  type: SparkQueryCardComponent,
2058
2228
  isStandalone: true,
2059
2229
  selector: "spark-query-card",
@@ -2246,6 +2416,7 @@ var SparkQueryCardComponent = class SparkQueryCardComponent {
2246
2416
  "parentType",
2247
2417
  "presetFilters",
2248
2418
  "data",
2419
+ "live",
2249
2420
  "search",
2250
2421
  "columns",
2251
2422
  "reloadToken",
@@ -2293,7 +2464,7 @@ var SparkQueryCardComponent = class SparkQueryCardComponent {
2293
2464
  };
2294
2465
  i0.ɵɵngDeclareClassMetadata({
2295
2466
  minVersion: "12.0.0",
2296
- version: "22.2.0",
2467
+ version: "22.2.2",
2297
2468
  ngImport: i0,
2298
2469
  type: SparkQueryCardComponent,
2299
2470
  decorators: [{