spiderly 19.11.9 → 19.12.1

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,16 +1,17 @@
1
- import { AfterViewInit, ErrorHandler, EventEmitter, OnChanges, OnDestroy, OnInit, QueryList, TemplateRef } from '@angular/core';
1
+ import { AfterViewInit, ErrorHandler, EventEmitter, OnChanges, OnDestroy, OnInit, QueryList, SimpleChanges, TemplateRef } from '@angular/core';
2
2
  import { ActivatedRoute, Router } from '@angular/router';
3
3
  import { TranslocoService } from '@jsverse/transloco';
4
- import { SelectItem, SortMeta } from 'primeng/api';
4
+ import { SortMeta } from 'primeng/api';
5
5
  import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
6
- import { Table, TableFilterEvent, TableLazyLoadEvent } from 'primeng/table';
6
+ import { Table, TableLazyLoadEvent } from 'primeng/table';
7
7
  import { Observable } from 'rxjs';
8
8
  import { CellTemplateContext, SpiderlyCellTemplateDirective } from '../../directives/spiderly-cell-template.directive';
9
9
  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
+ import { FilterSource, SortKeyLabel } from '../../filters/filter-store';
14
+ import { SpiderlyFilterTemplateDirective } from '../../directives/spiderly-filter-template.directive';
14
15
  import { ConfigServiceBase } from '../../services/config.service.base';
15
16
  import { SpiderlyMessageService } from '../../services/spiderly-message.service';
16
17
  import { SpiderlyFormControl } from '../spiderly-form-control/spiderly-form-control';
@@ -26,6 +27,52 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
26
27
  private locale;
27
28
  private readonly destroy$;
28
29
  table: Table;
30
+ private readonly injector;
31
+ private readonly zone;
32
+ /**
33
+ * The selection column's declared share. Under the fixed layout this is a RATIO, not a width.
34
+ *
35
+ * 4rem, down from 6: the original reserved room for a five-digit select-all counter sitting at
36
+ * body text size beside the box, which is the widest thing it can ever hold and not what it
37
+ * holds on any ordinary screen. The counter is a smaller step now (see the SCSS), so the same
38
+ * five digits fit in two thirds of the space.
39
+ */
40
+ protected readonly selectionColumnWidth = "4rem";
41
+ /**
42
+ * The selection column's MEASURED width, which is what the frozen identity column offsets
43
+ * itself by. Declaring `6rem` on both looked like the one-telling answer and is wrong: widths
44
+ * are shares, so that column renders at whatever proportion of the table it gets — 148px for a
45
+ * declared 6rem in one spec — and the identity column would sit 52px inside it, overlapping the
46
+ * checkboxes the moment anyone scrolled. The spec caught it; nothing about the page would have.
47
+ */
48
+ protected frozenOffsetPx: number;
49
+ private selectionWidthObserver?;
50
+ /** Aborts an in-flight column resize's document listeners, from `onUp` or from teardown. */
51
+ private resizeAbort;
52
+ /**
53
+ * Whether this column is pinned against the left edge. Only the FIRST column, and only when it
54
+ * is `lockVisible`: a sticky column in the middle of the grid pins the wrong thing and leaves a
55
+ * hole where it used to be. Once the grid scrolls sideways a row otherwise loses its identity
56
+ * — "Intesa, plaćeno, 12.400" with no idea whose order it is — and NN/g's Data Tables is
57
+ * explicit that the leftmost header column must lock in place. `lockVisible` already names
58
+ * exactly that column in every table, so this needs no new API (CLAUDE.md -> decision 8).
59
+ */
60
+ isColumnFrozen(col: Column): boolean;
61
+ /**
62
+ * The rightmost pinned cell, which carries the seam's shadow. It is the identity column when
63
+ * there is one and the checkbox column otherwise — a fact only this side knows, which is why
64
+ * the SCSS asking for it as `:last-of-type` drew nothing at all.
65
+ */
66
+ isLastFrozenColumn(col: Column): boolean;
67
+ get isSelectionColumnLastFrozen(): boolean;
68
+ /** A frozen column starts after the checkbox column, when there is one. */
69
+ frozenColumnOffset(): string;
70
+ /**
71
+ * Tracks the selection column's rendered width. A ResizeObserver rather than a one-off read:
72
+ * every column drag and every window resize re-splits the shares, so the offset moves without
73
+ * anything about the selection column itself changing.
74
+ */
75
+ private observeSelectionWidth;
29
76
  /**
30
77
  * Custom toolbar content projected via `<ng-template spiderlyDataTableActions>`.
31
78
  * Rendered in the caption action area ahead of the built-in buttons.
@@ -43,6 +90,29 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
43
90
  /** Paginator page-size choices; merge rules and persistence: CLAUDE.md → "Rows-per-page". */
44
91
  rowsPerPageOptions: number[];
45
92
  cols: Column[];
