1import { IsMobile } from '$lib/hooks/is-mobile.svelte.js';
 2import { getContext, setContext } from 'svelte';
 3import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js';
 4
 5type Getter<T> = () => T;
 6
 7export type SidebarStateProps = {
 8	/**
 9	 * A getter function that returns the current open state of the sidebar.
10	 * We use a getter function here to support `bind:open` on the `Sidebar.Provider`
11	 * component.
12	 */
13	open: Getter<boolean>;
14
15	/**
16	 * A function that sets the open state of the sidebar. To support `bind:open`, we need
17	 * a source of truth for changing the open state to ensure it will be synced throughout
18	 * the sub-components and any `bind:` references.
19	 */
20	setOpen: (open: boolean) => void;
21};
22
23class SidebarState {
24	readonly props: SidebarStateProps;
25	open = $derived.by(() => this.props.open());
26	openMobile = $state(false);
27	setOpen: SidebarStateProps['setOpen'];
28	#isMobile: IsMobile;
29	state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
30
31	constructor(props: SidebarStateProps) {
32		this.setOpen = props.setOpen;
33		this.#isMobile = new IsMobile();
34		this.props = props;
35	}
36
37	// Convenience getter for checking if the sidebar is mobile
38	// without this, we would need to use `sidebar.isMobile.current` everywhere
39	get isMobile() {
40		return this.#isMobile.current;
41	}
42
43	// Event handler to apply to the `<svelte:window>`
44	handleShortcutKeydown = (e: KeyboardEvent) => {
45		if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
46			e.preventDefault();
47			this.toggle();
48		}
49	};
50
51	setOpenMobile = (value: boolean) => {
52		this.openMobile = value;
53	};
54
55	toggle = () => {
56		return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
57	};
58}
59
60const SYMBOL_KEY = 'scn-sidebar';
61
62/**
63 * Instantiates a new `SidebarState` instance and sets it in the context.
64 *
65 * @param props The constructor props for the `SidebarState` class.
66 * @returns  The `SidebarState` instance.
67 */
68export function setSidebar(props: SidebarStateProps): SidebarState {
69	return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
70}
71
72/**
73 * Retrieves the `SidebarState` instance from the context. This is a class instance,
74 * so you cannot destructure it.
75 * @returns The `SidebarState` instance.
76 */
77export function useSidebar(): SidebarState {
78	return getContext(Symbol.for(SYMBOL_KEY));
79}