@mintplayer/ng-spark 22.6.0 → 22.9.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 (31) hide show
  1. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +68 -0
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -0
  3. package/fesm2022/mintplayer-ng-spark-grid.mjs +134 -52
  4. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  5. package/fesm2022/mintplayer-ng-spark-models.mjs +77 -7
  6. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  7. package/fesm2022/mintplayer-ng-spark-pipes.mjs +87 -4
  8. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  9. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +4 -3
  10. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  11. package/fesm2022/mintplayer-ng-spark-po-form.mjs +15 -16
  12. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  13. package/fesm2022/mintplayer-ng-spark-query-list.mjs +21 -7
  14. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  15. package/fesm2022/mintplayer-ng-spark-renderers.mjs +16 -1
  16. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  17. package/fesm2022/mintplayer-ng-spark-services.mjs +92 -5
  18. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  19. package/fesm2022/mintplayer-ng-spark-shell.mjs +41 -4
  20. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  21. package/package.json +5 -1
  22. package/types/mintplayer-ng-spark-attribute-description.d.ts +30 -0
  23. package/types/mintplayer-ng-spark-grid.d.ts +75 -33
  24. package/types/mintplayer-ng-spark-models.d.ts +136 -7
  25. package/types/mintplayer-ng-spark-pipes.d.ts +33 -5
  26. package/types/mintplayer-ng-spark-po-detail.d.ts +2 -2
  27. package/types/mintplayer-ng-spark-po-form.d.ts +7 -7
  28. package/types/mintplayer-ng-spark-query-list.d.ts +11 -4
  29. package/types/mintplayer-ng-spark-renderers.d.ts +31 -11
  30. package/types/mintplayer-ng-spark-services.d.ts +71 -2
  31. package/types/mintplayer-ng-spark-shell.d.ts +32 -2
