@mintplayer/ng-spark 22.29.0 → 22.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +3 -3
  2. package/auth/README.md +216 -0
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +2023 -0
  4. package/fesm2022/mintplayer-ng-spark-auth-account.mjs.map +1 -0
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +96 -0
  6. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -0
  7. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +200 -0
  8. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs.map +1 -0
  9. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +452 -0
  10. package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -0
  11. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +201 -0
  12. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs.map +1 -0
  13. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +196 -0
  14. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs.map +1 -0
  15. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs +16 -0
  16. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs.map +1 -0
  17. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs +18 -0
  18. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs.map +1 -0
  19. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +282 -0
  20. package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -0
  21. package/fesm2022/mintplayer-ng-spark-auth-models.mjs +114 -0
  22. package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -0
  23. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +43 -0
  24. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs.map +1 -0
  25. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +216 -0
  26. package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -0
  27. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +223 -0
  28. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs.map +1 -0
  29. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs +194 -0
  30. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs.map +1 -0
  31. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +210 -0
  32. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs.map +1 -0
  33. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +218 -0
  34. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs.map +1 -0
  35. package/fesm2022/mintplayer-ng-spark-auth.mjs +48 -0
  36. package/fesm2022/mintplayer-ng-spark-auth.mjs.map +1 -0
  37. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +368 -2
  38. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  39. package/fesm2022/mintplayer-ng-spark-grid.mjs +22 -9
  40. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +389 -0
  42. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs.map +1 -0
  43. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +204 -0
  44. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs.map +1 -0
  45. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +486 -0
  46. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs.map +1 -0
  47. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +350 -0
  48. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs.map +1 -0
  49. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs +66 -0
  50. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs.map +1 -0
  51. package/fesm2022/mintplayer-ng-spark-models.mjs +2 -1
  52. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  53. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +13 -3
  54. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  55. package/fesm2022/mintplayer-ng-spark-po-form.mjs +6 -3
  56. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  57. package/fesm2022/mintplayer-ng-spark-pwa.mjs +162 -0
  58. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -0
  59. package/fesm2022/mintplayer-ng-spark-query-list.mjs +5 -2
  60. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  61. package/fesm2022/mintplayer-ng-spark-renderers.mjs +109 -4
  62. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  63. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +36 -6
  64. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  65. package/fesm2022/mintplayer-ng-spark-services.mjs +48 -6
  66. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  67. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  68. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  69. package/package.json +101 -3
  70. package/types/mintplayer-ng-spark-auth-account.d.ts +255 -0
  71. package/types/mintplayer-ng-spark-auth-auth-bar.d.ts +22 -0
  72. package/types/mintplayer-ng-spark-auth-confirm-email.d.ts +21 -0
  73. package/types/mintplayer-ng-spark-auth-core.d.ts +197 -0
  74. package/types/mintplayer-ng-spark-auth-external-login.d.ts +132 -0
  75. package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +18 -0
  76. package/types/mintplayer-ng-spark-auth-guards.d.ts +15 -0
  77. package/types/mintplayer-ng-spark-auth-interceptors.d.ts +2 -0
  78. package/types/mintplayer-ng-spark-auth-login.d.ts +46 -0
  79. package/types/mintplayer-ng-spark-auth-models.d.ts +489 -0
  80. package/types/mintplayer-ng-spark-auth-pipes.d.ts +8 -0
  81. package/types/mintplayer-ng-spark-auth-register.d.ts +21 -0
  82. package/types/mintplayer-ng-spark-auth-reset-password.d.ts +25 -0
  83. package/types/mintplayer-ng-spark-auth-routes.d.ts +154 -0
  84. package/types/mintplayer-ng-spark-auth-sign-in.d.ts +84 -0
  85. package/types/mintplayer-ng-spark-auth-two-factor.d.ts +23 -0
  86. package/types/mintplayer-ng-spark-auth.d.ts +24 -0
  87. package/types/mintplayer-ng-spark-client-operations.d.ts +148 -3
  88. package/types/mintplayer-ng-spark-grid.d.ts +19 -1
  89. package/types/mintplayer-ng-spark-identity-provider-connected-applications.d.ts +35 -0
  90. package/types/mintplayer-ng-spark-identity-provider-core.d.ts +144 -0
  91. package/types/mintplayer-ng-spark-identity-provider-developers.d.ts +55 -0
  92. package/types/mintplayer-ng-spark-identity-provider-management.d.ts +33 -0
  93. package/types/mintplayer-ng-spark-identity-provider.d.ts +64 -0
  94. package/types/mintplayer-ng-spark-models.d.ts +28 -0
  95. package/types/mintplayer-ng-spark-po-detail.d.ts +8 -0
  96. package/types/mintplayer-ng-spark-po-form.d.ts +2 -0
  97. package/types/mintplayer-ng-spark-pwa.d.ts +89 -0
  98. package/types/mintplayer-ng-spark-query-list.d.ts +2 -0
  99. package/types/mintplayer-ng-spark-renderers.d.ts +47 -0
  100. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +1 -0
  101. package/types/mintplayer-ng-spark-services.d.ts +37 -1
  102. package/types/mintplayer-ng-spark-shell.d.ts +2 -2
  103. package/types/mintplayer-ng-spark.d.ts +4 -4
