@mintplayer/ng-spark 22.26.0 → 22.28.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 (41) hide show
  1. package/README.md +6 -4
  2. package/fesm2022/mintplayer-ng-spark-grid.mjs +73 -50
  3. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-history.mjs +1 -1
  5. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-models.mjs +19 -10
  7. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-moderation.mjs +1 -1
  9. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -1
  10. package/fesm2022/mintplayer-ng-spark-pipes.mjs +5 -2
  11. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  12. package/fesm2022/mintplayer-ng-spark-po-create.mjs +16 -7
  13. package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
  14. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +30 -14
  15. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +45 -15
  17. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  18. package/fesm2022/mintplayer-ng-spark-po-form.mjs +4 -4
  19. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  20. package/fesm2022/mintplayer-ng-spark-query-list.mjs +18 -2
  21. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  22. package/fesm2022/mintplayer-ng-spark-renderers.mjs +2 -1
  23. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +0 -1
  25. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-services.mjs +12 -9
  27. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +4 -3
  29. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -1
  30. package/package.json +3 -3
  31. package/types/mintplayer-ng-spark-grid.d.ts +50 -46
  32. package/types/mintplayer-ng-spark-models.d.ts +75 -40
  33. package/types/mintplayer-ng-spark-moderation.d.ts +8 -4
  34. package/types/mintplayer-ng-spark-pipes.d.ts +3 -0
  35. package/types/mintplayer-ng-spark-po-create.d.ts +4 -0
  36. package/types/mintplayer-ng-spark-po-detail.d.ts +12 -2
  37. package/types/mintplayer-ng-spark-po-edit.d.ts +16 -0
  38. package/types/mintplayer-ng-spark-po-form.d.ts +1 -0
  39. package/types/mintplayer-ng-spark-query-list.d.ts +11 -1
  40. package/types/mintplayer-ng-spark-services.d.ts +38 -9
  41. package/types/mintplayer-ng-spark-soft-delete.d.ts +1 -1
@@ -10,14 +10,6 @@ import { SparkQueryRowAction } from "@mintplayer/ng-spark/panels";
10
10
  import { ReferenceChip } from "@mintplayer/ng-spark/pipes";
11
11
  /** Page sizes offered by every Spark grid. */
12
12
  export declare const SPARK_GRID_PAGE_SIZES: number[];
13
- /**
14
- * The attributes a grid shows, in display order.
15
- *
16
- * Shared so the two grids cannot disagree about what "visible" means — they each had their own
17
- * copy of this expression, which is the kind of thing that stays identical right up until it
18
- * doesn't.
19
- */
20
- export declare function visibleGridAttributes(entityType: EntityType | null): EntityAttributeDefinition[];
21
13
  /**
22
14
  * Initial datatable settings for a query, seeded with the query's declared sort.
23
15
  *
@@ -200,21 +192,21 @@ export declare class SparkQueryActionsContext {
200
192
  query: SparkQuery | null;
201
193
  }
202
194
  /**
203
- * One button of a query's toolbar (#460, M15): the built-in New or Delete, or a custom action.
195
+ * One button of a query's toolbar (#460 M15, #467 R3): the built-in New, Edit or Delete, or a custom action.
204
196
  *
205
197
  * Built by `SparkQueryGridComponent.toolbarActions()` and rendered by every host of the grid — the
206
198
  * query card's header and the query-list page's action bar — so both surfaces offer the same
207
199
  * actions, enabled by the same rules.
208
200
  */
209
201
  export interface SparkQueryToolbarAction {
210
- kind: 'new' | 'delete' | 'custom' | 'addon';
202
+ kind: 'new' | 'edit' | 'delete' | 'custom' | 'addon';
211
203
  /** For `addon` (row menu only): the add-on's entry (`SPARK_QUERY_ROW_ACTIONS`). */
212
204
  addon?: SparkQueryRowAction;
213
205
  /** The action's name, unique within the toolbar. */
214
206
  name: string;
215
207
  /** Its catalogue entry: label, icon, `selectionRule`, `variant`, confirmation. */
216
208
  definition: CustomActionDefinition;
217
- /** `*bsPriorityNavItem` priority: New first, then Delete, then custom actions by offset. */
209
+ /** `*bsPriorityNavItem` priority: New first, then Edit, then Delete, then custom actions by offset. */
218
210
  priority: number;
219
211
  }
220
212
  /**
@@ -357,14 +349,13 @@ export declare class SparkQueryGridComponent {
357
349
  }>;
358
350
  /**
359
351
  * A click anywhere on a row opens it, like its first-column link — the link used to be the only
360
- * target, so a row whose first column was empty ("—") could not be opened at all. While rows are
361
- * selectable a click selects instead, and a double-click opens. Clicks on the link itself or on an
362
- * interactive element inside the row (a vote button, a checkbox) are theirs, not the row's; a
363
- * modified click (Ctrl, ⌘, Shift, middle) is left to the browser.
352
+ * target, so a row whose first column was empty ("—") could not be opened at all. This holds on
353
+ * every list, selectable or not (#467, D9): only the checkbox cell selects, which the datatable's
354
+ * `'checkbox'` mode guarantees. Clicks on the link itself or on an interactive element inside the
355
+ * row (a vote button, a checkbox) are theirs, not the row's; a modified click (Ctrl, ⌘, Shift,
356
+ * middle) is left to the browser.
364
357
  */
