spiderly 19.11.7 → 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.
@@ -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: boolean;
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
- /** The column's declared {@link Column.minWidth}, or the default for its filter type. */
237
- getColHeaderWidth(col: Column): string;
238
- getColMatchModeOptions(filterType: string): SelectItem<any>[];
239
- getColMatchMode(filterType: string): any;
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 overriding this column's default minimum width (e.g. `'8rem'`). The defaults are
355
- * per filter type and sized for the HEADER — filter input plus match-mode dropdown — so they
356
- * are generous for a column of short values. It is a MINIMUM, not a width: the table still
357
- * distributes the leftover space.
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
- minWidth?: string;
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, minWidth, onCellClick, }?: {
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
- minWidth?: string;
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: boolean;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "spiderly",
3
- "version": "19.11.7",
3
+ "version": "19.11.9",
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
@@ -25,7 +25,6 @@ export * from './lib/components/auth/partials/login-verification.component';
25
25
  export * from './lib/components/auth/partials/verification-wrapper.component';
26
26
  export * from './lib/components/base-form/base-form.component';
27
27
  export * from './lib/components/card-skeleton/card-skeleton.component';
28
- export * from './lib/components/footer/footer.component';
29
28
  export * from './lib/components/index-card/index-card.component';
30
29
  export * from './lib/components/info-card/info-card.component';
31
30
  export * from './lib/components/layout/layout.component';
@@ -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,8 +4,7 @@
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  min-height: 100vh;
7
- justify-content: space-between;
8
- padding: 7rem 2rem 2rem 4rem;
7
+ padding: calc(var(--spiderly-topbar-height) + 2rem) 2rem 2rem 4rem;
9
8
  transition: margin-left $transitionDuration;
10
9
  }
11
10
 
@@ -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
- scroll-behavior: smooth;
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 {
@@ -8,7 +8,7 @@
8
8
  z-index: 999;
9
9
  overflow-y: auto;
10
10
  user-select: none;
11
- top: 7rem;
11
+ top: calc(var(--spiderly-topbar-height) + 2rem);
12
12
  left: 2rem;
13
13
  transition:
14
14
  transform $transitionDuration,
@@ -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: 5rem;
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: 5rem;
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: 5rem;
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: 4.5rem;
199
+ top: calc(var(--spiderly-topbar-height) - 0.5rem);
200
200
  }
201
201
 
202
202
  .layout-topbar-profile-dropdown-button {
@@ -5,7 +5,6 @@
5
5
  @use "./_topbar";
6
6
  @use "./_menu";
7
7
  @use "./_content";
8
- @use "./_footer";
9
8
  @use "./_responsive";
10
9
  @use "./_utils";
11
10
  @use "./_typography";
@@ -1,9 +0,0 @@
1
- import { ConfigServiceBase } from '../../services/config.service.base';
2
- import * as i0 from "@angular/core";
3
- export declare class FooterComponent {
4
- private config;
5
- companyName: string;
6
- constructor(config: ConfigServiceBase);
7
- static ɵfac: i0.ɵɵFactoryDeclaration<FooterComponent, never>;
8
- static ɵcmp: i0.ɵɵComponentDeclaration<FooterComponent, "footer", never, {}, {}, never, never, true, never>;
9
- }
@@ -1,11 +0,0 @@
1
- @use "./_variables" as *;
2
-
3
- .layout-footer {
4
- transition: margin-left $transitionDuration;
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- padding-top: 1.5rem;
9
- border-top: 1px solid var(--surface-border);
10
- margin-top: 2rem;
11
- }