@@ -0,0 +1,162 @@
1
+ import * as i0 from "@angular/core";
2
+ import { ApplicationRef, DestroyRef, Injectable, InjectionToken, PLATFORM_ID, inject, isDevMode, makeEnvironmentProviders, provideAppInitializer, signal } from "@angular/core";
3
+ import { SwUpdate, provideServiceWorker } from "@angular/service-worker";
4
+ import { DOCUMENT, Location, isPlatformBrowser } from "@angular/common";
5
+ import { NavigationStart, Router } from "@angular/router";
6
+ import { Subscription, filter, first } from "rxjs";
7
+ const SPARK_SW_PAGE_LOADER = new InjectionToken("SPARK_SW_PAGE_LOADER", {
8
+ providedIn: "root",
9
+ factory: () => {
10
+ const location = inject(DOCUMENT).defaultView?.location;
11
+ return {
12
+ assign: (url) => location?.assign(url),
13
+ reload: () => location?.reload()
14
+ };
15
+ }
16
+ });
17
+ const SPARK_SW_SCHEDULE = new InjectionToken("SPARK_SW_SCHEDULE", {
18
+ providedIn: "root",
19
+ factory: () => ({
20
+ checkIntervalMs: 216e5,
21
+ visibleCheckThrottleMs: 6e4
22
+ })
23
+ });
24
+ const RELOADED_FOR_KEY = "spark-sw-reloaded-for";
25
+ var SparkServiceWorkerUpdates = class SparkServiceWorkerUpdates {
26
+ swUpdate = inject(SwUpdate);
27
+ router = inject(Router, { optional: true });
28
+ location = inject(Location, { optional: true });
29
+ appRef = inject(ApplicationRef);
30
+ document = inject(DOCUMENT);
31
+ platformId = inject(PLATFORM_ID);
32
+ destroyRef = inject(DestroyRef);
33
+ pageLoader = inject(SPARK_SW_PAGE_LOADER);
34
+ schedule = inject(SPARK_SW_SCHEDULE);
35
+ _updateReady = signal(false, ...ngDevMode ? [{ debugName: "_updateReady" }] : /* istanbul ignore next */ []);
36
+ _currentVersion = signal(null, ...ngDevMode ? [{ debugName: "_currentVersion" }] : /* istanbul ignore next */ []);
37
+ _latestVersion = signal(null, ...ngDevMode ? [{ debugName: "_latestVersion" }] : /* istanbul ignore next */ []);
38
+ updateReady = this._updateReady.asReadonly();
39
+ currentVersion = this._currentVersion.asReadonly();
40
+ latestVersion = this._latestVersion.asReadonly();
41
+ started = false;
42
+ leaving = false;
43
+ reloadWhenVisible = false;
44
+ lastCheck = 0;
45
+ start() {
46
+ if (this.started) return;
47
+ this.started = true;
48
+ if (!isPlatformBrowser(this.platformId) || !this.swUpdate.isEnabled) return;
49
+ const subscriptions = new Subscription();
50
+ this.destroyRef.onDestroy(() => subscriptions.unsubscribe());
51
+ subscriptions.add(this.swUpdate.versionUpdates.pipe(filter((event) => event.type === "VERSION_READY")).subscribe((event) => this.onVersionReady(event)));
52
+ subscriptions.add(this.swUpdate.unrecoverable.subscribe(() => this.onUnrecoverable()));
53
+ if (this.router) subscriptions.add(this.router.events.pipe(filter((event) => event instanceof NavigationStart)).subscribe((event) => this.onNavigationStart(event)));
54
+ subscriptions.add(this.appRef.isStable.pipe(first((stable) => stable)).subscribe(() => {
55
+ this.check();
56
+ const interval = setInterval(() => this.check(), this.schedule.checkIntervalMs);
57
+ subscriptions.add(() => clearInterval(interval));
58
+ }));
59
+ const onVisibilityChange = () => {
60
+ if (this.isHidden()) return;
61
+ if (this.reloadWhenVisible) {
62
+ this.reloadWhenVisible = false;
63
+ this.reload();
64
+ return;
65
+ }
66
+ if (Date.now() - this.lastCheck >= this.schedule.visibleCheckThrottleMs) this.check();
67
+ };
68
+ this.document.addEventListener("visibilitychange", onVisibilityChange);
69
+ subscriptions.add(() => this.document.removeEventListener("visibilitychange", onVisibilityChange));
70
+ }
71
+ onVersionReady(event) {
72
+ const latest = toVersion(event.latestVersion);
73
+ this._currentVersion.set(toVersion(event.currentVersion));
74
+ this._latestVersion.set(latest);
75
+ if (this.readReloadedFor() === latest.hash) return;
76
+ this._updateReady.set(true);
77
+ }
78
+ onUnrecoverable() {
79
+ if (this.isHidden()) {
80
+ this.reloadWhenVisible = true;
81
+ return;
82
+ }
83
+ this.reload();
84
+ }
85
+ onNavigationStart(event) {
86
+ if (!this._updateReady() || this.leaving || this.isHidden()) return;
87
+ this.leaving = true;
88
+ this.writeReloadedFor(this._latestVersion()?.hash);
89
+ this.router?.currentNavigation()?.abort();
90
+ if (event.navigationTrigger === "popstate") {
91
+ this.pageLoader.reload();
92
+ return;
93
+ }
94
+ const target = this.location ? this.location.prepareExternalUrl(event.url) : event.url;
95
+ this.pageLoader.assign(target);
96
+ }
97
+ check() {
98
+ this.lastCheck = Date.now();
99
+ this.swUpdate.checkForUpdate().catch(() => {});
100
+ }
101
+ reload() {
102
+ if (this.leaving) return;
103
+ this.leaving = true;
104
+ this.pageLoader.reload();
105
+ }
106
+ isHidden() {
107
+ return this.document.visibilityState === "hidden";
108
+ }
109
+ readReloadedFor() {
110
+ try {
111
+ return this.document.defaultView?.sessionStorage.getItem(RELOADED_FOR_KEY) ?? null;
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ writeReloadedFor(hash) {
117
+ if (!hash) return;
118
+ try {
119
+ this.document.defaultView?.sessionStorage.setItem(RELOADED_FOR_KEY, hash);
120
+ } catch {}
121
+ }
122
+ static ɵfac = i0.ɵɵngDeclareFactory({
123
+ minVersion: "12.0.0",
124
+ version: "22.2.0",
125
+ ngImport: i0,
126
+ type: SparkServiceWorkerUpdates,
127
+ deps: [],
128
+ target: i0.ɵɵFactoryTarget.Injectable
129
+ });
130
+ static ɵprov = i0.ɵɵngDeclareInjectable({
131
+ minVersion: "12.0.0",
132
+ version: "22.2.0",
133
+ ngImport: i0,
134
+ type: SparkServiceWorkerUpdates,
135
+ providedIn: "root"
136
+ });
137
+ };
138
+ i0.ɵɵngDeclareClassMetadata({
139
+ minVersion: "12.0.0",
140
+ version: "22.2.0",
141
+ ngImport: i0,
142
+ type: SparkServiceWorkerUpdates,
143
+ decorators: [{
144
+ type: Injectable,
145
+ args: [{ providedIn: "root" }]
146
+ }]
147
+ });
148
+ function toVersion(version) {
149
+ return {
150
+ hash: version.hash,
151
+ appData: version.appData
152
+ };
153
+ }
154
+ function provideSparkServiceWorker(options = {}) {
155
+ return makeEnvironmentProviders([provideServiceWorker(options.script ?? "ngsw-worker.js", {
156
+ enabled: options.enabled ?? !isDevMode(),
157
+ registrationStrategy: options.registrationStrategy ?? "registerWhenStable:30000"
158
+ }), provideAppInitializer(() => inject(SparkServiceWorkerUpdates).start())]);
159
+ }
160
+ export { SPARK_SW_PAGE_LOADER, SPARK_SW_SCHEDULE, SparkServiceWorkerUpdates, provideSparkServiceWorker };
161
+
162
+ //# sourceMappingURL=mintplayer-ng-spark-pwa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mintplayer-ng-spark-pwa.mjs","names":[],"sources":["../../pwa/src/spark-service-worker-updates.ts","../../pwa/src/provide-spark-service-worker.ts"],"sourcesContent":["import { DOCUMENT, Location, isPlatformBrowser } from '@angular/common';\nimport {\n ApplicationRef,\n DestroyRef,\n Injectable,\n InjectionToken,\n PLATFORM_ID,\n inject,\n signal,\n} from '@angular/core';\nimport { NavigationStart, Router } from '@angular/router';\nimport { SwUpdate, VersionReadyEvent } from '@angular/service-worker';\nimport { Subscription, filter, first } from 'rxjs';\n\n/** A service-worker version as `ngsw.json` describes it. `appData` is the config's `appData`. */\nexport interface SparkAppVersion {\n hash: string;\n appData?: Record<string, unknown>;\n}\n\n/** The two page loads the update policy performs. Replaceable for tests. */\nexport interface SparkPageLoader {\n assign(url: string): void;\n reload(): void;\n}\n\n/** How the update policy loads a page: `window.location` by default. */\nexport const SPARK_SW_PAGE_LOADER = new InjectionToken<SparkPageLoader>('SPARK_SW_PAGE_LOADER', {\n providedIn: 'root',\n factory: () => {\n const location = inject(DOCUMENT).defaultView?.location;\n return {\n assign: (url: string) => location?.assign(url),\n reload: () => location?.reload(),\n };\n },\n});\n\n/** Update-check timing. Defaults: every 6 h, and on becoming visible at most once a minute. */\nexport interface SparkServiceWorkerSchedule {\n checkIntervalMs: number;\n visibleCheckThrottleMs: number;\n}\n\nexport const SPARK_SW_SCHEDULE = new InjectionToken<SparkServiceWorkerSchedule>('SPARK_SW_SCHEDULE', {\n providedIn: 'root',\n factory: () => ({ checkIntervalMs: 6 * 60 * 60 * 1000, visibleCheckThrottleMs: 60 * 1000 }),\n});\n\n/**\n * Remembers, per tab, the version a full load was made for. If the page that load produced still\n * reports the same version as ready (another tab keeps the old version alive, or the worker did not\n * hand this client the new one), the policy does not reload again: one attempt per version.\n */\nconst RELOADED_FOR_KEY = 'spark-sw-reloaded-for';\n\n/**\n * The update policy of `provideSparkServiceWorker()` (#464, Q1b).\n *\n * - `VERSION_READY`: the **next** router navigation becomes a full page load of its target, so the\n * user lands on the page they asked for, in the new version, without losing anything on the page\n * they are leaving (the navigation was leaving it anyway).\n * - `unrecoverable`: the running version can no longer be served, so the page reloads at once.\n * - Checks: once the application is stable, every 6 h, and when the tab becomes visible again\n * (at most once a minute).\n * - **A hidden tab never reloads.** Spark has no unsaved-changes tracking to consult, so a reload\n * nobody is looking at could throw away an open edit. An `unrecoverable` in a hidden tab waits\n * until the tab is visible; a navigation in a hidden tab stays a normal in-app navigation.\n *\n * Nothing happens when the service worker is disabled (development builds, unsupported browsers)\n * or on the server.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkServiceWorkerUpdates {\n private readonly swUpdate = inject(SwUpdate);\n private readonly router = inject(Router, { optional: true });\n private readonly location = inject(Location, { optional: true });\n private readonly appRef = inject(ApplicationRef);\n private readonly document = inject(DOCUMENT);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly destroyRef = inject(DestroyRef);\n private readonly pageLoader = inject(SPARK_SW_PAGE_LOADER);\n private readonly schedule = inject(SPARK_SW_SCHEDULE);\n\n private readonly _updateReady = signal(false);\n private readonly _currentVersion = signal<SparkAppVersion | null>(null);\n private readonly _latestVersion = signal<SparkAppVersion | null>(null);\n\n /** True once a new version is downloaded and the next navigation will load it. */\n readonly updateReady = this._updateReady.asReadonly();\n /** The running version, known once an update was reported. */\n readonly currentVersion = this._currentVersion.asReadonly();\n /** The version the next navigation loads (`appData.build` is the ng-spark version). */\n readonly latestVersion = this._latestVersion.asReadonly();\n\n private started = false;\n private leaving = false;\n private reloadWhenVisible = false;\n private lastCheck = 0;\n\n /** Starts the policy. Idempotent; called by the app initializer `provideSparkServiceWorker()` adds. */\n start(): void {\n if (this.started) return;\n this.started = true;\n\n if (!isPlatformBrowser(this.platformId) || !this.swUpdate.isEnabled) return;\n\n const subscriptions = new Subscription();\n this.destroyRef.onDestroy(() => subscriptions.unsubscribe());\n\n subscriptions.add(\n this.swUpdate.versionUpdates\n .pipe(filter((event): event is VersionReadyEvent => event.type === 'VERSION_READY'))\n .subscribe((event) => this.onVersionReady(event)),\n );\n\n subscriptions.add(this.swUpdate.unrecoverable.subscribe(() => this.onUnrecoverable()));\n\n if (this.router) {\n subscriptions.add(\n this.router.events\n .pipe(filter((event): event is NavigationStart => event instanceof NavigationStart))\n .subscribe((event) => this.onNavigationStart(event)),\n );\n }\n\n // The first check waits for stability, so it never competes with the initial render.\n subscriptions.add(\n this.appRef.isStable.pipe(first((stable) => stable)).subscribe(() => {\n this.check();\n const interval = setInterval(() => this.check(), this.schedule.checkIntervalMs);\n subscriptions.add(() => clearInterval(interval));\n }),\n );\n\n const onVisibilityChange = () => {\n if (this.isHidden()) return;\n if (this.reloadWhenVisible) {\n this.reloadWhenVisible = false;\n this.reload();\n return;\n }\n if (Date.now() - this.lastCheck >= this.schedule.visibleCheckThrottleMs) this.check();\n };\n this.document.addEventListener('visibilitychange', onVisibilityChange);\n subscriptions.add(() => this.document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n private onVersionReady(event: VersionReadyEvent): void {\n const latest = toVersion(event.latestVersion);\n this._currentVersion.set(toVersion(event.currentVersion));\n this._latestVersion.set(latest);\n\n if (this.readReloadedFor() === latest.hash) {\n // This page is the full load made for that version and it is still not the one served.\n // Reloading again would loop; the next deploy, or the next fresh tab, picks it up.\n return;\n }\n this._updateReady.set(true);\n }\n\n private onUnrecoverable(): void {\n if (this.isHidden()) {\n this.reloadWhenVisible = true;\n return;\n }\n this.reload();\n }\n\n private onNavigationStart(event: NavigationStart): void {\n if (!this._updateReady() || this.leaving || this.isHidden()) return;\n\n this.leaving = true;\n this.writeReloadedFor(this._latestVersion()?.hash);\n this.router?.currentNavigation()?.abort();\n\n if (event.navigationTrigger === 'popstate') {\n // Back/forward: the address bar already shows the target, and assign() would add a\n // duplicate history entry.\n this.pageLoader.reload();\n return;\n }\n\n // event.url is relative to the router's base; prepareExternalUrl adds the <base href>.\n const target = this.location ? this.location.prepareExternalUrl(event.url) : event.url;\n this.pageLoader.assign(target);\n }\n\n private check(): void {\n this.lastCheck = Date.now();\n this.swUpdate.checkForUpdate().catch(() => {\n // Offline, or the worker is gone. The next scheduled check tries again.\n });\n }\n\n private reload(): void {\n if (this.leaving) return;\n this.leaving = true;\n this.pageLoader.reload();\n }\n\n private isHidden(): boolean {\n return this.document.visibilityState === 'hidden';\n }\n\n private readReloadedFor(): string | null {\n try {\n return this.document.defaultView?.sessionStorage.getItem(RELOADED_FOR_KEY) ?? null;\n } catch {\n return null;\n }\n }\n\n private writeReloadedFor(hash: string | undefined): void {\n if (!hash) return;\n try {\n this.document.defaultView?.sessionStorage.setItem(RELOADED_FOR_KEY, hash);\n } catch {\n // Storage blocked: the guard is best effort; one reload per navigation is still bounded.\n }\n }\n}\n\nfunction toVersion(version: { hash: string; appData?: object }): SparkAppVersion {\n return { hash: version.hash, appData: version.appData as Record<string, unknown> | undefined };\n}\n","import { EnvironmentProviders, inject, isDevMode, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\nimport { SwRegistrationOptions, provideServiceWorker } from '@angular/service-worker';\n\nimport { SparkServiceWorkerUpdates } from './spark-service-worker-updates';\n\nexport interface SparkServiceWorkerOptions {\n /** Whether the worker registers. Default: `!isDevMode()`, so only production builds register it. */\n enabled?: boolean;\n /** The worker script, relative to the base href. Default: `ngsw-worker.js`. */\n script?: string;\n /** When the worker registers. Default: `registerWhenStable:30000`. */\n registrationStrategy?: SwRegistrationOptions['registrationStrategy'];\n}\n\n/**\n * Registers Angular's service worker with Spark's update policy (#464, Q1b); see\n * {@link SparkServiceWorkerUpdates} for what happens when a new version is deployed.\n *\n * The application also needs an `ngsw-config.json` with Spark's navigation exclusions and\n * `serviceWorker` in its production build configuration; see `docs/guide-pwa.md`.\n */\nexport function provideSparkServiceWorker(options: SparkServiceWorkerOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideServiceWorker(options.script ?? 'ngsw-worker.js', {\n enabled: options.enabled ?? !isDevMode(),\n registrationStrategy: options.registrationStrategy ?? 'registerWhenStable:30000',\n }),\n provideAppInitializer(() => inject(SparkServiceWorkerUpdates).start()),\n ]);\n}\n"],"mappings":";;;;;;AA2BA,MAAa,uBAAuB,IAAI,eAAgC,wBAAwB;CAC9F,YAAY;CACZ,eAAc;EACZ,MAAM,WAAW,OAAO,QAAQ,CAAC,CAAC,aAAa;EAC/C,OAAO;GACL,SAAS,QAAgB,UAAU,OAAO,GAAG;GAC7C,cAAc,UAAU,OAAM;EAC/B;CACH;AACD,CAAA;AAQD,MAAa,oBAAoB,IAAI,eAA2C,qBAAqB;CACnG,YAAY;CACZ,gBAAgB;EAAE,iBAAiB;EAAoB,wBAAwB;CAAS;AACzF,CAAA;AAOD,MAAM,mBAAmB;AAmBzB,IAAa,4BAAb,MAAa,0BAAyB;CACnB,WAAW,OAAO,QAAQ;CAC1B,SAAS,OAAO,QAAQ,EAAE,UAAU,KAAI,CAAE;CAC1C,WAAW,OAAO,UAAU,EAAE,UAAU,KAAI,CAAE;CAC9C,SAAS,OAAO,cAAc;CAC9B,WAAW,OAAO,QAAQ;CAC1B,aAAa,OAAO,WAAW;CAC/B,aAAa,OAAO,UAAU;CAC9B,aAAa,OAAO,oBAAoB;CACxC,WAAW,OAAO,iBAAiB;CAEnC,eAAe,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,kBAAkB,OAA+B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CACtD,iBAAiB,OAA+B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG7D,cAAc,KAAK,aAAa,WAAU;CAE1C,iBAAiB,KAAK,gBAAgB,WAAU;CAEhD,gBAAgB,KAAK,eAAe,WAAU;CAE/C,UAAU;CACV,UAAU;CACV,oBAAoB;CACpB,YAAY;CAGpB,QAAK;EACH,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EAEf,IAAI,CAAC,kBAAkB,KAAK,UAAU,KAAK,CAAC,KAAK,SAAS,WAAW;EAErE,MAAM,gBAAgB,IAAI,aAAY;EACtC,KAAK,WAAW,gBAAgB,cAAc,YAAW,CAAE;EAE3D,cAAc,IACZ,KAAK,SAAS,eACX,KAAK,QAAQ,UAAsC,MAAM,SAAS,eAAe,CAAC,CAAC,CACnF,WAAW,UAAU,KAAK,eAAe,KAAK,CAAC,CAAC;EAGrD,cAAc,IAAI,KAAK,SAAS,cAAc,gBAAgB,KAAK,gBAAe,CAAE,CAAC;EAErF,IAAI,KAAK,QACP,cAAc,IACZ,KAAK,OAAO,OACT,KAAK,QAAQ,UAAoC,iBAAiB,eAAe,CAAC,CAAC,CACnF,WAAW,UAAU,KAAK,kBAAkB,KAAK,CAAC,CAAC;EAK1D,cAAc,IACZ,KAAK,OAAO,SAAS,KAAK,OAAO,WAAW,MAAM,CAAC,CAAC,CAAC,gBAAe;GAClE,KAAK,MAAK;GACV,MAAM,WAAW,kBAAkB,KAAK,MAAK,GAAI,KAAK,SAAS,eAAe;GAC9E,cAAc,UAAU,cAAc,QAAQ,CAAC;EACjD,CAAC,CAAC;EAGJ,MAAM,2BAA0B;GAC9B,IAAI,KAAK,SAAQ,GAAI;GACrB,IAAI,KAAK,mBAAmB;IAC1B,KAAK,oBAAoB;IACzB,KAAK,OAAM;IACX;GACF;GACA,IAAI,KAAK,IAAG,IAAK,KAAK,aAAa,KAAK,SAAS,wBAAwB,KAAK,MAAK;EACrF;EACA,KAAK,SAAS,iBAAiB,oBAAoB,kBAAkB;EACrE,cAAc,UAAU,KAAK,SAAS,oBAAoB,oBAAoB,kBAAkB,CAAC;CACnG;CAEQ,eAAe,OAAwB;EAC7C,MAAM,SAAS,UAAU,MAAM,aAAa;EAC5C,KAAK,gBAAgB,IAAI,UAAU,MAAM,cAAc,CAAC;EACxD,KAAK,eAAe,IAAI,MAAM;EAE9B,IAAI,KAAK,gBAAe,MAAO,OAAO,MAGpC;EAEF,KAAK,aAAa,IAAI,IAAI;CAC5B;CAEQ,kBAAe;EACrB,IAAI,KAAK,SAAQ,GAAI;GACnB,KAAK,oBAAoB;GACzB;EACF;EACA,KAAK,OAAM;CACb;CAEQ,kBAAkB,OAAsB;EAC9C,IAAI,CAAC,KAAK,aAAY,KAAM,KAAK,WAAW,KAAK,SAAQ,GAAI;EAE7D,KAAK,UAAU;EACf,KAAK,iBAAiB,KAAK,eAAc,CAAE,EAAE,IAAI;EACjD,KAAK,QAAQ,kBAAiB,CAAE,EAAE,MAAK;EAEvC,IAAI,MAAM,sBAAsB,YAAY;GAG1C,KAAK,WAAW,OAAM;GACtB;EACF;EAGA,MAAM,SAAS,KAAK,WAAW,KAAK,SAAS,mBAAmB,MAAM,GAAG,IAAI,MAAM;EACnF,KAAK,WAAW,OAAO,MAAM;CAC/B;CAEQ,QAAK;EACX,KAAK,YAAY,KAAK,IAAG;EACzB,KAAK,SAAS,eAAc,CAAE,CAAC,YAAW,CAE1C,CAAC;CACH;CAEQ,SAAM;EACZ,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EACf,KAAK,WAAW,OAAM;CACxB;CAEQ,WAAQ;EACd,OAAO,KAAK,SAAS,oBAAoB;CAC3C;CAEQ,kBAAe;EACrB,IAAI;GACF,OAAO,KAAK,SAAS,aAAa,eAAe,QAAQ,gBAAgB,KAAK;EAChF,QAAQ;GACN,OAAO;EACT;CACF;CAEQ,iBAAiB,MAAwB;EAC/C,IAAI,CAAC,MAAM;EACX,IAAI;GACF,KAAK,SAAS,aAAa,eAAe,QAAQ,kBAAkB,IAAI;EAC1E,QAAQ,CAER;CACF;CAnJW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAyB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAzB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAyB,YADZ;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAyB,YAAA,CAAA;EADrC,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;AAuJlC,SAAS,UAAU,SAA2C;CAC5D,OAAO;EAAE,MAAM,QAAQ;EAAM,SAAS,QAAQ;CAA8C;AAC9F;AC5MA,SAAgB,0BAA0B,UAAqC,CAAA,GAAE;CAC/E,OAAO,yBAAyB,CAC9B,qBAAqB,QAAQ,UAAU,kBAAkB;EACvD,SAAS,QAAQ,WAAW,CAAC,UAAS;EACtC,sBAAsB,QAAQ,wBAAwB;CACvD,CAAA,GACD,4BAA4B,OAAO,yBAAyB,CAAC,CAAC,MAAK,CAAE,CACtE,CAAA;AACH"}
@@ -85,6 +85,9 @@ var SparkQueryListComponent = class SparkQueryListComponent {
85
85
  isToolbarActionEnabled(action) {
86
86
  return this.grid()?.isToolbarActionEnabled(action) ?? false;
87
87
  }
88
+ toolbarActionUnavailableReason(action) {
89
+ return this.grid()?.actionUnavailableReason(action.definition) ?? null;
90
+ }
88
91
  runToolbarAction(action) {
89
92
  if (action.kind === "new") {
90
93
  this.onCreate();
@@ -339,7 +342,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
339
342
  isSignal: true
340
343
  }],
341
344
  ngImport: i0,
342
- template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
345
+ template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
343
346
  styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 calc(-1 * var(--spark-main-padding, 1.5rem)) calc(-1 * var(--spark-main-padding, 1.5rem));--spark-content-inset-inline: 0px}:host.virtual-scrolling spark-query-grid{display:flex;flex-direction:column;min-height:0}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:rgba(var(--bs-emphasis-color-rgb),.05)}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}.spark-live-badge{font-size:.6em}.spark-url-filter-clear{background:none;border:0;padding:0 0 0 .25rem;color:inherit;line-height:1}\n"],
