@mintplayer/ng-spark 22.31.0 → 22.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +101 -1
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
  4. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
  7. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
  8. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
  9. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
  10. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
  11. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
  12. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
  13. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
  14. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
  15. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
  16. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
  17. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
  18. package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
  19. package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
  20. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  21. package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
  22. package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
  23. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
  24. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
  25. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
  26. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
  27. package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
  28. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  29. package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
  30. package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
  31. package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
  32. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
  33. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
  34. package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
  35. package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
  36. package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
  37. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
  39. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +90 -26
  40. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
  42. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  43. package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
  44. package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
  45. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
  46. package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
  47. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  48. package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
  49. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
  50. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
  51. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
  52. package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
  53. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  54. package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
  55. package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
  56. package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
  57. package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
  58. package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
  59. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  60. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
  61. package/package.json +25 -1
  62. package/types/mintplayer-ng-spark-grid.d.ts +77 -26
  63. package/types/mintplayer-ng-spark-models.d.ts +150 -17
  64. package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
  65. package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
  66. package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
  67. package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
  68. package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
  69. package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
  70. package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
  71. package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
  72. package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
  73. package/types/mintplayer-ng-spark-services.d.ts +146 -6
  74. package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
  75. package/types/mintplayer-ng-spark-share.d.ts +26 -0
