@richardmcquiston01/dashboard-widgets-toolkit 0.15.0 → 0.17.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/dist/react.js CHANGED
@@ -828,6 +828,14 @@ var DEFAULT_LABELS = {
828
828
  menuMoveLater: "Move later",
829
829
  menuHide: "Hide",
830
830
  menuOptions: "Options\u2026",
831
+ menuDuplicate: "Duplicate",
832
+ duplicate: (title) => `Duplicate ${title}`,
833
+ duplicateBlocked: (title, reason) => `Duplicate ${title} (unavailable: ${reason})`,
834
+ duplicated: (title) => `Duplicated ${title}`,
835
+ optionsDelete: "Delete this widget",
836
+ confirmDelete: "Delete this widget? Its settings are lost.",
837
+ deleted: (title) => `Deleted ${title}`,
838
+ suggestedViewTitle: (title, viewLabel) => `${title} ${viewLabel}`,
831
839
  optionsFor: (title) => `Options for ${title}`,
832
840
  optionsTitleField: "Title",
833
841
  titleInUse: "Another widget already uses this title.",
@@ -1937,13 +1945,15 @@ var NOT_LOCKED = Object.freeze({
1937
1945
  move: false,
1938
1946
  hide: false,
1939
1947
  minimize: false,
1940
- options: false
1948
+ options: false,
1949
+ clone: false
1941
1950
  });
1942
1951
  var FULLY_LOCKED = Object.freeze({
1943
1952
  move: true,
1944
1953
  hide: true,
1945
1954
  minimize: true,
1946
- options: true
1955
+ options: true,
1956
+ clone: true
1947
1957
  });
1948
1958
  function resolveWidgetLock(definition) {
1949
1959
  const setting = definition.locked;
@@ -1953,7 +1963,8 @@ function resolveWidgetLock(definition) {
1953
1963
  move: setting.move === true,
1954
1964
  hide: setting.hide === true,
1955
1965
  minimize: setting.minimize === true,
1956
- options: setting.options === true
1966
+ options: setting.options === true,
1967
+ clone: setting.clone === true
1957
1968
  };
1958
1969
  }
1959
1970
  function sortDefinitions(definitions) {
@@ -2055,6 +2066,9 @@ function viewChoices(item) {
2055
2066
  }))
2056
2067
  ];
2057
2068
  }
2069
+ function isProviderView(view) {
2070
+ return view.convert === void 0;
2071
+ }
2058
2072
  function isKnownView(item, key) {
2059
2073
  return key !== void 0 && (key === BASE_VIEW_KEY || findView(item, key) !== void 0);
2060
2074
  }
@@ -2064,6 +2078,14 @@ function effectiveViewKey(item, chosen) {
2064
2078
  if (isKnownView(item, item.defaultView)) return item.defaultView;
2065
2079
  return BASE_VIEW_KEY;
2066
2080
  }