93
+ /**
94
+ * The consumer's filter store — the table's WHOLE filter surface, rendered as the chip bar. A
95
+ * table given none has no filtering at all (`Column.filterType` is only the value's shape; the
96
+ * legacy header-filter fallback was deleted — CLAUDE.md -> "Operator-owned view").
97
+ *
98
+ * READ ONCE, in ngOnInit — a store assigned later is silently ignored: the requery effect
99
+ * stays subscribed to the first store's `applied()`, and `resolvedStateKey` (which
100
+ * `additionalFilterIdLong` feeds) is never re-derived either. A consumer that must swap
101
+ * stores at runtime destroys and recreates the table around the swap — PACMS
102
+ * `integration-matching-products` nulls its view model for exactly this. Teaching
103
+ * ngOnChanges to re-arm both is the open upstream fix.
104
+ */
105
+ filters?: FilterSource;
106
+ /** Saved questions for this table, rendered as tabs above the bar. */
107
+ views?: TableView[];
108
+ /**
109
+ * The view currently selected. Seeded from the first view in `ngOnInit` — then overridden by
110
+ * `restoreActiveView`, so a reload lands where the operator was — and never left null on a
111
+ * table that has views: `viewScope` keys the layout off it, so an unseeded table writes its
112
+ * width and order to the SAME key an unmigrated table uses — orphaned the moment the operator
113
+ * clicks the first tab — and no tab reads as selected until they do.
114
+ */
115
+ activeViewId: string | null;
46
116
  /** Whether the paginator is shown. Pass only when `hasLazyLoad === false`. Defaults to `true`. */
47
117
  showPaginator: boolean;
48
118
  /** Whether the table is wrapped in a card container. Defaults to `false`. */
@@ -86,9 +156,6 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
86
156
  onIsAllSelectedChange: EventEmitter<AllClickEvent>;
87
157
  selectedLazyLoadObservableMethod: (tableFilter: Filter) => Observable<LazyLoadSelectedIdsResult>;
88
158
  additionalFilterIdLong: number;
89
- matchModeDateOptions: SelectItem[];
90
- matchModeNumberOptions: SelectItem[];
91
- matchModeTextOptions: SelectItem[];
92
159
  /** Whether the "Add" button is shown. Defaults to `true`. */
93
160
  showAddButton: boolean;
94
161
  /** Whether the "Export to Excel" button is shown. Defaults to `true`. */
@@ -163,7 +230,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
163
230
  * backend `.Take`s whatever it is sent.
164
231
  */
165
232
  private mergeActivePageSizesIntoOptions;
166
- ngOnChanges(): void;
233
+ ngOnChanges(changes: SimpleChanges): void;
167
234
  ngOnInit(): void;
168
235
  /**
169
236
  * User visibility choices (field → visible), holding only fields the user explicitly
@@ -171,12 +238,6 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
171
238
  * default later flows through to every user who never touched that column.
172
239
  */
173
240
  private columnVisibilityOverrides;
174
- /**
175
- * Columns revealed by the load-time reconciliation. Kept apart from
176
- * `columnVisibilityOverrides` so persisting a later unrelated toggle can't promote a
177
- * transient safety reveal into a durable choice.
178
- */
179
- private revealedByConstraint;
180
241
  /** A data column shows values for a `field`; anything else (the actions column) always renders. */
181
242
  private static isDataColumn;
182
243
  /**
@@ -185,8 +246,46 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
185
246
  * are a transient working set — different natural lifetimes.
186
247
  */
187
248
  private get columnsStateKey();
188
- /** Actions columns always render; data columns follow reveal/override, then declared default. */
249
+ /** Actions columns always render; data columns follow the override, then declared default. */
189
250
  isColumnVisible(col: Column): boolean;
251
+ /**
252
+ * `cols` in the operator's order. A field the stored order has never seen — a column added in a
253
+ * later release — sorts to the END rather than displacing a layout someone built by hand
254
+ * (CLAUDE.md -> "Operator-owned view", decision 7); the sort is stable, so those keep their
255
+ * declared order among themselves. Actions columns carry no field and are always last, which is
256
+ * where every consumer already declares them.
257
+ */
258
+ private orderedCols;
259
+ /**
260
+ * Whether this column can move one place in `direction` (-1 left, 1 right). A locked column is
261
+ * the row's anchor — and, once the left edge freezes, the thing a horizontal scroll keeps in
262
+ * view — so it does not move and nothing moves in front of it.
263
+ */
264
+ canMoveColumn(col: Column, direction: -1 | 1): boolean;
265
+ moveMenuColumn(direction: -1 | 1): void;
266
+ /** The move itself, shared by the menu and the drag — two entry points, one rule. */
267
+ private placeColumn;
268
+ private dataColumnsInOrder;
269
+ /**
270
+ * Header drag, ours rather than PrimeNG's `pReorderableColumn`, for two reasons: its
271
+ * `onMouseDown` arms the th for anything that is not an INPUT, a TEXTAREA or its OWN resizer —
272
+ * so our menu chevron and our resize grip would each start a column drag, and reaching for a
273
+ * width would silently reorder the grid — and its `onColumnDrop` reorders blind, with no notion
274
+ * of a locked column (CLAUDE.md -> decision 6).
275
+ *
276
+ * A shortcut for neighbours, never the mechanism: HTML5 dnd has no edge auto-scroll, so a column
277
+ * scrolled off the right is unreachable this way, and there is no keyboard path at all. The menu
278
+ * carries both.
279
+ *
280
+ * This half is the arming rule; the drop rule is `canDropOn` below.
281
+ */
282
+ onHeaderMouseDown(event: MouseEvent): void;
283
+ onHeaderDragStart(col: Column, event: DragEvent): void;
284
+ onHeaderDragOver(col: Column, event: DragEvent): void;
285
+ onHeaderDrop(col: Column, event: DragEvent): void;
286
+ /** Nothing lands on, or in front of, the locked column. Enforced here as well as in the menu:
287
+ * a drop is a separate entry point, and the rule is about the grid rather than about a button. */
288
+ private canDropOn;
190
289
  private refreshVisibleCols;