365
358
  protected onRowClick(event: BsDatatableRowEvent<unknown>): void;
366
- protected onRowDblClick(event: BsDatatableRowEvent<unknown>): void;
367
- private openRow;
368
359
  /**
369
360
  * Paging and sorting. Two-way, so a host driving `data` can sort those rows by whatever the
370
361
  * user actually clicked — the datatable writes the new sort back through here.
@@ -449,45 +440,60 @@ export declare class SparkQueryGridComponent {
449
440
  */
450
441
  errorMessage: import("@angular/core").WritableSignal<string | null>;
451
442
  /**
452
- * The rendered selection mode (#460, D17): the host's override (a sub-query entry's
453
- * `selectionMode`), else the query's own, else `'auto'` — derived from the CUSTOM actions offered,
454
- * exactly as before the setting existed, so no existing grid changes behaviour. The default Delete
455
- * does not widen `'auto'`: selection is opt-in, and without it Delete stays reachable per row
456
- * through the row menu.
443
+ * The built-in actions this result offers (#467, R1(1–3)), each with its toolbar kind: listed for
444
+ * the caller (the right), on the query side (`defaultQueryActions` already filtered `showedOn`),
445
+ * not withheld by this result's `disabledActions` — Edit also not when `Save` is — and nothing in
446
+ * the recycle bin. New additionally needs the type's create right ({@link offersCreate}).
447
+ */
448
+ readonly offeredDefaultActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
449
+ /**
450
+ * The rendered selection mode (#467, R1): the host's override (a sub-query entry's
451
+ * `selectionMode`), else the query's own, else `'auto'` — derived from the EFFECTIVE action list
452
+ * (built-ins and custom actions alike), so a list is selectable exactly when the caller can use an
453
+ * action on ticked rows here. Recomputed whenever `disabledActions` or the deleted mode changes;
454
+ * the selection is cleared when the mode becomes `'none'`.
457
455
  */
458
456
  selectionMode: import("@angular/core").Signal<import("@mintplayer/ng-spark/models").SparkSelectionMode>;
459
457
  /**
460
- * The actions a host renders in its toolbar, in one list (#460, M15): New, Delete and the custom
461
- * actions, each with the rule that enables it. The query card and the query-list page render this
462
- * same list, so the two surfaces cannot drift.
458
+ * The actions a host renders in its toolbar, in one list (#460 M15, #467 R3): New, Edit, Delete and
459
+ * the custom actions, each with the rule that enables it. The query card and the query-list page
460
+ * render this same list, so the two surfaces cannot drift. The same effective list decides
461
+ * {@link selectionMode}.
463
462
  *
464
- * - New: the default entry's `showedOn` includes the query, the caller holds `New/T`, and the
465
- * result does not withhold `New`/`Save`.
466
- * - Delete: likewise with `Delete/T` and `Delete`, and only while rows can be selected (otherwise
467
- * it could never be enabled — the row menu offers it per row instead).
463
+ * - New, Edit, Delete: {@link offeredDefaultActions}. Edit and Delete act on ticked rows, so a list
464
+ * declared `'none'` leaves them to the row menu, where they would otherwise never be enabled.
468
465
  * - Custom: `visibleCustomActions()`.
469
- * - In the recycle bin ({@link recycleBin}) none of the three: the list is empty.
466
+ * - In the recycle bin ({@link recycleBin}) none: the list is empty.
470
467
  */
471
468
  toolbarActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
472
469
  /**
473
- * The per-row `⋮` menu: every offered action whose rule accepts exactly one row — Delete included,
474
- * whatever the selection mode. An action without a rule acts on the query, not on a row, and is
475
- * left to the toolbar.
470
+ * The per-row `⋮` menu: every offered action whose rule accepts exactly one row — Edit and Delete
471
+ * included, whatever the selection mode. An action without a rule acts on the query, not on a row,
472
+ * and is left to the toolbar.
476
473
  */
477
474
  rowActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
478
475
  /** What an add-on row action is asked whether it is offered; null until the type resolved. */
479
476
  private readonly rowActionScope;
480
- /** The default Delete: not withheld by the result, and never in the recycle bin. */
481
- private offersDelete;
482
477
  /** Whether a toolbar action can run with the current selection. The server checks again. */
483
478
  isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean;
484
479
  /** Runs a toolbar action on the current selection. */
485
480
  runToolbarAction(action: SparkQueryToolbarAction): Promise<void>;
