@richardmcquiston01/dashboard-widgets-toolkit 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,758 +0,0 @@
1
- /**
2
- * Widget data payloads, one shape per kind, discriminated by `kind`.
3
- *
4
- * The kind names are upper case to stay compatible with Maker Toolkit's
5
- * `WidgetViewType` (`TEXT`, `GAUGE`, `TABLE`, `GRAPH`). `KPI`, `BAR_LIST`
6
- * and `ALERT_LIST` are new. Every payload is plain JSON: strings, finite
7
- * numbers, booleans, null, arrays and objects. No dates (send ISO strings),
8
- * no functions, no class instances.
9
- */
10
- /** Every widget kind, in a stable order. */
11
- declare const WIDGET_KINDS: readonly ["TEXT", "KPI", "GAUGE", "TABLE", "BAR_LIST", "ALERT_LIST", "GRAPH"];
12
- type WidgetKind = (typeof WIDGET_KINDS)[number];
13
- declare function isWidgetKind(value: unknown): value is WidgetKind;
14
- /** How a number is shown: a plain count, money, or a fraction as a percent. */
15
- declare const VALUE_FORMATS: readonly ["number", "currency", "percent"];
16
- type ValueFormat = (typeof VALUE_FORMATS)[number];
17
- /** A single headline value as text, e.g. "12" or "Open". */
18
- interface TextWidgetData {
19
- readonly kind: 'TEXT';
20
- readonly value: string;
21
- readonly label: string;
22
- }
23
- /**
24
- * A stat tile: one number, optionally compared with the previous period.
25
- *
26
- * `value` and `previous` are in major units for `currency` (12.5 is $12.50)
27
- * and fractions for `percent` (0.25 is 25%).
28
- */
29
- interface KpiWidgetData {
30
- readonly kind: 'KPI';
31
- readonly value: number;
32
- /** The same measure for the previous period; null or absent for none. */
33
- readonly previous?: number | null;
34
- readonly format: ValueFormat;
35
- /** ISO 4217 code for `currency`, e.g. "USD". Defaults to USD. */
36
- readonly currency?: string;
37
- readonly label: string;
38
- /** Short context under the value, e.g. "Incl. shipping and tax". */
39
- readonly hint?: string;
40
- /** Whether a rise is good news (default true). Colors the change. */
41
- readonly higherIsBetter?: boolean;
42
- }
43
- /** A value against a maximum, drawn as a meter. */
44
- interface GaugeWidgetData {
45
- readonly kind: 'GAUGE';
46
- readonly value: number;
47
- readonly max: number;
48
- readonly label: string;
49
- }
50
- interface TableColumn {
51
- readonly label: string;
52
- /** Right-aligned, tabular figures. */
53
- readonly numeric?: boolean;
54
- }
55
- interface TableCell {
56
- readonly text: string;
57
- /** Renders the cell as a link (http, https, mailto or relative only). */
58
- readonly href?: string;
59
- /**
60
- * What the card's table controls (and the detail view) sort and search
61
- * on, when `text` is formatted for reading: a timestamp behind "3 days
62
- * ago", a rating behind stars, an amount behind "$1,234.00". Default:
63
- * `text`.
64
- */
65
- readonly value?: string | number;
66
- }
67
- interface TableWidgetData {
68
- readonly kind: 'TABLE';
69
- readonly columns: readonly TableColumn[];
70
- /** One array of cells per row, each as long as `columns`. */
71
- readonly rows: readonly (readonly TableCell[])[];
72
- /** A note under the table, e.g. "and 12 more". */
73
- readonly footer?: string;
74
- }
75
- interface BarListItem {
76
- readonly label: string;
77
- readonly value: number;
78
- /** Text shown beside the bar; defaults to the formatted value. */
79
- readonly display?: string;
80
- }
81
- /**
82
- * Labelled horizontal bars, e.g. ratings or purchases by country. Bars are a
83
- * share of `total` when it is given, otherwise of the largest value.
84
- */
85
- interface BarListWidgetData {
86
- readonly kind: 'BAR_LIST';
87
- readonly items: readonly BarListItem[];
88
- readonly total?: number;
89
- }
90
- interface AlertListItem {
91
- readonly title: string;
92
- /** Renders the title as a link (http, https, mailto or relative only). */
93
- readonly href?: string;
94
- /** A small square image (http, https or relative only). */
95
- readonly thumbnailUrl?: string;
96
- /** The short figure on the right, e.g. "2 left" or "31 views". */
97
- readonly valueLabel: string;
98
- /** A secondary line under the title. */
99
- readonly detail?: string;
100
- }
101
- /**
102
- * Things that need attention (low stock, stale listings...). `items` may be
103
- * the first few of `total`; the renderer says "and N more".
104
- */
105
- interface AlertListWidgetData {
106
- readonly kind: 'ALERT_LIST';
107
- readonly items: readonly AlertListItem[];
108
- readonly total: number;
109
- /** Shown when there is nothing to flag, e.g. "Nothing low on stock." */
110
- readonly emptyText: string;
111
- }
112
- interface GraphPoint {
113
- /** The category or date label on the x axis, e.g. "Oct 2026". */
114
- readonly label: string;
115
- readonly value: number;
116
- }
117
- interface GraphSeries {
118
- readonly name: string;
119
- readonly points: readonly GraphPoint[];
120
- }
121
- /** The most series a graph may carry: the categorical palette's eight slots. */
122
- declare const MAX_GRAPH_SERIES = 8;
123
- /**
124
- * A bar (grouped for several series) or line chart. All series share the
125
- * x labels of the first series, by position, and one y axis.
126
- */
127
- interface GraphWidgetData {
128
- readonly kind: 'GRAPH';
129
- readonly series: readonly GraphSeries[];
130
- readonly chartType: 'bar' | 'line';
131
- readonly valueFormat: ValueFormat;
132
- /** ISO 4217 code for `currency`. Defaults to USD. */
133
- readonly currency?: string;
134
- /** Heading for the label column in the table view, e.g. "Month". */
135
- readonly xLabel?: string;
136
- }
137
- /** Any widget's data, discriminated by `kind`. */
138
- type WidgetData = TextWidgetData | KpiWidgetData | GaugeWidgetData | TableWidgetData | BarListWidgetData | AlertListWidgetData | GraphWidgetData;
139
- /** The payload type for one kind, e.g. `WidgetDataOf<'KPI'>`. */
140
- type WidgetDataOf<K extends WidgetKind> = Extract<WidgetData, {
141
- kind: K;
142
- }>;
143
- /**
144
- * A widget with nothing to show yet, with the reason as text ("No orders
145
- * synced yet."). Any widget may return this instead of its kind's payload.
146
- */
147
- interface WidgetEmptyState {
148
- readonly empty: true;
149
- readonly text: string;
150
- }
151
- /** What a provider or API returns for a widget: its data, or an empty state. */
152
- type WidgetPayload = WidgetData | WidgetEmptyState;
153
- /** The default text for an empty widget. */
154
- declare const DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
155
- /** Builds an empty state. */
156
- declare function emptyWidget(text?: string): WidgetEmptyState;
157
- declare function isWidgetEmptyState(payload: WidgetPayload): payload is WidgetEmptyState;
158
- /**
159
- * The empty text for a payload with nothing in it (a table with no rows, a
160
- * list with no items, a graph with no points), or null when it has content.
161
- * TEXT, KPI and GAUGE always have content.
162
- */
163
- declare function emptyTextFor(payload: WidgetPayload): string | null;
164
-
165
- /**
166
- * Number formatting via `Intl`, with an injectable locale (undefined uses
167
- * the runtime's default), plus KPI period-over-period deltas.
168
- */
169
-
170
- type Locale = string | readonly string[] | undefined;
171
- interface FormatOptions {
172
- readonly locale?: Locale;
173
- /** ISO 4217 code for `currency`. Default "USD". */
174
- readonly currency?: string;
175
- /** "1.2K" style, for axis ticks. */
176
- readonly compact?: boolean;
177
- }
178
- declare const DEFAULT_CURRENCY = "USD";
179
- /**
180
- * Formats `value` as a count ("1,284"), money ("$12.50", major units) or a
181
- * percent ("25%" from 0.25).
182
- */
183
- declare function formatValue(value: number, format: ValueFormat, options?: FormatOptions): string;
184
- /** A plain count, e.g. "1,284". */
185
- declare function formatCount(value: number, locale?: Locale): string;
186
- /** `part` as a whole percentage of `total` (0 when total is 0 or less). */
187
- declare function shareOf(part: number, total: number): number;
188
- /**
189
- * Change from `previous` to `current` in percent (unrounded), or null when
190
- * there is nothing to compare: no previous value, or a previous of 0.
191
- */
192
- declare function percentChange(current: number, previous: number | null | undefined): number | null;
193
- /**
194
- * "+12%", "-8%", "±0%". Below 10% one decimal is kept ("+2.5%"), so small
195
- * changes don't round away.
196
- */
197
- declare function formatPercentChange(change: number, locale?: Locale): string;
198
- type DeltaDirection = 'up' | 'down' | 'flat';
199
- type DeltaSentiment = 'good' | 'bad' | 'neutral';
200
- interface KpiDelta {
201
- /** Percent change, unrounded. */
202
- readonly percent: number;
203
- readonly direction: DeltaDirection;
204
- /** Direction combined with whether higher is better. */
205
- readonly sentiment: DeltaSentiment;
206
- /** "+12%" */
207
- readonly text: string;
208
- /** "+12% vs previous period" */
209
- readonly description: string;
210
- }
211
- interface KpiDeltaOptions {
212
- /** Default true. */
213
- readonly higherIsBetter?: boolean;
214
- readonly locale?: Locale;
215
- /** What the change is against. Default "previous period". */
216
- readonly comparisonLabel?: string;
217
- }
218
- /** The default comparison wording in delta descriptions. */
219
- declare const DEFAULT_COMPARISON_LABEL = "previous period";
220
- /**
221
- * The period-over-period change for a KPI, or null when it can't be worked
222
- * out (no previous value, or a previous of 0).
223
- */
224
- declare function kpiDelta(current: number, previous: number | null | undefined, options?: KpiDeltaOptions): KpiDelta | null;
225
-
226
- /**
227
- * Runs each widget's data provider and collects the outcome per widget, so
228
- * one failing provider never breaks the rest of the dashboard. The package
229
- * never fetches anything itself: providers are the consumer's functions
230
- * (a Prisma query, an HTTP call, a computed value).
231
- */
232
-
233
- /** The minimum a resolve context carries; add whatever your providers need. */
234
- interface WidgetContext {
235
- /** The viewer's roles. Widgets with `roles` need at least one of them. */
236
- readonly roles?: readonly string[];
237
- }
238
- /** What the resolver hands every provider, after the context and definition. */
239
- interface ProviderOptions {
240
- /**
241
- * Aborted when the widget times out, is refreshed, or the dashboard is torn
242
- * down. Pass it to `fetch` (or any cancellable call) to stop the work; a
243
- * provider that ignores it is still abandoned, just not stopped.
244
- */
245
- readonly signal: AbortSignal;
246
- }
247
- /**
248
- * Loads one widget's payload. May be sync or async; may throw. The third
249
- * argument is optional to use: a provider that takes only `(context,
250
- * definition)` keeps working.
251
- */
252
- type WidgetProvider<C = WidgetContext> = (context: C, definition: WidgetDefinition, options: ProviderOptions) => WidgetPayload | Promise<WidgetPayload>;
253
- /** Providers keyed by widget key. */
254
- type WidgetProviders<C = WidgetContext> = Readonly<Record<string, WidgetProvider<C>>>;
255
- interface OkWidget {
256
- readonly definition: WidgetDefinition;
257
- readonly status: 'ok';
258
- readonly data: WidgetData;
259
- /** When the data was produced (epoch ms), set when a provider resolved it. */
260
- readonly updatedAt?: number;
261
- /** True while cached data is shown and a fresh load is still running. */
262
- readonly stale?: boolean;
263
- }
264
- interface EmptyWidget {
265
- readonly definition: WidgetDefinition;
266
- readonly status: 'empty';
267
- /** Why there is nothing to show, e.g. "No orders synced yet." */
268
- readonly emptyText: string;
269
- }
270
- interface ErrorWidget {
271
- readonly definition: WidgetDefinition;
272
- readonly status: 'error';
273
- /** Names the widget and what failed. */
274
- readonly error: string;
275
- }
276
- /** One widget after its provider ran. */
277
- type ResolvedWidget = OkWidget | EmptyWidget | ErrorWidget;
278
- /** A widget whose data hasn't arrived yet (for client-side loading states). */
279
- interface LoadingWidget {
280
- readonly definition: WidgetDefinition;
281
- readonly status: 'loading';
282
- }
283
- /** Anything a dashboard can render: resolved, or still loading. */
284
- type DashboardWidget = ResolvedWidget | LoadingWidget;
285
- type WidgetStatus = DashboardWidget['status'];
286
- /** A payload the cache holds, with when it was stored (epoch ms). */
287
- interface CachedPayload {
288
- readonly payload: unknown;
289
- readonly storedAt: number;
290
- }
291
- /**
292
- * Consumer-supplied storage for widget payloads (memory, localStorage, a
293
- * database). The package never stores anything itself. Payloads read back
294
- * are validated again, so a corrupt entry cannot break a widget.
295
- */
296
- interface WidgetCache {
297
- get(key: string): CachedPayload | undefined | Promise<CachedPayload | undefined>;
298
- set(key: string, entry: CachedPayload): void | Promise<void>;
299
- }
300
- interface ResolveOptions {
301
- /**
302
- * Validate each provider's payload with `validateWidgetData` (and check
303
- * its kind matches the definition). Default true; turn off only for
304
- * trusted, typed providers.
305
- */
306
- readonly validate?: boolean;
307
- /**
308
- * Give each provider this many milliseconds. A timeout aborts the
309
- * provider's signal and yields an `error` widget naming the key.
310
- */
311
- readonly timeoutMs?: number;
312
- /** Cancels loading; affected widgets resolve as `error` (canceled). */
313
- readonly signal?: AbortSignal;
314
- /** Where to store successful payloads (see `WidgetCache`). */
315
- readonly cache?: WidgetCache;
316
- /** Cache key for a widget. Default: the widget's `key`. */
317
- readonly cacheKey?: (definition: WidgetDefinition) => string;
318
- }
319
- /**
320
- * Turns a payload (typically JSON from an API) into a resolved widget:
321
- * validated against the definition's kind, and marked empty when it has
322
- * nothing to show.
323
- */
324
- declare function resolvePayload(definition: WidgetDefinition, payload: unknown, options?: ResolveOptions): ResolvedWidget;
325
- /** A widget that failed, with the reason. */
326
- declare function failedWidget(definition: WidgetDefinition, error: string): ErrorWidget;
327
- /** Loading placeholders for definitions, before their data arrives. */
328
- declare function loadingWidgets(definitions: readonly WidgetDefinition[]): LoadingWidget[];
329
- /** The cache key for a widget under these options. */
330
- declare function cacheKeyFor(definition: WidgetDefinition, options: ResolveOptions): string;
331
- /**
332
- * Runs one widget's provider, capturing a throw, a timeout, a cancellation or
333
- * a bad payload as an error widget. Never rejects.
334
- */
335
- declare function resolveWidget<C extends WidgetContext>(definition: WidgetDefinition, provider: WidgetProvider<C> | undefined, context: C, options?: ResolveOptions): Promise<ResolvedWidget>;
336
- /**
337
- * The widgets a viewer can see: active, allowed for `roles`, in sort order.
338
- */
339
- declare function widgetsFor(definitions: readonly WidgetDefinition[], roles: readonly string[] | undefined): WidgetDefinition[];
340
- /**
341
- * Resolves every widget the context's roles may see, in parallel. Inactive
342
- * and role-filtered widgets are skipped (and their providers never run).
343
- * Results come back in sort order; a provider that throws, is missing or
344
- * returns a bad payload yields an `error` widget and never rejects.
345
- */
346
- declare function resolveWidgets<C extends WidgetContext>(definitions: readonly WidgetDefinition[], providers: WidgetProviders<C>, context: C, options?: ResolveOptions): Promise<ResolvedWidget[]>;
347
-
348
- /**
349
- * A small Result type. Functions in this package return a Result rather than
350
- * throwing, so the caller has to look at the outcome. `throw` is reserved for
351
- * programmer error.
352
- */
353
- interface Ok<T> {
354
- readonly ok: true;
355
- readonly value: T;
356
- }
357
- interface Err {
358
- readonly ok: false;
359
- /** Specific enough to act on: names the field, the widget and the problem. */
360
- readonly error: string;
361
- }
362
- type Result<T> = Ok<T> | Err;
363
- declare function ok<T>(value: T): Ok<T>;
364
- declare function err(error: string): Err;
365
- /** Turns an unknown thrown value into a readable message. */
366
- declare function describeUnknownError(cause: unknown): string;
367
-
368
- /**
369
- * Detail view, core part: the data and query types, sorting, filtering and
370
- * paging, query (de)serialisation for URLs, and validation. Pure functions;
371
- * nothing here renders, fetches or reads `location`. See
372
- * `docs/design/detail-view.md`.
373
- */
374
-
375
- /** `client`: the provider returns every row; `server`: it honors the query. */
376
- declare const DETAIL_MODES: readonly ["client", "server"];
377
- type DetailMode = (typeof DETAIL_MODES)[number];
378
- declare const DEFAULT_DETAIL_PAGE_SIZE = 25;
379
- declare const MAX_DETAIL_PAGE_SIZE = 200;
380
- /** The `detail` setting on a widget definition. `true` means all defaults. */
381
- interface WidgetDetailOptions {
382
- /** Heading of the detail view. Default: the widget's title. */
383
- readonly title?: string;
384
- /** Rows per page, 1 to 200. Default 25. */
385
- readonly pageSize?: number;
386
- /** Default `client`. */
387
- readonly mode?: DetailMode;
388
- }
389
- /** `WidgetDetailOptions` with every default filled in. */
390
- interface ResolvedDetailOptions {
391
- readonly title: string;
392
- readonly pageSize: number;
393
- readonly mode: DetailMode;
394
- }
395
- /** The `tableControls` setting on a widget definition. `true` means both. */
396
- interface WidgetTableControls {
397
- /** A search box over every column. Default true. */
398
- readonly search?: boolean;
399
- /** Sortable column headers. Default true. */
400
- readonly sort?: boolean;
401
- }
402
- /** `WidgetTableControls` with every default filled in. */
403
- interface ResolvedTableControls {
404
- readonly search: boolean;
405
- readonly sort: boolean;
406
- }
407
- interface DetailColumn {
408
- /** Stable id used by sort, filters and URLs. Unique within the data. */
409
- readonly key: string;
410
- readonly label: string;
411
- /** Right-aligned, and sorted as numbers when cell values are numbers. */
412
- readonly numeric?: boolean;
413
- /** Default true. */
414
- readonly sortable?: boolean;
415
- /** Default false: gets its own filter input when true. */
416
- readonly filterable?: boolean;
417
- }
418
- interface DetailCell {
419
- /** What is shown. */
420
- readonly text: string;
421
- /** What sorts and filters. Default: `text`. */
422
- readonly value?: string | number;
423
- /** Renders the cell as a link (http, https, mailto or relative only). */
424
- readonly href?: string;
425
- }
426
- interface DetailData {
427
- readonly columns: readonly DetailColumn[];
428
- /** One array of cells per row, each as long as `columns`. */
429
- readonly rows: readonly (readonly DetailCell[])[];
430
- /** Server mode: rows matching the query across all pages. */
431
- readonly totalRows?: number;
432
- }
433
- type SortDirection = 'asc' | 'desc';
434
- interface DetailSort {
435
- readonly column: string;
436
- readonly direction: SortDirection;
437
- }
438
- interface DetailQuery {
439
- /** Free text, matched against every column. */
440
- readonly search?: string;
441
- /** Per-column text, keyed by column key. Blank entries are ignored. */
442
- readonly filters?: Readonly<Record<string, string>>;
443
- readonly sort?: DetailSort;
444
- /** 1-based. */
445
- readonly page: number;
446
- readonly pageSize: number;
447
- }
448
- /** What a detail provider receives beyond the usual arguments. */
449
- interface DetailProviderOptions extends ProviderOptions {
450
- readonly query: DetailQuery;
451
- }
452
- /**
453
- * Loads the full data for one widget's detail view. In client mode it may
454
- * ignore `options.query`; in server mode it returns one page and `totalRows`.
455
- */
456
- type DetailProvider<C = WidgetContext> = (context: C, definition: WidgetDefinition, options: DetailProviderOptions) => DetailData | Promise<DetailData>;
457
- /** Detail providers keyed by widget key. */
458
- type DetailProviders<C = WidgetContext> = Readonly<Record<string, DetailProvider<C>>>;
459
- interface DetailPage {
460
- /** The rows of the requested page (after filtering and sorting). */
461
- readonly rows: readonly (readonly DetailCell[])[];
462
- /** Rows matching the query, across all pages. */
463
- readonly totalRows: number;
464
- /** The page shown: the requested one, clamped into range. */
465
- readonly page: number;
466
- readonly pageCount: number;
467
- }
468
- /** Reads `definition.detail` with defaults; undefined when there is none. */
469
- declare function resolveDetailOptions(definition: Pick<WidgetDefinition, 'title' | 'detail'>): ResolvedDetailOptions | undefined;
470
- /**
471
- * Reads `definition.tableControls` with defaults; undefined when the widget
472
- * has none (or both are turned off).
473
- */
474
- declare function resolveTableControls(definition: Pick<WidgetDefinition, 'tableControls'>): ResolvedTableControls | undefined;
475
- /** The first page, unsorted and unfiltered. */
476
- declare function defaultDetailQuery(pageSize?: number): DetailQuery;
477
- /**
478
- * Filters, sorts and pages `data` in memory (client mode). Matching is
479
- * case- and accent-insensitive substring matching on each cell's `value`
480
- * (else `text`). Sorting is stable, locale-aware (`locale`, default the
481
- * runtime's) and numeric for number values. `page` is clamped into range.
482
- * Fails, naming the column, when the query names a column that does not
483
- * exist or cannot be sorted.
484
- */
485
- declare function queryRows(data: DetailData, query: DetailQuery, locale?: string): Result<DetailPage>;
486
- /**
487
- * The query as a URL query string without the leading `?`: `q` (search),
488
- * `sort` (`column:asc|desc`), `page` and `f.<column>` filters. `pageSize` is
489
- * not included: the widget definition owns it. Defaults are left out.
490
- */
491
- declare function serializeDetailQuery(query: DetailQuery): string;
492
- /**
493
- * Reads a query string (with or without the leading `?`) written by
494
- * `serializeDetailQuery`. Unknown parameters are ignored, so it can share a
495
- * URL with the rest of an app. A malformed value fails with a message naming
496
- * the parameter. When `data` is given, column keys are checked against it.
497
- */
498
- declare function parseDetailQuery(queryString: string, pageSize?: number, data?: Pick<DetailData, 'columns'>): Result<DetailQuery>;
499
- /**
500
- * Checks data from a detail provider: unique non-empty column keys, labels,
501
- * every row as long as `columns`, cell `text` strings, `value` a string or
502
- * finite number, safe `href`s, and `totalRows` a non-negative integer.
503
- * Reports every problem, naming the column or row and cell.
504
- */
505
- declare function validateDetailData(data: unknown, subject?: string): Result<DetailData>;
506
- /**
507
- * A TABLE's columns and rows as detail data (column keys `c0`, `c1`, ...),
508
- * footer ignored, so the table can be searched and sorted with `queryRows`.
509
- */
510
- declare function tableDetailData(data: TableWidgetData): DetailData;
511
- /**
512
- * The detail data for a widget whose card payload is already complete, so a
513
- * detail view needs no provider: a TABLE without a `footer` (a footer means
514
- * rows were left out) and a BAR_LIST. Returns undefined for everything else
515
- * (alert lists, gauges, KPIs, text and charts): those need a detail provider.
516
- */
517
- declare function deriveDetailData(data: WidgetData, locale?: Locale): DetailData | undefined;
518
-
519
- /** A size hint for grid layouts; renderers may map it to column spans. */
520
- declare const WIDGET_SIZES: readonly ["small", "medium", "large", "full"];
521
- type WidgetSize = (typeof WIDGET_SIZES)[number];
522
- /**
523
- * How a widget fills free space in a grid layout. `height` stretches it to
524
- * the height of its row; `width` widens it to take the columns left over in
525
- * its row; `both` does both. Absent, a widget is only as big as its content
526
- * and `defaultSize`.
527
- */
528
- declare const WIDGET_FILLS: readonly ["height", "width", "both"];
529
- type WidgetFill = (typeof WIDGET_FILLS)[number];
530
- /**
531
- * What a widget is, independent of its data. Mirrors Maker Toolkit's
532
- * `widget` table (`widget_key`, `title`, `description`, `view_type`,
533
- * `roles`, `sort_order`, `active`).
534
- */
535
- interface WidgetDefinition {
536
- /** Stable, unique id. Layouts and providers are keyed by it. */
537
- readonly key: string;
538
- readonly title: string;
539
- readonly description?: string;
540
- readonly kind: WidgetKind;
541
- /** Lower comes first. Default 0; ties keep definition order. */
542
- readonly sortOrder?: number;
543
- /** Roles allowed to see it. Absent or empty means everyone. */
544
- readonly roles?: readonly string[];
545
- /** False hides it from every user. Default true. */
546
- readonly active?: boolean;
547
- readonly defaultSize?: WidgetSize;
548
- /**
549
- * Share of the row the widget takes, in twelfths: an integer from 2 to 12
550
- * (6 is half the row, 12 the whole row). When any widget in a grid sets
551
- * it, the grid becomes 12 columns wide and widgets without a `width` use
552
- * the one their `defaultSize` implies (see `widthForSize`). Narrow grids
553
- * give widgets more room automatically.
554
- */
555
- readonly width?: number;
556
- /**
557
- * Gives the widget a detail view: a full list with sorting, filtering and
558
- * paging. `true` uses defaults. The data comes from a detail provider.
559
- */
560
- readonly detail?: boolean | WidgetDetailOptions;
561
- /**
562
- * TABLE widgets only: a search box and sortable column headers in the card
563
- * itself, working on the rows the card holds. `true` turns on both. With a
564
- * `footer` (rows were left out) they only see the rows shown; use `detail`
565
- * for the full list.
566
- */
567
- readonly tableControls?: boolean | WidgetTableControls;
568
- /** Fill free space in the row (height, width or both). Default: none. */
569
- readonly fill?: WidgetFill;
570
- /**
571
- * Keeps viewers from rearranging this widget. `true` locks everything:
572
- * move, hide and minimize. Use an object to lock only some; fields left out
573
- * are not locked.
574
- */
575
- readonly locked?: boolean | WidgetLock;
576
- /**
577
- * The widget's home page in a paged dashboard: a page key or title. A
578
- * widget no page lists yet (and a widget locked against moving) shows here;
579
- * without it, on the first page. Ignored when the layout has no pages.
580
- */
581
- readonly page?: string;
582
- }
583
- /** What a viewer may not do to a locked widget. */
584
- interface WidgetLock {
585
- /** Can't be reordered, and holds its place (a pinned slot). */
586
- readonly move?: boolean;
587
- /** Can't be hidden. */
588
- readonly hide?: boolean;
589
- /** Can't be collapsed to its header. */
590
- readonly minimize?: boolean;
591
- }
592
- /** `WidgetLock` with every default filled in. */
593
- type ResolvedWidgetLock = Required<WidgetLock>;
594
- /**
595
- * Reads a widget's `locked` setting with defaults: `true` locks move, hide
596
- * and minimize, an object locks the fields it sets to `true`, and anything
597
- * else (absent, `false`) locks nothing.
598
- */
599
- declare function resolveWidgetLock(definition: Pick<WidgetDefinition, 'locked'>): ResolvedWidgetLock;
600
- /**
601
- * Identity helper that keeps the literal `kind` (so typed providers can be
602
- * checked against it) and gives a definition its type without a cast.
603
- */
604
- declare function defineWidget<const D extends WidgetDefinition>(definition: D): D;
605
- /** Definitions sorted by `sortOrder` (stable: ties keep their input order). */
606
- declare function sortDefinitions<D extends WidgetDefinition>(definitions: readonly D[]): D[];
607
- /** Whether a widget is active and visible to someone with these roles. */
608
- declare function isWidgetVisibleTo(definition: WidgetDefinition, roles: readonly string[] | undefined): boolean;
609
-
610
- /**
611
- * A viewer's dashboard customization: order, hidden and minimized widgets.
612
- * Pure functions over a small JSON-serialisable object. Persist it however
613
- * you like (a settings table, localStorage); `parseLayout` tolerates
614
- * anything, so a corrupt saved value never breaks the dashboard.
615
- *
616
- * Generalised from Maker Toolkit's desktop `dashboard-layout.ts`.
617
- */
618
-
619
- /** What layout functions need from a widget definition. */
620
- interface LayoutItem {
621
- readonly key: string;
622
- readonly sortOrder?: number;
623
- /** See `WidgetDefinition.locked`. Pinned widgets hold their slot. */
624
- readonly locked?: boolean | WidgetLock;
625
- /** See `WidgetDefinition.page`: the widget's home page. */
626
- readonly page?: string;
627
- }
628
- interface DashboardLayout {
629
- /** Widget keys in display order. Keys not listed follow, by sortOrder. */
630
- readonly order: readonly string[];
631
- /** Widget keys removed from the dashboard (restorable). */
632
- readonly hidden: readonly string[];
633
- /** Widget keys collapsed to their header. */
634
- readonly minimized: readonly string[];
635
- /**
636
- * Pages of widgets. Absent, the lists above are the one and only page. With
637
- * pages, each page owns its own lists and the top-level lists are left
638
- * empty. See `pages.ts`.
639
- */
640
- readonly pages?: readonly LayoutPage[];
641
- }
642
- /** One page of a paged layout: the same lists as a layout, plus a name. */
643
- interface LayoutPage {
644
- /** Unique within the layout and stable when the page is renamed or moved. */
645
- readonly key: string;
646
- /** Shown in the page bar. At most `MAX_PAGE_TITLE_LENGTH` characters. */
647
- readonly title: string;
648
- readonly order: readonly string[];
649
- readonly hidden: readonly string[];
650
- readonly minimized: readonly string[];
651
- /** Rows this page holds, overriding the dashboard's default. */
652
- readonly maxRows?: number;
653
- }
654
- /** Longest page title; longer saved titles are cut. */
655
- declare const MAX_PAGE_TITLE_LENGTH = 30;
656
- /**
657
- * Most pages `parseLayout` keeps: a safety ceiling against malformed or
658
- * hostile JSON, not a product limit.
659
- */
660
- declare const MAX_PARSED_PAGES = 50;
661
- /** Most rows a page may be given with `maxRows`. */
662
- declare const MAX_PAGE_ROWS = 20;
663
- declare const EMPTY_LAYOUT: DashboardLayout;
664
- /**
665
- * Reads a saved layout: a JSON string or an already-parsed object. Missing,
666
- * corrupt or wrongly typed values give the empty layout (or empty lists for
667
- * the fields that are wrong); non-string and duplicate keys are dropped.
668
- */
669
- declare function parseLayout(raw: unknown): DashboardLayout;
670
- /** The JSON string to persist. */
671
- declare function serializeLayout(layout: DashboardLayout): string;
672
- /**
673
- * The widgets to show, in order: saved keys first (when still present),
674
- * then widgets the layout doesn't know yet, by sortOrder. Hidden widgets
675
- * are left out. Widgets locked against moving are pinned: each keeps its
676
- * place from the definitions (never from `layout.order`) and the others
677
- * rearrange around it.
678
- */
679
- declare function visibleWidgets<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout): T[];
680
- /** Hidden widgets, by sortOrder, for a "restore" menu. */
681
- declare function hiddenWidgets<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout): T[];
682
- /**
683
- * Moves `key` to `toIndex` within the current visible order. Returns the
684
- * same layout object when the key isn't visible, the index is out of range,
685
- * or the move isn't allowed: a pinned widget can't move, and nothing can be
686
- * moved onto a pinned slot. Free widgets keep their relative order around
687
- * pinned ones, so a move that crosses a pinned slot lands on the nearest
688
- * free slot you named.
689
- */
690
- declare function moveWidget<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout, key: string, toIndex: number): DashboardLayout;
691
- /**
692
- * Moves `key` by `offset` places (negative is earlier). Pinned slots are
693
- * stepped over, not counted, and the move is clamped at the first and last
694
- * free slot.
695
- */
696
- declare function moveWidgetBy<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout, key: string, offset: number): DashboardLayout;
697
- /** Moves `key` one place earlier. */
698
- declare function moveWidgetUp<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout, key: string): DashboardLayout;
699
- /** Moves `key` one place later. */
700
- declare function moveWidgetDown<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout, key: string): DashboardLayout;
701
- declare function isHidden(layout: DashboardLayout, key: string): boolean;
702
- declare function isMinimized(layout: DashboardLayout, key: string): boolean;
703
- /** Hides `key`. No-op (same object) if already hidden. */
704
- declare function hideWidget(layout: DashboardLayout, key: string): DashboardLayout;
705
- /** Shows `key` again, expanded. No-op (same object) if not hidden. */
706
- declare function showWidget(layout: DashboardLayout, key: string): DashboardLayout;
707
- /** Hides or shows `key` (showing also clears its minimized state). */
708
- declare function toggleHidden(layout: DashboardLayout, key: string): DashboardLayout;
709
- /** Collapses `key` to its header. No-op (same object) if already minimized. */
710
- declare function minimizeWidget(layout: DashboardLayout, key: string): DashboardLayout;
711
- /** Expands `key`. No-op (same object) if not minimized. */
712
- declare function restoreWidget(layout: DashboardLayout, key: string): DashboardLayout;
713
- /** Minimizes or restores `key`. */
714
- declare function toggleMinimized(layout: DashboardLayout, key: string): DashboardLayout;
715
- /**
716
- * Drops keys for widgets that no longer exist. Only pass the full list of
717
- * definitions (not a role-filtered one), or a viewer's settings for widgets
718
- * they temporarily can't see are lost.
719
- */
720
- declare function pruneLayout<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout): DashboardLayout;
721
- /** Options for `enforceLocks`. */
722
- interface EnforceLocksOptions {
723
- /**
724
- * Skip enforcement. Set it server-side only for a user who may rearrange
725
- * locked widgets (an administrator editing the default layout), never from
726
- * a flag the client sends.
727
- */
728
- readonly overrideLocks?: boolean;
729
- }
730
- /**
731
- * Removes whatever a widget's lock forbids from a layout: a hide-locked key
732
- * leaves `hidden`, a minimize-locked key leaves `minimized`, and a
733
- * move-locked key leaves `order` (a pinned widget's place comes from the
734
- * definitions). Idempotent; returns the same object when nothing changes.
735
- * Run it on a layout a client sends before saving it.
736
- */
737
- declare function enforceLocks<T extends LayoutItem>(definitions: readonly T[], layout: DashboardLayout, options?: EnforceLocksOptions): DashboardLayout;
738
-
739
- /** Axis maths for charts: clean domains and tick values. Pure. */
740
- /** The smallest 1, 2 or 5 × 10^n that is at least `value` (1 for 0 or less). */
741
- declare function niceStep(value: number): number;
742
- interface NiceDomain {
743
- readonly min: number;
744
- readonly max: number;
745
- /** From min to max inclusive, evenly spaced on a clean step. */
746
- readonly ticks: readonly number[];
747
- }
748
- /**
749
- * A y-axis domain that always includes 0 (bars grow from a single
750
- * baseline), rounded out to a clean step, with about `tickCount` intervals.
751
- */
752
- declare function niceDomain(values: readonly number[], tickCount?: number): NiceDomain;
753
- /**
754
- * Show every `n`th x label so at most `maxLabels` appear (always at least 1).
755
- */
756
- declare function labelStride(count: number, maxLabels?: number): number;
757
-
758
- export { type KpiDeltaOptions as $, type AlertListWidgetData as A, type BarListWidgetData as B, type CachedPayload as C, type DashboardWidget as D, type DetailCell as E, type DetailColumn as F, type GraphWidgetData as G, type DetailMode as H, type DetailPage as I, type DetailProvider as J, type KpiWidgetData as K, type Locale as L, type DetailProviders as M, type NiceDomain as N, type DetailSort as O, EMPTY_LAYOUT as P, type EmptyWidget as Q, type ResolvedTableControls as R, type EnforceLocksOptions as S, type TableWidgetData as T, type Err as U, type ErrorWidget as V, type WidgetSize as W, type FormatOptions as X, type GraphPoint as Y, type GraphSeries as Z, type KpiDelta as _, type WidgetFill as a, queryRows as a$, type LoadingWidget as a0, MAX_DETAIL_PAGE_SIZE as a1, MAX_GRAPH_SERIES as a2, MAX_PAGE_ROWS as a3, MAX_PAGE_TITLE_LENGTH as a4, MAX_PARSED_PAGES as a5, type Ok as a6, type OkWidget as a7, type ProviderOptions as a8, type ResolvedDetailOptions as a9, err as aA, failedWidget as aB, formatCount as aC, formatPercentChange as aD, formatValue as aE, hiddenWidgets as aF, hideWidget as aG, isHidden as aH, isMinimized as aI, isWidgetEmptyState as aJ, isWidgetKind as aK, isWidgetVisibleTo as aL, kpiDelta as aM, labelStride as aN, loadingWidgets as aO, minimizeWidget as aP, moveWidget as aQ, moveWidgetBy as aR, moveWidgetDown as aS, moveWidgetUp as aT, niceDomain as aU, niceStep as aV, ok as aW, parseDetailQuery as aX, parseLayout as aY, percentChange as aZ, pruneLayout as a_, type ResolvedWidget as aa, type ResolvedWidgetLock as ab, type SortDirection as ac, type TableCell as ad, type TableColumn as ae, VALUE_FORMATS as af, type ValueFormat as ag, WIDGET_FILLS as ah, WIDGET_KINDS as ai, WIDGET_SIZES as aj, type WidgetCache as ak, type WidgetDataOf as al, type WidgetDetailOptions as am, type WidgetEmptyState as an, type WidgetLock as ao, type WidgetProvider as ap, type WidgetStatus as aq, type WidgetTableControls as ar, cacheKeyFor as as, defaultDetailQuery as at, defineWidget as au, deriveDetailData as av, describeUnknownError as aw, emptyTextFor as ax, emptyWidget as ay, enforceLocks as az, type WidgetDefinition as b, resolveDetailOptions as b0, resolvePayload as b1, resolveTableControls as b2, resolveWidget as b3, resolveWidgetLock as b4, resolveWidgets as b5, restoreWidget as b6, serializeDetailQuery as b7, serializeLayout as b8, shareOf as b9, showWidget as ba, sortDefinitions as bb, tableDetailData as bc, toggleHidden as bd, toggleMinimized as be, validateDetailData as bf, visibleWidgets as bg, widgetsFor as bh, type DetailProviderOptions as c, type DetailData as d, type DetailQuery as e, type DashboardLayout as f, type GaugeWidgetData as g, type TextWidgetData as h, type WidgetData as i, type ResolveOptions as j, type WidgetContext as k, type WidgetProviders as l, type LayoutItem as m, type Result as n, type LayoutPage as o, type WidgetKind as p, type WidgetPayload as q, type AlertListItem as r, type BarListItem as s, DEFAULT_COMPARISON_LABEL as t, DEFAULT_CURRENCY as u, DEFAULT_DETAIL_PAGE_SIZE as v, DEFAULT_EMPTY_TEXT as w, DETAIL_MODES as x, type DeltaDirection as y, type DeltaSentiment as z };