191
290
  /**
192
291
  * Locked columns can't be toggled; the last visible data column can't be hidden — the
@@ -194,46 +293,109 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
194
293
  */
195
294
  canToggleColumn(col: Column): boolean;
196
295
  toggleColumn(col: Column, visible: boolean): void;
197
- /** Restores every column to its declared default and forgets the stored override. */
198
- resetColumnVisibility(): void;
199
296
  /**
200
- * A hidden column contributes nothing to filtering or sorting: the header is the only
201
- * filter surface, so a kept constraint would restrict the data invisibly. Reloads (once)
202
- * only when a constraint was actually cleared — plain hides don't need a server round-trip.
297
+ * Restores every column to its declared default and forgets the stored overrides — visibility,
298
+ * wrap, order and width alike. It undid visibility only until the header menu shipped three
299
+ * more gestures beside it, and a layout with no way back is worse than one with no knobs.
300
+ *
301
+ * On a STORE table, hiding — here or from the menu — never touches filters or sort: the chip
302
+ * bar names every applied filter and the sort chip names the sort, hidden column or not
303
+ * (decision 2b). A storeless table keeps the sort half of the legacy invariant instead — see
304
+ * `dropHiddenColumnSort`.
305
+ */
306
+ resetColumnLayout(): void;
307
+ /**
308
+ * The sort half of the legacy "hidden contributes nothing" invariant, kept ONLY for storeless
309
+ * tables (the generated M2M details grids, until spiderly#407 scaffolds their stores): they
310
+ * have no sort chip, so a hidden column's sort would order the grid with nothing on screen
311
+ * naming it and no control left to clear it. The filter half needs no heir — `lazyLoad`
312
+ * overwrites the outgoing filters unconditionally. On a store table this is a no-op:
313
+ * decision 2b's chips are the visible surface, hidden column or not.
314
+ */
315
+ private dropHiddenColumnSort;
316
+ /**
317
+ * Per-column wrap, by `field`. A SEPARATE key from the visibility overrides on purpose: folding
318
+ * both into one object would invalidate every layout already in an operator's localStorage, and
319
+ * this is the key the width and order overrides will join (decision 5).
203
320
  */
204
- private clearHiddenColumnConstraints;
205
- /** Whether filter metadata (single or array) carries a real constraint, not just an empty slot. */
206
- private static isActiveFilterMeta;
321
+ private columnWrap;
322
+ /** Data-column fields in the operator's order. Empty means "as declared". */
323
+ private columnOrder;
207
324
  /**
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.
325
+ * Per-column width overrides, as SHARES in the same unit `getColWidth` emits — never pixels.
326
+ * Under the fixed layout the browser splits surplus in proportion to these, so a pixel would
327
+ * pin the column and stop it answering the window: the operator on a 1280 laptop and the one
328
+ * on a 2560 monitor would get the same frozen column (decision 4).
210
329
  */
211
- filterKey(col: Column): string | undefined;
330
+ private columnWidths;
212
331
  /**
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.
332
+ * Layout keys carry the ACTIVE VIEW, which is what makes a view more than a saved filter: a
333
+ * picking view and a payments view want different COLUMNS, not just different rows. Global
334
+ * layout would re-create the original complaint one level up — the operator still re-picking
335
+ * columns every time the job changes.
336
+ *
337
+ * A table with no views has no segment, so the twenty-six tables that have not migrated keep
338
+ * reading exactly the keys they already wrote.
219
339
  */
220
- private appliedFilterKeys;
340
+ private get viewScope();
221
341
  /**
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".
342
+ * Where the applied filters live. Follows `stateStorage` rather than the layout's always-local
343
+ * rule, because that is where they lived before the bar took them off the header: PrimeNG's
344
+ * stateful table persisted them for free, and nothing replaced it — a refresh dropped every
345
+ * filter (Filip, on /tags). A filter is a question you are in the middle of asking, not a
346
+ * durable preference like a column's width.
225
347
  */
226
- isColumnFiltered(col: Column): boolean;
227
- /** Whether the column carries a constraint at all, applied or still being typed. */
228
- private columnHasConstraint;
348
+ private get filtersStateKey();
349
+ private get filterStorage();
229
350
  /**
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.
351
+ * Where the active view id lives. Deliberately NOT view-scoped — it is the input `viewScope`
352
+ * derives from — and it follows `stateStorage` like the filters rather than the layout's
353
+ * always-local rule, because the id and the filters it scopes are one question: a durable id
354
+ * over session-scoped filters would restore a tab with nothing under it.
235
355
  */
