UNPKG

@stackoverfloweth/prefect-design

Version:

A collection of low-level Vue components.

183 lines (149 loc) 4.92 kB
import { computed, ComputedRef, inject, InjectionKey, MaybeRefOrGetter, provide, reactive, readonly, Ref, ref, toRef } from 'vue' import { CascadePanelsNotFound, PanelsNotProvided } from '@/models/cascade' import { isDefined } from '@/utilities' export const cascadePanelsKey: InjectionKey<UseCascadePanels> = Symbol('UseCascadePanels') export type CascadePanelId = string | symbol | number export type CascadeState = Record<CascadePanelId, boolean> export type CascadePanel = { id: CascadePanelId, level?: number, } function getInjectedCascadePanels(): UseCascadePanels { const cascadePanels = inject(cascadePanelsKey) if (!cascadePanels) { throw new CascadePanelsNotFound() } return cascadePanels } export type UseCascadePanels = { openPanels: ComputedRef<CascadePanel[]>, panels: Ref<CascadePanel[]>, state: Readonly<CascadeState>, isOpen: Ref<boolean>, getPanelById: (id: CascadePanelId) => CascadePanel | undefined, getPanelIsOpenById: (id: CascadePanelId) => boolean, openPanelById: (id: CascadePanelId) => void, closePanelById: (id: CascadePanelId) => void, togglePanelById: (id: CascadePanelId) => void, closeAll: () => void, close: () => void, open: () => void, toggle: () => void, } /** * Provides a mechanism to manage a cascade of panels, each identified by a unique id, with operations to open, close, and toggle their states. * * @param {CascadePanel[]} panelsRefOrGetter - Initial array of panel definitions. * @returns {UseCascadePanels} - An object including: * - `panels`: Ref array of panel definitions. * - `openPanels`: Computed array of open panels. * - `state`: Readonly object tracking the open/close state of each panel by id. * - `isOpen`: Ref boolean indicating whether the cascade panel group is open. * - `getPanelIsOpenById`: Function to get the open state of a panel by id. * - `getPanelById`: Function to get a panel by id. * - `openPanelById`: Function to open a panel by id. * - `closePanelById`: Function to close a panel by id. * - `togglePanelById`: Function to toggle the open state of a panel by id. * - `closeAll`: Function to close all panels. * - `close`, `open`, `toggle`: Functions to control the overall cascade state. */ export function useCascadePanels(panelsRefOrGetter?: MaybeRefOrGetter<CascadePanel[]>): UseCascadePanels { if (!panelsRefOrGetter) { try { return getInjectedCascadePanels() } catch { throw new PanelsNotProvided() } } const panels = toRef(panelsRefOrGetter) const state = reactive<CascadeState>({}) const isOpen = ref(false) const openPanels = computed(() => panels.value.filter((panel) => state[panel.id])) function closePanelsAtOrAboveLevel(level?: number): void { const panelsAtLevel = panels.value.filter((panel) => panel.level === level) panelsAtLevel.map((panel) => panel.id).forEach(closePanelById) } function getPanelIsOpenById(id: CascadePanelId): boolean { return state[id] } function getPanelById(id: CascadePanelId): CascadePanel | undefined { return panels.value.find((panel) => panel.id === id) } function openPanelById(id: CascadePanelId): void { const panel = getPanelById(id) if (!panel) { console.warn(`Panel with id "${String(id)}" not found.`) return } closePanelsAtOrAboveLevel(panel.level) state[id] = true if (!isOpen.value) { open() } } function closePanelById(id: CascadePanelId): void { const panel = getPanelById(id) if (!panel) { console.warn(`Panel with id "${String(id)}" not found.`) return } state[id] = false if (isDefined(panel.level)) { closePanelsAtOrAboveLevel(panel.level + 1) } if (openPanels.value.length === 0) { close() } } function togglePanelById(id: CascadePanelId): void { if (state[id]) { closePanelById(id) } else { openPanelById(id) } } function close(): void { isOpen.value = false } function open(): void { if (openPanels.value.length === 0) { openFirstPanelAtLevel() } isOpen.value = true } function toggle(): void { if (isOpen.value) { close() } else { open() } } function closeAll(): void { closePanelsAtOrAboveLevel() } function openFirstPanelAtLevel(level: number = 0): void { const panelsAtLevel = panels.value.filter((panel) => panel.level === level) if (panelsAtLevel.length === 0) { return } const [panel] = panelsAtLevel openPanelById(panel.id) } const cascadePanels: UseCascadePanels = { panels, openPanels, isOpen, state: readonly(state), getPanelIsOpenById, getPanelById, openPanelById, closePanelById, togglePanelById, closeAll, close, open, toggle, } provide(cascadePanelsKey, cascadePanels) return cascadePanels }