344
347
  dependencies: [
345
348
  {
@@ -531,7 +534,7 @@ i0.ɵɵngDeclareClassMetadata({
531
534
  ],
532
535
  changeDetection: ChangeDetectionStrategy.OnPush,
533
536
  host: { "[class.virtual-scrolling]": "isVirtualScrolling()" },
534
- template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
537
+ template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
535
538
  styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 calc(-1 * var(--spark-main-padding, 1.5rem)) calc(-1 * var(--spark-main-padding, 1.5rem));--spark-content-inset-inline: 0px}:host.virtual-scrolling spark-query-grid{display:flex;flex-direction:column;min-height:0}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:rgba(var(--bs-emphasis-color-rgb),.05)}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}.spark-live-badge{font-size:.6em}.spark-url-filter-clear{background:none;border:0;padding:0 0 0 .25rem;color:inherit;line-height:1}\n"]
536
539
  }]
537
540
  }],
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-query-list.mjs","names":[],"sources":["../../query-list/src/spark-query-list.component.ts","../../query-list/src/spark-query-list.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, viewChild, TemplateRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { Subscription } from 'rxjs';\nimport { CommonModule, NgTemplateOutlet } from '@angular/common';\nimport { ActivatedRoute, ParamMap, Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { SparkService, SparkStreamingService, SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryGridComponent, SparkQueryToolbarAction, SparkSearchBoxComponent, sparkActionClass } from '@mintplayer/ng-spark/grid';\nimport {\n CustomActionDefinition,\n StreamingMessage,\n QueryColumn,\n QueryColumnFilter,\n QueryResultItem,\n SparkDeletedFilter,\n isDateDataType,\n parseWireDate,\n type SparkSelectionModeSetting,\n} from '@mintplayer/ng-spark/models';\nimport { NgComponentOutlet } from '@angular/common';\nimport {\n SPARK_QUERY_LIST_ACTIONS,\n SparkQueryListContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\n\n/**\n * The routed query page: chrome around one {@link SparkQueryGridComponent}.\n *\n * It owns what is genuinely page-shaped and route-shaped, and nothing else:\n *\n * - **Route resolution.** It has no `queryId` input; it reads `paramMap`, and it serves two\n * routes — `query/:queryId`, and `po/:type`, which resolves an entity type to a query. That\n * second one is type-to-query resolution, not query rendering, and is why this component still\n * exists rather than the router pointing at the grid.\n * - **Streaming.** The websocket lives here so it stays out of every PO detail page's bundle;\n * the snapshot is filtered and sorted client-side and handed to the grid as `[data]`.\n * - The action bar, the caption, the LIVE badge, the search box and the New button.\n *\n * The grid itself — columns, cells, paging, the row link, selection, custom-action execution —\n * is the shared component. This page previously wrote out `<bs-datatable>` twice, once per\n * transport, with a shared row template between them; both are gone.\n */\n@Component({\n selector: 'spark-query-list',\n imports: [BsBadgeComponent, CommonModule, NgTemplateOutlet, NgComponentOutlet, BsAlertComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryGridComponent, SparkSearchBoxComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-query-list.component.html',\n styleUrl: './spark-query-list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.virtual-scrolling]': 'isVirtualScrolling()'\n }\n})\nexport class SparkQueryListComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly streamingService = inject(SparkStreamingService);\n private readonly destroyRef = inject(DestroyRef);\n protected readonly lang = inject(SparkLanguageService);\n\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n showCustomActions = input(true);\n\n /**\n * Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a\n * host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.\n */\n selectionMode = input<SparkSelectionModeSetting | null>(null);\n\n /**\n * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card\n * forwards it. Without this the escape hatch existed only for a directly-embedded grid.\n */\n rowRoute = input<((row: QueryResultItem) => unknown[] | null) | null>(null);\n\n rowClicked = output<QueryResultItem>();\n createClicked = output<void>();\n customActionExecuted = output<{ action: CustomActionDefinition }>();\n\n colors = Color;\n\n /** The query the grid should render, resolved from the route. Null until it is known. */\n queryId = signal<string | null>(null);\n errorMessage = signal<string | null>(null);\n searchTerm = signal('');\n\n private readonly grid = viewChild(SparkQueryGridComponent);\n\n /**\n * Grid state, surfaced for this page's chrome.\n *\n * Optional `viewChild`, read defensively: the action bar and caption render above the grid, so\n * on the first change-detection pass the query has not resolved yet.\n */\n protected readonly query = computed(() => this.grid()?.query() ?? null);\n protected readonly entityType = computed(() => this.grid()?.entityType() ?? null);\n protected readonly customActions = computed(() => this.grid()?.customActions() ?? []);\n protected readonly canCreate = computed(() => this.grid()?.offersCreate() ?? false);\n protected readonly resultCount = computed(() => this.grid()?.resultCount() ?? null);\n protected readonly isVirtualScrolling = computed(() => this.grid()?.isVirtualScrolling() ?? false);\n protected readonly gridError = computed(() => this.grid()?.errorMessage() ?? null);\n protected readonly permissions = computed(() => this.grid()?.permissions() ?? null);\n\n /** Add-on buttons for the action bar (#460, `SPARK_QUERY_LIST_ACTIONS`), e.g. the Deleted toggle. */\n protected readonly listActions = orderSparkExtensions(inject(SPARK_QUERY_LIST_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n /**\n * The soft-deletion mode (#460, T2), read from the route's `?deleted=` so the recycle bin survives\n * a reload and the back button from a row opened in it. `exclude` unless the route says otherwise.\n */\n protected readonly deletedMode = signal<SparkDeletedFilter>('exclude');\n\n /** Bumped by `context.reload()`; the grid treats any new value as \"re-run the query\". */\n private readonly reloadToken = signal(0);\n\n protected readonly listContext = computed((): SparkQueryListContext | null => {\n const query = this.query();\n if (!query) return null;\n return {\n query,\n entityType: this.entityType(),\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n setDeleted: (mode: SparkDeletedFilter) => this.setDeleted(mode),\n reload: () => this.reloadToken.update(n => n + 1),\n };\n });\n\n protected readonly gridReloadToken = this.reloadToken.asReadonly();\n\n private setDeleted(mode: SparkDeletedFilter): void {\n this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: mode === 'exclude' ? null : mode },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n /** Whether an action's selection rule is satisfied. Delegated: the grid holds the selection. */\n /** @see SparkPoDetailComponent.customActionClass — same allow-list, same default. */\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /** The grid's toolbar model (#460, M15), the same list the sub-query card renders. */\n protected readonly toolbarActions = computed(() => this.grid()?.toolbarActions() ?? []);\n\n protected toolbarActionClass(action: SparkQueryToolbarAction): string {\n // New keeps its solid primary look; the rest follow their variant.\n return action.kind === 'new' ? 'btn btn-primary' : sparkActionClass(action.definition);\n }\n\n protected isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean {\n return this.grid()?.isToolbarActionEnabled(action) ?? false;\n }\n\n protected runToolbarAction(action: SparkQueryToolbarAction): void {\n if (action.kind === 'new') {\n // Through this page's own onCreate, so `createClicked` fires exactly as it always has.\n this.onCreate();\n return;\n }\n void this.grid()?.runToolbarAction(action);\n }\n\n protected isActionEnabled(action: CustomActionDefinition): boolean {\n return this.grid()?.isActionEnabled(action) ?? false;\n }\n\n // --- streaming ---------------------------------------------------------------\n\n isStreaming = signal(false);\n private streamingSub: Subscription | null = null;\n /** Columns as sent with the stream's snapshot; empty until it arrives. */\n protected readonly streamColumns = signal<QueryColumn[]>([]);\n private readonly allItems = signal<QueryResultItem[]>([]);\n private readonly streamItems = signal<QueryResultItem[]>([]);\n\n /**\n * Rows handed to the grid, or `null` to let it fetch for itself.\n *\n * Null for a normal query — an empty array would read as \"here are no rows\" and suppress the\n * fetch entirely.\n */\n protected readonly gridData = computed(() =>\n this.query()?.isStreamingQuery ? this.streamItems() : null);\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => {\n // The handler is async and this is a subscribe, so a rejection lands nowhere: the metadata\n // load would reject, the query would stay null, and the template would render a spinner\n // FOREVER — while this component has had an errorMessage surface all along that only the\n // fetch path ever reached.\n this.onParamsChange(params).catch((e: unknown) => this.reportLoadFailure(e as HttpErrorResponse));\n });\n\n // Separate from paramMap: switching the recycle bin on changes only the query string, and must\n // not re-resolve the query (which would reset page, sort and filters for nothing).\n this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe(query => {\n this.deletedMode.set(parseSparkDeletedParam(query.get('deleted')) ?? 'exclude');\n this.applyUrlScope(query);\n });\n\n this.destroyRef.onDestroy(() => this.disconnectStreaming());\n\n // Connect the socket once the grid has resolved a streaming query, and disconnect whenever it\n // resolves anything else. The grid knows not to fetch for a streaming query, so there is no\n // window in which both transports are live.\n effect(() => {\n const q = this.query();\n if (q?.isStreamingQuery) {\n this.connectStreaming(q.id);\n } else {\n this.disconnectStreaming();\n }\n });\n\n // Client-side filter and sort for the streaming snapshot. The server never sees these: there\n // is no request to attach them to.\n effect(() => {\n this.searchTerm();\n this.grid()?.settings();\n this.allItems();\n if (this.isStreaming()) this.applyFilter();\n });\n }\n\n // --- URL scope: parent and column filters ----------------------------------------------------\n\n /**\n * The parent the URL scopes the query to (`?parentId=…&parentType=…`), sent exactly as a sub-query\n * sends its container. Both or neither: one without the other is ignored, as the server does.\n */\n protected readonly urlParentId = signal('');\n protected readonly urlParentType = signal('');\n\n /**\n * Column filters from the URL: every other query-string parameter, `?Language=en&Script=Latn`\n * (repeat a name for several values). The grid applies only those naming an attribute of the\n * query's type; the rest are ignored. This is the contributions History link's shape, and works for\n * any query.\n */\n protected readonly urlFilters = signal<QueryColumnFilter[]>([]);\n\n /** The URL filters the grid applied, as removable chips. */\n protected readonly activeUrlFilters = computed(() => this.grid()?.appliedPresetFilters() ?? []);\n\n /** Query-string names the page or the row links own, never column filters. */\n private static readonly reservedParams = new Set(['deleted', 'parentId', 'parentType', 'queryId', 'parentDeleted']);\n\n private applyUrlScope(query: ParamMap): void {\n const parentId = query.get('parentId') ?? '';\n const parentType = query.get('parentType') ?? '';\n const both = !!parentId && !!parentType;\n if (this.urlParentId() !== (both ? parentId : '')) this.urlParentId.set(both ? parentId : '');\n if (this.urlParentType() !== (both ? parentType : '')) this.urlParentType.set(both ? parentType : '');\n\n const filters: QueryColumnFilter[] = query.keys\n .filter(key => !SparkQueryListComponent.reservedParams.has(key))\n .map(key => ({ name: key, includes: query.getAll(key).filter(v => v !== '') }))\n .filter(f => f.includes.length > 0);\n // Only on a real change: a new array would refetch the grid for nothing (the Deleted toggle\n // also changes the query string).\n if (JSON.stringify(filters) !== JSON.stringify(this.urlFilters())) this.urlFilters.set(filters);\n }\n\n /** Drops one URL filter (its query-string parameter); the grid refetches. */\n protected clearUrlFilter(name: string): void {\n this.router.navigate([], { relativeTo: this.route, queryParams: { [name]: null }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n /** Drops the parent and every URL filter. */\n protected clearUrlScope(): void {\n const queryParams: Record<string, null> = { parentId: null, parentType: null };\n for (const filter of this.urlFilters()) queryParams[filter.name] = null;\n this.router.navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n protected filterValues(filter: QueryColumnFilter): string {\n return (filter.includes ?? []).map(v => String(v)).join(', ');\n }\n\n /** The column's label for a chip: the query's own column label, else the attribute's, else its name. */\n protected filterLabel(name: string): string {\n const attribute = this.entityType()?.attributes?.find(a => a.name === name);\n return this.lang.resolve(attribute?.label) || name;\n }\n\n private async onParamsChange(params: ParamMap): Promise<void> {\n this.errorMessage.set(null);\n this.queryId.set(null);\n this.allItems.set([]);\n this.streamItems.set([]);\n this.streamColumns.set([]);\n this.disconnectStreaming();\n\n const queryId = params.get('queryId');\n const typeParam = params.get('type');\n\n if (queryId) {\n this.queryId.set(queryId);\n return;\n }\n\n if (!typeParam) return;\n\n // `po/:type` — the route names an entity type, so find the query that lists it. The grid takes\n // a query, and this translation is the only reason it cannot take the route directly.\n const [entityTypes, queries] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.getQueries(),\n ]);\n const entityType = entityTypes.find(t => t.id === typeParam || t.alias === typeParam);\n if (!entityType) {\n this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n return;\n }\n\n const singularName = entityType.name;\n const match = queries.find(q => {\n if (q.entityType === singularName) return true;\n const sourceName = q.source.includes('.') ? q.source.substring(q.source.indexOf('.') + 1) : q.source;\n return sourceName === singularName || sourceName === singularName + 's';\n });\n\n if (match) this.queryId.set(match.alias || match.id);\n else this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n }\n\n /**\n * A load failure has to render, not just be swallowed: a denied query answers 404 (audit M-3, so\n * existence is not leaked), which is indistinguishable from a missing one — hence a deliberately\n * generic message rather than a guess at which it was.\n */\n private reportLoadFailure(err: HttpErrorResponse): void {\n this.errorMessage.set(\n err?.status === 404\n ? (this.lang.t('spark.query.unavailable') || 'This list is not available.')\n : (err?.error?.error || err?.message || 'An unexpected error occurred'));\n }\n\n protected async onCustomAction(action: CustomActionDefinition): Promise<void> {\n await this.grid()?.onCustomAction(action);\n }\n\n protected onCreate(): void {\n this.createClicked.emit();\n const et = this.entityType();\n if (et) this.router.navigate(['/po', et.alias || et.id, 'new']);\n }\n\n protected clearSearch(): void {\n this.searchTerm.set('');\n }\n\n private connectStreaming(queryId: string): void {\n if (this.streamingSub) return;\n this.isStreaming.set(true);\n\n this.streamingSub = this.streamingService.connectToStreamingQuery(queryId).subscribe({\n next: (message) => this.handleStreamingMessage(message),\n error: (err) => {\n this.errorMessage.set(err?.message || 'Streaming connection failed');\n this.isStreaming.set(false);\n },\n complete: () => this.isStreaming.set(false),\n });\n }\n\n private disconnectStreaming(): void {\n if (this.streamingSub) {\n this.streamingSub.unsubscribe();\n this.streamingSub = null;\n }\n this.isStreaming.set(false);\n }\n\n private handleStreamingMessage(message: StreamingMessage): void {\n switch (message.type) {\n case 'snapshot':\n this.errorMessage.set(null);\n // Columns come with the snapshot and never change for the life of the stream, so they are\n // stored once and handed to the grid alongside the rows — a projection cannot describe\n // itself, and the grid has no entity-type metadata to fall back on any more.\n this.streamColumns.set(message.columns);\n this.allItems.set(message.data);\n break;\n\n case 'patch':\n if (message.updated.length > 0) {\n this.allItems.update(items => items.map(item => {\n const patch = message.updated.find(u => u.id === item.id);\n if (!patch) return item;\n return {\n ...item,\n values: item.values.map(v =>\n v.key in patch.values ? { ...v, value: patch.values[v.key] } : v),\n };\n }));\n }\n break;\n\n case 'error':\n this.errorMessage.set(message.message);\n break;\n }\n }\n\n private applyFilter(): void {\n let items = this.allItems();\n\n const term = this.searchTerm().toLowerCase();\n if (term) {\n items = items.filter(item =>\n item.values.some(v => String(v.value ?? '').toLowerCase().includes(term)));\n }\n\n // The datatable in `[data]` mode also sorts on header clicks, but the sort must survive a\n // patch: re-deriving from `allItems` without re-applying it would silently reorder the grid\n // under the user on every update.\n const sortCols = this.grid()?.settings().sortColumns ?? [];\n if (sortCols.length > 0) {\n const dataTypes = new Map(this.streamColumns().map(c => [c.name, c.dataType]));\n items = [...items].sort((a, b) => {\n for (const col of sortCols) {\n const aVal = a.values.find(v => v.key === col.property)?.value;\n const bVal = b.values.find(v => v.key === col.property)?.value;\n const cmp = compareStreamValues(aVal, bVal, dataTypes.get(col.property));\n if (cmp !== 0) return col.direction === 'descending' ? -cmp : cmp;\n }\n return 0;\n });\n }\n\n this.streamItems.set(items);\n }\n}\n\n/**\n * Orders two streamed cell values the way the server orders the same column.\n *\n * The values are what the wire carries: a date is the raw ISO string WITH its own offset and with\n * trailing fraction zeros trimmed (`…T09:00:00-05:00`, `…T09:00:00.5-05:00`), a number is a number.\n * Comparing them as text sorted dates by wall clock and `10` before `9`; a date compares by instant\n * and a number numerically (docs/datetimeoffset_query_sort_filter_PRD.md, F4).\n *\n * A missing value sorts first ascending, as the empty string always did.\n */\nfunction compareStreamValues(a: unknown, b: unknown, dataType: string | undefined): number {\n const aMissing = a === null || a === undefined || a === '';\n const bMissing = b === null || b === undefined || b === '';\n if (aMissing || bMissing) return aMissing === bMissing ? 0 : aMissing ? -1 : 1;\n\n if (isDateDataType(dataType)) {\n const aDate = parseWireDate(a);\n const bDate = parseWireDate(b);\n if (aDate && bDate) return aDate.getTime() - bDate.getTime();\n } else if (dataType === 'number' || dataType === 'decimal') {\n const aNumber = Number(a);\n const bNumber = Number(b);\n if (!isNaN(aNumber) && !isNaN(bNumber)) return aNumber - bNumber;\n }\n\n return String(a).localeCompare(String(b));\n}\n","<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-query-list.mjs","names":[],"sources":["../../query-list/src/spark-query-list.component.ts","../../query-list/src/spark-query-list.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, viewChild, TemplateRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { Subscription } from 'rxjs';\nimport { CommonModule, NgTemplateOutlet } from '@angular/common';\nimport { ActivatedRoute, ParamMap, Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { SparkService, SparkStreamingService, SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryGridComponent, SparkQueryToolbarAction, SparkSearchBoxComponent, sparkActionClass } from '@mintplayer/ng-spark/grid';\nimport {\n CustomActionDefinition,\n StreamingMessage,\n QueryColumn,\n QueryColumnFilter,\n QueryResultItem,\n SparkDeletedFilter,\n isDateDataType,\n parseWireDate,\n type SparkSelectionModeSetting,\n} from '@mintplayer/ng-spark/models';\nimport { NgComponentOutlet } from '@angular/common';\nimport {\n SPARK_QUERY_LIST_ACTIONS,\n SparkQueryListContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\n\n/**\n * The routed query page: chrome around one {@link SparkQueryGridComponent}.\n *\n * It owns what is genuinely page-shaped and route-shaped, and nothing else:\n *\n * - **Route resolution.** It has no `queryId` input; it reads `paramMap`, and it serves two\n * routes — `query/:queryId`, and `po/:type`, which resolves an entity type to a query. That\n * second one is type-to-query resolution, not query rendering, and is why this component still\n * exists rather than the router pointing at the grid.\n * - **Streaming.** The websocket lives here so it stays out of every PO detail page's bundle;\n * the snapshot is filtered and sorted client-side and handed to the grid as `[data]`.\n * - The action bar, the caption, the LIVE badge, the search box and the New button.\n *\n * The grid itself — columns, cells, paging, the row link, selection, custom-action execution —\n * is the shared component. This page previously wrote out `<bs-datatable>` twice, once per\n * transport, with a shared row template between them; both are gone.\n */\n@Component({\n selector: 'spark-query-list',\n imports: [BsBadgeComponent, CommonModule, NgTemplateOutlet, NgComponentOutlet, BsAlertComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryGridComponent, SparkSearchBoxComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-query-list.component.html',\n styleUrl: './spark-query-list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.virtual-scrolling]': 'isVirtualScrolling()'\n }\n})\nexport class SparkQueryListComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly streamingService = inject(SparkStreamingService);\n private readonly destroyRef = inject(DestroyRef);\n protected readonly lang = inject(SparkLanguageService);\n\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n showCustomActions = input(true);\n\n /**\n * Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a\n * host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.\n */\n selectionMode = input<SparkSelectionModeSetting | null>(null);\n\n /**\n * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card\n * forwards it. Without this the escape hatch existed only for a directly-embedded grid.\n */\n rowRoute = input<((row: QueryResultItem) => unknown[] | null) | null>(null);\n\n rowClicked = output<QueryResultItem>();\n createClicked = output<void>();\n customActionExecuted = output<{ action: CustomActionDefinition }>();\n\n colors = Color;\n\n /** The query the grid should render, resolved from the route. Null until it is known. */\n queryId = signal<string | null>(null);\n errorMessage = signal<string | null>(null);\n searchTerm = signal('');\n\n private readonly grid = viewChild(SparkQueryGridComponent);\n\n /**\n * Grid state, surfaced for this page's chrome.\n *\n * Optional `viewChild`, read defensively: the action bar and caption render above the grid, so\n * on the first change-detection pass the query has not resolved yet.\n */\n protected readonly query = computed(() => this.grid()?.query() ?? null);\n protected readonly entityType = computed(() => this.grid()?.entityType() ?? null);\n protected readonly customActions = computed(() => this.grid()?.customActions() ?? []);\n protected readonly canCreate = computed(() => this.grid()?.offersCreate() ?? false);\n protected readonly resultCount = computed(() => this.grid()?.resultCount() ?? null);\n protected readonly isVirtualScrolling = computed(() => this.grid()?.isVirtualScrolling() ?? false);\n protected readonly gridError = computed(() => this.grid()?.errorMessage() ?? null);\n protected readonly permissions = computed(() => this.grid()?.permissions() ?? null);\n\n /** Add-on buttons for the action bar (#460, `SPARK_QUERY_LIST_ACTIONS`), e.g. the Deleted toggle. */\n protected readonly listActions = orderSparkExtensions(inject(SPARK_QUERY_LIST_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n /**\n * The soft-deletion mode (#460, T2), read from the route's `?deleted=` so the recycle bin survives\n * a reload and the back button from a row opened in it. `exclude` unless the route says otherwise.\n */\n protected readonly deletedMode = signal<SparkDeletedFilter>('exclude');\n\n /** Bumped by `context.reload()`; the grid treats any new value as \"re-run the query\". */\n private readonly reloadToken = signal(0);\n\n protected readonly listContext = computed((): SparkQueryListContext | null => {\n const query = this.query();\n if (!query) return null;\n return {\n query,\n entityType: this.entityType(),\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n setDeleted: (mode: SparkDeletedFilter) => this.setDeleted(mode),\n reload: () => this.reloadToken.update(n => n + 1),\n };\n });\n\n protected readonly gridReloadToken = this.reloadToken.asReadonly();\n\n private setDeleted(mode: SparkDeletedFilter): void {\n this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: mode === 'exclude' ? null : mode },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n /** Whether an action's selection rule is satisfied. Delegated: the grid holds the selection. */\n /** @see SparkPoDetailComponent.customActionClass — same allow-list, same default. */\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /** The grid's toolbar model (#460, M15), the same list the sub-query card renders. */\n protected readonly toolbarActions = computed(() => this.grid()?.toolbarActions() ?? []);\n\n protected toolbarActionClass(action: SparkQueryToolbarAction): string {\n // New keeps its solid primary look; the rest follow their variant.\n return action.kind === 'new' ? 'btn btn-primary' : sparkActionClass(action.definition);\n }\n\n protected isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean {\n return this.grid()?.isToolbarActionEnabled(action) ?? false;\n }\n\n /** The tooltip of an action disabled for want of its client method (`requiresClient`); null otherwise. */\n protected toolbarActionUnavailableReason(action: SparkQueryToolbarAction): string | null {\n return this.grid()?.actionUnavailableReason(action.definition) ?? null;\n }\n\n protected runToolbarAction(action: SparkQueryToolbarAction): void {\n if (action.kind === 'new') {\n // Through this page's own onCreate, so `createClicked` fires exactly as it always has.\n this.onCreate();\n return;\n }\n void this.grid()?.runToolbarAction(action);\n }\n\n protected isActionEnabled(action: CustomActionDefinition): boolean {\n return this.grid()?.isActionEnabled(action) ?? false;\n }\n\n // --- streaming ---------------------------------------------------------------\n\n isStreaming = signal(false);\n private streamingSub: Subscription | null = null;\n /** Columns as sent with the stream's snapshot; empty until it arrives. */\n protected readonly streamColumns = signal<QueryColumn[]>([]);\n private readonly allItems = signal<QueryResultItem[]>([]);\n private readonly streamItems = signal<QueryResultItem[]>([]);\n\n /**\n * Rows handed to the grid, or `null` to let it fetch for itself.\n *\n * Null for a normal query — an empty array would read as \"here are no rows\" and suppress the\n * fetch entirely.\n */\n protected readonly gridData = computed(() =>\n this.query()?.isStreamingQuery ? this.streamItems() : null);\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => {\n // The handler is async and this is a subscribe, so a rejection lands nowhere: the metadata\n // load would reject, the query would stay null, and the template would render a spinner\n // FOREVER — while this component has had an errorMessage surface all along that only the\n // fetch path ever reached.\n this.onParamsChange(params).catch((e: unknown) => this.reportLoadFailure(e as HttpErrorResponse));\n });\n\n // Separate from paramMap: switching the recycle bin on changes only the query string, and must\n // not re-resolve the query (which would reset page, sort and filters for nothing).\n this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe(query => {\n this.deletedMode.set(parseSparkDeletedParam(query.get('deleted')) ?? 'exclude');\n this.applyUrlScope(query);\n });\n\n this.destroyRef.onDestroy(() => this.disconnectStreaming());\n\n // Connect the socket once the grid has resolved a streaming query, and disconnect whenever it\n // resolves anything else. The grid knows not to fetch for a streaming query, so there is no\n // window in which both transports are live.\n effect(() => {\n const q = this.query();\n if (q?.isStreamingQuery) {\n this.connectStreaming(q.id);\n } else {\n this.disconnectStreaming();\n }\n });\n\n // Client-side filter and sort for the streaming snapshot. The server never sees these: there\n // is no request to attach them to.\n effect(() => {\n this.searchTerm();\n this.grid()?.settings();\n this.allItems();\n if (this.isStreaming()) this.applyFilter();\n });\n }\n\n // --- URL scope: parent and column filters ----------------------------------------------------\n\n /**\n * The parent the URL scopes the query to (`?parentId=…&parentType=…`), sent exactly as a sub-query\n * sends its container. Both or neither: one without the other is ignored, as the server does.\n */\n protected readonly urlParentId = signal('');\n protected readonly urlParentType = signal('');\n\n /**\n * Column filters from the URL: every other query-string parameter, `?Language=en&Script=Latn`\n * (repeat a name for several values). The grid applies only those naming an attribute of the\n * query's type; the rest are ignored. This is the contributions History link's shape, and works for\n * any query.\n */\n protected readonly urlFilters = signal<QueryColumnFilter[]>([]);\n\n /** The URL filters the grid applied, as removable chips. */\n protected readonly activeUrlFilters = computed(() => this.grid()?.appliedPresetFilters() ?? []);\n\n /** Query-string names the page or the row links own, never column filters. */\n private static readonly reservedParams = new Set(['deleted', 'parentId', 'parentType', 'queryId', 'parentDeleted']);\n\n private applyUrlScope(query: ParamMap): void {\n const parentId = query.get('parentId') ?? '';\n const parentType = query.get('parentType') ?? '';\n const both = !!parentId && !!parentType;\n if (this.urlParentId() !== (both ? parentId : '')) this.urlParentId.set(both ? parentId : '');\n if (this.urlParentType() !== (both ? parentType : '')) this.urlParentType.set(both ? parentType : '');\n\n const filters: QueryColumnFilter[] = query.keys\n .filter(key => !SparkQueryListComponent.reservedParams.has(key))\n .map(key => ({ name: key, includes: query.getAll(key).filter(v => v !== '') }))\n .filter(f => f.includes.length > 0);\n // Only on a real change: a new array would refetch the grid for nothing (the Deleted toggle\n // also changes the query string).\n if (JSON.stringify(filters) !== JSON.stringify(this.urlFilters())) this.urlFilters.set(filters);\n }\n\n /** Drops one URL filter (its query-string parameter); the grid refetches. */\n protected clearUrlFilter(name: string): void {\n this.router.navigate([], { relativeTo: this.route, queryParams: { [name]: null }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n /** Drops the parent and every URL filter. */\n protected clearUrlScope(): void {\n const queryParams: Record<string, null> = { parentId: null, parentType: null };\n for (const filter of this.urlFilters()) queryParams[filter.name] = null;\n this.router.navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n protected filterValues(filter: QueryColumnFilter): string {\n return (filter.includes ?? []).map(v => String(v)).join(', ');\n }\n\n /** The column's label for a chip: the query's own column label, else the attribute's, else its name. */\n protected filterLabel(name: string): string {\n const attribute = this.entityType()?.attributes?.find(a => a.name === name);\n return this.lang.resolve(attribute?.label) || name;\n }\n\n private async onParamsChange(params: ParamMap): Promise<void> {\n this.errorMessage.set(null);\n this.queryId.set(null);\n this.allItems.set([]);\n this.streamItems.set([]);\n this.streamColumns.set([]);\n this.disconnectStreaming();\n\n const queryId = params.get('queryId');\n const typeParam = params.get('type');\n\n if (queryId) {\n this.queryId.set(queryId);\n return;\n }\n\n if (!typeParam) return;\n\n // `po/:type` — the route names an entity type, so find the query that lists it. The grid takes\n // a query, and this translation is the only reason it cannot take the route directly.\n const [entityTypes, queries] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.getQueries(),\n ]);\n const entityType = entityTypes.find(t => t.id === typeParam || t.alias === typeParam);\n if (!entityType) {\n this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n return;\n }\n\n const singularName = entityType.name;\n const match = queries.find(q => {\n if (q.entityType === singularName) return true;\n const sourceName = q.source.includes('.') ? q.source.substring(q.source.indexOf('.') + 1) : q.source;\n return sourceName === singularName || sourceName === singularName + 's';\n });\n\n if (match) this.queryId.set(match.alias || match.id);\n else this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n }\n\n /**\n * A load failure has to render, not just be swallowed: a denied query answers 404 (audit M-3, so\n * existence is not leaked), which is indistinguishable from a missing one — hence a deliberately\n * generic message rather than a guess at which it was.\n */\n private reportLoadFailure(err: HttpErrorResponse): void {\n this.errorMessage.set(\n err?.status === 404\n ? (this.lang.t('spark.query.unavailable') || 'This list is not available.')\n : (err?.error?.error || err?.message || 'An unexpected error occurred'));\n }\n\n protected async onCustomAction(action: CustomActionDefinition): Promise<void> {\n await this.grid()?.onCustomAction(action);\n }\n\n protected onCreate(): void {\n this.createClicked.emit();\n const et = this.entityType();\n if (et) this.router.navigate(['/po', et.alias || et.id, 'new']);\n }\n\n protected clearSearch(): void {\n this.searchTerm.set('');\n }\n\n private connectStreaming(queryId: string): void {\n if (this.streamingSub) return;\n this.isStreaming.set(true);\n\n this.streamingSub = this.streamingService.connectToStreamingQuery(queryId).subscribe({\n next: (message) => this.handleStreamingMessage(message),\n error: (err) => {\n this.errorMessage.set(err?.message || 'Streaming connection failed');\n this.isStreaming.set(false);\n },\n complete: () => this.isStreaming.set(false),\n });\n }\n\n private disconnectStreaming(): void {\n if (this.streamingSub) {\n this.streamingSub.unsubscribe();\n this.streamingSub = null;\n }\n this.isStreaming.set(false);\n }\n\n private handleStreamingMessage(message: StreamingMessage): void {\n switch (message.type) {\n case 'snapshot':\n this.errorMessage.set(null);\n // Columns come with the snapshot and never change for the life of the stream, so they are\n // stored once and handed to the grid alongside the rows — a projection cannot describe\n // itself, and the grid has no entity-type metadata to fall back on any more.\n this.streamColumns.set(message.columns);\n this.allItems.set(message.data);\n break;\n\n case 'patch':\n if (message.updated.length > 0) {\n this.allItems.update(items => items.map(item => {\n const patch = message.updated.find(u => u.id === item.id);\n if (!patch) return item;\n return {\n ...item,\n values: item.values.map(v =>\n v.key in patch.values ? { ...v, value: patch.values[v.key] } : v),\n };\n }));\n }\n break;\n\n case 'error':\n this.errorMessage.set(message.message);\n break;\n }\n }\n\n private applyFilter(): void {\n let items = this.allItems();\n\n const term = this.searchTerm().toLowerCase();\n if (term) {\n items = items.filter(item =>\n item.values.some(v => String(v.value ?? '').toLowerCase().includes(term)));\n }\n\n // The datatable in `[data]` mode also sorts on header clicks, but the sort must survive a\n // patch: re-deriving from `allItems` without re-applying it would silently reorder the grid\n // under the user on every update.\n const sortCols = this.grid()?.settings().sortColumns ?? [];\n if (sortCols.length > 0) {\n const dataTypes = new Map(this.streamColumns().map(c => [c.name, c.dataType]));\n items = [...items].sort((a, b) => {\n for (const col of sortCols) {\n const aVal = a.values.find(v => v.key === col.property)?.value;\n const bVal = b.values.find(v => v.key === col.property)?.value;\n const cmp = compareStreamValues(aVal, bVal, dataTypes.get(col.property));\n if (cmp !== 0) return col.direction === 'descending' ? -cmp : cmp;\n }\n return 0;\n });\n }\n\n this.streamItems.set(items);\n }\n}\n\n/**\n * Orders two streamed cell values the way the server orders the same column.\n *\n * The values are what the wire carries: a date is the raw ISO string WITH its own offset and with\n * trailing fraction zeros trimmed (`…T09:00:00-05:00`, `…T09:00:00.5-05:00`), a number is a number.\n * Comparing them as text sorted dates by wall clock and `10` before `9`; a date compares by instant\n * and a number numerically (docs/datetimeoffset_query_sort_filter_PRD.md, F4).\n *\n * A missing value sorts first ascending, as the empty string always did.\n */\nfunction compareStreamValues(a: unknown, b: unknown, dataType: string | undefined): number {\n const aMissing = a === null || a === undefined || a === '';\n const bMissing = b === null || b === undefined || b === '';\n if (aMissing || bMissing) return aMissing === bMissing ? 0 : aMissing ? -1 : 1;\n\n if (isDateDataType(dataType)) {\n const aDate = parseWireDate(a);\n const bDate = parseWireDate(b);\n if (aDate && bDate) return aDate.getTime() - bDate.getTime();\n } else if (dataType === 'number' || dataType === 'decimal') {\n const aNumber = Number(a);\n const bNumber = Number(b);\n if (!isNaN(aNumber) && !isNaN(bNumber)) return aNumber - bNumber;\n }\n\n return String(a).localeCompare(String(b));\n}\n","<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\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 @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { InjectionToken, reflectComponentType } from "@angular/core";
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, InjectionToken, computed, input, reflectComponentType } from "@angular/core";
2
3
  import { ShowedOn, hasShowedOnFlag } from "@mintplayer/ng-spark/models";
3
4
  const declaredInputs = /* @__PURE__ */ new Map();
4
5
  function withDeclaredInputs(component, inputs) {
@@ -15,11 +16,115 @@ function rendererValue(attr) {
15
16
  function cellValue(value) {
16
17
  return value?.value;
17
18
  }
18
- const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken("SparkAttributeRenderers", { factory: () => [] });
19
+ var SparkParagraphRendererComponent = class SparkParagraphRendererComponent {
20
+ value = input(...ngDevMode ? [void 0, { debugName: "value" }] : /* istanbul ignore next */ []);
21
+ options = input(...ngDevMode ? [void 0, { debugName: "options" }] : /* istanbul ignore next */ []);
22
+ text = computed(() => {
23
+ const value = this.value();
24
+ return value === null || value === void 0 ? "" : String(value);
25
+ }, ...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
26
+ asHtml = computed(() => this.options()?.["sanitize"] === false, ...ngDevMode ? [{ debugName: "asHtml" }] : /* istanbul ignore next */ []);
27
+ paragraphs = computed(() => {
28
+ const text = this.text().replace(/\r\n?/g, "\n").trim();
29
+ if (!text) return [];
30
+ return text.split(/\n[ \t]*\n+/).map((paragraph) => paragraph.split("\n"));
31
+ }, ...ngDevMode ? [{ debugName: "paragraphs" }] : /* istanbul ignore next */ []);
32
+ static ɵfac = i0.ɵɵngDeclareFactory({
33
+ minVersion: "12.0.0",
34
+ version: "22.2.0",
35
+ ngImport: i0,
36
+ type: SparkParagraphRendererComponent,
37
+ deps: [],
38
+ target: i0.ɵɵFactoryTarget.Component
39
+ });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({
41
+ minVersion: "17.0.0",
42
+ version: "22.2.0",
43
+ type: SparkParagraphRendererComponent,
44
+ isStandalone: true,
45
+ selector: "spark-paragraph-renderer",
46
+ inputs: {
47
+ value: {
48
+ classPropertyName: "value",
49
+ publicName: "value",
50
+ isSignal: true,
51
+ isRequired: false,
52
+ transformFunction: null
53
+ },
54
+ options: {
55
+ classPropertyName: "options",
56
+ publicName: "options",
57
+ isSignal: true,
58
+ isRequired: false,
59
+ transformFunction: null
60
+ }
61
+ },
62
+ ngImport: i0,
63
+ template: `
64
+ @if (asHtml()) {
65
+ <div class="spark-paragraph" [innerHTML]="text()"></div>
66
+ } @else {
67
+ @for (paragraph of paragraphs(); track $index) {
68
+ <p class="spark-paragraph">@for (line of paragraph; track $index) {@if (!$first) {<br />}{{ line }}}</p>
69
+ }
70
+ }
71
+ `,
72
+ isInline: true,
73
+ styles: [":host{display:block}.spark-paragraph:last-child{margin-bottom:0}\n"],
74
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
75
+ });
76
+ };
77
+ i0.ɵɵngDeclareClassMetadata({
78
+ minVersion: "12.0.0",
79
+ version: "22.2.0",
80
+ ngImport: i0,
81
+ type: SparkParagraphRendererComponent,
82
+ decorators: [{
83
+ type: Component,
84
+ args: [{
85
+ selector: "spark-paragraph-renderer",
86
+ changeDetection: ChangeDetectionStrategy.OnPush,
87
+ template: `
88
+ @if (asHtml()) {
89
+ <div class="spark-paragraph" [innerHTML]="text()"></div>
90
+ } @else {
91
+ @for (paragraph of paragraphs(); track $index) {
92
+ <p class="spark-paragraph">@for (line of paragraph; track $index) {@if (!$first) {<br />}{{ line }}}</p>
93
+ }
94
+ }
95
+ `,
96
+ styles: [":host{display:block}.spark-paragraph:last-child{margin-bottom:0}\n"]
97
+ }]
98
+ }],
99
+ propDecorators: {
100
+ value: [{
101
+ type: i0.Input,
102
+ args: [{
103
+ isSignal: true,
104
+ alias: "value",
105
+ required: false
106
+ }]
107
+ }],
108
+ options: [{
109
+ type: i0.Input,
110
+ args: [{
111
+ isSignal: true,
112
+ alias: "options",
113
+ required: false
114
+ }]
115
+ }]
116
+ }
117
+ });
118
+ const sparkCoreRenderers = [{
119
+ name: "paragraph",
120
+ detailComponent: SparkParagraphRendererComponent,
121
+ fullWidth: true
122
+ }];
123
+ const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken("SparkAttributeRenderers", { factory: () => [...sparkCoreRenderers] });
19
124
  function provideSparkAttributeRenderers(renderers) {
20
125
  return {
21
126
  provide: SPARK_ATTRIBUTE_RENDERERS,
22
- useValue: renderers
127
+ useValue: [...renderers, ...sparkCoreRenderers]
23
128
  };
24
129
  }
25
130
  function isRowRendered(attribute, registry) {
@@ -49,6 +154,6 @@ function rowRendererInputs(renderer, row, ownerId) {
49
154
  ownerId
50
155
  });
51
156
  }
52
- export { SPARK_ATTRIBUTE_RENDERERS, cellValue, isRowRendered, provideSparkAttributeRenderers, rendererValue, resolveRowRenderer, rowRendererInputs, withDeclaredInputs };
157
+ export { SPARK_ATTRIBUTE_RENDERERS, SparkParagraphRendererComponent, cellValue, isRowRendered, provideSparkAttributeRenderers, rendererValue, resolveRowRenderer, rowRendererInputs, sparkCoreRenderers, withDeclaredInputs };
53
158
 
54
159
  //# sourceMappingURL=mintplayer-ng-spark-renderers.mjs.map