@@ -0,0 +1,340 @@
1
+ import { Color } from "@mintplayer/ng-bootstrap";
2
+ import { SparkAttributeColumnRenderer, SparkAttributeDetailRenderer, SparkAttributeRendererRegistration } from "@mintplayer/ng-spark/renderers";
3
+ import * as i0 from "@angular/core";
4
+ import { Provider } from "@angular/core";
5
+ import { TrendPoint, TrendSeries } from "@mintplayer/ng-bootstrap/charts/trend";
6
+ /** A Bootstrap theme colour a band paints with: `bg-*` on a bar, `--bs-*` on a gauge, `text-bg-*` on a grade. */
7
+ export type SparkScoreColor = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark';
8
+ /**
9
+ * One band of a score scale: it applies to a value `>= min`. The band without a `min` is the floor,
10
+ * applying to everything below the lowest `min`. Bands may be listed in any order; the highest `min`
11
+ * the value reaches wins.
12
+ */
13
+ export interface SparkScoreBand {
14
+ /** Inclusive lower bound. Omit for the floor band. */
15
+ min?: number;
16
+ color: SparkScoreColor;
17
+ /** A grade shown next to a gauge ("A"). Ignored by the threshold bar. */
18
+ label?: string;
19
+ }
20
+ /**
21
+ * The bands a `bands` / `grades` option accepts: a list, or the name of a preset (so a model's
22
+ * `rendererOptions` JSON, which cannot reference a TypeScript constant, can still pick one).
23
+ */
24
+ export type SparkScoreBandsOption = readonly SparkScoreBand[] | 'traffic-light' | 'letter-grades';
25
+ /**
26
+ * Green from 80, amber from 60, red below: the coverage thresholds the CodeCoverage badge and bar
27
+ * have always used. The default of every score renderer.
28
+ */
29
+ export declare const sparkTrafficLightBands: readonly SparkScoreBand[];
30
+ /** A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 50, else F: the grade scale of a 0–100 score (SeoTester). */
31
+ export declare const sparkLetterGrades: readonly SparkScoreBand[];
32
+ /** The bands an option names, or `fallback` for an absent or unrecognised one. */
33
+ export declare function resolveSparkScoreBands(option: unknown, fallback?: readonly SparkScoreBand[]): readonly SparkScoreBand[];
34
+ /** The band `value` falls in: the one with the highest `min` it reaches, else the floor, else none. */
35
+ export declare function sparkScoreBand(value: number | null | undefined, bands: readonly SparkScoreBand[]): SparkScoreBand | null;
36
+ /** The ng-bootstrap {@link Color} of `value`'s band; `secondary` when there is no value or no band. */
37
+ export declare function sparkScoreColor(value: number | null | undefined, bands?: readonly SparkScoreBand[]): Color;
38
+ /**
39
+ * A cell value as a number, or null for "no value". Numbers pass through, numeric strings parse,
40
+ * and null, undefined, the empty string and anything unparseable are no value. Never 0: an absent
41
+ * measurement must not draw as a real zero.
42
+ */
43
+ export declare function toSparkScoreNumber(raw: unknown): number | null;
44
+ /** `rendererOptions` of the `spark-threshold-bar` renderer. Every member is optional. */
45
+ export interface SparkThresholdBarOptions {
46
+ /** Scale minimum. Default 0. */
47
+ min?: number;
48
+ /** Scale maximum. Default 100. */
49
+ max?: number;
50
+ /** Colour bands. Default `'traffic-light'` (green ≥ 80, amber ≥ 60, red below). */
51
+ bands?: SparkScoreBandsOption;
52
+ /** Decimals of the shown value. Default 1. */
53
+ decimals?: number;
54
+ /** Text after the shown value. Default `'%'`. */
55
+ suffix?: string;
56
+ /** The bar's accessible name. Default `'Value'`. */
57
+ label?: string;
58
+ /** CSS width of the bar. Default `'80px'`. */
59
+ width?: string;
60
+ /** CSS height of the bar. Default `'8px'`. */
61
+ height?: string;
62
+ /** Show the value as text next to the bar. Default true. */
63
+ showValue?: boolean;
64
+ }
65
+ interface ThresholdBarView {
66
+ value: number;
67
+ text: string;
68
+ color: Color;
69
+ }
70
+ /**
71
+ * The `spark-threshold-bar` renderer: a compact progress bar coloured by the band the value falls
72
+ * in, with the value next to it. No value (null, undefined, empty, unparseable) draws a muted dash
73
+ * rather than an empty bar, which would read as a real 0. The colour comes from the unrounded value,
74
+ * so 59.95 shows "60.0%" yet stays below a 60 threshold.
75
+ *
76
+ * Generalised from CodeCoverage's coverage bar, which is this renderer with `label: 'Coverage'`.
77
+ */
78
+ export declare class SparkThresholdBarComponent implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
79
+ private readonly locale;
80
+ readonly value: import("@angular/core").InputSignal<any>;
81
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
82
+ protected readonly settings: import("@angular/core").Signal<{
83
+ min: number;
84
+ max: number;
85
+ bands: readonly import("@mintplayer/ng-spark/score-renderers").SparkScoreBand[];
86
+ decimals: number;
87
+ suffix: string;
88
+ label: string;
89
+ width: string;
90
+ height: string;
91
+ showValue: boolean;
92
+ }>;
93
+ protected readonly view: import("@angular/core").Signal<ThresholdBarView | null>;
94
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkThresholdBarComponent, never>;
95
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkThresholdBarComponent, "spark-threshold-bar", never, {
96
+ "value": {
97
+ "alias": "value";
98
+ "required": false;
99
+ "isSignal": true;
100
+ };
101
+ "options": {
102
+ "alias": "options";
103
+ "required": false;
104
+ "isSignal": true;
105
+ };
106
+ }, {}, never, never, true, never>;
107
+ }
108
+ /** `rendererOptions` of the `spark-delta` renderer. Every member is optional. */
109
+ export interface SparkDeltaOptions {
110
+ /** Decimals of the shown change. Default 1. */
111
+ decimals?: number;
112
+ /** A rise is good (green) and a drop bad (red). Set false for a cost, an error count, a load time. Default true. */
113
+ higherIsBetter?: boolean;
114
+ /** Text after the number, e.g. `' pt'` or `'%'`. Default none. */
115
+ suffix?: string;
116
+ /** Text for "no comparison". Default `'—'`. */
117
+ empty?: string;
118
+ }
119
+ /** How a delta reads: the text and its tone. */
120
+ export interface SparkDeltaView {
121
+ /** The raw change is positive. Rounding does not change it: +0.04 at one decimal is `+0.0`, still up. */
122
+ up: boolean;
123
+ /** The raw change is negative. */
124
+ down: boolean;
125
+ /** `+1.3`, `-0.3`, `0.0`, or the empty text when there is nothing to compare. */
126
+ text: string;
127
+ /** `text-success`, `text-danger` or `text-muted`, honouring `higherIsBetter`. */
128
+ tone: 'text-success' | 'text-danger' | 'text-muted';
129
+ }
130
+ /**
131
+ * Formats a signed change. No value (null, undefined, empty, unparseable) is the empty text and
132
+ * never `0.0`, which would claim a comparison that did not happen; an exact zero is a real, neutral
133
+ * comparison.
134
+ */
135
+ export declare function formatSparkDelta(raw: unknown, options?: SparkDeltaOptions | Record<string, any> | null): SparkDeltaView;
136
+ /**
137
+ * The `spark-delta` renderer: a signed change, green when it improves and red when it worsens
138
+ * (`higherIsBetter`), muted for no change, and a muted dash when there is nothing to compare.
139
+ *
140
+ * Generalised from CodeCoverage's coverage delta, which is this renderer with its defaults.
141
+ */
142
+ export declare class SparkDeltaComponent implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
143
+ readonly value: import("@angular/core").InputSignal<any>;
144
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
145
+ protected readonly view: import("@angular/core").Signal<SparkDeltaView>;
146
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkDeltaComponent, never>;
147
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkDeltaComponent, "spark-delta", never, {
148
+ "value": {
149
+ "alias": "value";
150
+ "required": false;
151
+ "isSignal": true;
152
+ };
153
+ "options": {
154
+ "alias": "options";
155
+ "required": false;
156
+ "isSignal": true;
157
+ };
158
+ }, {}, never, never, true, never>;
159
+ }
160
+ /** `rendererOptions` of the `spark-trend` renderer. Every member is optional. */
161
+ export interface SparkTrendOptions {
162
+ /** `'sparkline'` (default): a compact inline line for a cell. `'chart'`: an interactive chart with axes. */
163
+ display?: 'sparkline' | 'chart';
164
+ /** Fixed scale minimum, e.g. 0 for a percentage. Default: fitted to the data. */
165
+ yMin?: number;
166
+ /** Fixed scale maximum, e.g. 100 for a percentage. Default: fitted to the data. */
167
+ yMax?: number;
168
+ /** Accessible name of the line or chart. */
169
+ label?: string;
170
+ /** Fewer points than this draw the dash: one point is not a trend. Default 2. */
171
+ minPoints?: number;
172
+ /** Sparkline CSS width. Default `'90px'`. */
173
+ width?: string;
174
+ /** Sparkline CSS height. Default `'24px'`. */
175
+ height?: string;
176
+ /** Chart only: a goal line at this value. */
177
+ goal?: number;
178
+ /** Chart only: the goal line's label. */
179
+ goalLabel?: string;
180
+ /** Chart only: the series id. Default `'value'`. */
181
+ seriesId?: string;
182
+ /** Chart only: the series label (legend, tooltip). Default `label`. */
183
+ seriesLabel?: string;
184
+ }
185
+ /** One point of a trend value: a plain number (x = its index), null for a gap, or an explicit `{ x, y }`. */
186
+ export type SparkTrendValuePoint = number | null | {
187
+ x: number | Date;
188
+ y: number | null;
189
+ };
190
+ /**
191
+ * The value as `{ x, y }` points, or null when it is not a series or has fewer than `minPoints`.
192
+ * A number is a point at its index; an array holding anything else is not a series.
193
+ */
194
+ export declare function toSparkTrendPoints(value: unknown, minPoints?: number): TrendPoint[] | null;
195
+ /** What the three trend components share: the inputs, the options read once, and the points. */
196
+ declare abstract class SparkTrendBase implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
197
+ readonly value: import("@angular/core").InputSignal<any>;
198
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
199
+ protected readonly settings: import("@angular/core").Signal<{
200
+ display: "sparkline" | "chart";
201
+ yMin: number | undefined;
202
+ yMax: number | undefined;
203
+ label: string | undefined;
204
+ minPoints: number;
205
+ width: string;
206
+ height: string;
207
+ goal: number | undefined;
208
+ goalLabel: string | undefined;
209
+ seriesId: string;
210
+ seriesLabel: string;
211
+ }>;
212
+ protected readonly points: import("@angular/core").Signal<import("@mintplayer/web-components/charts/core").TrendPoint[] | null>;
213
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkTrendBase, never>;
214
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SparkTrendBase, never, never, {
215
+ "value": {
216
+ "alias": "value";
217
+ "required": false;
218
+ "isSignal": true;
219
+ };
220
+ "options": {
221
+ "alias": "options";
222
+ "required": false;
223
+ "isSignal": true;
224
+ };
225
+ }, {}, never, never, true, never>;
226
+ }
227
+ /**
228
+ * The sparkline half of `spark-trend`, usable on its own: a compact line for a cell, or a muted dash.
229
+ * Imports only the sparkline, so an app drawing sparklines does not load the trend chart with them.
230
+ */
231
+ export declare class SparkTrendSparklineComponent extends SparkTrendBase {
232
+ protected ys(points: TrendPoint[]): (number | null)[];
233
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkTrendSparklineComponent, never>;
234
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkTrendSparklineComponent, "spark-trend-sparkline", never, {}, {}, never, never, true, never>;
235
+ }
236
+ /**
237
+ * The chart half of `spark-trend`, usable on its own: an interactive trend chart of one series with
238
+ * an optional goal line, or a muted dash. Imports only the trend chart.
239
+ */
240
+ export declare class SparkTrendChartComponent extends SparkTrendBase {
241
+ protected readonly series: import("@angular/core").Signal<TrendSeries[]>;
242
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkTrendChartComponent, never>;
243
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkTrendChartComponent, "spark-trend-chart", never, {}, {}, never, never, true, never>;
244
+ }
245
+ /**
246
+ * The `spark-trend` renderer: a series as a sparkline in a cell, or with `display: 'chart'` as an
247
+ * interactive trend chart. The value is an array of numbers (null for a gap) or of `{ x, y }` points
248
+ * (x a number or a Date); anything else, or fewer than `minPoints` points, draws a muted dash.
249
+ *
250
+ * Generalised from CodeCoverage's repository sparkline (90×24 on a fixed 0–100 scale) and its
251
+ * "coverage over time" chart (an 80% goal line). Registering this renderer loads both charts; a
252
+ * template that needs only one uses `<spark-trend-sparkline>` or `<spark-trend-chart>`.
253
+ */
254
+ export declare class SparkTrendComponent extends SparkTrendBase {
255
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkTrendComponent, never>;
256
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkTrendComponent, "spark-trend", never, {}, {}, never, never, true, never>;
257
+ }
258
+ /** `rendererOptions` of the `spark-gauge` renderer. Every member is optional. */
259
+ export interface SparkGaugeOptions {
260
+ /** Scale minimum. Default 0. */
261
+ min?: number;
262
+ /** Scale maximum. Default 100. */
263
+ max?: number;
264
+ /**
265
+ * The bands that colour the ring and, when they carry a `label`, name the grade drawn next to it.
266
+ * `'letter-grades'` is A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 50, else F. Default `'traffic-light'` (no grade).
267
+ */
268
+ bands?: SparkScoreBandsOption;
269
+ /** Decimals of the score in the ring. Default 0. */
270
+ decimals?: number;
271
+ /** Text after the score, e.g. `'%'`. Default none. */
272
+ suffix?: string;
273
+ /** The gauge's accessible name. Default `'Score'`. */
274
+ label?: string;
275
+ /** Diameter in CSS pixels. Default 48. */
276
+ size?: number;
277
+ }
278
+ interface GaugeView {
279
+ value: number;
280
+ text: string;
281
+ color: SparkScoreColor;
282
+ grade: string | null;
283
+ /** The grade badge's colour (the band's). */
284
+ badge: Color;
285
+ /** Arc length on a ring of circumference 100: the share of the scale reached, clamped to 0–100. */
286
+ arc: number;
287
+ }
288
+ /**
289
+ * The `spark-gauge` renderer: a score as a ring filled to its share of the scale, coloured by its
290
+ * band, with the score inside and the band's grade (when the bands carry labels) beside it. No value
291
+ * draws a muted dash rather than an empty ring, which would read as a real 0.
292
+ *
293
+ * Exposed as an ARIA `meter` whose value text includes the grade ("72 (C)").
294
+ */
295
+ export declare class SparkGaugeComponent implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
296
+ private readonly locale;
297
+ readonly value: import("@angular/core").InputSignal<any>;
298
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
299
+ protected readonly settings: import("@angular/core").Signal<{
300
+ min: number;
301
+ max: number;
302
+ bands: readonly import("@mintplayer/ng-spark/score-renderers").SparkScoreBand[];
303
+ decimals: number;
304
+ suffix: string;
305
+ label: string;
306
+ size: number;
307
+ }>;
308
+ protected readonly view: import("@angular/core").Signal<GaugeView | null>;
309
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkGaugeComponent, never>;
310
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkGaugeComponent, "spark-gauge", never, {
311
+ "value": {
312
+ "alias": "value";
313
+ "required": false;
314
+ "isSignal": true;
315
+ };
316
+ "options": {
317
+ "alias": "options";
318
+ "required": false;
319
+ "isSignal": true;
320
+ };
321
+ }, {}, never, never, true, never>;
322
+ }
323
+ /**
324
+ * The score renderers under their generic names, each drawing both a grid cell and a detail value.
325
+ * Configured per attribute through the model's `rendererOptions`.
326
+ */
327
+ export declare const sparkScoreRenderers: readonly SparkAttributeRendererRegistration[];
328
+ /**
329
+ * Registers the score renderers (`spark-gauge`, `spark-threshold-bar`, `spark-trend`, `spark-delta`)
330
+ * together with the app's own. Use it **instead of** `provideSparkAttributeRenderers`, not next to
331
+ * it: both provide the one renderer list, and the last provider would win.
332
+ *
333
+ * The app's registrations come first, so an app renderer of the same name replaces a score renderer.
334
+ *
335
+ * @example
336
+ * provideSparkScoreRenderers([
337
+ * { name: 'coverage-delta', columnComponent: SparkDeltaComponent, detailComponent: SparkDeltaComponent },
338
+ * ])
339
+ */
340
+ export declare function provideSparkScoreRenderers(renderers?: SparkAttributeRendererRegistration[]): Provider;
@@ -1,5 +1,6 @@
1
- import { CustomActionDefinition, DistinctValuesResult, EntityPermissions, EntityType, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, ProgramUnitsConfiguration, QueryColumnFilter, QueryResult, RetryActionPayload, RetryActionResult, SparkDeletedFilter, SparkQuery, StreamingMessage, TranslatedString } from "@mintplayer/ng-spark/models";
1
+ import { CustomActionDefinition, DistinctValuesResult, EntityPermissions, EntityType, LivePoEvent, LiveQueryEvent, LiveQueryRequest, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, ProgramUnitsConfiguration, QueryColumnFilter, QueryResult, RetryActionPayload, RetryActionResult, SparkDeletedFilter, SparkQuery, TranslatedString } from "@mintplayer/ng-spark/models";
2
2
  import * as i0 from "@angular/core";
