spiderly 19.11.2 → 19.11.4

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.
@@ -1,10 +1,11 @@
1
- import { AfterViewInit, EventEmitter, OnDestroy, OnInit, TemplateRef } from '@angular/core';
1
+ import { AfterViewInit, EventEmitter, OnDestroy, OnInit, QueryList, TemplateRef } from '@angular/core';
2
2
  import { ActivatedRoute, Router } from '@angular/router';
3
3
  import { TranslocoService } from '@jsverse/transloco';
4
4
  import { SelectItem, SortMeta } from 'primeng/api';
5
5
  import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
6
6
  import { Table, TableFilterEvent, TableLazyLoadEvent } from 'primeng/table';
7
7
  import { Observable } from 'rxjs';
8
+ import { CellTemplateContext, SpiderlyCellTemplateDirective } from '../../directives/spiderly-cell-template.directive';
8
9
  import { Filter } from '../../entities/filter';
9
10
  import { LazyLoadSelectedIdsResult } from '../../entities/lazy-load-selected-ids-result';
10
11
  import { PaginatedResult } from '../../entities/paginated-result';
@@ -28,6 +29,11 @@ export declare class SpiderlyDataTableComponent implements OnInit, AfterViewInit
28
29
  * Rendered in the caption action area ahead of the built-in buttons.
29
30
  */
30
31
  actionsTemplate: TemplateRef<any>;
32
+ /**
33
+ * Per-column cell templates projected via `<ng-template spiderlyCellTemplate="field">`.
34
+ * A column with no template keeps the built-in rendering.
35
+ */
36
+ cellTemplates: QueryList<SpiderlyCellTemplateDirective>;
31
37
  tableTitle: string;
32
38
  tableIcon: string;
33
39
  items: any[];
@@ -117,6 +123,15 @@ export declare class SpiderlyDataTableComponent implements OnInit, AfterViewInit
117
123
  rowNavigationPath: string;
118
124
  constructor(router: Router, dialogService: DialogService, route: ActivatedRoute, messageService: SpiderlyMessageService, translocoService: TranslocoService, configService: ConfigServiceBase, locale: string);
119
125
  ngAfterViewInit(): void;
126
+ /**
127
+ * The template that renders this column's cells, or null to use the built-in rendering.
128
+ * Actions columns have no `field`, so they never match one.
129
+ *
130
+ * Read straight off the QueryList, which is live — a consumer declaring its templates inside a
131
+ * `@for`/`@if` is served with no subscription and nothing to invalidate. The scan is over the
132
+ * TEMPLATED columns only (typically one or two), not over `cols`.
133
+ */
134
+ getCellTemplate(col: Column): TemplateRef<CellTemplateContext> | null;
120
135
  private setupRemovableSort;
121
136
  ngOnInit(): void;
122
137
  /**
@@ -179,13 +194,32 @@ export declare class SpiderlyDataTableComponent implements OnInit, AfterViewInit
179
194
  */
180
195
  private defaultMultiSortMeta;
181
196
  private persistedMultiSortMeta;
197
+ /**
198
+ * Drops sort meta the backend has no sort case for. Persisted state outlives the rule that
199
+ * produced it — a sort stored before a column became non-sortable (or before the library
200
+ * started disabling `*CommaSeparated` headers) would otherwise ride every lazy load, and the
201
+ * generated `Build` answers an unknown sort field with a 400. There would be no way out from
202
+ * the UI either: the header that could clear it is exactly the one no longer clickable.
203
+ * Unknown fields (no matching column) are left alone — a consumer may sort on something it
204
+ * declares no column for.
205
+ */
206
+ private keepSortableMeta;
182
207
  private applyDefaultSortIfUnsorted;
183
208
  lazyLoad(event: TableLazyLoadEvent): void;
184
209
  clientLoad(): void;
185
210
  private clientFilterCount;
186
211
  filter(event: TableFilterEvent): void;
187
212
  private loadFormArrayItems;
