@richardmcquiston01/dashboard-widgets-toolkit 0.11.1 → 0.12.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.cjs CHANGED
@@ -825,6 +825,10 @@ var DEFAULT_LABELS = {
825
825
  confirmDeletePage: (title) => `Delete the page "${title}" and move its widgets to another page?`,
826
826
  movePageLeft: (title) => `Move ${title} page left`,
827
827
  movePageRight: (title) => `Move ${title} page right`,
828
+ arrangeWidget: (title) => `Arrange ${title}`,
829
+ menuMoveEarlier: "Move earlier",
830
+ menuMoveLater: "Move later",
831
+ menuHide: "Hide",
828
832
  moveToPage: (title) => `Move ${title} to another page`,
829
833
  moveToPagePlaceholder: "Move to page\u2026",
830
834
  moveToPageOption: (title, rowsFree) => `${title} (${rowsFree} ${rowsFree === 1 ? "row" : "rows"} free)`,
@@ -2420,6 +2424,144 @@ function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {
2420
2424
  }
2421
2425
  return ok(moved);
2422
2426
  }
2427
+ var EditMenuInitialOpenContext = react.createContext(false);
2428
+ var ITEM_SELECTOR = '[role="menuitem"]:not(:disabled)';
2429
+ function WidgetEditMenu({
2430
+ label,
2431
+ items,
2432
+ initialOpen
2433
+ }) {
2434
+ const slot = useSlotClassName();
2435
+ const startOpen = react.useContext(EditMenuInitialOpenContext);
2436
+ const menuId = react.useId();
2437
+ const [open, setOpen] = react.useState(initialOpen ?? startOpen);
2438
+ const wrapper = react.useRef(null);
2439
+ const button = react.useRef(null);
2440
+ const menu = react.useRef(null);
2441
+ react.useEffect(() => {
2442
+ if (!open) return void 0;
2443
+ menu.current?.querySelector(ITEM_SELECTOR)?.focus();
2444
+ const onPointerDown = (event) => {
2445
+ if (!wrapper.current?.contains(event.target)) setOpen(false);
2446
+ };
2447
+ document.addEventListener("pointerdown", onPointerDown);
2448
+ return () => document.removeEventListener("pointerdown", onPointerDown);
2449
+ }, [open]);
2450
+ function close(returnFocus) {
2451
+ setOpen(false);
2452
+ if (returnFocus) button.current?.focus();
2453
+ }
2454
+ function onKeyDown(event) {
2455
+ if (event.key === "Escape") {
2456
+ event.stopPropagation();
2457
+ close(true);
2458
+ return;
2459
+ }
2460
+ const enabled = [
2461
+ ...menu.current?.querySelectorAll(ITEM_SELECTOR) ?? []
2462
+ ];
2463
+ if (enabled.length === 0) return;
2464
+ const current = enabled.indexOf(
2465
+ document.activeElement
2466
+ );
2467
+ let next;
2468
+ if (event.key === "ArrowDown") next = (current + 1) % enabled.length;
2469
+ else if (event.key === "ArrowUp") {
2470
+ next = (current - 1 + enabled.length) % enabled.length;
2471
+ } else if (event.key === "Home") next = 0;
2472
+ else if (event.key === "End") next = enabled.length - 1;
2473
+ if (next !== void 0) {
2474
+ event.preventDefault();
2475
+ enabled[next]?.focus();
2476
+ }
2477
+ }
2478
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2479
+ "span",
2480
+ {
2481
+ ref: wrapper,
2482
+ className: "dwt-edit-menu",
2483
+ onBlur: (event) => {
2484
+ if (!event.currentTarget.contains(event.relatedTarget)) {
2485
+ setOpen(false);
2486
+ }
2487
+ },
2488
+ children: [
2489
+ /* @__PURE__ */ jsxRuntime.jsx(
2490
+ "button",
2491
+ {
2492
+ ref: button,
2493
+ type: "button",
2494
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2495
+ "aria-label": label,
2496
+ title: label,
2497
+ "aria-haspopup": "menu",
2498
+ "aria-expanded": open,
2499
+ "aria-controls": open ? menuId : void 0,
2500
+ onClick: () => setOpen((current) => !current),
2501
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2502
+ "svg",
2503
+ {
2504
+ viewBox: "0 0 24 24",
2505
+ width: "16",
2506
+ height: "16",
2507
+ "aria-hidden": "true",
2508
+ focusable: "false",
2509
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2510
+ "path",
2511
+ {
2512
+ d: "M5 9l-3 3 3 3M9 5l3-3 3 3M15 19l-3 3-3-3M19 9l3 3-3 3M2 12h20M12 2v20",
2513
+ fill: "none",
2514
+ stroke: "currentColor",
2515
+ strokeWidth: "2",
2516
+ strokeLinecap: "round",
2517
+ strokeLinejoin: "round"
2518
+ }
2519
+ )
2520
+ }
2521
+ )
2522
+ }
2523
+ ),
2524
+ open && /* @__PURE__ */ jsxRuntime.jsx(
2525
+ "div",
2526
+ {
2527
+ ref: menu,
2528
+ id: menuId,
2529
+ className: "dwt-edit-popover",
2530
+ role: "menu",
2531
+ "aria-label": label,
2532
+ onKeyDown,
2533
+ children: items.map(
2534
+ (item) => item.kind === "heading" ? /* @__PURE__ */ jsxRuntime.jsx(
2535
+ "p",
2536
+ {
2537
+ className: "dwt-edit-heading",
2538
+ role: "presentation",
2539
+ children: item.label
2540
+ },
2541
+ item.key
2542
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2543
+ "button",
2544
+ {
2545
+ type: "button",
2546
+ role: "menuitem",
2547
+ className: "dwt-edit-option",
2548
+ ...item.ariaLabel === void 0 ? {} : { "aria-label": item.ariaLabel },
2549
+ disabled: item.disabled === true,
2550
+ onClick: () => {
2551
+ close(false);
2552
+ item.onSelect();
2553
+ },
2554
+ children: item.label
2555
+ },
2556
+ item.key
2557
+ )
2558
+ )
2559
+ }
2560
+ )
2561
+ ]
2562
+ }
2563
+ );
2564
+ }
2423
2565
  function nextSort(current, column) {
2424
2566
  if (current?.column !== column) return { column, direction: "asc" };
2425
2567
  return current.direction === "asc" ? { column, direction: "desc" } : void 0;
@@ -3143,6 +3285,7 @@ function DashboardInner({
3143
3285
  const [announcement, setAnnouncement] = react.useState("");
3144
3286
  const [pageAnnouncement, setPageAnnouncement] = react.useState("");
3145
3287
  const [pageError, setPageError] = react.useState(void 0);
3288
+ const [newPage, setNewPage] = react.useState(void 0);
3146
3289
  const [pageEditor, setPageEditor] = react.useState(void 0);
3147
3290
  const editorMode = pageEditor?.key === activeKey ? pageEditor.mode : void 0;
3148
3291
  const instanceId = react.useId();
@@ -3173,6 +3316,7 @@ function DashboardInner({
3173
3316
  onActivePageChange?.(pageKey);
3174
3317
  setPageError(void 0);
3175
3318
  setPageEditor(void 0);
3319
+ setNewPage(void 0);
3176
3320
  setPageAnnouncement(
3177
3321
  labels.pageTabName(
3178
3322
  index + 1,
@@ -3190,6 +3334,7 @@ function DashboardInner({
3190
3334
  change(added.value);
3191
3335
  const created = added.value.pages?.at(-1);
3192
3336
  if (created !== void 0) {
3337
+ setNewPage({ key: created.key, activeBefore: activeKey });
3193
3338
  if (activePageProp === void 0) setActiveState(created.key);
3194
3339
  onActivePageChange?.(created.key);
3195
3340
  setPageError(void 0);
@@ -3201,8 +3346,21 @@ function DashboardInner({
3201
3346
  if (!renamed.ok) return renamed.error;
3202
3347
  change(renamed.value);
3203
3348
  setPageEditor(void 0);
3349
+ setNewPage(void 0);
3204
3350
  return void 0;
3205
3351
  }
3352
+ function cancelPageEditor() {
3353
+ const fresh = newPage;
3354
+ setPageEditor(void 0);
3355
+ setNewPage(void 0);
3356
+ if (fresh === void 0 || fresh.key !== activeKey) return;
3357
+ const pagesWithout = pages.filter(
3358
+ (page) => page.key !== fresh.key
3359
+ );
3360
+ if (pagesWithout.length === pages.length) return;
3361
+ change({ ...layout, pages: pagesWithout });
3362
+ selectPage(fresh.activeBefore);
3363
+ }
3206
3364
  function deletePageAction() {
3207
3365
  const index = pages.findIndex((page) => page.key === activeKey);
3208
3366
  const removed = removePage(definitions, layout, activeKey, roomOptions);
@@ -3376,7 +3534,7 @@ function DashboardInner({
3376
3534
  {
3377
3535
  initial: activePage.title,
3378
3536
  onSubmit: renamePageAction,
3379
- onCancel: () => setPageEditor(void 0)
3537
+ onCancel: cancelPageEditor
3380
3538
  },
3381
3539
  activeKey
3382
3540
  ),
@@ -3424,7 +3582,27 @@ function DashboardInner({
3424
3582
  "aria-label": labels.deletePage(activePage.title),
3425
3583
  title: labels.deletePage(activePage.title),
3426
3584
  onClick: () => setPageEditor({ mode: "delete", key: activeKey }),
3427
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
3585
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3586
+ "svg",
3587
+ {
3588
+ viewBox: "0 0 16 16",
3589
+ width: "14",
3590
+ height: "14",
3591
+ "aria-hidden": "true",
3592
+ focusable: "false",
3593
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3594
+ "path",
3595
+ {
3596
+ d: "M3 4.5h10M6.5 4.5V3h3v1.5M4.5 4.5l.6 8.5h5.8l.6-8.5M6.8 7v3.5M9.2 7v3.5",
3597
+ fill: "none",
3598
+ stroke: "currentColor",
3599
+ strokeWidth: "1.3",
3600
+ strokeLinecap: "round",
3601
+ strokeLinejoin: "round"
3602
+ }
3603
+ )
3604
+ }
3605
+ )
3428
3606
  }
3429
3607
  )
3430
3608
  ] }),
@@ -3506,6 +3684,64 @@ function DashboardInner({
3506
3684
  const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
3507
3685
  const canMoveEarlier = !lock.move && moveWidgetBy(pageDefs, view, key, -1) !== view;
3508
3686
  const canMoveLater = !lock.move && moveWidgetBy(pageDefs, view, key, 1) !== view;
3687
+ const pageTargets = paged && !lock.move ? [
3688
+ {
3689
+ kind: "heading",
3690
+ key: "page-heading",
3691
+ label: labels.moveToPagePlaceholder
3692
+ },
3693
+ ...pages.filter((page) => page.key !== activeKey).map((page) => ({
3694
+ kind: "action",
3695
+ key: `page:${page.key}`,
3696
+ label: labels.moveToPageOption(
3697
+ page.title,
3698
+ pageRoom(definitions, layout, page.key, roomOptions)?.rowsFree ?? 0
3699
+ ),
3700
+ ariaLabel: labels.moveToPageOption(
3701
+ page.title,
3702
+ pageRoom(definitions, layout, page.key, roomOptions)?.rowsFree ?? 0
3703
+ ),
3704
+ onSelect: () => moveWidgetAction(key, page.key)
3705
+ })),
3706
+ ...maxPages === void 0 || pages.length < maxPages ? [
3707
+ {
3708
+ kind: "action",
3709
+ key: "page:new",
3710
+ label: labels.newPageOption,
3711
+ onSelect: () => moveWidgetAction(key, NEW_PAGE_VALUE)
3712
+ }
3713
+ ] : []
3714
+ ] : [];
3715
+ const editItems = [
3716
+ ...lock.move ? [] : [
3717
+ {
3718
+ kind: "action",
3719
+ key: "earlier",
3720
+ label: labels.menuMoveEarlier,
3721
+ ariaLabel: labels.moveEarlier(title),
3722
+ disabled: index === 0 || !canMoveEarlier,
3723
+ onSelect: () => changePage(moveWidgetBy(pageDefs, view, key, -1))
3724
+ },
3725
+ {
3726
+ kind: "action",
3727
+ key: "later",
3728
+ label: labels.menuMoveLater,
3729
+ ariaLabel: labels.moveLater(title),
3730
+ disabled: index === shown.length - 1 || !canMoveLater,
3731
+ onSelect: () => changePage(moveWidgetBy(pageDefs, view, key, 1))
3732
+ }
3733
+ ],
3734
+ ...pageTargets,
3735
+ ...lock.hide ? [] : [
3736
+ {
3737
+ kind: "action",
3738
+ key: "hide",
3739
+ label: labels.menuHide,
3740
+ ariaLabel: labels.hide(title),
3741
+ onSelect: () => changePage(hideWidget(view, key))
3742
+ }
3743
+ ]
3744
+ ];
3509
3745
  const actions = controlsShown ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3510
3746
  isLocked && /* @__PURE__ */ jsxRuntime.jsx(
3511
3747
  "span",
@@ -3548,79 +3784,11 @@ function DashboardInner({
3548
3784
  )
3549
3785
  }
3550
3786
  ),
3551
- !lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3552
- /* @__PURE__ */ jsxRuntime.jsx(
3553
- "button",
3554
- {
3555
- type: "button",
3556
- className: slot(
3557
- "button",
3558
- "dwt-button",
3559
- "dwt-icon-button"
3560
- ),
3561
- "aria-label": labels.moveEarlier(title),
3562
- title: labels.moveEarlier(title),
3563
- disabled: index === 0 || !canMoveEarlier,
3564
- onClick: () => changePage(moveWidgetBy(pageDefs, view, key, -1)),
3565
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
3566
- }
3567
- ),
3568
- /* @__PURE__ */ jsxRuntime.jsx(
3569
- "button",
3570
- {
3571
- type: "button",
3572
- className: slot(
3573
- "button",
3574
- "dwt-button",
3575
- "dwt-icon-button"
3576
- ),
3577
- "aria-label": labels.moveLater(title),
3578
- title: labels.moveLater(title),
3579
- disabled: index === shown.length - 1 || !canMoveLater,
3580
- onClick: () => changePage(moveWidgetBy(pageDefs, view, key, 1)),
3581
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
3582
- }
3583
- ),
3584
- paged && /* @__PURE__ */ jsxRuntime.jsxs(
3585
- "select",
3586
- {
3587
- className: "dwt-move-select",
3588
- "aria-label": labels.moveToPage(title),
3589
- value: "",
3590
- onChange: (event) => {
3591
- if (event.target.value !== "") {
3592
- moveWidgetAction(key, event.target.value);
3593
- }
3594
- },
3595
- children: [
3596
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: labels.moveToPagePlaceholder }),
3597
- pages.filter((page) => page.key !== activeKey).map((page) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: page.key, children: labels.moveToPageOption(
3598
- page.title,
3599
- pageRoom(
3600
- definitions,
3601
- layout,
3602
- page.key,
3603
- roomOptions
3604
- )?.rowsFree ?? 0
3605
- ) }, page.key)),
3606
- (maxPages === void 0 || pages.length < maxPages) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: NEW_PAGE_VALUE, children: labels.newPageOption })
3607
- ]
3608
- }
3609
- )
3610
- ] }),
3611
- !lock.hide && /* @__PURE__ */ jsxRuntime.jsx(
3612
- "button",
3787
+ editItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3788
+ WidgetEditMenu,
3613
3789
  {
3614
- type: "button",
3615
- className: slot(
3616
- "button",
3617
- "dwt-button",
3618
- "dwt-icon-button"
3619
- ),
3620
- "aria-label": labels.hide(title),
3621
- title: labels.hide(title),
3622
- onClick: () => changePage(hideWidget(view, key)),
3623
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
3790
+ label: labels.arrangeWidget(title),
3791
+ items: editItems
3624
3792
  }
3625
3793
  )
3626
3794
  ] }) : void 0;