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