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