188
- getColHeaderWidth(filterType: string): "min-width: 12rem;" | "min-width: 10rem;" | "min-width: 8rem;" | "width: 0rem;";
213
+ /**
214
+ * Single source for whether a column header offers click-to-sort (drives both
215
+ * `pSortableColumnDisabled` and the sort icon). Beyond the consumer's explicit `sortable: false`,
216
+ * `*CommaSeparated` fields are auto-disabled: the backend's `PaginatedResultGenerator` never emits
217
+ * a sort case for collection columns (the same naming convention decides both), and since unknown
218
+ * sort fields are rejected with a 400, a clickable header here would be a guaranteed error toast.
219
+ */
220
+ isColumnSortable(col: Column): boolean;
221
+ /** The column's declared {@link Column.minWidth}, or the default for its filter type. */
222
+ getColHeaderWidth(col: Column): string;
189
223
  getColMatchModeOptions(filterType: string): SelectItem<any>[];
190
224
  getColMatchMode(filterType: string): any;
191
225
  isDropOrMulti(filterType: string): boolean;
@@ -218,7 +252,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, AfterViewInit
218
252
  rowUnselect(id: number): void;
219
253
  getFormArrayControlByIndex(formControlName: string, index: number): SpiderlyFormControl;
220
254
  static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyDataTableComponent, never>;