3
+ import { Injector, Signal } from "@angular/core";
3
4
  import { Observable } from "rxjs";
4
5
  import { SortColumn } from "@mintplayer/pagination";
5
6
  import { SafeHtml } from "@angular/platform-browser";
@@ -35,19 +36,105 @@ export declare class SparkLanguageService {
35
36
  private loadTranslations;
36
37
  setLanguage(lang: string): void;
37
38
  resolve(ts: TranslatedString | undefined): string;
38
- t(key: string): string;
39
+ /**
40
+ * The text of `key` in the current language. When there is none (unknown key, empty text, or the
41
+ * translations have not arrived yet) it is `fallback` when given, else the key itself, so
42
+ * `t('spark.query.unavailable', 'This list is not available.')` never shows a raw key to a user.
43
+ */
44
+ t(key: string, fallback?: string): string;
39
45
  static ɵfac: i0.ɵɵFactoryDeclaration<SparkLanguageService, never>;
40
46
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
41
47
  }
42
- export declare class SparkStreamingService {
48
+ /** Options of {@link SparkLiveService.watchPersistentObject}. */
49
+ export interface LivePoWatchOptions {
50
+ /** The soft-deletion mode the object is loaded with, as `/spark/po/load` takes it. */
51
+ deleted?: SparkDeletedFilter | null;
52
+ }
53
+ /** Options of the signal helpers: an injector when called outside an injection context. */
54
+ export interface LiveSignalOptions {
55
+ injector?: Injector;
56
+ }
57
+ /**
58
+ * Live updates over ONE lazy WebSocket to `/spark/live` (X4; `docs/guide-live-updates.md`).
59
+ *
60
+ * - Subscriptions are ref-counted by a canonical JSON of their request, so two components watching the
61
+ * same object or the same query page share one server subscription; the last one to leave unsubscribes,
62
+ * and the socket closes when nothing is watched.
63
+ * - `po`/`result` replace the held state and `poPatch`/`patch` are applied to it, so a watcher always
64
+ * receives the whole object or page.
65
+ * - A subscription's `error` or `gone` ends that subscription only: it is never a reason to reconnect.
66
+ * (The streaming service it replaces retried ten times on "access denied".)
67
+ * - The socket reconnects with exponential backoff and jitter after a network loss or a server restart,
68
+ * then resubscribes everything, which yields fresh snapshots. `4401` reconnects at once (the cookie is
69
+ * validated afresh) unless it repeats within seconds; `4429` and `1009` are surfaced, not retried.
70
+ * - The socket and its timers run outside the Angular zone; only emissions re-enter it.
71
+ */
72
+ export declare class SparkLiveService {
43
73
  private readonly config;
44
74
  private readonly baseUrl;
45
75
  private readonly ngZone;
46
- connectToStreamingQuery(queryId: string): Observable<StreamingMessage>;
76
+ private readonly entries;
77
+ private readonly bySub;
78
+ private nextSub;
79
+ private socket;
80
+ private reconnectTimer;
81
+ private attempt;
82
+ private lastReauthAt;
83
+ /**
84
+ * Follows one persistent object: `po` with the object as `/spark/po/load` returns it (patches applied),
85
+ * then `gone` or `error`, after either of which the stream completes.
86
+ */
87
+ watchPersistentObject(objectTypeId: string, id: string, options?: LivePoWatchOptions): Observable<LivePoEvent>;
88
+ /**
89
+ * Follows one query page: `result` with the page as `/spark/queries/execute` returns it (patches applied),
90
+ * or `error`, after which the stream completes.
91
+ *
92
+ * Given an observable, the page follows it: a new request is sent as an `update` of the same server
93
+ * subscription when this watcher has it to itself, and joins (or opens) the subscription of the new request
94
+ * otherwise. `null` leaves the current subscription without completing.
95
+ */
96
+ watchQuery(request: LiveQueryRequest | Observable<LiveQueryRequest | null>): Observable<LiveQueryEvent>;
97
+ /**
98
+ * A signal of one live object for a custom page: `null` until the first state (and after an `error`),
99
+ * the object afterwards, `'gone'` once it is deleted or no longer readable. Follows `type` and `id`;
100
+ * a null or empty id watches nothing. Call it in an injection context or pass an injector.
101
+ */
102
+ livePersistentObject(type: Signal<string>, id: Signal<string | null | undefined>, options?: LiveSignalOptions): Signal<PersistentObject | null | 'gone'>;
103
+ /**
104
+ * A signal of one live query page for a custom page: `null` until the first page (and after an `error`).
105
+ * A changed request (page, sort, search) is sent as an `update`; `null` watches nothing. Call it in an
106
+ * injection context or pass an injector.
107
+ */
108
+ liveQuery(request: Signal<LiveQueryRequest | null>, options?: LiveSignalOptions): Signal<QueryResult | null>;
109
+ private attach;
110
+ private update;
111
+ private detach;
112
+ private forget;
113
+ /** Ends one subscription for all its watchers: the event, then completion. */
114
+ private end;
115
+ /** Ends every subscription with a socket-level failure and drops the socket without reconnecting. */
116
+ private failAll;
117
+ private isOpen;
118
+ private ensureSocket;
119
+ private open;
120
+ private onClose;
121
+ /**
122
+ * Closes the socket once nothing is watched — after a microtask, so a watcher that switches objects
123
+ * (leave one, watch the next in the same turn) keeps the socket instead of reconnecting.
124
+ */
125
+ private closeIfIdle;
126
+ private closeSocket;
127
+ private sendSubscribe;
128
+ private send;
129
+ private onFrame;
130
+ private broadcast;
131
+ private emit;
47
132
  private buildWebSocketUrl;
48
- static ɵfac: i0.ɵɵFactoryDeclaration<SparkStreamingService, never>;
133
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkLiveService, never>;
49
134
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
50
135
  }
136
+ /** JSON with object keys sorted and `null`/`undefined` fields dropped, so equal requests key equal. */
137
+ export declare function canonicalJson(value: unknown): string;
51
138
  /**
52
139
  * Context for {@link SparkService.newObject}. Everything is optional: a standalone New sends an
53
140
  * empty body, and an AsDetail row on a parent that has never been saved sends no `parentId` —
@@ -528,4 +615,57 @@ export interface SparkDatatableLabels {
528
615
  * Every translation is read EAGERLY, so a `computed` that calls this tracks the language and the
529
616
  * translation map, and hands the datatable a new object — which re-renders it — when either changes.
530
617
  */
531
- export declare function sparkDatatableLabels(lang: SparkLanguageService): SparkDatatableLabels;
618
+ export declare function sparkDatatableLabels(lang: SparkLanguageService): SparkDatatableLabels;
619
+ /** What {@link describeSparkError} needs of the language service (`SparkLanguageService` fits). */
620
+ export interface SparkErrorLanguage {
621
+ t(key: string, fallback?: string): string;
622
+ resolve(ts: TranslatedString | undefined): string;
623
+ }
624
+ /**
625
+ * The seconds a refused call (429) was told to wait: the envelope's `result.retryAfterSeconds` (core's
626
+ * `ClientResult.Throttled`), a bare body's `retryAfterSeconds`, else the `Retry-After` header in seconds. `null` when
627
+ * the refusal named no wait, or the error is not a 429.
628
+ */
629
+ export declare function sparkRetryAfterSeconds(e: unknown): number | null;
630
+ /**
631
+ * A wait in words, rounded the way a person says it: "a few seconds", "1 minute", "12 minutes", "3 hours", "2 days"
632
+ * (keys `spark.wait.*`, `{0}` is the number).
633
+ */
634
+ export declare function formatSparkWait(seconds: number, lang: Pick<SparkErrorLanguage, 't'>): string;
635
+ /**
636
+ * The readable reason a Spark call failed, for an error banner: the first validation message (a `400`'s
637
+ * `result.errors`), else the server's `result.error` (the envelope every action and write answers with), else a bare
638
+ * body's `error`, else `fallback`. A refusal with a wait (429 + `retryAfterSeconds`/`Retry-After`) gets "Try again in
639
+ * 3 hours." appended, so a quota or cool-down says when, not just no.
640
+ */
641
+ export declare function describeSparkError(e: unknown, lang: SparkErrorLanguage, fallback: string): string;
642
+ /**
643
+ * Sends the language the user picked to the server, as `Accept-Language`.
644
+ *
645
+ * `SparkLanguageService` keeps the choice in `localStorage` and never puts it on the wire: it is a
646
+ * client-side concern, because almost everything translated reaches the client as a whole
647
+ * `TranslatedString` and is resolved by the `resolveTranslation` pipe. Server-*resolved* strings
648
+ * are the exception (CodeCoverage's Home page title, SeoTester's refusals and its dashboard title) because a `PersistentObject`'s breadcrumb and an
649
+ * attribute's value are plain strings with nowhere to carry three languages.
650
+ *
651
+ * Spark resolves those through `IRequestCultureResolver`, which reads `Accept-Language`. Without
652
+ * this the browser's header wins, so a visitor whose browser prefers Dutch sees a Dutch title
653
+ * above an otherwise English page, and switching the language selector never changes it.
654
+ *
655
+ * ⚠️ Reads the module-level `currentLanguage` signal, and **must not** `inject(SparkLanguageService)`.
656
+ * That service fetches `/culture` and `/translations` from its own constructor, so an interceptor
657
+ * that injects it is re-entered while it is still being constructed: Angular throws NG0200 for
658
+ * every request, the culture and translation payloads never arrive, and the whole UI renders raw
659
+ * keys ("app.title") with the language selector missing. `currentLanguage` is the same value
660
+ * without the cycle — the service writes it on load and on every change.
661
+ *
662
+ * Empty until `/culture` resolves, which is exactly the window where there is no user choice to
663
+ * honour yet; those first requests fall through to the browser's own header.
664
+ */
665
+ export declare const sparkLanguageInterceptor: HttpInterceptorFn;
666
+ /**
667
+ * `provideHttpClient(...withSparkLanguage())`: registers {@link sparkLanguageInterceptor}, so server-resolved texts
668
+ * (breadcrumbs, action messages, refusals) follow the language selector instead of the browser's preference.
669
+ * Moved from CodeCoverage (M11), where it was the app's own; SeoTester needed the same.
670
+ */
671
+ export declare function withSparkLanguage(): HttpFeature<HttpFeatureKind>[];
@@ -0,0 +1,67 @@
1
+ import { Color } from "@mintplayer/ng-bootstrap";
2
+ import { PersistentObject, PersistentObjectAttribute, QueryColumn, QueryResult, QueryResultItem, TranslatedString } from "@mintplayer/ng-spark/models";
3
+ import * as i0 from "@angular/core";
4
+ /** What `POST /spark/share/load` answers: the declared attributes and the first page of each declared child query. */
5
+ export interface SparkSharedObject {
6
+ id: string;
7
+ type: string;
8
+ /** The type's label (the model's, translated); the page's heading. */
9
+ label?: TranslatedString | null;
10
+ attributes: PersistentObjectAttribute[];
11
+ /** By query name. */
12
+ queries: Record<string, QueryResult>;
13
+ /** Each shown query's label, by query name; the tables' headings. */
14
+ queryLabels?: Record<string, TranslatedString | null>;
15
+ }
16
+ type State = 'loading' | 'ready' | 'notFound' | 'failed';
17
+ /**
18
+ * The read-only page of a share link (`docs/seo_analyzer_PRD.md` X6, D31), routed by `withShareRoutes()`:
19
+ * exactly what `POST /spark/share/load` returned, the object's attributes through the same cells (and
20
+ * custom renderers, such as the score renderers) as the rest of Spark, and one read-only table per
21
+ * child query, paged through `POST /spark/share/query`. Every refusal reads as the one "this link does
22
+ * not work" (`share.notFound`), since the server answers every refusal with the same 404. No signed-in
23
+ * user, no links into the app, no assumptions about the surrounding chrome.
24
+ */
25
+ export declare class SparkSharedObjectComponent {
26
+ private readonly http;
27
+ private readonly route;
28
+ private readonly language;
29
+ private readonly baseUrl;
30
+ private readonly cellPipe;
31
+ protected readonly colors: typeof Color;
32
+ private readonly type;
33
+ private readonly token;
34
+ private readonly id;
35
+ readonly state: import("@angular/core").WritableSignal<State>;
36
+ readonly shared: import("@angular/core").WritableSignal<SparkSharedObject | null>;
37
+ /** The page shown of each child query, by name: the first from the load, then whatever was paged to. */
38
+ readonly pages: import("@angular/core").WritableSignal<Record<string, QueryResult>>;
39
+ readonly busy: import("@angular/core").WritableSignal<boolean>;
40
+ /** A passing problem (rate limit, network) shown above the content, which stays. */
41
+ readonly notice: import("@angular/core").WritableSignal<string | null>;
42
+ /** The object in the shape renderers receive as `item`. */
43
+ protected readonly object: import("@angular/core").Signal<PersistentObject | null>;
44
+ protected readonly attributeCells: import("@angular/core").Signal<{
45
+ attribute: PersistentObjectAttribute;
46
+ display: any;
47
+ value: any;
48
+ }[]>;
49
+ protected readonly queryTables: import("@angular/core").Signal<{
50
+ name: string;
51
+ label: TranslatedString | undefined;
52
+ result: QueryResult;
53
+ columns: QueryColumn[];
54
+ last: number;
55
+ }[]>;
56
+ constructor();
57
+ protected t(key: string): string;
58
+ protected display(column: QueryColumn, item: QueryResultItem): unknown;
59
+ protected raw(item: QueryResultItem, column: QueryColumn): unknown;
60
+ load(): Promise<void>;
61
+ /** Shows the page of `query` that starts at `skip`, the same size as the one shown. */
62
+ page(query: string, skip: number): Promise<void>;
63
+ private post;
64
+ private fail;
65
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkSharedObjectComponent, never>;
66
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkSharedObjectComponent, "spark-shared-object", never, {}, {}, never, never, true, never>;
67
+ }
@@ -0,0 +1,26 @@
1
+ import { Type } from "@angular/core";
2
+ import { Routes } from "@angular/router";
3
+ import { SparkSharedObject } from "@mintplayer/ng-spark/share/page";
4
+ /** Options of {@link withShareRoutes}. */
5
+ export interface SparkShareRoutesOptions {
6
+ /** The route path; it must hold `:type` and `:token`. Default `shared/:type/:token`, matching the server's `Spark:Share:LinkPath` (`/shared`). */
7
+ path?: string;
8
+ /** A page to use instead of `SparkSharedObjectComponent`. */
9
+ component?: Type<unknown>;
10
+ }
11
+ /**
12
+ * The page a share link opens (`docs/seo_analyzer_PRD.md` X6, D31): `{origin}/shared/{type}/{token}?id={id}`.
13
+ * Anonymous by design (the token is the credential), so it carries no guard; put it where no app chrome
14
+ * assumes a signed-in user:
15
+ *
16
+ * ```ts
17
+ * export const routes: Routes = [
18
+ * ...withShareRoutes(),
19
+ * { path: '', component: ShellComponent, children: [...sparkRoutes()] },
20
+ * ];
21
+ * ```
22
+ *
23
+ * Moving the page means setting `Spark:Share:LinkPath` to match, so the links the server makes follow.
24
+ */
25
+ export declare function withShareRoutes(options?: SparkShareRoutesOptions): Routes;
26
+ export type { SparkSharedObject };