236
- private snapshotAppliedFilters;
356
+ private get viewStateKey();
357
+ /** Only `selectView` writes; the `ngOnInit` seed does not — absence means "the default". */
358
+ private persistActiveView;
359
+ /**
360
+ * Puts a reload back on the view the operator was using. Every persisted key carries the view
361
+ * segment, so before this existed the seed decided what got restored: F5 landed on the first
362
+ * view and read `…:all:filters` — the tab snapped to "Sve" and the bar came back empty while
363
+ * the operator's filters sat intact under the old view's segment (Filip, on /porudzbine).
364
+ *
365
+ * An id naming no current view keeps the seed AND the stored key: "vanished by deploy" and
366
+ * "not yet arrived" are indistinguishable here (order-list's catalog-licensed "Za pakovanje"
367
+ * is literally both), so `adoptStoredView` re-reads the key on every `[views]` change and the
368
+ * key self-heals on the next explicit click.
369
+ */
370
+ private restoreActiveView;
371
+ /**
372
+ * The second half of `restoreActiveView`: a stored view that did not exist at init is adopted
373
+ * when a later `[views]` change brings it — through `selectView`, so it gets the same
374
+ * stored-wins/transient/layout/requery semantics a click would have had. There is no parked
375
+ * copy of the stored id — the key itself is re-read, and because every explicit pick
376
+ * overwrites it, the operator's own choice wins by construction. Accepted cost of adopting
377
+ * late instead of holding the first request: a brief flash of the seeded view's rows and a
378
+ * second request when the late view lands.
379
+ */
380
+ private adoptStoredView;
381
+ private viewById;
382
+ private persistAppliedFilters;
383
+ /**
384
+ * Applies the right filters for entering `view` — the transient rule's one home. A TRANSIENT
385
+ * view's `apply` is a function of NOW, so it always re-derives; restoring would put
386
+ * yesterday's date under a tab claiming "today" (`persistAppliedFilters` has no transient
387
+ * guard, so storage holds the LAST answer). A non-transient view's own stored answer wins
388
+ * over its `apply`. The semantics argument is the one deliberate difference between the
389
+ * callers: a 'click' re-asks the view's question when nothing is stored, a 'refresh' returns
390
+ * the empty state the operator left — they cleared it, and F5 must give the cleared state
391
+ * back. On a table with no views, `view` is null and this is a plain restore of the
392
+ * `persistAppliedFilters` snapshot.
393
+ */
394
+ private enterViewFilters;
395
+ private get layoutStateKey();
396
+ /** One key for the whole layout — wrap now, widths next (decision 5). */
397
+ private persistColumnLayout;
398
+ private restoreColumnLayout;
237
399
  private persistColumnVisibility;
238
400
  private restoreColumnVisibility;
239
401
  /**
@@ -241,20 +403,74 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
241
403
  * because we need it before PrimeNG's restoreState() runs on the first [value] change.
242
404
  */
243
405
  private persistedTableState;
406
+ private activeView;
407
+ /**
408
+ * Where the operator's per-view sort override lives. Null on a table without views — those
409
+ * keep the pure PrimeNG sort persistence they always had — and null on a STORELESS table:
410
+ * there "hidden contributes nothing" retires a hidden column's sort (`dropHiddenColumnSort`)
411
+ * with no persist call at the hide site, so an override restored blind at the next init
412
+ * would re-impose a sort no chip can name. Store tables — where every viewed consumer
413
+ * lives — have the chip. Follows `stateStorage` like the filters and the view id: the sort
414
+ * is part of the same question they scope.
415
+ */
416
+ private get viewSortStateKey();
417
+ /** The operator's stored sort for the active view, sanitized like any persisted sort. */
418
+ private persistedViewSort;
419
+ /** The sort this view asks for: the operator's stored override, else its declared sort. */
420
+ private viewSort;
421
+ /**
422
+ * Records the current sort as the active view's override — called from the EXPLICIT sort
423
+ * gestures only (header click, menu sort, Clear filters), never from a load: a sort merely
424
+ * inherited at init must not crystallize into an override, or a later change to the view's
425
+ * declared sort would stop flowing to operators who never touched sort — the same
426
+ * flows-through rule `columnVisibilityOverrides` documents. A sort equal to the declared
427
+ * default is stored as ABSENCE for the same reason.
428
+ */
429
+ private persistActiveViewSort;
430
+ /**
431
+ * Puts the entering view's sort on the grid — stored override first, then the declared sort;
432
+ * a view stating no preference keeps the ordering already there (the pre-decision-10-sort
433
+ * behavior, and the least surprising for the tab an operator never sorted). Broadcast only —
434
+ * the caller owns the refetch, because which request carries the new sort differs between the
435
+ * store and storeless paths.
436
+ */
437
+ private applyViewSort;
438
+ /**
439
+ * Rewrites the sort inside PrimeNG's persisted state to the one `ngOnInit` resolved. The two
440
+ * can disagree only on a viewed table — PrimeNG's state is table-global while the resolved
441
+ * sort is the view's — and PrimeNG's restoreState() applies its copy on the first [value]
442
+ * change, AFTER the initial request already left with ours: without this line the grid's
443
+ * arrows and every later request silently fall back to the other view's sort. Handed the
444
+ * already-parsed state: `ngOnInit` reads the blob once for the whole init pass.
445
+ */
446
+ private alignPersistedTableStateSort;
447
+ /**
448
+ * The DECLARED default in the current context: the active view's own sort when it declares
449
+ * one, else the table-level `defaultSortField`. Every "fall back to the declared default"
450
+ * site — tri-state un-sort, Clear filters, the unsorted-load safety net — reads this, so
451
+ * un-sorting inside a view lands on the view's ordering, not the table's.
452
+ */
453
+ private defaultMultiSortMeta;
244
454
  /**
245
- * Guards the "hidden contributes nothing" invariant against state this component didn't
246
- * write (older blobs, hand-edited storage): a hidden column the persisted table state
247
- * still filters or sorts by is revealed rather than constraining the data invisibly.
248
- * In-memory only — once the constraint is gone, the user's stored choice reapplies.
455
+ * The active view's declared sort, sanitized: a non-sortable field would answer every load
456
+ * with a 400 (`keepSortableMeta`'s rationale), so it is dropped rather than sent; a view
457
+ * whose declaration sanitizes to nothing falls through to the table default.
249
458
  */
