spiderly 19.11.8 → 19.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/docs/angular-customization/index.md +34 -1
- package/agent/docs/angular-customization/references/helper-functions.generated.md +1 -0
- package/fesm2022/spiderly.mjs +396 -75
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +160 -14
- package/lib/components/spiderly-data-view/spiderly-data-view.component.d.ts +8 -1
- package/lib/services/helper-functions.d.ts +6 -0
- package/package.json +1 -1
- package/styles/components/spiderly-data-table/spiderly-data-table.component.scss +34 -0
- package/styles/components/spiderly-data-view/spiderly-data-view.component.scss +16 -0
- package/styles/styles/layout/_content.scss +1 -1
- package/styles/styles/layout/_main.scss +15 -1
- package/styles/styles/layout/_menu.scss +1 -1
- package/styles/styles/layout/_mixins.scss +33 -0
- package/styles/styles/layout/_topbar.scss +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AfterViewInit, EventEmitter, OnChanges, OnDestroy, OnInit, QueryList, TemplateRef } from '@angular/core';
|
|
1
|
+
import { AfterViewInit, ErrorHandler, EventEmitter, OnChanges, 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';
|
|
@@ -10,6 +10,7 @@ import { Filter } from '../../entities/filter';
|
|
|
10
10
|
import { LazyLoadSelectedIdsResult } from '../../entities/lazy-load-selected-ids-result';
|
|
11
11
|
import { PaginatedResult } from '../../entities/paginated-result';
|
|
12
12
|
import { PrimengOption } from '../../entities/primeng-option';
|
|
13
|
+
import { MatchModeCodes } from '../../enums/match-mode-enum-codes';
|
|
13
14
|
import { ConfigServiceBase } from '../../services/config.service.base';
|
|
14
15
|
import { SpiderlyMessageService } from '../../services/spiderly-message.service';
|
|
15
16
|
import { SpiderlyFormControl } from '../spiderly-form-control/spiderly-form-control';
|
|
@@ -21,6 +22,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
21
22
|
private messageService;
|
|
22
23
|
private translocoService;
|
|
23
24
|
private configService;
|
|
25
|
+
private errorHandler;
|
|
24
26
|
private locale;
|
|
25
27
|
private readonly destroy$;
|
|
26
28
|
table: Table;
|
|
@@ -47,6 +49,19 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
47
49
|
showCardWrapper: boolean;
|
|
48
50
|
readonly: boolean;
|
|
49
51
|
idField: string;
|
|
52
|
+
/**
|
|
53
|
+
* Sizes the columns from their declared widths instead of from the rendered rows.
|
|
54
|
+
*
|
|
55
|
+
* The browser default is `table-layout: auto`, which computes every column from the content
|
|
56
|
+
* of the rows currently on screen — so a column is one width on this page and another on the
|
|
57
|
+
* next, and a filter (which narrows the variety of values in the column it filters) moves the
|
|
58
|
+
* whole grid. PrimeNG sets no `table-layout` of its own, so without this the table inherits
|
|
59
|
+
* that default. Bound to a field, never an object literal in the template: a literal is a new
|
|
60
|
+
* reference on every change-detection pass.
|
|
61
|
+
*/
|
|
62
|
+
protected readonly tableStyle: {
|
|
63
|
+
'table-layout': string;
|
|
64
|
+
};
|
|
50
65
|
totalRecords: number;
|
|
51
66
|
onTotalRecordsChange: EventEmitter<number>;
|
|
52
67
|
getPaginatedListObservableMethod: (tableFilter: Filter) => Observable<PaginatedResult>;
|
|
@@ -54,7 +69,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
54
69
|
deleteItemFromTableObservableMethod: (rowId: number) => Observable<any>;
|
|
55
70
|
deleteListFromTableObservableMethod: (ids: number[]) => Observable<any>;
|
|
56
71
|
lastLazyLoadEvent: TableLazyLoadEvent;
|
|
57
|
-
loading
|
|
72
|
+
private loading;
|
|
58
73
|
newlySelectedItems: number[];
|
|
59
74
|
fakeSelectedItems: number[];
|
|
60
75
|
currentPageSelectedItemsFromDb: number[];
|
|
@@ -73,6 +88,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
73
88
|
additionalFilterIdLong: number;
|
|
74
89
|
matchModeDateOptions: SelectItem[];
|
|
75
90
|
matchModeNumberOptions: SelectItem[];
|
|
91
|
+
matchModeTextOptions: SelectItem[];
|
|
76
92
|
/** Whether the "Add" button is shown. Defaults to `true`. */
|
|
77
93
|
showAddButton: boolean;
|
|
78
94
|
/** Whether the "Export to Excel" button is shown. Defaults to `true`. */
|
|
@@ -126,7 +142,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
126
142
|
* Example: 'details' will navigate to '/details/{rowId}'.
|
|
127
143
|
*/
|
|
128
144
|
rowNavigationPath: string;
|
|
129
|
-
constructor(router: Router, dialogService: DialogService, route: ActivatedRoute, messageService: SpiderlyMessageService, translocoService: TranslocoService, configService: ConfigServiceBase, locale: string);
|
|
145
|
+
constructor(router: Router, dialogService: DialogService, route: ActivatedRoute, messageService: SpiderlyMessageService, translocoService: TranslocoService, configService: ConfigServiceBase, errorHandler: ErrorHandler, locale: string);
|
|
130
146
|
ngAfterViewInit(): void;
|
|
131
147
|
/**
|
|
132
148
|
* The template that renders this column's cells, or null to use the built-in rendering.
|
|
@@ -188,6 +204,36 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
188
204
|
private clearHiddenColumnConstraints;
|
|
189
205
|
/** Whether filter metadata (single or array) carries a real constraint, not just an empty slot. */
|
|
190
206
|
private static isActiveFilterMeta;
|
|
207
|
+
/**
|
|
208
|
+
* The one home of the filter-state key rule (sort meta is keyed by `field` instead).
|
|
209
|
+
* Instance rather than static so the template's `[field]` binding can share it.
|
|
210
|
+
*/
|
|
211
|
+
filterKey(col: Column): string | undefined;
|
|
212
|
+
/**
|
|
213
|
+
* The filter keys the rows on screen were actually narrowed by. Kept apart from
|
|
214
|
+
* `table.filters` because those are not the same question: PrimeNG's `onModelChange`
|
|
215
|
+
* writes each keystroke of a text/numeric filter straight into the meta and only calls
|
|
216
|
+
* `_filter()` for the auto-applying types, so `table.filters` holds PENDING edits too.
|
|
217
|
+
* Reading it for the header icon marked a column filtered the moment the operator typed
|
|
218
|
+
* a character, before Apply — claiming the grid was narrowed when it was not.
|
|
219
|
+
*/
|
|
220
|
+
private appliedFilterKeys;
|
|
221
|
+
/**
|
|
222
|
+
* Whether the rows on screen are narrowed by this column — feeds the projected
|
|
223
|
+
* `filtericon` template. Why not the template's `let-hasFilter`, and why applied rather
|
|
224
|
+
* than pending state: CLAUDE.md → "Active-filter header icon".
|
|
225
|
+
*/
|
|
226
|
+
isColumnFiltered(col: Column): boolean;
|
|
227
|
+
/** Whether the column carries a constraint at all, applied or still being typed. */
|
|
228
|
+
private columnHasConstraint;
|
|
229
|
+
/**
|
|
230
|
+
* Records what a just-applied filter set narrows by. Called from every path that commits
|
|
231
|
+
* one: `(onFilter)` (Apply, auto-apply, a per-column Clear), `lazyLoad` (which also covers
|
|
232
|
+
* `table.clear()` — it re-queries WITHOUT emitting onFilter), the caption's Clear filters,
|
|
233
|
+
* and once from `ngOnInit` off persisted state, so a restored filter is marked on first
|
|
234
|
+
* paint rather than after the first response.
|
|
235
|
+
*/
|
|
236
|
+
private snapshotAppliedFilters;
|
|
191
237
|
private persistColumnVisibility;
|
|
192
238
|
private restoreColumnVisibility;
|
|
193
239
|
/**
|
|
@@ -219,8 +265,40 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
219
265
|
* declares no column for.
|
|
220
266
|
*/
|
|
221
267
|
private keepSortableMeta;
|
|
268
|
+
/**
|
|
269
|
+
* The filter twin of `keepSortableMeta` — persisted state outlives the rule that produced
|
|
270
|
+
* it, here a match mode stored before the column declared `matchModes`. It has to REWRITE
|
|
271
|
+
* storage rather than filter a value on the way past: `ColumnFilter.ngOnInit` skips
|
|
272
|
+
* `initFieldFilterConstraint()` whenever the field already carries a constraint, so our
|
|
273
|
+
* `[matchMode]` never applies to a restored one. Left alone, the column keeps filtering by
|
|
274
|
+
* a mode it no longer offers while its match-mode `<p-select>` renders blank (the stored
|
|
275
|
+
* value is not among the options). Runs before PrimeNG's `restoreState()`, which reads this
|
|
276
|
+
* same key on the first `[value]` change.
|
|
277
|
+
*
|
|
278
|
+
* Deliberately narrow: it touches only constraints whose mode the declaring column no
|
|
279
|
+
* longer offers, so every other byte of the persisted blob survives.
|
|
280
|
+
*/
|
|
281
|
+
private reconcilePersistedMatchModes;
|
|
222
282
|
private applyDefaultSortIfUnsorted;
|
|
283
|
+
onPageChange(container: HTMLElement): void;
|
|
284
|
+
/**
|
|
285
|
+
* The one pending predicate: PrimeNG's overlay and the container's `aria-busy` must never
|
|
286
|
+
* disagree about whether a fetch is in flight.
|
|
287
|
+
*
|
|
288
|
+
* It used to also test `items === undefined`, for the first load. That clause is dead now
|
|
289
|
+
* that `loading` initialises true and every lazyLoad raises it — except in the one state
|
|
290
|
+
* where it was actively wrong: a FAILED first load lowers the flag with `items` still
|
|
291
|
+
* undefined, which pinned the overlay up forever. Same stranded overlay this component was
|
|
292
|
+
* fixed for once already, through the other door.
|
|
293
|
+
*/
|
|
294
|
+
get isPending(): boolean;
|
|
223
295
|
lazyLoad(event: TableLazyLoadEvent): void;
|
|
296
|
+
/**
|
|
297
|
+
* Repaints the checkbox column for the page that just landed. Split out of the subscriber so
|
|
298
|
+
* the try/finally that guarantees the pending flag wraps an await, not fifty lines of
|
|
299
|
+
* selection algebra.
|
|
300
|
+
*/
|
|
301
|
+
private reconcileSelectionForLoadedPage;
|
|
224
302
|
clientLoad(): void;
|
|
225
303
|
private clientFilterCount;
|
|
226
304
|
filter(event: TableFilterEvent): void;
|
|
@@ -233,11 +311,60 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
|
|
|
233
311
|
* sort fields are rejected with a 400, a clickable header here would be a guaranteed error toast.
|
|
234
312
|
*/
|
|
235
313
|
isColumnSortable(col: Column): boolean;
|
|
236
|
-
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
314
|
+
/**
|
|
315
|
+
* The column's declared {@link Column.width}, or the default for its filter type.
|
|
316
|
+
*
|
|
317
|
+
* A width, not a minimum: under the fixed layout {@link tableStyle} establishes, this is what
|
|
318
|
+
* the column is sized from, and the table shares its surplus in PROPORTION to these numbers.
|
|
319
|
+
* Columns declaring none would otherwise take an equal share, which throws away what the
|
|
320
|
+
* per-type defaults say — a boolean holds "Da"/"Ne", a text column holds a name.
|
|
321
|
+
*/
|
|
322
|
+
getColWidth(col: Column): string;
|
|
323
|
+
/**
|
|
324
|
+
* Memo for the resolution below. Two jobs: the narrowing maps to a NEW array and a
|
|
325
|
+
* template binding must not hand PrimeNG a fresh reference every CD pass, and both
|
|
326
|
+
* halves (offered list, default mode) must come from ONE computation so they cannot
|
|
327
|
+
* disagree. Keyed on the declared array's identity, so reassigning `col.matchModes`
|
|
328
|
+
* recomputes — note PrimeNG itself reads `matchModeOptions` only in its own `ngOnInit`,
|
|
329
|
+
* so a post-init change still never reaches the rendered dropdown.
|
|
330
|
+
*/
|
|
331
|
+
private matchModeResolutions;
|
|
332
|
+
getColMatchModeOptions(col: Column): SelectItem[] | null;
|
|
333
|
+
getColMatchMode(col: Column): any;
|
|
334
|
+
private resolveMatchModes;
|
|
335
|
+
/**
|
|
336
|
+
* Applies `Column.matchModes`, and refuses to half-apply it. Both ways a declaration can
|
|
337
|
+
* be wrong are consumer mistakes that PrimeNG would otherwise turn into a broken filter
|
|
338
|
+
* rather than an error: it reads `matchModeOptions || <type defaults>`, so handing it
|
|
339
|
+
* `[]` renders an EMPTY dropdown (an empty array is truthy) while the default mode still
|
|
340
|
+
* seeds the constraint, and handing it `null` silently restores PrimeNG's own list. So an
|
|
341
|
+
* unusable narrowing falls back to the full list and says so, instead of shipping a
|
|
342
|
+
* dropdown the user cannot pick from.
|
|
343
|
+
*/
|
|
344
|
+
private computeMatchModes;
|
|
345
|
+
private matchModeOptionsForType;
|
|
346
|
+
private defaultMatchModeForType;
|
|
240
347
|
isDropOrMulti(filterType: string): boolean;
|
|
348
|
+
/**
|
|
349
|
+
* Whether the filter type applies on every value change, which hides the menu's Apply
|
|
350
|
+
* button — an Apply there would promise a pending state that cannot exist. Boolean
|
|
351
|
+
* auto-applies through PrimeNG's own `onModelChange`; date and dropdown/multiselect
|
|
352
|
+
* through the filter templates THIS component projects, which call `filterCallback`
|
|
353
|
+
* directly (PrimeNG's built-in element, and with it its `onModelChange`, is only
|
|
354
|
+
* rendered when no template is projected — so the date path is ours, not PrimeNG's).
|
|
355
|
+
* With showApplyButton off, PrimeNG also auto-applies match-mode, operator and
|
|
356
|
+
* constraint-removal changes, so the whole menu stays consistent. Typed input
|
|
357
|
+
* (text/numeric) is deliberately NOT here — it commits on Enter/Apply, since applying
|
|
358
|
+
* per keystroke would fire a lazy load per key. Listing the auto types (not the typed
|
|
359
|
+
* ones) is the safe polarity: an unlisted future type keeps its Apply button rather
|
|
360
|
+
* than silently losing its commit.
|
|
361
|
+
*
|
|
362
|
+
* Known gap this does not close, and it predates the flag: the projected date template
|
|
363
|
+
* binds `[ngModel]` one-way and commits on `(onSelect)`, which the datepicker raises for
|
|
364
|
+
* a calendar pick but not for `onUserInput` — so a date TYPED into the field never
|
|
365
|
+
* reaches the constraint at all, with or without an Apply button.
|
|
366
|
+
*/
|
|
367
|
+
filterAppliesOnChange(filterType: string): boolean;
|
|
241
368
|
navigateToDetails(rowId: number): void;
|
|
242
369
|
/**
|
|
243
370
|
* Handle row click event. Arbitration between "this click navigates the row" and "this click
|
|
@@ -332,6 +459,21 @@ export declare class Column<T = any> {
|
|
|
332
459
|
filterType?: 'text' | 'date' | 'multiselect' | 'boolean' | 'numeric' | 'blob';
|
|
333
460
|
filterPlaceholder?: string;
|
|
334
461
|
showMatchModes?: boolean;
|
|
462
|
+
/**
|
|
463
|
+
* Narrows the match modes this column's filter menu offers. Declaration order is display
|
|
464
|
+
* order, and the FIRST entry becomes the column's default match mode. Omit for the filter
|
|
465
|
+
* type's full list and standard default. Example — a datetime column where an exact-equals
|
|
466
|
+
* match can never hit: `matchModes: [MatchModeCodes.GreaterThan, MatchModeCodes.LessThan]`.
|
|
467
|
+
*
|
|
468
|
+
* Read at declaration time: PrimeNG generates the dropdown once in its own `ngOnInit`, so
|
|
469
|
+
* reassigning this later never reaches the rendered menu.
|
|
470
|
+
*
|
|
471
|
+
* The OFFERED list needs `showMatchModes: true` to be visible, but the default mode applies
|
|
472
|
+
* either way — declaring `matchModes` on a column with no picker is the supported way to
|
|
473
|
+
* change just that column's default. Only modes the filter type actually has are honored;
|
|
474
|
+
* anything else is logged and ignored (`computeMatchModes`).
|
|
475
|
+
*/
|
|
476
|
+
matchModes?: MatchModeCodes[];
|
|
335
477
|
showAddButton?: boolean;
|
|
336
478
|
dropdownOrMultiselectValues?: PrimengOption[];
|
|
337
479
|
actions?: Action[];
|
|
@@ -351,12 +493,15 @@ export declare class Column<T = any> {
|
|
|
351
493
|
*/
|
|
352
494
|
lockVisible?: boolean;
|
|
353
495
|
/**
|
|
354
|
-
* CSS length
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
496
|
+
* CSS length fixing this column's width (e.g. `'8rem'`). Overrides the per-filter-type default,
|
|
497
|
+
* which is sized for the HEADER — filter input plus match-mode dropdown — and so is generous
|
|
498
|
+
* for a column of short values.
|
|
499
|
+
*
|
|
500
|
+
* Declare a width on the columns that must stay NARROW and leave the flexible one undeclared:
|
|
501
|
+
* the table distributes its surplus in proportion to the declared widths, so a column carrying
|
|
502
|
+
* the long values wants the larger number, not a cap.
|
|
358
503
|
*/
|
|
359
|
-
|
|
504
|
+
width?: string;
|
|
360
505
|
/**
|
|
361
506
|
* Fired when this column's cell is clicked. Receives a {@link CellClickEvent} with the row id,
|
|
362
507
|
* the column field, the full row, the raw and formatted cell value, the clicked `<td>` element
|
|
@@ -367,13 +512,14 @@ export declare class Column<T = any> {
|
|
|
367
512
|
* Not applied to editable cells — those belong to their inline input.
|
|
368
513
|
*/
|
|
369
514
|
onCellClick?: (event: CellClickEvent) => void;
|
|
370
|
-
constructor({ name, field, filterField, filterType, filterPlaceholder, showMatchModes, showAddButton, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible,
|
|
515
|
+
constructor({ name, field, filterField, filterType, filterPlaceholder, showMatchModes, matchModes, showAddButton, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible, width, onCellClick, }?: {
|
|
371
516
|
name?: string;
|
|
372
517
|
field?: string & keyof T;
|
|
373
518
|
filterField?: string & keyof T;
|
|
374
519
|
filterType?: 'text' | 'date' | 'multiselect' | 'boolean' | 'numeric' | 'blob';
|
|
375
520
|
filterPlaceholder?: string;
|
|
376
521
|
showMatchModes?: boolean;
|
|
522
|
+
matchModes?: MatchModeCodes[];
|
|
377
523
|
showAddButton?: boolean;
|
|
378
524
|
dropdownOrMultiselectValues?: PrimengOption[];
|
|
379
525
|
actions?: Action[];
|
|
@@ -383,7 +529,7 @@ export declare class Column<T = any> {
|
|
|
383
529
|
sortable?: boolean;
|
|
384
530
|
visible?: boolean;
|
|
385
531
|
lockVisible?: boolean;
|
|
386
|
-
|
|
532
|
+
width?: string;
|
|
387
533
|
onCellClick?: (event: CellClickEvent) => void;
|
|
388
534
|
});
|
|
389
535
|
}
|
|
@@ -35,12 +35,19 @@ export declare class SpiderlyDataViewComponent<T> implements OnInit {
|
|
|
35
35
|
clearFiltersIcon: string;
|
|
36
36
|
getPaginatedListObservableMethod: (filter: Filter) => Observable<PaginatedResult>;
|
|
37
37
|
lastLazyLoadEvent: TableLazyLoadEvent;
|
|
38
|
-
loading
|
|
38
|
+
private loading;
|
|
39
39
|
matchModeDateOptions: SelectItem[];
|
|
40
40
|
matchModeNumberOptions: SelectItem[];
|
|
41
41
|
cardBody: TemplateRef<any>;
|
|
42
42
|
constructor(translocoService: TranslocoService, locale: string);
|
|
43
43
|
ngOnInit(): void;
|
|
44
|
+
onPageChange(container: HTMLElement): void;
|
|
45
|
+
/**
|
|
46
|
+
* The one pending predicate: PrimeNG's overlay and the container's `aria-busy` must never
|
|
47
|
+
* disagree. Mirrors the data table's, including why it is not also testing `items`; the
|
|
48
|
+
* rationale lives there.
|
|
49
|
+
*/
|
|
50
|
+
get isPending(): boolean;
|
|
44
51
|
lazyLoad(event: TableLazyLoadEvent): void;
|
|
45
52
|
filter(event: TableFilterEvent): void;
|
|
46
53
|
getDefaultMatchMode(filterType: string): any;
|
|
@@ -45,3 +45,9 @@ export declare function getImageDimensions(file: File): Promise<{
|
|
|
45
45
|
width: number;
|
|
46
46
|
height: number;
|
|
47
47
|
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Scrolls `element` back under the top of the viewport, but only when it has already scrolled
|
|
50
|
+
* above it. Set the element's `scroll-margin-top` to whatever fixed chrome must stay clear.
|
|
51
|
+
* Assumes the element's nearest scroll container is the viewport itself.
|
|
52
|
+
*/
|
|
53
|
+
export declare function scrollElementIntoViewIfAboveViewport(element: HTMLElement): void;
|
package/package.json
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
@use "../../styles/layout/mixins" as *;
|
|
2
|
+
|
|
1
3
|
.table-header {
|
|
2
4
|
display: flex;
|
|
3
5
|
justify-content: space-between;
|
|
4
6
|
align-items: center;
|
|
5
7
|
}
|
|
6
8
|
|
|
9
|
+
// The active-filter header icon (the projected filtericon template). Bare on purpose:
|
|
10
|
+
// scoping it under PrimeNG's .p-datatable-column-filter-button would compile to a
|
|
11
|
+
// descendant selector requiring OUR _ngcontent on the button, which is PrimeNG-rendered
|
|
12
|
+
// markup — the rule would die silently. Only elements authored in this component's
|
|
13
|
+
// template can match, and the icon is the only .pi-filter-fill here.
|
|
14
|
+
.pi-filter-fill {
|
|
15
|
+
color: var(--p-primary-color);
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
@media (max-width: 640px) {
|
|
8
19
|
.table-header {
|
|
9
20
|
display: flex;
|
|
@@ -64,6 +75,11 @@
|
|
|
64
75
|
|
|
65
76
|
:host {
|
|
66
77
|
// In-template content only — rules for teleported overlay content go at top level (see above).
|
|
78
|
+
|
|
79
|
+
// onPageChange scrolls this wrapper back into view; the mixin carries the offset contract.
|
|
80
|
+
.spiderly-table-container {
|
|
81
|
+
@include scroll-target-below-top-chrome();
|
|
82
|
+
}
|
|
67
83
|
// The `.clickable` affordance is shared: clickable rows (navigateOnRowClick) and opt-in cells
|
|
68
84
|
// (Column.onCellClick) both get a pointer cursor; cells additionally underline on hover.
|
|
69
85
|
::ng-deep .clickable {
|
|
@@ -78,6 +94,24 @@
|
|
|
78
94
|
user-select: none;
|
|
79
95
|
}
|
|
80
96
|
|
|
97
|
+
// The cost the fixed layout would otherwise hand over: text no longer widens its column, so
|
|
98
|
+
// it wraps inside it and a long value grows the row — which pushes every row below it, a worse
|
|
99
|
+
// shift than the horizontal one the fixed layout removes. One line, ellipsis, whole value still
|
|
100
|
+
// in the DOM to select and copy.
|
|
101
|
+
//
|
|
102
|
+
// Default cell only; why it cannot reach a consumer's cell template: CLAUDE.md → Column widths.
|
|
103
|
+
.cell-text {
|
|
104
|
+
display: block;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
text-overflow: ellipsis;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// PrimeNG-rendered markup, so ::ng-deep is the only way in; the mixin carries the contract.
|
|
111
|
+
::ng-deep .p-datatable-mask {
|
|
112
|
+
@include table-pending-veil();
|
|
113
|
+
}
|
|
114
|
+
|
|
81
115
|
::ng-deep {
|
|
82
116
|
.p-datatable-thead {
|
|
83
117
|
position: unset !important;
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
@use "../../styles/layout/mixins" as *;
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
// onPageChange scrolls this wrapper back into view; the mixin carries the offset contract.
|
|
5
|
+
.spiderly-table-container {
|
|
6
|
+
@include scroll-target-below-top-chrome();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// PrimeNG-rendered markup, so ::ng-deep is the only way in. Kept under :host deliberately:
|
|
10
|
+
// the block further down is at SCSS top level, which strips encapsulation entirely and leaks
|
|
11
|
+
// its rules to every component in the consuming app. Do not extend that one.
|
|
12
|
+
::ng-deep .p-datatable-mask {
|
|
13
|
+
@include table-pending-veil();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
::ng-deep {
|
|
2
18
|
.p-datatable-tbody {
|
|
3
19
|
display: flex;
|
|
@@ -4,10 +4,24 @@
|
|
|
4
4
|
box-sizing: border-box;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Declared through :where() so both vars carry ZERO specificity: a consumer's own
|
|
8
|
+
// `html { --spiderly-… }` then wins wherever it sits in the cascade, instead of the override
|
|
9
|
+
// working or not depending on stylesheet order. Full contract, and the fallback rule
|
|
10
|
+
// components must follow: Angular/CLAUDE.md → fixed-chrome offset.
|
|
11
|
+
:where(html) {
|
|
12
|
+
// Two names because they are two quantities that merely coincide in THIS shell: the topbar's
|
|
13
|
+
// own height, and how much fixed chrome covers the top of the viewport. A shell that pins a
|
|
14
|
+
// second bar under the topbar raises the inset without stretching the topbar.
|
|
15
|
+
--spiderly-topbar-height: 5rem;
|
|
16
|
+
--spiderly-viewport-top-inset: var(--spiderly-topbar-height);
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
html {
|
|
8
20
|
height: 100%;
|
|
9
21
|
font-size: $scale;
|
|
10
|
-
|
|
22
|
+
|
|
23
|
+
// No `scroll-behavior` here, deliberately (2026-08-29) — the argument against re-adding it
|
|
24
|
+
// is in Angular/CLAUDE.md → fixed-chrome offset.
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
body {
|
|
@@ -11,3 +11,36 @@
|
|
|
11
11
|
transition: box-shadow 0.2s;
|
|
12
12
|
box-shadow: inset 0 0 0 0.2rem var(--p-primary-400);
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
// Marks an element as a scroll-into-view target that must clear the shell's fixed top chrome.
|
|
16
|
+
// This is the ONE declaration of that offset: scrollElementIntoViewIfAboveViewport reads the
|
|
17
|
+
// computed value back rather than taking a number, so nothing in TypeScript knows the shell's
|
|
18
|
+
// dimensions. The 0px fallback is what stops a consumer running its own shell (no
|
|
19
|
+
// --spiderly-viewport-top-inset declared) from getting a phantom gap above the element.
|
|
20
|
+
@mixin scroll-target-below-top-chrome() {
|
|
21
|
+
scroll-margin-top: calc(var(--spiderly-viewport-top-inset, 0px) + 1rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The overlay PrimeNG mounts over a table while a fetch is in flight (.p-datatable-mask).
|
|
25
|
+
// Its own theme token is a modal-strength scrim -- rgba(0,0,0,0.4) light, 0.6 dark -- which
|
|
26
|
+
// makes the rows underneath unreadable. Since the whole point of keeping the previous page on
|
|
27
|
+
// screen during a refetch is that it stays READABLE, the scrim is replaced by a light veil.
|
|
28
|
+
// Three parts, each load-bearing:
|
|
29
|
+
// - `color`, because SpinnerIcon paints fill="currentColor". Overriding only the background
|
|
30
|
+
// leaves a near-white spinner on a near-white veil.
|
|
31
|
+
// - `flex-start` plus a top pad, because the mask spans the whole datatable root: a centred
|
|
32
|
+
// spinner on a long list sits far below the fold, worst right after a page change has
|
|
33
|
+
// scrolled the reader to the table's top. The pad is deliberately approximate -- it only has
|
|
34
|
+
// to land the spinner inside the first screenful, not on any particular row. `position:
|
|
35
|
+
// sticky` was tried and cannot work here: the icon's containing block IS the mask.
|
|
36
|
+
// - A fallback on every var: Karma loads no PrimeNG theme, and a consumer need not either.
|
|
37
|
+
@mixin table-pending-veil() {
|
|
38
|
+
background: color-mix(
|
|
39
|
+
in srgb,
|
|
40
|
+
var(--p-content-background, #ffffff) 65%,
|
|
41
|
+
transparent
|
|
42
|
+
);
|
|
43
|
+
color: var(--p-primary-color, #334155);
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
padding-top: 6rem;
|
|
46
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
.layout-topbar {
|
|
5
5
|
position: fixed;
|
|
6
|
-
height:
|
|
6
|
+
height: var(--spiderly-topbar-height);
|
|
7
7
|
z-index: 997;
|
|
8
8
|
left: 0;
|
|
9
9
|
top: 0;
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
border-radius: var(--p-content-border-radius);
|
|
137
137
|
padding: 1rem;
|
|
138
138
|
right: 2rem;
|
|
139
|
-
top:
|
|
139
|
+
top: var(--spiderly-topbar-height);
|
|
140
140
|
min-width: 15rem;
|
|
141
141
|
display: none;
|
|
142
142
|
-webkit-animation: scalein 0.15s linear;
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
border-radius: var(--p-content-border-radius);
|
|
182
182
|
padding: 1rem;
|
|
183
183
|
right: 2rem;
|
|
184
|
-
top:
|
|
184
|
+
top: var(--spiderly-topbar-height);
|
|
185
185
|
min-width: 15rem;
|
|
186
186
|
-webkit-animation: scalein 0.15s linear;
|
|
187
187
|
animation: scalein 0.15s linear;
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
@media (max-width: 991px) {
|
|
197
197
|
width: 210px;
|
|
198
198
|
right: 1rem;
|
|
199
|
-
top:
|
|
199
|
+
top: calc(var(--spiderly-topbar-height) - 0.5rem);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
.layout-topbar-profile-dropdown-button {
|