@mintplayer/ng-spark 22.22.0 → 22.24.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.
- package/README.md +181 -0
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +308 -329
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-grid.mjs +2259 -1117
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-history.mjs +611 -0
- package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
- package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs +365 -753
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
- package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs +52 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +1122 -765
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +786 -410
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +292 -284
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +2187 -1225
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +516 -303
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +17 -55
- package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
- package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-shell.mjs +1013 -397
- package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark.mjs +13 -50
- package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
- package/package.json +17 -1
- package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
- package/types/mintplayer-ng-spark-client-operations.d.ts +133 -160
- package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
- package/types/mintplayer-ng-spark-grid.d.ts +885 -448
- package/types/mintplayer-ng-spark-history.d.ts +223 -0
- package/types/mintplayer-ng-spark-icon.d.ts +15 -13
- package/types/mintplayer-ng-spark-models.d.ts +624 -573
- package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
- package/types/mintplayer-ng-spark-panels.d.ts +108 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +120 -149
- package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
- package/types/mintplayer-ng-spark-po-detail.d.ts +177 -117
- package/types/mintplayer-ng-spark-po-edit.d.ts +64 -65
- package/types/mintplayer-ng-spark-po-form.d.ts +507 -371
- package/types/mintplayer-ng-spark-query-list.d.ts +118 -82
- package/types/mintplayer-ng-spark-renderers.d.ts +79 -85
- package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
- package/types/mintplayer-ng-spark-routes.d.ts +7 -11
- package/types/mintplayer-ng-spark-services.d.ts +377 -261
- package/types/mintplayer-ng-spark-shell.d.ts +209 -128
- package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
- package/types/mintplayer-ng-spark.d.ts +7 -12
|
@@ -1,1131 +1,2273 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import * as i0 from
|
|
4
|
-
import {
|
|
5
|
-
import { SPARK_ATTRIBUTE_RENDERERS, cellValue, withDeclaredInputs } from
|
|
6
|
-
import {
|
|
7
|
-
import * as i1 from
|
|
8
|
-
import { CommonModule, NgComponentOutlet } from
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import { BsDatatableColumnDirective, BsDatatableComponent, BsDatatableFilterPanelDirective, BsRowTemplateDirective, DatatableSettings } from "@mintplayer/ng-bootstrap/datatable";
|
|
2
|
+
import { ShowedOn, defaultQueryActions, filterQueryActions, hasShowedOnFlag, parseSelectionRule, selectionModeFor, valueFor } from "@mintplayer/ng-spark/models";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, Directive, Injectable, TemplateRef, computed, contentChildren, effect, inject, input, linkedSignal, model, output, signal, untracked, viewChild } from "@angular/core";
|
|
5
|
+
import { SPARK_ATTRIBUTE_RENDERERS, cellValue, withDeclaredInputs } from "@mintplayer/ng-spark/renderers";
|
|
6
|
+
import { SPARK_RETURN_URL_STATE_KEY, SparkLanguageService, SparkService } from "@mintplayer/ng-spark/services";
|
|
7
|
+
import * as i1$1 from "@angular/common";
|
|
8
|
+
import { CommonModule, NgComponentOutlet } from "@angular/common";
|
|
9
|
+
import { toSignal } from "@angular/core/rxjs-interop";
|
|
10
|
+
import * as i2 from "@angular/router";
|
|
11
|
+
import { NavigationEnd, Router, RouterModule } from "@angular/router";
|
|
12
|
+
import { filter, map } from "rxjs";
|
|
13
|
+
import { Color } from "@mintplayer/ng-bootstrap";
|
|
14
|
+
import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
|
|
15
|
+
import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
|
|
16
|
+
import { BsBadgeComponent } from "@mintplayer/ng-bootstrap/badge";
|
|
17
|
+
import { CdkConnectedOverlay, CdkOverlayOrigin } from "@angular/cdk/overlay";
|
|
18
|
+
import { BsDropdownItemDirective, BsDropdownMenuComponent } from "@mintplayer/ng-bootstrap/dropdown-menu";
|
|
19
|
+
import { SparkIconComponent } from "@mintplayer/ng-spark/icon";
|
|
20
|
+
import { SparkQueryRefreshService } from "@mintplayer/ng-spark/client-operations";
|
|
21
|
+
import { QueryCellValuePipe, QueryReferenceChipsPipe, ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
|
|
22
|
+
import { SparkAttributeDescriptionComponent } from "@mintplayer/ng-spark/attribute-description";
|
|
23
|
+
import { SparkColumnFilterPanelComponent } from "@mintplayer/ng-spark/column-filter";
|
|
24
|
+
import { BsCardComponent, BsCardHeaderComponent } from "@mintplayer/ng-bootstrap/card";
|
|
25
|
+
import { BsPriorityNavComponent, BsPriorityNavItemDirective } from "@mintplayer/ng-bootstrap/priority-nav";
|
|
26
|
+
import * as i1 from "@angular/forms";
|
|
27
|
+
import { FormsModule } from "@angular/forms";
|
|
28
|
+
import { BsFormComponent, BsFormControlDirective } from "@mintplayer/ng-bootstrap/form";
|
|
29
|
+
import { BsInputGroupComponent } from "@mintplayer/ng-bootstrap/input-group";
|
|
30
|
+
const SPARK_GRID_PAGE_SIZES = [
|
|
31
|
+
10,
|
|
32
|
+
25,
|
|
33
|
+
50
|
|
34
|
+
];
|
|
30
35
|
function visibleGridAttributes(entityType) {
|
|
31
|
-
|
|
32
|
-
.filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
|
|
33
|
-
.sort((a, b) => a.order - b.order) ?? [];
|
|
36
|
+
return entityType?.attributes.filter((a) => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query)).sort((a, b) => a.order - b.order) ?? [];
|
|
34
37
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Initial datatable settings for a query, seeded with the query's declared sort.
|
|
37
|
-
*
|
|
38
|
-
* The datatable owns paging and sorting from here on and calls the fetch callback per page; this
|
|
39
|
-
* only decides where it starts.
|
|
40
|
-
*/
|
|
41
38
|
function initialGridSettings(query) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
const sortColumns = (query?.sortColumns || []).map((sc) => ({
|
|
40
|
+
property: sc.property,
|
|
41
|
+
direction: sc.direction === "desc" ? "descending" : "ascending"
|
|
42
|
+
}));
|
|
43
|
+
return new DatatableSettings({
|
|
44
|
+
perPage: {
|
|
45
|
+
values: SPARK_GRID_PAGE_SIZES,
|
|
46
|
+
selected: SPARK_GRID_PAGE_SIZES[0]
|
|
47
|
+
},
|
|
48
|
+
page: {
|
|
49
|
+
values: [1],
|
|
50
|
+
selected: 1
|
|
51
|
+
},
|
|
52
|
+
sortColumns
|
|
53
|
+
});
|
|
53
54
|
}
|
|
54
|
-
/** Whether a query renders as a virtual-scrolling grid rather than a paged one. */
|
|
55
55
|
function isVirtualScrollingQuery(query) {
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Renderer lookup and lookup-reference loading for a Spark grid.
|
|
61
|
-
*
|
|
62
|
-
* This was extracted when `spark-query-list` and `spark-sub-query` were two components holding
|
|
63
|
-
* byte-identical copies of it — around 120 lines between them. That duplication was not a
|
|
64
|
-
* tidiness complaint: it produced drift. The two copies disagreed about `[indeterminate]`, about
|
|
65
|
-
* resetting permission state, about whether a fetch failure surfaces or is swallowed, and about
|
|
66
|
-
* virtual-scroll sizing — four user-visible bugs, each fixed on one side and not the other.
|
|
67
|
-
*
|
|
68
|
-
* There is now one grid, {@link SparkQueryGridComponent}, so the drift cannot recur. This stays
|
|
69
|
-
* separate because it is stateless service-shaped logic rather than view state, and because it is
|
|
70
|
-
* what a custom grid would need in order to render Spark cells at all.
|
|
71
|
-
*/
|
|
72
|
-
class SparkGridRenderers {
|
|
73
|
-
registry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
74
|
-
sparkService = inject(SparkService);
|
|
75
|
-
/** The registered column component for a column, or null to fall back to the default cell. */
|
|
76
|
-
columnComponentFor(column) {
|
|
77
|
-
if (!column.renderer)
|
|
78
|
-
return null;
|
|
79
|
-
return this.registry.find(r => r.name === column.renderer)?.columnComponent ?? null;
|
|
80
|
-
}
|
|
81
|
-
/** The registered filter-label function for a column, or null to use the server-computed text. */
|
|
82
|
-
filterLabelFor(column) {
|
|
83
|
-
if (!column.renderer)
|
|
84
|
-
return null;
|
|
85
|
-
return this.registry.find(r => r.name === column.renderer)?.filterLabel ?? null;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Inputs for a query-grid cell renderer, filtered to what the component actually declares —
|
|
89
|
-
* `NgComponentOutlet` throws on an input the target does not have, which is what lets every
|
|
90
|
-
* member of the renderer contract be optional.
|
|
91
|
-
*/
|
|
92
|
-
columnInputsFor(component, item, column) {
|
|
93
|
-
return this.cellInputsFor(component, cellValue(valueFor(item, column.name)), column, item);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The same contract for a row that is not a query result — an AsDetail sub-table row, which is
|
|
97
|
-
* a plain dictionary of embedded values with no id and no column metadata of its own, so its
|
|
98
|
-
* value cannot be read the way {@link columnInputsFor} reads one.
|
|
99
|
-
*
|
|
100
|
-
* Only the extraction differs; the renderer contract is identical, and stating it once is what
|
|
101
|
-
* stops the two from drifting the way the cell markup already had.
|
|
102
|
-
*/
|
|
103
|
-
cellInputsFor(component, value, column, item) {
|
|
104
|
-
return withDeclaredInputs(component, {
|
|
105
|
-
value,
|
|
106
|
-
column,
|
|
107
|
-
options: column.rendererOptions,
|
|
108
|
-
item,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Loads every lookup reference the visible columns need, in one pass.
|
|
113
|
-
*
|
|
114
|
-
* Returns an empty map rather than throwing when there are none, so callers never branch on it.
|
|
115
|
-
*/
|
|
116
|
-
async loadLookupOptions(columns) {
|
|
117
|
-
const lookupColumns = columns.filter(c => c.lookupReferenceType);
|
|
118
|
-
if (lookupColumns.length === 0)
|
|
119
|
-
return {};
|
|
120
|
-
const names = [...new Set(lookupColumns.map(c => c.lookupReferenceType))];
|
|
121
|
-
const entries = await Promise.all(names.map(async (name) => [name, await this.sparkService.getLookupReference(name)]));
|
|
122
|
-
return entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
|
|
123
|
-
}
|
|
124
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkGridRenderers, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
125
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkGridRenderers, providedIn: 'root' });
|
|
56
|
+
return query?.renderMode === "VirtualScrolling";
|
|
126
57
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
58
|
+
var SparkGridRenderers = class SparkGridRenderers {
|
|
59
|
+
registry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
60
|
+
sparkService = inject(SparkService);
|
|
61
|
+
columnComponentFor(column) {
|
|
62
|
+
if (!column.renderer) return null;
|
|
63
|
+
return this.registry.find((r) => r.name === column.renderer)?.columnComponent ?? null;
|
|
64
|
+
}
|
|
65
|
+
filterLabelFor(column) {
|
|
66
|
+
if (!column.renderer) return null;
|
|
67
|
+
return this.registry.find((r) => r.name === column.renderer)?.filterLabel ?? null;
|
|
68
|
+
}
|
|
69
|
+
columnInputsFor(component, item, column) {
|
|
70
|
+
return this.cellInputsFor(component, cellValue(valueFor(item, column.name)), column, item);
|
|
71
|
+
}
|
|
72
|
+
cellInputsFor(component, value, column, item) {
|
|
73
|
+
return withDeclaredInputs(component, {
|
|
74
|
+
value,
|
|
75
|
+
column,
|
|
76
|
+
options: column.rendererOptions,
|
|
77
|
+
item
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async loadLookupOptions(columns) {
|
|
81
|
+
const lookupColumns = columns.filter((c) => c.lookupReferenceType);
|
|
82
|
+
if (lookupColumns.length === 0) return {};
|
|
83
|
+
const names = [...new Set(lookupColumns.map((c) => c.lookupReferenceType))];
|
|
84
|
+
return (await Promise.all(names.map(async (name) => [name, await this.sparkService.getLookupReference(name)]))).reduce((acc, [k, v]) => ({
|
|
85
|
+
...acc,
|
|
86
|
+
[k]: v
|
|
87
|
+
}), {});
|
|
88
|
+
}
|
|
89
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
90
|
+
minVersion: "12.0.0",
|
|
91
|
+
version: "22.2.0",
|
|
92
|
+
ngImport: i0,
|
|
93
|
+
type: SparkGridRenderers,
|
|
94
|
+
deps: [],
|
|
95
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
96
|
+
});
|
|
97
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
98
|
+
minVersion: "12.0.0",
|
|
99
|
+
version: "22.2.0",
|
|
100
|
+
ngImport: i0,
|
|
101
|
+
type: SparkGridRenderers,
|
|
102
|
+
providedIn: "root"
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
106
|
+
minVersion: "12.0.0",
|
|
107
|
+
version: "22.2.0",
|
|
108
|
+
ngImport: i0,
|
|
109
|
+
type: SparkGridRenderers,
|
|
110
|
+
decorators: [{
|
|
111
|
+
type: Injectable,
|
|
112
|
+
args: [{ providedIn: "root" }]
|
|
113
|
+
}]
|
|
114
|
+
});
|
|
172
115
|
function slotFor(slots, query) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
target.localeCompare(query?.id ?? '', undefined, { sensitivity: 'accent' }) === 0);
|
|
177
|
-
// A targeted slot wins over the catch-all even when it is declared later: "this one query"
|
|
178
|
-
// is the more specific statement, and declaration order in a template is not a priority.
|
|
179
|
-
return slots.find(s => matches(s.forQuery())) ?? slots.find(s => !s.forQuery()) ?? null;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* The icon at the header's leading edge. No default: nothing in the model describes one.
|
|
183
|
-
*
|
|
184
|
-
* ```html
|
|
185
|
-
* <spark-query-card [queryId]="'cars'">
|
|
186
|
-
* <spark-icon *sparkQueryIcon name="car-front" />
|
|
187
|
-
* </spark-query-card>
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
class SparkQueryIconDirective {
|
|
191
|
-
templateRef = inject(TemplateRef);
|
|
192
|
-
/** Query alias or id this slot applies to. Empty targets every query on the page. */
|
|
193
|
-
forQuery = input('', { ...(ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {}), alias: 'sparkQueryIcon' });
|
|
194
|
-
static ngTemplateContextGuard(_dir, ctx) {
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
198
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: SparkQueryIconDirective, isStandalone: true, selector: "[sparkQueryIcon]", inputs: { forQuery: { classPropertyName: "forQuery", publicName: "sparkQueryIcon", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
199
|
-
}
|
|
200
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryIconDirective, decorators: [{
|
|
201
|
-
type: Directive,
|
|
202
|
-
args: [{ selector: '[sparkQueryIcon]' }]
|
|
203
|
-
}], propDecorators: { forQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkQueryIcon", required: false }] }] } });
|
|
204
|
-
/**
|
|
205
|
-
* The header caption. Defaults to the query's translated description, falling back to its name.
|
|
206
|
-
*
|
|
207
|
-
* The context carries that resolved default as `$implicit`, so a host can decorate it — add a
|
|
208
|
-
* count, a badge — without re-resolving the translation itself.
|
|
209
|
-
*/
|
|
210
|
-
class SparkQueryCaptionDirective {
|
|
211
|
-
templateRef = inject(TemplateRef);
|
|
212
|
-
forQuery = input('', { ...(ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {}), alias: 'sparkQueryCaption' });
|
|
213
|
-
static ngTemplateContextGuard(_dir, ctx) {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryCaptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
217
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: SparkQueryCaptionDirective, isStandalone: true, selector: "[sparkQueryCaption]", inputs: { forQuery: { classPropertyName: "forQuery", publicName: "sparkQueryCaption", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
218
|
-
}
|
|
219
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryCaptionDirective, decorators: [{
|
|
220
|
-
type: Directive,
|
|
221
|
-
args: [{ selector: '[sparkQueryCaption]' }]
|
|
222
|
-
}], propDecorators: { forQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkQueryCaption", required: false }] }] } });
|
|
223
|
-
/**
|
|
224
|
-
* The buttons at the header's trailing edge. Defaults to the server-declared custom actions.
|
|
225
|
-
*
|
|
226
|
-
* The context carries those actions and the current selection, so a host that wants its own
|
|
227
|
-
* buttons ALONGSIDE the server's can render both rather than choosing. Without that, overriding
|
|
228
|
-
* this slot to add one button would silently drop every action the type declares — and the
|
|
229
|
-
* server's actions are the ones carrying `selectionRule` and the permission filter.
|
|
230
|
-
*
|
|
231
|
-
* ```html
|
|
232
|
-
* <ng-container *sparkQueryActions="let actions; selection as rows">
|
|
233
|
-
* <button (click)="export(rows)">Export</button>
|
|
234
|
-
* </ng-container>
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
class SparkQueryActionsDirective {
|
|
238
|
-
templateRef = inject(TemplateRef);
|
|
239
|
-
forQuery = input('', { ...(ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {}), alias: 'sparkQueryActions' });
|
|
240
|
-
static ngTemplateContextGuard(_dir, ctx) {
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
244
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: SparkQueryActionsDirective, isStandalone: true, selector: "[sparkQueryActions]", inputs: { forQuery: { classPropertyName: "forQuery", publicName: "sparkQueryActions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
116
|
+
if (!slots.length) return null;
|
|
117
|
+
const matches = (target) => !!target && (target.localeCompare(query?.alias ?? "", void 0, { sensitivity: "accent" }) === 0 || target.localeCompare(query?.id ?? "", void 0, { sensitivity: "accent" }) === 0);
|
|
118
|
+
return slots.find((s) => matches(s.forQuery())) ?? slots.find((s) => !s.forQuery()) ?? null;
|
|
245
119
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
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 [distincts]=\"distinctsFn\"\n (filterChange)=\"onFilterChange($event)\"\n [(settings)]=\"settings\">\n @for (col of visibleColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override \u2192 attribute \u2192\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n \u26A0\uFE0F `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view \u2014 while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n\n <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 \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", "distincts", "labels", "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", "filterChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable", "bsDatatableColumnFilterable", "bsDatatableColumnFilterActive", "bsDatatableColumnFilterSummary", "bsDatatableColumnFilterSelection", "bsDatatableColumnFilterMode", "bsDatatableColumnFilterInputType", "bsDatatableColumnFilterOperators"] }, { kind: "directive", type: BsDatatableFilterPanelDirective, selector: "[bsDatatableFilterPanel]" }, { 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: "component", type: SparkColumnFilterPanelComponent, selector: "spark-column-filter-panel", inputs: ["column", "values", "ctx", "filterLabel"] }, { 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 });
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
120
|
+
var SparkQueryIconDirective = class SparkQueryIconDirective {
|
|
121
|
+
templateRef = inject(TemplateRef);
|
|
122
|
+
forQuery = input("", {
|
|
123
|
+
...ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {},
|
|
124
|
+
alias: "sparkQueryIcon"
|
|
125
|
+
});
|
|
126
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
130
|
+
minVersion: "12.0.0",
|
|
131
|
+
version: "22.2.0",
|
|
132
|
+
ngImport: i0,
|
|
133
|
+
type: SparkQueryIconDirective,
|
|
134
|
+
deps: [],
|
|
135
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
136
|
+
});
|
|
137
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
138
|
+
minVersion: "17.1.0",
|
|
139
|
+
version: "22.2.0",
|
|
140
|
+
type: SparkQueryIconDirective,
|
|
141
|
+
isStandalone: true,
|
|
142
|
+
selector: "[sparkQueryIcon]",
|
|
143
|
+
inputs: { forQuery: {
|
|
144
|
+
classPropertyName: "forQuery",
|
|
145
|
+
publicName: "sparkQueryIcon",
|
|
146
|
+
isSignal: true,
|
|
147
|
+
isRequired: false,
|
|
148
|
+
transformFunction: null
|
|
149
|
+
} },
|
|
150
|
+
ngImport: i0
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
154
|
+
minVersion: "12.0.0",
|
|
155
|
+
version: "22.2.0",
|
|
156
|
+
ngImport: i0,
|
|
157
|
+
type: SparkQueryIconDirective,
|
|
158
|
+
decorators: [{
|
|
159
|
+
type: Directive,
|
|
160
|
+
args: [{ selector: "[sparkQueryIcon]" }]
|
|
161
|
+
}],
|
|
162
|
+
propDecorators: { forQuery: [{
|
|
163
|
+
type: i0.Input,
|
|
164
|
+
args: [{
|
|
165
|
+
isSignal: true,
|
|
166
|
+
alias: "sparkQueryIcon",
|
|
167
|
+
required: false
|
|
168
|
+
}]
|
|
169
|
+
}] }
|
|
170
|
+
});
|
|
171
|
+
var SparkQueryCaptionDirective = class SparkQueryCaptionDirective {
|
|
172
|
+
templateRef = inject(TemplateRef);
|
|
173
|
+
forQuery = input("", {
|
|
174
|
+
...ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {},
|
|
175
|
+
alias: "sparkQueryCaption"
|
|
176
|
+
});
|
|
177
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
181
|
+
minVersion: "12.0.0",
|
|
182
|
+
version: "22.2.0",
|
|
183
|
+
ngImport: i0,
|
|
184
|
+
type: SparkQueryCaptionDirective,
|
|
185
|
+
deps: [],
|
|
186
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
187
|
+
});
|
|
188
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
189
|
+
minVersion: "17.1.0",
|
|
190
|
+
version: "22.2.0",
|
|
191
|
+
type: SparkQueryCaptionDirective,
|
|
192
|
+
isStandalone: true,
|
|
193
|
+
selector: "[sparkQueryCaption]",
|
|
194
|
+
inputs: { forQuery: {
|
|
195
|
+
classPropertyName: "forQuery",
|
|
196
|
+
publicName: "sparkQueryCaption",
|
|
197
|
+
isSignal: true,
|
|
198
|
+
isRequired: false,
|
|
199
|
+
transformFunction: null
|
|
200
|
+
} },
|
|
201
|
+
ngImport: i0
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
205
|
+
minVersion: "12.0.0",
|
|
206
|
+
version: "22.2.0",
|
|
207
|
+
ngImport: i0,
|
|
208
|
+
type: SparkQueryCaptionDirective,
|
|
209
|
+
decorators: [{
|
|
210
|
+
type: Directive,
|
|
211
|
+
args: [{ selector: "[sparkQueryCaption]" }]
|
|
212
|
+
}],
|
|
213
|
+
propDecorators: { forQuery: [{
|
|
214
|
+
type: i0.Input,
|
|
215
|
+
args: [{
|
|
216
|
+
isSignal: true,
|
|
217
|
+
alias: "sparkQueryCaption",
|
|
218
|
+
required: false
|
|
219
|
+
}]
|
|
220
|
+
}] }
|
|
221
|
+
});
|
|
222
|
+
var SparkQueryActionsDirective = class SparkQueryActionsDirective {
|
|
223
|
+
templateRef = inject(TemplateRef);
|
|
224
|
+
forQuery = input("", {
|
|
225
|
+
...ngDevMode ? { debugName: "forQuery" } : /* istanbul ignore next */ {},
|
|
226
|
+
alias: "sparkQueryActions"
|
|
227
|
+
});
|
|
228
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
232
|
+
minVersion: "12.0.0",
|
|
233
|
+
version: "22.2.0",
|
|
234
|
+
ngImport: i0,
|
|
235
|
+
type: SparkQueryActionsDirective,
|
|
236
|
+
deps: [],
|
|
237
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
238
|
+
});
|
|
239
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
240
|
+
minVersion: "17.1.0",
|
|
241
|
+
version: "22.2.0",
|
|
242
|
+
type: SparkQueryActionsDirective,
|
|
243
|
+
isStandalone: true,
|
|
244
|
+
selector: "[sparkQueryActions]",
|
|
245
|
+
inputs: { forQuery: {
|
|
246
|
+
classPropertyName: "forQuery",
|
|
247
|
+
publicName: "sparkQueryActions",
|
|
248
|
+
isSignal: true,
|
|
249
|
+
isRequired: false,
|
|
250
|
+
transformFunction: null
|
|
251
|
+
} },
|
|
252
|
+
ngImport: i0
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
256
|
+
minVersion: "12.0.0",
|
|
257
|
+
version: "22.2.0",
|
|
258
|
+
ngImport: i0,
|
|
259
|
+
type: SparkQueryActionsDirective,
|
|
260
|
+
decorators: [{
|
|
261
|
+
type: Directive,
|
|
262
|
+
args: [{ selector: "[sparkQueryActions]" }]
|
|
263
|
+
}],
|
|
264
|
+
propDecorators: { forQuery: [{
|
|
265
|
+
type: i0.Input,
|
|
266
|
+
args: [{
|
|
267
|
+
isSignal: true,
|
|
268
|
+
alias: "sparkQueryActions",
|
|
269
|
+
required: false
|
|
270
|
+
}]
|
|
271
|
+
}] }
|
|
272
|
+
});
|
|
273
|
+
var SparkQuerySlotContext = class {
|
|
274
|
+
$implicit = null;
|
|
275
|
+
};
|
|
276
|
+
var SparkQueryCaptionContext = class {
|
|
277
|
+
$implicit = "";
|
|
278
|
+
query = null;
|
|
279
|
+
};
|
|
280
|
+
var SparkQueryActionsContext = class {
|
|
281
|
+
$implicit = [];
|
|
282
|
+
selection = [];
|
|
283
|
+
query = null;
|
|
284
|
+
};
|
|
285
|
+
var SparkGridCellComponent = class SparkGridCellComponent {
|
|
286
|
+
renderers = inject(SparkGridRenderers);
|
|
287
|
+
column = input.required(...ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []);
|
|
288
|
+
display = input(null, ...ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []);
|
|
289
|
+
rendererValue = input(null, ...ngDevMode ? [{ debugName: "rendererValue" }] : /* istanbul ignore next */ []);
|
|
290
|
+
item = input(null, ...ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []);
|
|
291
|
+
chips = input([], ...ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []);
|
|
292
|
+
link = input(null, ...ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []);
|
|
293
|
+
rendererComponent = computed(() => this.renderers.columnComponentFor(this.column()), ...ngDevMode ? [{ debugName: "rendererComponent" }] : /* istanbul ignore next */ []);
|
|
294
|
+
rendererInputs = computed(() => {
|
|
295
|
+
const component = this.rendererComponent();
|
|
296
|
+
return component ? this.renderers.cellInputsFor(component, this.rendererValue(), this.column(), this.item()) : {};
|
|
297
|
+
}, ...ngDevMode ? [{ debugName: "rendererInputs" }] : /* istanbul ignore next */ []);
|
|
298
|
+
isUnsetBoolean = computed(() => this.display() == null, ...ngDevMode ? [{ debugName: "isUnsetBoolean" }] : /* istanbul ignore next */ []);
|
|
299
|
+
dateValue = computed(() => {
|
|
300
|
+
const value = this.display();
|
|
301
|
+
return value instanceof Date && !Number.isNaN(value.getTime()) ? value : null;
|
|
302
|
+
}, ...ngDevMode ? [{ debugName: "dateValue" }] : /* istanbul ignore next */ []);
|
|
303
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
304
|
+
minVersion: "12.0.0",
|
|
305
|
+
version: "22.2.0",
|
|
306
|
+
ngImport: i0,
|
|
307
|
+
type: SparkGridCellComponent,
|
|
308
|
+
deps: [],
|
|
309
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
310
|
+
});
|
|
311
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
312
|
+
minVersion: "17.0.0",
|
|
313
|
+
version: "22.2.0",
|
|
314
|
+
type: SparkGridCellComponent,
|
|
315
|
+
isStandalone: true,
|
|
316
|
+
selector: "spark-grid-cell",
|
|
317
|
+
inputs: {
|
|
318
|
+
column: {
|
|
319
|
+
classPropertyName: "column",
|
|
320
|
+
publicName: "column",
|
|
321
|
+
isSignal: true,
|
|
322
|
+
isRequired: true,
|
|
323
|
+
transformFunction: null
|
|
324
|
+
},
|
|
325
|
+
display: {
|
|
326
|
+
classPropertyName: "display",
|
|
327
|
+
publicName: "display",
|
|
328
|
+
isSignal: true,
|
|
329
|
+
isRequired: false,
|
|
330
|
+
transformFunction: null
|
|
331
|
+
},
|
|
332
|
+
rendererValue: {
|
|
333
|
+
classPropertyName: "rendererValue",
|
|
334
|
+
publicName: "rendererValue",
|
|
335
|
+
isSignal: true,
|
|
336
|
+
isRequired: false,
|
|
337
|
+
transformFunction: null
|
|
338
|
+
},
|
|
339
|
+
item: {
|
|
340
|
+
classPropertyName: "item",
|
|
341
|
+
publicName: "item",
|
|
342
|
+
isSignal: true,
|
|
343
|
+
isRequired: false,
|
|
344
|
+
transformFunction: null
|
|
345
|
+
},
|
|
346
|
+
chips: {
|
|
347
|
+
classPropertyName: "chips",
|
|
348
|
+
publicName: "chips",
|
|
349
|
+
isSignal: true,
|
|
350
|
+
isRequired: false,
|
|
351
|
+
transformFunction: null
|
|
352
|
+
},
|
|
353
|
+
link: {
|
|
354
|
+
classPropertyName: "link",
|
|
355
|
+
publicName: "link",
|
|
356
|
+
isSignal: true,
|
|
357
|
+
isRequired: false,
|
|
358
|
+
transformFunction: null
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
ngImport: i0,
|
|
362
|
+
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 === 'date' || column().dataType === 'datetime') {\n <!--\n Formatted in ONE place for both grids. Before this, a date column printed whatever its pipe\n produced, which was the raw ISO string — `2026-09-07T13:25:57.3119825Z` in a table cell. Both\n cell pipes now hand over a real Date, and `date:` accepts a string too, so an unparseable value\n that fell back to text still renders as that text rather than blank.\n -->\n @if (dateValue(); as when) {\n {{ when | date:(column().dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ display() }}\n }\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Shares `.spark-color-swatch` with the detail page, which is where the sizing already\n lived. It used to be inline here because the grid rendered inside <mp-datatable>'s\n shadow root, where neither this component's scoped rules nor Bootstrap's arrived;\n ng-bootstrap 22.18.0 moved the datatable to the light DOM, so the class reaches it. -->\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"></span>\n }\n} @else if (column().dataType === 'image') {\n @if (display(); as src) {\n <!-- `alt` is empty on purpose — the cell is decorative next to the row's own labelled\n columns, and a filename read aloud is worse than nothing. -->\n <img [src]=\"src\" alt=\"\" loading=\"lazy\" class=\"spark-grid-image align-middle rounded\">\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",
|
|
363
|
+
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}.spark-grid-image{max-height:2.5em;max-width:100%;object-fit:contain}\n"],
|
|
364
|
+
dependencies: [
|
|
365
|
+
{
|
|
366
|
+
kind: "ngmodule",
|
|
367
|
+
type: CommonModule
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
kind: "directive",
|
|
371
|
+
type: i1$1.NgComponentOutlet,
|
|
372
|
+
selector: "[ngComponentOutlet]",
|
|
373
|
+
inputs: [
|
|
374
|
+
"ngComponentOutlet",
|
|
375
|
+
"ngComponentOutletInputs",
|
|
376
|
+
"ngComponentOutletInjector",
|
|
377
|
+
"ngComponentOutletEnvironmentInjector",
|
|
378
|
+
"ngComponentOutletContent",
|
|
379
|
+
"ngComponentOutletNgModule"
|
|
380
|
+
],
|
|
381
|
+
exportAs: ["ngComponentOutlet"]
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
kind: "ngmodule",
|
|
385
|
+
type: RouterModule
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
kind: "directive",
|
|
389
|
+
type: i2.RouterLink,
|
|
390
|
+
selector: "[routerLink]",
|
|
391
|
+
inputs: [
|
|
392
|
+
"target",
|
|
393
|
+
"queryParams",
|
|
394
|
+
"fragment",
|
|
395
|
+
"queryParamsHandling",
|
|
396
|
+
"state",
|
|
397
|
+
"info",
|
|
398
|
+
"relativeTo",
|
|
399
|
+
"preserveFragment",
|
|
400
|
+
"skipLocationChange",
|
|
401
|
+
"replaceUrl",
|
|
402
|
+
"browserUrl",
|
|
403
|
+
"routerLink"
|
|
404
|
+
]
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
kind: "pipe",
|
|
408
|
+
type: i1$1.DatePipe,
|
|
409
|
+
name: "date"
|
|
410
|
+
}
|
|
411
|
+
],
|
|
412
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
416
|
+
minVersion: "12.0.0",
|
|
417
|
+
version: "22.2.0",
|
|
418
|
+
ngImport: i0,
|
|
419
|
+
type: SparkGridCellComponent,
|
|
420
|
+
decorators: [{
|
|
421
|
+
type: Component,
|
|
422
|
+
args: [{
|
|
423
|
+
selector: "spark-grid-cell",
|
|
424
|
+
imports: [
|
|
425
|
+
CommonModule,
|
|
426
|
+
NgComponentOutlet,
|
|
427
|
+
RouterModule
|
|
428
|
+
],
|
|
429
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
430
|
+
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 === 'date' || column().dataType === 'datetime') {\n <!--\n Formatted in ONE place for both grids. Before this, a date column printed whatever its pipe\n produced, which was the raw ISO string — `2026-09-07T13:25:57.3119825Z` in a table cell. Both\n cell pipes now hand over a real Date, and `date:` accepts a string too, so an unparseable value\n that fell back to text still renders as that text rather than blank.\n -->\n @if (dateValue(); as when) {\n {{ when | date:(column().dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ display() }}\n }\n} @else if (column().dataType === 'color') {\n @if (display(); as colorVal) {\n <!-- Shares `.spark-color-swatch` with the detail page, which is where the sizing already\n lived. It used to be inline here because the grid rendered inside <mp-datatable>'s\n shadow root, where neither this component's scoped rules nor Bootstrap's arrived;\n ng-bootstrap 22.18.0 moved the datatable to the light DOM, so the class reaches it. -->\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded\"\n [style.background-color]=\"colorVal\"></span>\n }\n} @else if (column().dataType === 'image') {\n @if (display(); as src) {\n <!-- `alt` is empty on purpose — the cell is decorative next to the row's own labelled\n columns, and a filename read aloud is worse than nothing. -->\n <img [src]=\"src\" alt=\"\" loading=\"lazy\" class=\"spark-grid-image align-middle rounded\">\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",
|
|
431
|
+
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}.spark-grid-image{max-height:2.5em;max-width:100%;object-fit:contain}\n"]
|
|
432
|
+
}]
|
|
433
|
+
}],
|
|
434
|
+
propDecorators: {
|
|
435
|
+
column: [{
|
|
436
|
+
type: i0.Input,
|
|
437
|
+
args: [{
|
|
438
|
+
isSignal: true,
|
|
439
|
+
alias: "column",
|
|
440
|
+
required: true
|
|
441
|
+
}]
|
|
442
|
+
}],
|
|
443
|
+
display: [{
|
|
444
|
+
type: i0.Input,
|
|
445
|
+
args: [{
|
|
446
|
+
isSignal: true,
|
|
447
|
+
alias: "display",
|
|
448
|
+
required: false
|
|
449
|
+
}]
|
|
450
|
+
}],
|
|
451
|
+
rendererValue: [{
|
|
452
|
+
type: i0.Input,
|
|
453
|
+
args: [{
|
|
454
|
+
isSignal: true,
|
|
455
|
+
alias: "rendererValue",
|
|
456
|
+
required: false
|
|
457
|
+
}]
|
|
458
|
+
}],
|
|
459
|
+
item: [{
|
|
460
|
+
type: i0.Input,
|
|
461
|
+
args: [{
|
|
462
|
+
isSignal: true,
|
|
463
|
+
alias: "item",
|
|
464
|
+
required: false
|
|
465
|
+
}]
|
|
466
|
+
}],
|
|
467
|
+
chips: [{
|
|
468
|
+
type: i0.Input,
|
|
469
|
+
args: [{
|
|
470
|
+
isSignal: true,
|
|
471
|
+
alias: "chips",
|
|
472
|
+
required: false
|
|
473
|
+
}]
|
|
474
|
+
}],
|
|
475
|
+
link: [{
|
|
476
|
+
type: i0.Input,
|
|
477
|
+
args: [{
|
|
478
|
+
isSignal: true,
|
|
479
|
+
alias: "link",
|
|
480
|
+
required: false
|
|
481
|
+
}]
|
|
482
|
+
}]
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
var SparkQueryGridComponent = class SparkQueryGridComponent {
|
|
486
|
+
sparkService = inject(SparkService);
|
|
487
|
+
gridRenderers = inject(SparkGridRenderers);
|
|
488
|
+
queryRefresh = inject(SparkQueryRefreshService);
|
|
489
|
+
lang = inject(SparkLanguageService);
|
|
490
|
+
queryId = input.required(...ngDevMode ? [{ debugName: "queryId" }] : /* istanbul ignore next */ []);
|
|
491
|
+
parentId = input("", ...ngDevMode ? [{ debugName: "parentId" }] : /* istanbul ignore next */ []);
|
|
492
|
+
parentType = input("", ...ngDevMode ? [{ debugName: "parentType" }] : /* istanbul ignore next */ []);
|
|
493
|
+
data = input(null, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
|
|
494
|
+
search = input("", ...ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []);
|
|
495
|
+
columns = input(null, ...ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []);
|
|
496
|
+
reloadToken = input(null, ...ngDevMode ? [{ debugName: "reloadToken" }] : /* istanbul ignore next */ []);
|
|
497
|
+
deleted = input(null, ...ngDevMode ? [{ debugName: "deleted" }] : /* istanbul ignore next */ []);
|
|
498
|
+
effectiveDeleted = computed(() => {
|
|
499
|
+
const mode = this.deleted();
|
|
500
|
+
return mode && mode !== "exclude" ? mode : void 0;
|
|
501
|
+
}, ...ngDevMode ? [{ debugName: "effectiveDeleted" }] : /* istanbul ignore next */ []);
|
|
502
|
+
parentDeleted = input(false, ...ngDevMode ? [{ debugName: "parentDeleted" }] : /* istanbul ignore next */ []);
|
|
503
|
+
recycleBin = computed(() => this.effectiveDeleted() === "only" || this.parentDeleted(), ...ngDevMode ? [{ debugName: "recycleBin" }] : /* istanbul ignore next */ []);
|
|
504
|
+
rowQueryParams = computed(() => {
|
|
505
|
+
const mode = this.effectiveDeleted();
|
|
506
|
+
return mode ? { deleted: mode } : null;
|
|
507
|
+
}, ...ngDevMode ? [{ debugName: "rowQueryParams" }] : /* istanbul ignore next */ []);
|
|
508
|
+
router = inject(Router);
|
|
509
|
+
currentUrl = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map(() => this.router.url)), { initialValue: this.router.url });
|
|
510
|
+
rowLinkState = computed(() => ({ [SPARK_RETURN_URL_STATE_KEY]: this.currentUrl() }), ...ngDevMode ? [{ debugName: "rowLinkState" }] : /* istanbul ignore next */ []);
|
|
511
|
+
onRowClick(event) {
|
|
512
|
+
if (this.selectionMode() === "none") this.openRow(event);
|
|
513
|
+
}
|
|
514
|
+
onRowDblClick(event) {
|
|
515
|
+
if (this.selectionMode() !== "none") this.openRow(event);
|
|
516
|
+
}
|
|
517
|
+
openRow(event) {
|
|
518
|
+
const row = event.row;
|
|
519
|
+
const original = event.originalEvent;
|
|
520
|
+
if ((original?.target)?.closest?.("a, button, input, select, textarea, label, [role=\"button\"]")) return;
|
|
521
|
+
if (original && (original.ctrlKey || original.metaKey || original.shiftKey || original.button === 1)) return;
|
|
522
|
+
if (!row || !this.canRead()) return;
|
|
523
|
+
const route = this.rowRouteFor(row);
|
|
524
|
+
if (!route) return;
|
|
525
|
+
this.rowClicked.emit(row);
|
|
526
|
+
this.router.navigate(route, {
|
|
527
|
+
queryParams: this.rowQueryParams() ?? void 0,
|
|
528
|
+
state: this.rowLinkState()
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
settings = model(new DatatableSettings({
|
|
532
|
+
perPage: {
|
|
533
|
+
values: SPARK_GRID_PAGE_SIZES,
|
|
534
|
+
selected: SPARK_GRID_PAGE_SIZES[0]
|
|
535
|
+
},
|
|
536
|
+
page: {
|
|
537
|
+
values: [1],
|
|
538
|
+
selected: 1
|
|
539
|
+
},
|
|
540
|
+
sortColumns: []
|
|
541
|
+
}), ...ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []);
|
|
542
|
+
selection = model([], ...ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []);
|
|
543
|
+
rowRoute = input(null, ...ngDevMode ? [{ debugName: "rowRoute" }] : /* istanbul ignore next */ []);
|
|
544
|
+
selectionModeSetting = input(null, ...ngDevMode ? [{ debugName: "selectionModeSetting" }] : /* istanbul ignore next */ []);
|
|
545
|
+
error = output();
|
|
546
|
+
rowClicked = output();
|
|
547
|
+
customActionExecuted = output();
|
|
548
|
+
createClicked = output();
|
|
549
|
+
colors = Color;
|
|
550
|
+
rowRouteFor(row) {
|
|
551
|
+
const custom = this.rowRoute();
|
|
552
|
+
if (custom) return custom(row);
|
|
553
|
+
const type = this.entityType();
|
|
554
|
+
if (!type?.clrType) return null;
|
|
555
|
+
return [
|
|
556
|
+
"/po",
|
|
557
|
+
type.alias || type.id,
|
|
558
|
+
row.id
|
|
559
|
+
];
|
|
560
|
+
}
|
|
561
|
+
query = signal(null, ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
562
|
+
entityType = signal(null, ...ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []);
|
|
563
|
+
allEntityTypes = signal([], ...ngDevMode ? [{ debugName: "allEntityTypes" }] : /* istanbul ignore next */ []);
|
|
564
|
+
lookupReferenceOptions = signal({}, ...ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : /* istanbul ignore next */ []);
|
|
565
|
+
loading = signal(true, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
|
|
566
|
+
canRead = signal(false, ...ngDevMode ? [{ debugName: "canRead" }] : /* istanbul ignore next */ []);
|
|
567
|
+
canCreate = signal(false, ...ngDevMode ? [{ debugName: "canCreate" }] : /* istanbul ignore next */ []);
|
|
568
|
+
permissions = signal(null, ...ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []);
|
|
569
|
+
resultCount = signal(null, ...ngDevMode ? [{ debugName: "resultCount" }] : /* istanbul ignore next */ []);
|
|
570
|
+
customActions = signal([], ...ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []);
|
|
571
|
+
defaultActions = signal([], ...ngDevMode ? [{ debugName: "defaultActions" }] : /* istanbul ignore next */ []);
|
|
572
|
+
disabledActions = signal([], ...ngDevMode ? [{ debugName: "disabledActions" }] : /* istanbul ignore next */ []);
|
|
573
|
+
offersCreate = computed(() => {
|
|
574
|
+
if (!this.canCreate() || this.recycleBin()) return false;
|
|
575
|
+
const withheld = this.disabledActions().map((name) => name.toLowerCase());
|
|
576
|
+
return !withheld.includes("new") && !withheld.includes("save");
|
|
577
|
+
}, ...ngDevMode ? [{ debugName: "offersCreate" }] : /* istanbul ignore next */ []);
|
|
578
|
+
visibleCustomActions = computed(() => {
|
|
579
|
+
if (this.recycleBin()) return [];
|
|
580
|
+
const withheld = this.disabledActions();
|
|
581
|
+
if (!withheld.length) return this.customActions();
|
|
582
|
+
const lowered = new Set(withheld.map((name) => name.toLowerCase()));
|
|
583
|
+
return this.customActions().filter((action) => !lowered.has(action.name.toLowerCase()));
|
|
584
|
+
}, ...ngDevMode ? [{ debugName: "visibleCustomActions" }] : /* istanbul ignore next */ []);
|
|
585
|
+
fetchFn = signal(null, ...ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []);
|
|
586
|
+
errorMessage = signal(null, ...ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []);
|
|
587
|
+
selectionMode = computed(() => selectionModeFor(this.visibleCustomActions(), this.selectionModeSetting() ?? this.query()?.selectionMode ?? "auto"), ...ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []);
|
|
588
|
+
toolbarActions = computed(() => {
|
|
589
|
+
const actions = [];
|
|
590
|
+
const withheld = new Set(this.disabledActions().map((name) => name.toLowerCase()));
|
|
591
|
+
for (const definition of this.defaultActions()) {
|
|
592
|
+
const name = definition.name.toLowerCase();
|
|
593
|
+
if (name === "new" && this.offersCreate()) actions.push({
|
|
594
|
+
kind: "new",
|
|
595
|
+
name: definition.name,
|
|
596
|
+
definition,
|
|
597
|
+
priority: 1 + definition.offset
|
|
598
|
+
});
|
|
599
|
+
else if (name === "delete" && this.offersDelete(withheld) && this.selectionMode() !== "none") actions.push({
|
|
600
|
+
kind: "delete",
|
|
601
|
+
name: definition.name,
|
|
602
|
+
definition,
|
|
603
|
+
priority: 5 + definition.offset
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
for (const definition of this.visibleCustomActions()) actions.push({
|
|
607
|
+
kind: "custom",
|
|
608
|
+
name: definition.name,
|
|
609
|
+
definition,
|
|
610
|
+
priority: 10 + definition.offset
|
|
611
|
+
});
|
|
612
|
+
return actions;
|
|
613
|
+
}, ...ngDevMode ? [{ debugName: "toolbarActions" }] : /* istanbul ignore next */ []);
|
|
614
|
+
rowActions = computed(() => {
|
|
615
|
+
const withheld = new Set(this.disabledActions().map((name) => name.toLowerCase()));
|
|
616
|
+
const rowTaking = (definition) => !!definition.selectionRule?.trim() && parseSelectionRule(definition.selectionRule)(1);
|
|
617
|
+
const actions = [];
|
|
618
|
+
for (const definition of this.defaultActions()) if (definition.name.toLowerCase() === "delete" && this.offersDelete(withheld) && rowTaking(definition)) actions.push({
|
|
619
|
+
kind: "delete",
|
|
620
|
+
name: definition.name,
|
|
621
|
+
definition,
|
|
622
|
+
priority: 5 + definition.offset
|
|
623
|
+
});
|
|
624
|
+
for (const definition of this.visibleCustomActions()) if (rowTaking(definition)) actions.push({
|
|
625
|
+
kind: "custom",
|
|
626
|
+
name: definition.name,
|
|
627
|
+
definition,
|
|
628
|
+
priority: 10 + definition.offset
|
|
629
|
+
});
|
|
630
|
+
return actions;
|
|
631
|
+
}, ...ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []);
|
|
632
|
+
offersDelete(withheld) {
|
|
633
|
+
return !this.recycleBin() && !withheld.has("delete");
|
|
634
|
+
}
|
|
635
|
+
isToolbarActionEnabled(action) {
|
|
636
|
+
return this.isActionEnabled(action.definition);
|
|
637
|
+
}
|
|
638
|
+
async runToolbarAction(action) {
|
|
639
|
+
switch (action.kind) {
|
|
640
|
+
case "new":
|
|
641
|
+
this.startNew();
|
|
642
|
+
return;
|
|
643
|
+
case "delete":
|
|
644
|
+
await this.deleteRows(this.selection().map((r) => r.id), action.definition);
|
|
645
|
+
return;
|
|
646
|
+
default: await this.onCustomAction(action.definition);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
clearSelection() {
|
|
650
|
+
this.selection.set([]);
|
|
651
|
+
}
|
|
652
|
+
openRowMenu = signal(null, ...ngDevMode ? [{ debugName: "openRowMenu" }] : /* istanbul ignore next */ []);
|
|
653
|
+
rowMenuPositions = [{
|
|
654
|
+
originX: "end",
|
|
655
|
+
originY: "bottom",
|
|
656
|
+
overlayX: "end",
|
|
657
|
+
overlayY: "top"
|
|
658
|
+
}, {
|
|
659
|
+
originX: "end",
|
|
660
|
+
originY: "top",
|
|
661
|
+
overlayX: "end",
|
|
662
|
+
overlayY: "bottom"
|
|
663
|
+
}];
|
|
664
|
+
toggleRowMenu(row) {
|
|
665
|
+
this.openRowMenu.update((open) => open === row.id ? null : row.id);
|
|
666
|
+
}
|
|
667
|
+
closeRowMenu(rowId) {
|
|
668
|
+
if (rowId !== void 0 && this.openRowMenu() !== rowId) return;
|
|
669
|
+
this.openRowMenu.set(null);
|
|
670
|
+
}
|
|
671
|
+
onRowMenuOutsideClick(event, toggle, rowId) {
|
|
672
|
+
if (event.target instanceof Node && toggle.contains(event.target)) return;
|
|
673
|
+
this.closeRowMenu(rowId);
|
|
674
|
+
}
|
|
675
|
+
async chooseRowAction(action, row) {
|
|
676
|
+
this.closeRowMenu();
|
|
677
|
+
await this.runRowAction(action, row);
|
|
678
|
+
}
|
|
679
|
+
async runRowAction(action, row) {
|
|
680
|
+
if (action.kind === "delete") {
|
|
681
|
+
await this.deleteRows([row.id], action.definition);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
await this.onCustomAction(action.definition, [row.id]);
|
|
685
|
+
}
|
|
686
|
+
startNew() {
|
|
687
|
+
const type = this.entityType();
|
|
688
|
+
if (!type) return;
|
|
689
|
+
this.createClicked.emit();
|
|
690
|
+
const parentId = this.parentId();
|
|
691
|
+
const parentType = this.parentType();
|
|
692
|
+
const query = this.query();
|
|
693
|
+
const queryParams = parentId && parentType && query ? {
|
|
694
|
+
parentId,
|
|
695
|
+
parentType,
|
|
696
|
+
queryId: query.alias || query.id
|
|
697
|
+
} : void 0;
|
|
698
|
+
this.router.navigate([
|
|
699
|
+
"/po",
|
|
700
|
+
type.alias || type.id,
|
|
701
|
+
"new"
|
|
702
|
+
], {
|
|
703
|
+
queryParams,
|
|
704
|
+
state: this.rowLinkState()
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
async deleteRows(ids, definition) {
|
|
708
|
+
const type = this.entityType();
|
|
709
|
+
if (!type || !ids.length) return;
|
|
710
|
+
const key = definition?.confirmationMessageKey;
|
|
711
|
+
const message = ids.length === 1 ? this.lang.t("common.confirmDelete") : (key ? this.lang.t(key) : "") || this.lang.t("common.confirmDeleteSelected");
|
|
712
|
+
if (!confirm(`${message || "Are you sure?"}${ids.length > 1 ? ` (${ids.length})` : ""}`)) return;
|
|
713
|
+
const parentId = this.parentId();
|
|
714
|
+
const parentType = this.parentType();
|
|
715
|
+
try {
|
|
716
|
+
await this.sparkService.deleteMany(type.id, ids, {
|
|
717
|
+
queryId: this.query()?.id,
|
|
718
|
+
...parentId && parentType ? {
|
|
719
|
+
parentId,
|
|
720
|
+
parentType
|
|
721
|
+
} : {}
|
|
722
|
+
});
|
|
723
|
+
const removed = new Set(ids);
|
|
724
|
+
this.selection.set(this.selection().filter((r) => !removed.has(r.id)));
|
|
725
|
+
this.reload();
|
|
726
|
+
} catch (e) {
|
|
727
|
+
const err = e;
|
|
728
|
+
this.errorMessage.set(err.error?.result?.errors?.[0]?.message || err.error?.result?.error || err.error?.error || (err.status === 404 ? this.lang.t("common.deleteRefused") : "") || err.message || this.lang.t("common.actionFailed") || "Action failed");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
isVirtualScrolling = computed(() => isVirtualScrollingQuery(this.query()), ...ngDevMode ? [{ debugName: "isVirtualScrolling" }] : /* istanbul ignore next */ []);
|
|
732
|
+
fetchedColumns = signal([], ...ngDevMode ? [{ debugName: "fetchedColumns" }] : /* istanbul ignore next */ []);
|
|
733
|
+
allColumns = computed(() => this.columns() ?? this.fetchedColumns(), ...ngDevMode ? [{ debugName: "allColumns" }] : /* istanbul ignore next */ []);
|
|
734
|
+
visibleColumns = computed(() => this.allColumns().filter((c) => c.isVisible !== false), ...ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []);
|
|
735
|
+
hasExternalData = computed(() => this.data() !== null || this.query()?.isStreamingQuery === true, ...ngDevMode ? [{ debugName: "hasExternalData" }] : /* istanbul ignore next */ []);
|
|
736
|
+
isActionEnabled(action) {
|
|
737
|
+
return parseSelectionRule(action.selectionRule)(this.selection().map((r) => r.id).length);
|
|
738
|
+
}
|
|
739
|
+
constructor() {
|
|
740
|
+
effect(() => {
|
|
741
|
+
const qId = this.queryId();
|
|
742
|
+
const pId = this.parentId();
|
|
743
|
+
const pType = this.parentType();
|
|
744
|
+
if (qId) this.loadData(qId, pId, pType);
|
|
745
|
+
else this.loading.set(false);
|
|
746
|
+
});
|
|
747
|
+
let first = true;
|
|
748
|
+
effect(() => {
|
|
749
|
+
this.reloadToken();
|
|
750
|
+
this.queryRefresh.tokenFor(this.queryId());
|
|
751
|
+
if (first) {
|
|
752
|
+
first = false;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
untracked(() => this.reload());
|
|
756
|
+
});
|
|
757
|
+
let firstSearch = true;
|
|
758
|
+
effect(() => {
|
|
759
|
+
this.search();
|
|
760
|
+
if (firstSearch) {
|
|
761
|
+
firstSearch = false;
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
untracked(() => {
|
|
765
|
+
this.selection.set([]);
|
|
766
|
+
this.onSearchChanged();
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
let firstDeleted = true;
|
|
770
|
+
effect(() => {
|
|
771
|
+
this.effectiveDeleted();
|
|
772
|
+
if (firstDeleted) {
|
|
773
|
+
firstDeleted = false;
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
untracked(() => {
|
|
777
|
+
this.selection.set([]);
|
|
778
|
+
this.onFilterChanged();
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
reload() {
|
|
783
|
+
if (this.hasExternalData()) return;
|
|
784
|
+
const q = this.query();
|
|
785
|
+
if (q) this.fetchFn.set(this.makeFetch(q, this.parentId(), this.parentType()));
|
|
786
|
+
}
|
|
787
|
+
onSearchChanged() {
|
|
788
|
+
if (this.hasExternalData()) return;
|
|
789
|
+
const s = this.settings();
|
|
790
|
+
this.settings.set(new DatatableSettings({
|
|
791
|
+
perPage: {
|
|
792
|
+
values: s.perPage.values,
|
|
793
|
+
selected: s.perPage.selected
|
|
794
|
+
},
|
|
795
|
+
page: {
|
|
796
|
+
values: [1],
|
|
797
|
+
selected: 1
|
|
798
|
+
},
|
|
799
|
+
sortColumns: s.sortColumns
|
|
800
|
+
}));
|
|
801
|
+
this.reload();
|
|
802
|
+
}
|
|
803
|
+
setFetchedColumns(columns) {
|
|
804
|
+
const current = this.fetchedColumns();
|
|
805
|
+
if (current.length === columns.length && JSON.stringify(current) === JSON.stringify(columns)) return;
|
|
806
|
+
this.fetchedColumns.set(columns);
|
|
807
|
+
}
|
|
808
|
+
filters = signal([], ...ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []);
|
|
809
|
+
completeDistincts = /* @__PURE__ */ new Map();
|
|
810
|
+
distinctsFn = async (request) => {
|
|
811
|
+
const queryId = this.query()?.id;
|
|
812
|
+
if (!queryId) return null;
|
|
813
|
+
const others = this.filters().filter((f) => f.name !== request.column);
|
|
814
|
+
const querySearch = this.search() || void 0;
|
|
815
|
+
const parentId = this.parentId();
|
|
816
|
+
const parentType = this.parentType();
|
|
817
|
+
const result = await this.sparkService.getDistinctValues(queryId, request.column, {
|
|
818
|
+
search: request.search,
|
|
819
|
+
querySearch,
|
|
820
|
+
columns: others,
|
|
821
|
+
parentId,
|
|
822
|
+
parentType,
|
|
823
|
+
deleted: this.effectiveDeleted(),
|
|
824
|
+
parentDeleted: this.parentDeleted() && parentId && parentType ? "include" : void 0
|
|
825
|
+
});
|
|
826
|
+
if (result && !result.hasMore && !request.search && !querySearch) this.completeDistincts.set(request.column, result.matching.map((v) => v.value));
|
|
827
|
+
else this.completeDistincts.delete(request.column);
|
|
828
|
+
return result;
|
|
829
|
+
};
|
|
830
|
+
onFilterChange(detail) {
|
|
831
|
+
if (detail.mode !== "values") return;
|
|
832
|
+
const values = detail.selected.map((v) => v.value);
|
|
833
|
+
const next = this.filters().filter((f) => f.name !== detail.column);
|
|
834
|
+
if (values.length > 0) {
|
|
835
|
+
const complete = this.completeDistincts.get(detail.column);
|
|
836
|
+
if (values.some((v) => v === null || v === void 0) && !detail.inverse && complete) {
|
|
837
|
+
const unselected = complete.filter((v) => v !== null && v !== void 0 && !values.some((s) => s === v));
|
|
838
|
+
if (unselected.length > 0) next.push({
|
|
839
|
+
name: detail.column,
|
|
840
|
+
excludes: unselected
|
|
841
|
+
});
|
|
842
|
+
} else next.push(detail.inverse ? {
|
|
843
|
+
name: detail.column,
|
|
844
|
+
excludes: values
|
|
845
|
+
} : {
|
|
846
|
+
name: detail.column,
|
|
847
|
+
includes: values
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
this.filters.set(next);
|
|
851
|
+
this.onFilterChanged();
|
|
852
|
+
}
|
|
853
|
+
onFilterChanged() {
|
|
854
|
+
if (this.hasExternalData()) return;
|
|
855
|
+
const s = this.settings();
|
|
856
|
+
this.settings.set(new DatatableSettings({
|
|
857
|
+
perPage: {
|
|
858
|
+
values: s.perPage.values,
|
|
859
|
+
selected: s.perPage.selected
|
|
860
|
+
},
|
|
861
|
+
page: {
|
|
862
|
+
values: [1],
|
|
863
|
+
selected: 1
|
|
864
|
+
},
|
|
865
|
+
sortColumns: s.sortColumns
|
|
866
|
+
}));
|
|
867
|
+
this.reload();
|
|
868
|
+
}
|
|
869
|
+
async onCustomAction(action, rowIds) {
|
|
870
|
+
if (action.confirmationMessageKey) {
|
|
871
|
+
const message = this.lang.t(action.confirmationMessageKey) || "Are you sure?";
|
|
872
|
+
if (!confirm(message)) return;
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
const pId = this.parentId();
|
|
876
|
+
const pType = this.parentType();
|
|
877
|
+
await this.sparkService.executeCustomAction(this.entityType().id, action.name, void 0, rowIds ?? this.selection().map((r) => r.id), pId && pType ? {
|
|
878
|
+
id: pId,
|
|
879
|
+
type: pType
|
|
880
|
+
} : void 0, this.query()?.id);
|
|
881
|
+
this.customActionExecuted.emit({ action });
|
|
882
|
+
if (action.refreshOnCompleted) this.reload();
|
|
883
|
+
} catch (e) {
|
|
884
|
+
const err = e;
|
|
885
|
+
this.errorMessage.set(err.error?.error || err.message || this.lang.t("common.actionFailed") || "Action failed");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
reportError(err) {
|
|
889
|
+
this.errorMessage.set(this.describe(err));
|
|
890
|
+
this.error.emit(err);
|
|
891
|
+
}
|
|
892
|
+
describe(err) {
|
|
893
|
+
if (err?.status === 404) return this.lang.t("spark.query.unavailable") || "This list is not available.";
|
|
894
|
+
return err?.error?.error || err?.message || this.lang.t("common.unexpectedError") || "An unexpected error occurred";
|
|
895
|
+
}
|
|
896
|
+
async loadData(queryId, parentId, parentType) {
|
|
897
|
+
this.loading.set(true);
|
|
898
|
+
this.errorMessage.set(null);
|
|
899
|
+
this.fetchFn.set(null);
|
|
900
|
+
this.query.set(null);
|
|
901
|
+
this.entityType.set(null);
|
|
902
|
+
this.canRead.set(false);
|
|
903
|
+
this.canCreate.set(false);
|
|
904
|
+
this.permissions.set(null);
|
|
905
|
+
this.customActions.set([]);
|
|
906
|
+
this.defaultActions.set([]);
|
|
907
|
+
this.resultCount.set(null);
|
|
908
|
+
this.selection.set([]);
|
|
909
|
+
this.filters.set([]);
|
|
910
|
+
try {
|
|
911
|
+
const [resolvedQuery, entityTypes] = await Promise.all([this.sparkService.getQuery(queryId), this.sparkService.getEntityTypes()]);
|
|
912
|
+
this.query.set(resolvedQuery);
|
|
913
|
+
this.allEntityTypes.set(entityTypes);
|
|
914
|
+
const et = this.resolveEntityType(resolvedQuery, entityTypes);
|
|
915
|
+
this.entityType.set(et);
|
|
916
|
+
if (et) {
|
|
917
|
+
const [permissions, actions] = await Promise.all([this.sparkService.getPermissions(et.id), this.sparkService.getCustomActions(et.id)]);
|
|
918
|
+
this.canRead.set(permissions.canRead);
|
|
919
|
+
this.canCreate.set(permissions.canCreate);
|
|
920
|
+
this.permissions.set(permissions);
|
|
921
|
+
this.customActions.set(filterQueryActions(actions));
|
|
922
|
+
this.defaultActions.set(defaultQueryActions(actions));
|
|
923
|
+
} else if (resolvedQuery) console.warn(`[spark] Query '${resolvedQuery.name}' names entity type '${resolvedQuery.entityType ?? resolvedQuery.source}', which resolves to no type in /spark/types; the grid offers no actions and no row links.`);
|
|
924
|
+
this.settings.set(initialGridSettings(resolvedQuery));
|
|
925
|
+
if (this.data() === null && !resolvedQuery?.isStreamingQuery) this.fetchFn.set(this.makeFetch(resolvedQuery, parentId, parentType));
|
|
926
|
+
this.loadLookupReferenceOptions();
|
|
927
|
+
} catch (e) {
|
|
928
|
+
this.fetchFn.set(null);
|
|
929
|
+
this.reportError(e);
|
|
930
|
+
} finally {
|
|
931
|
+
this.loading.set(false);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
resolveEntityType(query, entityTypes) {
|
|
935
|
+
if (!query) return null;
|
|
936
|
+
if (query.entityType) return entityTypes.find((t) => t.name === query.entityType || t.alias === query.entityType?.toLowerCase()) ?? null;
|
|
937
|
+
const sourceName = extractSourceName(query.source);
|
|
938
|
+
const singular = singularize(sourceName);
|
|
939
|
+
return entityTypes.find((t) => t.name === sourceName || t.name === singular || t.name + "s" === sourceName || t.clrType?.endsWith(singular)) ?? null;
|
|
940
|
+
}
|
|
941
|
+
makeFetch(query, parentId, parentType) {
|
|
942
|
+
return (req) => this.sparkService.executeQuery(query.id, {
|
|
943
|
+
sortColumns: req.sortColumns,
|
|
944
|
+
skip: (req.page - 1) * req.perPage,
|
|
945
|
+
take: req.perPage,
|
|
946
|
+
search: this.search() || void 0,
|
|
947
|
+
columns: this.filters(),
|
|
948
|
+
parentId,
|
|
949
|
+
parentType,
|
|
950
|
+
...this.effectiveDeleted() ? { deleted: this.effectiveDeleted() } : {},
|
|
951
|
+
...this.parentDeleted() && parentId && parentType ? { parentDeleted: "include" } : {}
|
|
952
|
+
}).then((r) => {
|
|
953
|
+
this.errorMessage.set(null);
|
|
954
|
+
this.resultCount.set(r.totalItems);
|
|
955
|
+
this.setFetchedColumns(r.columns ?? []);
|
|
956
|
+
this.disabledActions.set(r.disabledActions ?? []);
|
|
957
|
+
return {
|
|
958
|
+
data: r.items,
|
|
959
|
+
totalRecords: r.totalItems,
|
|
960
|
+
totalPages: Math.ceil(r.totalItems / req.perPage) || 1,
|
|
961
|
+
perPage: req.perPage,
|
|
962
|
+
page: req.page
|
|
963
|
+
};
|
|
964
|
+
}).catch((e) => {
|
|
965
|
+
this.reportError(e);
|
|
966
|
+
this.resultCount.set(0);
|
|
967
|
+
return {
|
|
968
|
+
data: [],
|
|
969
|
+
totalRecords: 0,
|
|
970
|
+
totalPages: 1,
|
|
971
|
+
perPage: req.perPage,
|
|
972
|
+
page: req.page
|
|
973
|
+
};
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
async loadLookupReferenceOptions() {
|
|
977
|
+
this.lookupReferenceOptions.set(await this.gridRenderers.loadLookupOptions(this.allColumns()));
|
|
978
|
+
}
|
|
979
|
+
rendererValueFor(item, column) {
|
|
980
|
+
return cellValue(valueFor(item, column.name));
|
|
981
|
+
}
|
|
982
|
+
getColumnRendererComponent(column) {
|
|
983
|
+
return this.gridRenderers.columnComponentFor(column);
|
|
984
|
+
}
|
|
985
|
+
filterLabelFor(column) {
|
|
986
|
+
return this.gridRenderers.filterLabelFor(column);
|
|
987
|
+
}
|
|
988
|
+
getColumnRendererInputs(component, item, column) {
|
|
989
|
+
return this.gridRenderers.columnInputsFor(component, item, column);
|
|
990
|
+
}
|
|
991
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
992
|
+
minVersion: "12.0.0",
|
|
993
|
+
version: "22.2.0",
|
|
994
|
+
ngImport: i0,
|
|
995
|
+
type: SparkQueryGridComponent,
|
|
996
|
+
deps: [],
|
|
997
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
998
|
+
});
|
|
999
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
1000
|
+
minVersion: "17.0.0",
|
|
1001
|
+
version: "22.2.0",
|
|
1002
|
+
type: SparkQueryGridComponent,
|
|
1003
|
+
isStandalone: true,
|
|
1004
|
+
selector: "spark-query-grid",
|
|
1005
|
+
inputs: {
|
|
1006
|
+
queryId: {
|
|
1007
|
+
classPropertyName: "queryId",
|
|
1008
|
+
publicName: "queryId",
|
|
1009
|
+
isSignal: true,
|
|
1010
|
+
isRequired: true,
|
|
1011
|
+
transformFunction: null
|
|
1012
|
+
},
|
|
1013
|
+
parentId: {
|
|
1014
|
+
classPropertyName: "parentId",
|
|
1015
|
+
publicName: "parentId",
|
|
1016
|
+
isSignal: true,
|
|
1017
|
+
isRequired: false,
|
|
1018
|
+
transformFunction: null
|
|
1019
|
+
},
|
|
1020
|
+
parentType: {
|
|
1021
|
+
classPropertyName: "parentType",
|
|
1022
|
+
publicName: "parentType",
|
|
1023
|
+
isSignal: true,
|
|
1024
|
+
isRequired: false,
|
|
1025
|
+
transformFunction: null
|
|
1026
|
+
},
|
|
1027
|
+
data: {
|
|
1028
|
+
classPropertyName: "data",
|
|
1029
|
+
publicName: "data",
|
|
1030
|
+
isSignal: true,
|
|
1031
|
+
isRequired: false,
|
|
1032
|
+
transformFunction: null
|
|
1033
|
+
},
|
|
1034
|
+
search: {
|
|
1035
|
+
classPropertyName: "search",
|
|
1036
|
+
publicName: "search",
|
|
1037
|
+
isSignal: true,
|
|
1038
|
+
isRequired: false,
|
|
1039
|
+
transformFunction: null
|
|
1040
|
+
},
|
|
1041
|
+
columns: {
|
|
1042
|
+
classPropertyName: "columns",
|
|
1043
|
+
publicName: "columns",
|
|
1044
|
+
isSignal: true,
|
|
1045
|
+
isRequired: false,
|
|
1046
|
+
transformFunction: null
|
|
1047
|
+
},
|
|
1048
|
+
reloadToken: {
|
|
1049
|
+
classPropertyName: "reloadToken",
|
|
1050
|
+
publicName: "reloadToken",
|
|
1051
|
+
isSignal: true,
|
|
1052
|
+
isRequired: false,
|
|
1053
|
+
transformFunction: null
|
|
1054
|
+
},
|
|
1055
|
+
deleted: {
|
|
1056
|
+
classPropertyName: "deleted",
|
|
1057
|
+
publicName: "deleted",
|
|
1058
|
+
isSignal: true,
|
|
1059
|
+
isRequired: false,
|
|
1060
|
+
transformFunction: null
|
|
1061
|
+
},
|
|
1062
|
+
parentDeleted: {
|
|
1063
|
+
classPropertyName: "parentDeleted",
|
|
1064
|
+
publicName: "parentDeleted",
|
|
1065
|
+
isSignal: true,
|
|
1066
|
+
isRequired: false,
|
|
1067
|
+
transformFunction: null
|
|
1068
|
+
},
|
|
1069
|
+
settings: {
|
|
1070
|
+
classPropertyName: "settings",
|
|
1071
|
+
publicName: "settings",
|
|
1072
|
+
isSignal: true,
|
|
1073
|
+
isRequired: false,
|
|
1074
|
+
transformFunction: null
|
|
1075
|
+
},
|
|
1076
|
+
selection: {
|
|
1077
|
+
classPropertyName: "selection",
|
|
1078
|
+
publicName: "selection",
|
|
1079
|
+
isSignal: true,
|
|
1080
|
+
isRequired: false,
|
|
1081
|
+
transformFunction: null
|
|
1082
|
+
},
|
|
1083
|
+
rowRoute: {
|
|
1084
|
+
classPropertyName: "rowRoute",
|
|
1085
|
+
publicName: "rowRoute",
|
|
1086
|
+
isSignal: true,
|
|
1087
|
+
isRequired: false,
|
|
1088
|
+
transformFunction: null
|
|
1089
|
+
},
|
|
1090
|
+
selectionModeSetting: {
|
|
1091
|
+
classPropertyName: "selectionModeSetting",
|
|
1092
|
+
publicName: "selectionModeSetting",
|
|
1093
|
+
isSignal: true,
|
|
1094
|
+
isRequired: false,
|
|
1095
|
+
transformFunction: null
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
outputs: {
|
|
1099
|
+
settings: "settingsChange",
|
|
1100
|
+
selection: "selectionChange",
|
|
1101
|
+
error: "error",
|
|
1102
|
+
rowClicked: "rowClicked",
|
|
1103
|
+
customActionExecuted: "customActionExecuted",
|
|
1104
|
+
createClicked: "createClicked"
|
|
1105
|
+
},
|
|
1106
|
+
ngImport: i0,
|
|
1107
|
+
template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. See the class comment for why rows can\n arrive from outside at all (streaming must not put a websocket in every detail page's bundle).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. Deselect-all is the\n datatable's own header checkbox, which it shows only while a row is selected; the chip does\n not repeat it. Single selection has no checkbox column, so no header checkbox either, and a\n row click only ever selects: there the chip's ⊗ is the deselect. The bar keeps its height\n while empty, so ticking the first row does not push the table down under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (selectionMode() === 'single') {\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n }\n </bs-badge>\n }\n </div>\n }\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 [distincts]=\"distinctsFn\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n (rowDblClick)=\"onRowDblClick($event)\"\n [(settings)]=\"settings\">\n @for (col of visibleColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n @if (rowActions().length) {\n <!-- The per-row ⋮ menu's column: no header, never sorted or filtered. -->\n <div *bsDatatableColumn=\"'__sparkRowActions'; sortable: false; filterable: false\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of 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 — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.displayName | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
|
|
1108
|
+
styles: [":host ::ng-deep tbody tr[data-selected=true] a:not(.btn),:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{color:var(--mp-datatable-row-selected-color)}:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{--bs-btn-color: var(--mp-datatable-row-selected-color);--bs-btn-hover-color: var(--mp-datatable-row-selected-color);--bs-btn-active-color: var(--mp-datatable-row-selected-color)}\n"],
|
|
1109
|
+
dependencies: [
|
|
1110
|
+
{
|
|
1111
|
+
kind: "ngmodule",
|
|
1112
|
+
type: CommonModule
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
kind: "directive",
|
|
1116
|
+
type: i1$1.NgTemplateOutlet,
|
|
1117
|
+
selector: "[ngTemplateOutlet]",
|
|
1118
|
+
inputs: [
|
|
1119
|
+
"ngTemplateOutletContext",
|
|
1120
|
+
"ngTemplateOutlet",
|
|
1121
|
+
"ngTemplateOutletInjector"
|
|
1122
|
+
]
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
kind: "ngmodule",
|
|
1126
|
+
type: RouterModule
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
kind: "directive",
|
|
1130
|
+
type: i2.RouterLink,
|
|
1131
|
+
selector: "[routerLink]",
|
|
1132
|
+
inputs: [
|
|
1133
|
+
"target",
|
|
1134
|
+
"queryParams",
|
|
1135
|
+
"fragment",
|
|
1136
|
+
"queryParamsHandling",
|
|
1137
|
+
"state",
|
|
1138
|
+
"info",
|
|
1139
|
+
"relativeTo",
|
|
1140
|
+
"preserveFragment",
|
|
1141
|
+
"skipLocationChange",
|
|
1142
|
+
"replaceUrl",
|
|
1143
|
+
"browserUrl",
|
|
1144
|
+
"routerLink"
|
|
1145
|
+
]
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
kind: "component",
|
|
1149
|
+
type: BsAlertComponent,
|
|
1150
|
+
selector: "bs-alert",
|
|
1151
|
+
inputs: [
|
|
1152
|
+
"type",
|
|
1153
|
+
"announce",
|
|
1154
|
+
"isVisible"
|
|
1155
|
+
],
|
|
1156
|
+
outputs: ["isVisibleChange", "afterOpenedOrClosed"]
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
kind: "component",
|
|
1160
|
+
type: BsDatatableComponent,
|
|
1161
|
+
selector: "bs-datatable",
|
|
1162
|
+
inputs: [
|
|
1163
|
+
"columns",
|
|
1164
|
+
"data",
|
|
1165
|
+
"fetch",
|
|
1166
|
+
"distincts",
|
|
1167
|
+
"labels",
|
|
1168
|
+
"settings",
|
|
1169
|
+
"selectionMode",
|
|
1170
|
+
"selectable",
|
|
1171
|
+
"selection",
|
|
1172
|
+
"rowKey",
|
|
1173
|
+
"resizableColumns",
|
|
1174
|
+
"pagination",
|
|
1175
|
+
"virtualScroll",
|
|
1176
|
+
"itemSize",
|
|
1177
|
+
"virtualBuffer",
|
|
1178
|
+
"isResponsive",
|
|
1179
|
+
"compareWith",
|
|
1180
|
+
"tree",
|
|
1181
|
+
"idKey",
|
|
1182
|
+
"childCountKey",
|
|
1183
|
+
"treeIndent",
|
|
1184
|
+
"expandedIds",
|
|
1185
|
+
"selectionStrategy"
|
|
1186
|
+
],
|
|
1187
|
+
outputs: [
|
|
1188
|
+
"settingsChange",
|
|
1189
|
+
"selectionChange",
|
|
1190
|
+
"rowClick",
|
|
1191
|
+
"rowDblClick",
|
|
1192
|
+
"rowContextMenu",
|
|
1193
|
+
"expandedIdsChange",
|
|
1194
|
+
"rowExpand",
|
|
1195
|
+
"rowCollapse",
|
|
1196
|
+
"filterChange"
|
|
1197
|
+
]
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
kind: "directive",
|
|
1201
|
+
type: BsDatatableColumnDirective,
|
|
1202
|
+
selector: "[bsDatatableColumn]",
|
|
1203
|
+
inputs: [
|
|
1204
|
+
"bsDatatableColumn",
|
|
1205
|
+
"bsDatatableColumnSortable",
|
|
1206
|
+
"bsDatatableColumnFilterable",
|
|
1207
|
+
"bsDatatableColumnFilterActive",
|
|
1208
|
+
"bsDatatableColumnFilterSummary",
|
|
1209
|
+
"bsDatatableColumnFilterSelection",
|
|
1210
|
+
"bsDatatableColumnFilterMode",
|
|
1211
|
+
"bsDatatableColumnFilterInputType",
|
|
1212
|
+
"bsDatatableColumnFilterOperators"
|
|
1213
|
+
]
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
kind: "directive",
|
|
1217
|
+
type: BsDatatableFilterPanelDirective,
|
|
1218
|
+
selector: "[bsDatatableFilterPanel]"
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
kind: "directive",
|
|
1222
|
+
type: BsRowTemplateDirective,
|
|
1223
|
+
selector: "[bsRowTemplate]"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
kind: "component",
|
|
1227
|
+
type: BsSpinnerComponent,
|
|
1228
|
+
selector: "bs-spinner",
|
|
1229
|
+
inputs: ["type", "color"]
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
kind: "component",
|
|
1233
|
+
type: SparkGridCellComponent,
|
|
1234
|
+
selector: "spark-grid-cell",
|
|
1235
|
+
inputs: [
|
|
1236
|
+
"column",
|
|
1237
|
+
"display",
|
|
1238
|
+
"rendererValue",
|
|
1239
|
+
"item",
|
|
1240
|
+
"chips",
|
|
1241
|
+
"link"
|
|
1242
|
+
]
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
kind: "component",
|
|
1246
|
+
type: SparkAttributeDescriptionComponent,
|
|
1247
|
+
selector: "spark-attribute-description",
|
|
1248
|
+
inputs: ["description", "position"]
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
kind: "component",
|
|
1252
|
+
type: SparkColumnFilterPanelComponent,
|
|
1253
|
+
selector: "spark-column-filter-panel",
|
|
1254
|
+
inputs: [
|
|
1255
|
+
"column",
|
|
1256
|
+
"values",
|
|
1257
|
+
"ctx",
|
|
1258
|
+
"filterLabel"
|
|
1259
|
+
]
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
kind: "component",
|
|
1263
|
+
type: BsBadgeComponent,
|
|
1264
|
+
selector: "bs-badge",
|
|
1265
|
+
inputs: [
|
|
1266
|
+
"type",
|
|
1267
|
+
"unit",
|
|
1268
|
+
"decorative"
|
|
1269
|
+
]
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
kind: "directive",
|
|
1273
|
+
type: CdkOverlayOrigin,
|
|
1274
|
+
selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]",
|
|
1275
|
+
exportAs: ["cdkOverlayOrigin"]
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
kind: "directive",
|
|
1279
|
+
type: CdkConnectedOverlay,
|
|
1280
|
+
selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]",
|
|
1281
|
+
inputs: [
|
|
1282
|
+
"cdkConnectedOverlayOrigin",
|
|
1283
|
+
"cdkConnectedOverlayPositions",
|
|
1284
|
+
"cdkConnectedOverlayPositionStrategy",
|
|
1285
|
+
"cdkConnectedOverlayOffsetX",
|
|
1286
|
+
"cdkConnectedOverlayOffsetY",
|
|
1287
|
+
"cdkConnectedOverlayWidth",
|
|
1288
|
+
"cdkConnectedOverlayHeight",
|
|
1289
|
+
"cdkConnectedOverlayMinWidth",
|
|
1290
|
+
"cdkConnectedOverlayMinHeight",
|
|
1291
|
+
"cdkConnectedOverlayBackdropClass",
|
|
1292
|
+
"cdkConnectedOverlayPanelClass",
|
|
1293
|
+
"cdkConnectedOverlayViewportMargin",
|
|
1294
|
+
"cdkConnectedOverlayScrollStrategy",
|
|
1295
|
+
"cdkConnectedOverlayOpen",
|
|
1296
|
+
"cdkConnectedOverlayDisableClose",
|
|
1297
|
+
"cdkConnectedOverlayTransformOriginOn",
|
|
1298
|
+
"cdkConnectedOverlayHasBackdrop",
|
|
1299
|
+
"cdkConnectedOverlayLockPosition",
|
|
1300
|
+
"cdkConnectedOverlayFlexibleDimensions",
|
|
1301
|
+
"cdkConnectedOverlayGrowAfterOpen",
|
|
1302
|
+
"cdkConnectedOverlayPush",
|
|
1303
|
+
"cdkConnectedOverlayDisposeOnNavigation",
|
|
1304
|
+
"cdkConnectedOverlayUsePopover",
|
|
1305
|
+
"cdkConnectedOverlayMatchWidth",
|
|
1306
|
+
"cdkConnectedOverlay"
|
|
1307
|
+
],
|
|
1308
|
+
outputs: [
|
|
1309
|
+
"backdropClick",
|
|
1310
|
+
"positionChange",
|
|
1311
|
+
"attach",
|
|
1312
|
+
"detach",
|
|
1313
|
+
"overlayKeydown",
|
|
1314
|
+
"overlayOutsideClick"
|
|
1315
|
+
],
|
|
1316
|
+
exportAs: ["cdkConnectedOverlay"]
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
kind: "component",
|
|
1320
|
+
type: BsDropdownMenuComponent,
|
|
1321
|
+
selector: "bs-dropdown-menu",
|
|
1322
|
+
inputs: ["menuMode", "maxHeight"],
|
|
1323
|
+
outputs: ["select"]
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
kind: "directive",
|
|
1327
|
+
type: BsDropdownItemDirective,
|
|
1328
|
+
selector: "[bsDropdownItem]",
|
|
1329
|
+
inputs: [
|
|
1330
|
+
"active",
|
|
1331
|
+
"disabled",
|
|
1332
|
+
"value"
|
|
1333
|
+
]
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
kind: "component",
|
|
1337
|
+
type: SparkIconComponent,
|
|
1338
|
+
selector: "spark-icon",
|
|
1339
|
+
inputs: ["name"]
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
kind: "pipe",
|
|
1343
|
+
type: ResolveTranslationPipe,
|
|
1344
|
+
name: "resolveTranslation"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
kind: "pipe",
|
|
1348
|
+
type: QueryCellValuePipe,
|
|
1349
|
+
name: "queryCellValue"
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
kind: "pipe",
|
|
1353
|
+
type: QueryReferenceChipsPipe,
|
|
1354
|
+
name: "queryReferenceChips"
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
kind: "pipe",
|
|
1358
|
+
type: TranslateKeyPipe,
|
|
1359
|
+
name: "t"
|
|
1360
|
+
}
|
|
1361
|
+
],
|
|
1362
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
1363
|
+
});
|
|
1364
|
+
};
|
|
1365
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
1366
|
+
minVersion: "12.0.0",
|
|
1367
|
+
version: "22.2.0",
|
|
1368
|
+
ngImport: i0,
|
|
1369
|
+
type: SparkQueryGridComponent,
|
|
1370
|
+
decorators: [{
|
|
1371
|
+
type: Component,
|
|
1372
|
+
args: [{
|
|
1373
|
+
selector: "spark-query-grid",
|
|
1374
|
+
imports: [
|
|
1375
|
+
CommonModule,
|
|
1376
|
+
RouterModule,
|
|
1377
|
+
BsAlertComponent,
|
|
1378
|
+
BsDatatableComponent,
|
|
1379
|
+
BsDatatableColumnDirective,
|
|
1380
|
+
BsDatatableFilterPanelDirective,
|
|
1381
|
+
BsRowTemplateDirective,
|
|
1382
|
+
BsSpinnerComponent,
|
|
1383
|
+
SparkGridCellComponent,
|
|
1384
|
+
ResolveTranslationPipe,
|
|
1385
|
+
QueryCellValuePipe,
|
|
1386
|
+
QueryReferenceChipsPipe,
|
|
1387
|
+
TranslateKeyPipe,
|
|
1388
|
+
SparkAttributeDescriptionComponent,
|
|
1389
|
+
SparkColumnFilterPanelComponent,
|
|
1390
|
+
BsBadgeComponent,
|
|
1391
|
+
CdkOverlayOrigin,
|
|
1392
|
+
CdkConnectedOverlay,
|
|
1393
|
+
BsDropdownMenuComponent,
|
|
1394
|
+
BsDropdownItemDirective,
|
|
1395
|
+
SparkIconComponent
|
|
1396
|
+
],
|
|
1397
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1398
|
+
template: "<!--\n Three explicit states, in this order, and the order is load-bearing.\n\n This template used to open with `@if (query(); as q)` wrapping EVERYTHING, and `query()` is only\n set after loadData's awaits resolve. Three separate bugs fell out of that one line: the spinner\n was unreachable on a first load (it rendered inside the gate, while `loading` starts true and\n `query` starts null), a first-load failure rendered ZERO DOM — no card, no message, nothing to\n see or report — and a failed RELOAD left the previous query's rows on screen.\n\n So: `loading` is checked first and outside any dependency on `query`, and the final `@else`\n always renders something. Do not fold these back into one gate, and do not move the spinner\n inside the `query()` branch.\n-->\n@if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n} @else if (query()) {\n <!-- A page-fetch failure while the grid is already resolved. Without this the catch returns an\n empty page and a failed query is indistinguishable from one that legitimately has no rows. -->\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <!--\n One datatable, two row sources. `[data]` and `[fetch]` are mutually exclusive in\n ng-bootstrap — binding both makes `[fetch]` win silently — so exactly one is ever non-null\n here: `fetchFn` is left null whenever `data` is bound. See the class comment for why rows can\n arrive from outside at all (streaming must not put a websocket in every detail page's bundle).\n -->\n <!--\n The selection bar (#460, M15): the \"N selected\" chip, only while rows are selected, and part\n of the grid rather than of a host, so the query card and the query-list page show the same\n thing. There is deliberately no select-all (owner decision, 2026-09-29): with paged, lazy or\n virtual-scrolled rows it could only tick the rows loaded so far. Deselect-all is the\n datatable's own header checkbox, which it shows only while a row is selected; the chip does\n not repeat it. Single selection has no checkbox column, so no header checkbox either, and a\n row click only ever selects: there the chip's ⊗ is the deselect. The bar keeps its height\n while empty, so ticking the first row does not push the table down under the pointer.\n -->\n @if (selectionMode() !== 'none') {\n <div class=\"spark-selection-bar d-flex align-items-center gap-2 mb-2\" style=\"min-height: 1.5rem\">\n @if (selection().length) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-selection-chip\">\n {{ selection().length }} {{ 'common.selected' | t }}\n @if (selectionMode() === 'single') {\n <button type=\"button\" class=\"spark-selection-clear\"\n [attr.aria-label]=\"'common.clearSelection' | t\" (click)=\"clearSelection()\">\n <spark-icon name=\"x-circle\" />\n </button>\n }\n </bs-badge>\n }\n </div>\n }\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 [distincts]=\"distinctsFn\"\n (filterChange)=\"onFilterChange($event)\"\n (rowClick)=\"onRowClick($event)\"\n (rowDblClick)=\"onRowDblClick($event)\"\n [(settings)]=\"settings\">\n @for (col of visibleColumns(); track col.name) {\n <!--\n `canSort`/`canFilter` arrive already resolved by the server (query override → attribute →\n true), so this only spells the polarity. `!== false` is needed for BOTH, for opposite\n reasons: absent means capable on our wire, while the datatable's own `filterable` defaults\n to false, unlike `sortable`.\n\n ⚠️ `filterActive` and `filterSummary` are deliberately NOT bound. Any *bsDatatableColumn\n input change recomputes the column set, which retires the live filter view — while the web\n component's mount-once guard leaves the panel's nodes in place and never re-renders them.\n Binding them to state our own panel mutates would freeze the panel the instant a value is\n ticked, until it is closed and reopened. The cost is that a filtered column carries no\n visual marker yet, which is worth far less than a panel that dies on first use. See #431 F7.\n -->\n <div *bsDatatableColumn=\"col.name; sortable: col.canSort !== false; filterable: col.canFilter !== false\">\n {{ (col.label | resolveTranslation) || col.name }}\n <spark-attribute-description [description]=\"col.description\" />\n\n @if (col.canFilter !== false) {\n <ng-container *bsDatatableFilterPanel=\"let values; let ctx = ctx\">\n <spark-column-filter-panel\n [column]=\"col\"\n [values]=\"values()\"\n [ctx]=\"ctx\"\n [filterLabel]=\"filterLabelFor(col)\" />\n </ng-container>\n }\n </div>\n }\n @if (rowActions().length) {\n <!-- The per-row ⋮ menu's column: no header, never sorted or filtered. -->\n <div *bsDatatableColumn=\"'__sparkRowActions'; sortable: false; filterable: false\">\n <span class=\"visually-hidden\">{{ 'common.actions' | t }}</span>\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (col of 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 — a renderer that emits its own <a> produces nested anchors\n (invalid HTML) rather than replacing this one.\n 2. `canRead()` is the whole gate, and it is the rights model: `Query` without\n `Read` lists the rows and withholds the link. Withhold `Read` for a query\n whose rows no detail page can load — a Custom.* query that fabricates rows in\n memory, say.\n 3. `rowRoute` replaces the DESTINATION only, never the gate: returning a route\n from it cannot make a row clickable that `canRead()` withheld, and returning\n null from it suppresses the link for that one row. -->\n @if (first && canRead() && rowRouteFor(row); as route) {\n <a [routerLink]=\"route\" [queryParams]=\"rowQueryParams()\" [state]=\"rowLinkState()\"\n (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, col: col }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n @if (rowActions().length) {\n <!-- The row menu: every action whose rule accepts one row, run on THIS row only. The\n checkbox selection is left alone (runRowAction never reads or writes it). -->\n <td class=\"spark-row-actions text-end\" (click)=\"$event.stopPropagation()\" (dblclick)=\"$event.stopPropagation()\">\n @if (row) {\n <button type=\"button\" cdkOverlayOrigin #rowMenuOrigin=\"cdkOverlayOrigin\" #rowMenuToggle\n class=\"btn btn-sm btn-link spark-row-menu-toggle\"\n aria-haspopup=\"menu\" [attr.aria-expanded]=\"openRowMenu() === row.id\"\n [attr.aria-label]=\"'common.actions' | t\" (click)=\"toggleRowMenu(row)\">\n <spark-icon name=\"three-dots-vertical\" />\n </button>\n <ng-template cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"rowMenuOrigin\"\n [cdkConnectedOverlayOpen]=\"openRowMenu() === row.id\"\n [cdkConnectedOverlayPositions]=\"rowMenuPositions\"\n (overlayOutsideClick)=\"onRowMenuOutsideClick($event, rowMenuToggle, row.id)\"\n (overlayKeydown)=\"$event.key === 'Escape' && closeRowMenu(row.id)\"\n (detach)=\"closeRowMenu(row.id)\">\n <bs-dropdown-menu class=\"spark-row-menu\">\n @for (action of rowActions(); track action.name) {\n <li bsDropdownItem class=\"spark-row-action\" [attr.data-action]=\"action.name\"\n (click)=\"chooseRowAction(action, row)\">\n {{ action.definition.displayName | resolveTranslation }}\n </li>\n }\n </bs-dropdown-menu>\n </ng-template>\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n} @else {\n <!-- The query could not be resolved. Rendering something keeps the host's layout intact and,\n crucially, makes the failure visible: this branch used to render nothing at all. -->\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">\n {{ errorMessage() || ('spark.query.unavailable' | t) }}\n </bs-alert>\n}\n\n<ng-template #cellContent let-item let-col=\"col\">\n <!--\n Presentation lives in <spark-grid-cell>, shared with the AsDetail table on the PO detail page.\n Value resolution stays here: a query row is a PersistentObject with an attribute list, and the\n AsDetail table's rows are plain dictionaries, so only the rendering is common.\n\n `link` is deliberately not passed. The first-column link is the grid's own rule and wraps this\n template from outside; setting both would nest anchors.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(col | queryCellValue:item:lookupReferenceOptions())\"\n [rendererValue]=\"rendererValueFor(item, col)\"\n [item]=\"item\"\n [chips]=\"(col | queryReferenceChips:item)\" />\n</ng-template>\n",
|
|
1399
|
+
styles: [":host ::ng-deep tbody tr[data-selected=true] a:not(.btn),:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{color:var(--mp-datatable-row-selected-color)}:host ::ng-deep tbody tr[data-selected=true] .spark-row-menu-toggle{--bs-btn-color: var(--mp-datatable-row-selected-color);--bs-btn-hover-color: var(--mp-datatable-row-selected-color);--bs-btn-active-color: var(--mp-datatable-row-selected-color)}\n"]
|
|
1400
|
+
}]
|
|
1401
|
+
}],
|
|
1402
|
+
ctorParameters: () => [],
|
|
1403
|
+
propDecorators: {
|
|
1404
|
+
queryId: [{
|
|
1405
|
+
type: i0.Input,
|
|
1406
|
+
args: [{
|
|
1407
|
+
isSignal: true,
|
|
1408
|
+
alias: "queryId",
|
|
1409
|
+
required: true
|
|
1410
|
+
}]
|
|
1411
|
+
}],
|
|
1412
|
+
parentId: [{
|
|
1413
|
+
type: i0.Input,
|
|
1414
|
+
args: [{
|
|
1415
|
+
isSignal: true,
|
|
1416
|
+
alias: "parentId",
|
|
1417
|
+
required: false
|
|
1418
|
+
}]
|
|
1419
|
+
}],
|
|
1420
|
+
parentType: [{
|
|
1421
|
+
type: i0.Input,
|
|
1422
|
+
args: [{
|
|
1423
|
+
isSignal: true,
|
|
1424
|
+
alias: "parentType",
|
|
1425
|
+
required: false
|
|
1426
|
+
}]
|
|
1427
|
+
}],
|
|
1428
|
+
data: [{
|
|
1429
|
+
type: i0.Input,
|
|
1430
|
+
args: [{
|
|
1431
|
+
isSignal: true,
|
|
1432
|
+
alias: "data",
|
|
1433
|
+
required: false
|
|
1434
|
+
}]
|
|
1435
|
+
}],
|
|
1436
|
+
search: [{
|
|
1437
|
+
type: i0.Input,
|
|
1438
|
+
args: [{
|
|
1439
|
+
isSignal: true,
|
|
1440
|
+
alias: "search",
|
|
1441
|
+
required: false
|
|
1442
|
+
}]
|
|
1443
|
+
}],
|
|
1444
|
+
columns: [{
|
|
1445
|
+
type: i0.Input,
|
|
1446
|
+
args: [{
|
|
1447
|
+
isSignal: true,
|
|
1448
|
+
alias: "columns",
|
|
1449
|
+
required: false
|
|
1450
|
+
}]
|
|
1451
|
+
}],
|
|
1452
|
+
reloadToken: [{
|
|
1453
|
+
type: i0.Input,
|
|
1454
|
+
args: [{
|
|
1455
|
+
isSignal: true,
|
|
1456
|
+
alias: "reloadToken",
|
|
1457
|
+
required: false
|
|
1458
|
+
}]
|
|
1459
|
+
}],
|
|
1460
|
+
deleted: [{
|
|
1461
|
+
type: i0.Input,
|
|
1462
|
+
args: [{
|
|
1463
|
+
isSignal: true,
|
|
1464
|
+
alias: "deleted",
|
|
1465
|
+
required: false
|
|
1466
|
+
}]
|
|
1467
|
+
}],
|
|
1468
|
+
parentDeleted: [{
|
|
1469
|
+
type: i0.Input,
|
|
1470
|
+
args: [{
|
|
1471
|
+
isSignal: true,
|
|
1472
|
+
alias: "parentDeleted",
|
|
1473
|
+
required: false
|
|
1474
|
+
}]
|
|
1475
|
+
}],
|
|
1476
|
+
settings: [{
|
|
1477
|
+
type: i0.Input,
|
|
1478
|
+
args: [{
|
|
1479
|
+
isSignal: true,
|
|
1480
|
+
alias: "settings",
|
|
1481
|
+
required: false
|
|
1482
|
+
}]
|
|
1483
|
+
}, {
|
|
1484
|
+
type: i0.Output,
|
|
1485
|
+
args: ["settingsChange"]
|
|
1486
|
+
}],
|
|
1487
|
+
selection: [{
|
|
1488
|
+
type: i0.Input,
|
|
1489
|
+
args: [{
|
|
1490
|
+
isSignal: true,
|
|
1491
|
+
alias: "selection",
|
|
1492
|
+
required: false
|
|
1493
|
+
}]
|
|
1494
|
+
}, {
|
|
1495
|
+
type: i0.Output,
|
|
1496
|
+
args: ["selectionChange"]
|
|
1497
|
+
}],
|
|
1498
|
+
rowRoute: [{
|
|
1499
|
+
type: i0.Input,
|
|
1500
|
+
args: [{
|
|
1501
|
+
isSignal: true,
|
|
1502
|
+
alias: "rowRoute",
|
|
1503
|
+
required: false
|
|
1504
|
+
}]
|
|
1505
|
+
}],
|
|
1506
|
+
selectionModeSetting: [{
|
|
1507
|
+
type: i0.Input,
|
|
1508
|
+
args: [{
|
|
1509
|
+
isSignal: true,
|
|
1510
|
+
alias: "selectionModeSetting",
|
|
1511
|
+
required: false
|
|
1512
|
+
}]
|
|
1513
|
+
}],
|
|
1514
|
+
error: [{
|
|
1515
|
+
type: i0.Output,
|
|
1516
|
+
args: ["error"]
|
|
1517
|
+
}],
|
|
1518
|
+
rowClicked: [{
|
|
1519
|
+
type: i0.Output,
|
|
1520
|
+
args: ["rowClicked"]
|
|
1521
|
+
}],
|
|
1522
|
+
customActionExecuted: [{
|
|
1523
|
+
type: i0.Output,
|
|
1524
|
+
args: ["customActionExecuted"]
|
|
1525
|
+
}],
|
|
1526
|
+
createClicked: [{
|
|
1527
|
+
type: i0.Output,
|
|
1528
|
+
args: ["createClicked"]
|
|
1529
|
+
}]
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
994
1532
|
function extractSourceName(source) {
|
|
995
|
-
|
|
996
|
-
|
|
1533
|
+
const dotIndex = source.indexOf(".");
|
|
1534
|
+
return dotIndex >= 0 ? source.substring(dotIndex + 1) : source;
|
|
997
1535
|
}
|
|
998
|
-
/**
|
|
999
|
-
* Enough English to map a query source onto a type name — `Cars` to `Car`, `People` to `Person`.
|
|
1000
|
-
*
|
|
1001
|
-
* Deliberately a small table plus three suffix rules rather than a library: it runs against type
|
|
1002
|
-
* names the application itself chose, and a wrong guess costs nothing, since the caller falls
|
|
1003
|
-
* through to matching the plural and the CLR type name.
|
|
1004
|
-
*/
|
|
1005
1536
|
function singularize(plural) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
return plural.slice(0, -2);
|
|
1018
|
-
if (plural.endsWith('s'))
|
|
1019
|
-
return plural.slice(0, -1);
|
|
1020
|
-
return plural;
|
|
1537
|
+
const irregulars = {
|
|
1538
|
+
"People": "Person",
|
|
1539
|
+
"Children": "Child",
|
|
1540
|
+
"Men": "Man",
|
|
1541
|
+
"Women": "Woman"
|
|
1542
|
+
};
|
|
1543
|
+
if (irregulars[plural]) return irregulars[plural];
|
|
1544
|
+
if (plural.endsWith("ies")) return plural.slice(0, -3) + "y";
|
|
1545
|
+
if (plural.endsWith("es")) return plural.slice(0, -2);
|
|
1546
|
+
if (plural.endsWith("s")) return plural.slice(0, -1);
|
|
1547
|
+
return plural;
|
|
1021
1548
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
* server. Overriding a default needs no host; replacing one does.
|
|
1034
|
-
*
|
|
1035
|
-
* ## Two ways in, one set of templates
|
|
1036
|
-
*
|
|
1037
|
-
* Hand-written markup uses the structural directives and is found with `contentChildren`.
|
|
1038
|
-
* The auto-rendered path cannot: `spark-po-detail` is created by the router, so in a default app
|
|
1039
|
-
* there is no tag to project into. It therefore forwards `TemplateRef`s as inputs instead — a
|
|
1040
|
-
* directive cannot cross a component boundary but its template can, and that forwarding is
|
|
1041
|
-
* already the idiom in `spark-po-detail` (`extraActionsTemplate`, `extraContentTemplate`).
|
|
1042
|
-
*
|
|
1043
|
-
* Content wins over a forwarded input: the closer declaration is the more specific one.
|
|
1044
|
-
*/
|
|
1045
|
-
class SparkQueryCardComponent {
|
|
1046
|
-
lang = inject(SparkLanguageService);
|
|
1047
|
-
queryId = input.required(/* @ts-ignore */
|
|
1048
|
-
...(ngDevMode ? [{ debugName: "queryId" }] : /* istanbul ignore next */ []));
|
|
1049
|
-
parentId = input('', /* @ts-ignore */
|
|
1050
|
-
...(ngDevMode ? [{ debugName: "parentId" }] : /* istanbul ignore next */ []));
|
|
1051
|
-
parentType = input('', /* @ts-ignore */
|
|
1052
|
-
...(ngDevMode ? [{ debugName: "parentType" }] : /* istanbul ignore next */ []));
|
|
1053
|
-
reloadToken = input(null, /* @ts-ignore */
|
|
1054
|
-
...(ngDevMode ? [{ debugName: "reloadToken" }] : /* istanbul ignore next */ []));
|
|
1055
|
-
data = input(null, /* @ts-ignore */
|
|
1056
|
-
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
1057
|
-
search = input('', /* @ts-ignore */
|
|
1058
|
-
...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
|
|
1059
|
-
/**
|
|
1060
|
-
* Slots forwarded from a host that cannot project content — see the class comment. A card
|
|
1061
|
-
* reached through hand-written markup should use the structural directives instead.
|
|
1062
|
-
*/
|
|
1063
|
-
iconTemplate = input(null, /* @ts-ignore */
|
|
1064
|
-
...(ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []));
|
|
1065
|
-
captionTemplate = input(null, /* @ts-ignore */
|
|
1066
|
-
...(ngDevMode ? [{ debugName: "captionTemplate" }] : /* istanbul ignore next */ []));
|
|
1067
|
-
actionsTemplate = input(null, /* @ts-ignore */
|
|
1068
|
-
...(ngDevMode ? [{ debugName: "actionsTemplate" }] : /* istanbul ignore next */ []));
|
|
1069
|
-
error = output();
|
|
1070
|
-
rowClicked = output();
|
|
1071
|
-
customActionExecuted = output();
|
|
1072
|
-
/**
|
|
1073
|
-
* Forwarded to the grid. Without this the escape hatch was unreachable from a card — i.e. from
|
|
1074
|
-
* every auto-rendered sub-query and program-unit query page, which is most grids.
|
|
1075
|
-
*/
|
|
1076
|
-
rowRoute = input(null, /* @ts-ignore */
|
|
1077
|
-
...(ngDevMode ? [{ debugName: "rowRoute" }] : /* istanbul ignore next */ []));
|
|
1078
|
-
colors = Color;
|
|
1079
|
-
iconSlots = contentChildren(SparkQueryIconDirective, /* @ts-ignore */
|
|
1080
|
-
...(ngDevMode ? [{ debugName: "iconSlots" }] : /* istanbul ignore next */ []));
|
|
1081
|
-
captionSlots = contentChildren(SparkQueryCaptionDirective, /* @ts-ignore */
|
|
1082
|
-
...(ngDevMode ? [{ debugName: "captionSlots" }] : /* istanbul ignore next */ []));
|
|
1083
|
-
actionSlots = contentChildren(SparkQueryActionsDirective, /* @ts-ignore */
|
|
1084
|
-
...(ngDevMode ? [{ debugName: "actionSlots" }] : /* istanbul ignore next */ []));
|
|
1085
|
-
/**
|
|
1086
|
-
* The grid, read only to surface its state — the query, the actions, the selection — to this
|
|
1087
|
-
* component's own header.
|
|
1088
|
-
*
|
|
1089
|
-
* Optional rather than `required` on purpose. The header renders ABOVE the grid in this
|
|
1090
|
-
* template, so on the first change-detection pass the view query has not resolved yet and
|
|
1091
|
-
* `viewChild.required()` would throw where a plain one returns `undefined`. Everything below
|
|
1092
|
-
* therefore reads it defensively; the signal updates once the view initialises and the header
|
|
1093
|
-
* re-renders with the real query.
|
|
1094
|
-
*
|
|
1095
|
-
* A host reads grid state through a template reference variable instead, because a host may put
|
|
1096
|
-
* the grid behind an `@if`, where a view query genuinely is intermittently undefined.
|
|
1097
|
-
*/
|
|
1098
|
-
grid = viewChild(SparkQueryGridComponent, /* @ts-ignore */
|
|
1099
|
-
...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
|
|
1100
|
-
query = computed(() => this.grid()?.query() ?? null, /* @ts-ignore */
|
|
1101
|
-
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
1102
|
-
iconTpl = computed(() => slotFor(this.iconSlots(), this.query())?.templateRef ?? this.iconTemplate(), /* @ts-ignore */
|
|
1103
|
-
...(ngDevMode ? [{ debugName: "iconTpl" }] : /* istanbul ignore next */ []));
|
|
1104
|
-
captionTpl = computed(() => slotFor(this.captionSlots(), this.query())?.templateRef ?? this.captionTemplate(), /* @ts-ignore */
|
|
1105
|
-
...(ngDevMode ? [{ debugName: "captionTpl" }] : /* istanbul ignore next */ []));
|
|
1106
|
-
actionsTpl = computed(() => slotFor(this.actionSlots(), this.query())?.templateRef ?? this.actionsTemplate(), /* @ts-ignore */
|
|
1107
|
-
...(ngDevMode ? [{ debugName: "actionsTpl" }] : /* istanbul ignore next */ []));
|
|
1108
|
-
/** The caption the card renders unless a slot replaces it. */
|
|
1109
|
-
caption = computed(() => {
|
|
1110
|
-
const q = this.query();
|
|
1111
|
-
return (q?.description ? this.lang.resolve(q.description) : '') || q?.name || '';
|
|
1112
|
-
}, /* @ts-ignore */
|
|
1113
|
-
...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
|
|
1114
|
-
customActions = computed(() => this.grid()?.visibleCustomActions() ?? [], /* @ts-ignore */
|
|
1115
|
-
...(ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []));
|
|
1116
|
-
selection = computed(() => this.grid()?.selection() ?? [], /* @ts-ignore */
|
|
1117
|
-
...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
1118
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkQueryCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1119
|
-
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 });
|
|
1549
|
+
function sparkActionClass(definition, size) {
|
|
1550
|
+
const variant = definition.variant?.toLowerCase();
|
|
1551
|
+
const sizeClass = size ? ` btn-${size}` : "";
|
|
1552
|
+
switch (variant) {
|
|
1553
|
+
case "danger":
|
|
1554
|
+
case "warning":
|
|
1555
|
+
case "primary":
|
|
1556
|
+
case "secondary":
|
|
1557
|
+
case "success": return `btn btn-${variant}${sizeClass}`;
|
|
1558
|
+
default: return `btn btn-outline-primary${sizeClass}`;
|
|
1559
|
+
}
|
|
1120
1560
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1561
|
+
const SPARK_SEARCH_DEBOUNCE_MS = 300;
|
|
1562
|
+
var SparkSearchBoxComponent = class SparkSearchBoxComponent {
|
|
1563
|
+
term = model("", ...ngDevMode ? [{ debugName: "term" }] : /* istanbul ignore next */ []);
|
|
1564
|
+
size = input("md", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
|
|
1565
|
+
debounce = input(300, ...ngDevMode ? [{ debugName: "debounce" }] : /* istanbul ignore next */ []);
|
|
1566
|
+
text = signal("", ...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
|
|
1567
|
+
timer = null;
|
|
1568
|
+
constructor() {
|
|
1569
|
+
effect(() => {
|
|
1570
|
+
const term = this.term();
|
|
1571
|
+
untracked(() => {
|
|
1572
|
+
if (term !== this.text()) {
|
|
1573
|
+
this.cancelPending();
|
|
1574
|
+
this.text.set(term);
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
inject(DestroyRef).onDestroy(() => this.cancelPending());
|
|
1579
|
+
}
|
|
1580
|
+
onInput(value) {
|
|
1581
|
+
this.text.set(value ?? "");
|
|
1582
|
+
this.cancelPending();
|
|
1583
|
+
const delay = this.debounce();
|
|
1584
|
+
if (delay <= 0 || !value) {
|
|
1585
|
+
this.term.set(value ?? "");
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
this.timer = setTimeout(() => {
|
|
1589
|
+
this.timer = null;
|
|
1590
|
+
this.term.set(this.text());
|
|
1591
|
+
}, delay);
|
|
1592
|
+
}
|
|
1593
|
+
clear() {
|
|
1594
|
+
this.cancelPending();
|
|
1595
|
+
this.text.set("");
|
|
1596
|
+
this.term.set("");
|
|
1597
|
+
}
|
|
1598
|
+
cancelPending() {
|
|
1599
|
+
if (this.timer !== null) {
|
|
1600
|
+
clearTimeout(this.timer);
|
|
1601
|
+
this.timer = null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1605
|
+
minVersion: "12.0.0",
|
|
1606
|
+
version: "22.2.0",
|
|
1607
|
+
ngImport: i0,
|
|
1608
|
+
type: SparkSearchBoxComponent,
|
|
1609
|
+
deps: [],
|
|
1610
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
1611
|
+
});
|
|
1612
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
1613
|
+
minVersion: "17.0.0",
|
|
1614
|
+
version: "22.2.0",
|
|
1615
|
+
type: SparkSearchBoxComponent,
|
|
1616
|
+
isStandalone: true,
|
|
1617
|
+
selector: "spark-search-box",
|
|
1618
|
+
inputs: {
|
|
1619
|
+
term: {
|
|
1620
|
+
classPropertyName: "term",
|
|
1621
|
+
publicName: "term",
|
|
1622
|
+
isSignal: true,
|
|
1623
|
+
isRequired: false,
|
|
1624
|
+
transformFunction: null
|
|
1625
|
+
},
|
|
1626
|
+
size: {
|
|
1627
|
+
classPropertyName: "size",
|
|
1628
|
+
publicName: "size",
|
|
1629
|
+
isSignal: true,
|
|
1630
|
+
isRequired: false,
|
|
1631
|
+
transformFunction: null
|
|
1632
|
+
},
|
|
1633
|
+
debounce: {
|
|
1634
|
+
classPropertyName: "debounce",
|
|
1635
|
+
publicName: "debounce",
|
|
1636
|
+
isSignal: true,
|
|
1637
|
+
isRequired: false,
|
|
1638
|
+
transformFunction: null
|
|
1639
|
+
}
|
|
1640
|
+
},
|
|
1641
|
+
outputs: { term: "termChange" },
|
|
1642
|
+
ngImport: i0,
|
|
1643
|
+
template: `
|
|
1644
|
+
<bs-form>
|
|
1645
|
+
<bs-input-group [size]="size()">
|
|
1646
|
+
<span class="addon">
|
|
1647
|
+
<spark-icon name="search" />
|
|
1648
|
+
</span>
|
|
1649
|
+
<input
|
|
1650
|
+
type="text"
|
|
1651
|
+
class="spark-search-input"
|
|
1652
|
+
[attr.aria-label]="'common.search' | t"
|
|
1653
|
+
[placeholder]="'common.search' | t"
|
|
1654
|
+
[ngModel]="text()"
|
|
1655
|
+
(ngModelChange)="onInput($event)"
|
|
1656
|
+
(keydown.escape)="clear()">
|
|
1657
|
+
@if (text()) {
|
|
1658
|
+
<button
|
|
1659
|
+
type="button"
|
|
1660
|
+
class="btn btn-outline-secondary spark-search-clear"
|
|
1661
|
+
[attr.aria-label]="'common.clearSearch' | t"
|
|
1662
|
+
(click)="clear()">
|
|
1663
|
+
<spark-icon name="x-lg" />
|
|
1664
|
+
</button>
|
|
1665
|
+
}
|
|
1666
|
+
</bs-input-group>
|
|
1667
|
+
</bs-form>
|
|
1668
|
+
`,
|
|
1669
|
+
isInline: true,
|
|
1670
|
+
styles: [":host{display:block}:host ::ng-deep form{margin:0}\n"],
|
|
1671
|
+
dependencies: [
|
|
1672
|
+
{
|
|
1673
|
+
kind: "ngmodule",
|
|
1674
|
+
type: FormsModule
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
kind: "directive",
|
|
1678
|
+
type: i1.DefaultValueAccessor,
|
|
1679
|
+
selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
kind: "directive",
|
|
1683
|
+
type: i1.NgControlStatus,
|
|
1684
|
+
selector: "[formControlName],[ngModel],[formControl]"
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
kind: "directive",
|
|
1688
|
+
type: i1.NgModel,
|
|
1689
|
+
selector: "[ngModel]:not([formControlName]):not([formControl])",
|
|
1690
|
+
inputs: [
|
|
1691
|
+
"name",
|
|
1692
|
+
"disabled",
|
|
1693
|
+
"ngModel",
|
|
1694
|
+
"ngModelOptions"
|
|
1695
|
+
],
|
|
1696
|
+
outputs: ["ngModelChange"],
|
|
1697
|
+
exportAs: ["ngModel"]
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
kind: "component",
|
|
1701
|
+
type: BsFormComponent,
|
|
1702
|
+
selector: "bs-form",
|
|
1703
|
+
inputs: ["action", "method"],
|
|
1704
|
+
outputs: ["submitted"]
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
kind: "directive",
|
|
1708
|
+
type: BsFormControlDirective,
|
|
1709
|
+
selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)"
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
kind: "component",
|
|
1713
|
+
type: BsInputGroupComponent,
|
|
1714
|
+
selector: "bs-input-group",
|
|
1715
|
+
inputs: ["size"]
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
kind: "component",
|
|
1719
|
+
type: SparkIconComponent,
|
|
1720
|
+
selector: "spark-icon",
|
|
1721
|
+
inputs: ["name"]
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
kind: "pipe",
|
|
1725
|
+
type: TranslateKeyPipe,
|
|
1726
|
+
name: "t"
|
|
1727
|
+
}
|
|
1728
|
+
],
|
|
1729
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
1730
|
+
});
|
|
1731
|
+
};
|
|
1732
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
1733
|
+
minVersion: "12.0.0",
|
|
1734
|
+
version: "22.2.0",
|
|
1735
|
+
ngImport: i0,
|
|
1736
|
+
type: SparkSearchBoxComponent,
|
|
1737
|
+
decorators: [{
|
|
1738
|
+
type: Component,
|
|
1739
|
+
args: [{
|
|
1740
|
+
selector: "spark-search-box",
|
|
1741
|
+
imports: [
|
|
1742
|
+
FormsModule,
|
|
1743
|
+
BsFormComponent,
|
|
1744
|
+
BsFormControlDirective,
|
|
1745
|
+
BsInputGroupComponent,
|
|
1746
|
+
SparkIconComponent,
|
|
1747
|
+
TranslateKeyPipe
|
|
1748
|
+
],
|
|
1749
|
+
template: `
|
|
1750
|
+
<bs-form>
|
|
1751
|
+
<bs-input-group [size]="size()">
|
|
1752
|
+
<span class="addon">
|
|
1753
|
+
<spark-icon name="search" />
|
|
1754
|
+
</span>
|
|
1755
|
+
<input
|
|
1756
|
+
type="text"
|
|
1757
|
+
class="spark-search-input"
|
|
1758
|
+
[attr.aria-label]="'common.search' | t"
|
|
1759
|
+
[placeholder]="'common.search' | t"
|
|
1760
|
+
[ngModel]="text()"
|
|
1761
|
+
(ngModelChange)="onInput($event)"
|
|
1762
|
+
(keydown.escape)="clear()">
|
|
1763
|
+
@if (text()) {
|
|
1764
|
+
<button
|
|
1765
|
+
type="button"
|
|
1766
|
+
class="btn btn-outline-secondary spark-search-clear"
|
|
1767
|
+
[attr.aria-label]="'common.clearSearch' | t"
|
|
1768
|
+
(click)="clear()">
|
|
1769
|
+
<spark-icon name="x-lg" />
|
|
1770
|
+
</button>
|
|
1771
|
+
}
|
|
1772
|
+
</bs-input-group>
|
|
1773
|
+
</bs-form>
|
|
1774
|
+
`,
|
|
1775
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1776
|
+
styles: [":host{display:block}:host ::ng-deep form{margin:0}\n"]
|
|
1777
|
+
}]
|
|
1778
|
+
}],
|
|
1779
|
+
ctorParameters: () => [],
|
|
1780
|
+
propDecorators: {
|
|
1781
|
+
term: [{
|
|
1782
|
+
type: i0.Input,
|
|
1783
|
+
args: [{
|
|
1784
|
+
isSignal: true,
|
|
1785
|
+
alias: "term",
|
|
1786
|
+
required: false
|
|
1787
|
+
}]
|
|
1788
|
+
}, {
|
|
1789
|
+
type: i0.Output,
|
|
1790
|
+
args: ["termChange"]
|
|
1791
|
+
}],
|
|
1792
|
+
size: [{
|
|
1793
|
+
type: i0.Input,
|
|
1794
|
+
args: [{
|
|
1795
|
+
isSignal: true,
|
|
1796
|
+
alias: "size",
|
|
1797
|
+
required: false
|
|
1798
|
+
}]
|
|
1799
|
+
}],
|
|
1800
|
+
debounce: [{
|
|
1801
|
+
type: i0.Input,
|
|
1802
|
+
args: [{
|
|
1803
|
+
isSignal: true,
|
|
1804
|
+
alias: "debounce",
|
|
1805
|
+
required: false
|
|
1806
|
+
}]
|
|
1807
|
+
}]
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
var SparkQueryCardComponent = class SparkQueryCardComponent {
|
|
1811
|
+
lang = inject(SparkLanguageService);
|
|
1812
|
+
queryId = input.required(...ngDevMode ? [{ debugName: "queryId" }] : /* istanbul ignore next */ []);
|
|
1813
|
+
parentId = input("", ...ngDevMode ? [{ debugName: "parentId" }] : /* istanbul ignore next */ []);
|
|
1814
|
+
parentType = input("", ...ngDevMode ? [{ debugName: "parentType" }] : /* istanbul ignore next */ []);
|
|
1815
|
+
reloadToken = input(null, ...ngDevMode ? [{ debugName: "reloadToken" }] : /* istanbul ignore next */ []);
|
|
1816
|
+
data = input(null, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
|
|
1817
|
+
search = input("", ...ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []);
|
|
1818
|
+
searchable = input(true, ...ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []);
|
|
1819
|
+
searchTerm = linkedSignal(() => this.search(), ...ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []);
|
|
1820
|
+
showSearch = computed(() => this.searchable() && this.data() === null, ...ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []);
|
|
1821
|
+
selectionMode = input(null, ...ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []);
|
|
1822
|
+
deleted = input(null, ...ngDevMode ? [{ debugName: "deleted" }] : /* istanbul ignore next */ []);
|
|
1823
|
+
parentDeleted = input(false, ...ngDevMode ? [{ debugName: "parentDeleted" }] : /* istanbul ignore next */ []);
|
|
1824
|
+
iconTemplate = input(null, ...ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []);
|
|
1825
|
+
captionTemplate = input(null, ...ngDevMode ? [{ debugName: "captionTemplate" }] : /* istanbul ignore next */ []);
|
|
1826
|
+
actionsTemplate = input(null, ...ngDevMode ? [{ debugName: "actionsTemplate" }] : /* istanbul ignore next */ []);
|
|
1827
|
+
error = output();
|
|
1828
|
+
rowClicked = output();
|
|
1829
|
+
customActionExecuted = output();
|
|
1830
|
+
rowRoute = input(null, ...ngDevMode ? [{ debugName: "rowRoute" }] : /* istanbul ignore next */ []);
|
|
1831
|
+
colors = Color;
|
|
1832
|
+
iconSlots = contentChildren(SparkQueryIconDirective, ...ngDevMode ? [{ debugName: "iconSlots" }] : /* istanbul ignore next */ []);
|
|
1833
|
+
captionSlots = contentChildren(SparkQueryCaptionDirective, ...ngDevMode ? [{ debugName: "captionSlots" }] : /* istanbul ignore next */ []);
|
|
1834
|
+
actionSlots = contentChildren(SparkQueryActionsDirective, ...ngDevMode ? [{ debugName: "actionSlots" }] : /* istanbul ignore next */ []);
|
|
1835
|
+
grid = viewChild(SparkQueryGridComponent, ...ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []);
|
|
1836
|
+
query = computed(() => this.grid()?.query() ?? null, ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
1837
|
+
iconTpl = computed(() => slotFor(this.iconSlots(), this.query())?.templateRef ?? this.iconTemplate(), ...ngDevMode ? [{ debugName: "iconTpl" }] : /* istanbul ignore next */ []);
|
|
1838
|
+
captionTpl = computed(() => slotFor(this.captionSlots(), this.query())?.templateRef ?? this.captionTemplate(), ...ngDevMode ? [{ debugName: "captionTpl" }] : /* istanbul ignore next */ []);
|
|
1839
|
+
actionsTpl = computed(() => slotFor(this.actionSlots(), this.query())?.templateRef ?? this.actionsTemplate(), ...ngDevMode ? [{ debugName: "actionsTpl" }] : /* istanbul ignore next */ []);
|
|
1840
|
+
caption = computed(() => {
|
|
1841
|
+
const q = this.query();
|
|
1842
|
+
return (q?.description ? this.lang.resolve(q.description) : "") || q?.name || "";
|
|
1843
|
+
}, ...ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []);
|
|
1844
|
+
customActions = computed(() => this.grid()?.visibleCustomActions() ?? [], ...ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []);
|
|
1845
|
+
toolbarActions = computed(() => this.grid()?.toolbarActions() ?? [], ...ngDevMode ? [{ debugName: "toolbarActions" }] : /* istanbul ignore next */ []);
|
|
1846
|
+
actionClass(action) {
|
|
1847
|
+
return `${sparkActionClass(action.definition, "sm")} rounded-0`;
|
|
1848
|
+
}
|
|
1849
|
+
isEnabled(action) {
|
|
1850
|
+
return this.grid()?.isToolbarActionEnabled(action) ?? false;
|
|
1851
|
+
}
|
|
1852
|
+
run(action) {
|
|
1853
|
+
this.grid()?.runToolbarAction(action);
|
|
1854
|
+
}
|
|
1855
|
+
selection = computed(() => this.grid()?.selection() ?? [], ...ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []);
|
|
1856
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1857
|
+
minVersion: "12.0.0",
|
|
1858
|
+
version: "22.2.0",
|
|
1859
|
+
ngImport: i0,
|
|
1860
|
+
type: SparkQueryCardComponent,
|
|
1861
|
+
deps: [],
|
|
1862
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
1863
|
+
});
|
|
1864
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
1865
|
+
minVersion: "17.0.0",
|
|
1866
|
+
version: "22.2.0",
|
|
1867
|
+
type: SparkQueryCardComponent,
|
|
1868
|
+
isStandalone: true,
|
|
1869
|
+
selector: "spark-query-card",
|
|
1870
|
+
inputs: {
|
|
1871
|
+
queryId: {
|
|
1872
|
+
classPropertyName: "queryId",
|
|
1873
|
+
publicName: "queryId",
|
|
1874
|
+
isSignal: true,
|
|
1875
|
+
isRequired: true,
|
|
1876
|
+
transformFunction: null
|
|
1877
|
+
},
|
|
1878
|
+
parentId: {
|
|
1879
|
+
classPropertyName: "parentId",
|
|
1880
|
+
publicName: "parentId",
|
|
1881
|
+
isSignal: true,
|
|
1882
|
+
isRequired: false,
|
|
1883
|
+
transformFunction: null
|
|
1884
|
+
},
|
|
1885
|
+
parentType: {
|
|
1886
|
+
classPropertyName: "parentType",
|
|
1887
|
+
publicName: "parentType",
|
|
1888
|
+
isSignal: true,
|
|
1889
|
+
isRequired: false,
|
|
1890
|
+
transformFunction: null
|
|
1891
|
+
},
|
|
1892
|
+
reloadToken: {
|
|
1893
|
+
classPropertyName: "reloadToken",
|
|
1894
|
+
publicName: "reloadToken",
|
|
1895
|
+
isSignal: true,
|
|
1896
|
+
isRequired: false,
|
|
1897
|
+
transformFunction: null
|
|
1898
|
+
},
|
|
1899
|
+
data: {
|
|
1900
|
+
classPropertyName: "data",
|
|
1901
|
+
publicName: "data",
|
|
1902
|
+
isSignal: true,
|
|
1903
|
+
isRequired: false,
|
|
1904
|
+
transformFunction: null
|
|
1905
|
+
},
|
|
1906
|
+
search: {
|
|
1907
|
+
classPropertyName: "search",
|
|
1908
|
+
publicName: "search",
|
|
1909
|
+
isSignal: true,
|
|
1910
|
+
isRequired: false,
|
|
1911
|
+
transformFunction: null
|
|
1912
|
+
},
|
|
1913
|
+
searchable: {
|
|
1914
|
+
classPropertyName: "searchable",
|
|
1915
|
+
publicName: "searchable",
|
|
1916
|
+
isSignal: true,
|
|
1917
|
+
isRequired: false,
|
|
1918
|
+
transformFunction: null
|
|
1919
|
+
},
|
|
1920
|
+
selectionMode: {
|
|
1921
|
+
classPropertyName: "selectionMode",
|
|
1922
|
+
publicName: "selectionMode",
|
|
1923
|
+
isSignal: true,
|
|
1924
|
+
isRequired: false,
|
|
1925
|
+
transformFunction: null
|
|
1926
|
+
},
|
|
1927
|
+
deleted: {
|
|
1928
|
+
classPropertyName: "deleted",
|
|
1929
|
+
publicName: "deleted",
|
|
1930
|
+
isSignal: true,
|
|
1931
|
+
isRequired: false,
|
|
1932
|
+
transformFunction: null
|
|
1933
|
+
},
|
|
1934
|
+
parentDeleted: {
|
|
1935
|
+
classPropertyName: "parentDeleted",
|
|
1936
|
+
publicName: "parentDeleted",
|
|
1937
|
+
isSignal: true,
|
|
1938
|
+
isRequired: false,
|
|
1939
|
+
transformFunction: null
|
|
1940
|
+
},
|
|
1941
|
+
iconTemplate: {
|
|
1942
|
+
classPropertyName: "iconTemplate",
|
|
1943
|
+
publicName: "iconTemplate",
|
|
1944
|
+
isSignal: true,
|
|
1945
|
+
isRequired: false,
|
|
1946
|
+
transformFunction: null
|
|
1947
|
+
},
|
|
1948
|
+
captionTemplate: {
|
|
1949
|
+
classPropertyName: "captionTemplate",
|
|
1950
|
+
publicName: "captionTemplate",
|
|
1951
|
+
isSignal: true,
|
|
1952
|
+
isRequired: false,
|
|
1953
|
+
transformFunction: null
|
|
1954
|
+
},
|
|
1955
|
+
actionsTemplate: {
|
|
1956
|
+
classPropertyName: "actionsTemplate",
|
|
1957
|
+
publicName: "actionsTemplate",
|
|
1958
|
+
isSignal: true,
|
|
1959
|
+
isRequired: false,
|
|
1960
|
+
transformFunction: null
|
|
1961
|
+
},
|
|
1962
|
+
rowRoute: {
|
|
1963
|
+
classPropertyName: "rowRoute",
|
|
1964
|
+
publicName: "rowRoute",
|
|
1965
|
+
isSignal: true,
|
|
1966
|
+
isRequired: false,
|
|
1967
|
+
transformFunction: null
|
|
1968
|
+
}
|
|
1969
|
+
},
|
|
1970
|
+
outputs: {
|
|
1971
|
+
error: "error",
|
|
1972
|
+
rowClicked: "rowClicked",
|
|
1973
|
+
customActionExecuted: "customActionExecuted"
|
|
1974
|
+
},
|
|
1975
|
+
queries: [
|
|
1976
|
+
{
|
|
1977
|
+
propertyName: "iconSlots",
|
|
1978
|
+
predicate: SparkQueryIconDirective,
|
|
1979
|
+
isSignal: true
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
propertyName: "captionSlots",
|
|
1983
|
+
predicate: SparkQueryCaptionDirective,
|
|
1984
|
+
isSignal: true
|
|
1985
|
+
},
|
|
1986
|
+
{
|
|
1987
|
+
propertyName: "actionSlots",
|
|
1988
|
+
predicate: SparkQueryActionsDirective,
|
|
1989
|
+
isSignal: true
|
|
1990
|
+
}
|
|
1991
|
+
],
|
|
1992
|
+
viewQueries: [{
|
|
1993
|
+
propertyName: "grid",
|
|
1994
|
+
first: true,
|
|
1995
|
+
predicate: SparkQueryGridComponent,
|
|
1996
|
+
descendants: true,
|
|
1997
|
+
isSignal: true
|
|
1998
|
+
}],
|
|
1999
|
+
ngImport: i0,
|
|
2000
|
+
template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly — 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 <!--\n Caption first, actions on the trailing edge (#460 M15, as query-grid-card-PRD.md always said,\n and as Vidyano's sub-query tabs do). `me-auto` on the caption pushes the actions right; a card\n without actions looks exactly as a plain caption did.\n -->\n <span class=\"me-auto spark-query-card-caption\">\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(), actions: toolbarActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (toolbarActions().length) {\n <!-- The default: New, Delete and the custom actions the server offers this caller, with the\n overflow in the priority nav under the translated \"More\" (common.more). The nav renders only when there are any.\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 rules. -->\n <bs-priority-nav class=\"spark-query-card-actions\" [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of toolbarActions(); track action.name) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"actionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isEnabled(action)\" (click)=\"run(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n\n <!-- The query search, on the trailing edge next to the actions, as Vidyano's sub-query tabs\n have it (#460 M15, D17 addendum). The same box as the query-list page; it feeds the\n grid's `search`, which the server applies together with the parent. Absent for bound\n `data`, which the grid never refetches. -->\n @if (showSearch()) {\n <spark-search-box class=\"spark-query-card-search\" size=\"sm\" [(term)]=\"searchTerm\" />\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]=\"searchTerm()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n [deleted]=\"deleted()\"\n [parentDeleted]=\"parentDeleted()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n",
|
|
2001
|
+
styles: [".spark-query-card-search{flex:0 1 14rem;min-width:8rem}\n"],
|
|
2002
|
+
dependencies: [
|
|
2003
|
+
{
|
|
2004
|
+
kind: "ngmodule",
|
|
2005
|
+
type: CommonModule
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
kind: "directive",
|
|
2009
|
+
type: i1$1.NgTemplateOutlet,
|
|
2010
|
+
selector: "[ngTemplateOutlet]",
|
|
2011
|
+
inputs: [
|
|
2012
|
+
"ngTemplateOutletContext",
|
|
2013
|
+
"ngTemplateOutlet",
|
|
2014
|
+
"ngTemplateOutletInjector"
|
|
2015
|
+
]
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
kind: "component",
|
|
2019
|
+
type: BsCardComponent,
|
|
2020
|
+
selector: "bs-card",
|
|
2021
|
+
inputs: ["color", "outline"]
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
kind: "component",
|
|
2025
|
+
type: BsCardHeaderComponent,
|
|
2026
|
+
selector: "bs-card-header",
|
|
2027
|
+
inputs: ["color", "navStyle"]
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
kind: "component",
|
|
2031
|
+
type: BsPriorityNavComponent,
|
|
2032
|
+
selector: "bs-priority-nav",
|
|
2033
|
+
inputs: [
|
|
2034
|
+
"moreLabel",
|
|
2035
|
+
"moreLabelTemplate",
|
|
2036
|
+
"collapseAt",
|
|
2037
|
+
"overflowFrom",
|
|
2038
|
+
"hideEmptyMore",
|
|
2039
|
+
"ariaLabel"
|
|
2040
|
+
],
|
|
2041
|
+
outputs: ["overflowChange"]
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
kind: "directive",
|
|
2045
|
+
type: BsPriorityNavItemDirective,
|
|
2046
|
+
selector: "[bsPriorityNavItem]",
|
|
2047
|
+
inputs: ["bsPriorityNavItem", "bsPriorityNavItemHideBelow"]
|
|
2048
|
+
},
|
|
2049
|
+
{
|
|
2050
|
+
kind: "component",
|
|
2051
|
+
type: SparkQueryGridComponent,
|
|
2052
|
+
selector: "spark-query-grid",
|
|
2053
|
+
inputs: [
|
|
2054
|
+
"queryId",
|
|
2055
|
+
"parentId",
|
|
2056
|
+
"parentType",
|
|
2057
|
+
"data",
|
|
2058
|
+
"search",
|
|
2059
|
+
"columns",
|
|
2060
|
+
"reloadToken",
|
|
2061
|
+
"deleted",
|
|
2062
|
+
"parentDeleted",
|
|
2063
|
+
"settings",
|
|
2064
|
+
"selection",
|
|
2065
|
+
"rowRoute",
|
|
2066
|
+
"selectionModeSetting"
|
|
2067
|
+
],
|
|
2068
|
+
outputs: [
|
|
2069
|
+
"settingsChange",
|
|
2070
|
+
"selectionChange",
|
|
2071
|
+
"error",
|
|
2072
|
+
"rowClicked",
|
|
2073
|
+
"customActionExecuted",
|
|
2074
|
+
"createClicked"
|
|
2075
|
+
]
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
kind: "component",
|
|
2079
|
+
type: SparkSearchBoxComponent,
|
|
2080
|
+
selector: "spark-search-box",
|
|
2081
|
+
inputs: [
|
|
2082
|
+
"term",
|
|
2083
|
+
"size",
|
|
2084
|
+
"debounce"
|
|
2085
|
+
],
|
|
2086
|
+
outputs: ["termChange"]
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
kind: "component",
|
|
2090
|
+
type: SparkIconComponent,
|
|
2091
|
+
selector: "spark-icon",
|
|
2092
|
+
inputs: ["name"]
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
kind: "pipe",
|
|
2096
|
+
type: ResolveTranslationPipe,
|
|
2097
|
+
name: "resolveTranslation"
|
|
2098
|
+
}
|
|
2099
|
+
],
|
|
2100
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
2101
|
+
});
|
|
2102
|
+
};
|
|
2103
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
2104
|
+
minVersion: "12.0.0",
|
|
2105
|
+
version: "22.2.0",
|
|
2106
|
+
ngImport: i0,
|
|
2107
|
+
type: SparkQueryCardComponent,
|
|
2108
|
+
decorators: [{
|
|
2109
|
+
type: Component,
|
|
2110
|
+
args: [{
|
|
2111
|
+
selector: "spark-query-card",
|
|
2112
|
+
imports: [
|
|
2113
|
+
CommonModule,
|
|
2114
|
+
BsCardComponent,
|
|
2115
|
+
BsCardHeaderComponent,
|
|
2116
|
+
BsPriorityNavComponent,
|
|
2117
|
+
BsPriorityNavItemDirective,
|
|
2118
|
+
SparkQueryGridComponent,
|
|
2119
|
+
SparkSearchBoxComponent,
|
|
2120
|
+
ResolveTranslationPipe,
|
|
2121
|
+
SparkIconComponent
|
|
2122
|
+
],
|
|
2123
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2124
|
+
template: "<!--\n The card is always rendered. A host that wants no card uses <spark-query-grid> directly — 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 <!--\n Caption first, actions on the trailing edge (#460 M15, as query-grid-card-PRD.md always said,\n and as Vidyano's sub-query tabs do). `me-auto` on the caption pushes the actions right; a card\n without actions looks exactly as a plain caption did.\n -->\n <span class=\"me-auto spark-query-card-caption\">\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(), actions: toolbarActions(), selection: selection(), query: query() }\"></ng-container>\n } @else if (toolbarActions().length) {\n <!-- The default: New, Delete and the custom actions the server offers this caller, with the\n overflow in the priority nav under the translated \"More\" (common.more). The nav renders only when there are any.\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 rules. -->\n <bs-priority-nav class=\"spark-query-card-actions\" [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @for (action of toolbarActions(); track action.name) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"actionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isEnabled(action)\" (click)=\"run(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.displayName | resolveTranslation }}\n </button>\n }\n </bs-priority-nav>\n }\n\n <!-- The query search, on the trailing edge next to the actions, as Vidyano's sub-query tabs\n have it (#460 M15, D17 addendum). The same box as the query-list page; it feeds the\n grid's `search`, which the server applies together with the parent. Absent for bound\n `data`, which the grid never refetches. -->\n @if (showSearch()) {\n <spark-search-box class=\"spark-query-card-search\" size=\"sm\" [(term)]=\"searchTerm\" />\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]=\"searchTerm()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n [deleted]=\"deleted()\"\n [parentDeleted]=\"parentDeleted()\"\n (error)=\"error.emit($event)\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n </div>\n</bs-card>\n",
|
|
2125
|
+
styles: [".spark-query-card-search{flex:0 1 14rem;min-width:8rem}\n"]
|
|
2126
|
+
}]
|
|
2127
|
+
}],
|
|
2128
|
+
propDecorators: {
|
|
2129
|
+
queryId: [{
|
|
2130
|
+
type: i0.Input,
|
|
2131
|
+
args: [{
|
|
2132
|
+
isSignal: true,
|
|
2133
|
+
alias: "queryId",
|
|
2134
|
+
required: true
|
|
2135
|
+
}]
|
|
2136
|
+
}],
|
|
2137
|
+
parentId: [{
|
|
2138
|
+
type: i0.Input,
|
|
2139
|
+
args: [{
|
|
2140
|
+
isSignal: true,
|
|
2141
|
+
alias: "parentId",
|
|
2142
|
+
required: false
|
|
2143
|
+
}]
|
|
2144
|
+
}],
|
|
2145
|
+
parentType: [{
|
|
2146
|
+
type: i0.Input,
|
|
2147
|
+
args: [{
|
|
2148
|
+
isSignal: true,
|
|
2149
|
+
alias: "parentType",
|
|
2150
|
+
required: false
|
|
2151
|
+
}]
|
|
2152
|
+
}],
|
|
2153
|
+
reloadToken: [{
|
|
2154
|
+
type: i0.Input,
|
|
2155
|
+
args: [{
|
|
2156
|
+
isSignal: true,
|
|
2157
|
+
alias: "reloadToken",
|
|
2158
|
+
required: false
|
|
2159
|
+
}]
|
|
2160
|
+
}],
|
|
2161
|
+
data: [{
|
|
2162
|
+
type: i0.Input,
|
|
2163
|
+
args: [{
|
|
2164
|
+
isSignal: true,
|
|
2165
|
+
alias: "data",
|
|
2166
|
+
required: false
|
|
2167
|
+
}]
|
|
2168
|
+
}],
|
|
2169
|
+
search: [{
|
|
2170
|
+
type: i0.Input,
|
|
2171
|
+
args: [{
|
|
2172
|
+
isSignal: true,
|
|
2173
|
+
alias: "search",
|
|
2174
|
+
required: false
|
|
2175
|
+
}]
|
|
2176
|
+
}],
|
|
2177
|
+
searchable: [{
|
|
2178
|
+
type: i0.Input,
|
|
2179
|
+
args: [{
|
|
2180
|
+
isSignal: true,
|
|
2181
|
+
alias: "searchable",
|
|
2182
|
+
required: false
|
|
2183
|
+
}]
|
|
2184
|
+
}],
|
|
2185
|
+
selectionMode: [{
|
|
2186
|
+
type: i0.Input,
|
|
2187
|
+
args: [{
|
|
2188
|
+
isSignal: true,
|
|
2189
|
+
alias: "selectionMode",
|
|
2190
|
+
required: false
|
|
2191
|
+
}]
|
|
2192
|
+
}],
|
|
2193
|
+
deleted: [{
|
|
2194
|
+
type: i0.Input,
|
|
2195
|
+
args: [{
|
|
2196
|
+
isSignal: true,
|
|
2197
|
+
alias: "deleted",
|
|
2198
|
+
required: false
|
|
2199
|
+
}]
|
|
2200
|
+
}],
|
|
2201
|
+
parentDeleted: [{
|
|
2202
|
+
type: i0.Input,
|
|
2203
|
+
args: [{
|
|
2204
|
+
isSignal: true,
|
|
2205
|
+
alias: "parentDeleted",
|
|
2206
|
+
required: false
|
|
2207
|
+
}]
|
|
2208
|
+
}],
|
|
2209
|
+
iconTemplate: [{
|
|
2210
|
+
type: i0.Input,
|
|
2211
|
+
args: [{
|
|
2212
|
+
isSignal: true,
|
|
2213
|
+
alias: "iconTemplate",
|
|
2214
|
+
required: false
|
|
2215
|
+
}]
|
|
2216
|
+
}],
|
|
2217
|
+
captionTemplate: [{
|
|
2218
|
+
type: i0.Input,
|
|
2219
|
+
args: [{
|
|
2220
|
+
isSignal: true,
|
|
2221
|
+
alias: "captionTemplate",
|
|
2222
|
+
required: false
|
|
2223
|
+
}]
|
|
2224
|
+
}],
|
|
2225
|
+
actionsTemplate: [{
|
|
2226
|
+
type: i0.Input,
|
|
2227
|
+
args: [{
|
|
2228
|
+
isSignal: true,
|
|
2229
|
+
alias: "actionsTemplate",
|
|
2230
|
+
required: false
|
|
2231
|
+
}]
|
|
2232
|
+
}],
|
|
2233
|
+
error: [{
|
|
2234
|
+
type: i0.Output,
|
|
2235
|
+
args: ["error"]
|
|
2236
|
+
}],
|
|
2237
|
+
rowClicked: [{
|
|
2238
|
+
type: i0.Output,
|
|
2239
|
+
args: ["rowClicked"]
|
|
2240
|
+
}],
|
|
2241
|
+
customActionExecuted: [{
|
|
2242
|
+
type: i0.Output,
|
|
2243
|
+
args: ["customActionExecuted"]
|
|
2244
|
+
}],
|
|
2245
|
+
rowRoute: [{
|
|
2246
|
+
type: i0.Input,
|
|
2247
|
+
args: [{
|
|
2248
|
+
isSignal: true,
|
|
2249
|
+
alias: "rowRoute",
|
|
2250
|
+
required: false
|
|
2251
|
+
}]
|
|
2252
|
+
}],
|
|
2253
|
+
iconSlots: [{
|
|
2254
|
+
type: i0.ContentChildren,
|
|
2255
|
+
args: [i0.forwardRef(() => SparkQueryIconDirective), { isSignal: true }]
|
|
2256
|
+
}],
|
|
2257
|
+
captionSlots: [{
|
|
2258
|
+
type: i0.ContentChildren,
|
|
2259
|
+
args: [i0.forwardRef(() => SparkQueryCaptionDirective), { isSignal: true }]
|
|
2260
|
+
}],
|
|
2261
|
+
actionSlots: [{
|
|
2262
|
+
type: i0.ContentChildren,
|
|
2263
|
+
args: [i0.forwardRef(() => SparkQueryActionsDirective), { isSignal: true }]
|
|
2264
|
+
}],
|
|
2265
|
+
grid: [{
|
|
2266
|
+
type: i0.ViewChild,
|
|
2267
|
+
args: [i0.forwardRef(() => SparkQueryGridComponent), { isSignal: true }]
|
|
2268
|
+
}]
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
export { SPARK_GRID_PAGE_SIZES, SPARK_SEARCH_DEBOUNCE_MS, SparkGridCellComponent, SparkGridRenderers, SparkQueryActionsContext, SparkQueryActionsDirective, SparkQueryCaptionContext, SparkQueryCaptionDirective, SparkQueryCardComponent, SparkQueryGridComponent, SparkQueryIconDirective, SparkQuerySlotContext, SparkSearchBoxComponent, initialGridSettings, isVirtualScrollingQuery, slotFor, sparkActionClass, visibleGridAttributes };
|
|
1129
2272
|
|
|
1130
|
-
|
|
1131
|
-
//# sourceMappingURL=mintplayer-ng-spark-grid.mjs.map
|
|
2273
|
+
//# sourceMappingURL=mintplayer-ng-spark-grid.mjs.map
|