250
- private reconcileVisibilityWithPersistedConstraints;
459
+ private viewDeclaredMultiSortMeta;
251
460
  /**
252
- * The declared default as PrimeNG sort meta, or null when none is declared — or when its
253
- * column is hidden ("hidden contributes nothing"): the table then falls back to the
254
- * backend's implicit Id DESC, exactly as if no default were declared.
461
+ * The table-level declared default as PrimeNG sort meta, or null when none is declared. On a
462
+ * store table it applies even while its column is hidden — the sort chip names the ordering
463
+ * whether or not the column is on screen (decision 2b). A STORELESS table has no chip, so a
464
+ * hidden default would order the grid with nothing on screen naming it; there the default
465
+ * dies with the column, exactly as the legacy invariant had it (see `dropHiddenColumnSort`).
255
466
  */
256
- private defaultMultiSortMeta;
257
- private persistedMultiSortMeta;
467
+ private tableDefaultMultiSortMeta;
468
+ /**
469
+ * `keepSortableMeta` with "sanitized to nothing" surfaced as null — the rule every sort
470
+ * SOURCE shares ("a sort that sanitizes to nothing is no sort"), so the resolution chains
471
+ * can `??` past an empty answer instead of stopping on an empty array.
472
+ */
473
+ private sanitizedSortMeta;
258
474
  /**
259
475
  * Drops sort meta the backend has no sort case for. Persisted state outlives the rule that
260
476
  * produced it — a sort stored before a column became non-sortable (or before the library
@@ -265,21 +481,9 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
265
481
  * declares no column for.
266
482
  */
267
483
  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;
282
484
  private applyDefaultSortIfUnsorted;
485
+ /** Puts `meta` on the grid and tells the header icons — no fetch; the caller owns that. */
486
+ private broadcastSort;
283
487
  onPageChange(container: HTMLElement): void;
284
488
  /**
285
489
  * The one pending predicate: PrimeNG's overlay and the container's `aria-busy` must never
@@ -300,8 +504,6 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
300
504
  */
301
505
  private reconcileSelectionForLoadedPage;
302
506
  clientLoad(): void;
303
- private clientFilterCount;
304
- filter(event: TableFilterEvent): void;
305
507
  private loadFormArrayItems;
306
508
  /**
307
509
  * Single source for whether a column header offers click-to-sort (drives both
@@ -311,6 +513,148 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
311
513
  * sort fields are rejected with a 400, a clickable header here would be a guaranteed error toast.
312
514
  */
313
515
  isColumnSortable(col: Column): boolean;
