@navette-js/tables 1.0.0-beta.2 → 1.0.0-beta.4

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/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { DataAdapter, ListQuery, Page as RecordPage, SortDirection } from '@navette-js/database';
2
2
  import { Attributes, type ColorManager } from '@navette-js/support';
3
+ import { type RelationshipQueryModifier } from './query-builder.ts';
3
4
  export * from './query-builder.ts';
4
5
  /** Render hook slots of a table. */
5
6
  export declare const TablesRenderHook: {
@@ -27,7 +28,6 @@ export declare const TablesRenderHook: {
27
28
  readonly TOOLBAR_TOGGLE_COLUMN_TRIGGER_AFTER: "tables::toolbar.toggle-column-trigger.after";
28
29
  readonly TOOLBAR_TOGGLE_COLUMN_TRIGGER_BEFORE: "tables::toolbar.toggle-column-trigger.before";
29
30
  };
30
- /** Methods and properties that trigger the table's loading state (`wire:target`). */
31
31
  export declare const LOADING_TARGETS = "activeTab,applyTableFilters,gotoPage,nextPage,previousPage,removeTableFilter,removeTableFilters,reorderTable,resetTableColumnSearch,resetTableFiltersForm,resetTableSearch,setPage,sortTable,tableColumnSearches,tableFilters,tableGrouping,tableRecordsPerPage,tableSearch,tableSort,toggleTableReordering";
32
32
  type StateResolver<R> = (ctx: {
33
33
  record: R;
@@ -63,6 +63,20 @@ export declare abstract class Column<R = any> {
63
63
  isSortInverted(): boolean;
64
64
  /** Link on the cell; takes precedence over the row action. */
65
65
  url(resolver: (record: R) => string | null): this;
66
+ protected _cellAction: string | {
67
+ getName(): string;
68
+ } | null;
69
+ /**
70
+ * Clicking the cell runs an action on its record: the name of a record action of the table, or an `Action` (checked and
71
+ * authorized like record actions when it is mounted). A column `url()` takes precedence; the action takes precedence over the row's link and action.
72
+ */
73
+ action(action: string | {
74
+ getName(): string;
75
+ } | null): this;
76
+ getAction(): {
77
+ getName(): string;
78
+ } | null;
79
+ getActionName(): string | null;
66
80
  getUrl(record: R): string | null;
67
81
  /** `isGlobal: false` excludes the column from the global search; `isIndividual` adds a search field for this column. */
68
82
  getSearchColumn(): string;
@@ -76,10 +90,42 @@ export declare abstract class Column<R = any> {
76
90
  isToggledHiddenByDefault?: boolean;
77
91
  }): this;
78
92
  alignment(alignment: 'start' | 'center' | 'end' | null): this;
93
+ protected _visibleFrom: string | null;
94
+ protected _hiddenFrom: string | null;
95
+ protected _columnWidth: string | null;
96
+ protected _grow: boolean | null;
97
+ protected _wrapHeader: boolean;
98
+ /** Shows the column only from this screen size up (`sm`, `md`, `lg`, `xl`, `2xl`). */
99
+ visibleFrom(breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | null): this;
100
+ /** Hides the column from this screen size up. */
101
+ hiddenFrom(breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | null): this;
102
+ /** Classes hiding the header and cells on some screen sizes. */
103
+ getResponsiveClasses(): string[];
104
+ /** Width of the column, as a CSS length (`200px`, `20%`...). */
105
+ width(width: string | number | null): this;
106
+ getWidth(): string | null;
107
+ /** The column takes the remaining width of the table (when it has no `width()`). */
108
+ grow(condition?: boolean): this;
109
+ /** In a table, columns do not grow unless told to; in a layout (split), they do. */
110
+ canGrow(defaultValue?: boolean): boolean;
111
+ /** Lets a long label wrap in the header. */
112
+ wrapHeader(condition?: boolean): this;
113
+ canHeaderWrap(): boolean;
79
114
  alignEnd(): this;
80
115
  alignLeft(): this;
81
116
  alignCenter(): this;
82
117
  getStateUsing(resolver: StateResolver<R>): this;
118
+ /** Fixed state, or a function of the record (alias of `getStateUsing()` for a value). */
119
+ state(state: unknown | ((record: R) => unknown)): this;
120
+ protected _default: unknown;
121
+ /** Value used when the state is empty. */
122
+ default(value: unknown): this;
123
+ protected _hidden: boolean | (() => boolean);
124
+ protected _visible: boolean | (() => boolean);
125
+ /** Removes the column from the table (header, cells, search, sort and column manager). */
126
+ hidden(condition?: boolean | (() => boolean)): this;
127
+ visible(condition?: boolean | (() => boolean)): this;
128
+ isHidden(): boolean;
83
129
  getLabel(): string;
84
130
  isSortable(): boolean;
85
131
  isGloballySearchable(): boolean;
@@ -94,53 +140,136 @@ export declare abstract class Column<R = any> {
94
140
  isClickDisabled(): boolean;
95
141
  abstract toHtml(record: R, recordKey?: string | number): Promise<string>;
96
142
  }
143
+ /** Value of an option that may depend on the record and the state item. */
144
+ type PerItem<R, T> = T | ((record: R, state: unknown) => T);
145
+ /** Date in a PHP-style format (`M j, Y`, `d/m/Y H:i`…), in UTC or in the given IANA time zone. Database values without a zone are read as UTC. */
146
+ export declare function formatDate(value: unknown, format: string, timeZone?: string | null): string;
97
147
  export declare class TextColumn<R = any> extends Column<R> {
98
148
  private _weight;
99
149
  private _copyable;
150
+ private _copyableState;
151
+ private _copyMessage;
152
+ private _copyMessageDuration;
100
153
  private _money;
101
154
  private _placeholder;
102
155
  private _badge;
103
156
  private _color;
104
157
  private _icon;
158
+ private _iconColor;
159
+ private _iconPosition;
105
160
  private _format;
106
161
  private _date;
162
+ private _timeZone;
163
+ private _since;
107
164
  private _numeric;
165
+ private _decimals;
108
166
  private _summarizers;
109
167
  private _limit;
168
+ private _words;
169
+ private _prefix;
170
+ private _suffix;
110
171
  private _tooltip;
172
+ private _size;
173
+ private _fontFamily;
174
+ private _wrap;
175
+ private _lineClamp;
176
+ private _inline;
177
+ private _descriptionAbove;
178
+ private _descriptionBelow;
179
+ private _listWithLineBreaks;
180
+ private _bulleted;
181
+ private _listLimit;
182
+ private _expandableLimitedList;
183
+ private _separator;
111
184
  static make<R = any>(name: string): TextColumn<R>;
112
185
  /** Displays the value in a badge. */
113
186
  badge(condition?: boolean): this;
114
- color(color: string | ((record: R) => string)): this;
115
- icon(resolver: (record: R) => string | null): this;
187
+ /** Text color (or badge color): a color name, or a function of the record and the state item. */
188
+ color(color: PerItem<R, string | null>): this;
189
+ /** Icon next to the text: a name, or a function of the record and the state item returning a name or `null`. */
190
+ icon(icon: PerItem<R, string | null> | null): this;
191
+ iconColor(color: PerItem<R, string | null>): this;
192
+ iconPosition(position: 'before' | 'after'): this;
116
193
  /** Text displayed instead of the raw value (e.g. an enum label). */
117
194
  formatStateUsing(formatter: (ctx: {
118
195
  state: unknown;
119
196
  record: R;
120
197
  }) => string): this;
121
- /** Short date, e.g. "Nov 9, 2026". */
122
- date(condition?: boolean): this;
123
- private _dateTime;
124
- /** Date and time, e.g. "Nov 9, 2026 14:30:00". */
125
- dateTime(condition?: boolean): this;
126
- /** Number formatted with thousands separators. */
127
- private _decimals;
128
- /** `numeric(1)`: fixed number of decimals. */
198
+ /** Date in a PHP-style format, `M j, Y` by default ("Nov 9, 2026"). `timeZone`: IANA zone to display the date in. */
199
+ date(format?: string | boolean, timeZone?: string | null): this;
200
+ /** Date and time, `M j, Y H:i:s` by default. */
201
+ dateTime(format?: string | boolean, timeZone?: string | null): this;
202
+ /** Time only, `H:i:s` by default. */
203
+ time(format?: string, timeZone?: string | null): this;
204
+ /** Relative date, e.g. "2 days ago". */
205
+ since(condition?: boolean): this;
206
+ /** Time zone used by `date()`, `dateTime()` and `time()`. */
207
+ timezone(timeZone: string | null): this;
208
+ /** Number formatted with thousands separators; `numeric(1)`: fixed number of decimals. */
129
209
  numeric(condition?: boolean | number): this;
130
210
  /** Summary row below the table. */
131
211
  summarize(summarizer: Summarizer | Summarizer[]): this;
132
212
  getSummarizers(): Summarizer[];
133
- weight(weight: string): this;
213
+ /** Font weight: `thin`, `light`, `medium`, `semibold`, `bold`… */
214
+ weight(weight: PerItem<R, string | null>): this;
215
+ /** Text size: `xs`, `sm` (default), `md` or `lg`. */
216
+ size(size: PerItem<R, string | null>): this;
217
+ /** Font family: `sans`, `serif` or `mono`. */
218
+ fontFamily(family: PerItem<R, string | null>): this;
219
+ /** Lets long text wrap onto several lines instead of overflowing. */
220
+ wrap(condition?: boolean): this;
221
+ /** Clamps the text to this number of lines. */
222
+ lineClamp(lines: number | null): this;
223
+ /** Renders inline (without a block layout), e.g. inside a stacked layout. */
224
+ inline(condition?: boolean): this;
134
225
  /** Truncates the displayed text to `length` characters, followed by "...". */
135
226
  limit(length: number | null): this;
227
+ /** Truncates the displayed text to this number of words, followed by "...". */
228
+ words(count: number | null): this;
229
+ prefix(prefix: string | null): this;
230
+ suffix(suffix: string | null): this;
136
231
  /** Tooltip on hover; `null` for none. */
137
- tooltip(tooltip: ((record: R) => string | null) | string | null): this;
138
- copyable(condition?: boolean): this;
232
+ tooltip(tooltip: PerItem<R, string | null>): this;
233
+ copyable(condition?: PerItem<R, boolean>): this;
234
+ /** Value copied to the clipboard, when it differs from the displayed text. */
235
+ copyableState(state: PerItem<R, string | null>): this;
236
+ copyMessage(message: string | null): this;
237
+ copyMessageDuration(milliseconds: number): this;
139
238
  /** Amount formatted in the given currency, e.g. `money('usd')`. */
140
239
  money(currency?: string | ((record: R) => string)): this;
141
240
  /** Text displayed when the value is empty. */
142
241
  placeholder(text: string | null): this;
143
- /** Cell content: a short markup for plain text, a full markup as soon as an option (badge, icon, color…) is set. */
242
+ /** Secondary text below the value (`'below'`, the default) or above it. */
243
+ description(text: ((record: R) => string | null) | string | null, position?: 'above' | 'below'): this;
244
+ /** One value per line, for a state that is a list (or a string split by `separator()`). */
245
+ listWithLineBreaks(condition?: boolean): this;
246
+ /** Bulleted list. */
247
+ bulleted(condition?: boolean): this;
248
+ /** Shows only the first values of a list; `expandable`: a link reveals the others. */
249
+ limitList(count: number | null, expandable?: boolean): this;
250
+ expandableLimitedList(condition?: boolean): this;
251
+ /** Splits a string state into a list on this separator. */
252
+ separator(separator?: string | null): this;
253
+ private _html;
254
+ private _markdown;
255
+ /**
256
+ * Renders the state as HTML. It is sanitized first: only formatting elements (paragraphs, emphasis, lists, links with safe
257
+ * URLs...) are kept, without any attribute that could run scripts or change the page's styles.
258
+ */
259
+ html(condition?: PerItem<R, boolean>): this;
260
+ /** Renders the state as Markdown. Raw HTML in it is shown as text, and links only keep safe URLs. */
261
+ markdown(condition?: PerItem<R, boolean>): this;
262
+ /** Item text as HTML: Markdown, sanitized HTML, or escaped text. */
263
+ private renderItemText;
264
+ /** Displayed text of one state item, escaped by the caller. */
265
+ private formatItem;
266
+ private stateItems;
267
+ private hasDescriptions;
268
+ /** Plain value without per-item options: Filament's short markup. */
269
+ private canRenderOptimized;
270
+ private alignClass;
271
+ private containerClasses;
272
+ /** Cell content, as Filament renders it: a short markup for a plain value, the full markup as soon as an item option is set. */
144
273
  toHtml(record: R): Promise<string>;
145
274
  }
146
275
  /** Values a summarizer works from: aggregates over the query (all records) or over the displayed page. */
@@ -152,41 +281,94 @@ export type SummaryValues = {
152
281
  max: Record<string, unknown>;
153
282
  };
154
283
  export type SummaryItem = {
155
- type: 'text' | 'range';
284
+ type: 'text' | 'range' | 'values';
156
285
  label: string | null;
157
286
  value: string;
287
+ labelHidden?: boolean;
288
+ values?: string[];
289
+ bulleted?: boolean;
290
+ };
291
+ /** Records a custom summarizer works on: all records, the displayed page, or a group. */
292
+ export type SummaryDataset = {
293
+ column: string;
294
+ records: () => Promise<any[]>;
158
295
  };
159
- /** Column summary below the table: sum, average, count or range. */
160
- export declare abstract class Summarizer {
161
- abstract readonly kind: 'sum' | 'avg' | 'count' | 'range';
296
+ /**
297
+ * Column summary below the table: sum, average, count, range, distinct values, or your own with `Summarizer.make().using()`.
298
+ */
299
+ export declare class Summarizer {
300
+ readonly kind: 'sum' | 'avg' | 'count' | 'range' | 'values' | 'custom';
162
301
  protected _label: string | null;
163
302
  protected _money: string | null;
303
+ protected _decimals: number | null;
304
+ protected _prefix: string | null;
305
+ protected _suffix: string | null;
306
+ protected _formatStateUsing: ((state: unknown) => string | null) | null;
307
+ protected _labelHidden: boolean;
308
+ protected _hidden: boolean | (() => boolean);
309
+ protected _using: ((dataset: SummaryDataset) => unknown) | null;
310
+ protected _scope: ((query: any) => void) | null;
311
+ /**
312
+ * Restricts the records summarized by this summarizer (all records, the page, or a group), e.g.
313
+ * `Count.make().query((query) => query.where('is_published', 1))`. The function receives the data adapter's query builder.
314
+ */
315
+ query(scope: ((query: any) => void) | null): this;
316
+ getQueryScope(): ((query: any) => void) | null;
317
+ static make(): Summarizer;
164
318
  label(label: string | null): this;
165
319
  money(currency?: string): this;
320
+ /** Number formatting, with a fixed number of decimals. */
321
+ numeric(decimals?: number | null): this;
322
+ prefix(prefix: string | null): this;
323
+ suffix(suffix: string | null): this;
324
+ /** Formats the value yourself (the result is escaped). */
325
+ formatStateUsing(callback: ((state: unknown) => string | null) | null): this;
326
+ /** Keeps the label for screen readers only. */
327
+ hiddenLabel(condition?: boolean): this;
328
+ isLabelHidden(): boolean;
329
+ hidden(condition?: boolean | (() => boolean)): this;
330
+ visible(condition?: boolean | (() => boolean)): this;
331
+ isHidden(): boolean;
332
+ /** Computes the value from the summarized records (`records()` loads them: keep the dataset small). */
333
+ using(callback: ((dataset: SummaryDataset) => unknown) | null): this;
334
+ hasCustomCalculation(): boolean;
335
+ compute(dataset: SummaryDataset): Promise<unknown>;
166
336
  getLabel(): string | null;
167
- format(value: number): string;
168
- abstract summarize(column: string, values: SummaryValues): SummaryItem | null;
337
+ /** Formatted value: `formatStateUsing()`, money or number, then the prefix and suffix. */
338
+ format(value: unknown): string;
339
+ protected item(value: unknown, type?: SummaryItem['type']): SummaryItem;
340
+ /** Summary item from the aggregates (or from `customValue`, computed by `using()`). */
341
+ summarize(_column: string, _values: SummaryValues, customValue?: unknown): SummaryItem | null;
169
342
  }
170
343
  /** Column total. */
171
344
  export declare class Sum extends Summarizer {
172
345
  readonly kind = "sum";
173
346
  static make(): Sum;
174
347
  getLabel(): string;
175
- summarize(column: string, values: SummaryValues): SummaryItem;
348
+ summarize(column: string, values: SummaryValues, customValue?: unknown): SummaryItem;
176
349
  }
177
350
  /** Column average. */
178
351
  export declare class Average extends Summarizer {
179
352
  readonly kind = "avg";
180
353
  static make(): Average;
181
354
  getLabel(): string;
182
- summarize(column: string, values: SummaryValues): SummaryItem;
355
+ summarize(column: string, values: SummaryValues, customValue?: unknown): SummaryItem;
183
356
  }
184
357
  /** Record count. */
185
358
  export declare class Count extends Summarizer {
186
359
  readonly kind = "count";
187
360
  static make(): Count;
188
361
  getLabel(): string;
189
- summarize(_column: string, values: SummaryValues): SummaryItem;
362
+ summarize(_column: string, values: SummaryValues, customValue?: unknown): SummaryItem;
363
+ }
364
+ /** Distinct values of the column, as a (bulleted) list. */
365
+ export declare class Values extends Summarizer {
366
+ readonly kind = "values";
367
+ private _bulleted;
368
+ static make(): Values;
369
+ bulleted(condition?: boolean): this;
370
+ constructor();
371
+ summarize(_column: string, _values: SummaryValues, customValue?: unknown): SummaryItem;
190
372
  }
191
373
  /** Smallest and largest value of a column. */
192
374
  export declare class Range extends Summarizer {
@@ -195,7 +377,7 @@ export declare class Range extends Summarizer {
195
377
  static make(): Range;
196
378
  /** Dates: day only when the bounds fall on different days, day and time otherwise; a single value when both are equal. */
197
379
  minimalDateTimeDifference(condition?: boolean): this;
198
- summarize(column: string, values: SummaryValues): SummaryItem | null;
380
+ summarize(column: string, values: SummaryValues, _customValue?: unknown): SummaryItem | null;
199
381
  }
200
382
  /** Row grouping: rows are sorted by the column and each group gets a header row. */
201
383
  export declare class Group<R = any> {
@@ -207,7 +389,17 @@ export declare class Group<R = any> {
207
389
  private _collapsible;
208
390
  private _date;
209
391
  private _titlePrefixedWithLabel;
392
+ private _keyUsing;
393
+ private _scopeUsing;
394
+ private _orderUsing;
210
395
  constructor(id: string);
396
+ /** Group key of a record, when it is not the column value (records with the same key form a group). Use with `scopeQueryByKeyUsing()`. */
397
+ getKeyFromRecordUsing(resolver: ((record: R) => string | null) | null): this;
398
+ /** Restricts the query to one group (summaries, "select the group"): needed when `getKeyFromRecordUsing()` is not the column value. */
399
+ scopeQueryByKeyUsing(scope: ((query: any, key: string | null) => void) | null): this;
400
+ /** Orders the query by group, when ordering by the column is not right (e.g. a status with a custom order). */
401
+ orderQueryUsing(order: ((query: any, direction: SortDirection) => void) | null): this;
402
+ getOrderQueryUsing(): ((query: any, direction: SortDirection) => void) | null;
211
403
  static make<R = any>(id: string): Group<R>;
212
404
  label(label: string | null): this;
213
405
  /** Query column holding the group value, when it differs from the id (relationship). */
@@ -242,11 +434,41 @@ export declare class Group<R = any> {
242
434
  */
243
435
  export declare abstract class ColumnLayout<R = any> {
244
436
  protected _children: (Column<R> | ColumnLayout<R>)[];
437
+ protected _grow: boolean | null;
438
+ protected _visibleFrom: string | null;
439
+ protected _hiddenFrom: string | null;
440
+ protected _columnSpan: Record<string, number | string>;
441
+ protected _collapsible: boolean;
442
+ protected _collapsed: boolean;
443
+ protected _hidden: boolean | (() => boolean);
245
444
  constructor(children: (Column<R> | ColumnLayout<R>)[]);
246
445
  getChildren(): (Column<R> | ColumnLayout<R>)[];
247
446
  /** Leaf columns, in order: the ones the table sorts, searches and toggles. */
248
447
  getColumns(): Column<R>[];
249
448
  abstract getClasses(): string[];
449
+ /** `false`: the component takes only the width it needs in a split, instead of sharing the remaining space. */
450
+ grow(condition?: boolean): this;
451
+ canGrow(): boolean;
452
+ visibleFrom(breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | null): this;
453
+ hiddenFrom(breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | null): this;
454
+ getResponsiveClasses(): string[];
455
+ /** Columns of a parent `Grid` taken by the component: a number, `'full'`, or one per breakpoint. */
456
+ columnSpan(span: number | string | Record<string, number | string>): this;
457
+ columnSpanFull(): this;
458
+ getColumnSpan(): Record<string, number | string>;
459
+ /**
460
+ * Content hidden behind a toggle button at the bottom of the record. Only one top-level component of the layout can
461
+ * be collapsible. It starts collapsed; `collapsed(false)` shows it open.
462
+ */
463
+ collapsible(condition?: boolean): this;
464
+ collapsed(condition?: boolean): this;
465
+ isCollapsible(): boolean;
466
+ isCollapsed(): boolean;
467
+ hidden(condition?: boolean | (() => boolean)): this;
468
+ visible(condition?: boolean | (() => boolean)): this;
469
+ isHidden(): boolean;
470
+ /** Inner markup of the component, around the already rendered children. */
471
+ renderContent(children: string[], _record: R, _key: string): Promise<string>;
250
472
  }
251
473
  /** Columns side by side, from a given breakpoint. */
252
474
  export declare class Split<R = any> extends ColumnLayout<R> {
@@ -265,6 +487,46 @@ export declare class Stack<R = any> extends ColumnLayout<R> {
265
487
  alignment(alignment: 'start' | 'center' | 'end'): this;
266
488
  getClasses(): string[];
267
489
  }
490
+ /** Responsive grid of columns: `Grid.make(2)` (2 columns from `lg`) or `Grid.make({ default: 1, md: 2, xl: 3 })`. */
491
+ export declare class Grid<R = any> extends ColumnLayout<R> {
492
+ private _gridColumns;
493
+ constructor(columns?: number | Record<string, number> | null);
494
+ static make<R = any>(columns?: number | Record<string, number> | null): Grid<R>;
495
+ /** Number of columns, or one per breakpoint (\`default\`, \`sm\`, \`md\`, \`lg\`, \`xl\`, \`2xl\`). */
496
+ columns(columns?: number | Record<string, number> | null): this;
497
+ /** Components of the grid. */
498
+ schema(children: (Column<R> | ColumnLayout<R>)[]): this;
499
+ getGridColumns(): Record<string, number>;
500
+ getClasses(): string[];
501
+ renderContent(children: string[]): Promise<string>;
502
+ }
503
+ /** Plain container, styled as a panel: often made collapsible. */
504
+ export declare class Panel<R = any> extends ColumnLayout<R> {
505
+ static make<R = any>(children: (Column<R> | ColumnLayout<R>)[]): Panel<R>;
506
+ getClasses(): string[];
507
+ }
508
+ /**
509
+ * Your own markup inside the record: a function returning HTML, or the name of an EJS view rendered with `record` and
510
+ * `recordKey`. The HTML is inserted as it is: escape record data with `e()` (EJS's `<%= %>` does it).
511
+ */
512
+ export declare class View<R = any> extends ColumnLayout<R> {
513
+ private _view;
514
+ private _viewData;
515
+ constructor(view: string | ((ctx: {
516
+ record: R;
517
+ recordKey: string;
518
+ }) => string | Promise<string>));
519
+ static make<R = any>(view: string | ((ctx: {
520
+ record: R;
521
+ recordKey: string;
522
+ }) => string | Promise<string>)): View<R>;
523
+ /** Extra data passed to the EJS view. */
524
+ viewData(data: Record<string, unknown>): this;
525
+ /** Columns rendered inside the view are not part of the table: the view has no children. */
526
+ components(children: (Column<R> | ColumnLayout<R>)[]): this;
527
+ getClasses(): string[];
528
+ renderContent(children: string[], record: R, key: string): Promise<string>;
529
+ }
268
530
  /** Column group: a shared label above the column headers. */
269
531
  export declare class ColumnGroup<R = any> {
270
532
  private label;
@@ -274,28 +536,108 @@ export declare class ColumnGroup<R = any> {
274
536
  columns(columns: Column<R>[]): this;
275
537
  getColumns(): Column<R>[];
276
538
  }
277
- /** Color swatch column. */
539
+ /** Color swatch per state (a color, or a list of colors). */
278
540
  export declare class ColorColumn<R = any> extends Column<R> {
541
+ private _copyable;
542
+ private _copyMessage;
543
+ private _copyMessageDuration;
544
+ private _tooltip;
545
+ private _placeholder;
546
+ private _wrap;
547
+ private _inline;
279
548
  static make<R = any>(name: string): ColorColumn<R>;
549
+ /** Clicking the swatch copies the color. Browsers only expose the clipboard on HTTPS or `localhost`. */
550
+ copyable(condition?: PerItem<R, boolean>): this;
551
+ copyMessage(message: string | null): this;
552
+ copyMessageDuration(milliseconds: number): this;
553
+ tooltip(tooltip: PerItem<R, string | null>): this;
554
+ placeholder(text: string | null): this;
555
+ /** Lets several swatches wrap onto more lines. */
556
+ wrap(condition?: boolean): this;
557
+ inline(condition?: boolean): this;
280
558
  toHtml(record: R): Promise<string>;
281
559
  }
282
- /** Image column: the state is the image URL. Only local or http(s) URLs are rendered. */
560
+ /** Image per state (an image URL, or a list of URLs). Only local or http(s) URLs are rendered. */
283
561
  export declare class ImageColumn<R = any> extends Column<R> {
284
562
  private _alt;
285
563
  private _height;
564
+ private _width;
286
565
  private _circular;
566
+ private _square;
567
+ private _stacked;
568
+ private _ring;
569
+ private _overlap;
570
+ private _limit;
571
+ private _limitedRemainingText;
572
+ private _limitedRemainingTextSize;
573
+ private _defaultImageUrl;
574
+ private _tooltip;
575
+ private _placeholder;
576
+ private _wrap;
577
+ private _inline;
287
578
  static make<R = any>(name: string): ImageColumn<R>;
288
- alt(resolver: (record: R) => string | null): this;
289
- /** CSS height of the image (`2.5rem` by default). */
290
- imageHeight(height: string): this;
579
+ alt(alt: PerItem<R, string | null>): this;
580
+ /** CSS height of the images (`2.5rem` by default, `2rem` when stacked). */
581
+ imageHeight(height: string | number | null): this;
582
+ imageWidth(width: string | number | null): this;
583
+ /** Same width and height. */
584
+ imageSize(size: string | number | null): this;
291
585
  circular(condition?: boolean): this;
586
+ square(condition?: boolean): this;
587
+ /** Overlapping images, with a `ring` (border width, 0 to 8) and an `overlap` (0 to 8). */
588
+ stacked(condition?: boolean): this;
589
+ ring(width: number | null): this;
590
+ overlap(overlap: number | null): this;
591
+ /** Shows only the first images; `limitedRemainingText()` adds "+3" for the others. */
592
+ limit(count: number | null): this;
593
+ limitedRemainingText(condition?: boolean, size?: string | null): this;
594
+ /** Image shown when the state is empty. */
595
+ defaultImageUrl(url: string | ((record: R) => string | null) | null): this;
596
+ tooltip(tooltip: PerItem<R, string | null>): this;
597
+ placeholder(text: string | null): this;
598
+ wrap(condition?: boolean): this;
599
+ inline(condition?: boolean): this;
292
600
  toHtml(record: R): Promise<string>;
293
601
  }
294
602
  /** Icon depending on the value; `boolean()` shows a green check or a red cross. */
603
+ /**
604
+ * Icon per state. `boolean()` shows a green check or a red cross; `icon()` and `color()` choose the icon and color from the
605
+ * record and the state, e.g. `IconColumn.make('status').icon((record) => STATUSES[record.status].icon)`.
606
+ */
295
607
  export declare class IconColumn<R = any> extends Column<R> {
296
608
  private _boolean;
609
+ private _icon;
610
+ private _color;
611
+ private _size;
612
+ private _trueIcon;
613
+ private _falseIcon;
614
+ private _trueColor;
615
+ private _falseColor;
616
+ private _tooltip;
617
+ private _placeholder;
618
+ private _listWithLineBreaks;
619
+ private _wrap;
620
+ private _inline;
297
621
  static make<R = any>(name: string): IconColumn<R>;
298
622
  boolean(condition?: boolean): this;
623
+ icon(icon: PerItem<R, string | null> | null): this;
624
+ color(color: PerItem<R, string | null> | null): this;
625
+ /** Icon size: `xs`, `sm`, `md`, `lg` (default), `xl` or `2xl`. */
626
+ size(size: PerItem<R, string | null>): this;
627
+ /** Icons and colors of a boolean column; `false` shows no icon for that value. */
628
+ trueIcon(icon: string | false | null): this;
629
+ falseIcon(icon: string | false | null): this;
630
+ trueColor(color: string | null): this;
631
+ falseColor(color: string | null): this;
632
+ tooltip(tooltip: PerItem<R, string | null>): this;
633
+ placeholder(text: string | null): this;
634
+ /** One icon per line, for a state that is a list. */
635
+ listWithLineBreaks(condition?: boolean): this;
636
+ wrap(condition?: boolean): this;
637
+ inline(condition?: boolean): this;
638
+ private isBooleanItem;
639
+ private iconOf;
640
+ private colorOf;
299
641
  toHtml(record: R): Promise<string>;
300
642
  }
301
643
  /** Application-defined validation rule of an editable column: synchronous check and message (`:attribute` already replaced). */
@@ -345,6 +687,8 @@ export declare abstract class EditableColumn<R = any> extends Column<R> {
345
687
  * Error message, or `null` if the value is valid. An empty value is only checked by `required`, and several errors
346
688
  * produce the first message followed by "(and N more errors)".
347
689
  */
690
+ /** Validation of a value for a given record (column types whose rules depend on the record override it). */
691
+ validateFor(_record: R, input: unknown): Promise<string | null>;
348
692
  validate(input: unknown): string | null;
349
693
  /** Custom write, replacing the update of the record's column. Its return value is sent back to the browser. */
350
694
  updateStateUsing(callback: StateHook<R> | null): this;
@@ -361,7 +705,7 @@ export declare abstract class EditableColumn<R = any> extends Column<R> {
361
705
  */
362
706
  updateState(record: R, input: unknown, adapter: Pick<DataAdapter<R>, 'getKey' | 'update'>): Promise<unknown>;
363
707
  /** `x-tooltip` expression of editable controls: the column tooltip, replaced by the validation error when there is one. */
364
- protected tooltipExpression(record: R): string;
708
+ protected tooltipExpression(record: R, extra?: string | null): string;
365
709
  /** Shared opening markup of editable cells: lazily loaded Alpine component, server state and record key. */
366
710
  protected editableAttributes(component: string, asset: string, recordKey: string | number, state: unknown): Attributes;
367
711
  protected static readonly STATUS_HTML = "<p x-cloak x-show=\"error\" x-bind:id=\"$id('table-column-error')\" x-ref=\"error\" x-text=\"error\" class=\"fi-sr-only\"></p><p role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" x-text=\"status\" class=\"fi-sr-only\"></p>";
@@ -369,14 +713,56 @@ export declare abstract class EditableColumn<R = any> extends Column<R> {
369
713
  /**
370
714
  * In-cell editable select (native select only). The received value must be one of the declared options.
371
715
  */
716
+ export type SelectColumnOptions = [string, string][] | Record<string, string>;
372
717
  export declare class SelectColumn<R = any> extends EditableColumn<R> {
373
718
  private _options;
374
719
  private _selectablePlaceholder;
720
+ private _native;
721
+ private _searchable;
722
+ private _disabledOptions;
723
+ private _placeholder;
724
+ private _optionsLimit;
725
+ private _canOptionLabelsWrap;
726
+ private _position;
727
+ private _inline;
375
728
  static make<R = any>(name: string): SelectColumn<R>;
376
- options(options: [string, string][] | Record<string, string>): this;
729
+ /** `{ value: label }` pairs, `[value, label]` pairs, or a function of the record returning either. */
730
+ options(options: SelectColumnOptions | ((record: R) => SelectColumnOptions | Promise<SelectColumnOptions>)): this;
731
+ /** Static options (empty when they depend on the record: use `getOptionsFor()`). */
377
732
  getOptions(): [string, string][];
733
+ getOptionsFor(record: R): Promise<[string, string][]>;
734
+ private _optionsRelationship;
735
+ private _relationshipOptions;
736
+ /**
737
+ * Options from the records of a relationship of the record's model (an Objection model with `relationMappings`): the
738
+ * column stores the picked record's key, e.g. `SelectColumn.make('author_id').optionsRelationship('author', 'name')`.
739
+ * Up to 1,000 options are listed; any related record's key is accepted when saving.
740
+ */
741
+ optionsRelationship(name: string, titleAttribute?: string | null, modifyQueryUsing?: RelationshipQueryModifier | null): this;
378
742
  selectablePlaceholder(condition?: boolean): this;
743
+ /** `false`: a JavaScript select instead of the browser's native one. */
744
+ native(condition?: boolean): this;
745
+ private _allowHtml;
746
+ /** Option labels are HTML, sanitized on the server (JavaScript select only; the native select shows their text). */
747
+ allowHtml(condition?: boolean): this;
748
+ private labelForDisplay;
749
+ /** A search field in the select (implies a JavaScript select). */
750
+ searchableOptions(condition?: boolean): this;
751
+ isNative(): boolean;
752
+ /** Options that cannot be chosen. A disabled option is also refused on the server. */
753
+ disableOptionWhen(callback: (value: string, label: string, record: R) => boolean): this;
754
+ isOptionDisabled(value: string, label: string, record: R): boolean;
755
+ placeholder(text: string | null): this;
756
+ /** Maximum number of options listed by the JavaScript select (50 by default). */
757
+ optionsLimit(limit: number): this;
758
+ /** `false`: long option labels are truncated in the JavaScript select instead of wrapping. */
759
+ allowOptionsLabelsWrap(condition?: boolean): this;
760
+ /** Side where the JavaScript select's dropdown opens (automatic by default). */
761
+ position(position: 'top' | 'bottom' | null): this;
762
+ inline(condition?: boolean): this;
379
763
  protected getImplicitRules(): ColumnValidationRule[];
764
+ /** The value must be one of the record's options, and not a disabled one. */
765
+ validateFor(record: R, input: unknown): Promise<string | null>;
380
766
  toHtml(record: R, recordKey?: string | number): Promise<string>;
381
767
  }
382
768
  /** In-cell editable toggle. The received value must be a boolean (`boolean` rule by default). */
@@ -473,17 +859,22 @@ export declare function updateTableColumnState<R>(table: Table<R>, host: TableHo
473
859
  /** Filter state: the values of its form. */
474
860
  export type FilterState = Record<string, unknown>;
475
861
  /** Active filter indicator: its label, and the filter field cleared when it is removed. */
862
+ /** Active filter indicator: its label, the filter field cleared when it is removed (`''`: the whole filter), whether it can be removed, and its badge color. */
476
863
  export type FilterIndicator = {
477
864
  label: string;
478
865
  field: string;
866
+ removable?: boolean;
867
+ color?: string | null;
479
868
  };
480
- /** Filters layout: toolbar dropdown (default), or collapsible panel above the content. */
481
- export type FiltersLayout = 'dropdown' | 'above-content-collapsible';
869
+ /** Where the filters form is shown: in a toolbar dropdown (default) or modal, or in a panel around the table content. */
870
+ export type FiltersLayout = 'dropdown' | 'modal' | 'above-content' | 'above-content-collapsible' | 'below-content' | 'before-content' | 'before-content-collapsible' | 'after-content' | 'after-content-collapsible';
482
871
  export interface TableFilter {
483
872
  readonly name: string;
484
873
  getLabel(): string;
485
874
  /** Initial state (empty form). */
486
875
  getDefaultState(): FilterState;
876
+ /** State after the filter is removed (its indicator or "remove all"); `getDefaultState()` when absent. */
877
+ getResetState?(): FilterState;
487
878
  /** Options of the `value` field as [value, label] pairs, and the empty option's label. */
488
879
  getFormDefinition(): {
489
880
  placeholder: string;
@@ -504,88 +895,204 @@ export interface TableFilter {
504
895
  /** Data loaded from the database before indicators are computed (e.g. related record labels). */
505
896
  loadIndicatorData?(state: FilterState, host: TableHost): Promise<void>;
506
897
  }
507
- /** "Trashed" filter for soft-deletable models. */
508
- export declare class TrashedFilter implements TableFilter {
509
- readonly name = "trashed";
510
- static make(): TrashedFilter;
898
+ /** Badge shown above the table for an applied filter. */
899
+ export declare class Indicator {
900
+ private _label;
901
+ private _removeField;
902
+ private _removable;
903
+ private _color;
904
+ constructor(label: string);
905
+ static make(label: string): Indicator;
906
+ label(label: string): this;
511
907
  getLabel(): string;
512
- getDefaultState(): FilterState;
908
+ /** Field of the filter form cleared when the indicator is removed (by default, the whole filter is reset). */
909
+ removeField(field: string | null): this;
910
+ getRemoveField(): string | null;
911
+ /** `false`: no remove button. A forged removal is refused on the server too. */
912
+ removable(condition?: boolean): this;
913
+ isRemovable(): boolean;
914
+ color(color: string | null): this;
915
+ getColor(): string | null;
916
+ }
917
+ /** What `indicateUsing()` may return: a label, an `Indicator`, a list of them, or `{ field: label }` pairs. */
918
+ export type IndicatorResult = string | Indicator | null | undefined | (string | Indicator)[] | Record<string, string | Indicator>;
919
+ /** Shared options of the built-in filters: label, default state, indicators and custom query. */
920
+ declare abstract class BaseFilter implements TableFilter {
921
+ readonly name: string;
922
+ protected _label: string | null;
923
+ protected _default: unknown;
924
+ protected _indicator: string | Indicator | null;
925
+ protected _indicateUsing: ((data: FilterState) => IndicatorResult) | null;
926
+ protected _query: ((query: any, data: FilterState) => void) | null;
927
+ protected _columnSpan: Record<string, number | string>;
928
+ constructor(name: string);
929
+ label(label: string): this;
930
+ getLabel(): string;
931
+ /** State applied when the table opens and when the user clicks "Reset". */
932
+ default(value?: unknown): this;
933
+ /** Label of the indicator (the filter label by default). */
934
+ indicator(indicator: string | Indicator | null): this;
935
+ getIndicator(): string | Indicator;
936
+ /** Indicators of the applied state, replacing the default one. */
937
+ indicateUsing(callback: ((data: FilterState) => IndicatorResult) | null): this;
938
+ /** Query condition built from the sanitized state, replacing the default one. */
939
+ query(modifier: (query: any, data: FilterState) => void): this;
940
+ /** Width of the filter in the filters form grid. */
941
+ columnSpan(span: number | string | Record<string, number | string>): this;
942
+ columnSpanFull(): this;
943
+ getColumnSpan(): Record<string, number | string>;
944
+ /** State after removing the filter: blank, whatever the default. */
945
+ abstract getResetState(): FilterState;
946
+ abstract getDefaultState(): FilterState;
947
+ abstract sanitizeState(state: unknown): FilterState;
948
+ abstract isActive(state: FilterState): boolean;
949
+ abstract apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
513
950
  getFormDefinition(): {
514
951
  placeholder: string;
515
952
  options: [string, string][];
516
953
  };
517
- isActive(state: FilterState): boolean;
518
- apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
954
+ protected defaultIndicators(_data: FilterState): IndicatorResult;
955
+ getIndicators(state: FilterState): FilterIndicator[];
956
+ /** Prefixes a value label with the indicator, unless the indicator is a ready-made `Indicator`. */
957
+ protected labelled(valueLabel: string): string | Indicator;
519
958
  }
520
- /** Filters on a column value picked from a list (single select). */
521
- export declare class SelectFilter implements TableFilter {
522
- readonly name: string;
523
- private _label;
959
+ /** Filters on a column value picked from a list. */
960
+ export declare class SelectFilter extends BaseFilter {
524
961
  private _column;
525
962
  private _options;
526
- constructor(name: string);
963
+ private _multiple;
964
+ private _searchable;
965
+ private _selectablePlaceholder;
966
+ private _placeholder;
967
+ private _optionLabels;
527
968
  static make(name: string): SelectFilter;
528
- label(label: string): this;
529
969
  /** Filtered column when it differs from the filter name (e.g. a relationship's foreign key). */
530
970
  column(column: string): this;
971
+ attribute(column: string): this;
972
+ getColumn(): string;
531
973
  /** Fixed options, or an async function (e.g. records of a relationship). */
532
974
  options(options: [string, string][] | Record<string, string> | (() => Promise<[string, string][]>)): this;
533
- getLabel(): string;
534
- getDefaultState(): FilterState;
975
+ /** Several values can be picked: records matching any of them are shown. */
976
+ multiple(condition?: boolean): this;
977
+ isMultiple(): boolean;
978
+ searchable(condition?: boolean): this;
979
+ /** `false`: the empty option cannot be picked again once a value is chosen. */
980
+ selectablePlaceholder(condition?: boolean): this;
981
+ placeholder(placeholder: string | null): this;
982
+ /** No-op: options are always loaded with the form. */
983
+ preload(): this;
984
+ private _relationship;
985
+ private _emptyOptionLabel;
986
+ private _host;
987
+ /**
988
+ * Options from the records of a relationship of the table's model (an Objection model with `relationMappings`); records
989
+ * related to the picked ones are shown. `hasEmptyOption` adds a "None" option for records without related records.
990
+ */
991
+ relationship(name: string, titleAttribute?: string | null, modifyQueryUsing?: RelationshipQueryModifier | null, hasEmptyOption?: boolean): this;
992
+ hasRelationship(): boolean;
993
+ /** Label of the "None" option of `relationship(..., hasEmptyOption: true)`. */
994
+ emptyRelationshipOptionLabel(label: string | null): this;
995
+ getEmptyRelationshipOptionLabel(): string;
535
996
  getFormDefinition(): {
536
997
  placeholder: string;
537
998
  options: [string, string][];
538
999
  };
539
- getFormOptions(): Promise<[string, string][]>;
1000
+ getFormOptions(host?: unknown): Promise<[string, string][]>;
1001
+ private loadRelationshipOptions;
540
1002
  /** Options are evaluated on every render, so the select gets a key derived from them (forces a re-render when they change). */
541
1003
  readonly hasDynamicOptions = true;
1004
+ getFormComponents(): unknown[];
1005
+ private defaultValues;
1006
+ getResetState(): FilterState;
1007
+ getDefaultState(): FilterState;
1008
+ sanitizeState(state: unknown): FilterState;
1009
+ /** Values that can filter: with fixed options, only offered values; loaded options are bound parameters (an unknown value matches nothing). */
1010
+ private values;
542
1011
  isActive(state: FilterState): boolean;
543
1012
  apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
1013
+ /** Labels of the applied values, loaded before the indicators are computed. */
1014
+ loadIndicatorData(state: FilterState, host?: TableHost): Promise<void>;
1015
+ protected defaultIndicators(data: FilterState): IndicatorResult;
544
1016
  }
545
- /** Three-state filter on a boolean column: all, yes, no. */
546
- export declare class TernaryFilter implements TableFilter {
547
- readonly name: string;
548
- private _label;
549
- constructor(name: string);
1017
+ /** Three-state filter: all, yes, no. By default on a boolean column; `nullable()` checks whether the column is filled. */
1018
+ export declare class TernaryFilter extends BaseFilter {
1019
+ private _column;
1020
+ protected _trueLabel: string | null;
1021
+ protected _falseLabel: string | null;
1022
+ protected _placeholder: string | null;
1023
+ protected _nullable: boolean;
1024
+ protected _queries: {
1025
+ true: (query: any) => void;
1026
+ false: (query: any) => void;
1027
+ blank?: (query: any) => void;
1028
+ } | null;
550
1029
  static make(name: string): TernaryFilter;
551
- label(label: string): this;
552
- getLabel(): string;
553
- getDefaultState(): FilterState;
1030
+ attribute(column: string): this;
1031
+ column(column: string): this;
1032
+ getColumn(): string;
1033
+ trueLabel(label: string | null): this;
1034
+ falseLabel(label: string | null): this;
1035
+ placeholder(label: string | null): this;
1036
+ getTrueLabel(): string;
1037
+ getFalseLabel(): string;
1038
+ /** "Yes" shows records where the column is filled, "No" those where it is empty. */
1039
+ nullable(condition?: boolean): this;
1040
+ /** Custom conditions for "yes", "no" and, optionally, the empty choice. */
1041
+ queries(queries: {
1042
+ true: (query: any) => void;
1043
+ false: (query: any) => void;
1044
+ blank?: (query: any) => void;
1045
+ }): this;
554
1046
  getFormDefinition(): {
555
1047
  placeholder: string;
556
1048
  options: [string, string][];
557
1049
  };
1050
+ getFormComponents(): unknown[];
1051
+ getResetState(): FilterState;
1052
+ getDefaultState(): FilterState;
1053
+ sanitizeState(state: unknown): FilterState;
558
1054
  isActive(state: FilterState): boolean;
559
1055
  apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
1056
+ protected defaultIndicators(data: FilterState): IndicatorResult;
560
1057
  }
561
- /** Custom form filter (`schema()`): the query and indicators are written by the application. */
562
- export declare class Filter implements TableFilter {
563
- readonly name: string;
564
- private _label;
565
- private _components;
566
- private _query;
567
- private _indicate;
568
- constructor(name: string);
569
- static make(name: string): Filter;
570
- label(label: string): this;
571
- /** Filter fields; their names are the only state keys accepted from the browser. */
572
- schema(components: unknown[]): this;
573
- /** Query condition built from the sanitized form state. */
574
- query(modifier: (query: any, data: FilterState) => void): this;
575
- /** Indicators of applied values as `{ field: label }`; removing one clears its field. */
576
- indicateUsing(resolver: (data: FilterState) => Record<string, string>): this;
577
- private fields;
1058
+ /** "Trashed" filter for soft-deletable models: without (default), with, or only deleted records. */
1059
+ export declare class TrashedFilter extends TernaryFilter {
1060
+ constructor(name?: string);
1061
+ static make(name?: string): TrashedFilter;
578
1062
  getLabel(): string;
579
- getDefaultState(): FilterState;
1063
+ getTrueLabel(): string;
1064
+ getFalseLabel(): string;
580
1065
  getFormDefinition(): {
581
1066
  placeholder: string;
582
1067
  options: [string, string][];
583
1068
  };
1069
+ apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
1070
+ protected defaultIndicators(data: FilterState): IndicatorResult;
1071
+ }
1072
+ /**
1073
+ * Custom filter. Without `schema()`, it is a checkbox (or a toggle with `toggle()`) and `query()` runs when it is checked.
1074
+ * With `schema()`, the query and indicators are written by the application.
1075
+ */
1076
+ export declare class Filter extends BaseFilter {
1077
+ private _components;
1078
+ private _toggle;
1079
+ private _formComponents;
1080
+ static make(name: string): Filter;
1081
+ /** Filter fields; their names are the only state keys accepted from the browser. */
1082
+ schema(components: unknown[]): this;
1083
+ /** Form fields of a filter without `schema()`: a toggle instead of a checkbox. */
1084
+ toggle(condition?: boolean): this;
1085
+ checkbox(): this;
1086
+ private hasSchema;
584
1087
  getFormComponents(): unknown[];
1088
+ private fields;
1089
+ getResetState(): FilterState;
1090
+ getDefaultState(): FilterState;
585
1091
  sanitizeState(state: unknown): FilterState;
1092
+ /** A field left empty, or an unchecked checkbox, does not filter. */
586
1093
  isActive(state: FilterState): boolean;
587
1094
  apply(query: Omit<ListQuery, 'page' | 'perPage'>, state: FilterState): void;
588
- getIndicators(state: FilterState): FilterIndicator[];
1095
+ protected defaultIndicators(data: FilterState): IndicatorResult;
589
1096
  removeIndicator(state: FilterState, field: string): FilterState;
590
1097
  }
591
1098
  export type ColumnState = {
@@ -597,9 +1104,59 @@ export type ColumnState = {
597
1104
  isToggleable: boolean;
598
1105
  isToggledHiddenByDefault: boolean | null;
599
1106
  };
1107
+ /** Look of a filters action (trigger, apply, reset), changed with `filtersTriggerAction()`, `filtersApplyAction()` and `filtersResetAction()`. */
1108
+ export declare class FiltersAction {
1109
+ private _label;
1110
+ private _icon;
1111
+ private _color;
1112
+ private _view;
1113
+ private _hidden;
1114
+ label(label: string | null): this;
1115
+ icon(icon: string | null): this;
1116
+ color(color: string | null): this;
1117
+ /** Labeled button. */
1118
+ button(): this;
1119
+ link(): this;
1120
+ /** Icon only, the label becomes the tooltip and accessible name. */
1121
+ iconButton(): this;
1122
+ hidden(condition?: boolean): this;
1123
+ resolve(defaults: {
1124
+ label: string;
1125
+ icon: string | null;
1126
+ color: string;
1127
+ view: 'button' | 'link' | 'icon-button';
1128
+ }): {
1129
+ label: string;
1130
+ icon: string;
1131
+ color: string;
1132
+ view: "link" | "button" | "icon-button";
1133
+ hidden: boolean;
1134
+ };
1135
+ }
1136
+ type FiltersActionView = ReturnType<FiltersAction['resolve']> & {
1137
+ customized: boolean;
1138
+ };
1139
+ /** Bulk actions grouped in the "Bulk actions" dropdown of the toolbar (`toolbarActions([BulkActionGroup.make([...])])`). */
1140
+ export declare class BulkActionGroup {
1141
+ private _actions;
1142
+ constructor(actions: any[]);
1143
+ static make(actions: any[]): BulkActionGroup;
1144
+ getActions(): any[];
1145
+ }
1146
+ /** Upper bound on the rows loaded by an unpaginated table or the `'all'` page option. */
1147
+ export declare const MAX_UNPAGINATED_RECORDS = 10000;
600
1148
  export declare class Table<R = any> {
1149
+ /** Default setup for every table, e.g. `Table.configureUsing((table) => table.paginationPageOptions([10, 25]))`. */
1150
+ static configureUsing(configure: (table: Table<any>) => void): void;
1151
+ constructor();
601
1152
  private _columns;
602
1153
  private _recordAction;
1154
+ private _pageSummary;
1155
+ private _allTableSummary;
1156
+ /** Which summary rows to show: "this page" (`pageCondition`) and the whole table (`allTableCondition`); group summaries are not affected. */
1157
+ summaries(pageCondition?: boolean, allTableCondition?: boolean): this;
1158
+ hasPageSummary(): boolean;
1159
+ hasAllTableSummary(): boolean;
603
1160
  private _recordActions;
604
1161
  private _heading;
605
1162
  private _headerActions;
@@ -610,6 +1167,12 @@ export declare class Table<R = any> {
610
1167
  private _defaultSort;
611
1168
  private _recordUrl;
612
1169
  private _pageOptions;
1170
+ private _paginated;
1171
+ private _emptyStateHeading;
1172
+ private _emptyStateDescription;
1173
+ private _emptyStateIcon;
1174
+ private _emptyStateActions;
1175
+ private _emptyState;
613
1176
  private _stackedOnMobile;
614
1177
  private _label;
615
1178
  private _groups;
@@ -651,8 +1214,14 @@ export declare class Table<R = any> {
651
1214
  areGroupsCollapsedByDefault(): boolean;
652
1215
  private _columnsLayout;
653
1216
  columns(columns: (Column<R> | ColumnGroup<R> | ColumnLayout<R>)[]): this;
654
- /** Column layout, or `null` for a regular table. */
1217
+ /** Column layout, or `null` for a regular table. The collapsible component is left out (see `getCollapsibleColumnsLayout()`). */
655
1218
  getColumnsLayout(): (Column<R> | ColumnLayout<R>)[] | null;
1219
+ /** The first top-level layout component marked `collapsible()`, shown behind a toggle button. */
1220
+ getCollapsibleColumnsLayout(): ColumnLayout<R> | null;
1221
+ private _contentGrid;
1222
+ /** Arranges the record cards of a column layout in a grid: `contentGrid({ md: 2, xl: 3 })`. */
1223
+ contentGrid(columns: Record<string, number> | number | null): this;
1224
+ getContentGrid(): Record<string, number> | null;
656
1225
  /** Action mounted when a row is clicked (e.g. `'edit'`), instead of a URL. */
657
1226
  recordAction(name: string | null): this;
658
1227
  getRecordAction(): string | null;
@@ -664,10 +1233,18 @@ export declare class Table<R = any> {
664
1233
  getHeaderActions(): any[];
665
1234
  /** Bulk actions ("Bulk actions" menu); if never called, the host provides its default ones. */
666
1235
  groupedBulkActions(actions: any[]): this;
1236
+ /** Bulk actions: those of `groupedBulkActions()`, then those of the `BulkActionGroup`s of `toolbarActions()`; `null` when none is declared. */
667
1237
  getBulkActions(): any[] | null;
1238
+ private _toolbarActions;
1239
+ /** Actions in the toolbar, left of the search field: buttons, and a `BulkActionGroup` for the bulk actions dropdown. */
1240
+ toolbarActions(actions: any[]): this;
1241
+ /** Toolbar actions other than bulk action groups. */
1242
+ getToolbarActions(): any[];
668
1243
  /** Row actions: `Action`s or an `ActionGroup` (dropdown). */
669
1244
  recordActions(actions: any[]): this;
670
1245
  getRecordActions(): any[];
1246
+ /** Actions given to columns with `action()` (not those named after a record action). */
1247
+ getColumnActions(): any[];
671
1248
  /** CSS classes added to a row depending on its record. */
672
1249
  recordClasses(resolver: (record: R) => string | null): this;
673
1250
  getRecordClasses(record: R): string | null;
@@ -676,19 +1253,110 @@ export declare class Table<R = any> {
676
1253
  }): this;
677
1254
  filtersLayout(layout: FiltersLayout): this;
678
1255
  getFiltersLayout(): FiltersLayout;
679
- /** No-op kept for API compatibility: filters always wait for the "Apply" button. */
680
- deferFilters(): this;
1256
+ private _deferFilters;
1257
+ private _filtersFormColumns;
1258
+ private _filtersFormWidth;
1259
+ private _filtersFormMaxHeight;
1260
+ private _filtersResetPosition;
1261
+ private _filterIndicatorsHidden;
1262
+ private _persist;
1263
+ private _recordActionsPosition;
1264
+ private _recordActionsAlignment;
1265
+ /**
1266
+ * Where row actions go: after the columns (default), before the columns (after the checkbox), or before the checkbox
1267
+ * (`before-cells`). `after-cells` is the same as `after-columns`.
1268
+ */
1269
+ recordActionsPosition(position: 'before-columns' | 'before-cells' | 'after-columns' | 'after-cells'): this;
1270
+ getRecordActionsPosition(): 'before-columns' | 'before-cells' | 'after-columns' | 'after-cells';
1271
+ /** Alignment of row actions in their cell. */
1272
+ recordActionsAlignment(alignment: 'start' | 'center' | 'end' | null): this;
1273
+ getRecordActionsAlignment(): 'start' | 'center' | 'end' | null;
1274
+ private _isRecordSelectable;
1275
+ private _maxSelectableRecords;
1276
+ private _selectsCurrentPageOnly;
1277
+ /** Records for which this returns `false` have no checkbox, and are ignored by bulk actions even if the browser sends them. */
1278
+ checkIfRecordIsSelectableUsing(callback: ((record: R) => boolean | Promise<boolean>) | null): this;
1279
+ hasRecordSelectabilityCheck(): boolean;
1280
+ isRecordSelectable(record: R): Promise<boolean>;
1281
+ /** Maximum number of records the user may select at once (also enforced on the server). */
1282
+ maxSelectableRecords(count: number | null): this;
1283
+ getMaxSelectableRecords(): number | null;
1284
+ /** "Select all" only selects the records of the current page. */
1285
+ selectCurrentPageOnly(condition?: boolean): this;
1286
+ selectsCurrentPageOnly(): boolean;
1287
+ /** `false`: filters apply as soon as a field changes, without the "Apply" button. */
1288
+ deferFilters(condition?: boolean): this;
1289
+ private _filterActions;
1290
+ /** Customizes the button that opens the filters: `filtersTriggerAction((action) => action.button().label('Filter'))`. */
1291
+ filtersTriggerAction(configure: (action: FiltersAction) => FiltersAction | void): this;
1292
+ /** Customizes the "Apply" button of deferred filters. */
1293
+ filtersApplyAction(configure: (action: FiltersAction) => FiltersAction | void): this;
1294
+ /** Customizes the "Reset" button (`hidden()` removes it). */
1295
+ filtersResetAction(configure: (action: FiltersAction) => FiltersAction | void): this;
1296
+ getFiltersActionView(kind: 'trigger' | 'apply' | 'reset'): FiltersActionView;
1297
+ private _filtersFormSchema;
1298
+ /**
1299
+ * Arranges the filters form yourself: the function receives each filter's form group, keyed by filter name, and returns
1300
+ * the form components (e.g. sections). Filters left out are not shown.
1301
+ */
1302
+ filtersFormSchema(schema: ((filters: Record<string, any>) => any[]) | null): this;
1303
+ getFiltersFormSchema(): ((filters: Record<string, any>) => any[]) | null;
1304
+ hasDeferredFilters(): boolean;
1305
+ /** Columns of the filters form grid (1 in a dropdown or modal, up to 5 above or below the content by default). */
1306
+ filtersFormColumns(columns: number | Record<string, number> | null): this;
1307
+ getFiltersFormColumns(): number | Record<string, number>;
1308
+ /** Width of the filters dropdown, modal or side panel: `xs` to `7xl`, `full`, `screen`... */
1309
+ filtersFormWidth(width: string | null): this;
1310
+ getFiltersFormWidth(): string | null;
1311
+ /** Maximum height of the filters dropdown, as a CSS length; it scrolls beyond. */
1312
+ filtersFormMaxHeight(height: string | null): this;
1313
+ getFiltersFormMaxHeight(): string | null;
1314
+ /** `footer`: the "Reset" button goes next to "Apply" instead of the filters heading. */
1315
+ filtersResetActionPosition(position: 'header' | 'footer'): this;
1316
+ getFiltersResetActionPosition(): 'header' | 'footer';
1317
+ /** Hides the active filter badges above the table. */
1318
+ hiddenFilterIndicators(condition?: boolean): this;
1319
+ areFilterIndicatorsHidden(): boolean;
1320
+ /** Keeps the applied filters for the user's browser session (restored when the page is opened again). */
1321
+ persistFiltersInSession(condition?: boolean): this;
1322
+ persistSearchInSession(condition?: boolean): this;
1323
+ persistColumnSearchesInSession(condition?: boolean): this;
1324
+ persistSortInSession(condition?: boolean): this;
1325
+ private _persistGrouping;
1326
+ persistGroupingInSession(condition?: boolean): this;
1327
+ persistsGroupingInSession(): boolean;
1328
+ persistsFiltersInSession(): boolean;
1329
+ persistsSearchInSession(): boolean;
1330
+ persistsColumnSearchesInSession(): boolean;
1331
+ persistsSortInSession(): boolean;
681
1332
  defaultSort(column: string, direction?: SortDirection): this;
682
1333
  recordUrl(resolver: (record: R) => string | null): this;
683
1334
  private _reorderColumn;
684
1335
  /** Lets users reorder rows by hand; `column` stores the position. */
685
1336
  reorderable(column: string | null): this;
686
1337
  getReorderColumn(): string | null;
687
- paginationPageOptions(options: number[]): this;
1338
+ /** Page sizes offered; `'all'` shows every record (up to `MAX_UNPAGINATED_RECORDS`). */
1339
+ paginationPageOptions(options: (number | 'all')[]): this;
1340
+ /** `false`: no pagination, every record is shown (up to `MAX_UNPAGINATED_RECORDS`). An array sets the page options. */
1341
+ paginated(condition?: boolean | (number | 'all')[]): this;
1342
+ isPaginated(): boolean;
1343
+ /** Heading of the empty state, instead of "No <records>". */
1344
+ emptyStateHeading(heading: string | (() => string) | null): this;
1345
+ emptyStateDescription(description: string | (() => string | null) | null): this;
1346
+ emptyStateIcon(icon: string | null): this;
1347
+ /** Actions shown in the empty state, e.g. a create action. They are mounted like table header actions. */
1348
+ emptyStateActions(actions: any[]): this;
1349
+ getEmptyStateActions(): any[];
1350
+ /** Replaces the whole empty state with your HTML (inserted as it is: escape your data with `e()`). */
1351
+ emptyState(html: string | (() => string | Promise<string>) | null): this;
1352
+ getEmptyStateHeading(): string;
1353
+ getEmptyStateDescription(): string | null;
1354
+ getEmptyStateIcon(): string;
1355
+ getCustomEmptyState(): Promise<string | null>;
688
1356
  private _defaultPageOption;
689
1357
  /** Rows per page when the table opens. */
690
- defaultPaginationPageOption(option: number | null): this;
691
- getDefaultPageOption(): number;
1358
+ defaultPaginationPageOption(option: number | 'all' | null): this;
1359
+ getDefaultPageOption(): number | 'all';
692
1360
  private _paginationMode;
693
1361
  /** `'simple'`: previous / next buttons only, without page numbers or total. */
694
1362
  paginationMode(mode: 'default' | 'simple'): this;
@@ -703,7 +1371,7 @@ export declare class Table<R = any> {
703
1371
  direction: SortDirection;
704
1372
  } | null;
705
1373
  getRecordUrl(record: R): string | null;
706
- getPageOptions(): number[];
1374
+ getPageOptions(): (number | 'all')[];
707
1375
  isStackedOnMobile(): boolean;
708
1376
  getPluralModelLabel(): string;
709
1377
  hasToggleableColumns(): boolean;
@@ -717,7 +1385,7 @@ export interface TableHost {
717
1385
  tableSearch: string;
718
1386
  tableSort: string | null;
719
1387
  tableColumnSearches: Record<string, string>;
720
- tableRecordsPerPage: number;
1388
+ tableRecordsPerPage: number | 'all';
721
1389
  paginators: Record<string, number>;
722
1390
  tableFilters?: Record<string, FilterState>;
723
1391
  tableColumns?: ColumnState[];
@@ -749,6 +1417,10 @@ export declare function getFilterIndicators<R>(table: Table<R>, host: TableHost)
749
1417
  field: string;
750
1418
  label: string;
751
1419
  }[];
1420
+ /** Whether an applied filter can be removed: none of its indicators is marked `removable(false)`. */
1421
+ export declare function isTableFilterRemovable<R>(table: Table<R>, host: TableHost, filterName: string): Promise<boolean>;
1422
+ /** Keys whose record passes `checkIfRecordIsSelectableUsing()`, loaded in chunks. Without a check, the keys are returned as they are. */
1423
+ export declare function filterSelectableKeys<R>(table: Table<R>, adapter: Pick<DataAdapter<R>, 'find'>, keys: string[], chunkSize?: number): Promise<string[]>;
752
1424
  /** Cycles a column's sort: none → asc → desc → none. */
753
1425
  export declare function sortTable(host: TableHost, column?: string | null, direction?: string | null): void;
754
1426
  /** Loads the page of records matching the host's state. */
@@ -774,6 +1446,10 @@ export type RenderTableOptions<R> = {
774
1446
  after?: string;
775
1447
  /** Already rendered HTML of the table header actions. */
776
1448
  headerActions?: string[];
1449
+ /** Already rendered HTML of the toolbar actions. */
1450
+ toolbarActions?: string[];
1451
+ /** Already rendered HTML of the empty state actions. */
1452
+ emptyStateActions?: string[];
777
1453
  /** Already rendered HTML of the row actions (ActionGroup…), replacing the `rowActions` links. */
778
1454
  rowActionsHtml?: (record: R) => Promise<string>;
779
1455
  bulkActions?: string[];