eru-grid 0.0.40 → 0.0.42

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.
@@ -3,6 +3,7 @@ import * as _angular_core from '@angular/core';
3
3
  import { OnInit, AfterViewInit, OnDestroy, QueryList, ElementRef, TemplateRef, EventEmitter, ChangeDetectorRef, Provider } from '@angular/core';
4
4
  import { FixedSizeVirtualScrollStrategy, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
5
5
  import { MatDatepicker } from '@angular/material/datepicker';
6
+ import { ConnectedPosition } from '@angular/cdk/overlay';
6
7
 
7
8
  interface Row {
8
9
  entity_id: string;
@@ -67,6 +68,8 @@ interface GridFeatures {
67
68
  allowSelection?: boolean;
68
69
  actionColumn?: boolean;
69
70
  actionColumnPosition?: 'before' | 'after';
71
+ actions?: GridAction[];
72
+ action_display_type?: 'icons' | 'kebab';
70
73
  designMode?: boolean;
71
74
  groupBar?: boolean;
72
75
  excel_download?: boolean;
@@ -97,7 +100,9 @@ interface GridFeatures {
97
100
  boardCardSelectedBg?: string;
98
101
  boardCardSelectedOutline?: string;
99
102
  boardColumnMinWidth?: number;
103
+ boardColumnMaxWidth?: number;
100
104
  boardColumnHeight?: number;
105
+ boardWrapColumns?: boolean;
101
106
  }
102
107
  interface ExcelDownloadPayload {
103
108
  columns: Record<string, {
@@ -157,6 +162,40 @@ interface ExcelDownloadRequest extends ExcelDownloadPayload {
157
162
  id: number;
158
163
  timestamp: number;
159
164
  }
165
+ /**
166
+ * A message from a cell's server-side validation check, queued for the consumer
167
+ * to display. eru-grid does not render a snackbar — it delegates chrome to the
168
+ * consumer, the same way it delegates data fetching — so this follows the
169
+ * enqueue/drain pattern of ExcelDownloadRequest: the consumer watches
170
+ * `EruGridStore.serverValidationNotices()`, shows its own snackbar (red when
171
+ * status is 'error'), and calls removeServerValidationNotice(id).
172
+ */
173
+ interface ServerValidationNotice {
174
+ id: string;
175
+ timestamp: number;
176
+ rowId: string;
177
+ fieldName: string;
178
+ status: 'success' | 'error';
179
+ msg: string;
180
+ }
181
+ /**
182
+ * One action offered on every row, rendered in the action column.
183
+ *
184
+ * `action_name` is both the tooltip (icon mode) / label (kebab mode) and the
185
+ * identity the consumer matches on when the click comes back — eru-studio keys
186
+ * its configured 'Custom Action' subscription off it — so it has to be unique
187
+ * within a grid.
188
+ *
189
+ * `action_visible_condition` is a row-scoped expression in the same language
190
+ * eru-studio uses for its logic (`@field is not 'closed' AND @amount > 1000`),
191
+ * evaluated per row against that row's column values. Blank means always
192
+ * visible; see evaluateRowCondition().
193
+ */
194
+ interface GridAction {
195
+ action_name: string;
196
+ action_icon?: string;
197
+ action_visible_condition?: string;
198
+ }
160
199
  interface GridStyles {
161
200
  subTotalStyle?: 'bold' | 'italic' | 'highlighted';
162
201
  grandTotalStyle?: 'bold' | 'italic' | 'highlighted';
@@ -200,6 +239,7 @@ interface DynamicDataRequest {
200
239
  apiName?: string;
201
240
  apiField?: string;
202
241
  search?: string;
242
+ filter?: Record<string, any>;
203
243
  }
204
244
  interface Field {
205
245
  name: string;
@@ -207,8 +247,8 @@ interface Field {
207
247
  datatype: DataTypes;
208
248
  field_size: number;
209
249
  grid_index: number;
210
- required?: boolean;
211
250
  mandatory?: boolean;
251
+ required?: boolean;
212
252
  minWidth?: number;
213
253
  maxWidth?: number;
214
254
  symbol?: string;
@@ -245,24 +285,159 @@ interface Field {
245
285
  data_length?: number;
246
286
  start_value?: number;
247
287
  end_value?: number;
288
+ is_perc?: boolean;
248
289
  emoji_value?: string;
249
290
  max_files?: number;
250
291
  allowed_file_types?: any[];
251
- maxFileSize?: number;
292
+ max_file_size?: number;
252
293
  default_country?: string;
253
294
  date_format?: string;
254
295
  dateTimeFormat?: string;
255
296
  allow_days?: string[];
297
+ /**
298
+ * Dependent-dropdown mapping, for `option_type: 'ENTITY_DATA'`.
299
+ *
300
+ * Each pair links a field on the CURRENT record to the field it filters on in
301
+ * the option-source entity: `def` is the key in the current entity supplying
302
+ * the value, `dpef` is the key in the mapped entity to match it against.
303
+ * Several pairs mean the list is scoped by all of them.
304
+ *
305
+ * Replaces the earlier single `dpef` string, which could only express one
306
+ * dependency and conflated the two sides. A lone `dpef` is still read as
307
+ * `{ def: dpef, dpef: dpef }` for fields saved before this existed.
308
+ */
309
+ df_fields?: {
310
+ def: string;
311
+ dpef: string;
312
+ }[];
313
+ /** Legacy single dependency. Prefer `df_fields`. */
314
+ dpef?: string;
315
+ /** Legacy: key in the current entity paired with `dpef`. */
316
+ def?: string;
317
+ multiple?: boolean;
318
+ searchable?: boolean;
319
+ max_avatars?: number;
320
+ people_card?: string;
321
+ show_people_card?: 'none' | 'hover' | 'click';
322
+ value_true?: string;
323
+ value_false?: string;
256
324
  rich_text?: boolean;
257
325
  is_hyp?: boolean;
258
326
  hypl_nm?: string;
327
+ is_pii?: boolean;
328
+ is_ephi?: boolean;
329
+ to_encrypt?: boolean;
330
+ is_pf?: boolean;
331
+ is_unique?: boolean;
332
+ unq_sa?: boolean;
333
+ system_validate?: string;
334
+ ssv_api?: string;
335
+ can_group?: boolean;
336
+ default_group?: boolean;
337
+ mapped_entity?: string;
338
+ mapped_field?: string;
259
339
  tool_tip?: string;
260
340
  description?: string;
261
341
  default?: any;
262
342
  editable?: boolean | string;
263
343
  is_hidden?: boolean;
264
344
  }
265
- type DataTypes = 'number' | 'textbox' | 'currency' | 'date' | 'datetime' | 'duration' | 'dropdown_single_select' | 'dropdown_multi_select' | 'location' | 'email' | 'people' | 'checkbox' | 'phone' | 'priority' | 'status' | 'progress' | 'attachment' | 'tag' | 'rating' | 'website';
345
+ /**
346
+ * Field attributes a mapped column inherits from the data model.
347
+ *
348
+ * Single source of truth for two things that must agree: what
349
+ * `buildMappedColumnPatch()` merges onto the column on every load, and what the
350
+ * column design panel renders read-only. If a key is inherited but not locked,
351
+ * a user's edit is silently discarded on reload; if it is locked but not
352
+ * inherited, the control is dead. Keep both behaviours driven from this list.
353
+ *
354
+ * The rule: anything the data model authors belongs here; anything that is a
355
+ * property of *this view* of the field does not. Deliberately excluded —
356
+ * field_size, grid_index layout, set by column resize/reorder
357
+ * tool_tip per-grid header help text
358
+ * is_hidden hiding a column in one grid should not require
359
+ * hiding the field in the data model
360
+ * dateTimeFormat legacy alias; the model writes `date_format`
361
+ */
362
+ declare const INHERITED_FIELD_KEYS: readonly string[];
363
+ type DataTypes = 'number' | 'textbox' | 'textarea' | 'currency' | 'date' | 'datetime' | 'duration' | 'time' | 'dropdown_single_select' | 'dropdown_multi_select' | 'location' | 'email' | 'people' | 'checkbox' | 'phone' | 'priority' | 'status' | 'progress' | 'attachment' | 'tag' | 'rating' | 'website';
364
+ /** Resolve a data-model datatype to the canonical DataTypes value. */
365
+ declare function normalizeDatatype(datatype: any): DataTypes;
366
+ /** Short month names, for the `MMM` token. */
367
+ declare const MONTH_SHORT_NAMES: string[];
368
+ /**
369
+ * The date formats offered to users, in every layer.
370
+ *
371
+ * One list so eru-studio, eru-grid and the processo data model present the same
372
+ * choices — previously studio offered eight, while the grid's panel and processo
373
+ * offered three, so a format could be picked on a page and had no equivalent on
374
+ * a field.
375
+ *
376
+ * Values are in token form: `dd` day, `MM` month number, `MMM` month name,
377
+ * `yyyy` year. Casing is significant — lowercase `mm` means minutes in a
378
+ * datetime — so values are normalised rather than lowercased on read.
379
+ */
380
+ declare const DATE_FORMATS: {
381
+ value: string;
382
+ label: string;
383
+ }[];
384
+ /** The same orderings with a 24-hour time part, for datetime fields. */
385
+ declare const DATETIME_FORMATS: {
386
+ value: string;
387
+ label: string;
388
+ }[];
389
+ /**
390
+ * Normalise a date format to token casing, so a value saved in any casing
391
+ * matches the canonical option. The data model uppercases on save
392
+ * (`DD-MMM-YYYY`) and older UIs stored lowercase.
393
+ *
394
+ * `mmm` is handled before `mm`, or the longer token would be half-consumed and
395
+ * `dd-mmm-yyyy` would come out as `dd-MMm-yyyy`.
396
+ */
397
+ declare function normalizeDateFormat(format: string | null | undefined): string;
398
+ /**
399
+ * Normalise a datetime format. Only the date half is token-cased — `mm` in the
400
+ * time half is minutes and must stay lowercase, which is why this cannot just
401
+ * reuse normalizeDateFormat over the whole string. The date half is everything
402
+ * before the time, located by the `hh` token rather than by whitespace, since a
403
+ * date pattern may itself contain a space (`dd MMM yyyy`).
404
+ */
405
+ declare function normalizeDateTimeFormat(format: string | null | undefined): string;
406
+ /**
407
+ * Render a date with a token pattern (`dd`, `MM`, `MMM`, `yyyy`).
408
+ *
409
+ * Shared by both libraries so a date reads identically on a page and in a grid.
410
+ * The alternation lists longer tokens first, so `MMM` is matched before `MM`
411
+ * and `yyyy` is never partly consumed — the bug that a chain of string
412
+ * replacements invites.
413
+ */
414
+ declare function formatDateWithPattern(date: Date | null, pattern: string): string;
415
+ /**
416
+ * Parse text written in a token pattern back to a Date, or null.
417
+ *
418
+ * Built as a regex from the pattern rather than by splitting on a separator:
419
+ * a month name is not numeric, and patterns may use a space or a comma, so
420
+ * splitting cannot recover the field order on its own.
421
+ */
422
+ declare function parseDateWithPattern(text: string, pattern: string): Date | null;
423
+ /** One value band and the colours it paints. Either bound may be open-ended. */
424
+ interface ColorRangeBand {
425
+ from?: number | null;
426
+ to?: number | null;
427
+ color?: string | null;
428
+ background?: string | null;
429
+ }
430
+ /**
431
+ * Resolve a numeric value against a column's `color_ranges`, returning the
432
+ * first matching band or null.
433
+ *
434
+ * Shared by the progress, number and currency cells so a value falling in the
435
+ * same band paints the same way whichever datatype renders it. A blank or
436
+ * absent bound is open-ended — data-model bands are authored that way ("80 and
437
+ * above"), and coercing a missing bound to 0 would stop such a band matching.
438
+ * Bands are checked in order, so the author controls precedence.
439
+ */
440
+ declare function matchColorRange(value: any, ranges: any): ColorRangeBand | null;
266
441
  declare const DATA_TYPES: DataTypes[];
267
442
  type AggregationFunction = 'sum' | 'count' | 'avg' | 'min' | 'max';
268
443
  interface PivotColumnGroupState {
@@ -330,16 +505,67 @@ interface PivotResult {
330
505
  headerStructure?: PivotHeaderStructure;
331
506
  columnGroupState?: PivotColumnGroupState;
332
507
  }
508
+ /**
509
+ * A drill click on a data cell, in either table or pivot mode.
510
+ *
511
+ * `columnName` is the key the consumer matches on to decide what to do: a
512
+ * column name in table mode, and in pivot mode the leaf name — which for an
513
+ * aggregated cell is the aggregation's name. `pivotPath` carries the column
514
+ * dimension values that located the cell, and those are also merged into
515
+ * `rowData` so the whole coordinate of the clicked cell reads off one object.
516
+ */
517
+ /**
518
+ * What kind of pivot row a drilled cell sits on. A total aggregates rows the
519
+ * cell does not name, so a consumer has to treat it differently from a data
520
+ * cell — see Drilldown.scope.
521
+ */
522
+ type PivotRowKind = 'data' | 'row_subtotal' | 'row_grand_total';
523
+ /** What kind of pivot column a drilled cell sits in. */
524
+ type PivotColumnKind = 'data' | 'column_grand_total';
333
525
  interface Drilldown {
334
526
  frozenGrandTotalCell: boolean;
335
527
  columnName: string;
336
528
  rowId: string;
337
529
  rowData?: any;
338
530
  columnValue: any;
531
+ /** 'table' | 'pivot' — which surface the click came from. */
532
+ mode?: GridMode;
533
+ /** Pivot column-dimension values leading to this cell, outermost first. */
534
+ pivotPath?: string[];
535
+ /** The full leaf column name, pivot path included (pivot mode only). */
536
+ leafColumnName?: string;
537
+ /**
538
+ * Which row and column the cell belongs to, in total terms. A subtotal or
539
+ * grand-total cell is otherwise indistinguishable from a data cell — same
540
+ * column name, same shape of value — so a consumer could not tell that the
541
+ * number covers rows beyond the ones the cell names.
542
+ */
543
+ rowKind?: PivotRowKind;
544
+ columnKind?: PivotColumnKind;
545
+ /**
546
+ * The dimension values this cell is actually scoped to — what a consumer
547
+ * should filter the source by to reach the rows behind it.
548
+ *
549
+ * For a data cell that is every row and column dimension. For a row subtotal
550
+ * only the dimensions fixed at that level appear, because the rest are rolled
551
+ * up; for a grand total it is empty, meaning "everything". This is why the
552
+ * raw row cannot be used: on a total the transform overwrites the leading
553
+ * dimension with its label ("Subtotal - Rolling Test 2019") and blanks the
554
+ * rest, so filtering by the row's own fields would produce nonsense.
555
+ */
556
+ scope?: Record<string, any>;
557
+ /** How many source rows the cell aggregates, when the pivot reports it. */
558
+ sourceCount?: number;
339
559
  }
340
560
  interface ActionClick {
341
561
  rowId: string;
342
562
  rowData?: any;
563
+ /** Name of the configured action that was clicked; absent for the fallback icon. */
564
+ action_name?: string;
565
+ /** The full action descriptor, for consumers that need the icon/condition too. */
566
+ action?: GridAction | null;
567
+ mode?: 'table' | 'board';
568
+ group?: RowGroup;
343
569
  }
344
570
  interface RowDataRequest {
345
571
  groupId: string;
@@ -380,6 +606,25 @@ interface CellValueChange {
380
606
  * </ng-template>
381
607
  * <eru-grid [boardCardTemplate]="myCard" [gridConfig]="cfg"></eru-grid>
382
608
  */
609
+ /**
610
+ * Template context injected into a personCardTemplate on EruGridComponent.
611
+ *
612
+ * eru-studio renders a whole studio page as the person card. eru-grid cannot do
613
+ * that — it has no page renderer, and the dependency runs studio -> grid — so it
614
+ * follows the same pattern as boardCardTemplate: the grid owns the trigger,
615
+ * positioning and dismissal, and the consumer supplies the content.
616
+ *
617
+ * <ng-template #personCard let-userId let-column="column">…</ng-template>
618
+ * <eru-grid [personCardTemplate]="personCard" [gridConfig]="cfg"></eru-grid>
619
+ */
620
+ interface PersonCardContext {
621
+ /** The selected person's id ($implicit — bind with let-userId) */
622
+ $implicit: string;
623
+ /** The people column this avatar belongs to */
624
+ column: Field | null;
625
+ /** Page the field names as the card, from the data model's people_card */
626
+ cardPage?: string;
627
+ }
383
628
  interface BoardCardContext {
384
629
  /** Row data for this card ($implicit — bind with let-row) */
385
630
  $implicit: Row;
@@ -515,6 +760,17 @@ declare class PivotTransformService {
515
760
  /**
516
761
  * Add interleaved subtotals within column groups
517
762
  */
763
+ /**
764
+ * The column-dimension values a generated pivot column belongs to.
765
+ *
766
+ * Prefers the `parentPath` the generator already recorded. The fallback parses
767
+ * the name, which is "<dim>|<dim>|<aggregation>" — so the dimensions are every
768
+ * segment but the last. This used to be read as `name.split('_')[0]`, which
769
+ * only worked while no dimension value and no leading part of an aggregation
770
+ * name contained an underscore; one underscore in a dimension value silently
771
+ * grouped the columns wrongly.
772
+ */
773
+ private dimensionPartsOf;
518
774
  private addInterleavedSubtotals;
519
775
  /**
520
776
  * Group pivot columns by dimension prefixes for subtotal insertion
@@ -543,6 +799,48 @@ declare class PivotTransformService {
543
799
  /**
544
800
  * Apply aggregation function to array of values
545
801
  */
802
+ /**
803
+ * The function that combines already-aggregated cell values into a subtotal
804
+ * or grand total.
805
+ *
806
+ * Rolling a total up is NOT the same operation as computing the cells. A
807
+ * subtotal over `count` cells must ADD the counts; applying `count` again
808
+ * counts how many pivot rows contributed, which is why every subtotal read 1
809
+ * and the grand total read 7 (the number of pivot rows) instead of the real
810
+ * record counts. `sum`, `min` and `max` are associative so they roll up as
811
+ * themselves — which is why sums looked right and hid the bug.
812
+ *
813
+ * `avg` has no correct roll-up from the averages alone: the mean of means is
814
+ * only the true mean when every group has the same number of records. It is
815
+ * left as-is rather than silently reported as exact — see the note at the
816
+ * call sites.
817
+ */
818
+ private rollUpFunction;
819
+ /**
820
+ * The source records a pivot row covers, or null when they cannot be worked
821
+ * out and the caller must fall back to rolling up cells.
822
+ *
823
+ * A grand-total row covers everything; a subtotal row covers the records
824
+ * matching the dimension prefix in its key ("subtotal_<value>"); a data row
825
+ * covers the records matching all of its dimension values. Row keys are the
826
+ * dimension values joined by '|', which is what makes this recoverable — the
827
+ * row's own display fields are not usable on a total, where the transform has
828
+ * replaced the leading dimension with a label and blanked the rest.
829
+ */
830
+ /** Escape a value so it can be embedded in a RegExp as a literal. */
831
+ private escapeForRegExp;
832
+ private rowScope;
833
+ /**
834
+ * The source records a grand-total cell covers, or null when they cannot be
835
+ * determined and the caller should fall back to rolling up cells.
836
+ *
837
+ * `columnKey` is a generated column name. A data cell is
838
+ * "<dimValue>|…|<measure>", so the column-dimension values are the segments
839
+ * before the measure and the total spans every record matching them. A
840
+ * subtotal or column-grand-total column has no such coordinate — it is itself
841
+ * a roll-up — so those return null.
842
+ */
843
+ private grandTotalScope;
546
844
  private applyAggregationFunction;
547
845
  /**
548
846
  * Group rows by dimension values
@@ -687,6 +985,10 @@ declare class EruGridStore {
687
985
  private readonly _dataRequest;
688
986
  private readonly _requestQueue;
689
987
  private readonly _excelDownloadRequest;
988
+ private readonly _serverValidationNotices;
989
+ readonly serverValidationNotices: _angular_core.Signal<ServerValidationNotice[]>;
990
+ setServerValidationNotice(notice: Omit<ServerValidationNotice, 'id' | 'timestamp'>): void;
991
+ removeServerValidationNotice(id: string): void;
690
992
  private readonly _selectedDesignColumn;
691
993
  readonly selectedDesignColumn: _angular_core.Signal<string | null>;
692
994
  private readonly _columnDesignUpdate;
@@ -755,6 +1057,30 @@ declare class EruGridStore {
755
1057
  getConfiguration(): GridConfiguration;
756
1058
  reorderColumns(fromIndex: number, toIndex: number): void;
757
1059
  updateColumnRequired(columnName: string, required: boolean): void;
1060
+ /**
1061
+ * The field the design panel is editing.
1062
+ *
1063
+ * A pivot aggregation is a real, author-configured field but it is not in
1064
+ * `columns()` — that holds the source columns the dimensions come from. The
1065
+ * panel resolved only against those, so an aggregation header had nothing to
1066
+ * open. Checked after columns so a dimension always wins, which is what an
1067
+ * author clicking a row-dimension header means.
1068
+ */
1069
+ readonly designField: _angular_core.Signal<Field | null>;
1070
+ /**
1071
+ * Everything the design panel can edit, grouped for its target picker.
1072
+ *
1073
+ * Needed because not every designable field has a header to click. With a
1074
+ * single measure the pivot omits the measure-name row, so the one header cell
1075
+ * stands for the column dimension AND the measure fused together — clicking
1076
+ * it can only open one of them, leaving the dimension unreachable. Rather
1077
+ * than overload that header, the panel lets you switch target directly.
1078
+ * Narrow headers and hidden columns are reachable the same way.
1079
+ */
1080
+ readonly designTargets: _angular_core.Signal<{
1081
+ group: string;
1082
+ fields: Field[];
1083
+ }[]>;
758
1084
  updateColumnMeta(columnName: string, patch: Partial<Field>): void;
759
1085
  /**
760
1086
  * Apply metadata resolved from a mapped entity field. Unlike
@@ -765,6 +1091,29 @@ declare class EruGridStore {
765
1091
  */
766
1092
  applyResolvedColumnMeta(columnName: string, patch: Partial<Field>): void;
767
1093
  selectDesignColumn(columnName: string | null): void;
1094
+ /**
1095
+ * The field a generated pivot column belongs to.
1096
+ *
1097
+ * A pivot leaf ("<dimValue>|…|<measure>", or a "_…_subtotal_<measure>" /
1098
+ * "_…_column_grand_total_<measure>" roll-up) is a cell position, not a field,
1099
+ * and exists in neither `columns()` nor the aggregation list under that name.
1100
+ * Anything keyed off the leaf name therefore matched nothing. Resolving to the
1101
+ * owning measure — or leaving a real column name untouched — is what lets
1102
+ * width and design edits land somewhere that survives the next transform.
1103
+ *
1104
+ * Longest name first so a measure whose name is a suffix of another
1105
+ * ("amount" vs "net_amount") cannot claim the wrong leaf.
1106
+ */
1107
+ pivotOwnerName(columnName: string): string;
1108
+ /**
1109
+ * Set a column's width, at the level the width is actually configured.
1110
+ *
1111
+ * In pivot mode the resized header is a generated leaf, so the width is
1112
+ * stored on the measure (or the dimension) it came from and inherited by
1113
+ * every column that measure produces — resizing one value's column resizes
1114
+ * them all. Writing it per generated column would be lost as soon as the
1115
+ * data changed which dimension values exist.
1116
+ */
768
1117
  updateColumnWidth(columnName: string, field_size: number): void;
769
1118
  setCellValueChange(change: CellValueChange): void;
770
1119
  setGroups(groups: RowGroup[]): void;
@@ -848,7 +1197,29 @@ declare class EruGridStore {
848
1197
  * Check if freeze grand total is enabled
849
1198
  */
850
1199
  isFreezeGrandTotalEnabled(): boolean;
851
- setDrilldown(drilldown: Drilldown, columnValues: string): void;
1200
+ /**
1201
+ * Record a drill click. `pivotPath` is the list of column-dimension values
1202
+ * that located the cell (empty in table mode).
1203
+ *
1204
+ * The caller supplies `rowData` — the cell already holds the row it renders,
1205
+ * so there is nothing to look up. Only the frozen grand-total cell is
1206
+ * resolved here, because that row is not passed to a cell at all. Previously
1207
+ * this reconstructed the row from an index parsed out of the cell id, which
1208
+ * in table mode read `rows()[index - 2]` and so returned the wrong row, or
1209
+ * undefined for the first two.
1210
+ */
1211
+ setDrilldown(drilldown: Drilldown, pivotPath?: string[]): void;
1212
+ /**
1213
+ * Say what kind of pivot cell was drilled, and what it is scoped to.
1214
+ *
1215
+ * Without this a subtotal reports exactly like a data cell and a consumer
1216
+ * would filter the source by the row's own fields — which on a total row hold
1217
+ * the label ("Subtotal - Rolling Test 2019", "Grand Total") rather than the
1218
+ * dimension values, and blanks for the dimensions that were rolled up.
1219
+ */
1220
+ private describePivotCell;
1221
+ /** Drain the drill signal once a consumer has handled it. */
1222
+ clearDrilldown(): void;
852
1223
  clearPivotDrilldown(): void;
853
1224
  setActionClick(actionClick: ActionClick): void;
854
1225
  setColumnResize(columnResize: ColumnResize): void;
@@ -1118,6 +1489,13 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1118
1489
  * <eru-grid [boardCardTemplate]="myCard" [gridConfig]="cfg"></eru-grid>
1119
1490
  */
1120
1491
  boardCardTemplate?: TemplateRef<BoardCardContext>;
1492
+ /**
1493
+ * Content for a people cell's person card. The grid owns the trigger,
1494
+ * placement and dismissal; the consumer renders the content, because the card
1495
+ * is an eru-studio page and only the consumer can render one. Omit it and the
1496
+ * card trigger stays inert.
1497
+ */
1498
+ personCardTemplate?: TemplateRef<PersonCardContext>;
1121
1499
  /**
1122
1500
  * Height in pixels of each board card slot.
1123
1501
  * Must match the fixed height of the card rendered by boardCardTemplate (or the default card).
@@ -1153,6 +1531,21 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1153
1531
  mode: "table" | "board";
1154
1532
  group?: RowGroup;
1155
1533
  }>;
1534
+ /**
1535
+ * Fires when a user clicks a row action in the action column.
1536
+ *
1537
+ * Carries the same row/mode/group shape as `rowSelect` — the consumer that
1538
+ * handles a row click is the one that handles an action on it — plus the
1539
+ * action that was clicked, which is what tells the consumer which of its
1540
+ * configured handlers to run. `action` is null for the fallback more_horiz
1541
+ * icon shown when no `config.actions` were configured.
1542
+ */
1543
+ actionClick: EventEmitter<{
1544
+ action: GridAction | null;
1545
+ row: Row;
1546
+ mode: "table" | "board";
1547
+ group?: RowGroup;
1548
+ }>;
1156
1549
  emitRowSelect(row: Row, mode: 'table' | 'board', group?: RowGroup): void;
1157
1550
  /**
1158
1551
  * Fires when the user clicks the excel download icon (shown when
@@ -1258,6 +1651,14 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1258
1651
  private readonly SCROLL_THRESHOLD;
1259
1652
  /** Rows per lazy page: grid config `pageSize` if set, else ROWS_PER_PAGE. */
1260
1653
  private get rowsPerPage();
1654
+ /** Pages requested per group by the pivot loader; the anti-runaway counter. */
1655
+ private pivotPagesRequested;
1656
+ /**
1657
+ * How many rows pivot mode will pull before it stops asking for more. Beyond
1658
+ * this the pivot transform would not process client-side anyway, so pulling
1659
+ * further pages only costs round-trips.
1660
+ */
1661
+ private pivotRowCeiling;
1261
1662
  private lastLoadedGroupIds;
1262
1663
  private requestedGroupIds;
1263
1664
  private columnsInitialized;
@@ -1310,6 +1711,30 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1310
1711
  private requestRowsForGroup;
1311
1712
  isDesignMode(): boolean;
1312
1713
  onHeaderDesignClick(event: Event, column: Field): void;
1714
+ /**
1715
+ * The field a header's design control should edit.
1716
+ *
1717
+ * In table mode that is the column itself. In pivot mode a leaf column is a
1718
+ * generated cell position, not a field: "<dimValue>|<agg>" for an aggregated
1719
+ * cell, or a "_..._grand_total_<agg>" roll-up. Designing one of those would
1720
+ * format a single pivot cell and be lost the moment the data changed the set
1721
+ * of dimension values. What the author means is the AGGREGATION — format the
1722
+ * measure everywhere it appears — so the leaf resolves back to it. A row
1723
+ * dimension header is already a real source column and resolves to itself.
1724
+ */
1725
+ designTargetFor(column: Field): string;
1726
+ /**
1727
+ * Whether a pivot header can be resized.
1728
+ *
1729
+ * Only headers that stand for one field: a row dimension, or a header that
1730
+ * resolves to a measure. A column-dimension group header spans several
1731
+ * measures, so dragging it has no single width to set — and since widths are
1732
+ * now held per measure, resizing any one of that group's leaves widens the
1733
+ * matching column under every other dimension value anyway.
1734
+ */
1735
+ isResizablePivotHeader(header: any): boolean;
1736
+ /** True when this header's design control would edit a pivot aggregation. */
1737
+ isAggregationHeader(column: Field): boolean;
1313
1738
  trackByColumnFn(index: number, column: Field): string;
1314
1739
  trackByGroupItemFn(index: number, groupItem: GroupItem): string;
1315
1740
  trackByPivotRowFn(index: number, pivotRow: PivotRow): string;
@@ -1320,6 +1745,16 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1320
1745
  trackByHeaderFn(index: number, header: any): string;
1321
1746
  hasNestedHeaders(): boolean;
1322
1747
  getHeaderRows(): any[][];
1748
+ /**
1749
+ * What a pivot leaf inherits from the field it was generated from.
1750
+ *
1751
+ * Presentation plus `enableDrilldown` — everything the author configures on
1752
+ * the measure or the dimension. Identity and layout (name, label,
1753
+ * grid_index, field_size, aggregationFunction) belong to the generated leaf
1754
+ * and are deliberately absent: a leaf's label is its pivot dimension value,
1755
+ * not the measure's name.
1756
+ */
1757
+ private static readonly PIVOT_INHERITED_KEYS;
1323
1758
  getLeafColumns(): Field[];
1324
1759
  isRowDimensionHeader(header: any): boolean;
1325
1760
  /**
@@ -1331,7 +1766,41 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1331
1766
  private initializeColumnWidths;
1332
1767
  toggleRowSelection(event: Event, row: any): void;
1333
1768
  toggleGroupSelection(event: Event, groupId: string): void;
1334
- onActionClick(event: Event, row: any): void;
1769
+ /**
1770
+ * A row action was clicked. Announced two ways, because the two kinds of
1771
+ * consumer read the grid differently: the store signal is what processo's
1772
+ * data viewer already drains, and the output event carries the same shape as
1773
+ * `rowSelect` so an eru-studio page can wire it up with the event framework.
1774
+ *
1775
+ * `action` is absent when the column is showing its fallback more_horiz icon
1776
+ * — i.e. no `config.actions` were configured.
1777
+ */
1778
+ onActionClick(event: Event, row: any, action?: GridAction, mode?: 'table' | 'board', group?: RowGroup): void;
1779
+ /**
1780
+ * Row actions for the action column: blank names discarded, and at most one
1781
+ * entry per name.
1782
+ *
1783
+ * The name is what comes back on the click, so two entries sharing one are
1784
+ * indistinguishable to the consumer — they would draw two icons that do the
1785
+ * same thing. Collapsing them here also keeps the template's
1786
+ * `track action.action_name` keys unique; duplicates would otherwise abort
1787
+ * the render. Matching is case-insensitive and ignores surrounding space, so
1788
+ * the grouping agrees with how a consumer is likely to compare the name.
1789
+ */
1790
+ readonly gridActions: _angular_core.Signal<GridAction[]>;
1791
+ readonly hasConfiguredActions: _angular_core.Signal<boolean>;
1792
+ readonly actionDisplayType: _angular_core.Signal<"icons" | "kebab">;
1793
+ /**
1794
+ * The actions that apply to one row, after each action's
1795
+ * `action_visible_condition` is evaluated against that row's values.
1796
+ */
1797
+ visibleActionsFor(row: any): GridAction[];
1798
+ /**
1799
+ * Width of the action column. Icons sit side by side, so the column has to
1800
+ * grow with the number configured or they wrap; a kebab is always one icon.
1801
+ * Capped so a long action list scrolls the icons rather than eating the grid.
1802
+ */
1803
+ readonly actionColumnWidth: _angular_core.Signal<number>;
1335
1804
  onSortColumn(event: Event, column: Field, direction: 'asc' | 'desc'): void;
1336
1805
  getSortDirection(fieldName: string): 'asc' | 'desc' | null;
1337
1806
  getSortPriority(fieldName: string): number | null;
@@ -1497,6 +1966,12 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1497
1966
  groupSeperatorColSpan(): number;
1498
1967
  readonly BOARD_CARD_ITEM_HEIGHT = 208;
1499
1968
  readonly isBoardMode: _angular_core.Signal<boolean>;
1969
+ /**
1970
+ * Whether board columns wrap onto further rows once they no longer fit, or
1971
+ * stay on one horizontally scrolling row. Defaults to wrapping, which is the
1972
+ * behaviour that lets a narrow viewport stack its columns.
1973
+ */
1974
+ readonly boardWrapColumns: _angular_core.Signal<boolean>;
1500
1975
  readonly visibleBoardFields: _angular_core.Signal<Field[]>;
1501
1976
  readonly visibleColumns: _angular_core.Signal<Field[]>;
1502
1977
  readonly hiddenColumns: _angular_core.Signal<Field[]>;
@@ -1510,7 +1985,7 @@ declare class EruGridComponent implements OnInit, AfterViewInit, OnDestroy {
1510
1985
  requestBoardRowsForGroup(group: RowGroup): void;
1511
1986
  loadBoardAll(): void;
1512
1987
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EruGridComponent, never>;
1513
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EruGridComponent, "eru-grid", never, { "gridConfig": { "alias": "gridConfig"; "required": false; }; "boardCardTemplate": { "alias": "boardCardTemplate"; "required": false; }; "boardCardHeight": { "alias": "boardCardHeight"; "required": false; }; "boardCardGap": { "alias": "boardCardGap"; "required": false; }; "boardCardPadding": { "alias": "boardCardPadding"; "required": false; }; }, { "rowSelect": "rowSelect"; }, never, never, true, never>;
1988
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EruGridComponent, "eru-grid", never, { "gridConfig": { "alias": "gridConfig"; "required": false; }; "boardCardTemplate": { "alias": "boardCardTemplate"; "required": false; }; "personCardTemplate": { "alias": "personCardTemplate"; "required": false; }; "boardCardHeight": { "alias": "boardCardHeight"; "required": false; }; "boardCardGap": { "alias": "boardCardGap"; "required": false; }; "boardCardPadding": { "alias": "boardCardPadding"; "required": false; }; }, { "rowSelect": "rowSelect"; "actionClick": "actionClick"; }, never, never, true, never>;
1514
1989
  }
1515
1990
  interface GroupItem {
1516
1991
  type: 'header' | 'row' | 'table-header' | 'row-place-holder' | 'ghost-loading';
@@ -1521,6 +1996,36 @@ interface GroupItem {
1521
1996
  declare const MATERIAL_PROVIDERS: Provider[];
1522
1997
  declare const MATERIAL_MODULES: any[];
1523
1998
 
1999
+ /**
2000
+ * Row-scoped condition evaluation, used by the action column to decide whether
2001
+ * an action's icon applies to a given row.
2002
+ *
2003
+ * The authoring language is the one eru-studio already uses for page logic —
2004
+ * `@field` names a value, AND/OR/NOT join clauses, and the `is` / `is empty` /
2005
+ * `contains` / `in` / `between` phrases read the same way — so an author moving
2006
+ * between a page and a grid writes one language. The studio service itself
2007
+ * cannot be reused here: the dependency runs studio -> grid, and it resolves
2008
+ * names against page data and state, neither of which a grid row has. Here a
2009
+ * name is a column of the row being rendered.
2010
+ *
2011
+ * Expressions are compiled once and cached by their text, because a condition
2012
+ * is re-evaluated for every visible row on every change detection pass. A
2013
+ * condition that fails to compile or throws is treated as visible, matching how
2014
+ * the studio evaluator defaults on error — an authoring mistake should not
2015
+ * silently remove an action from every row.
2016
+ */
2017
+ /**
2018
+ * Read a field off a row. A grid row keeps its values under `entity_data`, but
2019
+ * the array-backed sources (query rows, pivot rows) put them at the top level,
2020
+ * so both are checked before falling back to walking a dotted path.
2021
+ */
2022
+ declare function resolveRowValue(row: any, fieldName: string): any;
2023
+ /**
2024
+ * Evaluate a visibility condition against one row. A blank or absent
2025
+ * expression means always visible.
2026
+ */
2027
+ declare function evaluateRowCondition(expression: string | null | undefined, row: any): boolean;
2028
+
1524
2029
  interface AttachmentConfig {
1525
2030
  disabled?: boolean;
1526
2031
  required?: boolean;
@@ -1549,6 +2054,15 @@ declare class AttachmentComponent implements OnInit {
1549
2054
  fileDeleted: EventEmitter<string>;
1550
2055
  fileViewed: EventEmitter<string>;
1551
2056
  currentValue: _angular_core.WritableSignal<string[]>;
2057
+ /**
2058
+ * Why the last attempted file was refused, or '' when none.
2059
+ *
2060
+ * The checks were already here but only reached console.warn, so a user
2061
+ * dropping an oversized file saw nothing happen at all. Shown in the panel,
2062
+ * matching how eru-studio's attachment reports a rejection.
2063
+ */
2064
+ rejectionMessage: _angular_core.WritableSignal<string>;
2065
+ private rejectFile;
1552
2066
  isOverlayOpen: _angular_core.WritableSignal<boolean>;
1553
2067
  isDragging: _angular_core.WritableSignal<boolean>;
1554
2068
  fileInput: ElementRef<HTMLInputElement>;
@@ -1581,25 +2095,38 @@ declare class AttachmentComponent implements OnInit {
1581
2095
  }
1582
2096
 
1583
2097
  declare class CheckboxComponent {
1584
- value: _angular_core.InputSignal<boolean>;
2098
+ value: _angular_core.InputSignal<any>;
2099
+ /** Column config; supplies value_true / value_false when the field uses literals. */
2100
+ config: _angular_core.InputSignal<Field | null>;
1585
2101
  isEditable: _angular_core.InputSignal<boolean>;
1586
2102
  isActive: _angular_core.InputSignal<boolean>;
1587
2103
  isDrillable: _angular_core.InputSignal<boolean>;
1588
2104
  label: _angular_core.InputSignal<string>;
1589
- valueChange: EventEmitter<boolean>;
1590
- change: EventEmitter<boolean>;
1591
- blur: EventEmitter<boolean>;
2105
+ valueChange: EventEmitter<any>;
2106
+ change: EventEmitter<any>;
2107
+ blur: EventEmitter<any>;
1592
2108
  focus: EventEmitter<void>;
1593
- drilldownClick: EventEmitter<boolean>;
2109
+ drilldownClick: EventEmitter<any>;
1594
2110
  editModeChange: EventEmitter<boolean>;
1595
2111
  currentValue: _angular_core.WritableSignal<boolean>;
1596
2112
  constructor();
2113
+ private toBool;
2114
+ private literals;
2115
+ /**
2116
+ * Stored representation -> checked state. When the field configures
2117
+ * value_true / value_false the backend may hold arbitrary literals ('Y'/'N',
2118
+ * 'Yes'/'No'); left blank, both fall through to the generic coercion. Ported
2119
+ * from the eru-studio checkbox so the two agree on what a stored value means.
2120
+ */
2121
+ private resolveCheckboxState;
2122
+ /** Inverse: checked state -> the representation to store. */
2123
+ private checkboxValueFor;
1597
2124
  onValueChange(event: boolean): void;
1598
2125
  onActivate(): void;
1599
2126
  onBlur(): void;
1600
2127
  onDrillableClick(event: Event): void;
1601
2128
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<CheckboxComponent, never>;
1602
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<CheckboxComponent, "eru-checkbox", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "change": "change"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
2129
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<CheckboxComponent, "eru-checkbox", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "change": "change"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
1603
2130
  }
1604
2131
 
1605
2132
  interface CurrencyValidationResult {
@@ -1615,6 +2142,12 @@ declare class CurrencyComponent {
1615
2142
  isDrillable: _angular_core.InputSignal<boolean>;
1616
2143
  replaceZeroValue: _angular_core.InputSignal<string | undefined>;
1617
2144
  placeholder: _angular_core.InputSignal<string>;
2145
+ /**
2146
+ * The row this cell belongs to. Needed for `symbol_field`, which names another
2147
+ * field on the SAME record supplying the currency symbol — so a multi-currency
2148
+ * column renders each row with its own symbol instead of one column-wide one.
2149
+ */
2150
+ row: _angular_core.InputSignal<any>;
1618
2151
  valueChange: EventEmitter<string | number | null>;
1619
2152
  blur: EventEmitter<string | number | null>;
1620
2153
  focus: EventEmitter<void>;
@@ -1623,6 +2156,20 @@ declare class CurrencyComponent {
1623
2156
  editModeChange: EventEmitter<boolean>;
1624
2157
  currentValue: _angular_core.WritableSignal<string | number | null>;
1625
2158
  error: _angular_core.WritableSignal<string>;
2159
+ /**
2160
+ * Colours from the column's configured value ranges, applied over the cell's
2161
+ * normal appearance. View mode only — tinting an active input reads as a
2162
+ * validation state rather than a value. Null when no band matches, matching
2163
+ * the eru-studio currency component so a value paints the same on a page and here.
2164
+ */
2165
+ /**
2166
+ * Symbol to show. `symbol_field` wins when set — read from this row, so each
2167
+ * record can carry its own currency — otherwise the column's static `symbol`.
2168
+ * Rows may nest their values under entity_data.
2169
+ */
2170
+ resolvedSymbol: _angular_core.Signal<string>;
2171
+ rangeColor: _angular_core.Signal<string | null>;
2172
+ rangeBackground: _angular_core.Signal<string | null>;
1626
2173
  constructor();
1627
2174
  formatNumberSignal: _angular_core.Signal<string | undefined>;
1628
2175
  onActivate(): void;
@@ -1632,7 +2179,7 @@ declare class CurrencyComponent {
1632
2179
  private validateField;
1633
2180
  private focusAndPositionCursor;
1634
2181
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<CurrencyComponent, never>;
1635
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<CurrencyComponent, "eru-currency", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "replaceZeroValue": { "alias": "replaceZeroValue"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "validationError": "validationError"; "editModeChange": "editModeChange"; }, never, never, true, never>;
2182
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<CurrencyComponent, "eru-currency", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "replaceZeroValue": { "alias": "replaceZeroValue"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "row": { "alias": "row"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "validationError": "validationError"; "editModeChange": "editModeChange"; }, never, never, true, never>;
1636
2183
  }
1637
2184
 
1638
2185
  declare class DateComponent {
@@ -1644,7 +2191,7 @@ declare class DateComponent {
1644
2191
  isActive: _angular_core.InputSignal<boolean>;
1645
2192
  isDrillable: _angular_core.InputSignal<boolean>;
1646
2193
  placeholder: _angular_core.InputSignal<string>;
1647
- readonly dateFormat: _angular_core.Signal<any>;
2194
+ readonly dateFormat: _angular_core.Signal<string>;
1648
2195
  readonly allowDays: _angular_core.Signal<string[]>;
1649
2196
  readonly dayFilter: _angular_core.Signal<(d: Date | null) => boolean>;
1650
2197
  valueChange: EventEmitter<string | null>;
@@ -1653,10 +2200,22 @@ declare class DateComponent {
1653
2200
  editModeChange: EventEmitter<boolean>;
1654
2201
  currentValue: _angular_core.WritableSignal<Date | null>;
1655
2202
  private shouldOpenPicker;
2203
+ /**
2204
+ * The column's configured default, resolved to a real date.
2205
+ *
2206
+ * The data model expresses it as a rule rather than a fixed value: the
2207
+ * `Current_Date` sentinel in `default`, optionally shifted by
2208
+ * `default_value_offset_days` (negative goes back). Resolving it here means a
2209
+ * cell with no stored value shows what the field defines, matching eru-studio
2210
+ * — previously the grid ignored both and showed an empty cell.
2211
+ */
2212
+ private resolveDefaultDate;
1656
2213
  constructor();
1657
2214
  onActivate(): void;
1658
2215
  onDateChange(date: Date | null): void;
1659
2216
  onDrillableClick(event: Event): void;
2217
+ /** Render a date in the column's configured format, via the shared token
2218
+ * formatter so a page and a grid read identically. */
1660
2219
  formatDate(date: Date | null): string;
1661
2220
  parseDateString(dateString: string): Date | null;
1662
2221
  getDisplayValue(): string;
@@ -1876,6 +2435,14 @@ declare class NumberComponent {
1876
2435
  editModeChange: EventEmitter<boolean>;
1877
2436
  currentValue: _angular_core.WritableSignal<string | number | null>;
1878
2437
  error: _angular_core.WritableSignal<string>;
2438
+ /**
2439
+ * Colours from the column's configured value ranges, applied over the cell's
2440
+ * normal appearance. View mode only — tinting an active input reads as a
2441
+ * validation state rather than a value. Null when no band matches, matching
2442
+ * the eru-studio number component so a value paints the same on a page and here.
2443
+ */
2444
+ rangeColor: _angular_core.Signal<string | null>;
2445
+ rangeBackground: _angular_core.Signal<string | null>;
1879
2446
  constructor();
1880
2447
  formatNumberSignal: _angular_core.Signal<string | undefined>;
1881
2448
  onActivate(): void;
@@ -1904,11 +2471,47 @@ declare class PeopleComponent implements OnInit {
1904
2471
  isDrillable: _angular_core.InputSignal<boolean>;
1905
2472
  columnWidth: _angular_core.InputSignal<number>;
1906
2473
  eruGridStore: _angular_core.InputSignal<EruGridStore | null>;
2474
+ /**
2475
+ * Content for the person card, supplied by the consumer. The grid owns the
2476
+ * trigger, positioning and dismissal; only the consumer can render a page,
2477
+ * which is what eru-studio's card is. Without a template the trigger is inert.
2478
+ */
2479
+ personCardTemplate: _angular_core.InputSignal<TemplateRef<PersonCardContext> | null | undefined>;
1907
2480
  valueChange: EventEmitter<string[] | null>;
1908
2481
  blur: EventEmitter<string[] | null>;
1909
2482
  focus: EventEmitter<void>;
1910
2483
  drilldownClick: EventEmitter<string[] | null>;
1911
2484
  editModeChange: EventEmitter<boolean>;
2485
+ /**
2486
+ * Whether more than one person may be picked. From the column config, so a
2487
+ * field declared single-select in the data model is enforced here too. The
2488
+ * stored value stays a list either way — the flag only limits how many may be
2489
+ * chosen, so no consumer has to handle a shape change.
2490
+ */
2491
+ isMultiple: _angular_core.Signal<boolean>;
2492
+ /** Show the filter box inside the dropdown. Configurable, as in eru-studio. */
2493
+ isSearchable: _angular_core.Signal<boolean>;
2494
+ /** mat-select holds a list when multiple, a single id otherwise. */
2495
+ selectValue: _angular_core.Signal<any>;
2496
+ /** Page the field names as the card. Blank means no card. */
2497
+ cardPage: _angular_core.Signal<string>;
2498
+ /** 'none' | 'hover' | 'click' — 'none' whenever no card page is mapped. */
2499
+ cardTrigger: _angular_core.Signal<"none" | "hover" | "click">;
2500
+ /** The card is a view-mode affordance, and needs content to show. */
2501
+ cardEnabled: _angular_core.Signal<boolean>;
2502
+ /** Beside the avatar first (right, then left), dropping below only when
2503
+ * neither side fits. Offsets stay small: on hover the cursor has to cross the
2504
+ * gap to reach the card, and a wide one makes that a miss. */
2505
+ readonly cardPositions: ConnectedPosition[];
2506
+ /** Person whose card is open, or null. */
2507
+ openCardUserId: _angular_core.WritableSignal<string | null>;
2508
+ private static readonly CARD_CLOSE_DELAY_MS;
2509
+ private closeCardTimer;
2510
+ onAvatarEnter(userId: string): void;
2511
+ onAvatarLeave(): void;
2512
+ onAvatarCardClick(event: Event, userId: string): void;
2513
+ closeCard(): void;
2514
+ private cancelPendingClose;
1912
2515
  currentValue: _angular_core.WritableSignal<string[]>;
1913
2516
  searchText: _angular_core.WritableSignal<string>;
1914
2517
  selectContainer: ElementRef<HTMLElement>;
@@ -1925,6 +2528,13 @@ declare class PeopleComponent implements OnInit {
1925
2528
  onActivate(): void;
1926
2529
  onBlur(event: Event): void;
1927
2530
  onValueChange(value: string[]): void;
2531
+ /**
2532
+ * mat-select emits a list when multiple and a bare id otherwise. The stored
2533
+ * value is a list in both cases, so a single selection is re-wrapped here —
2534
+ * that way turning cardinality off never changes the value's shape for
2535
+ * consumers or for the data model.
2536
+ */
2537
+ onSelectionChange(value: any): void;
1928
2538
  onSearchChange(searchValue: string): void;
1929
2539
  onDrillableClick(event: Event): void;
1930
2540
  onOpenedChange(opened: boolean): void;
@@ -1935,7 +2545,7 @@ declare class PeopleComponent implements OnInit {
1935
2545
  selectedMultiSelect(value: string[]): void;
1936
2546
  trackByValueAndIndex(index: number, option: any): string;
1937
2547
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PeopleComponent, never>;
1938
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PeopleComponent, "eru-people", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "columnWidth": { "alias": "columnWidth"; "required": false; "isSignal": true; }; "eruGridStore": { "alias": "eruGridStore"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
2548
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PeopleComponent, "eru-people", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "columnWidth": { "alias": "columnWidth"; "required": false; "isSignal": true; }; "eruGridStore": { "alias": "eruGridStore"; "required": false; "isSignal": true; }; "personCardTemplate": { "alias": "personCardTemplate"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
1939
2549
  }
1940
2550
 
1941
2551
  interface CountryCode {
@@ -2075,6 +2685,14 @@ declare class ProgressComponent implements OnInit {
2075
2685
  currentValue: _angular_core.WritableSignal<number>;
2076
2686
  displayValue: _angular_core.Signal<number>;
2077
2687
  barColor: _angular_core.Signal<string | null>;
2688
+ /**
2689
+ * Whether to show the value as a percentage. `is_perc` is the data model's
2690
+ * flag; it only reads as a percentage on a 0-100 scale, so a column with a
2691
+ * custom scale shows the raw value against its bound instead.
2692
+ */
2693
+ showPercent: _angular_core.Signal<boolean>;
2694
+ /** Label beside the bar: "45%" on a percentage scale, "3 / 5" otherwise. */
2695
+ valueLabel: _angular_core.Signal<string>;
2078
2696
  constructor();
2079
2697
  ngOnInit(): void;
2080
2698
  onActivate(): void;
@@ -2121,6 +2739,12 @@ declare class SelectComponent implements OnInit, AfterViewInit {
2121
2739
  fieldSize: _angular_core.InputSignal<number>;
2122
2740
  multiple: _angular_core.InputSignal<boolean>;
2123
2741
  eruGridStore: _angular_core.InputSignal<EruGridStore | null>;
2742
+ /**
2743
+ * The row this cell belongs to. Needed for a dependent dropdown: `dpef` names
2744
+ * another field on the SAME record whose value scopes this list, so the list
2745
+ * differs per row and cannot be resolved from the column config alone.
2746
+ */
2747
+ row: _angular_core.InputSignal<any>;
2124
2748
  valueChange: EventEmitter<any>;
2125
2749
  blur: EventEmitter<any>;
2126
2750
  focus: EventEmitter<void>;
@@ -2142,6 +2766,34 @@ declare class SelectComponent implements OnInit, AfterViewInit {
2142
2766
  onBlur(event: Event): void;
2143
2767
  onValueChange(value: any | any[] | null): void;
2144
2768
  toggleSelectAll(checked: boolean): void;
2769
+ /**
2770
+ * Dependency pairs for a cascading dropdown, normalised.
2771
+ *
2772
+ * `def` names the field on THIS ROW supplying a value; `dpef` names the field
2773
+ * in the option-source entity to match it against. A column saved before
2774
+ * `df_fields` existed carries a single `dpef`, read as `{ def: dpef, dpef }`.
2775
+ */
2776
+ dependencyPairs: _angular_core.Signal<{
2777
+ def: string;
2778
+ dpef: string;
2779
+ }[]>;
2780
+ /** Legacy single-dependency value from this row, for API sources. */
2781
+ private legacyDpefValue;
2782
+ /** True when this column's options are scoped by another field on the row. */
2783
+ isDependent: _angular_core.Signal<boolean>;
2784
+ /**
2785
+ * Filter sent with the options request: keys are fields in the option-source
2786
+ * entity, values read from this row. A pair whose source value is absent is
2787
+ * omitted rather than sent blank, since filtering on a blank would narrow the
2788
+ * list to nothing.
2789
+ */
2790
+ dependencyFilter: _angular_core.Signal<Record<string, any>>;
2791
+ /**
2792
+ * Map key the option list is cached under. Must be identical at request time
2793
+ * and at lookup time, or the response is written under one key and read from
2794
+ * another — so both go through this.
2795
+ */
2796
+ optionsCacheKey: _angular_core.Signal<string>;
2145
2797
  onSearchChange(searchValue: string): void;
2146
2798
  onOpenedChange(opened: boolean): void;
2147
2799
  ngAfterViewInit(): void;
@@ -2162,7 +2814,7 @@ declare class SelectComponent implements OnInit, AfterViewInit {
2162
2814
  */
2163
2815
  private firstPrimitiveValue;
2164
2816
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SelectComponent, never>;
2165
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectComponent, "eru-select", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "columnWidth": { "alias": "columnWidth"; "required": false; "isSignal": true; }; "fieldSize": { "alias": "fieldSize"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "eruGridStore": { "alias": "eruGridStore"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
2817
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectComponent, "eru-select", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "isEditable": { "alias": "isEditable"; "required": false; "isSignal": true; }; "isActive": { "alias": "isActive"; "required": false; "isSignal": true; }; "isDrillable": { "alias": "isDrillable"; "required": false; "isSignal": true; }; "columnWidth": { "alias": "columnWidth"; "required": false; "isSignal": true; }; "fieldSize": { "alias": "fieldSize"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "eruGridStore": { "alias": "eruGridStore"; "required": false; "isSignal": true; }; "row": { "alias": "row"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "drilldownClick": "drilldownClick"; "editModeChange": "editModeChange"; }, never, never, true, never>;
2166
2818
  }
2167
2819
 
2168
2820
  declare class StatusComponent implements OnInit {
@@ -2449,5 +3101,5 @@ declare class ThemeToggleComponent {
2449
3101
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ThemeToggleComponent, "eru-theme-toggle", never, {}, {}, never, never, true, never>;
2450
3102
  }
2451
3103
 
2452
- export { AttachmentComponent, CheckboxComponent, ColumnConstraintsService, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, NumberComponent, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent };
2453
- export type { ActionClick, AggregationFunction, AttachmentConfig, BoardCardContext, CellValueChange, ColumnReorder, ColumnResize, ColumnWidthConstraints, CurrencyValidationResult, DataTypes, Drilldown, DurationValue, DynamicDataRequest, EmailValidationResult, ExcelDownloadPayload, ExcelDownloadRequest, Field, GridConfiguration, GridFeatures, GridMode, GridPreset, GridStyles, GridTokens, GroupItem$1 as GroupItem, LocationConfig, LocationValidationResult, NumberValidationResult, PeopleOption, PivotColumnGroupState, PivotColumnHeader, PivotConfiguration, PivotHeaderStructure, PivotMetadata, PivotResult, PivotRow, RatingConfig, Row, RowDataRequest, RowDataResponse, RowGrandTotal, RowGroup, TextareaValidationResult, TextboxValidationResult, Theme, WebsiteValidationResult };
3104
+ export { AttachmentComponent, CheckboxComponent, ColumnConstraintsService, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, evaluateRowCondition, formatDateWithPattern, matchColorRange, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseDateWithPattern, resolveRowValue };
3105
+ export type { ActionClick, AggregationFunction, AttachmentConfig, BoardCardContext, CellValueChange, ColorRangeBand, ColumnReorder, ColumnResize, ColumnWidthConstraints, CurrencyValidationResult, DataTypes, Drilldown, DurationValue, DynamicDataRequest, EmailValidationResult, ExcelDownloadPayload, ExcelDownloadRequest, Field, GridAction, GridConfiguration, GridFeatures, GridMode, GridPreset, GridStyles, GridTokens, GroupItem$1 as GroupItem, LocationConfig, LocationValidationResult, NumberValidationResult, PeopleOption, PersonCardContext, PivotColumnGroupState, PivotColumnHeader, PivotColumnKind, PivotConfiguration, PivotHeaderStructure, PivotMetadata, PivotResult, PivotRow, PivotRowKind, RatingConfig, Row, RowDataRequest, RowDataResponse, RowGrandTotal, RowGroup, ServerValidationNotice, TextareaValidationResult, TextboxValidationResult, Theme, WebsiteValidationResult };