diff --git a/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigRegistry.ts b/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigRegistry.ts new file mode 100644 index 0000000..e146f11 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigRegistry.ts @@ -0,0 +1,491 @@ +/** + * ElementConfigRegistry + * + * Central registry for managing element configurations. + * Provides methods to register, retrieve, and apply element configs. + * + * @module noodl-editor/models/ElementConfigs + * @since 1.2.0 + */ + +import type { + ElementConfig, + VariantConfig, + SizeConfig, + RegisterConfigOptions, + ConfigValidationResult, + ApplyVariantParams, + ResolveStylesParams, + StyleResolutionResult, + CSSValue +} from './ElementConfigTypes'; + +/** + * Minimal node interface for applying configs + * This represents the shape we need from NodeModel + */ +interface NodeLike { + /** Node type identifier */ + type: string; + /** Node unique ID */ + id: string; + /** Node parameters/properties */ + parameters: Record; +} + +/** + * Registry for element configurations + * Singleton pattern - use ElementConfigRegistry.instance + */ +export class ElementConfigRegistry { + private static _instance: ElementConfigRegistry; + private configs: Map; + + private constructor() { + this.configs = new Map(); + } + + /** + * Get the singleton instance + */ + static get instance(): ElementConfigRegistry { + if (!ElementConfigRegistry._instance) { + ElementConfigRegistry._instance = new ElementConfigRegistry(); + } + return ElementConfigRegistry._instance; + } + + /** + * Register a new element configuration + * + * @param config - The element configuration to register + * @param options - Registration options + * @returns True if registered successfully, false if already exists and override is false + * + * @example + * ```typescript + * ElementConfigRegistry.instance.register(ButtonConfig); + * ``` + */ + register(config: ElementConfig, options: RegisterConfigOptions = {}): boolean { + const { override = false, validate = true } = options; + + // Validate if requested + if (validate) { + const validation = this.validate(config); + if (!validation.valid) { + console.error(`[ElementConfigRegistry] Invalid config for ${config.nodeType}:`, validation.errors); + return false; + } + + if (validation.warnings.length > 0) { + console.warn(`[ElementConfigRegistry] Warnings for ${config.nodeType}:`, validation.warnings); + } + } + + // Check if already exists + const exists = this.configs.has(config.nodeType); + if (exists && !override) { + console.warn( + `[ElementConfigRegistry] Config for ${config.nodeType} already exists. Use override: true to replace.` + ); + return false; + } + + // Register the config + this.configs.set(config.nodeType, config); + + console.log( + `[ElementConfigRegistry] ${exists ? 'Updated' : 'Registered'} config for ${config.nodeType} ` + + `(${Object.keys(config.variants).length} variants)` + ); + + return true; + } + + /** + * Get an element configuration by node type + * + * @param nodeType - The node type identifier + * @returns The element config, or undefined if not found + * + * @example + * ```typescript + * const config = ElementConfigRegistry.instance.get('net.noodl.visual.button'); + * ``` + */ + get(nodeType: string): ElementConfig | undefined { + return this.configs.get(nodeType); + } + + /** + * Check if a config exists for a node type + * + * @param nodeType - The node type identifier + * @returns True if a config exists + */ + has(nodeType: string): boolean { + return this.configs.has(nodeType); + } + + /** + * Get all variant names for a node type + * + * @param nodeType - The node type identifier + * @returns Array of variant names, or empty array if config not found + * + * @example + * ```typescript + * const variants = ElementConfigRegistry.instance.getVariants('net.noodl.visual.button'); + * // Returns: ['primary', 'secondary', 'outline', 'ghost', 'destructive', 'link'] + * ``` + */ + getVariants(nodeType: string): string[] { + const config = this.configs.get(nodeType); + if (!config) return []; + return Object.keys(config.variants); + } + + /** + * Get all size preset names for a node type + * + * @param nodeType - The node type identifier + * @returns Array of size names, or empty array if no sizes defined + */ + getSizes(nodeType: string): string[] { + const config = this.configs.get(nodeType); + if (!config || !config.sizes) return []; + return Object.keys(config.sizes); + } + + /** + * Get a specific variant configuration + * + * @param nodeType - The node type identifier + * @param variantName - The variant name + * @returns The variant config, or undefined if not found + */ + getVariant(nodeType: string, variantName: string): VariantConfig | undefined { + const config = this.configs.get(nodeType); + if (!config) return undefined; + return config.variants[variantName]; + } + + /** + * Get a specific size configuration + * + * @param nodeType - The node type identifier + * @param sizeName - The size name + * @returns The size config, or undefined if not found + */ + getSize(nodeType: string, sizeName: string): SizeConfig | undefined { + const config = this.configs.get(nodeType); + if (!config || !config.sizes) return undefined; + return config.sizes[sizeName]; + } + + /** + * Apply default styles to a node + * This should be called when a new node is created + * + * @param node - The node model to apply defaults to + * @returns True if defaults were applied, false if no config found + * + * @example + * ```typescript + * // In NodeModel constructor or creation hook: + * ElementConfigRegistry.instance.applyDefaults(newNode); + * ``` + */ + applyDefaults(node: NodeLike): boolean { + const config = this.configs.get(node.type); + if (!config) return false; + + // Apply default CSS properties to node parameters + for (const [property, value] of Object.entries(config.defaults)) { + // Only apply if the property doesn't already have a value + if (node.parameters[property] === undefined) { + node.parameters[property] = value; + } + } + + // Set default variant if defined in defaults + if (config.defaults['_variant'] && !node.parameters['_variant']) { + node.parameters['_variant'] = config.defaults['_variant']; + } + + console.log(`[ElementConfigRegistry] Applied defaults to ${node.type} (node ${node.id})`); + return true; + } + + /** + * Apply a variant to a node + * + * @param node - The node model to apply the variant to + * @param params - Variant application parameters + * @returns True if variant was applied, false if config or variant not found + * + * @example + * ```typescript + * ElementConfigRegistry.instance.applyVariant(buttonNode, { + * variantName: 'secondary', + * preserveUserOverrides: true + * }); + * ``` + */ + applyVariant(node: NodeLike, params: ApplyVariantParams | string): boolean { + const variantName = typeof params === 'string' ? params : params.variantName; + const size = typeof params === 'object' ? params.size : undefined; + const preserveUserOverrides = typeof params === 'object' ? params.preserveUserOverrides !== false : true; + + const config = this.configs.get(node.type); + if (!config) { + console.warn(`[ElementConfigRegistry] No config found for ${node.type}`); + return false; + } + + const variant = config.variants[variantName]; + if (!variant) { + console.warn(`[ElementConfigRegistry] Variant "${variantName}" not found for ${node.type}`); + return false; + } + + // Store user overrides if preserving + const userOverrides: Record = {}; + if (preserveUserOverrides) { + // Detect which properties were user-modified + // (properties not in defaults or previous variant) + const previousVariantName = node.parameters['_variant']; + const previousVariant = previousVariantName ? config.variants[previousVariantName] : null; + + for (const key in node.parameters) { + if (key.startsWith('_')) continue; // Skip system properties + + const isFromDefaults = config.defaults[key] !== undefined; + const isFromPreviousVariant = previousVariant && previousVariant[key] !== undefined; + + if (!isFromDefaults && !isFromPreviousVariant) { + userOverrides[key] = node.parameters[key]; + } + } + } + + // Apply variant properties + for (const [property, value] of Object.entries(variant)) { + if (property === 'states') continue; // States are handled separately + if (typeof value === 'string') { + node.parameters[property] = value; + } + } + + // Apply size if specified + if (size && config.sizes) { + const sizeConfig = config.sizes[size]; + if (sizeConfig) { + for (const [property, value] of Object.entries(sizeConfig)) { + node.parameters[property] = value; + } + } + } + + // Restore user overrides + if (preserveUserOverrides) { + for (const [property, value] of Object.entries(userOverrides)) { + node.parameters[property] = value; + } + } + + // Store the variant name + node.parameters['_variant'] = variantName; + if (size) { + node.parameters['_size'] = size; + } + + console.log(`[ElementConfigRegistry] Applied variant "${variantName}" to ${node.type} (node ${node.id})`); + return true; + } + + /** + * Resolve complete styles for a node + * Merges defaults + variant + size + user overrides + * + * @param params - Resolution parameters + * @returns Resolved styles with metadata + * + * @example + * ```typescript + * const result = ElementConfigRegistry.instance.resolveStyles({ + * nodeType: 'net.noodl.visual.button', + * variant: 'primary', + * size: 'md', + * userOverrides: { backgroundColor: '#custom' } + * }); + * ``` + */ + resolveStyles(params: ResolveStylesParams): StyleResolutionResult | null { + const { nodeType, variant, size, userOverrides = {} } = params; + + const config = this.configs.get(nodeType); + if (!config) return null; + + const styles: Record = {}; + const sources: Record = {}; + + // 1. Apply defaults + for (const [property, value] of Object.entries(config.defaults)) { + if (property.startsWith('_')) continue; // Skip system properties + styles[property] = value; + sources[property] = 'default'; + } + + // 2. Apply variant + let variantConfig: VariantConfig | undefined; + if (variant) { + variantConfig = config.variants[variant]; + if (variantConfig) { + for (const [property, value] of Object.entries(variantConfig)) { + if (property === 'states') continue; + if (typeof value === 'string') { + styles[property] = value; + sources[property] = 'variant'; + } + } + } + } + + // 3. Apply size + if (size && config.sizes) { + const sizeConfig = config.sizes[size]; + if (sizeConfig) { + for (const [property, value] of Object.entries(sizeConfig)) { + styles[property] = value; + sources[property] = 'size'; + } + } + } + + // 4. Apply user overrides (highest priority) + for (const [property, value] of Object.entries(userOverrides)) { + styles[property] = value; + sources[property] = 'user'; + } + + return { + styles: { + base: styles, + states: variantConfig?.states + }, + appliedVariant: variant, + appliedSize: size, + hasUserOverrides: Object.keys(userOverrides).length > 0, + sources + }; + } + + /** + * Validate an element configuration + * + * @param config - The config to validate + * @returns Validation result with errors and warnings + */ + validate(config: ElementConfig): ConfigValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + + // Check required fields + if (!config.nodeType) { + errors.push('nodeType is required'); + } + + if (!config.defaults) { + errors.push('defaults object is required'); + } + + if (!config.variants || Object.keys(config.variants).length === 0) { + errors.push('At least one variant is required'); + } + + // Check variant structure + if (config.variants) { + for (const [variantName, variantConfig] of Object.entries(config.variants)) { + if (!variantConfig || typeof variantConfig !== 'object') { + errors.push(`Variant "${variantName}" must be an object`); + } + } + } + + // Warnings for missing common properties + if (config.defaults && !config.defaults['_variant']) { + warnings.push('No default variant specified in defaults._variant'); + } + + return { + valid: errors.length === 0, + errors, + warnings + }; + } + + /** + * Get all registered node types + * + * @returns Array of registered node type identifiers + */ + getRegisteredNodeTypes(): string[] { + return Array.from(this.configs.keys()); + } + + /** + * Get count of registered configs + * + * @returns Number of registered configs + */ + getCount(): number { + return this.configs.size; + } + + /** + * Clear all registered configs + * WARNING: This is mainly for testing, use with caution + */ + clear(): void { + this.configs.clear(); + console.log('[ElementConfigRegistry] Cleared all configs'); + } + + /** + * Get a summary of the registry state + * + * @returns Summary object with counts and node types + */ + getSummary(): { + totalConfigs: number; + nodeTypes: string[]; + configDetails: Array<{ + nodeType: string; + variantCount: number; + sizeCount: number; + hasDescription: boolean; + }>; + } { + const nodeTypes = this.getRegisteredNodeTypes(); + const configDetails = nodeTypes.map((nodeType) => { + const config = this.configs.get(nodeType)!; + return { + nodeType, + variantCount: Object.keys(config.variants).length, + sizeCount: config.sizes ? Object.keys(config.sizes).length : 0, + hasDescription: !!config.description + }; + }); + + return { + totalConfigs: this.configs.size, + nodeTypes, + configDetails + }; + } +} + +// Export singleton instance for convenience +export const registry = ElementConfigRegistry.instance; diff --git a/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigTypes.ts b/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigTypes.ts new file mode 100644 index 0000000..04d18e7 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/ElementConfigs/ElementConfigTypes.ts @@ -0,0 +1,230 @@ +/** + * ElementConfigTypes + * + * Type definitions for the Element Configuration system. + * This system provides default styling, variants, and size presets + * for Noodl's visual nodes (Button, Text, Group, etc.). + * + * @module noodl-editor/models/ElementConfigs + * @since 1.2.0 + */ + +/** + * CSS property values with design token references + * Values can be direct CSS values or CSS variable references like 'var(--token-name)' + */ +export type CSSValue = string; + +/** + * State-specific style overrides for interactive elements + */ +export interface StateConfig { + /** Styles applied on hover state */ + hover?: Record; + + /** Styles applied on active/pressed state */ + active?: Record; + + /** Styles applied on focus state (inputs, buttons) */ + focus?: Record; + + /** Styles applied when element is disabled */ + disabled?: Record; + + /** Styles applied to placeholder text (inputs only) */ + placeholder?: Record; +} + +/** + * A style variant configuration + * Defines the CSS properties and state-specific overrides for a variant + */ +export interface VariantConfig { + /** Base CSS properties for this variant */ + [property: string]: CSSValue | StateConfig | undefined; + + /** Optional state-specific overrides */ + states?: StateConfig; +} + +/** + * Size preset configuration + * Defines CSS property overrides for different size variations (sm, md, lg, xl) + */ +export interface SizeConfig { + /** CSS properties for this size */ + [property: string]: CSSValue; +} + +/** + * Complete element configuration + * Defines defaults, variants, and size presets for a visual node type + */ +export interface ElementConfig { + /** Noodl node type identifier (e.g., 'net.noodl.visual.button') */ + nodeType: string; + + /** Default CSS properties applied on node creation */ + defaults: Record; + + /** Named style variants (e.g., 'primary', 'secondary', 'outline') */ + variants: Record; + + /** Optional size presets (e.g., 'sm', 'md', 'lg', 'xl') */ + sizes?: Record; + + /** Optional description for documentation */ + description?: string; + + /** Optional categories for grouping (e.g., ['button', 'form', 'input']) */ + categories?: string[]; +} + +/** + * Resolved styles for a node + * Result of merging defaults + variant + size + user overrides + */ +export interface ResolvedStyles { + /** Base CSS properties (defaults + variant + size) */ + base: Record; + + /** State-specific style overrides (if applicable) */ + states?: StateConfig; +} + +/** + * Parameters for applying a variant to a node + */ +export interface ApplyVariantParams { + /** The variant name to apply */ + variantName: string; + + /** Optional size to apply simultaneously */ + size?: string; + + /** Whether to preserve user overrides (default: true) */ + preserveUserOverrides?: boolean; +} + +/** + * Parameters for resolving styles for a node + */ +export interface ResolveStylesParams { + /** The node type identifier */ + nodeType: string; + + /** Current variant name */ + variant?: string; + + /** Current size name */ + size?: string; + + /** User-defined CSS overrides */ + userOverrides?: Record; +} + +/** + * Result of style resolution with metadata + */ +export interface StyleResolutionResult { + /** Resolved CSS properties */ + styles: ResolvedStyles; + + /** Which variant was applied */ + appliedVariant?: string; + + /** Which size was applied */ + appliedSize?: string; + + /** Whether user overrides were present */ + hasUserOverrides: boolean; + + /** Source of each property (for debugging) */ + sources?: Record; +} + +/** + * Options for registering a new element config + */ +export interface RegisterConfigOptions { + /** Whether to override existing config with same nodeType */ + override?: boolean; + + /** Whether to validate the config structure */ + validate?: boolean; +} + +/** + * Validation result for element config + */ +export interface ConfigValidationResult { + /** Whether the config is valid */ + valid: boolean; + + /** Validation errors (if any) */ + errors: string[]; + + /** Validation warnings (if any) */ + warnings: string[]; +} + +/** + * Custom variant created by user + * Contains variant configuration plus metadata + */ +export interface CustomVariant { + /** User-defined variant name */ + name: string; + + /** Node type this variant applies to */ + nodeType: string; + + /** The actual variant configuration (styles and states) */ + config: VariantConfig; + + /** Scope: 'project' (this project only) or 'global' (all projects) */ + scope: 'project' | 'global'; + + /** When the variant was created */ + createdAt: Date; + + /** Optional user description */ + description?: string; +} + +/** + * Event emitted when a variant is applied to a node + */ +export interface VariantAppliedEvent { + /** Node ID that was modified */ + nodeId: string; + + /** Node type */ + nodeType: string; + + /** Previous variant (if any) */ + previousVariant?: string; + + /** New variant applied */ + newVariant: string; + + /** Timestamp of the change */ + timestamp: Date; +} + +/** + * Event emitted when element config is registered/updated + */ +export interface ConfigRegisteredEvent { + /** Node type registered */ + nodeType: string; + + /** Number of variants in this config */ + variantCount: number; + + /** Whether this was an update to existing config */ + isUpdate: boolean; + + /** Timestamp of registration */ + timestamp: Date; +} diff --git a/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/ButtonConfig.ts b/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/ButtonConfig.ts new file mode 100644 index 0000000..6169b05 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/ButtonConfig.ts @@ -0,0 +1,250 @@ +/** + * ButtonConfig + * + * Element configuration for Button nodes. + * Defines default styling, 6 variants, and 4 size presets. + * + * @module noodl-editor/models/ElementConfigs/configs + * @since 1.2.0 + */ + +import type { ElementConfig } from '../ElementConfigTypes'; + +/** + * Button element configuration + * Provides modern, accessible button styles with multiple variants + */ +export const ButtonConfig: ElementConfig = { + nodeType: 'net.noodl.visual.button', + + description: 'Interactive button element with multiple style variants and sizes', + + categories: ['button', 'interactive', 'form'], + + // Default properties applied when a new Button is created + defaults: { + // Layout + paddingTop: 'var(--space-2)', + paddingBottom: 'var(--space-2)', + paddingLeft: 'var(--space-4)', + paddingRight: 'var(--space-4)', + + // Typography + fontSize: 'var(--text-sm)', + fontWeight: 'var(--font-medium)', + fontFamily: 'var(--font-sans)', + textAlign: 'center', + lineHeight: 'var(--leading-none)', + + // Border + borderRadius: 'var(--radius-md)', + borderWidth: '0', + borderStyle: 'solid', + + // Display + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + userSelect: 'none', + whiteSpace: 'nowrap', + + // Transitions + transitionProperty: 'background-color, border-color, color, box-shadow, transform', + transitionDuration: '150ms', + transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', + + // Default variant + _variant: 'primary' + }, + + // Size presets + sizes: { + sm: { + paddingTop: 'var(--space-1)', + paddingBottom: 'var(--space-1)', + paddingLeft: 'var(--space-3)', + paddingRight: 'var(--space-3)', + fontSize: 'var(--text-xs)', + height: '32px' + }, + + md: { + paddingTop: 'var(--space-2)', + paddingBottom: 'var(--space-2)', + paddingLeft: 'var(--space-4)', + paddingRight: 'var(--space-4)', + fontSize: 'var(--text-sm)', + height: '40px' + }, + + lg: { + paddingTop: 'var(--space-3)', + paddingBottom: 'var(--space-3)', + paddingLeft: 'var(--space-6)', + paddingRight: 'var(--space-6)', + fontSize: 'var(--text-base)', + height: '48px' + }, + + xl: { + paddingTop: 'var(--space-4)', + paddingBottom: 'var(--space-4)', + paddingLeft: 'var(--space-8)', + paddingRight: 'var(--space-8)', + fontSize: 'var(--text-lg)', + height: '56px' + } + }, + + // Style variants + variants: { + // Primary: Solid background, high emphasis + primary: { + backgroundColor: 'var(--primary)', + color: 'var(--primary-foreground)', + borderWidth: '0', + boxShadow: 'var(--shadow-sm)', + + states: { + hover: { + backgroundColor: 'var(--primary-hover)', + boxShadow: 'var(--shadow-md)' + }, + active: { + transform: 'scale(0.98)', + boxShadow: 'var(--shadow-sm)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + }, + + // Secondary: Subtle background, medium emphasis + secondary: { + backgroundColor: 'var(--secondary)', + color: 'var(--secondary-foreground)', + borderWidth: '0', + boxShadow: 'var(--shadow-sm)', + + states: { + hover: { + backgroundColor: 'var(--secondary-hover)', + boxShadow: 'var(--shadow-md)' + }, + active: { + transform: 'scale(0.98)', + boxShadow: 'var(--shadow-sm)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + }, + + // Outline: Transparent background with border + outline: { + backgroundColor: 'transparent', + color: 'var(--foreground)', + borderWidth: 'var(--border-1)', + borderColor: 'var(--border)', + borderStyle: 'solid', + boxShadow: 'none', + + states: { + hover: { + backgroundColor: 'var(--accent)', + color: 'var(--accent-foreground)', + borderColor: 'var(--accent)' + }, + active: { + transform: 'scale(0.98)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + }, + + // Ghost: Minimal style, subtle hover + ghost: { + backgroundColor: 'transparent', + color: 'var(--foreground)', + borderWidth: '0', + boxShadow: 'none', + + states: { + hover: { + backgroundColor: 'var(--accent)', + color: 'var(--accent-foreground)' + }, + active: { + transform: 'scale(0.98)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + }, + + // Destructive: For dangerous actions (delete, remove, etc.) + destructive: { + backgroundColor: 'var(--destructive)', + color: 'var(--destructive-foreground)', + borderWidth: '0', + boxShadow: 'var(--shadow-sm)', + + states: { + hover: { + backgroundColor: 'var(--destructive-hover)', + boxShadow: 'var(--shadow-md)' + }, + active: { + transform: 'scale(0.98)', + boxShadow: 'var(--shadow-sm)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + }, + + // Link: Text-only style, no background + link: { + backgroundColor: 'transparent', + color: 'var(--primary)', + borderWidth: '0', + boxShadow: 'none', + textDecoration: 'none', + paddingLeft: '0', + paddingRight: '0', + height: 'auto', + + states: { + hover: { + textDecoration: 'underline', + color: 'var(--primary-hover)' + }, + active: { + color: 'var(--primary)' + }, + disabled: { + opacity: '0.5', + cursor: 'not-allowed', + pointerEvents: 'none' + } + } + } + } +}; diff --git a/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/index.ts b/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/index.ts new file mode 100644 index 0000000..a543b46 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/ElementConfigs/configs/index.ts @@ -0,0 +1,19 @@ +/** + * Element Configs + * + * Pre-built element configurations for Noodl's visual nodes. + * Import and register these configs to enable default styling and variants. + * + * @module noodl-editor/models/ElementConfigs/configs + * @since 1.2.0 + */ + +export { ButtonConfig } from './ButtonConfig'; + +// TextConfig will be added next +// export { TextConfig } from './TextConfig'; + +// Other configs to be implemented: +// export { GroupConfig } from './GroupConfig'; +// export { TextInputConfig } from './TextInputConfig'; +// export { ImageConfig } from './ImageConfig'; diff --git a/packages/noodl-editor/src/editor/src/models/ElementConfigs/index.ts b/packages/noodl-editor/src/editor/src/models/ElementConfigs/index.ts new file mode 100644 index 0000000..21557b8 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/ElementConfigs/index.ts @@ -0,0 +1,18 @@ +/** + * ElementConfigs + * + * System for managing default configurations, style variants, and size presets + * for Noodl's visual nodes (Button, Text, Group, Input, etc.). + * + * @module noodl-editor/models/ElementConfigs + * @since 1.2.0 + */ + +// Export all types +export * from './ElementConfigTypes'; + +// Export registry +export { ElementConfigRegistry, registry } from './ElementConfigRegistry'; + +// Configs will be exported once implemented +// export * from './configs';