@richardmcquiston01/dashboard-widgets-toolkit 0.1.0 → 0.4.0
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/README.md +113 -8
- package/dist/core.cjs +690 -64
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +87 -18
- package/dist/core.d.ts +87 -18
- package/dist/core.js +666 -65
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +690 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +666 -65
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1594 -72
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +164 -14
- package/dist/react.d.ts +164 -14
- package/dist/react.js +1592 -74
- package/dist/react.js.map +1 -1
- package/dist/{scale-WlRozDCf.d.cts → scale-dWDGE1zW.d.cts} +327 -108
- package/dist/{scale-WlRozDCf.d.ts → scale-dWDGE1zW.d.ts} +327 -108
- package/dist/styles.css +156 -6
- package/package.json +1 -1
package/dist/core.cjs
CHANGED
|
@@ -85,6 +85,7 @@ function fromLegacyDefinition(legacy) {
|
|
|
85
85
|
|
|
86
86
|
// src/core/definition.ts
|
|
87
87
|
var WIDGET_SIZES = ["small", "medium", "large", "full"];
|
|
88
|
+
var WIDGET_FILLS = ["height", "width", "both"];
|
|
88
89
|
function defineWidget(definition) {
|
|
89
90
|
return definition;
|
|
90
91
|
}
|
|
@@ -191,6 +192,444 @@ function kpiDelta(current, previous, options = {}) {
|
|
|
191
192
|
};
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
// src/core/result.ts
|
|
196
|
+
function ok(value) {
|
|
197
|
+
return { ok: true, value };
|
|
198
|
+
}
|
|
199
|
+
function err(error) {
|
|
200
|
+
return { ok: false, error };
|
|
201
|
+
}
|
|
202
|
+
function describeUnknownError(cause) {
|
|
203
|
+
if (cause instanceof Error) {
|
|
204
|
+
return cause.message === "" ? cause.name : cause.message;
|
|
205
|
+
}
|
|
206
|
+
if (typeof cause === "string") {
|
|
207
|
+
return cause;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const json = JSON.stringify(cause);
|
|
211
|
+
return json ?? String(cause);
|
|
212
|
+
} catch {
|
|
213
|
+
return String(cause);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/core/url.ts
|
|
218
|
+
var SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i;
|
|
219
|
+
var IGNORED_IN_SCHEME = /[\u0000- \u007f]/g;
|
|
220
|
+
function schemeOf(url) {
|
|
221
|
+
const match = SCHEME_PATTERN.exec(
|
|
222
|
+
url.replace(IGNORED_IN_SCHEME, "")
|
|
223
|
+
);
|
|
224
|
+
return match === null ? null : (match[1] ?? "").toLowerCase();
|
|
225
|
+
}
|
|
226
|
+
function isSafeHref(url) {
|
|
227
|
+
if (url.trim() === "") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const scheme = schemeOf(url);
|
|
231
|
+
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
232
|
+
}
|
|
233
|
+
function isSafeImageUrl(url) {
|
|
234
|
+
if (url.trim() === "") {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const scheme = schemeOf(url);
|
|
238
|
+
return scheme === null || scheme === "http" || scheme === "https";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/core/detail.ts
|
|
242
|
+
var DETAIL_MODES = ["client", "server"];
|
|
243
|
+
var DEFAULT_DETAIL_PAGE_SIZE = 25;
|
|
244
|
+
var MAX_DETAIL_PAGE_SIZE = 200;
|
|
245
|
+
function resolveDetailOptions(definition) {
|
|
246
|
+
const detail = definition.detail;
|
|
247
|
+
if (detail === void 0 || detail === false) return void 0;
|
|
248
|
+
const options = detail === true ? {} : detail;
|
|
249
|
+
return {
|
|
250
|
+
title: options.title ?? definition.title,
|
|
251
|
+
pageSize: options.pageSize ?? DEFAULT_DETAIL_PAGE_SIZE,
|
|
252
|
+
mode: options.mode ?? "client"
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function defaultDetailQuery(pageSize = DEFAULT_DETAIL_PAGE_SIZE) {
|
|
256
|
+
return { page: 1, pageSize };
|
|
257
|
+
}
|
|
258
|
+
function fold(text) {
|
|
259
|
+
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
260
|
+
}
|
|
261
|
+
function cellValue(cell) {
|
|
262
|
+
return cell.value ?? cell.text;
|
|
263
|
+
}
|
|
264
|
+
function cellMatches(cell, needle) {
|
|
265
|
+
return fold(String(cellValue(cell))).includes(needle);
|
|
266
|
+
}
|
|
267
|
+
function compareCells(a, b, collator) {
|
|
268
|
+
const left = cellValue(a);
|
|
269
|
+
const right = cellValue(b);
|
|
270
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
271
|
+
return left - right;
|
|
272
|
+
}
|
|
273
|
+
if (typeof left === "number") return -1;
|
|
274
|
+
if (typeof right === "number") return 1;
|
|
275
|
+
return collator.compare(left, right);
|
|
276
|
+
}
|
|
277
|
+
function queryRows(data, query, locale) {
|
|
278
|
+
const indexOf = new Map(
|
|
279
|
+
data.columns.map((column, index) => [column.key, index])
|
|
280
|
+
);
|
|
281
|
+
const filterEntries = [];
|
|
282
|
+
for (const [key, text] of Object.entries(query.filters ?? {})) {
|
|
283
|
+
const index = indexOf.get(key);
|
|
284
|
+
if (index === void 0) {
|
|
285
|
+
return err(
|
|
286
|
+
`Detail query: filter column "${key}" does not exist. Columns: ${listKeys(data)}.`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const needle = fold(text.trim());
|
|
290
|
+
if (needle !== "") filterEntries.push({ index, needle });
|
|
291
|
+
}
|
|
292
|
+
let sortIndex = -1;
|
|
293
|
+
if (query.sort !== void 0) {
|
|
294
|
+
const found = indexOf.get(query.sort.column);
|
|
295
|
+
const column = found === void 0 ? void 0 : data.columns[found];
|
|
296
|
+
if (found === void 0 || column === void 0) {
|
|
297
|
+
return err(
|
|
298
|
+
`Detail query: sort column "${query.sort.column}" does not exist. Columns: ${listKeys(data)}.`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (column.sortable === false) {
|
|
302
|
+
return err(
|
|
303
|
+
`Detail query: column "${column.key}" is not sortable (sortable: false).`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
sortIndex = found;
|
|
307
|
+
}
|
|
308
|
+
const search = fold((query.search ?? "").trim());
|
|
309
|
+
let rows = data.rows.filter(
|
|
310
|
+
(row) => filterEntries.every(({ index, needle }) => {
|
|
311
|
+
const cell = row[index];
|
|
312
|
+
return cell !== void 0 && cellMatches(cell, needle);
|
|
313
|
+
}) && (search === "" || row.some((cell) => cellMatches(cell, search)))
|
|
314
|
+
);
|
|
315
|
+
if (query.sort !== void 0 && sortIndex >= 0) {
|
|
316
|
+
const collator = new Intl.Collator(locale, {
|
|
317
|
+
numeric: true,
|
|
318
|
+
sensitivity: "base"
|
|
319
|
+
});
|
|
320
|
+
const sign = query.sort.direction === "desc" ? -1 : 1;
|
|
321
|
+
rows = rows.map((row, position) => ({ row, position })).sort((a, b) => {
|
|
322
|
+
const left = a.row[sortIndex];
|
|
323
|
+
const right = b.row[sortIndex];
|
|
324
|
+
const order = left === void 0 || right === void 0 ? 0 : sign * compareCells(left, right, collator);
|
|
325
|
+
return order || a.position - b.position;
|
|
326
|
+
}).map(({ row }) => row);
|
|
327
|
+
}
|
|
328
|
+
const pageSize = Math.max(1, Math.floor(query.pageSize));
|
|
329
|
+
const pageCount = Math.max(1, Math.ceil(rows.length / pageSize));
|
|
330
|
+
const page = Math.min(Math.max(1, Math.floor(query.page)), pageCount);
|
|
331
|
+
return ok({
|
|
332
|
+
rows: rows.slice((page - 1) * pageSize, page * pageSize),
|
|
333
|
+
totalRows: rows.length,
|
|
334
|
+
page,
|
|
335
|
+
pageCount
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function listKeys(data) {
|
|
339
|
+
return data.columns.map((column) => `"${column.key}"`).join(", ");
|
|
340
|
+
}
|
|
341
|
+
var FILTER_PREFIX = "f.";
|
|
342
|
+
function serializeDetailQuery(query) {
|
|
343
|
+
const parts = [];
|
|
344
|
+
const add = (key, value) => {
|
|
345
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
346
|
+
};
|
|
347
|
+
if (query.search !== void 0 && query.search !== "") {
|
|
348
|
+
add("q", query.search);
|
|
349
|
+
}
|
|
350
|
+
if (query.sort !== void 0) {
|
|
351
|
+
add("sort", `${query.sort.column}:${query.sort.direction}`);
|
|
352
|
+
}
|
|
353
|
+
for (const [column, text] of Object.entries(query.filters ?? {})) {
|
|
354
|
+
if (text !== "") add(`${FILTER_PREFIX}${column}`, text);
|
|
355
|
+
}
|
|
356
|
+
if (query.page > 1) add("page", String(query.page));
|
|
357
|
+
return parts.join("&");
|
|
358
|
+
}
|
|
359
|
+
function parseDetailQuery(queryString, pageSize = DEFAULT_DETAIL_PAGE_SIZE, data) {
|
|
360
|
+
let search;
|
|
361
|
+
let sort;
|
|
362
|
+
let page = 1;
|
|
363
|
+
const filters = {};
|
|
364
|
+
const columnKeys = data === void 0 ? void 0 : new Set(data.columns.map((column) => column.key));
|
|
365
|
+
const text = queryString.startsWith("?") ? queryString.slice(1) : queryString;
|
|
366
|
+
for (const pair of text.split("&")) {
|
|
367
|
+
if (pair === "") continue;
|
|
368
|
+
const equals = pair.indexOf("=");
|
|
369
|
+
const rawKey = equals < 0 ? pair : pair.slice(0, equals);
|
|
370
|
+
const rawValue = equals < 0 ? "" : pair.slice(equals + 1);
|
|
371
|
+
let key;
|
|
372
|
+
let value;
|
|
373
|
+
try {
|
|
374
|
+
key = decodeURIComponent(rawKey.replace(/\+/g, " "));
|
|
375
|
+
value = decodeURIComponent(rawValue.replace(/\+/g, " "));
|
|
376
|
+
} catch {
|
|
377
|
+
return err(
|
|
378
|
+
`Detail query: "${pair}" is not valid percent-encoding in the URL.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (key === "q") {
|
|
382
|
+
search = value;
|
|
383
|
+
} else if (key === "page") {
|
|
384
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
385
|
+
return err(
|
|
386
|
+
`Detail query: page must be a whole number from 1, got "${value}".`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
page = Number(value);
|
|
390
|
+
} else if (key === "sort") {
|
|
391
|
+
const colon = value.lastIndexOf(":");
|
|
392
|
+
const column = colon < 0 ? value : value.slice(0, colon);
|
|
393
|
+
const direction = colon < 0 ? "asc" : value.slice(colon + 1);
|
|
394
|
+
if (column === "" || direction !== "asc" && direction !== "desc") {
|
|
395
|
+
return err(
|
|
396
|
+
`Detail query: sort must look like "column:asc" or "column:desc", got "${value}".`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (columnKeys !== void 0 && !columnKeys.has(column)) {
|
|
400
|
+
return err(`Detail query: sort column "${column}" does not exist.`);
|
|
401
|
+
}
|
|
402
|
+
sort = { column, direction };
|
|
403
|
+
} else if (key.startsWith(FILTER_PREFIX)) {
|
|
404
|
+
const column = key.slice(FILTER_PREFIX.length);
|
|
405
|
+
if (columnKeys !== void 0 && !columnKeys.has(column)) {
|
|
406
|
+
return err(`Detail query: filter column "${column}" does not exist.`);
|
|
407
|
+
}
|
|
408
|
+
if (value !== "") filters[column] = value;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return ok({
|
|
412
|
+
...search === void 0 || search === "" ? {} : { search },
|
|
413
|
+
...Object.keys(filters).length === 0 ? {} : { filters },
|
|
414
|
+
...sort === void 0 ? {} : { sort },
|
|
415
|
+
page,
|
|
416
|
+
pageSize
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function describeType(value) {
|
|
420
|
+
if (value === null) return "null";
|
|
421
|
+
if (Array.isArray(value)) return "array";
|
|
422
|
+
return typeof value;
|
|
423
|
+
}
|
|
424
|
+
function validateDetailData(data, subject = "Detail data") {
|
|
425
|
+
const problems = [];
|
|
426
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
427
|
+
return err(`${subject}: must be an object, got ${describeType(data)}.`);
|
|
428
|
+
}
|
|
429
|
+
const record = data;
|
|
430
|
+
const columns = record["columns"];
|
|
431
|
+
const rows = record["rows"];
|
|
432
|
+
const keys = /* @__PURE__ */ new Set();
|
|
433
|
+
let columnCount = 0;
|
|
434
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
435
|
+
problems.push(
|
|
436
|
+
`columns must be a non-empty array, got ${describeType(columns)}.`
|
|
437
|
+
);
|
|
438
|
+
} else {
|
|
439
|
+
columnCount = columns.length;
|
|
440
|
+
columns.forEach((column, index) => {
|
|
441
|
+
const at = `columns[${index}]`;
|
|
442
|
+
if (typeof column !== "object" || column === null) {
|
|
443
|
+
problems.push(`${at} must be an object, got ${describeType(column)}.`);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const fields = column;
|
|
447
|
+
const key = fields["key"];
|
|
448
|
+
if (typeof key !== "string" || key === "") {
|
|
449
|
+
problems.push(`${at}.key must be a non-empty string.`);
|
|
450
|
+
} else if (keys.has(key)) {
|
|
451
|
+
problems.push(`${at}.key "${key}" is used by an earlier column.`);
|
|
452
|
+
} else {
|
|
453
|
+
keys.add(key);
|
|
454
|
+
}
|
|
455
|
+
if (typeof fields["label"] !== "string") {
|
|
456
|
+
problems.push(
|
|
457
|
+
`${at}.label must be a string, got ${describeType(fields["label"])}.`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
for (const flag of ["numeric", "sortable", "filterable"]) {
|
|
461
|
+
const flagValue = fields[flag];
|
|
462
|
+
if (flagValue !== void 0 && typeof flagValue !== "boolean") {
|
|
463
|
+
problems.push(
|
|
464
|
+
`${at}.${flag} must be a boolean, got ${describeType(flagValue)}.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (!Array.isArray(rows)) {
|
|
471
|
+
problems.push(`rows must be an array, got ${describeType(rows)}.`);
|
|
472
|
+
} else {
|
|
473
|
+
rows.forEach((row, rowIndex) => {
|
|
474
|
+
if (!Array.isArray(row)) {
|
|
475
|
+
problems.push(
|
|
476
|
+
`rows[${rowIndex}] must be an array of cells, got ${describeType(row)}.`
|
|
477
|
+
);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (columnCount > 0 && row.length !== columnCount) {
|
|
481
|
+
problems.push(
|
|
482
|
+
`rows[${rowIndex}] has ${row.length} cells but there are ${columnCount} columns.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
row.forEach((cell, cellIndex) => {
|
|
486
|
+
const at = `rows[${rowIndex}][${cellIndex}]`;
|
|
487
|
+
if (typeof cell !== "object" || cell === null) {
|
|
488
|
+
problems.push(`${at} must be an object, got ${describeType(cell)}.`);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const fields = cell;
|
|
492
|
+
if (typeof fields["text"] !== "string") {
|
|
493
|
+
problems.push(
|
|
494
|
+
`${at}.text must be a string, got ${describeType(fields["text"])}.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const value = fields["value"];
|
|
498
|
+
if (value !== void 0 && typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) {
|
|
499
|
+
problems.push(
|
|
500
|
+
`${at}.value must be a string or a finite number, got ${describeType(value)}.`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const href = fields["href"];
|
|
504
|
+
if (href !== void 0) {
|
|
505
|
+
if (typeof href !== "string") {
|
|
506
|
+
problems.push(
|
|
507
|
+
`${at}.href must be a string, got ${describeType(href)}.`
|
|
508
|
+
);
|
|
509
|
+
} else if (!isSafeHref(href)) {
|
|
510
|
+
problems.push(
|
|
511
|
+
`${at}.href "${href}" is not allowed (only http, https, mailto and relative URLs).`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const totalRows = record["totalRows"];
|
|
519
|
+
if (totalRows !== void 0 && !(typeof totalRows === "number" && Number.isInteger(totalRows) && totalRows >= 0)) {
|
|
520
|
+
problems.push(
|
|
521
|
+
`totalRows must be a non-negative whole number, got ${describeType(totalRows)}.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (problems.length > 0) {
|
|
525
|
+
return err(`${subject}: ${problems.join(" ")}`);
|
|
526
|
+
}
|
|
527
|
+
return ok(data);
|
|
528
|
+
}
|
|
529
|
+
function deriveDetailData(data, locale) {
|
|
530
|
+
if (data.kind === "TABLE") {
|
|
531
|
+
if (data.footer !== void 0) return void 0;
|
|
532
|
+
return {
|
|
533
|
+
columns: data.columns.map((column, index) => ({
|
|
534
|
+
key: `c${index}`,
|
|
535
|
+
label: column.label,
|
|
536
|
+
...column.numeric === void 0 ? {} : { numeric: column.numeric }
|
|
537
|
+
})),
|
|
538
|
+
rows: data.rows
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (data.kind === "BAR_LIST") {
|
|
542
|
+
return {
|
|
543
|
+
columns: [
|
|
544
|
+
{ key: "label", label: "Name" },
|
|
545
|
+
{ key: "value", label: "Value", numeric: true }
|
|
546
|
+
],
|
|
547
|
+
rows: data.items.map((item) => [
|
|
548
|
+
{ text: item.label },
|
|
549
|
+
{
|
|
550
|
+
text: item.display ?? formatValue(item.value, "number", { locale }),
|
|
551
|
+
value: item.value
|
|
552
|
+
}
|
|
553
|
+
])
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return void 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/core/grid.ts
|
|
560
|
+
var WIDTH_COLUMNS = 12;
|
|
561
|
+
var MIN_WIDGET_WIDTH = 2;
|
|
562
|
+
var MAX_WIDGET_WIDTH = 12;
|
|
563
|
+
function isWidgetWidth(value) {
|
|
564
|
+
return typeof value === "number" && Number.isInteger(value) && value >= MIN_WIDGET_WIDTH && value <= MAX_WIDGET_WIDTH;
|
|
565
|
+
}
|
|
566
|
+
function widthForSize(size) {
|
|
567
|
+
switch (size) {
|
|
568
|
+
case "small":
|
|
569
|
+
return 3;
|
|
570
|
+
case "large":
|
|
571
|
+
return 6;
|
|
572
|
+
case "full":
|
|
573
|
+
return 12;
|
|
574
|
+
default:
|
|
575
|
+
return 4;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function itemWidth(item) {
|
|
579
|
+
return isWidgetWidth(item.width) ? item.width : widthForSize(item.defaultSize);
|
|
580
|
+
}
|
|
581
|
+
function usesWidthColumns(items) {
|
|
582
|
+
return items.some((item) => item.width !== void 0);
|
|
583
|
+
}
|
|
584
|
+
function fillsWidth(fill) {
|
|
585
|
+
return fill === "width" || fill === "both";
|
|
586
|
+
}
|
|
587
|
+
function fillsHeight(fill) {
|
|
588
|
+
return fill === "height" || fill === "both";
|
|
589
|
+
}
|
|
590
|
+
function baseColumnSpan(size, columns, largeSpan = 2) {
|
|
591
|
+
if (size === "full") return columns;
|
|
592
|
+
if (size === "large") return Math.min(largeSpan, columns);
|
|
593
|
+
return 1;
|
|
594
|
+
}
|
|
595
|
+
function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
596
|
+
return placeAndFill(
|
|
597
|
+
items,
|
|
598
|
+
columns,
|
|
599
|
+
(item, trackCount) => baseColumnSpan(item.defaultSize, trackCount, largeSpan)
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
function fillWidthSpans(items) {
|
|
603
|
+
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
604
|
+
}
|
|
605
|
+
function placeAndFill(items, columns, spanOf) {
|
|
606
|
+
const trackCount = Math.max(1, Math.floor(columns));
|
|
607
|
+
const spans = /* @__PURE__ */ new Map();
|
|
608
|
+
let row = [];
|
|
609
|
+
let used = 0;
|
|
610
|
+
function closeRow() {
|
|
611
|
+
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
612
|
+
const leftover = trackCount - used;
|
|
613
|
+
if (fillers.length > 0) {
|
|
614
|
+
const share = Math.floor(leftover / fillers.length);
|
|
615
|
+
const remainder = leftover % fillers.length;
|
|
616
|
+
fillers.forEach(({ item, span }, index) => {
|
|
617
|
+
spans.set(item.key, span + share + (index < remainder ? 1 : 0));
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
row = [];
|
|
621
|
+
used = 0;
|
|
622
|
+
}
|
|
623
|
+
for (const item of items) {
|
|
624
|
+
const span = Math.min(spanOf(item, trackCount), trackCount);
|
|
625
|
+
if (used + span > trackCount && row.length > 0) closeRow();
|
|
626
|
+
row.push({ item, span });
|
|
627
|
+
used += span;
|
|
628
|
+
}
|
|
629
|
+
closeRow();
|
|
630
|
+
return spans;
|
|
631
|
+
}
|
|
632
|
+
|
|
194
633
|
// src/core/layout.ts
|
|
195
634
|
var EMPTY_LAYOUT = Object.freeze({
|
|
196
635
|
order: Object.freeze([]),
|
|
@@ -333,55 +772,9 @@ function pruneLayout(definitions, layout) {
|
|
|
333
772
|
return next.order.length === layout.order.length && next.hidden.length === layout.hidden.length && next.minimized.length === layout.minimized.length ? layout : next;
|
|
334
773
|
}
|
|
335
774
|
|
|
336
|
-
// src/core/result.ts
|
|
337
|
-
function ok(value) {
|
|
338
|
-
return { ok: true, value };
|
|
339
|
-
}
|
|
340
|
-
function err(error) {
|
|
341
|
-
return { ok: false, error };
|
|
342
|
-
}
|
|
343
|
-
function describeUnknownError(cause) {
|
|
344
|
-
if (cause instanceof Error) {
|
|
345
|
-
return cause.message === "" ? cause.name : cause.message;
|
|
346
|
-
}
|
|
347
|
-
if (typeof cause === "string") {
|
|
348
|
-
return cause;
|
|
349
|
-
}
|
|
350
|
-
try {
|
|
351
|
-
const json = JSON.stringify(cause);
|
|
352
|
-
return json ?? String(cause);
|
|
353
|
-
} catch {
|
|
354
|
-
return String(cause);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// src/core/url.ts
|
|
359
|
-
var SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i;
|
|
360
|
-
var IGNORED_IN_SCHEME = /[\u0000- \u007f]/g;
|
|
361
|
-
function schemeOf(url) {
|
|
362
|
-
const match = SCHEME_PATTERN.exec(
|
|
363
|
-
url.replace(IGNORED_IN_SCHEME, "")
|
|
364
|
-
);
|
|
365
|
-
return match === null ? null : (match[1] ?? "").toLowerCase();
|
|
366
|
-
}
|
|
367
|
-
function isSafeHref(url) {
|
|
368
|
-
if (url.trim() === "") {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
const scheme = schemeOf(url);
|
|
372
|
-
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
373
|
-
}
|
|
374
|
-
function isSafeImageUrl(url) {
|
|
375
|
-
if (url.trim() === "") {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
const scheme = schemeOf(url);
|
|
379
|
-
return scheme === null || scheme === "http" || scheme === "https";
|
|
380
|
-
}
|
|
381
|
-
|
|
382
775
|
// src/core/validate.ts
|
|
383
776
|
var MAX_REPORTED_PROBLEMS = 10;
|
|
384
|
-
function
|
|
777
|
+
function describeType2(value) {
|
|
385
778
|
if (value === null) return "null";
|
|
386
779
|
if (Array.isArray(value)) return "array";
|
|
387
780
|
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
@@ -399,7 +792,7 @@ function requireString(record, field, path, problems) {
|
|
|
399
792
|
const value = record[field];
|
|
400
793
|
if (typeof value !== "string") {
|
|
401
794
|
problems.push(
|
|
402
|
-
`${join(path, field)} must be a string, got ${
|
|
795
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
403
796
|
);
|
|
404
797
|
}
|
|
405
798
|
}
|
|
@@ -412,7 +805,7 @@ function requireNumber(record, field, path, problems) {
|
|
|
412
805
|
const value = record[field];
|
|
413
806
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
414
807
|
problems.push(
|
|
415
|
-
`${join(path, field)} must be a finite number, got ${
|
|
808
|
+
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
416
809
|
);
|
|
417
810
|
}
|
|
418
811
|
}
|
|
@@ -425,7 +818,7 @@ function optionalBoolean(record, field, path, problems) {
|
|
|
425
818
|
const value = record[field];
|
|
426
819
|
if (value !== void 0 && typeof value !== "boolean") {
|
|
427
820
|
problems.push(
|
|
428
|
-
`${join(path, field)} must be a boolean, got ${
|
|
821
|
+
`${join(path, field)} must be a boolean, got ${describeType2(value)}.`
|
|
429
822
|
);
|
|
430
823
|
}
|
|
431
824
|
}
|
|
@@ -434,7 +827,7 @@ function optionalUrl(record, field, path, problems, isSafe, allowed) {
|
|
|
434
827
|
if (value === void 0) return;
|
|
435
828
|
if (typeof value !== "string") {
|
|
436
829
|
problems.push(
|
|
437
|
-
`${join(path, field)} must be a string, got ${
|
|
830
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
438
831
|
);
|
|
439
832
|
} else if (!isSafe(value)) {
|
|
440
833
|
problems.push(
|
|
@@ -446,7 +839,7 @@ function requireArray(record, field, path, problems) {
|
|
|
446
839
|
const value = record[field];
|
|
447
840
|
if (!Array.isArray(value)) {
|
|
448
841
|
problems.push(
|
|
449
|
-
`${join(path, field)} must be an array, got ${
|
|
842
|
+
`${join(path, field)} must be an array, got ${describeType2(value)}.`
|
|
450
843
|
);
|
|
451
844
|
return null;
|
|
452
845
|
}
|
|
@@ -456,7 +849,7 @@ function requireOneOf(record, field, options, path, problems) {
|
|
|
456
849
|
const value = record[field];
|
|
457
850
|
if (typeof value !== "string" || !options.includes(value)) {
|
|
458
851
|
problems.push(
|
|
459
|
-
`${join(path, field)} must be one of ${options.map((o) => `"${o}"`).join(", ")}, got ${typeof value === "string" ? JSON.stringify(value) :
|
|
852
|
+
`${join(path, field)} must be one of ${options.map((o) => `"${o}"`).join(", ")}, got ${typeof value === "string" ? JSON.stringify(value) : describeType2(value)}.`
|
|
460
853
|
);
|
|
461
854
|
}
|
|
462
855
|
}
|
|
@@ -467,7 +860,7 @@ function eachRecord(items, path, problems, check) {
|
|
|
467
860
|
check(item, itemPath);
|
|
468
861
|
} else {
|
|
469
862
|
problems.push(
|
|
470
|
-
`${itemPath} must be an object, got ${
|
|
863
|
+
`${itemPath} must be an object, got ${describeType2(item)}.`
|
|
471
864
|
);
|
|
472
865
|
}
|
|
473
866
|
});
|
|
@@ -513,7 +906,7 @@ function checkPayloadFields(kind, data, problems) {
|
|
|
513
906
|
const rowPath = `rows[${rowIndex}]`;
|
|
514
907
|
if (!Array.isArray(row)) {
|
|
515
908
|
problems.push(
|
|
516
|
-
`${rowPath} must be an array of cells, got ${
|
|
909
|
+
`${rowPath} must be an array of cells, got ${describeType2(row)}.`
|
|
517
910
|
);
|
|
518
911
|
return;
|
|
519
912
|
}
|
|
@@ -609,6 +1002,25 @@ function checkPayloadFields(kind, data, problems) {
|
|
|
609
1002
|
}
|
|
610
1003
|
}
|
|
611
1004
|
}
|
|
1005
|
+
function checkDetailOption(detail, problems) {
|
|
1006
|
+
if (detail === void 0 || typeof detail === "boolean") return;
|
|
1007
|
+
if (!isRecord(detail)) {
|
|
1008
|
+
problems.push(
|
|
1009
|
+
`detail must be true, false or an options object, got ${describeType2(detail)}.`
|
|
1010
|
+
);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
optionalString(detail, "title", "detail", problems);
|
|
1014
|
+
const pageSize = detail["pageSize"];
|
|
1015
|
+
if (pageSize !== void 0 && !(typeof pageSize === "number" && Number.isInteger(pageSize) && pageSize >= 1 && pageSize <= MAX_DETAIL_PAGE_SIZE)) {
|
|
1016
|
+
problems.push(
|
|
1017
|
+
`detail.pageSize must be a whole number from 1 to ${MAX_DETAIL_PAGE_SIZE}, got ${typeof pageSize === "number" ? String(pageSize) : describeType2(pageSize)}.`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
if (detail["mode"] !== void 0) {
|
|
1021
|
+
requireOneOf(detail, "mode", DETAIL_MODES, "detail", problems);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
612
1024
|
function formatProblems(prefix, problems) {
|
|
613
1025
|
const shown = problems.slice(0, MAX_REPORTED_PROBLEMS);
|
|
614
1026
|
const more = problems.length - shown.length;
|
|
@@ -617,12 +1029,12 @@ function formatProblems(prefix, problems) {
|
|
|
617
1029
|
function validateWidgetData(value, options = {}) {
|
|
618
1030
|
const subject = options.widgetKey === void 0 ? "Widget data" : `Widget ${JSON.stringify(options.widgetKey)}`;
|
|
619
1031
|
if (!isRecord(value)) {
|
|
620
|
-
return err(`${subject}: must be an object, got ${
|
|
1032
|
+
return err(`${subject}: must be an object, got ${describeType2(value)}.`);
|
|
621
1033
|
}
|
|
622
1034
|
if (value["empty"] === true) {
|
|
623
1035
|
if (typeof value["text"] !== "string") {
|
|
624
1036
|
return err(
|
|
625
|
-
`${subject} (empty): text must be a string, got ${
|
|
1037
|
+
`${subject} (empty): text must be a string, got ${describeType2(value["text"])}.`
|
|
626
1038
|
);
|
|
627
1039
|
}
|
|
628
1040
|
return ok(value);
|
|
@@ -630,7 +1042,7 @@ function validateWidgetData(value, options = {}) {
|
|
|
630
1042
|
const kind = value["kind"];
|
|
631
1043
|
if (!isWidgetKind(kind)) {
|
|
632
1044
|
return err(
|
|
633
|
-
`${subject}: kind must be one of ${WIDGET_KINDS.join(", ")} (or the payload must be {"empty": true, "text": ...}), got ${typeof kind === "string" ? JSON.stringify(kind) :
|
|
1045
|
+
`${subject}: kind must be one of ${WIDGET_KINDS.join(", ")} (or the payload must be {"empty": true, "text": ...}), got ${typeof kind === "string" ? JSON.stringify(kind) : describeType2(kind)}.`
|
|
634
1046
|
);
|
|
635
1047
|
}
|
|
636
1048
|
if (options.expectedKind !== void 0 && kind !== options.expectedKind) {
|
|
@@ -648,7 +1060,7 @@ function validateWidgetData(value, options = {}) {
|
|
|
648
1060
|
function validateWidgetDefinition(value) {
|
|
649
1061
|
if (!isRecord(value)) {
|
|
650
1062
|
return err(
|
|
651
|
-
`Widget definition: must be an object, got ${
|
|
1063
|
+
`Widget definition: must be an object, got ${describeType2(value)}.`
|
|
652
1064
|
);
|
|
653
1065
|
}
|
|
654
1066
|
const subject = typeof value["key"] === "string" ? `Widget definition ${JSON.stringify(value["key"])}` : "Widget definition";
|
|
@@ -666,7 +1078,7 @@ function validateWidgetDefinition(value) {
|
|
|
666
1078
|
roles?.forEach((role, index) => {
|
|
667
1079
|
if (typeof role !== "string") {
|
|
668
1080
|
problems.push(
|
|
669
|
-
`roles[${index}] must be a string, got ${
|
|
1081
|
+
`roles[${index}] must be a string, got ${describeType2(role)}.`
|
|
670
1082
|
);
|
|
671
1083
|
}
|
|
672
1084
|
});
|
|
@@ -675,6 +1087,15 @@ function validateWidgetDefinition(value) {
|
|
|
675
1087
|
if (value["defaultSize"] !== void 0) {
|
|
676
1088
|
requireOneOf(value, "defaultSize", WIDGET_SIZES, "", problems);
|
|
677
1089
|
}
|
|
1090
|
+
if (value["width"] !== void 0 && !isWidgetWidth(value["width"])) {
|
|
1091
|
+
problems.push(
|
|
1092
|
+
`width must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof value["width"] === "number" ? String(value["width"]) : describeType2(value["width"])}.`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
checkDetailOption(value["detail"], problems);
|
|
1096
|
+
if (value["fill"] !== void 0) {
|
|
1097
|
+
requireOneOf(value, "fill", WIDGET_FILLS, "", problems);
|
|
1098
|
+
}
|
|
678
1099
|
if (problems.length > 0) {
|
|
679
1100
|
return err(formatProblems(subject, problems));
|
|
680
1101
|
}
|
|
@@ -708,23 +1129,78 @@ function failedWidget(definition, error) {
|
|
|
708
1129
|
function loadingWidgets(definitions) {
|
|
709
1130
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
710
1131
|
}
|
|
1132
|
+
function cacheKeyFor(definition, options) {
|
|
1133
|
+
return options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
1134
|
+
}
|
|
1135
|
+
var LoadInterruption = class extends Error {
|
|
1136
|
+
};
|
|
711
1137
|
async function resolveWidget(definition, provider, context, options = {}) {
|
|
1138
|
+
const quotedKey = JSON.stringify(definition.key);
|
|
712
1139
|
if (provider === void 0) {
|
|
713
1140
|
return failedWidget(
|
|
714
1141
|
definition,
|
|
715
|
-
`Widget ${
|
|
1142
|
+
`Widget ${quotedKey}: no provider is registered for this key.`
|
|
716
1143
|
);
|
|
717
1144
|
}
|
|
1145
|
+
if (options.signal?.aborted === true) {
|
|
1146
|
+
return failedWidget(
|
|
1147
|
+
definition,
|
|
1148
|
+
`Widget ${quotedKey}: loading was canceled before it started.`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
const controller = new AbortController();
|
|
1152
|
+
const onExternalAbort = () => controller.abort(options.signal?.reason);
|
|
1153
|
+
options.signal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
1154
|
+
let timedOut = false;
|
|
1155
|
+
const timeoutMs = options.timeoutMs !== void 0 && options.timeoutMs > 0 ? options.timeoutMs : void 0;
|
|
1156
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1157
|
+
timedOut = true;
|
|
1158
|
+
controller.abort(new Error(`Timed out after ${timeoutMs} ms.`));
|
|
1159
|
+
}, timeoutMs);
|
|
1160
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
1161
|
+
controller.signal.addEventListener(
|
|
1162
|
+
"abort",
|
|
1163
|
+
() => reject(new LoadInterruption()),
|
|
1164
|
+
{ once: true }
|
|
1165
|
+
);
|
|
1166
|
+
});
|
|
718
1167
|
let payload;
|
|
719
1168
|
try {
|
|
720
|
-
payload = await
|
|
1169
|
+
payload = await Promise.race([
|
|
1170
|
+
Promise.resolve().then(() => {
|
|
1171
|
+
if (controller.signal.aborted) throw new LoadInterruption();
|
|
1172
|
+
return provider(context, definition, { signal: controller.signal });
|
|
1173
|
+
}),
|
|
1174
|
+
interrupted
|
|
1175
|
+
]);
|
|
721
1176
|
} catch (cause) {
|
|
1177
|
+
if (cause instanceof LoadInterruption) {
|
|
1178
|
+
return failedWidget(
|
|
1179
|
+
definition,
|
|
1180
|
+
timedOut ? `Widget ${quotedKey}: provider timed out after ${timeoutMs} ms.` : `Widget ${quotedKey}: loading was canceled.`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
722
1183
|
return failedWidget(
|
|
723
1184
|
definition,
|
|
724
|
-
`Widget ${
|
|
1185
|
+
`Widget ${quotedKey}: provider failed: ${describeUnknownError(cause)}`
|
|
725
1186
|
);
|
|
1187
|
+
} finally {
|
|
1188
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1189
|
+
options.signal?.removeEventListener("abort", onExternalAbort);
|
|
1190
|
+
}
|
|
1191
|
+
const resolved = resolvePayload(definition, payload, options);
|
|
1192
|
+
if (resolved.status !== "ok") return resolved;
|
|
1193
|
+
const storedAt = Date.now();
|
|
1194
|
+
if (options.cache !== void 0) {
|
|
1195
|
+
try {
|
|
1196
|
+
await options.cache.set(cacheKeyFor(definition, options), {
|
|
1197
|
+
payload: resolved.data,
|
|
1198
|
+
storedAt
|
|
1199
|
+
});
|
|
1200
|
+
} catch {
|
|
1201
|
+
}
|
|
726
1202
|
}
|
|
727
|
-
return
|
|
1203
|
+
return { ...resolved, updatedAt: storedAt };
|
|
728
1204
|
}
|
|
729
1205
|
function widgetsFor(definitions, roles) {
|
|
730
1206
|
return sortDefinitions(
|
|
@@ -745,6 +1221,131 @@ async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
|
745
1221
|
);
|
|
746
1222
|
}
|
|
747
1223
|
|
|
1224
|
+
// src/core/loader.ts
|
|
1225
|
+
function createWidgetLoader(options) {
|
|
1226
|
+
const { definitions, providers, context, ...resolveOptions } = options;
|
|
1227
|
+
const visible = widgetsFor(definitions, context.roles);
|
|
1228
|
+
let snapshot = loadingWidgets(visible);
|
|
1229
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1230
|
+
const started = /* @__PURE__ */ new Set();
|
|
1231
|
+
const generations = /* @__PURE__ */ new Map();
|
|
1232
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
1233
|
+
let disposed = false;
|
|
1234
|
+
function publish(next) {
|
|
1235
|
+
snapshot = next;
|
|
1236
|
+
for (const listener of [...listeners]) {
|
|
1237
|
+
try {
|
|
1238
|
+
listener();
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
function replace(key, widget) {
|
|
1244
|
+
publish(
|
|
1245
|
+
snapshot.map(
|
|
1246
|
+
(current) => current.definition.key === key ? widget : current
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
function abortAll(reason) {
|
|
1251
|
+
for (const controller of inFlight.values()) controller.abort(reason);
|
|
1252
|
+
inFlight.clear();
|
|
1253
|
+
}
|
|
1254
|
+
resolveOptions.signal?.addEventListener(
|
|
1255
|
+
"abort",
|
|
1256
|
+
() => abortAll(resolveOptions.signal?.reason),
|
|
1257
|
+
{ once: true }
|
|
1258
|
+
);
|
|
1259
|
+
async function run(definition, readCache) {
|
|
1260
|
+
const key = definition.key;
|
|
1261
|
+
const generation = (generations.get(key) ?? 0) + 1;
|
|
1262
|
+
generations.set(key, generation);
|
|
1263
|
+
inFlight.get(key)?.abort(new Error("Superseded by a newer load."));
|
|
1264
|
+
const controller = new AbortController();
|
|
1265
|
+
inFlight.set(key, controller);
|
|
1266
|
+
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
1267
|
+
if (readCache && resolveOptions.cache !== void 0) {
|
|
1268
|
+
try {
|
|
1269
|
+
const entry = await resolveOptions.cache.get(
|
|
1270
|
+
cacheKeyFor(definition, resolveOptions)
|
|
1271
|
+
);
|
|
1272
|
+
if (entry !== void 0 && isCurrent()) {
|
|
1273
|
+
const cached = resolvePayload(
|
|
1274
|
+
definition,
|
|
1275
|
+
entry.payload,
|
|
1276
|
+
resolveOptions
|
|
1277
|
+
);
|
|
1278
|
+
if (cached.status === "ok") {
|
|
1279
|
+
replace(key, { ...cached, updatedAt: entry.storedAt, stale: true });
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
} catch {
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
if (!isCurrent()) return;
|
|
1286
|
+
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
1287
|
+
const result = await resolveWidget(definition, provider, context, {
|
|
1288
|
+
...resolveOptions,
|
|
1289
|
+
signal: controller.signal
|
|
1290
|
+
});
|
|
1291
|
+
if (!isCurrent()) return;
|
|
1292
|
+
inFlight.delete(key);
|
|
1293
|
+
replace(key, result);
|
|
1294
|
+
}
|
|
1295
|
+
function start(definition, readCache) {
|
|
1296
|
+
started.add(definition.key);
|
|
1297
|
+
void run(definition, readCache);
|
|
1298
|
+
}
|
|
1299
|
+
function definitionFor(key) {
|
|
1300
|
+
return visible.find((definition) => definition.key === key);
|
|
1301
|
+
}
|
|
1302
|
+
function load(key) {
|
|
1303
|
+
if (disposed || started.has(key)) return;
|
|
1304
|
+
const definition = definitionFor(key);
|
|
1305
|
+
if (definition !== void 0) start(definition, true);
|
|
1306
|
+
}
|
|
1307
|
+
function loadAll() {
|
|
1308
|
+
for (const definition of visible) load(definition.key);
|
|
1309
|
+
}
|
|
1310
|
+
function refresh(key, refreshOptions) {
|
|
1311
|
+
if (disposed) return;
|
|
1312
|
+
const keys = key === void 0 ? [...started] : [key];
|
|
1313
|
+
for (const target of keys) {
|
|
1314
|
+
const definition = definitionFor(target);
|
|
1315
|
+
if (definition === void 0) continue;
|
|
1316
|
+
if (refreshOptions?.skipInFlight === true && inFlight.has(target)) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
const current = snapshot.find(
|
|
1320
|
+
(widget) => widget.definition.key === target
|
|
1321
|
+
);
|
|
1322
|
+
if (current?.status === "error") {
|
|
1323
|
+
replace(target, { definition, status: "loading" });
|
|
1324
|
+
}
|
|
1325
|
+
start(definition, false);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function dispose() {
|
|
1329
|
+
disposed = true;
|
|
1330
|
+
abortAll(new Error("The widget loader was disposed."));
|
|
1331
|
+
listeners.clear();
|
|
1332
|
+
}
|
|
1333
|
+
function subscribe(listener) {
|
|
1334
|
+
listeners.add(listener);
|
|
1335
|
+
return () => {
|
|
1336
|
+
listeners.delete(listener);
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
getSnapshot: () => snapshot,
|
|
1341
|
+
subscribe,
|
|
1342
|
+
load,
|
|
1343
|
+
loadAll,
|
|
1344
|
+
refresh,
|
|
1345
|
+
dispose
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
748
1349
|
// src/core/scale.ts
|
|
749
1350
|
function niceStep(value) {
|
|
750
1351
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -777,19 +1378,35 @@ function labelStride(count, maxLabels = 6) {
|
|
|
777
1378
|
|
|
778
1379
|
exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
|
|
779
1380
|
exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
|
|
1381
|
+
exports.DEFAULT_DETAIL_PAGE_SIZE = DEFAULT_DETAIL_PAGE_SIZE;
|
|
780
1382
|
exports.DEFAULT_EMPTY_TEXT = DEFAULT_EMPTY_TEXT;
|
|
1383
|
+
exports.DETAIL_MODES = DETAIL_MODES;
|
|
781
1384
|
exports.EMPTY_LAYOUT = EMPTY_LAYOUT;
|
|
1385
|
+
exports.MAX_DETAIL_PAGE_SIZE = MAX_DETAIL_PAGE_SIZE;
|
|
782
1386
|
exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
|
|
783
1387
|
exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
|
|
1388
|
+
exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
|
|
1389
|
+
exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
|
|
784
1390
|
exports.VALUE_FORMATS = VALUE_FORMATS;
|
|
1391
|
+
exports.WIDGET_FILLS = WIDGET_FILLS;
|
|
785
1392
|
exports.WIDGET_KINDS = WIDGET_KINDS;
|
|
786
1393
|
exports.WIDGET_SIZES = WIDGET_SIZES;
|
|
1394
|
+
exports.WIDTH_COLUMNS = WIDTH_COLUMNS;
|
|
1395
|
+
exports.baseColumnSpan = baseColumnSpan;
|
|
1396
|
+
exports.cacheKeyFor = cacheKeyFor;
|
|
1397
|
+
exports.createWidgetLoader = createWidgetLoader;
|
|
1398
|
+
exports.defaultDetailQuery = defaultDetailQuery;
|
|
787
1399
|
exports.defineWidget = defineWidget;
|
|
1400
|
+
exports.deriveDetailData = deriveDetailData;
|
|
788
1401
|
exports.describeUnknownError = describeUnknownError;
|
|
789
1402
|
exports.emptyTextFor = emptyTextFor;
|
|
790
1403
|
exports.emptyWidget = emptyWidget;
|
|
791
1404
|
exports.err = err;
|
|
792
1405
|
exports.failedWidget = failedWidget;
|
|
1406
|
+
exports.fillColumnSpans = fillColumnSpans;
|
|
1407
|
+
exports.fillWidthSpans = fillWidthSpans;
|
|
1408
|
+
exports.fillsHeight = fillsHeight;
|
|
1409
|
+
exports.fillsWidth = fillsWidth;
|
|
793
1410
|
exports.formatCount = formatCount;
|
|
794
1411
|
exports.formatPercentChange = formatPercentChange;
|
|
795
1412
|
exports.formatValue = formatValue;
|
|
@@ -803,6 +1420,8 @@ exports.isSafeImageUrl = isSafeImageUrl;
|
|
|
803
1420
|
exports.isWidgetEmptyState = isWidgetEmptyState;
|
|
804
1421
|
exports.isWidgetKind = isWidgetKind;
|
|
805
1422
|
exports.isWidgetVisibleTo = isWidgetVisibleTo;
|
|
1423
|
+
exports.isWidgetWidth = isWidgetWidth;
|
|
1424
|
+
exports.itemWidth = itemWidth;
|
|
806
1425
|
exports.kpiDelta = kpiDelta;
|
|
807
1426
|
exports.labelStride = labelStride;
|
|
808
1427
|
exports.loadingWidgets = loadingWidgets;
|
|
@@ -814,13 +1433,17 @@ exports.moveWidgetUp = moveWidgetUp;
|
|
|
814
1433
|
exports.niceDomain = niceDomain;
|
|
815
1434
|
exports.niceStep = niceStep;
|
|
816
1435
|
exports.ok = ok;
|
|
1436
|
+
exports.parseDetailQuery = parseDetailQuery;
|
|
817
1437
|
exports.parseLayout = parseLayout;
|
|
818
1438
|
exports.percentChange = percentChange;
|
|
819
1439
|
exports.pruneLayout = pruneLayout;
|
|
1440
|
+
exports.queryRows = queryRows;
|
|
1441
|
+
exports.resolveDetailOptions = resolveDetailOptions;
|
|
820
1442
|
exports.resolvePayload = resolvePayload;
|
|
821
1443
|
exports.resolveWidget = resolveWidget;
|
|
822
1444
|
exports.resolveWidgets = resolveWidgets;
|
|
823
1445
|
exports.restoreWidget = restoreWidget;
|
|
1446
|
+
exports.serializeDetailQuery = serializeDetailQuery;
|
|
824
1447
|
exports.serializeLayout = serializeLayout;
|
|
825
1448
|
exports.shareOf = shareOf;
|
|
826
1449
|
exports.showWidget = showWidget;
|
|
@@ -828,9 +1451,12 @@ exports.sortDefinitions = sortDefinitions;
|
|
|
828
1451
|
exports.toggleHidden = toggleHidden;
|
|
829
1452
|
exports.toggleMinimized = toggleMinimized;
|
|
830
1453
|
exports.upgradeLegacyWidgetData = upgradeLegacyWidgetData;
|
|
1454
|
+
exports.usesWidthColumns = usesWidthColumns;
|
|
1455
|
+
exports.validateDetailData = validateDetailData;
|
|
831
1456
|
exports.validateWidgetData = validateWidgetData;
|
|
832
1457
|
exports.validateWidgetDefinition = validateWidgetDefinition;
|
|
833
1458
|
exports.visibleWidgets = visibleWidgets;
|
|
834
1459
|
exports.widgetsFor = widgetsFor;
|
|
1460
|
+
exports.widthForSize = widthForSize;
|
|
835
1461
|
//# sourceMappingURL=core.cjs.map
|
|
836
1462
|
//# sourceMappingURL=core.cjs.map
|