486
481
  /**
487
- * The chip's ⊗, offered only for single selection: there the datatable renders no checkbox
488
- * column, so it has no header deselect-all, and a row click never deselects.
482
+ * The built-in Edit (#467, R2): it only navigates, to the row's edit page with this list as the
483
+ * return state. Get and Update enforce everything (row gate, `can`, etag, write shield); the `=1`
484
+ * rule is client-side only, since there is no bulk edit.
489
485
  */
486
+ editRow(row: QueryResultItem | undefined): void;
487
+ /** The chip's ⊗: drops the whole selection, ticked rows on other pages included. */
490
488
  clearSelection(): void;
489
+ /** The ids of the rows on the page currently shown; the chip counts the ticked rows outside it. */
490
+ private readonly loadedIds;
491
+ /**
492
+ * Ticked rows that are not on the page shown (#467, D19): the selection survives paging, so the
493
+ * chip says how many of the rows an action will act on cannot be seen ("7 selected, 4 on other
494
+ * pages"). Actions always receive the whole `selection()`, which is what the chip counts.
495
+ */
496
+ readonly offPageCount: import("@angular/core").Signal<number>;
491
497
  /** The id of the row whose `⋮` menu is open; at most one is. */
492
498
  readonly openRowMenu: import("@angular/core").WritableSignal<string | null>;
493
499
  /** Below the toggle, right-aligned; above it when there is no room below. */
