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.
- package/agent/docs/angular-customization/index.md +56 -14
- package/agent/docs/angular-customization/references/helper-functions.generated.md +1 -0
- package/agent/docs/e2e-testing/index.md +10 -15
- package/agent/skills/add-entity/SKILL.md +12 -1
- package/fesm2022/spiderly.mjs +2206 -449
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +512 -141
- package/lib/directives/spiderly-filter-template.directive.d.ts +34 -0
- package/lib/directives/spiderly-overflow-title.directive.d.ts +33 -0
- package/lib/entities/filter-rule.d.ts +5 -7
- package/lib/filters/allowed-operators.d.ts +24 -0
- package/lib/filters/filter-store.d.ts +264 -0
- package/lib/filters/spiderly-filter-bar.component.d.ts +132 -0
- package/lib/services/helper-functions.d.ts +8 -0
- package/package.json +1 -1
- package/public-api.d.ts +5 -0
- package/styles/components/spiderly-data-table/spiderly-data-table.component.scss +150 -18
- package/styles/filters/spiderly-filter-bar.component.scss +163 -0
- package/styles/styles/layout/_mixins.scss +13 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TemplateRef } from '@angular/core';
|
|
2
|
+
import type { FilterHandle } from '../filters/filter-store';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Context handed to a filter template: the filter's handle, and nothing else it does not already
|
|
6
|
+
* carry. Read it with `let-f` and drive the filter through it — `f.value()` is the draft the
|
|
7
|
+
* control shows, `f.set(...)` writes one, `f.commit()` applies it.
|
|
8
|
+
*/
|
|
9
|
+
export interface FilterTemplateContext {
|
|
10
|
+
$implicit: FilterHandle;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Renders the editor for ONE filter of a `spiderly-data-table`'s bar, replacing the control the
|
|
14
|
+
* bar would have drawn for that filter's kind. Bind it to the filter's id; filters with no
|
|
15
|
+
* matching template keep the built-in control, so a table opts in one filter at a time.
|
|
16
|
+
*
|
|
17
|
+
* This is the narrow job the directive has. Placing a filter ANYWHERE else — a drawer, a modal,
|
|
18
|
+
* a header cell — needs no directive at all: `store.get(id)` returns the same handle and depends
|
|
19
|
+
* on nothing in the component tree, which is why the store is the consumer's to own.
|
|
20
|
+
*/
|
|
21
|
+
export declare class SpiderlyFilterTemplateDirective {
|
|
22
|
+
readonly template: TemplateRef<FilterTemplateContext>;
|
|
23
|
+
/** The id of the filter whose control this template renders. */
|
|
24
|
+
filterId: string;
|
|
25
|
+
constructor(template: TemplateRef<FilterTemplateContext>);
|
|
26
|
+
/**
|
|
27
|
+
* Without this the `let-` variable is `any` at every consumer, however well the context is
|
|
28
|
+
* declared — a directive's injected `TemplateRef<C>` never reaches the template that declares
|
|
29
|
+
* it. Same mechanism as `SpiderlyCellTemplateDirective`.
|
|
30
|
+
*/
|
|
31
|
+
static ngTemplateContextGuard(dir: SpiderlyFilterTemplateDirective, ctx: unknown): ctx is FilterTemplateContext;
|
|
32
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyFilterTemplateDirective, never>;
|
|
33
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<SpiderlyFilterTemplateDirective, "ng-template[spiderlyFilterTemplate]", never, { "filterId": { "alias": "spiderlyFilterTemplate"; "required": false; }; }, {}, never, never, true, never>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Puts a native `title` on an element while its text is clipped — measured when the pointer
|
|
5
|
+
* arrives, which is the only moment a native title can surface.
|
|
6
|
+
*
|
|
7
|
+
* The data table clamps a default cell to one line (see the `.cell-text` rule), which hides the
|
|
8
|
+
* value and, before this, offered nothing to read it with — the reason PACMS hand-added `[title]`
|
|
9
|
+
* on four cells of one grid. Wrapping the column is a choice someone makes; this covers the
|
|
10
|
+
* default nobody touched.
|
|
11
|
+
*
|
|
12
|
+
* Native `title`, never `pTooltip`: it needs a deliberate hover rather than firing as the cursor
|
|
13
|
+
* crosses a dense list, which is what got the SKU tooltip removed.
|
|
14
|
+
*
|
|
15
|
+
* **Measured on hover, not watched.** The first shape ran `ngAfterViewChecked` on every instance
|
|
16
|
+
* every change-detection cycle (reading `textContent`, which allocates a fresh string each call)
|
|
17
|
+
* and gave each instance its OWN `ResizeObserver`. On a 100-row grid nineteen columns wide that is
|
|
18
|
+
* ~1900 hooks per cycle and ~1900 observers in Chrome's per-frame loop — all to decide something
|
|
19
|
+
* that can only ever be seen one cell at a time. Hover is exactly-in-time, costs nothing until it
|
|
20
|
+
* happens, and needs no separate handling for a column drag or a resized window: the next hover
|
|
21
|
+
* re-measures. Nothing is lost for a screen reader either — CSS clipping never hid the text from
|
|
22
|
+
* the accessibility tree, so the title was only ever a sighted-hover affordance.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SpiderlyOverflowTitleDirective implements OnDestroy {
|
|
25
|
+
private readonly el;
|
|
26
|
+
private readonly zone;
|
|
27
|
+
private readonly onPointerEnter;
|
|
28
|
+
constructor();
|
|
29
|
+
ngOnDestroy(): void;
|
|
30
|
+
private sync;
|
|
31
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyOverflowTitleDirective, never>;
|
|
32
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<SpiderlyOverflowTitleDirective, "[spiderlyOverflowTitle]", never, {}, {}, never, never, true, never>;
|
|
33
|
+
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { MatchModeCodes } from '../enums/match-mode-enum-codes';
|
|
2
|
+
import { AllowedOperatorFor } from '../filters/allowed-operators';
|
|
2
3
|
/**
|
|
3
4
|
* Represents a filter rule used for querying or filtering data collections.
|
|
4
5
|
*
|
|
5
6
|
* The `FilterRule` class is a generic structure that defines a single filtering condition,
|
|
6
7
|
* including the match mode (comparison operator), the value to compare, and an optional logical operator.
|
|
7
8
|
*
|
|
8
|
-
* The allowed match modes are determined by the type parameter `T
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - For `Date`: supports `Equals`, `GreaterThan`, and `LessThan`.
|
|
12
|
-
* - For `number`: supports `Equals`, `GreaterThan`, `LessThan`, and `In`.
|
|
13
|
-
* - For other types: allows any value from `MatchModeCodes`.
|
|
9
|
+
* The allowed match modes are determined by the type parameter `T`, each union derived from the
|
|
10
|
+
* one operator table in `filters/allowed-operators.ts` (for other types, any `MatchModeCodes`
|
|
11
|
+
* value is allowed).
|
|
14
12
|
*
|
|
15
13
|
* @template T The type of the value to filter by.
|
|
16
14
|
*/
|
|
@@ -19,5 +17,5 @@ export declare class FilterRule<T = any> {
|
|
|
19
17
|
value: T;
|
|
20
18
|
operator?: string;
|
|
21
19
|
}
|
|
22
|
-
type AllowedMatchModes<T> = T extends string ?
|
|
20
|
+
type AllowedMatchModes<T> = T extends string ? AllowedOperatorFor<'text'> : T extends boolean ? AllowedOperatorFor<'boolean'> : T extends Date ? AllowedOperatorFor<'date'> : T extends number ? AllowedOperatorFor<'number'> : MatchModeCodes;
|
|
23
21
|
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MatchModeCodes } from '../enums/match-mode-enum-codes';
|
|
2
|
+
/**
|
|
3
|
+
* What each value type may be asked — the ONE operator table, mirroring what the generated
|
|
4
|
+
* paginator implements per type (`PaginatedResultGenerator`): `In` on a string column answers
|
|
5
|
+
* with InvalidMatchMode, PrimeNG's own text list adds three modes the backend 400s, and so on.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately a LEAF module (imports nothing but the codes) so every consumer can derive from
|
|
8
|
+
* it without a cycle: the store's runtime validation and offered lists (`filter-store.ts`), the
|
|
9
|
+
* compile-time `FilterRule.matchMode` union (`entities/filter-rule.ts`), and the data view's
|
|
10
|
+
* kind-level lists (via `operatorOptionsForKind`). These used to be hand-kept copies; edit the
|
|
11
|
+
* table here and every surface follows.
|
|
12
|
+
*
|
|
13
|
+
* Tuple order is DISPLAY order for every operator picker (Equals, then "less/before", then
|
|
14
|
+
* "more/after" — the order the header dropdowns always shipped).
|
|
15
|
+
*/
|
|
16
|
+
export declare const ALLOWED_OPERATORS: {
|
|
17
|
+
readonly text: readonly [MatchModeCodes.StartsWith, MatchModeCodes.Contains, MatchModeCodes.Equals];
|
|
18
|
+
readonly number: readonly [MatchModeCodes.Equals, MatchModeCodes.LessThan, MatchModeCodes.GreaterThan, MatchModeCodes.In];
|
|
19
|
+
readonly boolean: readonly [MatchModeCodes.Equals];
|
|
20
|
+
readonly date: readonly [MatchModeCodes.Equals, MatchModeCodes.LessThan, MatchModeCodes.GreaterThan];
|
|
21
|
+
};
|
|
22
|
+
export type FilterValueKind = keyof typeof ALLOWED_OPERATORS;
|
|
23
|
+
/** The compile-time half of one kind's row, derived so it can never drift from the runtime half. */
|
|
24
|
+
export type AllowedOperatorFor<TKind extends FilterValueKind> = (typeof ALLOWED_OPERATORS)[TKind][number];
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
import { FilterRule } from '../entities/filter-rule';
|
|
3
|
+
import { MatchModeCodes } from '../enums/match-mode-enum-codes';
|
|
4
|
+
import { ALLOWED_OPERATORS, AllowedOperatorFor, FilterValueKind } from './allowed-operators';
|
|
5
|
+
/** One tickable choice. `value` is the filter's own value type — an id, not a display string. */
|
|
6
|
+
export interface FilterOption {
|
|
7
|
+
value: unknown;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A filter declaration. It carries its VALUE type, never a UI control name — that separation is
|
|
12
|
+
* the whole point: `multiselect` is a control, and it emits `In`, which `AllowedMatchModes` allows
|
|
13
|
+
* on a number and forbids on a string. Conflating the two is what shipped `paymentGatewayCode` as
|
|
14
|
+
* a text filter with a hand-written comment instead of a compile error.
|
|
15
|
+
*/
|
|
16
|
+
export interface FilterDefinition<TKind extends FilterValueKind = FilterValueKind> {
|
|
17
|
+
kind: TKind;
|
|
18
|
+
label: string;
|
|
19
|
+
/**
|
|
20
|
+
* Declaring options turns this filter into a pick-list, which means `In` — the same rule as the
|
|
21
|
+
* table's `[filters]`, where the SHAPE of what is supplied decides, not a mode flag. It is also
|
|
22
|
+
* the only way "Processing OR PreparingForShipping" can be expressed at all.
|
|
23
|
+
*/
|
|
24
|
+
options?: FilterOption[];
|
|
25
|
+
/** Offered-operator narrowing; semantics on `FilterConfig.operators`, the factories' door. */
|
|
26
|
+
operators?: MatchModeCodes[];
|
|
27
|
+
/** Whether "+ Filter" offers it; semantics on `FilterConfig.offered`, the factories' door. */
|
|
28
|
+
offered?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface OperatorOption {
|
|
31
|
+
value: MatchModeCodes;
|
|
32
|
+
/** Transloco key, resolved by the control — the store holds no translations. */
|
|
33
|
+
labelKey: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A KIND's operator picker options, for callers with no filter behind them — today that is
|
|
37
|
+
* `spiderly-data-view`, whose own filter config derives its operator lists here instead of
|
|
38
|
+
* hand-keeping copies (the drift `allowed-operators.ts` exists to end). Per-FILTER narrowing
|
|
39
|
+
* lives in the store (`createFilterStore` resolves it once per declaration), so this cannot be
|
|
40
|
+
* handed a fake definition to satisfy — that shape shipped once and interpolated an empty
|
|
41
|
+
* filter name into its own error message.
|
|
42
|
+
*/
|
|
43
|
+
export declare function operatorOptionsForKind(kind: FilterValueKind, hasOptions?: boolean): OperatorOption[];
|
|
44
|
+
/**
|
|
45
|
+
* The operator applied when nobody picked one, which has to be what a person MEANS by filling in
|
|
46
|
+
* the control. `Contains` for a fragment typed into a box; `GreaterThan` for a date, never
|
|
47
|
+
* `Equals` — a date equality against a TIMESTAMP matches only the row written in that exact
|
|
48
|
+
* second, and answers with an empty grid rather than an error (PACMS ran into this on all three
|
|
49
|
+
* of its date columns, Filip 2026-08-28).
|
|
50
|
+
*/
|
|
51
|
+
export declare const DEFAULT_OPERATOR: {
|
|
52
|
+
readonly text: MatchModeCodes.Contains;
|
|
53
|
+
readonly number: MatchModeCodes.Equals;
|
|
54
|
+
readonly boolean: MatchModeCodes.Equals;
|
|
55
|
+
readonly date: MatchModeCodes.GreaterThan;
|
|
56
|
+
};
|
|
57
|
+
/** The value each kind carries. Exhaustive over the kinds by construction. */
|
|
58
|
+
interface ValueByKind {
|
|
59
|
+
text: string;
|
|
60
|
+
number: number;
|
|
61
|
+
boolean: boolean;
|
|
62
|
+
date: Date;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What every factory takes, so narrowing does not have to be re-plumbed per factory (its adjacent
|
|
66
|
+
* asymmetry — `options` on `numberFilter` only — is FORCED and documented there; this one is not).
|
|
67
|
+
*
|
|
68
|
+
* `operators` narrows what the editor OFFERS, in display order; the first entry becomes the
|
|
69
|
+
* default. Typed per kind, so declaring an operator the kind cannot answer is a build error at
|
|
70
|
+
* the declaration — the store's runtime drop-and-report covers only what dodges the type system
|
|
71
|
+
* (a dynamically built array). The store still accepts every wire-legal operator on restore, so
|
|
72
|
+
* an old persisted constraint keeps filtering: the narrowing shapes the controls, not the
|
|
73
|
+
* contract. The forcing case: date-equality against a timestamp matches one second and answers
|
|
74
|
+
* with an empty grid, so PACMS's order dates offer only "posle"/"pre".
|
|
75
|
+
*/
|
|
76
|
+
interface FilterConfig<TKind extends FilterValueKind> {
|
|
77
|
+
label: string;
|
|
78
|
+
operators?: AllowedOperatorFor<TKind>[];
|
|
79
|
+
/**
|
|
80
|
+
* `false` keeps the filter out of the bar's "+ Filter" menu — for a filter with a DEDICATED
|
|
81
|
+
* control somewhere on the page (PACMS's order search box is a placement of the store's
|
|
82
|
+
* `mixedSearch`), where the generic entry point would give one question two homes. The chip
|
|
83
|
+
* still renders when the filter is applied, so the bar's claim to list every constraint holds.
|
|
84
|
+
*/
|
|
85
|
+
offered?: boolean;
|
|
86
|
+
}
|
|
87
|
+
export declare function textFilter(config: FilterConfig<'text'>): FilterDefinition<'text'>;
|
|
88
|
+
/**
|
|
89
|
+
* Only a NUMBER filter accepts options, and the asymmetry is forced rather than chosen: options
|
|
90
|
+
* mean `In`, and `In` is the one operator ALLOWED_OPERATORS grants to numbers alone — because the
|
|
91
|
+
* generated paginator answers it with InvalidMatchMode on a string column. A text pick-list used
|
|
92
|
+
* to type-check and then throw at the first `set`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function numberFilter(config: FilterConfig<'number'> & {
|
|
95
|
+
options?: FilterOption[];
|
|
96
|
+
}): FilterDefinition<'number'>;
|
|
97
|
+
export declare function booleanFilter(config: FilterConfig<'boolean'>): FilterDefinition<'boolean'>;
|
|
98
|
+
export declare function dateFilter(config: FilterConfig<'date'>): FilterDefinition<'date'>;
|
|
99
|
+
/**
|
|
100
|
+
* `In` is the only multi-valued operator MatchModeCodes has, so it is the only one whose value is
|
|
101
|
+
* a list. Distributing over the operator union yields a discriminated union rather than a loose
|
|
102
|
+
* `T | T[]`, so `{ operator: Equals, value: [2, 3] }` is a compile error rather than a query the
|
|
103
|
+
* paginator answers in some way nobody predicted.
|
|
104
|
+
*/
|
|
105
|
+
type ConstraintFor<TKind extends FilterValueKind, TOperator> = TOperator extends MatchModeCodes.In ? {
|
|
106
|
+
operator: TOperator;
|
|
107
|
+
value: ValueByKind[TKind][] | null | undefined;
|
|
108
|
+
} : {
|
|
109
|
+
operator: TOperator;
|
|
110
|
+
value: ValueByKind[TKind] | null | undefined;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* The kind is what carries BOTH halves — which operators are legal and what the value is. It has
|
|
114
|
+
* to stay a literal all the way from the factory to here: typing the definition's `kind` as the
|
|
115
|
+
* widened `FilterValueKind` silently allows every operator on every filter, which is the shape
|
|
116
|
+
* this first had (the runtime guard still fired, so only a `@ts-expect-error` pin caught it).
|
|
117
|
+
*/
|
|
118
|
+
export type FilterConstraint<TKind extends FilterValueKind> = ConstraintFor<TKind, (typeof ALLOWED_OPERATORS)[TKind][number]>;
|
|
119
|
+
/** One filter, addressed on its own. The placement API, and what the bar's editor drives. */
|
|
120
|
+
export interface FilterHandle {
|
|
121
|
+
id: string;
|
|
122
|
+
label: string;
|
|
123
|
+
kind: FilterValueKind;
|
|
124
|
+
/** The DRAFT — what a control shows. `applied()` answers the other question. */
|
|
125
|
+
value: Signal<unknown>;
|
|
126
|
+
operator: Signal<MatchModeCodes | undefined>;
|
|
127
|
+
/** Every operator this filter accepts, in display order. */
|
|
128
|
+
operators: OperatorOption[];
|
|
129
|
+
/** Applied when nobody picked an operator. */
|
|
130
|
+
defaultOperator: MatchModeCodes;
|
|
131
|
+
/** The tickable choices, when this filter declares any. */
|
|
132
|
+
options?: FilterOption[];
|
|
133
|
+
set(constraint: {
|
|
134
|
+
operator: MatchModeCodes;
|
|
135
|
+
value: unknown;
|
|
136
|
+
}): void;
|
|
137
|
+
commit(): void;
|
|
138
|
+
reset(): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* What the CHIP BAR needs from a store, and no more: it reads the applied set and removes from it.
|
|
142
|
+
* Narrow on purpose — it keeps the bar free of the store's generics, so the bar renders any store
|
|
143
|
+
* without the two types having to agree on filter ids.
|
|
144
|
+
*/
|
|
145
|
+
export interface FilterBarSource {
|
|
146
|
+
/** Every DECLARED filter, which is what "+ Filter" offers — not every visible column. */
|
|
147
|
+
definitions: Record<string, FilterDefinition<FilterValueKind>>;
|
|
148
|
+
applied: Signal<AppliedFilter[]>;
|
|
149
|
+
get(id: string): FilterHandle;
|
|
150
|
+
reset(id: string): void;
|
|
151
|
+
clear(): void;
|
|
152
|
+
}
|
|
153
|
+
/** What the TABLE needs: the bar's half, plus the payload it sends to the paginator. */
|
|
154
|
+
export interface FilterSource extends FilterBarSource {
|
|
155
|
+
toFilterPayload(): Record<string, FilterRule[]>;
|
|
156
|
+
/** The applied set as plain JSON, for whoever owns this table's persisted state. */
|
|
157
|
+
snapshot(): FilterSnapshot;
|
|
158
|
+
/** Replaces the applied set, and the drafts with it so every control agrees with its chip. */
|
|
159
|
+
restore(snapshot: FilterSnapshot): void;
|
|
160
|
+
}
|
|
161
|
+
/** The applied constraints in a form that survives `JSON.stringify`. */
|
|
162
|
+
export type FilterSnapshot = Record<string, {
|
|
163
|
+
operator: MatchModeCodes;
|
|
164
|
+
value: unknown;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* One sort key as the bar draws it. Sort deliberately does NOT live in the filter store:
|
|
168
|
+
* `multiSortMeta` already holds it, keyed by field, and a copy here would be a second source of
|
|
169
|
+
* truth for one fact. The bar is one surface reading two owners.
|
|
170
|
+
*/
|
|
171
|
+
export interface SortKeyLabel {
|
|
172
|
+
label: string;
|
|
173
|
+
descending: boolean;
|
|
174
|
+
}
|
|
175
|
+
/** One applied constraint, in the shape the chip bar draws. */
|
|
176
|
+
export interface AppliedFilter {
|
|
177
|
+
id: string;
|
|
178
|
+
label: string;
|
|
179
|
+
/** The bar reads this to word the chip — `String(false)` would print "false" at an operator. */
|
|
180
|
+
kind: FilterValueKind;
|
|
181
|
+
operator: MatchModeCodes;
|
|
182
|
+
/**
|
|
183
|
+
* Transloco key for the chip's middle word. Without it a chip reads "Firma Elektromont", which
|
|
184
|
+
* leaves the reader to guess whether the grid is narrowed by `contains` or by `equals` — very
|
|
185
|
+
* different sets for one typed value, on a surface whose only claim is that it cannot lie.
|
|
186
|
+
*/
|
|
187
|
+
operatorPhraseKey: string;
|
|
188
|
+
value: unknown;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The filter engine. It knows nothing about columns: a filter has an id, a value type and a
|
|
192
|
+
* constraint, and that is the entire vocabulary.
|
|
193
|
+
*/
|
|
194
|
+
export declare function createFilterStore<TDefs extends Record<string, FilterDefinition<FilterValueKind>>>(definitions: TDefs): {
|
|
195
|
+
definitions: TDefs;
|
|
196
|
+
applied: Signal<AppliedFilter[]>;
|
|
197
|
+
/**
|
|
198
|
+
* A handle bound to one filter. This is the PLACEMENT api: it needs nothing from the component
|
|
199
|
+
* tree, so the same filter can be driven from the bar, from a header cell, or from a drawer
|
|
200
|
+
* rendered at app root. `value` is the DRAFT — a control shows what was typed, not what was
|
|
201
|
+
* applied; `applied()` is the other question and the bar asks that one.
|
|
202
|
+
*/
|
|
203
|
+
get<K extends keyof TDefs>(id: K): {
|
|
204
|
+
id: string;
|
|
205
|
+
label: string;
|
|
206
|
+
kind: "number" | "boolean" | "text" | "date";
|
|
207
|
+
value: Signal<unknown>;
|
|
208
|
+
operator: Signal<MatchModeCodes>;
|
|
209
|
+
readonly operators: OperatorOption[];
|
|
210
|
+
readonly defaultOperator: MatchModeCodes;
|
|
211
|
+
readonly options: FilterOption[];
|
|
212
|
+
set: (constraint: FilterConstraint<TDefs[K]["kind"]>) => void;
|
|
213
|
+
commit: () => void;
|
|
214
|
+
reset: () => void;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* The ONE seam for choices that arrive after the store is built (PACMS fills its pick-lists
|
|
218
|
+
* from lookups that race a deploy). Never write `definitions[id].options` by hand: options'
|
|
219
|
+
* PRESENCE is what makes a filter a pick-list, so the setter also re-resolves the offered
|
|
220
|
+
* operators — a hand write would leave a late-filled filter offering `Equals` on a control
|
|
221
|
+
* that emits a list.
|
|
222
|
+
*/
|
|
223
|
+
setOptions<K extends keyof TDefs>(id: K, options: FilterOption[]): void;
|
|
224
|
+
set<K extends keyof TDefs>(id: K, constraint: FilterConstraint<TDefs[K]["kind"]>): void;
|
|
225
|
+
/**
|
|
226
|
+
* `set` + `commit` in one breath — the shape every PROGRAMMATIC write takes (a view's
|
|
227
|
+
* `apply`, the sheet flow). The split API stays for controls, where draft-on-type /
|
|
228
|
+
* publish-on-Apply is the point; but a bare `set` whose `commit` is forgotten (or written
|
|
229
|
+
* with the wrong id — the pair repeats the string twice) fails silently: the draft is
|
|
230
|
+
* written, nothing is published, and a view narrows by nothing under a labelled tab.
|
|
231
|
+
*/
|
|
232
|
+
setAndCommit<K extends keyof TDefs>(id: K, constraint: FilterConstraint<TDefs[K]["kind"]>): void;
|
|
233
|
+
/**
|
|
234
|
+
* Publishes the draft. A blanked draft REMOVES the constraint rather than committing an empty
|
|
235
|
+
* one, so `applied()` and the payload agree: no chip, no key, no `contains ''`.
|
|
236
|
+
*/
|
|
237
|
+
commit<K extends keyof TDefs>(id: K): void;
|
|
238
|
+
/**
|
|
239
|
+
* The chip's `x`. Drops the DRAFT as well as the committed constraint: a surviving draft
|
|
240
|
+
* leaves the control still showing the cleared text, and the next commit would restore a
|
|
241
|
+
* filter nobody re-typed.
|
|
242
|
+
*/
|
|
243
|
+
reset<K extends keyof TDefs>(id: K): void;
|
|
244
|
+
/**
|
|
245
|
+
* The applied set, plain enough to store. Drafts are deliberately NOT included: a half-typed
|
|
246
|
+
* value is not a state anyone should come back to, and the bar's whole claim is that what it
|
|
247
|
+
* shows is what narrows the grid.
|
|
248
|
+
*/
|
|
249
|
+
snapshot(): FilterSnapshot;
|
|
250
|
+
/**
|
|
251
|
+
* Replaces the applied set. The drafts are written too, so reopening a restored chip shows
|
|
252
|
+
* the value it is filtering by rather than an empty control.
|
|
253
|
+
*
|
|
254
|
+
* Ids the store no longer declares are dropped rather than carried: a filter removed from the
|
|
255
|
+
* declaration in a later release would otherwise ride every request from anyone whose storage
|
|
256
|
+
* still held it, with no chip able to name it and no way to clear it from the UI.
|
|
257
|
+
*/
|
|
258
|
+
restore(snapshot: FilterSnapshot): void;
|
|
259
|
+
/** The bar's "Clear all". `reset` for every filter, drafts included. */
|
|
260
|
+
clear(): void;
|
|
261
|
+
/** The `filters` half of `Filter` — what the generated paginator reads. */
|
|
262
|
+
toFilterPayload(): Record<string, FilterRule[]>;
|
|
263
|
+
};
|
|
264
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { QueryList, TemplateRef } from '@angular/core';
|
|
2
|
+
import { SpiderlyFilterTemplateDirective } from '../directives/spiderly-filter-template.directive';
|
|
3
|
+
import { MatchModeCodes } from '../enums/match-mode-enum-codes';
|
|
4
|
+
import { AppliedFilter, FilterBarSource, FilterHandle, FilterOption, SortKeyLabel } from './filter-store';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/**
|
|
7
|
+
* Lowercased and stripped of diacritics for matching. NFD decomposition handles č/ć/š/ž, but NOT
|
|
8
|
+
* đ — it is its own letter (U+0111), not a d carrying a mark, so it decomposes to nothing and has
|
|
9
|
+
* to be mapped by hand.
|
|
10
|
+
*
|
|
11
|
+
* It maps to "dj", which is what the rest of this workspace already does (pa-cms's
|
|
12
|
+
* `normalizePlaceQuery` and `toAscii`, the backend's `PlaceNameNormalizer`) and what someone
|
|
13
|
+
* without a Serbian layout actually types. Mapping it to "d" — as this first shipped — folds
|
|
14
|
+
* "Đorđe" to "dorde", so typing "djordje" finds nothing: the exact case the comment claimed to
|
|
15
|
+
* fix, and one no spec covered because the test used "drzava", which has no đ in it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function foldForSearch(value: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* The visible home of every applied constraint. This is what licenses a hidden column keeping its
|
|
20
|
+
* filter: the header is no longer the only place a filter can be seen, so hiding a column stops
|
|
21
|
+
* meaning "silently drop what it was filtering by".
|
|
22
|
+
*
|
|
23
|
+
* Everything drawn here comes from `applied()`, never from a draft — a chip over a half-typed
|
|
24
|
+
* value would repeat the mistake the header's filter icon shipped, claiming the grid is narrowed
|
|
25
|
+
* while it still shows every row.
|
|
26
|
+
*/
|
|
27
|
+
export declare class SpiderlyFilterBarComponent {
|
|
28
|
+
readonly filters: import("@angular/core").InputSignal<FilterBarSource>;
|
|
29
|
+
/** The current query's row count, supplied by whoever owns the query. Null hides the counter. */
|
|
30
|
+
readonly totalRecords: import("@angular/core").InputSignal<number>;
|
|
31
|
+
/**
|
|
32
|
+
* What the grid is ordered by, in priority order and already LABELLED — the bar knows nothing
|
|
33
|
+
* about columns, so whoever owns the sort resolves the names.
|
|
34
|
+
*/
|
|
35
|
+
readonly sort: import("@angular/core").InputSignal<SortKeyLabel[]>;
|
|
36
|
+
/** Asked for, never done here — see the button's comment. */
|
|
37
|
+
readonly clearAll: import("@angular/core").OutputEmitterRef<void>;
|
|
38
|
+
private readonly transloco;
|
|
39
|
+
/** For the chip's date wording — the same locale mechanism the table's cells format with. */
|
|
40
|
+
private readonly locale;
|
|
41
|
+
/**
|
|
42
|
+
* Controls a consumer projected for specific filters, collected by whoever hosts this bar. A
|
|
43
|
+
* filter with no template keeps the control the bar draws for its kind, so a table opts in one
|
|
44
|
+
* filter at a time.
|
|
45
|
+
*/
|
|
46
|
+
readonly controlTemplates: import("@angular/core").InputSignal<QueryList<SpiderlyFilterTemplateDirective>>;
|
|
47
|
+
readonly isAddOpen: import("@angular/core").WritableSignal<boolean>;
|
|
48
|
+
/** Cleared when the popover closes, so reopening never starts inside someone's old query. */
|
|
49
|
+
readonly addSearch: import("@angular/core").WritableSignal<string>;
|
|
50
|
+
/** The filter whose control is open. One at a time: the bar edits, it is not a form. */
|
|
51
|
+
readonly editing: import("@angular/core").WritableSignal<FilterHandle>;
|
|
52
|
+
/**
|
|
53
|
+
* `addable` narrowed by the search box. Matching is unaccented and case-folded because that is
|
|
54
|
+
* how the label gets typed: "drzava" for "Država" on any keyboard without a Serbian layout. The
|
|
55
|
+
* backend's own product search is unaccented for the same reason.
|
|
56
|
+
*/
|
|
57
|
+
readonly offered: import("@angular/core").Signal<{
|
|
58
|
+
kind: "number" | "boolean" | "text" | "date";
|
|
59
|
+
label: string;
|
|
60
|
+
options?: FilterOption[];
|
|
61
|
+
operators?: MatchModeCodes[];
|
|
62
|
+
offered?: boolean;
|
|
63
|
+
id: string;
|
|
64
|
+
}[]>;
|
|
65
|
+
/**
|
|
66
|
+
* What "+ Filter" offers: every declared filter that is not already on the bar. Sourced from the
|
|
67
|
+
* DEFINITIONS, so a filter reaches this list whether or not it has a column, and whether or not
|
|
68
|
+
* that column is visible. That is the whole reason the bar exists.
|
|
69
|
+
*
|
|
70
|
+
* `offered: false` filters stay out — they have a dedicated control of their own on the page
|
|
71
|
+
* (PACMS's order search box), so the generic entry point would give one question two homes.
|
|
72
|
+
* Their chips still render: the applied list above reads no such flag.
|
|
73
|
+
*/
|
|
74
|
+
readonly addable: import("@angular/core").Signal<{
|
|
75
|
+
kind: "number" | "boolean" | "text" | "date";
|
|
76
|
+
label: string;
|
|
77
|
+
options?: FilterOption[];
|
|
78
|
+
operators?: MatchModeCodes[];
|
|
79
|
+
offered?: boolean;
|
|
80
|
+
id: string;
|
|
81
|
+
}[]>;
|
|
82
|
+
/** Opens a filter's control. Where it renders, and why not a popover: the template comment. */
|
|
83
|
+
startEditing(id: string): void;
|
|
84
|
+
/** The raw-string controls only. Nothing reaches the bar or the query until `apply`. */
|
|
85
|
+
draft(handle: FilterHandle, event: Event): void;
|
|
86
|
+
controlTemplateFor(handle: FilterHandle): TemplateRef<unknown> | null;
|
|
87
|
+
/**
|
|
88
|
+
* The open editor's operator options, already translated — p-select takes labels, not keys.
|
|
89
|
+
*
|
|
90
|
+
* A `computed`, not a method in the binding: both inputs are fixed for the life of an open
|
|
91
|
+
* editor, and PrimeNG's `Select.options` setter deep-compares whatever it is handed — so a
|
|
92
|
+
* freshly mapped array per change-detection pass paid for a recursive structural compare to
|
|
93
|
+
* conclude nothing had changed.
|
|
94
|
+
*/
|
|
95
|
+
readonly operatorChoices: import("@angular/core").Signal<{
|
|
96
|
+
label: string;
|
|
97
|
+
value: MatchModeCodes;
|
|
98
|
+
}[]>;
|
|
99
|
+
/**
|
|
100
|
+
* The ONE write path, whatever the control. Every PrimeNG control here hands back a value of
|
|
101
|
+
* the right type already — a Date from the datepicker, a boolean from the checkbox, the whole
|
|
102
|
+
* array from the multiselect — so only the raw string ones need coercing, and only they go
|
|
103
|
+
* through `draft`.
|
|
104
|
+
*/
|
|
105
|
+
draftValue(handle: FilterHandle, value: unknown): void;
|
|
106
|
+
/**
|
|
107
|
+
* Changing the direction re-commits nothing on its own — it rewrites the draft, so the operator
|
|
108
|
+
* survives until Apply like the value does.
|
|
109
|
+
*/
|
|
110
|
+
pickOperator(handle: FilterHandle, operator: MatchModeCodes): void;
|
|
111
|
+
/**
|
|
112
|
+
* A DOM control hands back a string; the store holds the value type the filter declared.
|
|
113
|
+
*
|
|
114
|
+
* The empty case is the trap and it is not symmetric: `Number('')` is 0, so an emptied number
|
|
115
|
+
* box would apply a filter for zero and draw a chip over a control the operator had just
|
|
116
|
+
* cleared. `NaN` is folded in for the same reason — the store's `isBlank` cannot recognise
|
|
117
|
+
* either of them as "nothing was entered", because by then they are perfectly good numbers.
|
|
118
|
+
*/
|
|
119
|
+
private coerce;
|
|
120
|
+
apply(handle: FilterHandle): void;
|
|
121
|
+
/**
|
|
122
|
+
* `In` is the multi-valued operator, so its chip reads as a list — in the OPTIONS' words, not
|
|
123
|
+
* the wire's: "Status is one of 2, 3" narrates ids nobody chose by number. The lookup is live
|
|
124
|
+
* off the definition rather than snapshotted at commit, because option lists can be filled
|
|
125
|
+
* asynchronously (PACMS's admin/backend deploy race): a chip restored before the lookup answers
|
|
126
|
+
* upgrades from ids to labels the moment the options land, and a failed lookup degrades to the
|
|
127
|
+
* honest raw value.
|
|
128
|
+
*/
|
|
129
|
+
chipValue(chip: AppliedFilter): string;
|
|
130
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SpiderlyFilterBarComponent, never>;
|
|
131
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SpiderlyFilterBarComponent, "spiderly-filter-bar", never, { "filters": { "alias": "filters"; "required": true; "isSignal": true; }; "totalRecords": { "alias": "totalRecords"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "controlTemplates": { "alias": "controlTemplates"; "required": false; "isSignal": true; }; }, { "clearAll": "clearAll"; }, never, never, true, never>;
|
|
132
|
+
}
|
|
@@ -6,6 +6,7 @@ import { SpiderlyTab } from '../components/spiderly-panels/panel-header/panel-he
|
|
|
6
6
|
import { Codebook } from '../entities/codebook';
|
|
7
7
|
import { Filter } from '../entities/filter';
|
|
8
8
|
import { Namebook } from '../entities/namebook';
|
|
9
|
+
import { FilterOption } from '../filters/filter-store';
|
|
9
10
|
import { PrimengOption } from '../entities/primeng-option';
|
|
10
11
|
export declare function validatePrecisionScale(value: any, precision: number, scale: number, ignoreTrailingZeros: boolean): boolean;
|
|
11
12
|
export declare function getMimeTypeForFileName(fileName: string): string;
|
|
@@ -29,6 +30,13 @@ export declare function isExcelFileType(mimeType: string): boolean;
|
|
|
29
30
|
export declare function saveResponseAsFile(res: HttpResponse<Blob>, fallbackName: string): void;
|
|
30
31
|
export declare function exportListToExcel(exportListToExcelObservableMethod: (filter: Filter) => Observable<any>, filter: Filter): void;
|
|
31
32
|
export declare function getPrimengNamebookOptions(namebookList: Namebook[]): PrimengOption[];
|
|
33
|
+
/**
|
|
34
|
+
* The filter store's twin of `getPrimengNamebookOptions` — same namebook in, the store's
|
|
35
|
+
* `{ value, label }` shape out (a `FilterOption` is not a `PrimengOption`: that one's `code` is
|
|
36
|
+
* deliberate, see `primeng-option.ts`). Every consumer filling a pick-list filter from a lookup
|
|
37
|
+
* needs this map; without it each page re-derives the three lines.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getFilterOptions(namebookList: Namebook[]): FilterOption[];
|
|
32
40
|
export declare function getPrimengDropdownNamebookOptions(getDropdownListObservable: (parentEntityId?: number) => Observable<Namebook[]>, parentEntityId?: number): Observable<PrimengOption[]>;
|
|
33
41
|
export declare function getPrimengDropdownCodebookOptions(getDropdownListObservable: () => Observable<Codebook[]>): Observable<PrimengOption[]>;
|
|
34
42
|
export declare function getPrimengAutocompleteNamebookOptions(getAutocompleteListObservable: (limit: number, query: string, parentEntityId?: number) => Observable<Namebook[]>, limit: number, query: string, parentEntityId?: number): Observable<PrimengOption[]>;
|
package/package.json
CHANGED
package/public-api.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export * from './lib/components/spiderly-buttons/spiderly-button-base/spiderly-b
|
|
|
41
41
|
export * from './lib/components/spiderly-buttons/spiderly-button/spiderly-button.component';
|
|
42
42
|
export * from './lib/components/spiderly-buttons/spiderly-split-button/spiderly-split-button.component';
|
|
43
43
|
export * from './lib/components/spiderly-data-table/spiderly-data-table.component';
|
|
44
|
+
export * from './lib/directives/spiderly-filter-template.directive';
|
|
45
|
+
export * from './lib/directives/spiderly-overflow-title.directive';
|
|
46
|
+
export * from './lib/filters/allowed-operators';
|
|
47
|
+
export * from './lib/filters/filter-store';
|
|
48
|
+
export * from './lib/filters/spiderly-filter-bar.component';
|
|
44
49
|
export * from './lib/components/spiderly-data-view/spiderly-data-view.component';
|
|
45
50
|
export * from './lib/components/spiderly-delete-dialog/spiderly-delete-confirmation.component';
|
|
46
51
|
export * from './lib/components/spiderly-form-control/spiderly-form-control';
|