@broberg/app-shell 0.13.1 → 0.14.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/preact.cjs CHANGED
@@ -517,27 +517,50 @@ function RowActions(props) {
517
517
  const btn = hooks.useRef(null);
518
518
  const menu = hooks.useRef(null);
519
519
  const [stack, setStack] = hooks.useState([]);
520
- const [hi, setHi] = hooks.useState(0);
520
+ const [hi, setHiState] = hooks.useState(0);
521
+ const hiRef = hooks.useRef(0);
522
+ const setHi = (n) => {
523
+ hiRef.current = n;
524
+ setHiState(n);
525
+ };
526
+ const [armed, setArmedState] = hooks.useState(null);
527
+ const armedRef = hooks.useRef(null);
528
+ const setArmed = (v) => {
529
+ armedRef.current = v;
530
+ setArmedState(v);
531
+ };
532
+ const items = hooks.useRef([]);
533
+ const onKeyDownRef = hooks.useRef(() => {
534
+ });
521
535
  const [value, setValue] = hooks.useState("");
522
536
  const [pos, setPos] = hooks.useState(void 0);
523
537
  const view = stack[stack.length - 1];
524
538
  const open = !!view;
539
+ const stackRef = hooks.useRef([]);
540
+ const setStackNow = (next) => {
541
+ stackRef.current = next;
542
+ setStack(next);
543
+ };
525
544
  const close = (refocus = true) => {
526
- setStack([]);
545
+ setStackNow([]);
546
+ setArmed(null);
527
547
  if (refocus) btn.current?.focus();
528
548
  };
529
549
  const show = (v) => {
530
- setStack((s) => [...s, v]);
550
+ setStackNow([...stackRef.current, v]);
531
551
  setHi(0);
552
+ setArmed(null);
532
553
  if (v.kind === "input") setValue(v.action.input.value);
533
554
  };
534
555
  const back = () => {
535
- setStack((s) => s.slice(0, -1));
556
+ setStackNow(stackRef.current.slice(0, -1));
536
557
  setHi(0);
558
+ setArmed(null);
537
559
  };
538
560
  const pick = (a) => {
539
561
  if (a.submenu?.length) return show({ kind: "list", actions: a.submenu, title: a.label });
540
562
  if (a.input) return show({ kind: "input", action: a });
563
+ if (a.confirm && armedRef.current !== a.id) return setArmed(a.id);
541
564
  close();
542
565
  props.onAction(a.id);
543
566
  };
@@ -554,50 +577,69 @@ function RowActions(props) {
554
577
  const left = Math.max(m, Math.min(r.right - w, vw - w - m));
555
578
  const top = r.bottom + 4 + h <= vh - m || r.top - 4 - h < m ? Math.min(r.bottom + 4, vh - m - h) : r.top - 4 - h;
556
579
  setPos({ position: "fixed", left: `${left}px`, top: `${Math.max(m, top)}px`, width: `${w}px` });
557
- if (view?.kind === "input") menu.current?.querySelector("input")?.focus();
558
- else menu.current?.focus();
559
580
  }, [open, stack.length]);