516
+ /** The column the header menu is currently open for, and its header cell. */
517
+ menuColumn: Column | null;
518
+ private menuHeaderCell;
519
+ private draggedColumn;
520
+ /** False while a gesture that began on the menu chevron or the resize grip is in flight. */
521
+ private headerDragArmed;
522
+ /**
523
+ * A ViewChild rather than a template reference passed in from the header. The popover lives in
524
+ * the CAPTION template and the button that opens it in the HEADER one, and a reference variable
525
+ * does not cross an ng-template boundary — it arrived as undefined, and the click did nothing
526
+ * at all.
527
+ */
528
+ private readonly columnMenu;
529
+ private readonly filterBar;
530
+ /**
531
+ * Consumer-projected filter controls, forwarded to the bar. Collected here rather than by the
532
+ * bar itself because the bar is OURS: a consumer's template is projected at the table, and
533
+ * content queries only see what was projected into the component that declares them.
534
+ */
535
+ filterTemplates: QueryList<SpiderlyFilterTemplateDirective>;
536
+ private readonly selectionHeader;
537
+ openColumnMenu(col: Column, event: Event): void;
538
+ /**
539
+ * Switches to a view. Clears first, always: a view is a STATE rather than an addition, and two
540
+ * of them composed produce a question nobody asked. The clear runs through the store directly
541
+ * rather than through `clear(table)`, which would also wipe the sort and the persisted state a
542
+ * view is entitled to keep.
543
+ */
544
+ selectView(view: TableView): void;
545
+ /**
546
+ * The filter this column stands for. Defaults to the column's own `field`, because a store id
547
+ * IS a backend property name — `toFilterPayload` emits it straight into `Filter.filters`. So
548
+ * `filterId` is an escape hatch for the rare mismatch, not something every column restates:
549
+ * all eight of the first migrated table's declarations were the field name written a second
550
+ * time.
551
+ */
552
+ private filterIdFor;
553
+ /**
554
+ * Asks the STORE, not the column. Checking only that an id was declared let a typo through to
555
+ * `store.get(id)`, which reads `definitions[id].label` — so a mistyped id threw on click rather
556
+ * than greying the item out. With 195 declarations to migrate, that is the failure mode worth
557
+ * closing here rather than in each consumer.
558
+ */
559
+ canFilterColumn(col: Column): boolean;
560
+ /**
561
+ * Opens the bar's editor for this column's filter. The bar took the filter off the header and
562
+ * the header is still where an operator reaches for it, which is twenty-seven tables' worth of
563
+ * habit; this is the shortcut back rather than a second filter surface — it drives the same
564
+ * editor, on the same bar, that `+ Filter` opens.
565
+ */
566
+ filterMenuColumn(): void;
567
+ /**
568
+ * Sorts by this column in the NAMED direction. Clicking a header cycles asc, desc and off,
569
+ * which is fine for one column and a guess for a direction someone wants now — you click, look,
570
+ * and click again if it went the other way. This is also the only sort path that works from a
571
+ * keyboard.
572
+ *
573
+ * A column the generated paginator has no sort case for answers every load with a 400, so the
574
+ * items are disabled by the same predicate that keeps its header from being clickable.
575
+ */
576
+ sortMenuColumn(order: 1 | -1): void;
577
+ /**
578
+ * Sizes the column to the widest thing in it. The cheaper escape from a cramped column: no
579
+ * drag, no aim. Unlike a drag it does NOT trade with the neighbour — a minimum that grew is
580
+ * exactly the case decision 4 lets push the table past its container and start scrolling.
581
+ *
582
+ * Measures the CELL CONTENT, not the cell: the clamp lives on the inner span, so a `td` reports
583
+ * a scrollWidth that fits while the text inside it does not.
584
+ */
585
+ fitMenuColumn(): void;
586
+ /**
587
+ * The share that actually yields `needed` pixels — not `needed` scaled by the column's current
588
+ * px-per-share, which is the obvious answer and the wrong one. Shares are a PROPORTION of
589
+ * whatever width the table has, so growing this column's share also shrinks the realized width
590
+ * of every other column, and the naive figure lands short. The spec caught it as a cell that
591
+ * was still clipped after being told to fit.
592
+ *
593
+ * While the columns fit, the table keeps the container's width and the proportion has to solve
594
+ * needed / tableWidth = newShare / (otherShares + newShare)
595
+ * with `otherShares` recovered from what this column currently renders at. Once `needed` is the
596
+ * whole container the proportional regime has no answer: the table is about to overrun its
597
+ * container and scroll (decision 4), and there a share IS its own length, so the pixels convert
598
+ * straight through the root font size.
599
+ */
600
+ private shareThatFits;
601
+ hideMenuColumn(): void;
602
+ /**
603
+ * Whether this column's cells give up the one-line clamp. The DEFAULT stays clamped
604
+ * (CLAUDE.md -> "Operator-owned view", decision 9); what changes is that the operator picks
605
+ * which column pays the row height, not whoever wrote `cols`.
606
+ *
607
+ * Lands on the td as `.cell-wrap`, whose inherited `--spiderly-cell-*` properties are how the
608
+ * toggle crosses into a projected `spiderlyCellTemplate` too — the contract's one telling is
609
+ * on that SCSS rule; a consumer clamp that hard-codes its values opts out.
610
+ */
611
+ isColumnWrapped(col: Column): boolean;
612
+ get isMenuColumnWrapped(): boolean;
613
+ toggleWrapForMenuColumn(): void;
614
+ /** Exposed for the template: actions columns carry no field and get no menu. */
615
+ isDataColumn(col: Column): boolean;
616
+ /**
617
+ * What the grid is ordered by, resolved to COLUMN NAMES for the bar. Read off PrimeNG's live
618
+ * meta rather than mirrored into a field, so it cannot fall out of step with the sort the table
619
+ * is actually applying. A field with no column left (hidden, or removed in a later release)
620
+ * falls back to its own name rather than vanishing from the list.
621
+ */
622
+ /**
623
+ * What the grid is ordered by, resolved to COLUMN NAMES for the bar.
624
+ *
625
+ * A field refreshed per fetch, not a getter: it is bound to the bar's `sort`, which is a SIGNAL
626
+ * input, so a freshly mapped array failed `Object.is` on every change-detection pass and marked
627
+ * the bar's consumers dirty forever — for a value that changes once per request. Same reason
628
+ * `tableStyle` is a field.
629
+ */
630
+ sortKeys: SortKeyLabel[];
631
+ /**
632
+ * Read off the last REQUEST's sort, not `table._multiSortMeta`. On a first load with a declared
633
+ * default that field is still null: `applyDefaultSortIfUnsorted` writes the default onto the
634
+ * lazy-load event, and PrimeNG fills its own meta only once someone clicks a header. Reading the
635
+ * table there showed no sort on exactly the tables that always have one. A field with no column
636
+ * left falls back to its own name rather than vanishing.
637
+ */
638
+ private refreshSortKeys;
639
+ /** This column's share, override first, then its declared width, then the per-type default. */
640
+ columnShare(col: Column): number;
641
+ /**
642
+ * A declared width expressed in SHARE units. rem IS the share unit and px converts; any other
643
+ * unit falls back to the per-type default rather than being read as a bare number.
644
+ *
645
+ * `parseFloat` alone shipped here first, so `width: '150px'` resolved to share 150 against
646
+ * neighbours at 12 — one column eating the grid on the first drag or fit. Every declared width
647
+ * in the consumer happens to be rem today, which is exactly why nothing would have caught it.
648
+ */
649
+ private shareOfDeclaredWidth;
650
+ private defaultShare;
651
+ /**
652
+ * Trades share between the column being dragged and the one on its right, so the table keeps
653
+ * its total: widening a column never starts a horizontal scroll by itself, which only happens
654
+ * once the SUM of the minimums stops fitting. The px delta is converted through the column's
655
+ * own rendered width, which is the only place the share-to-pixel scale is actually known.
656
+ */
657
+ startColumnResize(col: Column, event: MouseEvent, th: HTMLElement): void;
314
658
  /**
315
659
  * The column's declared {@link Column.width}, or the default for its filter type.
316
660
  *
@@ -320,51 +664,6 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
320
664
  * per-type defaults say — a boolean holds "Da"/"Ne", a text column holds a name.
321
665
  */
