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