@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/README.md +15 -1
- package/css/app-shell.css +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +65 -17
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.d.cts +4 -0
- package/dist/preact.d.ts +4 -0
- package/dist/preact.js +65 -17
- package/dist/preact.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
543
|
+
setStackNow([]);
|
|
544
|
+
setArmed(null);
|
|
525
545
|
if (refocus) btn.current?.focus();
|
|
526
546
|
};
|
|
527
547
|
const show = (v) => {
|
|
528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
617
|
+
if (view2.kind === "input") {
|
|
582
618
|
if (e.key === "Enter") {
|
|
583
619
|
e.preventDefault();
|
|
584
|
-
save(
|
|
620
|
+
save(view2.action);
|
|
585
621
|
}
|
|
586
622
|
return;
|
|
587
623
|
}
|
|
588
|
-
if ((e.key === "ArrowLeft" || e.key === "Backspace") &&
|
|
624
|
+
if ((e.key === "ArrowLeft" || e.key === "Backspace") && stack2.length > 1) {
|
|
589
625
|
e.preventDefault();
|
|
590
626
|
return back();
|
|
591
627
|
}
|
|
592
|
-
const list =
|
|
593
|
-
|
|
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 !==
|
|
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: () =>
|
|
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
|
-
|
|
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
|
},
|