322
666
  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;
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;
368
667
  navigateToDetails(rowId: number): void;
369
668
  /**
370
669
  * Handle row click event. Arbitration between "this click navigates the row" and "this click
@@ -384,6 +683,28 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
384
683
  ngOnDestroy(): void;
385
684
  get showSelectAllCheckbox(): boolean;
386
685
  private resetSelection;
686
+ /**
687
+ * A COMMITTED filter re-queries; a draft does not. Re-fetching per keystroke would be the same
688
+ * lie the chip bar exists to prevent, only paid for in requests rather than in credibility.
689
+ *
690
+ * Back to page one first, and off FRESH metadata rather than `reload()`: that one replays the
691
+ * cached lazy-load event, whose `first` was copied when the event was built, so a filter applied
692
+ * from page 3 would keep asking the server to skip 50 rows a narrower result set does not have.
693
+ * The grid comes back empty for a value that is definitely in it.
694
+ *
695
+ * The first run is PrimeNG's: it fires the initial lazy load itself, and re-querying here would
696
+ * make every table load twice.
697
+ */
698
+ private requeryOnAppliedFilters;
699
+ /**
700
+ * Back to page one, state saved, one fetch off FRESH metadata — the shared tail of the
701
+ * requery effect and `selectView`'s sort-only switch. The save is load-bearing: PrimeNG
702
+ * saves state only from its own interactions, so without it the pre-change page offset
703
+ * survives in storage and the NEXT visit replays it against a narrowed result set — the
704
+ * backend answers data=[] for an offset the filtered total no longer reaches, and the grid
705
+ * renders empty under chips claiming matches.
706
+ */
707
+ private refetchFromFirstPage;
387
708
  reload(): void;
388
709
  showActions(): boolean;
389
710
  getStyleForBodyColumn(col: Column<any>): string;
@@ -394,6 +715,11 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
394
715
  colTrackByFn(index: any, item: any): any;
395
716
  actionTrackByFn(index: any, item: Action): string;
396
717
  exportListToExcel(): void;
718
+ /**
719
+ * Clears everything that narrows the grid. With a store supplied it has to reach BOTH — the
720
+ * store holds the constraints and PrimeNG holds the persisted state and sort — or the bar goes
721
+ * empty while a reload brings the old filters back.
722
+ */
397
723
  clear(table: Table): void;
398
724
  setFakeIsAllSelected(): void;
399
725
  selectAll(checked: boolean): void;
@@ -426,7 +752,7 @@ export declare class SpiderlyDataTableComponent implements OnInit, OnChanges, Af
426
752
  rowUnselect(id: number): void;
427
753
  getFormArrayControlByIndex(formControlName: string, index: number): SpiderlyFormControl;
428
754
  static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyDataTableComponent, never>;
429
- 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; }; "rowsPerPageOptions": { "alias": "rowsPerPageOptions"; "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>;
755
+ 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; }; "rowsPerPageOptions": { "alias": "rowsPerPageOptions"; "required": false; }; "cols": { "alias": "cols"; "required": false; }; "filters": { "alias": "filters"; "required": false; }; "views": { "alias": "views"; "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", "filterTemplates"], never, true, never>;
430
756
  }
431
757
  export declare class Action {
432
758
  name?: string;
@@ -452,29 +778,77 @@ export declare class Action {
452
778
  onClick?: (event: ActionClickEvent) => void;
453
779
  });
454
780
  }
