Repository navigation
Expand file tree
/
Copy pathcommandPaletteStore.ts
More file actions
77 lines (68 loc) · 2.61 KB
/
Copy pathcommandPaletteStore.ts
File metadata and controls
77 lines (68 loc) · 2.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { useEffect } from "react";
import { create } from "zustand";
import {
type CommandPaletteUiAction,
type CommandPaletteUiState,
reduceCommandPaletteUiState,
} from "./components/CommandPalette.logic";
import { getWorkspaceFocusRequestVersion } from "./lib/workspaceFocus";
const INITIAL_STATE: CommandPaletteUiState = {
open: false,
mode: "command",
openIntent: null,
session: 0,
};
export const useCommandPaletteStore = create<CommandPaletteUiState>(() => INITIAL_STATE);
let returnFocus: { element: HTMLElement | null; href: string; focusRequest: number } | null = null;
export function dispatchCommandPalette(action: CommandPaletteUiAction): void {
const state = useCommandPaletteStore.getState();
const next = reduceCommandPaletteUiState(state, action);
if (!state.open && next.open) {
// The workspace turns inert before the popup mounts, which blurs the opener.
returnFocus = {
element: document.activeElement instanceof HTMLElement ? document.activeElement : null,
href: window.location.href,
focusRequest: getWorkspaceFocusRequestVersion(),
};
}
useCommandPaletteStore.setState(next, true);
}
/**
* Where the closing palette should return focus: `false` when a command navigated or asked
* for workspace focus (the destination owns it), else the opener, or `true` for the default.
*/
export function getCommandPaletteReturnFocus(): HTMLElement | boolean {
if (
!returnFocus ||
window.location.href !== returnFocus.href ||
getWorkspaceFocusRequestVersion() !== returnFocus.focusRequest
)
return false;
const { element } = returnFocus;
return element?.isConnected && !element.matches(":disabled") ? element : true;
}
// The palette is only mounted on app-shell routes (not /pair, sign-in or
// landings), so its shortcuts must stay inert elsewhere.
let mountedPalettes = 0;
export function isCommandPaletteMounted(): boolean {
return mountedPalettes > 0;
}
/**
* Marks the palette as mounted while the calling component is. An unmounted
* palette cannot stay open, so the last unmount resets the store.
*/
export function useCommandPaletteMount(): void {
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- The palette's mount gates its global shortcuts, and leaving the route while it is open calls no close handler, so only unmount can reset the store.
useEffect(() => {
mountedPalettes += 1;
return () => {
mountedPalettes -= 1;
if (mountedPalettes === 0) {
useCommandPaletteStore.setState(
(state) => ({ ...INITIAL_STATE, session: state.session + 1 }),
true,
);
}
};
}, []);
}