@@ -0,0 +1,68 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { BsTooltipDirective } from '@mintplayer/ng-bootstrap/tooltip';
4
+ import { SparkIconComponent } from '@mintplayer/ng-spark/icon';
5
+ import { resolveTranslation, currentLanguage } from '@mintplayer/ng-spark/models';
6
+
7
+ /**
8
+ * The [i] beside an attribute label: a focusable button whose tooltip shows the attribute's
9
+ * `description` (#348). Renders nothing when the model declares no description, so every label
10
+ * site can include it unconditionally.
11
+ *
12
+ * Accessibility comes from `*bsTooltip`: it opens on hover AND focus, closes on Escape, and sets
13
+ * `aria-describedby` on the button while open. The button's own name is the description text, so
14
+ * a screen reader announces the help on focus without waiting for the tooltip.
15
+ *
16
+ * Clicks are stopped: the [i] lives inside sortable grid headers and `<label for>` elements, and
17
+ * must neither toggle the sort nor move focus into the input.
18
+ */
19
+ class SparkAttributeDescriptionComponent {
20
+ /** The attribute's `description`; `undefined` (the common case) renders nothing. */
21
+ description = input(/* @ts-ignore */
22
+ ...(ngDevMode ? [undefined, { debugName: "description" }] : /* istanbul ignore next */ []));
23
+ /** Where the tooltip opens relative to the [i]. */
24
+ position = input('top', /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
26
+ /** Resolved for the current language; re-evaluates when the user switches language. */
27
+ text = computed(() => resolveTranslation(this.description(), currentLanguage()).trim(), /* @ts-ignore */
28
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
29
+ onClick(event) {
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ }
33
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkAttributeDescriptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
34
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkAttributeDescriptionComponent, isStandalone: true, selector: "spark-attribute-description", inputs: { description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
35
+ @if (text(); as text) {
36
+ <button
37
+ type="button"
38
+ class="btn btn-link p-0 ms-1 align-baseline spark-attribute-description-trigger"
39
+ [attr.aria-label]="text"
40
+ (click)="onClick($event)">
41
+ <spark-icon name="info-circle" />
42
+ <span *bsTooltip="position()" class="spark-attribute-description-text">{{ text }}</span>
43
+ </button>
44
+ }
45
+ `, isInline: true, styles: [":host{display:inline-block;line-height:1}.spark-attribute-description-trigger{color:inherit;opacity:.6;font-size:.875em;vertical-align:baseline}.spark-attribute-description-trigger:hover,.spark-attribute-description-trigger:focus-visible{opacity:1}.spark-attribute-description-text{display:inline-block;white-space:pre-line;text-align:start;max-width:20rem}\n"], dependencies: [{ kind: "directive", type: BsTooltipDirective, selector: "*[bsTooltip]", inputs: ["bsTooltip"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
46
+ }
47
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkAttributeDescriptionComponent, decorators: [{
48
+ type: Component,
49
+ args: [{ selector: 'spark-attribute-description', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [BsTooltipDirective, SparkIconComponent], template: `
50
+ @if (text(); as text) {
51
+ <button
52
+ type="button"
53
+ class="btn btn-link p-0 ms-1 align-baseline spark-attribute-description-trigger"
54
+ [attr.aria-label]="text"
55
+ (click)="onClick($event)">
56
+ <spark-icon name="info-circle" />
57
+ <span *bsTooltip="position()" class="spark-attribute-description-text">{{ text }}</span>
58
+ </button>
59
+ }
60
+ `, styles: [":host{display:inline-block;line-height:1}.spark-attribute-description-trigger{color:inherit;opacity:.6;font-size:.875em;vertical-align:baseline}.spark-attribute-description-trigger:hover,.spark-attribute-description-trigger:focus-visible{opacity:1}.spark-attribute-description-text{display:inline-block;white-space:pre-line;text-align:start;max-width:20rem}\n"] }]
61
+ }], propDecorators: { description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
62
+
63
+ /**
64
+ * Generated bundle index. Do not edit.
65
+ */
66
+
67
+ export { SparkAttributeDescriptionComponent };
68
+ //# sourceMappingURL=mintplayer-ng-spark-attribute-description.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mintplayer-ng-spark-attribute-description.mjs","sources":["../../attribute-description/src/spark-attribute-description.component.ts","../../attribute-description/mintplayer-ng-spark-attribute-description.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { BsTooltipDirective } from '@mintplayer/ng-bootstrap/tooltip';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { TranslatedString, currentLanguage, resolveTranslation } from '@mintplayer/ng-spark/models';\n\nexport type SparkAttributeDescriptionPosition = 'top' | 'bottom' | 'start' | 'end';\n\n/**\n * The [i] beside an attribute label: a focusable button whose tooltip shows the attribute's\n * `description` (#348). Renders nothing when the model declares no description, so every label\n * site can include it unconditionally.\n *\n * Accessibility comes from `*bsTooltip`: it opens on hover AND focus, closes on Escape, and sets\n * `aria-describedby` on the button while open. The button's own name is the description text, so\n * a screen reader announces the help on focus without waiting for the tooltip.\n *\n * Clicks are stopped: the [i] lives inside sortable grid headers and `<label for>` elements, and\n * must neither toggle the sort nor move focus into the input.\n */\n@Component({\n selector: 'spark-attribute-description',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [BsTooltipDirective, SparkIconComponent],\n template: `\n @if (text(); as text) {\n <button\n type=\"button\"\n class=\"btn btn-link p-0 ms-1 align-baseline spark-attribute-description-trigger\"\n [attr.aria-label]=\"text\"\n (click)=\"onClick($event)\">\n <spark-icon name=\"info-circle\" />\n <span *bsTooltip=\"position()\" class=\"spark-attribute-description-text\">{{ text }}</span>\n </button>\n }\n `,\n styles: [`\n :host {\n display: inline-block;\n line-height: 1;\n }\n .spark-attribute-description-trigger {\n color: inherit;\n opacity: 0.6;\n font-size: 0.875em;\n vertical-align: baseline;\n }\n .spark-attribute-description-trigger:hover,\n .spark-attribute-description-trigger:focus-visible {\n opacity: 1;\n }\n .spark-attribute-description-text {\n display: inline-block;\n white-space: pre-line;\n text-align: start;\n max-width: 20rem;\n }\n `],\n})\nexport class SparkAttributeDescriptionComponent {\n /** The attribute's `description`; `undefined` (the common case) renders nothing. */\n description = input<TranslatedString | undefined>();\n\n /** Where the tooltip opens relative to the [i]. */\n position = input<SparkAttributeDescriptionPosition>('top');\n\n /** Resolved for the current language; re-evaluates when the user switches language. */\n text = computed(() => resolveTranslation(this.description(), currentLanguage()).trim());\n\n onClick(event: MouseEvent): void {\n event.preventDefault();\n event.stopPropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAOA;;;;;;;;;;;AAWG;MAyCU,kCAAkC,CAAA;;AAE7C,IAAA,WAAW,GAAG,KAAK;+FAAgC;;IAGnD,QAAQ,GAAG,KAAK,CAAoC,KAAK;iFAAC;;AAG1D,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAM,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE;6EAAC;AAEvF,IAAA,OAAO,CAAC,KAAiB,EAAA;QACvB,KAAK,CAAC,cAAc,EAAE;QACtB,KAAK,CAAC,eAAe,EAAE;IACzB;uGAbW,kCAAkC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAlC,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,kCAAkC,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,6BAAA,EAAA,MAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnCnC;;;;;;;;;;;GAWT,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,yWAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAZS,kBAAkB,gFAAE,kBAAkB,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAoCrC,kCAAkC,EAAA,UAAA,EAAA,CAAA;kBAxC9C,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,6BAA6B,EAAA,UAAA,EAC3B,IAAI,EAAA,eAAA,EACC,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,EAAA,QAAA,EACvC;;;;;;;;;;;AAWT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,yWAAA,CAAA,EAAA;;;ACnCH;;AAEG;;;;"}
@@ -1,8 +1,8 @@
1
1
  import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
2
- import { hasShowedOnFlag, ShowedOn, selectionModeFor, parseSelectionRule, filterQueryActions } from '@mintplayer/ng-spark/models';
2
+ import { hasShowedOnFlag, ShowedOn, valueFor, selectionModeFor, parseSelectionRule, filterQueryActions } from '@mintplayer/ng-spark/models';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, Injectable, TemplateRef, input, Directive, computed, ChangeDetectionStrategy, Component, model, output, signal, effect, untracked, contentChildren, viewChild } from '@angular/core';
5
- import { SPARK_ATTRIBUTE_RENDERERS, rendererValue, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';
5
+ import { SPARK_ATTRIBUTE_RENDERERS, cellValue, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';
6
6
  import { SparkService, SparkLanguageService } from '@mintplayer/ng-spark/services';
7
7
  import * as i1 from '@angular/common';
8
8
  import { CommonModule, NgComponentOutlet } from '@angular/common';
@@ -12,7 +12,8 @@ import { Color } from '@mintplayer/ng-bootstrap';
12
12
  import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
13
13
  import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
14
14
  import { SparkQueryRefreshService } from '@mintplayer/ng-spark/client-operations';
15
- import { ResolveTranslationPipe, AttributeValuePipe, ReferenceChipsPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';
15
+ import { ResolveTranslationPipe, QueryCellValuePipe, QueryReferenceChipsPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';
16
+ import { SparkAttributeDescriptionComponent } from '@mintplayer/ng-spark/attribute-description';
16
17
  import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
17
18
  import { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';
18
19
 
@@ -66,55 +67,50 @@ function isVirtualScrollingQuery(query) {
66
67
  * There is now one grid, {@link SparkQueryGridComponent}, so the drift cannot recur. This stays
67
68
  * separate because it is stateless service-shaped logic rather than view state, and because it is
68
69
  * what a custom grid would need in order to render Spark cells at all.
69
- *
70
- * The constraint that shaped the split still holds and is why the grid takes rows as an input
71
- * rather than owning a socket: streaming's dependency graph must not reach the bundle of every
72
- * detail page.
73
70
  */
74
71
  class SparkGridRenderers {
75
72
  registry = inject(SPARK_ATTRIBUTE_RENDERERS);
76
73
  sparkService = inject(SparkService);
77
- /** The registered column component for an attribute, or null to fall back to the default cell. */
78
- columnComponentFor(attr) {
79
- if (!attr.renderer)
74
+ /** The registered column component for a column, or null to fall back to the default cell. */
75
+ columnComponentFor(column) {
76
+ if (!column.renderer)
80
77
  return null;
81
- return this.registry.find(r => r.name === attr.renderer)?.columnComponent ?? null;
78
+ return this.registry.find(r => r.name === column.renderer)?.columnComponent ?? null;
82
79
  }
83
80
  /**
84
- * Inputs for a column renderer, filtered to what the component actually declares —
81
+ * Inputs for a query-grid cell renderer, filtered to what the component actually declares —
85
82
  * `NgComponentOutlet` throws on an input the target does not have, which is what lets every
86
83
  * member of the renderer contract be optional.
87
84
  */
88
- columnInputsFor(component, item, attr) {
89
- const itemAttr = item.attributes.find(a => a.name === attr.name);
90
- return this.cellInputsFor(component, rendererValue(itemAttr), attr, item);
85
+ columnInputsFor(component, item, column) {
86
+ return this.cellInputsFor(component, cellValue(valueFor(item, column.name)), column, item);
91
87
  }
92
88
  /**
93
- * The same contract for a row that is not a <see cref="PersistentObject"/>.
89
+ * The same contract for a row that is not a query result — an AsDetail sub-table row, which is
90
+ * a plain dictionary of embedded values with no id and no column metadata of its own, so its
91
+ * value cannot be read the way {@link columnInputsFor} reads one.
94
92
  *
95
- * An AsDetail row is a plain dictionary embedded values with no id and no attribute list so
96
- * its value cannot be read the way {@link columnInputsFor} reads one. Only the extraction
97
- * differs; the renderer contract is identical, and stating it once is what stops the two from
98
- * drifting the way the cell markup already had.
93
+ * Only the extraction differs; the renderer contract is identical, and stating it once is what
94
+ * stops the two from drifting the way the cell markup already had.
99
95
  */
100
96
  cellInputsFor(component, value, column, item) {
101
97
  return withDeclaredInputs(component, {
102
98
  value,
103
- attribute: column,
99
+ column,
104
100
  options: column.rendererOptions,
105
101
  item,
106
102
  });
107
103
  }
108
104
  /**
109
- * Loads every lookup reference the visible attributes need, in one pass.
105
+ * Loads every lookup reference the visible columns need, in one pass.
110
106
  *
111
107
  * Returns an empty map rather than throwing when there are none, so callers never branch on it.
112
108
  */
113
- async loadLookupOptions(attributes) {
114
- const lookupAttrs = attributes.filter(a => a.lookupReferenceType);
115
- if (lookupAttrs.length === 0)
109
+ async loadLookupOptions(columns) {
110
+ const lookupColumns = columns.filter(c => c.lookupReferenceType);
111
+ if (lookupColumns.length === 0)
116
112
  return {};
117
- const names = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
113
+ const names = [...new Set(lookupColumns.map(c => c.lookupReferenceType))];
118
114
  const entries = await Promise.all(names.map(async (name) => [name, await this.sparkService.getLookupReference(name)]));
119
115
  return entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
120
116
  }
@@ -338,11 +334,11 @@ class SparkGridCellComponent {
338
334
  isUnsetBoolean = computed(() => this.display() == null, /* @ts-ignore */
339
335
  ...(ngDevMode ? [{ debugName: "isUnsetBoolean" }] : /* istanbul ignore next */ []));
340
336
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkGridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
341
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkGridCellComponent, isStandalone: true, selector: "spark-grid-cell", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, rendererValue: { classPropertyName: "rendererValue", publicName: "rendererValue", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, chips: { classPropertyName: "chips", publicName: "chips", isSignal: true, isRequired: false, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (rendererComponent(); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: rendererInputs()\"></ng-container>\n} @else if (column().dataType === 'boolean') {\n <!-- [indeterminate] matters: without it a null boolean renders as an unchecked box, which reads\n as an explicit `false`. One of the three copies of this cell bound it and the others had\n drifted, so the same column said different things on a detail page and in a grid. -->\n <input type=\"checkbox\"\n [checked]=\"display() === true\"\n [indeterminate]=\"isUnsetBoolean()\"\n disabled\n onclick=\"return false;\">\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Inline on purpose, and it is not a style-guide slip. In a query grid this cell is\n rendered INSIDE <mp-datatable>'s shadow root, which does not see document\n stylesheets: neither this component's scoped rules nor Bootstrap utilities apply\n there. Inline styles do. Measured 2026-08-23 -- see the comment in the .scss. -->\n <span class=\"d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"\n style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n} @else if (chips().length) {\n <span class=\"d-inline-flex flex-wrap gap-1\">\n @for (chip of chips(); track chip.id) {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n </span>\n} @else if (link(); as route) {\n <a [routerLink]=\"route\">{{ display() }}</a>\n} @else {\n {{ display() }}\n}\n", styles: [".spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
337
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkGridCellComponent, isStandalone: true, selector: "spark-grid-cell", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, rendererValue: { classPropertyName: "rendererValue", publicName: "rendererValue", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, chips: { classPropertyName: "chips", publicName: "chips", isSignal: true, isRequired: false, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (rendererComponent(); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: rendererInputs()\"></ng-container>\n} @else if (column().dataType === 'boolean') {\n <!-- [indeterminate] matters: without it a null boolean renders as an unchecked box, which reads\n as an explicit `false`. One of the three copies of this cell bound it and the others had\n drifted, so the same column said different things on a detail page and in a grid. -->\n <input type=\"checkbox\"\n [checked]=\"display() === true\"\n [indeterminate]=\"isUnsetBoolean()\"\n disabled\n onclick=\"return false;\">\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Inline on purpose, and it is not a style-guide slip. In a query grid this cell is\n rendered INSIDE <mp-datatable>'s shadow root, which does not see document\n stylesheets: neither this component's scoped rules nor Bootstrap utilities apply\n there. Inline styles do. Measured 2026-08-23 -- see the comment in the .scss. -->\n <span class=\"d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"\n style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n} @else if (column().dataType === 'image') {\n @if (display(); as src) {\n <!-- Inline sizing for the same reason the colour swatch above uses it: in a query grid this\n renders inside <mp-datatable>'s shadow root, which sees neither this component's scoped\n rules nor Bootstrap's. `alt` is empty on purpose \u2014 the cell is decorative next to the\n row's own labelled columns, and a filename read aloud is worse than nothing. -->\n <img [src]=\"src\" alt=\"\" loading=\"lazy\" class=\"align-middle rounded\"\n style=\"max-height: 2.5em; max-width: 100%; object-fit: contain;\">\n }\n} @else if (column().dataType === 'url') {\n @if (display(); as href) {\n <!-- rel is not optional with target=_blank: without noopener the opened page gets a handle on\n this one through window.opener, and these hrefs are data. -->\n <a [href]=\"href\" target=\"_blank\" rel=\"noopener noreferrer\">{{ href }}</a>\n }\n} @else if (chips().length) {\n <span class=\"d-inline-flex flex-wrap gap-1\">\n @for (chip of chips(); track chip.id) {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n </span>\n} @else if (link(); as route) {\n <a [routerLink]=\"route\">{{ display() }}</a>\n} @else {\n {{ display() }}\n}\n", styles: [".spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
342
338
  }
343
339
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkGridCellComponent, decorators: [{
344
340
  type: Component,
345
- args: [{ selector: 'spark-grid-cell', imports: [CommonModule, NgComponentOutlet, RouterModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (rendererComponent(); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: rendererInputs()\"></ng-container>\n} @else if (column().dataType === 'boolean') {\n <!-- [indeterminate] matters: without it a null boolean renders as an unchecked box, which reads\n as an explicit `false`. One of the three copies of this cell bound it and the others had\n drifted, so the same column said different things on a detail page and in a grid. -->\n <input type=\"checkbox\"\n [checked]=\"display() === true\"\n [indeterminate]=\"isUnsetBoolean()\"\n disabled\n onclick=\"return false;\">\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Inline on purpose, and it is not a style-guide slip. In a query grid this cell is\n rendered INSIDE <mp-datatable>'s shadow root, which does not see document\n stylesheets: neither this component's scoped rules nor Bootstrap utilities apply\n there. Inline styles do. Measured 2026-08-23 -- see the comment in the .scss. -->\n <span class=\"d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"\n style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n} @else if (chips().length) {\n <span class=\"d-inline-flex flex-wrap gap-1\">\n @for (chip of chips(); track chip.id) {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n </span>\n} @else if (link(); as route) {\n <a [routerLink]=\"route\">{{ display() }}</a>\n} @else {\n {{ display() }}\n}\n", styles: [".spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}\n"] }]
341
+ args: [{ selector: 'spark-grid-cell', imports: [CommonModule, NgComponentOutlet, RouterModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (rendererComponent(); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: rendererInputs()\"></ng-container>\n} @else if (column().dataType === 'boolean') {\n <!-- [indeterminate] matters: without it a null boolean renders as an unchecked box, which reads\n as an explicit `false`. One of the three copies of this cell bound it and the others had\n drifted, so the same column said different things on a detail page and in a grid. -->\n <input type=\"checkbox\"\n [checked]=\"display() === true\"\n [indeterminate]=\"isUnsetBoolean()\"\n disabled\n onclick=\"return false;\">\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Inline on purpose, and it is not a style-guide slip. In a query grid this cell is\n rendered INSIDE <mp-datatable>'s shadow root, which does not see document\n stylesheets: neither this component's scoped rules nor Bootstrap utilities apply\n there. Inline styles do. Measured 2026-08-23 -- see the comment in the .scss. -->\n <span class=\"d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"\n style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n} @else if (column().dataType === 'image') {\n @if (display(); as src) {\n <!-- Inline sizing for the same reason the colour swatch above uses it: in a query grid this\n renders inside <mp-datatable>'s shadow root, which sees neither this component's scoped\n rules nor Bootstrap's. `alt` is empty on purpose \u2014 the cell is decorative next to the\n row's own labelled columns, and a filename read aloud is worse than nothing. -->\n <img [src]=\"src\" alt=\"\" loading=\"lazy\" class=\"align-middle rounded\"\n style=\"max-height: 2.5em; max-width: 100%; object-fit: contain;\">\n }\n} @else if (column().dataType === 'url') {\n @if (display(); as href) {\n <!-- rel is not optional with target=_blank: without noopener the opened page gets a handle on\n this one through window.opener, and these hrefs are data. -->\n <a [href]=\"href\" target=\"_blank\" rel=\"noopener noreferrer\">{{ href }}</a>\n }\n} @else if (chips().length) {\n <span class=\"d-inline-flex flex-wrap gap-1\">\n @for (chip of chips(); track chip.id) {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n </span>\n} @else if (link(); as route) {\n <a [routerLink]=\"route\">{{ display() }}</a>\n} @else {\n {{ display() }}\n}\n", styles: [".spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}\n"] }]
346
342
  }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], rendererValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rendererValue", required: false }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], chips: [{ type: i0.Input, args: [{ isSignal: true, alias: "chips", required: false }] }], link: [{ type: i0.Input, args: [{ isSignal: true, alias: "link", required: false }] }] } });
347
343
 
348
344
  /**
@@ -415,6 +411,12 @@ class SparkQueryGridComponent {
415
411
  */
416
412
  search = input('', /* @ts-ignore */
417
413
  ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
414
+ /**
415
+ * Column metadata for externally supplied rows. Required whenever `data` is bound, because a
416
+ * projection cannot describe itself — there is no per-row metadata left to infer it from.
417
+ */
418
+ columns = input(null, /* @ts-ignore */
419
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
418
420
  /**
419
421
  * Change this to re-run the query. Any value works; only its identity matters.
420
422
  *
@@ -441,11 +443,49 @@ class SparkQueryGridComponent {
441
443
  /** Rows the user has ticked. Two-way so chrome outside the grid can read and clear them. */
442
444
  selection = model([], /* @ts-ignore */
443
445
  ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
446
+ /**
447
+ * Replaces where the first column's link points, per row.
448
+ *
449
+ * Return `null` to suppress the link for that row — useful when a grid mixes rows that have a
450
+ * detail page with rows that do not.
451
+ *
452
+ * ⚠️ **It changes the destination, not the permission.** `canRead()` still gates whether any
453
+ * link is rendered at all, and this function is never consulted when that gate is closed. It
454
+ * cannot be used to reach a row the rights model withheld; it exists for the case where the
455
+ * row's natural destination is not `/po/{type}/{id}` — a composed row that maps to a page of
456
+ * its own, or a grid embedded in a route that owns its own child paths.
457
+ */
458
+ rowRoute = input(null, /* @ts-ignore */
459
+ ...(ngDevMode ? [{ debugName: "rowRoute" }] : /* istanbul ignore next */ []));
444
460
  /** Emitted whenever a load or a page fetch fails, for a host in bespoke chrome. */
445
461
  error = output();
446
462
  rowClicked = output();
447
463
  customActionExecuted = output();
448
464
  colors = Color;
465
+ /**
466
+ * Where the first column's link points for this row: the host's `rowRoute` when it supplied one,
467
+ * otherwise the row's own detail page.
468
+ *
469
+ * A method rather than a computed because the answer is per row, and the template already
470
+ * iterates rows — a computed would have to be a map keyed by id, rebuilt on every page.
471
+ */
472
+ rowRouteFor(row) {
473
+ const custom = this.rowRoute();
474
+ if (custom)
475
+ return custom(row);
476
+ const type = this.entityType();
477
+ // ⚠️ A composed type has no per-row detail page by construction: its rows are computed, and
478
+ // `{Name}Actions.OnLoadAsync` serves ONE page which is free to ignore the id it was given. So
479
+ // /po/{type}/{rowId} does not 404 — it silently renders that same page, which is worse, because
480
+ // nothing looks wrong. This branch is live in DemoApp today: Read/StartPage implies
481
+ // Query/StartPage, so the composed grid renders with canRead() true and every row linked.
482
+ //
483
+ // `clrType` is absent exactly for those types and is already on the wire; the grid simply never
484
+ // consulted it. A host whose composed rows DO have a page says so with `rowRoute`.
485
+ if (!type?.clrType)
486
+ return null;
487
+ return ['/po', type.alias || type.id, row.id];
488
+ }
449
489
  query = signal(null, /* @ts-ignore */
450
490
  ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
451
491
  entityType = signal(null, /* @ts-ignore */
@@ -481,8 +521,24 @@ class SparkQueryGridComponent {
481
521
  ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
482
522
  isVirtualScrolling = computed(() => isVirtualScrollingQuery(this.query()), /* @ts-ignore */
483
523
  ...(ngDevMode ? [{ debugName: "isVirtualScrolling" }] : /* istanbul ignore next */ []));
484
- visibleAttributes = computed(() => visibleGridAttributes(this.entityType()), /* @ts-ignore */
485
- ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : /* istanbul ignore next */ []));
524
+ /**
525
+ * Columns as sent with the most recent fetched page.
526
+ *
527
+ * A host binding `data` (streaming) supplies its own via the `columns` input instead — the
528
+ * snapshot carries them once, exactly as a paged result does.
529
+ */
530
+ fetchedColumns = signal([], /* @ts-ignore */
531
+ ...(ngDevMode ? [{ debugName: "fetchedColumns" }] : /* istanbul ignore next */ []));
532
+ /** Every column on the wire, drawn or not — what renderers and lookups resolve against. */
533
+ allColumns = computed(() => this.columns() ?? this.fetchedColumns(), /* @ts-ignore */
534
+ ...(ngDevMode ? [{ debugName: "allColumns" }] : /* istanbul ignore next */ []));
535
+ /**
536
+ * The columns the grid draws. `isVisible: false` ships a value without a column, so a renderer
537
+ * can read a sibling the grid does not show; undefined means visible, so a server that predates
538
+ * the field still draws everything.
539
+ */
540
+ visibleColumns = computed(() => this.allColumns().filter(c => c.isVisible !== false), /* @ts-ignore */
541
+ ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
486
542
  /**
487
543
  * True when rows do not come from this component's own fetch.
488
544
  *
@@ -496,7 +552,7 @@ class SparkQueryGridComponent {
496
552
  ...(ngDevMode ? [{ debugName: "hasExternalData" }] : /* istanbul ignore next */ []));
497
553
  /** Whether an action's selection rule is satisfied right now. The server checks it again. */
498
554
  isActionEnabled(action) {
499
- return parseSelectionRule(action.selectionRule)(this.selection().length);
555
+ return parseSelectionRule(action.selectionRule)(this.selection().map(r => r.id).length);
500
556
  }
501
557
  constructor() {
502
558
  effect(() => {
@@ -574,7 +630,19 @@ class SparkQueryGridComponent {
574
630
  return;
575
631
  }
576
632
  try {
577
- await this.sparkService.executeCustomAction(this.entityType().id, action.name, undefined, this.selection());
633
+ // The sub-query's container travels with the action (#327). Without it an action invoked
634
+ // from a company's Cars tab could not tell it was on a company's page at all — the grid knew
635
+ // (it filters the rows by that parent) and simply dropped the fact on the way out.
636
+ //
637
+ // Sent as parentId/parentType, NOT as the `parent` argument: that one means an object of this
638
+ // action's OWN type and is loaded under it server-side, so handing it a Company id for a Car
639
+ // action refuses — correctly, and confusingly.
640
+ const pId = this.parentId();
641
+ const pType = this.parentType();
642
+ await this.sparkService.executeCustomAction(this.entityType().id, action.name, undefined, this.selection().map(r => r.id), pId && pType ? { id: pId, type: pType } : undefined,
643
+ // The query too: the server re-runs it narrowed to these ids, so the action receives the
644
+ // rows this grid rendered rather than a re-derivation from documents.
645
+ this.query()?.id);
578
646
  this.customActionExecuted.emit({ action });
579
647
  if (action.refreshOnCompleted)
580
648
  this.reload();
@@ -674,7 +742,7 @@ class SparkQueryGridComponent {
674
742
  }
675
743
  const sourceName = extractSourceName(query.source);
676
744
  const singular = singularize(sourceName);
677
- return entityTypes.find(t => t.name === sourceName || t.name === singular || t.clrType.endsWith(singular)) ?? null;
745
+ return entityTypes.find(t => t.name === sourceName || t.name === singular || t.clrType?.endsWith(singular)) ?? null;
678
746
  }
679
747
  makeFetch(query, parentId, parentType) {
680
748
  return (req) => this.sparkService.executeQuery(query.id, {
@@ -685,11 +753,16 @@ class SparkQueryGridComponent {
685
753
  parentId, parentType,
686
754
  }).then(r => {
687
755
  this.errorMessage.set(null);
688
- this.resultCount.set(r.totalRecords);
756
+ this.resultCount.set(r.totalItems);
757
+ // Columns come from the result now, not from the entity type: the server decides the query
758
+ // surface (ShowedOn.Query) and is the same place the sort-column allow-list is checked.
759
+ // ?? [] because a malformed or older response must render an empty grid, not throw inside a
760
+ // computed — where the stack points at the column filter and not at the response that lacked them.
761
+ this.fetchedColumns.set(r.columns ?? []);
689
762
  return {
690
- data: r.data,
691
- totalRecords: r.totalRecords,
692
- totalPages: Math.ceil(r.totalRecords / req.perPage) || 1,
763
+ data: r.items,
764
+ totalRecords: r.totalItems,
765
+ totalPages: Math.ceil(r.totalItems / req.perPage) || 1,
693
766
  perPage: req.perPage,
694
767
  page: req.page,
695
768
  };
@@ -702,30 +775,33 @@ class SparkQueryGridComponent {
702
775
  });
703
776
  }
704
777
  async loadLookupReferenceOptions() {
705
- this.lookupReferenceOptions.set(await this.gridRenderers.loadLookupOptions(this.visibleAttributes()));
778
+ // Over ALL columns, not just the drawn ones: an undrawn column's value can still be rendered
779
+ // by a renderer reading it as a sibling, and it would resolve to a raw key without its options.
780
+ this.lookupReferenceOptions.set(await this.gridRenderers.loadLookupOptions(this.allColumns()));
706
781
  }
707
782
  /**
708
783
  * The underlying value a custom renderer receives, which is not the printable one.
709
784
  *
710
- * `rendererValue` unwraps an AsDetail attribute to the nested object (or objects) rather than
711
- * flattening it to text a renderer for a nested type needs the object, not a summary of it.
785
+ * A projection is flat, so this is the cell's own value rather than a display string. It is
786
+ * deliberately not the attribute-shaped `rendererValue`: a query row has no nested object to
787
+ * fall back to, and reaching for one would hand every renderer `undefined`.
712
788
  */
713
- rendererValueFor(item, attr) {
714
- return rendererValue(item.attributes.find(a => a.name === attr.name));
789
+ rendererValueFor(item, column) {
790
+ return cellValue(valueFor(item, column.name));
715
791
  }
716
- getColumnRendererComponent(attr) {
717
- return this.gridRenderers.columnComponentFor(attr);
792
+ getColumnRendererComponent(column) {
793
+ return this.gridRenderers.columnComponentFor(column);
718
794
  }
719
- getColumnRendererInputs(component, item, attr) {
720
- return this.gridRenderers.columnInputsFor(component, item, attr);
795
+ getColumnRendererInputs(component, item, column) {
796
+ return this.gridRenderers.columnInputsFor(component, item, column);
721
797
  }
722
798
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
723
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkQueryGridComponent, isStandalone: true, selector: "spark-query-grid", inputs: { queryId: { classPropertyName: "queryId", publicName: "queryId", isSignal: true, isRequired: true, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, reloadToken: { classPropertyName: "reloadToken", publicName: "reloadToken", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { settings: "settingsChange", selection: "selectionChange", error: "error", rowClicked: "rowClicked", customActionExecuted: "customActionExecuted" }, ngImport: i0, 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 \u2014 no card, no message, nothing to\n see or report \u2014 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 \u2014 binding both makes `[fetch]` win silently \u2014 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 <bs-datatable\n [selectionMode]=\"selectionMode()\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; 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 \u2014 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 \u2014 a Custom.* query that fabricates rows in\n memory, say. -->\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\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-attr=\"attr\">\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]=\"attr\"\n [display]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes())\"\n [rendererValue]=\"rendererValueFor(item, attr)\"\n [item]=\"item\"\n [chips]=\"(attr.name | referenceChips:item)\" />\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["columns", "data", "fetch", "settings", "selectionMode", "selectable", "selection", "rowKey", "resizableColumns", "pagination", "virtualScroll", "itemSize", "virtualBuffer", "isResponsive", "compareWith", "tree", "idKey", "childCountKey", "treeIndent", "expandedIds", "selectionStrategy"], outputs: ["settingsChange", "selectionChange", "rowClick", "rowDblClick", "rowContextMenu", "expandedIdsChange", "rowExpand", "rowCollapse"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkGridCellComponent, selector: "spark-grid-cell", inputs: ["column", "display", "rendererValue", "item", "chips", "link"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }, { kind: "pipe", type: ReferenceChipsPipe, name: "referenceChips" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkQueryGridComponent, isStandalone: true, selector: "spark-query-grid", inputs: { queryId: { classPropertyName: "queryId", publicName: "queryId", isSignal: true, isRequired: true, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, reloadToken: { classPropertyName: "reloadToken", publicName: "reloadToken", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, rowRoute: { classPropertyName: "rowRoute", publicName: "rowRoute", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { settings: "settingsChange", selection: "selectionChange", error: "error", rowClicked: "rowClicked", customActionExecuted: "customActionExecuted" }, ngImport: i0, 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 \u2014 no card, no message, nothing to\n see or report \u2014 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 \u2014 binding both makes `[fetch]` win silently \u2014 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 <bs-datatable\n [selectionMode]=\"selectionMode()\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (col of visibleColumns(); track col.name) {\n <div *bsDatatableColumn=\"col.name; sortable: true\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of visibleColumns(); 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 \u2014 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 \u2014 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\"\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 </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", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["columns", "data", "fetch", "settings", "selectionMode", "selectable", "selection", "rowKey", "resizableColumns", "pagination", "virtualScroll", "itemSize", "virtualBuffer", "isResponsive", "compareWith", "tree", "idKey", "childCountKey", "treeIndent", "expandedIds", "selectionStrategy"], outputs: ["settingsChange", "selectionChange", "rowClick", "rowDblClick", "rowContextMenu", "expandedIdsChange", "rowExpand", "rowCollapse"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkGridCellComponent, selector: "spark-grid-cell", inputs: ["column", "display", "rendererValue", "item", "chips", "link"] }, { kind: "component", type: SparkAttributeDescriptionComponent, selector: "spark-attribute-description", inputs: ["description", "position"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: QueryCellValuePipe, name: "queryCellValue" }, { kind: "pipe", type: QueryReferenceChipsPipe, name: "queryReferenceChips" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
724
800
  }
725
801
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryGridComponent, decorators: [{
726
802
  type: Component,
727
- args: [{ selector: 'spark-query-grid', imports: [CommonModule, RouterModule, BsAlertComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsSpinnerComponent, SparkGridCellComponent, ResolveTranslationPipe, AttributeValuePipe, ReferenceChipsPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, 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 \u2014 no card, no message, nothing to\n see or report \u2014 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 \u2014 binding both makes `[fetch]` win silently \u2014 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 <bs-datatable\n [selectionMode]=\"selectionMode()\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; 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 \u2014 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 \u2014 a Custom.* query that fabricates rows in\n memory, say. -->\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\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-attr=\"attr\">\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]=\"attr\"\n [display]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes())\"\n [rendererValue]=\"rendererValueFor(item, attr)\"\n [item]=\"item\"\n [chips]=\"(attr.name | referenceChips:item)\" />\n</ng-template>\n" }]
728
- }], ctorParameters: () => [], propDecorators: { queryId: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryId", required: true }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], reloadToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "reloadToken", required: false }] }], settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: false }] }, { type: i0.Output, args: ["settingsChange"] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], error: [{ type: i0.Output, args: ["error"] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
803
+ args: [{ selector: 'spark-query-grid', imports: [CommonModule, RouterModule, BsAlertComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsSpinnerComponent, SparkGridCellComponent, ResolveTranslationPipe, QueryCellValuePipe, QueryReferenceChipsPipe, TranslateKeyPipe, SparkAttributeDescriptionComponent], changeDetection: ChangeDetectionStrategy.OnPush, 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 \u2014 no card, no message, nothing to\n see or report \u2014 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 \u2014 binding both makes `[fetch]` win silently \u2014 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 <bs-datatable\n [selectionMode]=\"selectionMode()\"\n [(selection)]=\"selection\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"data()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (col of visibleColumns(); track col.name) {\n <div *bsDatatableColumn=\"col.name; sortable: true\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of visibleColumns(); 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 \u2014 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 \u2014 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\"\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 </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" }]
804
+ }], ctorParameters: () => [], propDecorators: { queryId: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryId", required: true }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], reloadToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "reloadToken", required: false }] }], settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: false }] }, { type: i0.Output, args: ["settingsChange"] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], rowRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowRoute", required: false }] }], error: [{ type: i0.Output, args: ["error"] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
729
805
  function extractSourceName(source) {
730
806
  const dotIndex = source.indexOf('.');
731
807
  return dotIndex >= 0 ? source.substring(dotIndex + 1) : source;
@@ -804,6 +880,12 @@ class SparkQueryCardComponent {
804
880
  error = output();
805
881
  rowClicked = output();
806
882
  customActionExecuted = output();
883
+ /**
884
+ * Forwarded to the grid. Without this the escape hatch was unreachable from a card — i.e. from
885
+ * every auto-rendered sub-query and program-unit query page, which is most grids.
886
+ */
887
+ rowRoute = input(null, /* @ts-ignore */
888
+ ...(ngDevMode ? [{ debugName: "rowRoute" }] : /* istanbul ignore next */ []));
807
889
  colors = Color;
808
890
  iconSlots = contentChildren(SparkQueryIconDirective, /* @ts-ignore */
809
891
  ...(ngDevMode ? [{ debugName: "iconSlots" }] : /* istanbul ignore next */ []));
@@ -845,12 +927,12 @@ class SparkQueryCardComponent {
845
927
  selection = computed(() => this.grid()?.selection() ?? [], /* @ts-ignore */
846
928
  ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
847
929
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
848
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkQueryCardComponent, isStandalone: true, selector: "spark-query-card", inputs: { queryId: { classPropertyName: "queryId", publicName: "queryId", isSignal: true, isRequired: true, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, reloadToken: { classPropertyName: "reloadToken", publicName: "reloadToken", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, captionTemplate: { classPropertyName: "captionTemplate", publicName: "captionTemplate", isSignal: true, isRequired: false, transformFunction: null }, actionsTemplate: { classPropertyName: "actionsTemplate", publicName: "actionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { error: "error", rowClicked: "rowClicked", customActionExecuted: "customActionExecuted" }, queries: [{ propertyName: "iconSlots", predicate: SparkQueryIconDirective, isSignal: true }, { propertyName: "captionSlots", predicate: SparkQueryCaptionDirective, isSignal: true }, { propertyName: "actionSlots", predicate: SparkQueryActionsDirective, isSignal: true }], viewQueries: [{ propertyName: "grid", first: true, predicate: SparkQueryGridComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly \u2014 which\n is what the old `showCard=\"false\"` meant, expressed as a component choice rather than a flag.\n-->\n<bs-card class=\"d-block my-3\">\n <bs-card-header>\n <div class=\"d-flex align-items-center gap-2\">\n @if (iconTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: query() }\"></ng-container>\n }\n\n <span class=\"me-auto\">\n @if (captionTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: caption(), query: query() }\"></ng-container>\n } @else {\n {{ caption() }}\n }\n </span>\n\n @if (actionsTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: customActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (customActions().length) {\n <!-- The default: the actions the server declares for this type. The nav renders only when\n there are any, so a query without actions is pixel-identical to a plain caption.\n\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rule. -->\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-sm btn-outline-primary\"\n [disabled]=\"!grid()?.isActionEnabled(action)\" (click)=\"grid()?.onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n </div>\n </bs-card-header>\n\n <div class=\"p-3\">\n <spark-query-grid\n [queryId]=\"queryId()\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\"\n [reloadToken]=\"reloadToken()\"\n [data]=\"data()\"\n [search]=\"search()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsPriorityNavComponent, selector: "bs-priority-nav", inputs: ["moreLabel", "moreLabelTemplate", "collapseAt", "overflowFrom", "hideEmptyMore", "ariaLabel"], outputs: ["overflowChange"] }, { kind: "directive", type: BsPriorityNavItemDirective, selector: "[bsPriorityNavItem]", inputs: ["bsPriorityNavItem", "bsPriorityNavItemHideBelow"] }, { kind: "component", type: SparkQueryGridComponent, selector: "spark-query-grid", inputs: ["queryId", "parentId", "parentType", "data", "search", "reloadToken", "settings", "selection"], outputs: ["settingsChange", "selectionChange", "error", "rowClicked", "customActionExecuted"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
930
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkQueryCardComponent, isStandalone: true, selector: "spark-query-card", inputs: { queryId: { classPropertyName: "queryId", publicName: "queryId", isSignal: true, isRequired: true, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, reloadToken: { classPropertyName: "reloadToken", publicName: "reloadToken", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, captionTemplate: { classPropertyName: "captionTemplate", publicName: "captionTemplate", isSignal: true, isRequired: false, transformFunction: null }, actionsTemplate: { classPropertyName: "actionsTemplate", publicName: "actionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, rowRoute: { classPropertyName: "rowRoute", publicName: "rowRoute", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { error: "error", rowClicked: "rowClicked", customActionExecuted: "customActionExecuted" }, queries: [{ propertyName: "iconSlots", predicate: SparkQueryIconDirective, isSignal: true }, { propertyName: "captionSlots", predicate: SparkQueryCaptionDirective, isSignal: true }, { propertyName: "actionSlots", predicate: SparkQueryActionsDirective, isSignal: true }], viewQueries: [{ propertyName: "grid", first: true, predicate: SparkQueryGridComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly \u2014 which\n is what the old `showCard=\"false\"` meant, expressed as a component choice rather than a flag.\n-->\n<bs-card class=\"d-block my-3\">\n <bs-card-header>\n <div class=\"d-flex align-items-center gap-2\">\n @if (iconTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: query() }\"></ng-container>\n }\n\n @if (actionsTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: customActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (customActions().length) {\n <!-- The default: the actions the server declares for this type. The nav renders only when\n there are any, so a query without actions is pixel-identical to a plain caption.\n\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rule. -->\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-sm btn-outline-primary\"\n [disabled]=\"!grid()?.isActionEnabled(action)\" (click)=\"grid()?.onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n\n <!--\n Caption last, actions first. The auto margin is conditional rather than fixed, and that is\n the point: with actions present `ms-auto` pushes the caption to the trailing edge, and with\n none it falls back to `me-auto` so a plain card looks exactly as it always did. A fixed\n `ms-auto` would have right-aligned the caption of every action-less query card in every app\n \u2014 a change nobody asked for, to fix a layout only some cards have.\n -->\n <span [class.ms-auto]=\"customActions().length\" [class.me-auto]=\"!customActions().length\">\n @if (captionTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: caption(), query: query() }\"></ng-container>\n } @else {\n {{ caption() }}\n }\n </span>\n </div>\n </bs-card-header>\n\n <div class=\"p-3\">\n <spark-query-grid\n [queryId]=\"queryId()\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\"\n [reloadToken]=\"reloadToken()\"\n [data]=\"data()\"\n [search]=\"search()\"\n [rowRoute]=\"rowRoute()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsPriorityNavComponent, selector: "bs-priority-nav", inputs: ["moreLabel", "moreLabelTemplate", "collapseAt", "overflowFrom", "hideEmptyMore", "ariaLabel"], outputs: ["overflowChange"] }, { kind: "directive", type: BsPriorityNavItemDirective, selector: "[bsPriorityNavItem]", inputs: ["bsPriorityNavItem", "bsPriorityNavItemHideBelow"] }, { kind: "component", type: SparkQueryGridComponent, selector: "spark-query-grid", inputs: ["queryId", "parentId", "parentType", "data", "search", "columns", "reloadToken", "settings", "selection", "rowRoute"], outputs: ["settingsChange", "selectionChange", "error", "rowClicked", "customActionExecuted"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
849
931
  }
850
932
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryCardComponent, decorators: [{
851
933
  type: Component,
852
- args: [{ selector: 'spark-query-card', imports: [CommonModule, BsCardComponent, BsCardHeaderComponent, BsPriorityNavComponent, BsPriorityNavItemDirective, SparkQueryGridComponent, ResolveTranslationPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly \u2014 which\n is what the old `showCard=\"false\"` meant, expressed as a component choice rather than a flag.\n-->\n<bs-card class=\"d-block my-3\">\n <bs-card-header>\n <div class=\"d-flex align-items-center gap-2\">\n @if (iconTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: query() }\"></ng-container>\n }\n\n <span class=\"me-auto\">\n @if (captionTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: caption(), query: query() }\"></ng-container>\n } @else {\n {{ caption() }}\n }\n </span>\n\n @if (actionsTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: customActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (customActions().length) {\n <!-- The default: the actions the server declares for this type. The nav renders only when\n there are any, so a query without actions is pixel-identical to a plain caption.\n\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rule. -->\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-sm btn-outline-primary\"\n [disabled]=\"!grid()?.isActionEnabled(action)\" (click)=\"grid()?.onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n </div>\n </bs-card-header>\n\n <div class=\"p-3\">\n <spark-query-grid\n [queryId]=\"queryId()\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\"\n [reloadToken]=\"reloadToken()\"\n [data]=\"data()\"\n [search]=\"search()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n" }]
853
- }], propDecorators: { queryId: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryId", required: true }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], reloadToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "reloadToken", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], iconTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconTemplate", required: false }] }], captionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionTemplate", required: false }] }], actionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsTemplate", required: false }] }], error: [{ type: i0.Output, args: ["error"] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }], iconSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryIconDirective), { isSignal: true }] }], captionSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryCaptionDirective), { isSignal: true }] }], actionSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryActionsDirective), { isSignal: true }] }], grid: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SparkQueryGridComponent), { isSignal: true }] }] } });
934
+ args: [{ selector: 'spark-query-card', imports: [CommonModule, BsCardComponent, BsCardHeaderComponent, BsPriorityNavComponent, BsPriorityNavItemDirective, SparkQueryGridComponent, ResolveTranslationPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly \u2014 which\n is what the old `showCard=\"false\"` meant, expressed as a component choice rather than a flag.\n-->\n<bs-card class=\"d-block my-3\">\n <bs-card-header>\n <div class=\"d-flex align-items-center gap-2\">\n @if (iconTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: query() }\"></ng-container>\n }\n\n @if (actionsTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: customActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (customActions().length) {\n <!-- The default: the actions the server declares for this type. The nav renders only when\n there are any, so a query without actions is pixel-identical to a plain caption.\n\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rule. -->\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-sm btn-outline-primary\"\n [disabled]=\"!grid()?.isActionEnabled(action)\" (click)=\"grid()?.onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n\n <!--\n Caption last, actions first. The auto margin is conditional rather than fixed, and that is\n the point: with actions present `ms-auto` pushes the caption to the trailing edge, and with\n none it falls back to `me-auto` so a plain card looks exactly as it always did. A fixed\n `ms-auto` would have right-aligned the caption of every action-less query card in every app\n \u2014 a change nobody asked for, to fix a layout only some cards have.\n -->\n <span [class.ms-auto]=\"customActions().length\" [class.me-auto]=\"!customActions().length\">\n @if (captionTpl(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: caption(), query: query() }\"></ng-container>\n } @else {\n {{ caption() }}\n }\n </span>\n </div>\n </bs-card-header>\n\n <div class=\"p-3\">\n <spark-query-grid\n [queryId]=\"queryId()\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\"\n [reloadToken]=\"reloadToken()\"\n [data]=\"data()\"\n [search]=\"search()\"\n [rowRoute]=\"rowRoute()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n" }]
935
+ }], propDecorators: { queryId: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryId", required: true }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], reloadToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "reloadToken", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], iconTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconTemplate", required: false }] }], captionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionTemplate", required: false }] }], actionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsTemplate", required: false }] }], error: [{ type: i0.Output, args: ["error"] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }], rowRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowRoute", required: false }] }], iconSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryIconDirective), { isSignal: true }] }], captionSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryCaptionDirective), { isSignal: true }] }], actionSlots: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SparkQueryActionsDirective), { isSignal: true }] }], grid: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SparkQueryGridComponent), { isSignal: true }] }] } });
854
936
 
855
937
  /**
856
938
  * Generated bundle index. Do not edit.