560
- hooks.useEffect(() => {
581
+ hooks.useLayoutEffect(() => {
582
+ if (!open || !pos) return;
583
+ if (view?.kind === "input") menu.current?.querySelector("input")?.focus();
584
+ else items.current[hiRef.current]?.focus();
585
+ }, [open, pos, hi, stack.length]);
586
+ hooks.useLayoutEffect(() => {
561
587
  if (!open) return;
562
588
  const outside = (e) => {
563
589
  const n = e.target instanceof Node ? e.target : null;
564
590
  if (n && (menu.current?.contains(n) || btn.current?.contains(n))) return;
565
591
  close(false);
566
592
  };
593
+ const early = (e) => {
594
+ const n = e.target instanceof Node ? e.target : null;
595
+ if (n && menu.current?.contains(n)) return;
596
+ if (e.key === "Enter" || e.key === " " || e.key === "Spacebar" || e.key === "Tab") return;
597
+ onKeyDownRef.current(e);
598
+ };
599
+ document.addEventListener("keydown", early, true);
567
600
  document.addEventListener("mousedown", outside);
568
601
  window.addEventListener("scroll", outside, true);
569
602
  window.addEventListener("resize", outside);
570
603
  return () => {
604
+ document.removeEventListener("keydown", early, true);
571
605
  document.removeEventListener("mousedown", outside);
572
606
  window.removeEventListener("scroll", outside, true);
573
607
  window.removeEventListener("resize", outside);
574
608
  };
575
609
  }, [open]);
576
610
  const onKeyDown = (e) => {
577
- if (!view) return;
611
+ const stack2 = stackRef.current;
612
+ const view2 = stack2[stack2.length - 1];
613
+ if (!view2) return;
578
614
  e.stopPropagation();
579
615
  if (e.key === "Escape") {
580
616
  e.preventDefault();
581
617
  return close();
582
618
  }
583
- if (view.kind === "input") {
619
+ if (view2.kind === "input") {
584
620
  if (e.key === "Enter") {
585
621
  e.preventDefault();
586
- save(view.action);
622
+ save(view2.action);
587
623
  }
588
624
  return;
589
625
  }
590
- if ((e.key === "ArrowLeft" || e.key === "Backspace") && stack.length > 1) {
626
+ if ((e.key === "ArrowLeft" || e.key === "Backspace") && stack2.length > 1) {
591
627
  e.preventDefault();
592
628
  return back();
593
629
  }
594
- const list = view.actions;
595
- const { state, intent } = uiControlsCore.selectKeyReducer({ open: true, highlighted: hi }, e.key, list.length);
630
+ const list = view2.actions;
631
+ if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
632
+ e.preventDefault();
633
+ const at = items.current.findIndex((b) => b === document.activeElement);
634
+ const idx = at >= 0 ? at : hiRef.current;
635
+ return list[idx] ? pick(list[idx]) : void 0;
636
+ }
637
+ const { state, intent } = uiControlsCore.selectKeyReducer({ open: true, highlighted: hiRef.current }, e.key, list.length);
596
638
  if (intent.type === "select") {
597
639
  e.preventDefault();
598
640
  return pick(list[intent.index]);
599
641
  }
600
- if (state.highlighted !== hi) {
642
+ if (state.highlighted !== hiRef.current) {
601
643
  e.preventDefault();
602
644
  return setHi(state.highlighted);
603
645
  }
@@ -607,6 +649,7 @@ function RowActions(props) {
607
649
  pick(byKbd);
608
650
  }
609
651
  };
652
+ onKeyDownRef.current = onKeyDown;
610
653
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
611
654
  /* @__PURE__ */ jsxRuntime.jsx(
612
655
  "button",
@@ -618,7 +661,7 @@ function RowActions(props) {
618
661
  "aria-label": `${t.moreFor} ${props.item.label}`,
619
662
  "aria-haspopup": "menu",
620
663
  "aria-expanded": open,
621
- onClick: () => open ? close() : show({ kind: "list", actions: props.item.actions }),
664
+ onClick: () => stackRef.current.length ? close() : show({ kind: "list", actions: props.item.actions }),
622
665
  children: /* @__PURE__ */ jsxRuntime.jsx(IconMore, {})
623
666
  }
624
667
  ),
@@ -643,13 +686,18 @@ function RowActions(props) {
643
686
  {
644
687
  type: "button",
645
688
  role: "menuitem",
646
- class: "bas-rowmenu__item" + (i === hi ? " is-active" : "") + (a.danger ? " is-danger" : ""),
689
+ ref: (el) => {
690
+ items.current[i] = el;
691
+ },
692
+ tabIndex: i === hi ? 0 : -1,
693
+ class: "bas-rowmenu__item" + (i === hi ? " is-active" : "") + (a.danger ? " is-danger" : "") + (armed === a.id ? " is-armed" : ""),
647
694
  "data-testid": `sidebar-item-${id}-action-${a.id}`,
648
695
  onMouseEnter: () => setHi(i),
696
+ onFocus: () => setHi(i),
649
697
  onClick: () => pick(a),
650
698
  children: [
651
699
  a.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-rowmenu__icon", children: a.icon }) : null,
652
- /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-rowmenu__label", children: a.label }),
700
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-rowmenu__label", children: armed === a.id && a.confirm ? a.confirm.label : a.label }),
653
701
  a.submenu?.length ? /* @__PURE__ */ jsxRuntime.jsx(IconChevronRight, {}) : a.kbd ? /* @__PURE__ */ jsxRuntime.jsx("kbd", { class: "bas-rowmenu__kbd", children: a.kbd.toUpperCase() }) : null
654
702
  ]
655
703
  },