@@ -520,10 +526,11 @@ export declare class SparkQueryGridComponent {
520
526
  */
521
527
  startNew(): void;
522
528
  /**
523
- * The default Delete on `ids`: one request, all rows or none (#460, D18). Asks first — with the
524
- * entry's `confirmationMessageKey` — then clears the selection and refreshes.
529
+ * The default Delete on `rows`: one request, all rows or none (#460, D18). Asks first — with the
530
+ * entry's `confirmation` (`{count}` substituted) — then clears the selection and refreshes. Each row
531
+ * goes with the `etag` it was listed with (#467, D14), so a row changed since is refused, not lost.
525
532
  */
526
- deleteRows(ids: string[], definition?: CustomActionDefinition): Promise<void>;
533
+ deleteRows(rows: QueryResultItem[], definition?: CustomActionDefinition): Promise<void>;
527
534
  isVirtualScrolling: import("@angular/core").Signal<boolean>;
528
535
  /**
529
536
  * Columns as sent with the most recent fetched page.
@@ -532,14 +539,11 @@ export declare class SparkQueryGridComponent {
532
539
  * snapshot carries them once, exactly as a paged result does.
533
540
  */
534
541
  private readonly fetchedColumns;
535
- /** Every column on the wire, drawn or not — what renderers and lookups resolve against. */
536
- allColumns: import("@angular/core").Signal<QueryColumn[]>;
537
542
  /**
538
- * The columns the grid draws. `isVisible: false` ships a value without a column, so a renderer
539
- * can read a sibling the grid does not show; undefined means visible, so a server that predates
540
- * the field still draws everything.
543
+ * Every column on the wire — what the grid draws, and what renderers and lookups resolve against.
544
+ * Every column that ships is drawn (#264): there is no shipped-but-hidden column.
541
545
  */
542
- visibleColumns: import("@angular/core").Signal<QueryColumn[]>;
546
+ allColumns: import("@angular/core").Signal<QueryColumn[]>;
543
547
  /**
544
548
  * True when rows do not come from this component's own fetch.
545
549
  *
@@ -23,6 +23,11 @@ export interface ValidationErrorResponse {
23
23
  * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject
24
24
  */
25
25
  export declare enum ShowedOn {
26
+ /**
27
+ * Drawn on no page (#264; Vidyano's `Never`). The attribute still ships on the object, and an action can show
28
+ * it for one object through the runtime `showedOn` it sets on load, new or refresh.
29
+ */
30
+ None = 0,
26
31
  Query = 1,
27
32
  PersistentObject = 2
28
33
  }
@@ -86,7 +91,6 @@ export interface PersistentObjectAttribute {
86
91
  dataType: string;
87
92
  isArray?: boolean;
88
93
  isRequired: boolean;
89
- isVisible: boolean;
90
94
  isReadOnly: boolean;
91
95
  order: number;
92
96
  query?: string;
@@ -128,51 +132,71 @@ export interface PersistentObjectAttribute {
128
132
  */
129
133
  asDetailType?: string;
130
134
  }
135
+ /**
136
+ * One action of the composed catalogue (#467, D7), as `/spark/actions/list` reports it for a type:
137
+ * a built-in (New, Edit, Delete) or a custom action, already narrowed to what the caller may run.
138
+ * Text arrives resolved by the server (D26).
139
+ */
131
140
  export interface CustomActionDefinition {
132
141
  name: string;
133
- displayName: TranslatedString;
142
+ /** `actions.{name}.label`, or the humanized name when untranslated. */
143
+ label: TranslatedString;
134
144
  icon?: string;
135
- description?: string;
145
+ /** `actions.{name}.description`; absent when untranslated. */
146
+ description?: TranslatedString;
136
147
  showedOn: string;
137
148
  selectionRule?: string;
138
149
  refreshOnCompleted: boolean;
139
- confirmationMessageKey?: string;
150
+ /**
151
+ * The confirmation to ask before running, `actions.{name}.confirmation` or the explicit key the
152
+ * catalogue names. May contain `{count}`, the number of rows about to be acted on. Absent: no prompt.
153
+ */
154
+ confirmation?: TranslatedString;
140
155
  /**
141
156
  * How prominently, and how warily, to present the action: 'primary', 'secondary', 'danger',
142
157
  * 'warning'. Undefined renders the neutral default.
143
158
  *
144
159
  * Presentation only — the server decides who may see and run an action. A 'danger' variant just
145
- * asks the button to look like what it does; pair it with `confirmationMessageKey` for anything
160
+ * asks the button to look like what it does; pair it with a `confirmation` for anything
146
161
  * irreversible, because the colour warns and the prompt is what actually prevents the accident.
147
162
  */
148
163
  variant?: string;
149
164
  offset: number;
150
165
  /**
151
- * True for the framework's built-in `New` and `Delete` (#460, D18): catalogue entries with a
152
- * `showedOn` and a `selectionRule` like any custom action (defaults: New has no rule, Delete is
153
- * `>0`; an app overrides them in `customActions.json`), but run through `/po/new` and
154
- * `/po/delete-many`, never `/actions/execute`. Absent for a custom action.
166
+ * True for the framework's built-in `New`, `Edit` and `Delete` (#460 D18, #467 D7): catalogue
167
+ * entries with a `showedOn` and a `selectionRule` like any custom action (the core's
168
+ * `actions.json` ships them; an app overrides or removes them in its own), but run through
169
+ * `/po/new`, the edit page and `/po/delete-many`, never `/actions/execute`. Absent for a custom action.
155
170
  */
156
171
  isDefault?: boolean;
157
172
  }
158
- /** How many rows a grid lets the user select — what `<bs-datatable>` renders. */
159
- export type SparkSelectionMode = 'none' | 'single' | 'multiple';
160
173
  /**
161
- * What a query definition (or a sub-query entry) declares (#460, D17): a fixed mode, or `'auto'` —
174
+ * Whether a grid lets the user tick rows — what `<bs-datatable>` renders. There is no single
175
+ * selection (#467, D10): a selectable list is a checkbox list, and actions whose rule does not
176
+ * match the count are disabled rather than the selection trimmed.
177
+ */
178
+ export type SparkSelectionMode = 'none' | 'multiple';
179
+ /**
180
+ * What a query definition (or a sub-query entry) declares (#460 D17): a fixed mode, or `'auto'` —
162
181
  * the default, and what an absent field means — to derive it from the actions offered.
163
182
  */
164
183
  export type SparkSelectionModeSetting = 'auto' | SparkSelectionMode;
184
+ /** The largest selection the server accepts in one request (`SparkDefaultActions.MaxSelectedItems`). */
185
+ export declare const SPARK_MAX_SELECTED_ITEMS = 200;
165
186
  /**
166
- * The selection mode a grid renders.
167
- *
168
- * A declared `'none'`, `'single'` or `'multiple'` wins outright. `'auto'` (or nothing) derives it,
169
- * so a grid gains a checkbox column exactly when an action needs one and is otherwise
170
- * pixel-identical to a grid with no selection at all — as Vidyano's query grid does. The offered
171
- * actions include the default Delete (`>0`) when the caller may delete, so such a grid is
172
- * multi-select.
187
+ * Whether an action needs ticked rows (#467, R1(4)): it has a rule, and the rule accepts at least
188
+ * one row. `=0` accepts none, so an action that runs only without a selection never makes a list
189
+ * selectable; an action without a rule acts on the query, not on rows.
190
+ */
191
+ export declare function needsSelection(action: CustomActionDefinition): boolean;
192
+ /**
193
+ * The selection mode a grid renders (#467, R1).
173
194
  *
174
- * Derived: `'single'` when every gated action is satisfied by one row and refused by two; anything
175
- * else that cares about the count gets `'multiple'`.
195
+ * A declared `'none'` or `'multiple'` wins outright. `'auto'` (or nothing) derives it from
196
+ * `actions`, which must already be the **effective** list for this result — listed for the caller
197
+ * (the right), shown on queries, not withheld by `disabledActions` and not hidden by the recycle
198
+ * bin. Built-in Edit and Delete count like any other action. `'multiple'` when at least one of them
199
+ * {@link needsSelection}, else `'none'`, so a read-only user sees a list with no checkbox column.
176
200
  */
177
201
  export declare function selectionModeFor(actions: CustomActionDefinition[], declared?: SparkSelectionModeSetting | null): SparkSelectionMode;
178
202
  /**
@@ -229,7 +253,6 @@ export interface EntityAttributeDefinition {
229
253
  description?: TranslatedString;
230
254
  dataType: string;
231
255
  isRequired: boolean;
232
- isVisible: boolean;
233
256
  isReadOnly: boolean;
234
257
  order: number;
235
258
  query?: string;
@@ -293,7 +316,8 @@ export interface AttributeGroup {
293
316
  export interface EntityType {
294
317
  id: string;
295
318
  name: string;
296
- description?: TranslatedString;
319
+ /** The type's display name, resolved by the server (#467, D6; formerly `description`). */
320
+ label?: TranslatedString;
297
321
  /**
298
322
  * The backing CLR type, or absent for a JSON-only virtual type (#325).
299
323
  *
@@ -369,7 +393,8 @@ export interface SparkQuerySortColumn {
369
393
  export interface SparkQuery {
370
394
  id: string;
371
395
  name: string;
372
- description?: TranslatedString;
396
+ /** The query's display name, resolved by the server (#467; formerly `description`). */
397
+ label?: TranslatedString;
373
398
  source: string;
374
399
  alias?: string;
375
400
  sortColumns: SparkQuerySortColumn[];
@@ -456,7 +481,7 @@ export interface QueryColumn extends SparkCellColumn {
456
481
  * header sortable anyway. Neither side read it. The attribute-level `isSortable` on `EntityType`
457
482
  * is the drag-reorder flag and stays.
458
483
  *
459
- * Absent means sortable, matching `isVisible`: a server predating the field still draws arrows.
484
+ * Absent means sortable: a server predating the field still draws arrows.
460
485
  */
461
486
  canSort?: boolean;
462
487
  /** Whether the grid draws a filter cell for this column. Absent means filterable. */
@@ -467,26 +492,26 @@ export interface QueryColumn extends SparkCellColumn {
467
492
  * stronger disclosure than filtering by one already known.
468
493
  */
469
494
  canListDistincts?: boolean;
470
- /**
471
- * Whether the grid draws this column. `false` means the row carries the value but no column is
472
- * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without
473
- * spending a column on it. `showedOn` decides what ships; this decides what is drawn.
474
- */
475
- isVisible?: boolean;
476
495
  /** The query backing a Reference column, by name — the client's option source. */
477
496
  query?: string;
478
497
  }
479
498
  /**
480
499
  * One row: an id, a display string, and a value per column.
481
500
  *
482
- * Deliberately too weak to act on — no attribute metadata, no `can` block, no etag, because none
483
- * of those can be trusted from a projection.
501
+ * Deliberately too weak to act on — no attribute metadata, no `can` block, because neither can be
502
+ * trusted from a projection. It does carry its document's `etag` (#467, D14), a version rather than
503
+ * a judgement, so a delete from the list says which version it removes.
484
504
  */
485
505
  export interface QueryResultItem {
486
506
  /** Never null and unique within a result; the server refuses anything else. */
487
507
  id: string;
488
508
  /** What to show when the row is named rather than tabulated (a reference picker's value). */
489
509
  breadcrumb?: string;
510
+ /**
511
+ * The document's change vector when the list loaded (#467, D14). A delete sends it back; a row
512
+ * changed since is a 409. Absent for a row with no document behind it.
513
+ */
514
+ etag?: string;
490
515
  values: QueryResultItemValue[];
491
516
  /** Presentation hints for the whole row. Keys arrive lower-cased. */
492
517
  typeHints?: Record<string, string>;
@@ -527,8 +552,8 @@ export type SparkRow = QueryResultItem | PersistentObject | Record<string, unkno
527
552
  * for its `objectId` as for its text, and a helper that discarded it would have to be replaced the
528
553
  * first time a renderer wanted to link somewhere. `?.value` is the cost, once per call site.
529
554
  *
530
- * ⚠️ A value only reaches a grid row if its attribute is on the **query surface**. To read a sibling
531
- * the grid does not draw, mark it `"showedOn": "Query", "isVisible": false` — shipped, not drawn. An
555
+ * ⚠️ A value only reaches a grid row if its attribute is on the **query surface**, which also makes it a
556
+ * column: there is no shipped-but-not-drawn value (#264). An
532
557
  * attribute marked `"showedOn": "PersistentObject"` is absent from a row by design, and this returns
533
558
  * `undefined` for it; that is the first thing to check when a sibling read comes back empty.
534
559
  */
@@ -926,6 +951,13 @@ export declare function comparableValue(attribute: PersistentObjectAttribute | u
926
951
  export declare function comparableFormValue(value: unknown): string;
927
952
  /** Whether two form-dict values hold the same content (see {@link comparableFormValue}). */
928
953
  export declare function formValuesEqual(a: unknown, b: unknown): boolean;
954
+ /**
955
+ * The prompt to show before running `action` on `count` rows, with `{count}` substituted, or null
956
+ * when the action asks for no confirmation (#467, D7).
957
+ */
958
+ export declare function confirmationText(action: CustomActionDefinition | undefined | null, count: number, lang?: string): string | null;
959
+ /** The built-in action named `name` (`New`, `Edit`, `Delete`) in `actions`, when the server listed it. */
960
+ export declare function builtInAction(actions: CustomActionDefinition[], name: 'New' | 'Edit' | 'Delete'): CustomActionDefinition | undefined;
929
961
  /** Whether `showedOn` offers the action on a query: `"query"` or `"both"`, in any casing. */
930
962
  export declare function isShowedOnQuery(showedOn: string | undefined | null): boolean;
931
963
  /** Whether `showedOn` offers the action on a detail page: `"detail"` or `"both"`, in any casing. */
@@ -937,7 +969,7 @@ export declare function isShowedOnDetail(showedOn: string | undefined | null): b
937
969
  * and `"both"` — as the server model and the custom-actions guide have always
938
970
  * documented — compared case-insensitively (#460 M15: `"Both"` used to render nowhere).
939
971
  *
940
- * The built-in New and Delete (`isDefault`, #460 D18) are left out: they are not executed as
972
+ * The built-in New, Edit and Delete (`isDefault`, #460 D18, #467 D7) are left out: they are not executed as
941
973
  * custom actions, and the grid offers them itself (see {@link defaultQueryActions}).
942
974
  *
943
975
  * ⚠️ This narrows what is DISPLAYED. It is NOT an authorization boundary: the grant
@@ -948,8 +980,8 @@ export declare function filterQueryActions(actions: CustomActionDefinition[]): C
948
980
  /** The custom actions a detail page should offer — the detail-side counterpart of {@link filterQueryActions}. */
949
981
  export declare function filterDetailActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
950
982
  /**
951
- * The built-in New and Delete the server listed for this caller (#460, D18), when their `showedOn`
952
- * includes the query side. Present only when the caller holds `New/T` or `Delete/T`.
983
+ * The built-in New, Edit and Delete the server listed for this caller (#460 D18, #467 D7), when their `showedOn`
984
+ * includes the query side. Present only when the caller holds `New/T`, `Edit/T` or `Delete/T`.
953
985
  */
954
986
  export declare function defaultQueryActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
955
987
  /**
@@ -991,7 +1023,8 @@ export interface RefreshedOption {
991
1023
  export interface AttributeOverlay {
992
1024
  isRequired?: boolean;
993
1025
  isReadOnly?: boolean;
994
- isVisible?: boolean;
1026
+ /** Where the attribute is drawn for this object; an action sets it on load, new or refresh (#264). */
1027
+ showedOn?: ShowedOn | string;
995
1028
  rules?: ValidationRule[];
996
1029
  query?: string;
997
1030
  /** `undefined` means the hook did not touch the options; an empty array means there are none. */
@@ -1001,7 +1034,9 @@ export type RefreshOverlay = Record<string, AttributeOverlay>;
1001
1034
  /** Applies an overlay to one attribute definition, returning a new object when anything changed. */
1002
1035
  export declare function applyOverlay(attr: EntityAttributeDefinition, overlay: AttributeOverlay | undefined): EntityAttributeDefinition;
1003
1036
  /**
1004
- * Reads a refresh response into an overlay.
1037
+ * Reads a refresh response into an overlay — or a loaded or new object, whose attributes carry the same
1038
+ * server-owned presentation: what `OnLoadAsync`/`OnNewAsync` set (`showedOn`, `isRequired`, `isReadOnly`) applies
1039
+ * from the first render, not only after the first refresh (#264, G5/G7).
1005
1040
  *
1006
1041
  * Everything here is presentation the server owns outright, so it is taken verbatim — there is no
1007
1042
  * merging to do on this half, only on values.
@@ -141,9 +141,9 @@ export declare class SparkModerationService {
141
141
  }>;
142
142
  /**
143
143
  * The caller's own reputation, shared by every caller in flight at once: the badge and the
144
- * review-queue link both re-read on the same navigation and cost one request.
144
+ * review-queue link both re-read on the same navigation and cost one request. `null` signed out.
145
145
  */
146
- ownReputation(): Promise<ModerationReputation>;
146
+ ownReputation(): Promise<ModerationReputation | null>;
147
147
  /** Casts (+1 / −1) or withdraws (0) the caller's vote; resolves to the target's new state. */
148
148
  vote(type: string, id: string, direction: -1 | 0 | 1): Promise<ModerationVoteState>;
149
149
  /**
@@ -159,8 +159,12 @@ export declare class SparkModerationService {
159
159
  unlock(type: string, id: string): Promise<void>;
160
160
  /** What the moderator panel shows about a target, including which tools the caller holds. */
161
161
  status(type: string, id: string): Promise<ModerationStatus>;
162
- /** A user's reputation; the caller's own when `userId` is omitted (with privileges and suspension). */
163
- reputation(userId?: string): Promise<ModerationReputation>;
162
+ /**
163
+ * A user's reputation; the caller's own when `userId` is omitted (with privileges and suspension).
164
+ * `null` for an anonymous caller: the server answers it 200 with no result rather than 401, so a
165
+ * badge on a page anyone may read never sends the app to the sign-in page.
166
+ */
167
+ reputation(userId?: string): Promise<ModerationReputation | null>;
164
168
  /** The caller's own ledger, newest first. Voters are never named; a reversal reads "Voting corrected (−N)". */
165
169
  history(take?: number): Promise<ModerationReputationLine[]>;
166
170
  cases(status?: string, skip?: number, take?: number): Promise<ModerationCaseSummary[]>;
@@ -117,6 +117,9 @@ export declare class AsDetailTypePipe implements PipeTransform {
117
117
  /**
118
118
  * The columns of an AsDetail table: the row type's visible attributes, in order — minus those a
119
119
  * registered row renderer draws (`rowComponent`), when the host passes its renderer registry.
120
+ *
121
+ * An attribute the row type's model draws nowhere (`showedOn: None`, #264) is not a column. Columns are per
122
+ * type, so they come from the model; a runtime `showedOn` on one row cannot add or remove a column.
120
123
  */
121
124
  export declare class AsDetailColumnsPipe implements PipeTransform {
122
125
  transform(attr: EntityAttributeDefinition, asDetailTypes: Record<string, EntityType>, renderers?: readonly SparkAttributeRendererRegistration[] | null): EntityAttributeDefinition[];
@@ -16,6 +16,10 @@ export declare class SparkPoCreateComponent {
16
16
  validationErrors: import("@angular/core").WritableSignal<ValidationError[]>;
17
17
  isSaving: import("@angular/core").WritableSignal<boolean>;
18
18
  private allEntityTypes;
19
+ /**
20
+ * The errors the form cannot show next to a field: those with no attribute, and those on an attribute the
21
+ * form does not draw (#264, G6). Those used to be swallowed; Vidyano promotes them to a notification too.
22
+ */
19
23
  generalErrors: import("@angular/core").Signal<ValidationError[]>;
20
24
  /**
21
25
  * The parent a sub-query card's New passed in the URL (`parentId`, `parentType`, `queryId`), or
@@ -60,8 +60,12 @@ export declare class SparkPoDetailComponent {
60
60
  asDetailReferenceOptions: import("@angular/core").WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
61
61
  type: string;
62
62
  id: string;
63
- canEdit: import("@angular/core").WritableSignal<boolean>;
64
- canDelete: import("@angular/core").WritableSignal<boolean>;
63
+ /** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */
64
+ editAction: import("@angular/core").WritableSignal<CustomActionDefinition | null>;
65
+ /** The catalogue's Delete, on the same terms as {@link editAction}. */
66
+ deleteAction: import("@angular/core").WritableSignal<CustomActionDefinition | null>;
67
+ canEdit: import("@angular/core").Signal<boolean>;
68
+ canDelete: import("@angular/core").Signal<boolean>;
65
69
  customActions: import("@angular/core").WritableSignal<CustomActionDefinition[]>;
66
70
  /** Type-level rights, kept for the add-on context; null until loaded. */
67
71
  permissions: import("@angular/core").WritableSignal<EntityPermissions | null>;
@@ -104,6 +108,10 @@ export declare class SparkPoDetailComponent {
104
108
  private loadItem;
105
109
  /** (Re)loads the object, its type, rights and actions. Also what add-ons call via `context.reload()`. */
106
110
  load(): Promise<void>;
111
+ /**
112
+ * Drawn from the loaded object's own `showedOn`, falling back to the model's (#264, G-Q3): an action sets
113
+ * it per object in `OnLoadAsync` — a stolen car's police report number is shown, the model says `None`.
114
+ */
107
115
  visibleAttributes: import("@angular/core").Signal<EntityAttributeDefinition[]>;
108
116
  private static readonly DEFAULT_TAB;
109
117
  ungroupedAttributes: import("@angular/core").Signal<EntityAttributeDefinition[]>;
@@ -125,6 +133,8 @@ export declare class SparkPoDetailComponent {
125
133
  * a louder style. Restricted to a known set rather than interpolated, because the value arrives
126
134
  * from a JSON file and must not be able to put arbitrary classes on the button.
127
135
  */
136
+ /** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */
137
+ protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string;
128
138
  protected customActionClass(action: CustomActionDefinition): string;
129
139
  /**
130
140
  * The action currently running, or null. Drives the disabled state on every custom-action
@@ -147,6 +147,11 @@ export declare class SparkPoEditComponent {
147
147
  * is not the question's author edits only its translations; the server drops the rest from the
148
148
  * save anyway). Shared by the form and {@link getEditableAttributes}, so what is drawn, what is
149
149
  * initialised and what is saved agree. A refresh overlay can still lift it.
150
+ * <para>
151
+ * The same goes for where an attribute is drawn and whether it is required (#264, G5/G7): an action
152
+ * sets the runtime `showedOn`/`isRequired` in `OnLoadAsync` (a stolen car shows its police report
153
+ * number, required), and the form honours that from its first render rather than after a refresh.
154
+ * </para>
150
155
  */
151
156
  formEntityType: import("@angular/core").Signal<EntityType | null>;
152
157
  type: string;
@@ -160,6 +165,10 @@ export declare class SparkPoEditComponent {
160
165
  validationErrors: import("@angular/core").WritableSignal<ValidationError[]>;
161
166
  isSaving: import("@angular/core").WritableSignal<boolean>;
162
167
  private allEntityTypes;
168
+ /**
169
+ * The errors the form cannot show next to a field: those with no attribute, and those on an attribute the
170
+ * form does not draw (#264, G6). Those used to be swallowed; Vidyano promotes them to a notification too.
171
+ */
163
172
  generalErrors: import("@angular/core").Signal<ValidationError[]>;
164
173
  /** After a 409 was merged: what they changed, and that nothing has been saved yet. */
165
174
  private readonly conflictNoticeText;
@@ -212,6 +221,13 @@ export declare class SparkPoEditComponent {
212
221
  * then dropped on the floor and refused by the server as missing — with no way out of the form.
213
222
  * The overlay is the form's, bound two-way, so the two halves cannot drift again.
214
223
  */
224
+ /**
225
+ * The attributes {@link formDataFrom} gives a slot: the editable ones, plus those the model makes editable
226
+ * but this object's load made read-only (#264). A refresh can lift a load-time `isReadOnly` (a stolen car's
227
+ * plate, once the car is back in use), and the control must then show the loaded value, not an empty
228
+ * required field. Only {@link getEditableAttributes} decides what is saved, so a slot alone sends nothing.
229
+ */
230
+ private formDataAttributes;
215
231
  getEditableAttributes(): import("@mintplayer/ng-spark/models").EntityAttributeDefinition[];
216
232
  onSave(): Promise<void>;
217
233
  onCancel(): void;
@@ -514,6 +514,7 @@ export declare class SparkReferencePickerComponent {
514
514
  pagination: import("@angular/core").WritableSignal<PaginationResponse<QueryResultItem> | undefined>;
515
515
  settings: import("@angular/core").WritableSignal<DatatableSettings>;
516
516
  searchTerm: string;
517
+ /** The picker lists query rows, so its columns are the query's: `showedOn` includes Query (#264; not None). */
517
518
  visibleAttributes: import("@angular/core").Signal<import("@mintplayer/ng-spark/models").EntityAttributeDefinition[]>;
518
519
  rows: import("@angular/core").Signal<QueryResultItem[]>;
519
520
  displayValue: import("@angular/core").Signal<string>;
@@ -3,7 +3,7 @@ import { TemplateRef } from "@angular/core";
3
3
  import { Color } from "@mintplayer/ng-bootstrap";
4
4
  import { SparkLanguageService } from "@mintplayer/ng-spark/services";
5
5
  import { SparkQueryToolbarAction } from "@mintplayer/ng-spark/grid";
6
- import { CustomActionDefinition, QueryColumn, QueryColumnFilter, QueryResultItem, SparkDeletedFilter } from "@mintplayer/ng-spark/models";
6
+ import { CustomActionDefinition, QueryColumn, QueryColumnFilter, QueryResultItem, SparkDeletedFilter, SparkSelectionModeSetting } from "@mintplayer/ng-spark/models";
7
7
  import { SparkQueryListContext } from "@mintplayer/ng-spark/panels";
8
8
  /**
9
9
  * The routed query page: chrome around one {@link SparkQueryGridComponent}.
@@ -31,6 +31,11 @@ export declare class SparkQueryListComponent {
31
31
  protected readonly lang: SparkLanguageService;
32
32
  extraActionsTemplate: import("@angular/core").InputSignal<TemplateRef<void> | null>;
33
33
  showCustomActions: import("@angular/core").InputSignal<boolean>;
34
+ /**
35
+ * Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a
36
+ * host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.
37
+ */
38
+ selectionMode: import("@angular/core").InputSignal<SparkSelectionModeSetting | null>;
34
39
  /**
35
40
  * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card
36
41
  * forwards it. Without this the escape hatch existed only for a directly-embedded grid.
@@ -147,6 +152,11 @@ export declare class SparkQueryListComponent {
147
152
  "required": false;
148
153
  "isSignal": true;
149
154
  };
155
+ "selectionMode": {
156
+ "alias": "selectionMode";
157
+ "required": false;
158
+ "isSignal": true;
159
+ };
150
160
  "rowRoute": {
151
161
  "alias": "rowRoute";
152
162
  "required": false;