@stackoverfloweth/prefect-design
Version:
A collection of low-level Vue components.
183 lines (149 loc) • 4.92 kB
text/typescript
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
}