import type { ControlModel, ControlModelBinding, ControlModelSectionConfig, ControlSchema, ControlSectionConfig, ControlSidebarConfig, ControlViewConfig, ControlWorkspaceOptions, } from './controlSchemaTypes' import { normalizeControlDefinition, } from './controlSchemaNormalize' import { omitUndefined, } from './controlSchemaUtils' export function isControlModelBinding( model: unknown, ): model is TBinding { return ( typeof model === 'object' && model !== null && 'controls' in model && 'controlOptions' in model ) } export function resolveControlModel( model: ControlModel | ControlModelBinding | undefined, ): ControlModelBinding | undefined { if (model === undefined) return undefined return isControlModelBinding(model) ? model : defineControlModel(model) } /** * Create a complete workspace component binding from a simple controls data model for ControlWorkspaceView, generated forms, and sidebars. * * @example Recommended * ```ts * const workspaceModel = defineControlModel({ * controls: { threshold: { type: 'number', default: 0.05 } }, * }) * ``` */ export function defineControlModel(model: ControlModel): ControlModelBinding { const { controls: rootControls, sections: rootSections, views: modelViews, components, componentBindings, componentProps, componentPropsById, ...baseOptions } = model const resolvedComponentBindings = componentBindings ?? components const controls: ControlSchema = {} const views: Record = {} const sections: Record = {} const defaultViewId = model.view ?? 'default' const defaultSectionId = model.section ?? 'controls' if (rootControls) { registerSectionOptions(sections, defaultSectionId, { id: defaultSectionId, label: model.sectionLabel, title: model.sectionLabel, columns: model.columns, }) appendModelControls(controls, rootControls, defaultViewId, defaultSectionId) } for (const [sectionId, section] of Object.entries(rootSections ?? {})) { registerSectionOptions(sections, sectionId, controlModelSectionOptions(section)) appendModelControls(controls, section.controls, defaultViewId, sectionId, section.sidebar) } for (const [viewId, view] of Object.entries(modelViews ?? {})) { const { controls: viewControls, sections: viewSections, section, sectionLabel, sectionDescription, ...viewConfig } = view views[viewId] = omitUndefined(viewConfig) if (viewControls) { const sectionId = section ?? `${viewId}-controls` const label = sectionLabel ?? view.label registerSectionOptions(sections, sectionId, { id: sectionId, label, title: label, description: sectionDescription, columns: model.columns, }) appendModelControls(controls, viewControls, viewId, sectionId) } for (const [sectionKey, sectionConfig] of Object.entries(viewSections ?? {})) { const sectionId = sectionConfig.id ?? `${viewId}-${sectionKey}` registerSectionOptions(sections, sectionId, controlModelSectionOptions(sectionConfig)) appendModelControls(controls, sectionConfig.controls, viewId, sectionId, sectionConfig.sidebar) } } const controlOptions: ControlWorkspaceOptions = { ...baseOptions, } if (Object.keys(views).length > 0) { controlOptions.views = views } if (Object.keys(sections).length > 0) { controlOptions.sections = sections } return { controls, controlOptions, ...(resolvedComponentBindings === undefined ? {} : { componentBindings: resolvedComponentBindings }), ...(componentProps === undefined ? {} : { componentProps }), ...(componentPropsById === undefined ? {} : { componentPropsById }), } } function appendModelControls( target: ControlSchema, source: ControlSchema, viewId: string, sectionId: string, sidebar?: boolean | ControlSidebarConfig, ): void { for (const [name, input] of Object.entries(source)) { if (Object.prototype.hasOwnProperty.call(target, name)) { throw new Error( `Duplicate control "${name}" in defineControlModel(). Control names must be unique across views and sections.`, ) } const definition = normalizeControlDefinition(input) target[name] = omitUndefined({ ...definition, view: definition.view ?? viewId, section: definition.section ?? sectionId, sidebar: definition.sidebar ?? sidebar, }) } } function controlModelSectionOptions(section: ControlModelSectionConfig): ControlSectionConfig { const { controls: _controls, sidebar: _sidebar, ...options } = section return omitUndefined(options) } function registerSectionOptions( target: Record, sectionId: string, options: ControlSectionConfig, ): void { target[sectionId] = omitUndefined({ ...(target[sectionId] ?? {}), ...options, id: sectionId, }) }