import { computed } from 'vue' import type { ComputedRef } from 'vue' import type { PageSelectorItem, PillNavItem } from '../types' import type { PluginWorkspaceViewEmit, PluginWorkspaceViewProps, } from '../components/PluginWorkspaceView.props' import { usePluginWorkspaceControls } from '../components/PluginWorkspaceView.controls' import { usePluginWorkspaceNavigation } from '../components/PluginWorkspaceView.navigation' import { getPluginWorkspaceSidebarSlotNames, getPluginWorkspaceTopBarSlotNames, hasPluginWorkspaceSidebarSurface, } from '../components/PluginWorkspaceView.shell' import type { PluginWorkspaceControlsModel, PluginWorkspaceShellModel, PluginWorkspaceSidebarModel, } from './pluginWorkspaceModel' /** * Loosely-typed view of a component's `useSlots()` result: only slot presence and names are * read here, so this accepts any `defineSlots()` shape without fighting Vue's `Slot` render * function type. Mirrors the `SlotKeyMap` helper type in PluginWorkspaceView.shell.ts. */ export type PluginWorkspaceSlotsLike = Readonly> export interface UsePluginWorkspaceOptions { /** PluginWorkspaceView's own reactive props object (from `defineProps`). */ props: PluginWorkspaceViewProps /** PluginWorkspaceView's own slots (from `useSlots()`), used to detect provided passthrough slots. */ slots: PluginWorkspaceSlotsLike /** PluginWorkspaceView's own typed `emit` function (from `defineEmits()`). */ emit: PluginWorkspaceViewEmit } export interface UsePluginWorkspaceReturn { /** Model consumed by WorkspaceShellInternal's `shell` prop. */ shell: ComputedRef /** Model consumed by WorkspaceSidebarInternal's `sidebar` prop. */ sidebar: ComputedRef /** Model consumed by WorkspaceControlsInternal's `controls` prop, and by the raw `default` slot. */ controls: ComputedRef /** Whether AppLayout should render a sidebar surface at all. */ hasSidebarSurface: ComputedRef /** Applies an AppSidebar `update:model-value` payload to the shared control values and emits `update:modelValue`/`update:values`. */ handleControlValuesUpdate: (values: Record) => void /** Resolves an AppTopBar `page-selector-select` payload into the active view and emits `page-selector-select`. */ handlePageSelectorSelect: (page: PageSelectorItem) => void /** Resolves an AppTopBar `pill-select` payload into the active view and emits `pill-select`. */ handlePillSelect: (item: PillNavItem) => void } /** * Maps PluginWorkspaceView's flat 72-prop surface into the three typed model objects * (`shell`, `sidebar`, `controls`) rendered by its internal pieces * (WorkspaceShellInternal, WorkspaceSidebarInternal, WorkspaceControlsInternal). Reuses the * existing `usePluginWorkspaceControls` / `usePluginWorkspaceNavigation` composables and the * PluginWorkspaceView.shell slot-detection helpers, so it changes only how that state is * assembled and exposed, not the underlying behavior. * * @example Recommended * ```ts * const props = withDefaults(defineProps(), { ... }) * const emit = defineEmits() * const slots = useSlots() * const { shell, sidebar, controls } = usePluginWorkspace({ props, slots, emit }) * ``` */ export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePluginWorkspaceReturn { const { props, slots, emit } = options const controlsState = usePluginWorkspaceControls({ model: () => props.model, controls: () => props.controls, controlOptions: () => props.controlOptions ?? {}, modelValue: () => props.modelValue, values: () => props.values, componentBindings: () => props.componentBindings, componentProps: () => props.componentProps, componentPropsById: () => props.componentPropsById, emitValues: (values) => { emit('update:modelValue', values) emit('update:values', values) }, }) const navigationState = usePluginWorkspaceNavigation({ activeView: () => props.activeView, defaultActiveView: () => props.defaultActiveView, currentPillId: () => props.currentPillId, currentPageSelectorId: () => props.currentPageSelectorId, pillNav: () => props.pillNav, pageSelector: () => props.pageSelector, panelIds: () => Object.keys(props.panels ?? {}), generatedPillNav: controlsState.generatedPillNav, generatedDefaultView: controlsState.generatedDefaultView, emitActiveView: viewId => emit('update:activeView', viewId), emitPageSelectorSelect: page => emit('page-selector-select', page), emitPillSelect: item => emit('pill-select', item), }) const topBarSlotNames = computed(() => getPluginWorkspaceTopBarSlotNames(slots)) const sidebarSlotNames = computed(() => getPluginWorkspaceSidebarSlotNames(slots)) const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({ showSidebar: props.showSidebar ?? true, panels: props.panels ?? {}, forms: props.forms ?? {}, model: props.model, controls: props.controls, showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false, sidebarContentId: props.sidebarContentId, sidebarTitle: props.sidebarTitle, sidebarSubtitle: props.sidebarSubtitle, sidebarBadge: props.sidebarBadge, hasSidebarSlot: Boolean(slots.sidebar), sidebarSlotCount: sidebarSlotNames.value.length, })) const controls = computed(() => ({ activeView: navigationState.resolvedActiveView.value, setActiveView: navigationState.setActiveView, values: controlsState.internalControlValues, componentBindings: controlsState.resolvedComponentBindings.value, componentBindingsById: controlsState.resolvedComponentBindingsById.value, componentProps: controlsState.resolvedComponentProps.value, componentPropsById: controlsState.resolvedComponentPropsById.value, })) const shell = computed(() => ({ sidebarPosition: props.sidebarPosition ?? 'left', sidebarWidth: props.sidebarWidth, floating: props.floating ?? true, responsiveSidebar: props.responsiveSidebar ?? true, sidebarToggleLabel: props.sidebarToggleLabel ?? 'Open workspace sidebar', sidebarCloseLabel: props.sidebarCloseLabel ?? 'Close workspace sidebar', hasSidebarSurface: hasSidebarSurface.value, title: props.title ?? 'Workspace', subtitle: props.subtitle, topBarVariant: props.topBarVariant ?? 'card', homePath: props.homePath ?? '/', showLogo: props.showLogo ?? true, pageSelector: props.pageSelector, currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value, pillNav: navigationState.resolvedPillNav.value, currentPillId: navigationState.resolvedCurrentPillId.value, showThemeToggle: props.showThemeToggle ?? true, showSettings: props.showSettings ?? true, settingsConfig: props.settingsConfig, showStandaloneLabel: props.showStandaloneLabel ?? false, standaloneLabel: props.standaloneLabel ?? 'Standalone', accountMenu: props.accountMenu, showNotifications: props.showNotifications ?? false, hasNotificationDot: props.hasNotificationDot ?? false, showAdmin: props.showAdmin ?? false, adminPath: props.adminPath ?? '/admin', userName: props.userName, userInitial: props.userInitial, userEmail: props.userEmail, topBarSlotNames: topBarSlotNames.value, controls: controls.value, })) const sidebar = computed(() => ({ title: props.sidebarTitle, subtitle: props.sidebarSubtitle, badge: props.sidebarBadge, panels: props.panels ?? {}, activeView: navigationState.resolvedActiveView.value, floating: false, dense: props.dense ?? true, width: props.sidebarWidth, side: props.sidebarPosition ?? 'left', forms: props.forms ?? {}, contentId: props.sidebarContentId, showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== undefined, model: props.model, controlSchema: props.controls, controlOptions: props.controlOptions ?? {}, values: controlsState.internalControlValues, formEnhancements: props.formEnhancements, showFormActions: props.showFormActions ?? false, formLoading: props.formLoading ?? false, formDisabled: props.formDisabled ?? false, formReadonly: props.formReadonly ?? false, formSize: props.formSize ?? 'sm', collapsible: props.sidebarCollapsible ?? true, collapsed: props.sidebarCollapsed, defaultCollapsed: props.defaultSidebarCollapsed ?? false, collapsedWidth: props.sidebarCollapsedWidth ?? '3rem', collapseButtonLabel: props.sidebarCollapseButtonLabel ?? 'Collapse sidebar', expandButtonLabel: props.sidebarExpandButtonLabel ?? 'Expand sidebar', slotNames: sidebarSlotNames.value, })) return { shell, sidebar, controls, hasSidebarSurface, handleControlValuesUpdate: controlsState.handleControlValuesUpdate, handlePageSelectorSelect: navigationState.handlePageSelectorSelect, handlePillSelect: navigationState.handlePillSelect, } }