2081
+ function loadViewKey(item, chosen) {
2082
+ const key = effectiveViewKey(item, chosen);
2083
+ const view = findView(item, key);
2084
+ return view !== void 0 && isProviderView(view) ? key : BASE_VIEW_KEY;
2085
+ }
2086
+ function loadViewKind(item, chosen) {
2087
+ return findView(item, loadViewKey(item, chosen))?.kind ?? item.kind;
2088
+ }
2067
2089
  function cellValue2(cell) {
2068
2090
  const value = cell?.value;
2069
2091
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
@@ -2198,6 +2220,11 @@ function convertView(widgetKey, view, data, options = {}) {
2198
2220
  where: `Widget ${JSON.stringify(widgetKey)} view ${JSON.stringify(view.key)}`,
2199
2221
  ...options.locale === void 0 ? {} : { locale: options.locale }
2200
2222
  };
2223
+ if (conversion === void 0) {
2224
+ return err(
2225
+ `${context.where}: the provider supplies this view, so there is nothing to convert.`
2226
+ );
2227
+ }
2201
2228
  const expected = VIEW_CONVERSIONS[conversion.type]?.from;
2202
2229
  if (expected === void 0) {
2203
2230
  return err(
@@ -2225,14 +2252,29 @@ function convertView(widgetKey, view, data, options = {}) {
2225
2252
  function applyView(widget, chosen, options = {}) {
2226
2253
  const key = effectiveViewKey(widget.definition, chosen);
2227
2254
  const view = findView(widget.definition, key);
2228
- if (view === void 0) return widget;
2255
+ const wanted = loadViewKey(widget.definition, chosen);
2256
+ const loaded = widget.status === "ok" ? widget.view ?? BASE_VIEW_KEY : void 0;
2257
+ const mismatched = loaded !== void 0 && loaded !== wanted;
2258
+ if (view === void 0) {
2259
+ return mismatched ? {
2260
+ definition: widget.definition,
2261
+ status: "loading",
2262
+ ...widget.options === void 0 ? {} : { options: widget.options }
2263
+ } : widget;
2264
+ }
2229
2265
  const definition = {
2230
2266
  ...widget.definition,
2231
2267
  kind: view.kind
2232
2268
  };
2233
- if (widget.status === "loading") return { ...widget, definition };
2234
- if (widget.status === "empty") return { ...widget, definition };
2235
- if (widget.status === "error") return { ...widget, definition };
2269
+ if (widget.status !== "ok") return { ...widget, definition };
2270
+ if (mismatched) {
2271
+ return {
2272
+ definition,
2273
+ status: "loading",
2274
+ ...widget.options === void 0 ? {} : { options: widget.options }
2275
+ };
2276
+ }
2277
+ if (isProviderView(view)) return { ...widget, definition };
2236
2278
  const converted = convertView(
2237
2279
  widget.definition.key,
2238
2280
  view,
@@ -2403,6 +2445,62 @@ function isTitleInUse(definitions, layout, key, title) {
2403
2445
  );
2404
2446
  }
2405
2447
 
2448
+ // src/core/clones.ts
2449
+ function cloneNumber(key) {
2450
+ const match = /#(\d+)$/.exec(key);
2451
+ return match === null ? void 0 : Number(match[1]);
2452
+ }
2453
+ function cleanClones(definitions, clones, options = {}) {
2454
+ if (clones === void 0) return void 0;
2455
+ const byKey = new Map(definitions.map((d) => [d.key, d]));
2456
+ const seen = /* @__PURE__ */ new Set();
2457
+ const kept = [];
2458
+ for (const clone of clones) {
2459
+ if (seen.has(clone.key)) continue;
2460
+ const original = byKey.get(clone.from);
2461
+ if (original === void 0 || original.sourceKey !== void 0) continue;
2462
+ const existing = byKey.get(clone.key);
2463
+ if (existing !== void 0 && existing.sourceKey !== clone.from) continue;
2464
+ if (options.overrideLocks !== true && resolveWidgetLock(original).clone) {
2465
+ continue;
2466
+ }
2467
+ seen.add(clone.key);
2468
+ kept.push(clone);
2469
+ }
2470
+ if (kept.length === 0) return void 0;
2471
+ return kept.length === clones.length && kept.every((c, i) => c === clones[i]) ? clones : kept;
2472
+ }
2473
+ function defaultCloneTitle(original, clone, index) {
2474
+ const base = original.title ?? original.key;
2475
+ const suffix = ` (#${cloneNumber(clone.key) ?? index + 1})`;
2476
+ return `${base.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
2477
+ }
2478
+ function withClones(definitions, layout, options = {}) {
2479
+ const valid = cleanClones(
2480
+ definitions,
2481
+ layout.clones,
2482
+ options
2483
+ );
2484
+ if (valid === void 0) return definitions;
2485
+ const present = new Set(definitions.map((d) => d.key));
2486
+ const toAdd = valid.filter((c) => !present.has(c.key));
2487
+ if (toAdd.length === 0) return definitions;
2488
+ const out = [];
2489
+ for (const definition of definitions) {
2490
+ out.push(definition);
2491
+ toAdd.forEach((clone, index) => {
2492
+ if (clone.from !== definition.key) return;
2493
+ const copy = { ...definition };
2494
+ copy.key = clone.key;
2495
+ copy.sourceKey = definition.key;
2496
+ copy.title = defaultCloneTitle(definition, clone, index);
2497
+ delete copy.locked;
2498
+ out.push(copy);
2499
+ });
2500
+ }
2501
+ return out;
2502
+ }
2503
+
2406
2504
  // src/core/layout.ts
2407
2505
  var MAX_PAGE_TITLE_LENGTH = 30;
2408
2506
  var MAX_PARSED_PAGES = 50;
@@ -2880,6 +2978,114 @@ function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {
2880
2978
  }
2881
2979
  return ok(moved);
2882
2980
  }
2981
+
2982
+ // src/core/duplicate.ts
2983
+ function canDuplicate(definitions, key, options = {}) {
2984
+ const definition = definitions.find(
2985
+ (d) => d.key === key
2986
+ );
2987
+ if (definition === void 0) return false;
2988
+ if (options.overrideLocks === true) return true;
2989
+ const sourceKey = definition.sourceKey ?? definition.key;
2990
+ const original = definitions.find(
2991
+ (d) => d.key === sourceKey
2992
+ );
2993
+ return original !== void 0 && !resolveWidgetLock(original).clone;
2994
+ }
2995
+ function nextCloneKey(definitions, layout, fromKey) {
2996
+ const taken = /* @__PURE__ */ new Set([
2997
+ ...definitions.map((d) => d.key),
2998
+ ...(layout.clones ?? []).map((c) => c.key)
2999
+ ]);
3000
+ let highest = 0;
3001
+ for (const clone of layout.clones ?? []) {
3002
+ if (clone.from === fromKey) {
3003
+ highest = Math.max(highest, cloneNumber(clone.key) ?? 0);
3004
+ }
3005
+ }
3006
+ let n = highest + 1;
3007
+ while (taken.has(`${fromKey}#${n}`)) n += 1;
3008
+ return `${fromKey}#${n}`;
3009
+ }
3010
+ function duplicateWidget(definitions, layout, key, options = {}) {
3011
+ const widget = definitions.find((d) => d.key === key);
3012
+ if (widget === void 0) {
3013
+ return err(`Widget ${JSON.stringify(key)} isn't on this dashboard.`);
3014
+ }
3015
+ const originalKey = widget.sourceKey ?? widget.key;
3016
+ if (!canDuplicate(definitions, key, options)) {
3017
+ return err(
3018
+ `Widget ${JSON.stringify(originalKey)} is locked against duplicating.`
3019
+ );
3020
+ }
3021
+ const cloneKey = nextCloneKey(definitions, layout, originalKey);
3022
+ const copiedSettings = (() => {
3023
+ const mine = layout.settings?.[key];
3024
+ if (mine === void 0) return void 0;
3025
+ const { title: _title, ...rest } = mine;
3026
+ return Object.keys(rest).length === 0 ? void 0 : rest;
3027
+ })();
3028
+ const settings = copiedSettings === void 0 ? layout.settings : { ...layout.settings, [cloneKey]: copiedSettings };
3029
+ let next = {
3030
+ ...layout,
3031
+ clones: [...layout.clones ?? [], { key: cloneKey, from: originalKey }],
3032
+ ...settings === void 0 ? {} : { settings }
3033
+ };
3034
+ const all = withClones(definitions, next, options);
3035
+ const assigned = assignPages(all, next);
3036
+ const pageKey = [...assigned].find(
3037
+ ([, widgets]) => widgets.some((w) => w.key === key)
3038
+ )?.[0];
3039
+ if (pageKey !== void 0) {
3040
+ const view = pageLayout(next, pageKey);
3041
+ const onPage = assigned.get(pageKey) ?? [];
3042
+ if (view !== void 0) {
3043
+ const shown = visibleWidgets(onPage, view).filter((d) => !resolveWidgetLock(d).move && d.key !== cloneKey).map((d) => d.key);
3044
+ const at = shown.indexOf(key);
3045
+ const order = at === -1 ? [...shown, cloneKey] : [...shown.slice(0, at + 1), cloneKey, ...shown.slice(at + 1)];
3046
+ next = withPageLayout(next, pageKey, { ...view, order });
3047
+ }
3048
+ }
3049
+ if (hasPages(next) && pageKey !== void 0) {
3050
+ const effective = effectiveDefinitions(all, next, options);
3051
+ const room = pageRoom(effective, next, pageKey, options);
3052
+ if (room !== void 0 && room.rowsUsed > room.maxRows) {
3053
+ const page = next.pages?.find(
3054
+ (p) => p.key === pageKey
3055
+ );
3056
+ const width = effective.find(
3057
+ (d) => d.key === cloneKey
3058
+ )?.width;
3059
+ return err(
3060
+ `Page "${page?.title ?? pageKey}" has no room for a widget${width === void 0 ? "" : ` ${width} wide`} (it holds ${room.maxRows} rows).`
3061
+ );
3062
+ }
3063
+ }
3064
+ return ok({ layout: next, key: cloneKey });
3065
+ }
3066
+ function deleteClone(layout, key) {
3067
+ if (layout.clones?.some((clone) => clone.key === key) !== true) return layout;
3068
+ const without2 = (keys) => keys.includes(key) ? keys.filter((k) => k !== key) : keys;
3069
+ const { clones, settings, ...rest } = layout;
3070
+ const remaining = (clones ?? []).filter((c) => c.key !== key);
3071
+ const { [key]: _gone, ...otherSettings } = settings ?? {};
3072
+ return {
3073
+ ...rest,
3074
+ order: without2(layout.order),
3075
+ hidden: without2(layout.hidden),
3076
+ minimized: without2(layout.minimized),
3077
+ ...layout.pages === void 0 ? {} : {
3078
+ pages: layout.pages.map((page) => ({
3079
+ ...page,
3080
+ order: without2(page.order),
3081
+ hidden: without2(page.hidden),
3082
+ minimized: without2(page.minimized)
3083
+ }))
3084
+ },
3085
+ ...Object.keys(otherSettings).length === 0 ? {} : { settings: otherSettings },
3086
+ ...remaining.length === 0 ? {} : { clones: remaining }
3087
+ };
3088
+ }
2883
3089
  function InlineConfirm({
2884
3090
  message,
2885
3091
  onAnswer
@@ -3130,6 +3336,8 @@ function WidgetOptionsDialog({
3130
3336
  settings,
3131
3337
  baseline,
3132
3338
  isTitleInUse: isTitleInUse2,
3339
+ sourceTitle,
3340
+ onDelete,
3133
3341
  onApply,
3134
3342
  onClose
3135
3343
  }) {
@@ -3142,6 +3350,7 @@ function WidgetOptionsDialog({
3142
3350
  const [confirming, setConfirming] = useState(
3143
3351
  void 0
3144
3352
  );
3353
+ const autoTitle = useRef(void 0);
3145
3354
  useEffect(() => {
3146
3355
  const dialog = dialogRef.current;
3147
3356
  if (dialog === null) return void 0;
@@ -3184,6 +3393,9 @@ function WidgetOptionsDialog({
3184
3393
  } else if (which === "reset") {
3185
3394
  onApply(EMPTY_PATCH);
3186
3395
  onClose();
3396
+ } else if (which === "delete") {
3397
+ onDelete?.();
3398
+ onClose();
3187
3399
  } else if (baseline !== void 0) {
3188
3400
  onApply(patchFrom(definition, draftFrom(baseline.settings)));
3189
3401
  onClose();
@@ -3199,7 +3411,8 @@ function WidgetOptionsDialog({
3199
3411
  apply: labels.confirmApply,
3200
3412
  discard: labels.confirmDiscard,
3201
3413
  reset: labels.confirmResetOptions,
3202
- revert: labels.confirmRevertOptions
3414
+ revert: labels.confirmRevertOptions,
3415
+ delete: labels.confirmDelete
3203
3416
  };
3204
3417
  const widths = [];
3205
3418
  for (let width = range.min; width <= range.max; width += 1)
@@ -3296,7 +3509,32 @@ function WidgetOptionsDialog({
3296
3509
  value: effectiveViewKey(definition, draft.view),
3297
3510
  onChange: (event) => {
3298
3511
  const value = event.target.value;
3299
- setDraft((current) => ({ ...current, view: value }));
3512
+ setDraft((current) => {
3513
+ const next = { ...current, view: value };
3514
+ if (sourceTitle === void 0 || current.title.trim() !== "" && current.title !== autoTitle.current) {
3515
+ return next;
3516
+ }
3517
+ const label = views.find(
3518
+ (view) => view.key === value
3519
+ )?.label;
3520
+ const startView = effectiveViewKey(
3521
+ definition,
3522
+ settings?.view
3523
+ );
3524
+ if (label === void 0 || value === startView) {
3525
+ autoTitle.current = void 0;
3526
+ return { ...next, title: "" };
3527
+ }
3528
+ let suggestion = labels.suggestedViewTitle(
3529
+ sourceTitle,
3530
+ label
3531
+ );
3532
+ for (let n = 2; isTitleInUse2?.(suggestion) === true && n < 50; n += 1) {
3533
+ suggestion = `${labels.suggestedViewTitle(sourceTitle, label)} (#${n})`;
3534
+ }
3535
+ autoTitle.current = suggestion;
3536
+ return { ...next, title: suggestion };
3537
+ });
3300
3538
  },
3301
3539
  children: views.map((view) => /* @__PURE__ */ jsx("option", { value: view.key, children: view.key === effectiveViewKey(definition, void 0) ? labels.viewDefault(view.label) : view.label }, view.key))
3302
3540
  }
@@ -3353,6 +3591,15 @@ function WidgetOptionsDialog({
3353
3591
  onClick: () => setConfirming("revert"),
3354
3592
  children: labels.revertToBeforeEditing
3355
3593
  }
3594
+ ),
3595
+ onDelete !== void 0 && /* @__PURE__ */ jsx(
3596
+ "button",
3597
+ {
3598
+ type: "button",
3599
+ className: slot("button", "dwt-button"),
3600
+ onClick: () => setConfirming("delete"),
3601
+ children: labels.optionsDelete
3602
+ }
3356
3603
  )
3357
3604
  ] }) : /* @__PURE__ */ jsx(
3358
3605
  InlineConfirm,
@@ -4357,6 +4604,29 @@ function DashboardInner({
4357
4604
  setPageError(void 0);
4358
4605
  change(withPageLayout(layout, activeKey, next));
4359
4606
  }
4607
+ function duplicateItems(definition, key, title) {
4608
+ const sources = [...authorDefinitions.values()];
4609
+ if (!canDuplicate(sources, key, { overrideLocks })) return [];
4610
+ const trial = duplicateWidget(sources, layout, key, {
4611
+ overrideLocks,
4612
+ ...roomOptions
4613
+ });
4614
+ return [
4615
+ {
4616
+ kind: "action",
4617
+ key: "duplicate",
4618
+ label: labels.menuDuplicate,
4619
+ ariaLabel: trial.ok ? labels.duplicate(title) : labels.duplicateBlocked(title, trial.error),
4620
+ disabled: !trial.ok,
4621
+ onSelect: () => {
4622
+ if (!trial.ok) return;
4623
+ change(trial.value.layout);
4624
+ setOptionsKey(trial.value.key);
4625
+ setAnnouncement(labels.duplicated(title));
4626
+ }
4627
+ }
4628
+ ];
4629
+ }
4360
4630
  function selectPage(pageKey) {
4361
4631
  const index = pages.findIndex((page) => page.key === pageKey);
4362
4632
  if (index === -1) return;
@@ -4486,10 +4756,16 @@ function DashboardInner({
4486
4756
  definitions,
4487
4757
  defaultLayout ?? EMPTY_LAYOUT
4488
4758
  );
4489
- const { settings: _defaultSettings, ...arrangement } = arranged;
4490
- change(
4491
- layout.settings === void 0 ? arrangement : { ...arrangement, settings: layout.settings }
4492
- );
4759
+ const {
4760
+ settings: _defaultSettings,
4761
+ clones: _defaultClones,
4762
+ ...arrangement
4763
+ } = arranged;
4764
+ change({
4765
+ ...arrangement,
4766
+ ...layout.settings === void 0 ? {} : { settings: layout.settings },
4767
+ ...layout.clones === void 0 ? {} : { clones: layout.clones }
4768
+ });
4493
4769
  } else if (yes && which === "revert" && snapshot !== void 0) {
4494
4770
  change(snapshot);
4495
4771
  }
@@ -4795,6 +5071,7 @@ function DashboardInner({
4795
5071
  onSelect: () => setOptionsKey(key)
4796
5072
  }
4797
5073
  ],
5074
+ ...duplicateItems(definition, key, title),
4798
5075
  ...pageTargets,
4799
5076
  ...lock.hide ? [] : [
4800
5077
  {
@@ -4889,6 +5166,21 @@ function DashboardInner({
4889
5166
  definition: authorDefinitions.get(optionsKey),
4890
5167
  settings: layout.settings?.[optionsKey],
4891
5168
  ...snapshot === void 0 ? {} : { baseline: { settings: snapshot.settings?.[optionsKey] } },
5169
+ ...authorDefinitions.get(optionsKey)?.sourceKey === void 0 ? {} : {
5170
+ sourceTitle: effectiveWidgets.find(
5171
+ (w) => w.definition.key === authorDefinitions.get(optionsKey)?.sourceKey
5172
+ )?.definition.title ?? authorDefinitions.get(optionsKey)?.sourceKey ?? "",
5173
+ onDelete: () => {
5174
+ change(deleteClone(layout, optionsKey));
5175
+ setAnnouncement(
5176
+ labels.deleted(
5177
+ effectiveWidgets.find(
5178
+ (w) => w.definition.key === optionsKey
5179
+ )?.definition.title ?? optionsKey
5180
+ )
5181
+ );
5182
+ }
5183
+ },
4892
5184
  isTitleInUse: (title) => isTitleInUse(
4893
5185
  widgets.map((w) => w.definition),
4894
5186
  layout,
@@ -5416,7 +5708,10 @@ function resolvePayload(definition, payload, options = {}) {
5416
5708
  } else {
5417
5709
  const result = validateWidgetData(payload, {
5418
5710
  widgetKey: definition.key,
5419
- expectedKind: definition.kind
5711
+ expectedKind: loadViewKind(
5712
+ definition,
5713
+ options.viewValues?.[definition.key]
5714
+ )
5420
5715
  });
5421
5716
  if (!result.ok) {
5422
5717
  return { definition, status: "error", error: result.error };
@@ -5427,7 +5722,14 @@ function resolvePayload(definition, payload, options = {}) {
5427
5722
  if (emptyText !== null) {
5428
5723
  return { definition, status: "empty", emptyText };
5429
5724
  }
5430
- return { definition, status: "ok", data: checked };
5725
+ return {
5726
+ definition,
5727
+ status: "ok",
5728
+ data: checked,
5729
+ ...hasViews(definition) ? {
5730
+ view: loadViewKey(definition, options.viewValues?.[definition.key])
5731
+ } : {}
5732
+ };
5431
5733
  }
5432
5734
  function failedWidget(definition, error) {
5433
5735
  return { definition, status: "error", error };
@@ -5436,8 +5738,15 @@ function loadingWidgets(definitions) {
5436
5738
  return definitions.map((definition) => ({ definition, status: "loading" }));
5437
5739
  }
5438
5740
  function cacheKeyFor(definition, options, values) {
5439
- const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
5440
- return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
5741
+ const base = options.cacheKey === void 0 ? definition.sourceKey ?? definition.key : options.cacheKey(definition);
5742
+ const view = loadViewKey(
5743
+ definition,
5744
+ options.viewValues?.[definition.key]
5745
+ );
5746
+ const viewPart = view === BASE_VIEW_KEY ? "" : `view=${encodeURIComponent(view)}`;
5747
+ const optionPart = values === void 0 ? "" : optionsCacheSuffix(definition, values, options);
5748
+ if (viewPart === "") return `${base}${optionPart}`;
5749
+ return optionPart === "" ? `${base}?${viewPart}` : `${base}?${viewPart}&${optionPart.slice(1)}`;
5441
5750
  }
5442
5751
  function optionValuesFor(definition, options) {
5443
5752
  return resolveOptionValues(
@@ -5448,6 +5757,9 @@ function optionValuesFor(definition, options) {
5448
5757
  }
5449
5758
  function finalizeWidget(widget, values) {
5450
5759
  if (Object.keys(values).length === 0) return widget;
5760
+ if (widget.status === "ok" && widget.view !== void 0 && widget.view !== BASE_VIEW_KEY) {
5761
+ return { ...widget, options: values };
5762
+ }
5451
5763
  if (widget.status === "ok") {
5452
5764
  const data = applyClientOptions(
5453
5765
  widget.definition,
@@ -5498,7 +5810,8 @@ async function resolveWidgetUnapplied(definition, provider, context, options = {
5498
5810
  if (controller.signal.aborted) throw new LoadInterruption();
5499
5811
  return provider(context, definition, {
5500
5812
  signal: controller.signal,
5501
- options: values
5813
+ options: values,
5814
+ view: loadViewKey(definition, options.viewValues?.[definition.key])
5502
5815
  });
5503
5816
  }),
5504
5817
  interrupted
@@ -5546,9 +5859,18 @@ function createWidgetLoader(options) {
5546
5859
  ([key, values]) => values === void 0 ? [] : [[key, values]]
5547
5860
  )
5548
5861
  );
5862
+ const chosenViews = new Map(
5863
+ Object.entries(baseOptions.viewValues ?? {}).flatMap(
5864
+ ([key, view]) => view === void 0 ? [] : [[key, view]]
5865
+ )
5866
+ );
5549
5867
  const resolveOptions = baseOptions;
5550
5868
  function currentOptions() {
5551
- return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
5869
+ return {
5870
+ ...resolveOptions,
5871
+ optionValues: Object.fromEntries(chosen),
5872
+ viewValues: Object.fromEntries(chosenViews)
5873
+ };
5552
5874
  }
5553
5875
  const unapplied = /* @__PURE__ */ new Map();
5554
5876
  const visible = widgetsFor(definitions, context.roles);
@@ -5610,7 +5932,11 @@ function createWidgetLoader(options) {
5610
5932
  }
5611
5933
  }
5612
5934
  if (!isCurrent()) return;
5613
- const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
5935
+ const providerKey = definition.sourceKey ?? key;
5936
+ const provider = Object.prototype.hasOwnProperty.call(
5937
+ providers,
5938
+ providerKey
5939
+ ) ? providers[providerKey] : void 0;
5614
5940
  const result = await resolveWidgetUnapplied(definition, provider, context, {
5615
5941
  ...runOptions,
5616
5942
  signal: controller.signal
@@ -5677,6 +6003,23 @@ function createWidgetLoader(options) {
5677
6003
  }
5678
6004
  start(definition, true);
5679
6005
  }
6006
+ function setView(key, view) {
6007
+ if (disposed) return;
6008
+ const definition = definitionFor(key);
6009
+ if (definition === void 0) return;
6010
+ const before = loadViewKey(definition, chosenViews.get(key));
6011
+ if (view === void 0 || view === "") chosenViews.delete(key);
6012
+ else chosenViews.set(key, view);
6013
+ const after = loadViewKey(definition, chosenViews.get(key));
6014
+ if (before === after || !started.has(key)) return;
6015
+ const current = snapshot.find(
6016
+ (widget) => widget.definition.key === key
6017
+ );
6018
+ if (current !== void 0 && current.status !== "loading") {
6019
+ replace(key, { definition, status: "loading" });
6020
+ }
6021
+ start(definition, true);
6022
+ }
5680
6023
  function dispose() {
5681
6024
  disposed = true;
5682
6025
  abortAll(new Error("The widget loader was disposed."));
@@ -5695,6 +6038,7 @@ function createWidgetLoader(options) {
5695
6038
  loadAll,
5696
6039
  refresh,
5697
6040
  setOptions,
6041
+ setView,
5698
6042
  dispose
5699
6043
  };
5700
6044
  }
@@ -5710,7 +6054,8 @@ function useWidgets(definitions, providers, context, options = {}) {
5710
6054
  signal,
5711
6055
  cache,
5712
6056
  cacheKey,
5713
- optionValues
6057
+ optionValues,
6058
+ viewValues
5714
6059
  } = options;
5715
6060
  const placeholders = useMemo(
5716
6061
  () => loadingWidgets(widgetsFor(definitions, context.roles)),
@@ -5722,6 +6067,8 @@ function useWidgets(definitions, providers, context, options = {}) {
5722
6067
  const gridRef = useRef(null);
5723
6068
  const optionValuesRef = useRef(optionValues);
5724
6069
  optionValuesRef.current = optionValues;
6070
+ const viewValuesRef = useRef(viewValues);
6071
+ viewValuesRef.current = viewValues;
5725
6072
  useEffect(() => {
5726
6073
  const created = createWidgetLoader({
5727
6074
  definitions,
@@ -5732,7 +6079,8 @@ function useWidgets(definitions, providers, context, options = {}) {
5732
6079
  ...signal === void 0 ? {} : { signal },
5733
6080
  ...cache === void 0 ? {} : { cache },
5734
6081
  ...cacheKey === void 0 ? {} : { cacheKey },
5735
- ...optionValuesRef.current === void 0 ? {} : { optionValues: optionValuesRef.current }
6082
+ ...optionValuesRef.current === void 0 ? {} : { optionValues: optionValuesRef.current },
6083
+ ...viewValuesRef.current === void 0 ? {} : { viewValues: viewValuesRef.current }
5736
6084
  });
5737
6085
  loaderRef.current = created;
5738
6086
  setWidgets(created.getSnapshot());
@@ -5811,6 +6159,16 @@ function useWidgets(definitions, providers, context, options = {}) {
5811
6159
  if (chosen !== void 0) loader.setOptions(key, chosen);
5812
6160
  }
5813
6161
  }, [loader, optionSignature]);
6162
+ const viewSignature = JSON.stringify(viewValues ?? {});
6163
+ useEffect(() => {
6164
+ if (loader === null) return;
6165
+ for (const [key, view] of Object.entries(viewValuesRef.current ?? {})) {
6166
+ loader.setView(key, view);
6167
+ }
6168
+ }, [loader, viewSignature]);
6169
+ const setView = useCallback((key, view) => {
6170
+ loaderRef.current?.setView(key, view);
6171
+ }, []);
5814
6172
  const setOptions = useCallback(
5815
6173
  (key, chosen) => {
5816
6174
  loaderRef.current?.setOptions(key, chosen);
@@ -5820,7 +6178,7 @@ function useWidgets(definitions, providers, context, options = {}) {
5820
6178
  const refresh = useCallback((key) => {
5821
6179
  loaderRef.current?.refresh(key);
5822
6180
  }, []);
5823
- return { widgets, refresh, setOptions, gridRef };
6181
+ return { widgets, refresh, setOptions, setView, gridRef };
5824
6182
  }
5825
6183
 
5826
6184
  export { AlertListWidget, BarListWidget, DEFAULT_LABELS, DEFAULT_SERIES_COLORS, Dashboard, GaugeWidget, GraphWidget, KpiWidget, ResolvedWidgetCard, TableWidget, TextWidget, Thumbnail, WidgetCard, WidgetContent, WidgetDetail, WidgetDetailDialog, WidgetGrid, WidgetLink, WidgetOptionsDialog, WidgetSettingsProvider, barPath, buildChartModel, describeChart, joinClassNames, seriesColor, useDetailData, useStoredLayout, useWidgetSettings, useWidgets, widgetTitle };