781
+ /**
782
+ * A saved question. The bar lets an operator BUILD one; a view is the one they ask every morning,
783
+ * asked in a click — and without them the whole column-and-filter rework is a pile of knobs
784
+ * someone re-sets daily (CLAUDE.md -> "Operator-owned view", decision 10).
785
+ *
786
+ * Declared in consumer code, not stored per user: with a handful of operators and a handful of
787
+ * fixed daily jobs, shared built-in views cover the work, while personal saved views would drift
788
+ * into five variants of the same question. Personal LAYOUT choices still persist on top, per view.
789
+ */
790
+ export interface TableView<TFilters = any> {
791
+ id: string;
792
+ label: string;
793
+ /**
794
+ * Sets the filters this view asks for. Receives the table's own store, so a consumer writes it
795
+ * with the store's typing. Called on a CLEARED store: a view is a state, not an addition.
796
+ */
797
+ apply?: (filters: TFilters) => void;
798
+ /**
799
+ * A view whose `apply` is a function of NOW — "Danas primljene", "this week". Selecting it
800
+ * always re-runs `apply` instead of restoring what an earlier visit stored: under the normal
801
+ * stored-wins rule a relative-date view clicked yesterday would restore yesterday's date under
802
+ * a tab claiming "today". Layout (columns, widths, wrap) still persists per view as usual —
803
+ * transience is about the QUESTION, not the furniture.
804
+ */
805
+ transient?: boolean;
806
+ /**
807
+ * The view's declared ordering — for the view whose question includes one ("oldest shipped
808
+ * first" IS the work list, not a reading preference). Entering the view applies it; the
809
+ * tri-state un-sort and Clear filters return to it instead of the table default; an operator's
810
+ * own sort overrides it, persisted per view on store tables (decision 10's sort half). A view
811
+ * declaring none keeps whatever ordering the grid already has, exactly as before this field
812
+ * existed.
813
+ */
814
+ sort?: SortMeta[];
815
+ }
455
816
  export declare class Column<T = any> {
456
817
  name?: string;
457
818
  field?: string & keyof T;
458
- filterField?: string & keyof T;
819
+ /**
820
+ * The column's VALUE SHAPE: it sizes the column (`getColWidth`), picks the cell rendering
821
+ * (`getRowData`, the blob branch) and the numeric right-align. The name is a fossil of the
822
+ * header-filter era — filtering lives in the store handed to `[filters]` — kept because
823
+ * renaming it would be a 195-declaration migration for zero behavior.
824
+ */
459
825
  filterType?: 'text' | 'date' | 'multiselect' | 'boolean' | 'numeric' | 'blob';
460
- filterPlaceholder?: string;
461
- showMatchModes?: boolean;
462
826
  /**
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.
827
+ * The id of the filter in the table's store that this column stands for, which is what the
828
+ * header menu's `Filter…` opens. A LINK, not a declaration: the filter exists in the store
829
+ * whether or not any column names it, and a column needs this only to offer the shortcut.
470
830
  *
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`).
831
+ * Untyped against the store on purpose — a `Column<T>` knows its row type and nothing about
832
+ * which store it will be rendered beside, so the compile-time key check lives where the store
833
+ * is declared (`createFilterStore`), not here.
475
834
  */
476
- matchModes?: MatchModeCodes[];
477
- showAddButton?: boolean;
835
+ filterId?: string;
836
+ /**
837
+ * @deprecated Dead since the header-filter deletion: NOTHING reads it. It survives on the type
838
+ * only so details-table cols emitted by an OLDER Spiderly.SourceGenerators (which wrote
839
+ * `showMatchModes: true`) still compile against this library during a mixed-version window —
840
+ * a consumer on published NuGet generators plus an npm-linked library build. Delete it once
841
+ * every consumer regenerates with a generator from this release or later (spiderly#407).
842
+ */
843
+ showMatchModes?: boolean;
844
+ /**
845
+ * @deprecated The same version-skew bridge as {@link showMatchModes}, and nothing else: older
846
+ * generators emit `filterField: 'xxxId'` for every dropdown-typed details-table column, so
847
+ * deleting it breaks the mixed window the moment a consumer adds one. NOTHING reads it —
848
+ * filter ids live in the store, linked from a column via {@link filterId}. Delete with
849
+ * `showMatchModes` (spiderly#407).
850
+ */
851
+ filterField?: string & keyof T;
478
852
  dropdownOrMultiselectValues?: PrimengOption[];
479
853
  actions?: Action[];
480
854
  editable?: boolean;
@@ -493,9 +867,8 @@ export declare class Column<T = any> {
493
867
  */
494
868
  lockVisible?: boolean;
495
869
  /**
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.
870
+ * CSS length fixing this column's width (e.g. `'8rem'`). Overrides the per-type default,
871
+ * which is generous for a column of short values.
499
872
  *
500
873
  * Declare a width on the columns that must stay NARROW and leave the flexible one undeclared:
501
874
  * the table distributes its surplus in proportion to the declared widths, so a column carrying
@@ -512,15 +885,13 @@ export declare class Column<T = any> {
512
885
  * Not applied to editable cells — those belong to their inline input.
513
886
  */
514
887
  onCellClick?: (event: CellClickEvent) => void;
515
- constructor({ name, field, filterField, filterType, filterPlaceholder, showMatchModes, matchModes, showAddButton, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible, width, onCellClick, }?: {
888
+ constructor({ name, field, filterType, filterId, showMatchModes, filterField, dropdownOrMultiselectValues, actions, editable, showTime, decimalPlaces, sortable, visible, lockVisible, width, onCellClick, }?: {
516
889
  name?: string;
517
890
  field?: string & keyof T;
518
- filterField?: string & keyof T;
519
891
  filterType?: 'text' | 'date' | 'multiselect' | 'boolean' | 'numeric' | 'blob';
520
- filterPlaceholder?: string;
892
+ filterId?: string;
521
893
  showMatchModes?: boolean;
522
- matchModes?: MatchModeCodes[];
523
- showAddButton?: boolean;
894
+ filterField?: string & keyof T;
524
895
  dropdownOrMultiselectValues?: PrimengOption[];
525
896
  actions?: Action[];
526
897
  editable?: boolean;