221
- static ɵcmp: i0.ɵɵComponentDeclaration<SpiderlyDataTableComponent, "spiderly-data-table", never, { "tableTitle": { "alias": "tableTitle"; "required": false; }; "tableIcon": { "alias": "tableIcon"; "required": false; }; "items": { "alias": "items"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "cols": { "alias": "cols"; "required": false; }; "showPaginator": { "alias": "showPaginator"; "required": false; }; "showCardWrapper": { "alias": "showCardWrapper"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "idField": { "alias": "idField"; "required": false; }; "getPaginatedListObservableMethod": { "alias": "getPaginatedListObservableMethod"; "required": false; }; "exportListToExcelObservableMethod": { "alias": "exportListToExcelObservableMethod"; "required": false; }; "deleteItemFromTableObservableMethod": { "alias": "deleteItemFromTableObservableMethod"; "required": false; }; "deleteListFromTableObservableMethod": { "alias": "deleteListFromTableObservableMethod"; "required": false; }; "newlySelectedItems": { "alias": "newlySelectedItems"; "required": false; }; "unselectedItems": { "alias": "unselectedItems"; "required": false; }; "selectionMode": { "alias": "selectionMode"; "required": false; }; "selectedLazyLoadObservableMethod": { "alias": "selectedLazyLoadObservableMethod"; "required": false; }; "additionalFilterIdLong": { "alias": "additionalFilterIdLong"; "required": false; }; "showAddButton": { "alias": "showAddButton"; "required": false; }; "showExportToExcelButton": { "alias": "showExportToExcelButton"; "required": false; }; "showReloadTableButton": { "alias": "showReloadTableButton"; "required": false; }; "getFormArrayItems": { "alias": "getFormArrayItems"; "required": false; }; "hasLazyLoad": { "alias": "hasLazyLoad"; "required": false; }; "stateKey": { "alias": "stateKey"; "required": false; }; "stateStorage": { "alias": "stateStorage"; "required": false; }; "defaultSortField": { "alias": "defaultSortField"; "required": false; }; "defaultSortOrder": { "alias": "defaultSortOrder"; "required": false; }; "getAlreadySelectedItemIds": { "alias": "getAlreadySelectedItemIds"; "required": false; }; "getAlreadySelectedItems": { "alias": "getAlreadySelectedItems"; "required": false; }; "getFormControl": { "alias": "getFormControl"; "required": false; }; "additionalIndexes": { "alias": "additionalIndexes"; "required": false; }; "navigateOnRowClick": { "alias": "navigateOnRowClick"; "required": false; }; "rowNavigationPath": { "alias": "rowNavigationPath"; "required": false; }; }, { "onTotalRecordsChange": "onTotalRecordsChange"; "onLazyLoad": "onLazyLoad"; "onIsAllSelectedChange": "onIsAllSelectedChange"; "onRowSelect": "onRowSelect"; "onRowUnselect": "onRowUnselect"; }, ["actionsTemplate"], never, true, never>;
255
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpiderlyDataTableComponent, "spiderly-data-table", never, { "tableTitle": { "alias": "tableTitle"; "required": false; }; "tableIcon": { "alias": "tableIcon"; "required": false; }; "items": { "alias": "items"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "cols": { "alias": "cols"; "required": false; }; "showPaginator": { "alias": "showPaginator"; "required": false; }; "showCardWrapper": { "alias": "showCardWrapper"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "idField": { "alias": "idField"; "required": false; }; "getPaginatedListObservableMethod": { "alias": "getPaginatedListObservableMethod"; "required": false; }; "exportListToExcelObservableMethod": { "alias": "exportListToExcelObservableMethod"; "required": false; }; "deleteItemFromTableObservableMethod": { "alias": "deleteItemFromTableObservableMethod"; "required": false; }; "deleteListFromTableObservableMethod": { "alias": "deleteListFromTableObservableMethod"; "required": false; }; "newlySelectedItems": { "alias": "newlySelectedItems"; "required": false; }; "unselectedItems": { "alias": "unselectedItems"; "required": false; }; "selectionMode": { "alias": "selectionMode"; "required": false; }; "selectedLazyLoadObservableMethod": { "alias": "selectedLazyLoadObservableMethod"; "required": false; }; "additionalFilterIdLong": { "alias": "additionalFilterIdLong"; "required": false; }; "showAddButton": { "alias": "showAddButton"; "required": false; }; "showExportToExcelButton": { "alias": "showExportToExcelButton"; "required": false; }; "showReloadTableButton": { "alias": "showReloadTableButton"; "required": false; }; "getFormArrayItems": { "alias": "getFormArrayItems"; "required": false; }; "hasLazyLoad": { "alias": "hasLazyLoad"; "required": false; }; "stateKey": { "alias": "stateKey"; "required": false; }; "stateStorage": { "alias": "stateStorage"; "required": false; }; "defaultSortField": { "alias": "defaultSortField"; "required": false; }; "defaultSortOrder": { "alias": "defaultSortOrder"; "required": false; }; "getAlreadySelectedItemIds": { "alias": "getAlreadySelectedItemIds"; "required": false; }; "getAlreadySelectedItems": { "alias": "getAlreadySelectedItems"; "required": false; }; "getFormControl": { "alias": "getFormControl"; "required": false; }; "additionalIndexes": { "alias": "additionalIndexes"; "required": false; }; "navigateOnRowClick": { "alias": "navigateOnRowClick"; "required": false; }; "rowNavigationPath": { "alias": "rowNavigationPath"; "required": false; }; }, { "onTotalRecordsChange": "onTotalRecordsChange"; "onLazyLoad": "onLazyLoad"; "onIsAllSelectedChange": "onIsAllSelectedChange"; "onRowSelect": "onRowSelect"; "onRowUnselect": "onRowUnselect"; }, ["actionsTemplate", "cellTemplates"], never, true, never>;
222
256
  }
223
257
  export declare class Action {
224
258
  name?: string;
@@ -269,6 +303,13 @@ export declare class Column<T = any> {
269
303
  * lose its anchor.
270
304
  */
271
305
  lockVisible?: boolean;
306
+ /**
307
+ * CSS length overriding this column's default minimum width (e.g. `'8rem'`). The defaults are
308
+ * per filter type and sized for the HEADER — filter input plus match-mode dropdown — so they
309
+ * are generous for a column of short values. It is a MINIMUM, not a width: the table still
310
+ * distributes the leftover space.
311
+ */
312
+ minWidth?: string;
272
313
  /**
273
314
  * Fired when this column's cell is clicked. Receives a {@link CellClickEvent} with the row id,
274
315
  * the column field, the full row, the raw and formatted cell value, the clicked `<td>` element
@@ -279,7 +320,7 @@ export declare class Column<T = any> {
279
320
  * Not applied to editable cells — those belong to their inline input.
280
321
  */
281
322
  onCellClick?: (event: CellClickEvent) => void;
282
- constructor({ name, field, filterField, filterType, filterPlaceholder, showMatchModes, showAddButton, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible, onCellClick, }?: {
323
+ constructor({ name, field, filterField, filterType, filterPlaceholder, showMatchModes, showAddButton, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible, minWidth, onCellClick, }?: {
283
324
  name?: string;
284
325
  field?: string & keyof T;
285
326
  filterField?: string & keyof T;
@@ -295,6 +336,7 @@ export declare class Column<T = any> {
295
336
  sortable?: boolean;
296
337
  visible?: boolean;
297
338
  lockVisible?: boolean;
339
+ minWidth?: string;
298
340
  onCellClick?: (event: CellClickEvent) => void;
299
341
  });
300
342
  }
@@ -19,6 +19,17 @@ export declare class BaseControl {
19
19
  constructor(translocoService: TranslocoService);
20
20
  ngOnInit(): void;
21
21
  ngAfterViewInit(): void;
22
+ /**
23
+ * Every control's TEMPLATE renders without a control (`*ngIf="control"`, `control?.errors`,
24
+ * spiderly-file's no-control custom uploads), and this was the one shared read that did not —
25
+ * so an absent control took the whole page down through the label rather than rendering
26
+ * label-less: an unhandled TypeError per change-detection pass, which the global ErrorHandler
27
+ * turns into the generic error toast.
28
+ *
29
+ * That fixes the shared path only. `spiderly-autocomplete`, `spiderly-colorpicker` and
30
+ * `spiderly-checkbox` (with `initializeToFalse`) still dereference `control` in their own
31
+ * `ngOnInit`, so those three are not yet safe to hand an absent control.
32
+ */
22
33
  getTranslatedLabel(): string;
23
34
  getValidationErrrorMessages(): any;
24
35
  static ɵfac: i0.ɵɵFactoryDeclaration<BaseControl, never>;
@@ -0,0 +1,47 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import type { Column } from '../components/spiderly-data-table/spiderly-data-table.component';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Context handed to a cell template. Read the row with `let-row`, the rest by name
6
+ * (`let-value="value"`, `let-displayValue="displayValue"`, `let-col="col"`).
7
+ *
8
+ * `value` and `displayValue` mean here exactly what they mean on `CellClickEvent`, the other seam
9
+ * this component hands a cell to — raw and formatted respectively. One column can use both
10
+ * (a click handler and a template), so they must not disagree.
11
+ */
12
+ export interface CellTemplateContext<T = any> {
13
+ /** The full row object the cell belongs to. */
14
+ $implicit: T;
15
+ /** The raw cell value, `row[col.field]`. */
16
+ value: any;
17
+ /**
18
+ * What the table would have rendered — the value formatted for the column's `filterType` and
19
+ * the app's `LOCALE_ID` (dates, numbers, Yes/No). A template that only adds something around
20
+ * the value never has to re-implement that formatting.
21
+ */
22
+ displayValue: string;
23
+ /** The column being rendered. */
24
+ col: Column<T>;
25
+ }
26
+ /**
27
+ * Renders the cells of ONE column of a `spiderly-data-table`, replacing the plain formatted
28
+ * value. Bind it to the column's `field`; columns with no matching template keep the built-in
29
+ * rendering, so a table opts in one column at a time.
30
+ *
31
+ * Consumer guide (context, what it does and does not replace, when to prefer it over
32
+ * `onCellClick`): the `angular-customization` doc, "Custom Cell Content".
33
+ */
34
+ export declare class SpiderlyCellTemplateDirective<T = any> {
35
+ readonly template: TemplateRef<CellTemplateContext<T>>;
36
+ /** The `field` of the column whose cells this template renders. */
37
+ field: string;
38
+ constructor(template: TemplateRef<CellTemplateContext<T>>);
39
+ /**
40
+ * Without this the `let-` variables are `any` at every consumer, however well
41
+ * {@link CellTemplateContext} is declared — a directive's injected `TemplateRef<C>` never
42
+ * reaches the template that declares it. Same mechanism as `SpiderlyTemplateTypeDirective`.
43
+ */
44
+ static ngTemplateContextGuard<T>(dir: SpiderlyCellTemplateDirective<T>, ctx: unknown): ctx is CellTemplateContext<T>;
45
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyCellTemplateDirective<any>, never>;
46
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SpiderlyCellTemplateDirective<any>, "ng-template[spiderlyCellTemplate]", never, { "field": { "alias": "spiderlyCellTemplate"; "required": false; }; }, {}, never, never, true, never>;
47
+ }
@@ -11,5 +11,8 @@ export declare const ApiErrorCodes: {
11
11
  readonly EmailNotVerified: "email_not_verified";
12
12
  readonly ExternalProviderNotConfigured: "external_provider_not_configured";
13
13
  readonly ExternalEmailMissing: "external_email_missing";
14
+ readonly UnknownFilterField: "unknown_filter_field";
15
+ readonly UnknownSortField: "unknown_sort_field";
16
+ readonly InvalidMatchMode: "invalid_match_mode";
14
17
  };
15
18
  export type ApiErrorCode = (typeof ApiErrorCodes)[keyof typeof ApiErrorCodes];
@@ -1,13 +1,24 @@
1
1
  import { TranslocoService } from '@jsverse/transloco';
2
2
  import { ErrorHandler } from '@angular/core';
3
3
  import { SpiderlyMessageService } from '../services/spiderly-message.service';
4
- import { ConfigServiceBase } from '../services/config.service.base';
5
4
  import * as i0 from "@angular/core";
5
+ /**
6
+ * The app's global {@link ErrorHandler}: logs the error and shows the generic error toast. HTTP
7
+ * errors are skipped entirely — `unauthorizedInterceptor` owns them end to end and has already
8
+ * logged the failed request and shown the message matched to its status.
9
+ *
10
+ * Logging is unconditional, production included. The toast tells the user the team was notified,
11
+ * and unless the app wires an error tracker this line is the only place the error exists — so
12
+ * staying quiet in production left a deployed app with a red toast and nothing to open. The
13
+ * console is a developer surface; a user never sees it.
14
+ *
15
+ * An error tracker plugs in by providing an `ErrorHandler` that reports and then delegates here,
16
+ * which keeps this class's toast copy true (see the PACMS admin's `AdminErrorHandler`).
17
+ */
6
18
  export declare class SpiderlyErrorHandler implements ErrorHandler {
7
19
  private messageService;
8
20
  private translocoService;
9
- private config;
10
- constructor(messageService: SpiderlyMessageService, translocoService: TranslocoService, config: ConfigServiceBase);
21
+ constructor(messageService: SpiderlyMessageService, translocoService: TranslocoService);
11
22
  handleError(error: any): void;
12
23
  static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyErrorHandler, never>;
13
24
  static ɵprov: i0.ɵɵInjectableDeclaration<SpiderlyErrorHandler>;
@@ -1,6 +1,12 @@
1
1
  import { HttpHeaders, HttpParams } from '@angular/common/http';
2
2
  import * as i0 from "@angular/core";
3
3
  export declare class ConfigServiceBase {
4
+ /**
5
+ * Consumer-facing only — nothing in the library reads it. The error handler and the HTTP
6
+ * interceptor used to gate their `console.error` on it, which left a deployed app's client
7
+ * errors with nowhere to go; both now log unconditionally. `spiderly init` still scaffolds the
8
+ * `override`, so apps keep branching on it.
9
+ */
4
10
  production: boolean;
5
11
  apiUrl: string;
6
12
  frontendUrl: string;
@@ -41,7 +41,6 @@ export declare function capitalizeFirstChar(str: string): string;
41
41
  export declare function kebabToTitleCase(input: string): string;
42
42
  export declare const PROPS_KEY: unique symbol;
43
43
  export declare function ReflectProp(target: any, propertyKey: string): void;
44
- export declare const primitiveArrayTypes: string[];
45
44
  export declare function getImageDimensions(file: File): Promise<{
46
45
  width: number;
47
46
  height: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spiderly",
3
- "version": "19.11.2",
3
+ "version": "19.11.4",
4
4
  "author": "Filip Trivan",
5
5
  "license": "MIT",
6
6
  "description": "Spiderly Angular Library to use in combination with Spiderly.",
package/public-api.d.ts CHANGED
@@ -90,5 +90,6 @@ export * from './lib/services/helper-functions';
90
90
  export * from './lib/services/spiderly-message.service';
91
91
  export * from './lib/services/spiderly-transloco-loader';
92
92
  export * from './lib/services/validator-abstract.service';
93
+ export * from './lib/directives/spiderly-cell-template.directive';
93
94
  export * from './lib/directives/spiderly-data-table-actions.directive';
94
95
  export * from './lib/directives/template-type.directive';