Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:05:04 +08:00
commit 7afdd6601b
69 changed files with 9552 additions and 0 deletions

197
docs/dial/api-notes.md Normal file
View File

@@ -0,0 +1,197 @@
1. [Home](/)
1. [Dial](/dial)
1. Api Notes
# API Design and Building Blocks
## DialPanel Component
The `DialPanel` component renders dial controls based on schemas. It accepts the following props:
### Props
| Prop | Type | Default | Description |
| `schemas` | `DialSchema[]` | Required | Array of dial schemas to render |
| `groups` | `DialGroupConfig[]` | `undefined` | Optional group configurations |
| `labelLayout` | `LabelPositionT` | `undefined` | Default label position layout for all inputs ("top", "left", "right", "inline", etc.) |
### Label Position Priority
The label position for inputs is determined by the following priority order:
1. **Component-specific**: Label position tags on individual properties (highest priority)
- `@dial-label-top`, `@dial-label-left`, `@dial-label-right`, `@dial-label-inline`
1. **Panel labelLayout**: `labelLayout` prop on DialPanel
1. **Component default**: Individual input component's default behavior (lowest priority)
### Example Usage
```tsx
import { DialPanel, DialProvider } from '@vuer-ai/vuer-uikit';
// With default label layout for all inputs
<DialProvider schemas={schemas}>
<DialPanel
schemas={schemas}
labelLayout="top" // All inputs will have top-aligned labels by default
/>
</DialProvider>
// Without specifying (components use their own defaults)
<DialProvider schemas={schemas}>
<DialPanel schemas={schemas} />
</DialProvider>
```
Individual components can still override the panel's default label layout:
```tsx
interface Props {
/**
* @dial-label-left // This overrides the panel's labelLayout
*/
specialField: number;
}
```
## Building Blocks
We want to specify the property menu without duplicating the code. Here are the basic building blocks:
| Control Entry | Control Group |
| ```jsx
const controlEntry = {
dtype: 'number',
value: 10,
min: 0,
max: 100,
step: 1,
}
```
| ```jsx
const controlGroup = {
tag: 'group',
children: [
controlEntry,
],
layout: 'row',
}
```
|
Now, let's convert this into a react schema that we can create using react.createElement:
```jsx
<Dial.Provider>
<Dial.Row>
<DialInput label="Position" column type="number" key="prop-name" value={10} min={0} max={100} step={1}/>
<DialInput label="Rotation" column type="number" key="prop-name" value={10} min={0} max={100} step={1}/>
<DialInput label="Scale" column type="number" key="prop-name" value={10} min={0} max={100} step={1}/>
</Dial.Row>
</Dial.Provider>
```
this can be written via react createElement below:
```jsx
React.createElement(
Dial.Provider,
null,
React.createElement(
Dial.Row,
null,
React.createElement(DialInput, {
label: "Position",
column: true,
type: "number",
key: "prop-name",
value: 10,
min: 0,
max: 100,
step: 1
}),
React.createElement(DialInput, {
label: "Rotation",
column: true,
type: "number",
key: "prop-name",
value: 10,
min: 0,
max: 100,
step: 1
}),
React.createElement(DialInput, {
label: "Scale",
column: true,
type: "number",
key: "prop-name",
value: 10,
min: 0,
max: 100,
step: 1
})
)
)
```
We can do this via a nested json object using a convenient helper function:
```jsx
function build({tag, children, ...props}) {
return React.createElement(tag, props, children)
}
```
We can then rewrite the schema as:
```jsx
{ name: 'position', dtype: 'vector3', value: [0, 0, 0], min: 0, max: 100, options: [10, 20, 30, 40, 50],
tags: { grouping: 'transform', col: true } }
{ name: 'rotation', dtype: 'euler', value: [0, 0, 0], min: 0, max: 100, options: [10, 20, 30, 40, 50],
tags: { grouping: 'transform', col: true } }
{ name: 'scale', dtype: 'vector3', value: [1, 1, 1], min: 0, max: 100, options: [10, 20, 30, 40, 50],
tags: { grouping: 'transform', col: true } }
```

777
docs/dial/cli-details.md Normal file
View File

@@ -0,0 +1,777 @@
1. [Home](/)
1. [Dial](/dial)
1. Cli Details
# Dial CLI Reference
The `dial-cli` tool is a powerful command-line utility for generating UI schemas from TypeScript interfaces with Dial annotations. This page provides a complete reference for all CLI features and options.
## Installation
The dial-cli is now available as a standalone package for cleaner installation:
```bash
# Install globally (recommended for CLI tools)
npm install -g @vuer-ai/dial-cli
# or
pnpm install -g @vuer-ai/dial-cli
# Or install as a dev dependency
npm install --save-dev @vuer-ai/dial-cli
# or
pnpm add -D @vuer-ai/dial-cli
```
Once installed, the CLI is available directly:
```bash
# If installed globally
dial-cli --help
# If installed as dev dependency
npx dial-cli --help
```
### Benefits of Standalone Package
- **Minimal dependencies** - Only includes what's needed for CLI operation (typescript, react-docgen-typescript)
- **No peer dependency warnings** - Doesn't require React, Tailwind, or other UI dependencies
- **Smaller install size** - ~32KB unpacked vs entire UI kit
- **Independent versioning** - CLI updates don't require UI kit updates
## Command Line Options
### Basic Usage
```bash
dial-cli [options] <files...>
```
### Options Reference
| Option | Alias | Description | Default |
| `--output <dir>` | `-o` | Output directory for generated files | `./metadata` |
| `--verbose` | | Enable verbose output with detailed information | `false` |
| `--quiet` | | Suppress all output except errors | `false` |
| `--remove` | | Remove generated metadata files | `false` |
| `--ignore <props>` | `-i` | Comma-separated list of properties to ignore | - |
| `--help` | `-h` | Display help information | - |
| `--version` | `-v` | Display version information | - |
### Verbose Mode
The `--verbose` flag enables detailed output and generates additional files:
```bash
dial-cli --verbose MyComponent.tsx
```
In verbose mode, dial-cli generates:
- `schema.dial` - Combined dial schema for all components (main output)
- `debug/` - Debug directory containing:
- `component-raw.json` - Raw docgen output
- `component-combined.json` - Enhanced metadata
- `component-schemas.json` - Component schemas
**Note:** Debug files are organized in a `debug/` subdirectory to keep the main output clean. These files are only generated in verbose mode and are useful for debugging schema generation issues.
### Quiet Mode
The `--quiet` flag suppresses all output except errors, useful for CI/CD:
```bash
dial-cli --quiet src/components/*.tsx
```
### Remove Mode
The `--remove` flag cleans up generated metadata files:
```bash
# Remove metadata for specific component
dial-cli --remove MyComponent.tsx
# Remove all metadata in output directory
dial-cli --remove --output ./metadata
```
**Important:** The remove command cleans up both the main `schema.dial` file and any debug files in the `debug/` directory. It supports both the current debug directory structure and legacy file locations for backward compatibility.
### Ignore Properties
Exclude specific properties from schema generation:
```bash
# Ignore single property
dial-cli --ignore ref MyComponent.tsx
# Ignore multiple properties
dial-cli --ignore "ref,key,children" MyComponent.tsx
# Using short alias
dial-cli -i "internalProp,debugValue" Component.tsx
```
## Output Files
### Standard Output (Default)
Without verbose mode, only generates:
- `schema.dial` - Combined schema for all processed components
### Verbose Output
With `--verbose` flag, generates additional files per component:
- `[component]-schemas.json` - Component-specific schema
- `[component]-raw.json` - Raw react-docgen output
- `[component]-combined.json` - Enhanced metadata with dial annotations
- `schema.dial` - Combined schema (always generated)
## Group-Level Configuration
You can apply configuration to entire groups of properties using interface-level JSDoc comments:
### Using @dial-no-wrap
The `@dial-no-wrap` annotation prevents line wrapping for all properties in a group:
```tsx
interface ComponentProps {
/**
* Layout configuration for transform properties
* @dial transform @dial-no-wrap
*/
/** @dial transform @dial-dtype vector3 */
position: number[];
/** @dial transform @dial-dtype euler */
rotation: number[];
/** @dial transform @dial-dtype vector3 */
scale: number[];
}
```
In this example, all properties in the "transform" group (position, rotation, scale) will be displayed on a single line without wrapping.
### Groups in Output Schema
The CLI now generates a `groups` section in the output schema:
```json
{
"component": "ExampleBox",
"schema": [...],
"groups": [
{
"name": "transform",
"noWrap": true
}
]
}
```
This allows the UI to apply group-specific styling and layout configuration.
## Excluding Properties
There are two ways to exclude properties from dial schema generation:
### 1. Using @dial-ignore Annotation
Add `@dial-ignore` to any property's JSDoc comment to exclude it from the generated schema:
```tsx
interface ComponentProps {
/**
* Public property - included in schema
* @dial transform
* @dial-dtype vector3
*/
position: number[];
/**
* Internal state - excluded from schema
* @dial-ignore
*/
_internalCache?: any;
/**
* React ref - excluded from schema
* @dial-ignore
*/
ref?: React.Ref<HTMLDivElement>;
}
```
### 2. Using CLI --ignore Option
The `-i` or `--ignore` option allows you to exclude properties by name or pattern at runtime:
```bash
# Exclude specific properties
dial-cli -i className -i style Component.tsx
# Exclude using comma-separated list
dial-cli --ignore ref,key,id Component.tsx
# Exclude using glob patterns
dial-cli -i "*Style" -i "on*" -i "_*" Component.tsx
```
#### Glob Pattern Examples
| Pattern | Matches | Example Properties |
| `*Style` | Ends with "Style" | `containerStyle`, `buttonStyle`, `textStyle` |
| `on*` | Starts with "on" | `onClick`, `onChange`, `onSubmit` |
| `_*` | Starts with underscore | `_internal`, `_cache`, `_private` |
| `*Ref` | Ends with "Ref" | `inputRef`, `containerRef`, `buttonRef` |
| `data*` | Starts with "data" | `dataSource`, `dataProvider`, `dataKey` |
## Class/Interface Level Suppression
The `@dial-ignore` annotation can be used at the class or interface level to completely suppress dial schema generation for an entire component and all its properties:
```tsx
/**
* Internal configuration component
* @dial-ignore
*/
interface InternalSettingsProps {
apiKey: string;
debugMode: boolean;
serverUrl: string;
// All properties will be excluded from dial schema
}
/**
* Admin-only component
* @dial-ignore
*/
export const AdminPanel: FC<AdminPanelProps> = ({ ... }) => {
// This component won't appear in dial UI
}
```
This is useful for:
- Internal/utility components that shouldn't be exposed in the UI
- Admin-only or developer-only components
- Components that are still under development
- Helper components that are only used internally
When `@dial-ignore` is used at the class/interface level:
- The entire component is skipped during dial schema generation
- No properties from that component will appear in the dial UI
- Any child properties or nested interfaces are also excluded
## Custom Property Labels
The `@dial-label` annotation allows you to specify custom labels for properties that will be displayed in the UI instead of the default auto-generated labels from the property name:
```tsx
interface ComponentProps {
/**
* Position in 3D space
* @dial transform
* @dial-dtype vector3
* @dial-label 3D Position
*/
pos3d: [number, number, number];
/**
* Background color
* @dial appearance
* @dial-dtype color
* @dial-label Background Color
*/
bgColor: string;
/**
* Enable shadows
* @dial-dtype boolean
* @dial-label Enable Shadow Rendering
*/
shadowsOn: boolean;
}
```
In the generated UI:
- `pos3d` will display as "3D Position" instead of "Pos3d"
- `bgColor` will display as "Background Color" instead of "BgColor"
- `shadowsOn` will display as "Enable Shadow Rendering" instead of "ShadowsOn"
This is particularly useful when property names follow coding conventions (camelCase, abbreviations) but you want more user-friendly labels in the UI.
## Complete Example
Here's a complete example showing exclusion methods and custom labels:
### Component Definition
```tsx
// Box3D.tsx
interface Box3DProps {
/**
* Box dimensions
* @dial geometry
* @dial-dtype vector3
* @dial-min 0.1
* @dial-max 10
* @dial-step 0.1
*/
size: [number, number, number];
/**
* Position in 3D space
* @dial transform
* @dial-dtype vector3
* @dial-min -100
* @dial-max 100
* @dial-step 0.5
*/
position: [number, number, number];
/**
* Material color
* @dial appearance
* @dial-dtype color
*/
color: string;
/**
* Opacity level
* @dial appearance
* @dial-min 0
* @dial-max 1
* @dial-step 0.01
*/
opacity: number;
// Properties to exclude:
/**
* Internal mesh reference
* @dial-ignore
*/
_meshRef?: THREE.Mesh;
/**
* React className - will be excluded via CLI
*/
className?: string;
/**
* React style - will be excluded via CLI
*/
style?: React.CSSProperties;
/**
* Click handler - will be excluded via CLI pattern
*/
onClick?: () => void;
/**
* Change handler - will be excluded via CLI pattern
*/
onChange?: (value: any) => void;
}
```
### CLI Commands
```bash
# Generate schema excluding React-specific props and handlers
dial-cli -i className -i style -i "on*" Box3D.tsx
# Or using comma-separated list
dial-cli --ignore className,style,onClick,onChange Box3D.tsx
# Exclude all private properties and event handlers
dial-cli -i "_*" -i "on*" Box3D.tsx
```
### Generated Schema
The generated schema will only include:
- `size` - Box dimensions control
- `position` - Position control
- `color` - Color picker
- `opacity` - Opacity slider
Excluded properties:
- `_meshRef` - Excluded by `@dial-ignore` annotation
- `className`, `style` - Excluded by CLI `-i` option
- `onClick`, `onChange` - Excluded by CLI pattern `"on*"`
## CLI Options Reference
```bash
dial-cli [options] <files...>
```
### Options
| Option | Short | Description | Example |
| `--output <dir>` | `-o` | Output directory for generated files | `-o ./schemas` |
| `--ignore <prop>` | `-i` | Properties to exclude (supports glob) | `-i className -i "on*"` |
| `--verbose` | | Enable verbose output | `--verbose` |
| `--quiet` | | Suppress output except errors | `--quiet` |
| `--help` | `-h` | Display help message | `--help` |
| `--version` | `-v` | Display version | `--version` |
## Output Files
The CLI generates files with a clean directory structure:
**Main Output:**
- **`schema.dial`** - Combined schemas for all components, ready for UI generation
**Debug Output (verbose mode only):**
- **`debug/component-raw.json`** - Raw AST and JSDoc extraction
- **`debug/component-combined.json`** - Enhanced metadata with dial schemas
- **`debug/component-schemas.json`** - Individual component schemas
This structure keeps your main output directory clean while providing detailed debug information when needed.
## Best Practices
### 1. Combine Both Exclusion Methods
Use `@dial-ignore` for properties that should never be exposed in the UI:
```tsx
/**
* @dial-ignore
*/
_internalState?: any;
```
Use CLI `--ignore` for context-specific exclusions:
```bash
# Exclude React-specific props when generating for non-React environments
dial-cli -i className -i style Component.tsx
```
### 2. Use Patterns for Consistency
Create consistent naming conventions and use patterns:
```bash
# Exclude all private properties, refs, and handlers
dial-cli -i "_*" -i "*Ref" -i "on*" Component.tsx
```
### 3. Create Build Scripts
Add scripts to your `package.json`:
```json
{
"scripts": {
"dial:generate": "dial-cli -o ./schemas -i className,style,key,ref src/components/*.tsx",
"dial:generate:clean": "dial-cli -o ./schemas -i '_*,on*,*Ref,className,style' src/components/*.tsx"
}
}
```
### 4. Document Excluded Properties
When using `@dial-ignore`, add a comment explaining why:
```tsx
/**
* Internal cache for performance optimization
* Not meant to be modified by users
* @dial-ignore
*/
_cache?: Map<string, any>;
```
## Integration Example
Here's how to use the generated schemas in your application:
```tsx
import { DialProvider, DialPanel } from '@vuer-ai/vuer-uikit';
import allSchemas from './schemas/schema.dial';
function App() {
const [props, setProps] = useState({
size: [1, 1, 1],
position: [0, 0, 0],
color: '#ff0000',
opacity: 1
});
const handleValueChange = (name: string, value: any) => {
setProps(prev => ({ ...prev, [name]: value }));
};
// Get the specific component schema from the combined schema file
const box3DSchema = allSchemas.find(s => s.component === 'Box3D');
return (
<DialProvider schemas={[box3DSchema]} onValueChange={handleValueChange}>
<div style={{ display: 'flex' }}>
<Canvas>
<Box3D {...props} />
</Canvas>
<DialPanel schemas={[box3DSchema]} />
</div>
</DialProvider>
);
}
```
## Troubleshooting
### Properties Not Being Excluded
1. **Check annotation syntax**: Ensure `@dial-ignore` is on its own line
1. **Check pattern syntax**: Use quotes for patterns with wildcards: `-i "*Style"`
1. **Check property names**: Property names are case-sensitive
### Generated Schema Is Empty
1. Ensure your interface has JSDoc comments with `@dial` annotations
1. Check that the TypeScript file exports the interface
1. Verify the file path is correct
### Build Errors
1. Ensure `@vuer-ai/dial-cli` is installed
1. Try using the full path: `node_modules/.bin/dial-cli` (if installed locally)
1. Check Node.js version (requires Node 14+)
## Summary
The dial-cli tool provides flexible property exclusion through:
- **`@dial-ignore` annotation** - Permanent, code-level exclusion
- **`--ignore` CLI option** - Runtime, context-specific exclusion
- **Glob patterns** - Flexible pattern-based exclusion
Combine these features to create clean, focused UI schemas that expose only the properties users need to control.

View File

@@ -0,0 +1,187 @@
1. [Home](/)
1. [Dial](/dial)
1. Controlled Dials
# Controlled Dials
The `DialProvider` component supports both controlled and uncontrolled modes, giving you flexibility in how you manage dial state.
## Simple 3D Object Example
transformPositionxyzRenderOrderResetExamplePosition: [0.0, 5.0, 0.0]Render Order: 0```jsx
const schemas = [
{
name: "position",
dtype: "vector3",
value: [1, 2, 3],
//...
}
];
const [values, setValues] = useState({
position: [0, 5, 0],
renderOrder: 0
});
const handleChange = (name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
};
return (
<DialProvider
schemas={schemas}
values={values}
onValueChange={handleChange}
>
<DialPanel schemas={schemas} />
</DialProvider>
);
```
## Full Schema Example from JSON
FooBar Object PropertiestransformPositionxyzRotationxyzScalexyzdisplayVisibleOpacityReset All```
{
"position": [
0,
5,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
],
"visible": true,
"opacity": 1
}
```
```jsx
// Schema data structure (could be loaded from JSON)
const schemaData = {
"FooBar": {
"schemas": [
{
"name": "position",
"dtype": "vector3",
"value": [0, 5, 0],
"min": -10,
"max": 10,
"step": 0.1,
"tags": {
"grouping": "transform",
"noWrap": true
}
},
// ... more schemas
]
}
};
// Initialize values from schemas
const [values, setValues] = useState(() => {
const initial = {};
schemaData.FooBar.schemas.forEach(schema => {
initial[schema.name] = schema.value;
});
return initial;
});
const handleChange = (name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
};
return (
<DialProvider
schemas={schemaData.FooBar.schemas}
values={values}
onValueChange={handleChange}
>
<DialPanel schemas={schemaData.FooBar.schemas} />
</DialProvider>
);
```
## API Reference
### DialProvider Props
| Prop | Type | Description |
| `schemas` | `DialSchema[]` | Array of dial schemas defining the dials |
| `values` | `Record<string, DialValue>` | Current values for controlled mode |
| `initialValues` | `Record<string, DialValue>` | Initial values for uncontrolled mode |
| `onValueChange` | `(name: string, value: DialValue) => void` | Callback when values change |
| `children` | `ReactNode` | Child components |
### Schema Structure
```typescript
interface DialSchema {
name: string; // Unique identifier
dtype: string; // "number", "boolean", "vector3", "select", etc.
value?: any; // Default value
min?: number; // For number/vector inputs
max?: number; // For number/vector inputs
step?: number; // Step size for number/vector inputs
options?: string[]; // For select inputs
tags?: {
grouping?: string; // Group related controls
noWrap?: boolean; // Prevent wrapping in group
// ... other tags
};
}
```
### Common Data Types
- **`number`**: Single numeric value
- **`boolean`**: True/false toggle
- **`vector3`**: 3D vector [x, y, z]
- **`select`**: Dropdown with options
- **`string`**: Text input
### Grouping with Tags
The `tags.grouping` property allows you to organize related controls together. Controls with the same `grouping` value will be visually grouped in the UI.
```json
{
"tags": {
"grouping": "transform",
"noWrap": true
}
}
```

View File

@@ -0,0 +1,129 @@
# Getting Started with Dial
## Overview
Dial is a schema-driven system that "parses JSDoc annotations in your TypeScript/React components" and then "generates UI schemas that describe controls and their properties."
The system automates interactive control creation from TypeScript interfaces, supporting complex types like vectors, tuples, and nested objects.
## Installation
Install the CLI tool using your package manager:
```bash
# Global installation
pnpm install -g @vuer-ai/dial-cli
# Or as a dev dependency
pnpm add -D @vuer-ai/dial-cli
```
## Basic Workflow
### Step 1: Annotate Your Component
Add JSDoc comments with `@dial` tags to your TypeScript component properties:
```typescript
interface BoxProps {
/**
* Box dimensions
* @dial geometry
* @dial-dtype vector3
* @dial-min 0.1
* @dial-max 10
* @dial-step 0.1
*/
size: [number, number, number];
/**
* Material color
* @dial appearance
* @dial-dtype color
*/
color: string;
/**
* Visibility toggle
* @dial visibility
* @dial-dtype boolean
*/
visible: boolean;
/**
* Opacity level
* @dial appearance
* @dial-dtype number
* @dial-min 0
* @dial-max 1
* @dial-step 0.01
*/
opacity: number;
}
```
### Step 2: Generate the Schema
```bash
dial-cli Box.tsx
# Outputs: metadata/schema.dial
```
### Step 3: Use the Generated Schema
```typescript
import { DialProvider, DialPanel } from '@vuer-ai/vuer-uikit';
import allSchemas from './metadata/schema.dial';
function App() {
const boxSchema = allSchemas.find(s => s.component === 'Box');
return (
<DialProvider schemas={[boxSchema]}>
<DialPanel schemas={[boxSchema]} />
</DialProvider>
);
}
```
## Core Annotation Tags
| Tag | Purpose |
|-----|---------|
| `@dial <group>` | Groups related properties together |
| `@dial-dtype <type>` | Specifies control data type |
| `@dial-min/max/step` | Sets numeric constraints |
| `@dial-icon <name>` | Adds Lucide icon to control |
| `@dial-ignore` | Excludes property from schema |
## Supported Data Types
| Type | Purpose | Example |
|------|---------|---------|
| `number` | Basic numeric input | `42` |
| `boolean` | Toggle switch | `true/false` |
| `string` | Text input | `"hello"` |
| `color` | Color picker | `"#ff0000"` |
| `vector3` | 3D vector | `[1, 2, 3]` |
| `euler` | Rotation angles | `[0, 90, 180]` |
| `select` | Dropdown menu | `"option1"` |
## Property Grouping
Organize properties by adding the same group name across multiple fields:
```typescript
interface Props {
/** @dial transform */
position: [number, number, number];
/** @dial transform */
rotation: [number, number, number];
/** @dial appearance */
color: string;
/** @dial appearance */
metalness: number;
}
```

1113
docs/dial/input-types.md Normal file

File diff suppressed because it is too large Load Diff

60
docs/dial/overview.md Normal file
View File

@@ -0,0 +1,60 @@
# Dial: A Schema-Driven Menu System
## Overview
Dial represents a system that generates user interface controls automatically from TypeScript interfaces using JSDoc annotations. This approach eliminates manual UI creation by defining control properties directly in code comments.
## Core Annotation Syntax
The system uses JSDoc comments to specify UI behavior:
**Grouping Controls:**
- `@dial <grouping>` organizes related properties
- `@dial <grouping> @dial-no-wrap` applies group-level settings
**Property Configuration:**
- `@dial-<property> <value>` sets control attributes using hyphen notation
- `@dial-col-<n>` arranges elements in n-column layouts
- `@dial-dtype <type>` specifies data types (vector3, euler, boolean, int, etc.)
- `@dial-min <number>` and `@dial-max <number>` establish value bounds
- `@dial-step <number>` defines increment sizes
- `@dial-options [...]` provides preset values
- `@dial-icon <name>` assigns Lucide icon names
- `@dial-label <text>` customizes property display names
- `@dial-ignore` excludes properties from schema generation
## Example Implementation
A TypeScript interface can be annotated to define a 3D box geometry with transformation controls:
```typescript
interface ExampleBoxProps {
args: ExampleBoxArgs;
position: number[] | null;
rotation: number[] | null;
scale: number[] | null;
hide: boolean;
alphaTest: boolean;
depthTest: boolean;
renderOrder: number;
_internalState?: any;
}
```
Annotations specify constraints, groupings, and visual representation for each property.
## Key Features
**Schema-Driven Architecture:** UI controls generate automatically from annotated TypeScript code, eliminating manual interface creation.
**Grouped Layout System:** Properties organize automatically into sections based on their grouping annotations.
**Integrated State Management:** Built-in state tracking with value change callbacks enables seamless application integration.
## Next Steps
- **Tutorial:** Step-by-step guide for using dial-cli
- **Input Types:** Complete reference of supported input types
- **Controlled Dials:** Advanced usage patterns
- **API Notes:** Detailed API reference
- **CLI Details:** Advanced command-line options

443
docs/dial/testing.md Normal file
View File

@@ -0,0 +1,443 @@
1. [Home](/)
1. [Dial](/dial)
1. Testing
# Testing Dial Annotations
Testing Guidedial-cli[v0.0.22](https://www.npmjs.com/package/@vuer-ai/dial-cli/v/0.0.22)
This guide covers testing strategies for Dial annotations, running the dial-cli test suite, and contributing to dial-cli development.
## Testing Your Dial Annotations
### 1. Validate Generated Schemas
After annotating your components, validate the generated schemas:
```bash
# Generate with verbose output for inspection
dial-cli --verbose MyComponent.tsx
# Check the generated files
cat metadata/schema.dial | jq '.'
```
### 2. Common Validation Checks
#### Check Property Types
Ensure properties have the correct dtype:
```json
{
"name": "position",
"dtype": "vector3", // Should match your annotation
"value": [0, 0, 0]
}
```
#### Verify Constraints
Check min/max/step values are applied:
```json
{
"name": "opacity",
"dtype": "number",
"min": 0,
"max": 1,
"step": 0.01
}
```
#### Validate Grouping
Ensure properties are correctly grouped:
```json
{
"name": "rotation",
"dtype": "euler",
"tags": {
"grouping": "transform"
}
}
```
### 3. Test Type Inheritance
When using interface inheritance or type intersections:
```bash
# Generate schema for inherited types
dial-cli --verbose ExtendedComponent.tsx
# Verify all parent properties are included
jq '.[] | select(.component == "ExtendedComponent") | .schemas[].name' \
metadata/schema.dial
```
### 4. Automated Testing
Create a test script to validate your schemas:
```javascript
// test-dial-schemas.js
const fs = require('fs');
const schema = require('./metadata/schema.dial');
describe('Dial Schemas', () => {
test('should have required properties', () => {
const component = schema.find(c => c.component === 'MyComponent');
expect(component).toBeDefined();
const propNames = component.schemas.map(s => s.name);
expect(propNames).toContain('position');
expect(propNames).toContain('rotation');
expect(propNames).toContain('scale');
});
test('should have correct types', () => {
const component = schema.find(c => c.component === 'MyComponent');
const position = component.schemas.find(s => s.name === 'position');
expect(position.dtype).toBe('vector3');
expect(position.value).toHaveLength(3);
});
test('should inherit group configurations', () => {
const component = schema.find(c => c.component === 'AnimatedBox');
const duration = component.schemas.find(s => s.name === 'duration');
expect(duration.tags?.noWrap).toBe(true);
});
});
```
## Running Dial CLI Tests
The dial-cli package includes a comprehensive test suite for validation.
### Prerequisites
```bash
# Navigate to dial-cli directory
cd packages/vuer-uikit/dial-cli
# Install dependencies
pnpm install
# Build the CLI
pnpm build
```
### Running Tests
```bash
# Run all tests
pnpm test
# Run tests in watch mode for development
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
```
### Test Output
Successful test run shows:
```
PASS spec/inheritance.test.ts
dial-cli inheritance tests
Interface Inheritance
✓ should resolve properties from extended interfaces
✓ should handle deep inheritance chains
Type Inheritance
✓ should resolve properties from type intersections
✓ should handle utility types (Pick, Omit, Partial)
Mixed Inheritance
✓ should handle interface extending type
✓ should handle type intersecting interface
Group configurations
✓ should inherit group-level @dial-no-wrap
dial-cli remove functionality
✓ should remove specific component metadata
✓ should remove all metadata files
Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total
```
## Contributing to Dial CLI
### Development Setup
1. **Fork and Clone**
```bash
git clone https://github.com/your-username/vuer-uikit.git
cd vuer-uikit/packages/vuer-uikit/dial-cli
```
1. **Install Dependencies**
```bash
pnpm install
```
1. **Start Development Mode**
```bash
pnpm dev
# This watches for changes and rebuilds automatically
```
### Writing New Tests
Tests are located in the `spec/` directory:
```
spec/
├── inheritance.test.ts # Main test suite
├── fixtures/ # Test TypeScript files
│ ├── InterfaceInheritance.tsx
│ ├── TypeInheritance.tsx
│ └── MixedInheritance.tsx
└── outputs/ # Expected outputs for comparison
```
#### Adding a Test Case
```typescript
// spec/inheritance.test.ts
test('should handle new feature', () => {
const fixture = join(FIXTURES_DIR, 'NewFeature.tsx');
// Generate schema
execSync(`node "${DIAL_CLI}" --verbose --output "${OUTPUT_DIR}" "${fixture}"`);
// Read and validate
const schemaPath = join(OUTPUT_DIR, 'schema.dial');
const schemas = JSON.parse(readFileSync(schemaPath, 'utf-8'));
// Assertions
expect(schemas).toBeDefined();
expect(schemas[0].component).toBe('NewFeature');
});
```
#### Creating Test Fixtures
```tsx
// spec/fixtures/NewFeature.tsx
import React from 'react';
/**
* Test component for new feature
* @dial config @dial-new-feature
*/
interface NewFeatureProps {
/**
* Test property
* @dial config
* @dial-dtype string
* @dial-new-annotation value
*/
testProp: string;
}
export const NewFeature: React.FC<NewFeatureProps> = ({ testProp }) => {
return <div>{testProp}</div>;
};
```
### Running Tests During Development
```bash
# Run specific test file
pnpm test inheritance.test.ts
# Run tests matching pattern
pnpm test -- --testNamePattern="type intersection"
# Run with debugging
NODE_OPTIONS="--inspect" pnpm test
```
### Debugging Tips
1. **Use Verbose Output**
```bash
dial-cli --verbose TestComponent.tsx
# Check all generated files for debugging
```
1. **Inspect AST**
Add debug logging to see TypeScript AST:
```typescript
console.log('AST Node:', node.kind, node.getText());
```
1. **Test Individual Components**
```bash
# Test a specific fixture
node dist/dial-cli.js --verbose spec/fixtures/TypeInheritance.tsx
```
## Common Issues and Solutions
### Issue: Properties Not Appearing in Schema
**Cause:** Missing `@dial` annotation
**Solution:** Ensure property has at least one `@dial` tag
```tsx
// Won't appear in schema
/** Just a comment */
prop: string;
// Will appear in schema
/** @dial control */
prop: string;
```
### Issue: Type Inheritance Not Working
**Cause:** Types not properly exported or resolved
**Solution:** Ensure all types are exported and accessible
```tsx
// Export types for proper resolution
export type BaseType = { ... };
export interface ExtendedInterface extends BaseType { ... }
```
### Issue: Group Configuration Not Applied
**Cause:** Group-level annotations at wrong position
**Solution:** Place group annotations in interface/type JSDoc
```tsx
/**
* @dial transform @dial-no-wrap // Correct position
*/
interface Props {
// Properties here inherit the configuration
}
```
## Next Steps
- Review [Type Inheritance](/dial/type-inheritance) patterns
- Check [CLI Reference](/dial/cli-reference) for all options
- See [Examples](/dial/examples) for real-world usage
- Read [Troubleshooting](/dial/cli-reference/troubleshooting) for common issues

374
docs/dial/tutorial.md Normal file
View File

@@ -0,0 +1,374 @@
1. [Home](/)
1. [Dial](/dial)
1. Tutorial
# Dial Tutorial: Using dial-cli
Dial CLI Tutorialdial-cli[v0.0.22](https://www.npmjs.com/package/@vuer-ai/dial-cli/v/0.0.22)
This tutorial will guide you through using the dial-cli tool to generate UI controls from TypeScript interfaces with Dial annotations.
## Using the dial-cli Tool
The `dial-cli` is now available as a standalone package for generating Dial schemas from TypeScript files, providing a cleaner installation experience without UI dependencies.
### Installation
```bash
# Install globally (recommended for CLI tools)
npm install -g @vuer-ai/dial-cli
# or
pnpm install -g @vuer-ai/dial-cli
# Check CLI is available
dial-cli --help
```
### Basic Usage
```bash
# Generate schemas from a TypeScript file
dial-cli <input-file> [input-file2...]
# Examples:
dial-cli ./src/components/Box.tsx
# Creates schema.dial in ./metadata directory
dial-cli ./src/components/Box.tsx -o ./schemas
# Outputs to specified directory
# Process multiple files
dial-cli Component1.tsx Component2.tsx
# Specify output directory
dial-cli -o ./metadata MyComponent.tsx
```
### What the CLI Does
The dial-cli tool will:
1. Parse your TypeScript file using the TypeScript compiler API
1. Extract all interfaces and types with Dial annotations
1. Process JSDoc comments following the `@dial` convention
1. Generate JSON schema files that can be directly used with DialPanel
### Output Files
The CLI generates files with a clean directory structure:
**Main Output:**
- `schema.dial` - Combined schemas for all components, ready for UI generation
**Debug Output (verbose mode only):**
- `debug/component-raw.json` - Raw output from react-docgen-typescript
- `debug/component-combined.json` - Enhanced metadata with dial schema information
- `debug/component-schemas.json` - Individual component schemas for debugging
### Local Script
This documentation includes a convenience script for generating metadata:
```bash
# From the dial directory
./generate-dial-metadata.sh
```
This will process the `BoxExample.tsx` file and output metadata to the `metadata/` directory.
## Using Generated Schemas
Once you've generated schemas using dial-cli, you can use them in your application.
The Dial system consists of three main components:
1. **DialProvider** - Manages state for all controls
1. **DialPanel** - Converts schemas to UI components
1. **Input Components** - Individual control types (number, vector, boolean, etc.)
### Component API
**DialPanel** accepts the following props:
```tsx
interface DialPanelProps {
schemas: DialSchema[]; // Array of control schemas
groups?: DialGroupConfig[]; // Optional group configurations
}
```
Usage:
```tsx
// Basic usage with just schemas
<DialPanel schemas={schemas} />
// With group configuration for layout control
<DialPanel schemas={schemas} groups={groups} />
```
### TypeScript Interfaces
The Dial system uses the following main interfaces:
```tsx
// Schema for individual controls
interface DialSchema {
name: string;
dtype: string;
value?: DialValue;
min?: number;
max?: number;
step?: number;
options?: Array<string | number | { label: string; value: string | number }>;
// ... other properties
tags?: {
grouping?: string;
col?: boolean | number;
row?: number;
layout?: string;
labelPosition?: LabelPositionT;
noWrap?: boolean;
};
}
// Group configuration for styling and layout
interface DialGroupConfig {
name: string;
noWrap?: boolean;
[key: string]: unknown;
}
// Complete schema with groups (output from dial-cli)
interface DialSchemaGroup {
component: string;
schema: DialSchema[];
groups?: DialGroupConfig[];
}
// Valid value types
type DialValue = string | number | boolean | number[] | string[] | null | undefined;
```
```tsx
import { DialProvider, DialPanel } from './dial';
const schemas = [
{
name: 'position',
dtype: 'vector3',
value: [0, 0, 0],
min: -10,
max: 10,
tags: { grouping: 'transform', col: true }
},
// ... more schemas
];
function MyComponent() {
const handleValueChange = (name, value) => {
console.log(`${name} changed to`, value);
};
return (
<DialProvider
schemas={schemas}
onValueChange={handleValueChange}
>
<DialPanel schemas={schemas} />
</DialProvider>
);
}
```
### Complete Example with dial-cli
1. **Create a TypeScript file with Dial annotations:**
```tsx
// Box.tsx
interface BoxProps {
/**
* Transform properties displayed on single line
* @dial transform @dial-no-wrap
*/
/** @dial transform @dial-dtype vector3 */
position: [number, number, number];
/** @dial transform @dial-dtype euler */
rotation: [number, number, number];
/**
* Box dimensions
* @dial geometry
* @dial-dtype vector3
* @dial-min 0.1
* @dial-max 10
* @dial-step 0.1
*/
size: [number, number, number];
/**
* Box color
* @dial appearance
* @dial-dtype color
*/
color: string;
}
export const Box: React.FC<BoxProps> = ({ size, position, rotation, color }) => {
// Component implementation
};
```
1. **Generate the schema:**
```bash
dial-cli Box.tsx -o ./schemas
# Creates schemas/schema.dial with groups configuration
```
The generated schema includes group-level settings:
```json
{
"component": "Box",
"schema": [
{ "name": "position", "dtype": "vector3", "tags": { "grouping": "transform", "noWrap": true } },
{ "name": "rotation", "dtype": "euler", "tags": { "grouping": "transform", "noWrap": true } },
{ "name": "size", "dtype": "vector3", "tags": { "grouping": "geometry" } },
{ "name": "color", "dtype": "color", "tags": { "grouping": "appearance" } }
],
"groups": [
{ "name": "transform", "noWrap": true }
]
}
```
1. **Use the generated schema in your app:**
```tsx
import { DialProvider, DialPanel, DialSchemaGroup, DialValue } from '@vuer-ai/vuer-uikit';
import allSchemas from './schemas/schema.dial';
// Get the Box component schema from the combined schema file
const boxSchema = allSchemas.find(s => s.component === 'Box');
function App() {
const [boxProps, setBoxProps] = useState({
position: [0, 0, 0],
rotation: [0, 0, 0],
size: [1, 1, 1],
color: '#ff0000'
});
const handleValueChange = (name: string, value: DialValue) => {
setBoxProps(prev => ({ ...prev, [name]: value }));
};
return (
<DialProvider
schemas={[boxSchema]}
onValueChange={handleValueChange}
>
<div style={{ display: 'flex' }}>
{/* Your 3D scene */}
<Box {...boxProps} />
{/* Auto-generated controls with group configuration */}
<DialPanel schemas={[boxSchema]} />
</div>
</DialProvider>
);
}
```
## Next Steps
- Learn more about [Dial Annotation Syntax](/dial/overview) in the overview
- Explore all [Input Types](/dial/input-types) available in Dial
- See [Controlled Dials](/dial/controlled-dials) for advanced usage
- Check out the [API Notes](/dial/api-notes) for detailed reference
- Read the [CLI Details](/dial/cli-details) for advanced CLI options

View File

@@ -0,0 +1,352 @@
1. [Home](/)
1. [Dial](/dial)
1. Type Inheritance
# Type Inheritance in Dial
Type Inheritance & Compositiondial-cli[v0.0.22](https://www.npmjs.com/package/@vuer-ai/dial-cli/v/0.0.22)
Dial CLI fully supports TypeScript's type system, including interface inheritance, type intersections, and utility types. This allows you to create reusable, composable type definitions while maintaining all Dial annotations and configurations.
## Interface Inheritance
When interfaces extend others, all properties and group configurations are inherited:
### Basic Interface Extension
```tsx
// Base interface with common properties
interface BaseProps {
/**
* Unique identifier
* @dial common
* @dial-dtype string
*/
id: string;
/**
* Visibility control
* @dial common
* @dial-dtype boolean
*/
visible: boolean;
}
// Extended interface inherits all base properties
interface BoxProps extends BaseProps {
/**
* Box dimensions
* @dial geometry
* @dial-dtype vector3
*/
size: [number, number, number];
/**
* Material color
* @dial appearance
* @dial-dtype color
*/
color: string;
}
// BoxProps will have: id, visible, size, color
```
### Deep Inheritance Chains
```tsx
interface Level1 {
/** @dial level1 */
prop1: string;
}
interface Level2 extends Level1 {
/** @dial level2 */
prop2: number;
}
interface Level3 extends Level2 {
/** @dial level3 */
prop3: boolean;
}
// Level3 has all properties from Level1, Level2, and its own
```
## Type Intersections
Type intersections combine multiple types while preserving all annotations and group configurations:
### Basic Type Intersection
```tsx
type TransformType = {
/** @dial transform @dial-dtype vector3 */
position: [number, number, number];
/** @dial transform @dial-dtype euler */
rotation: [number, number, number];
};
type AppearanceType = {
/** @dial appearance @dial-dtype color */
color: string;
/** @dial appearance @dial-dtype number @dial-min 0 @dial-max 1 */
opacity: number;
};
// Combine multiple types
type GameObject = TransformType & AppearanceType & {
/** @dial metadata @dial-dtype string */
name: string;
};
```
### Group Configuration Inheritance
The `@dial-no-wrap` configuration is inherited through type intersections:
```tsx
/**
* Animation properties that should display on one line
* @dial animation @dial-no-wrap
*/
type AnimationType = {
/** @dial animation @dial-dtype number @dial-min 0 @dial-max 10 */
duration: number;
/** @dial animation @dial-dtype string */
easing: string;
/** @dial animation @dial-dtype number @dial-min 0 @dial-max 5 */
delay: number;
};
// Properties from AnimationType inherit noWrap: true
type AnimatedObject = BaseType & AnimationType & {
/** @dial control @dial-dtype boolean */
playing: boolean;
};
```
## Utility Types
Dial supports TypeScript's built-in utility types:
### Pick - Select Specific Properties
```tsx
type FullTransform = {
/** @dial position @dial-dtype number */
x: number;
/** @dial position @dial-dtype number */
y: number;
/** @dial position @dial-dtype number */
z: number;
/** @dial rotation @dial-dtype number-deg */
rotationX: number;
/** @dial rotation @dial-dtype number-deg */
rotationY: number;
/** @dial rotation @dial-dtype number-deg */
rotationZ: number;
};
// Only position properties
type Position2D = Pick<FullTransform, 'x' | 'y'>;
// Result: { x: number, y: number } with annotations
```
### Omit - Exclude Properties
```tsx
// Everything except rotation
type TranslateOnly = Omit<FullTransform, 'rotationX' | 'rotationY' | 'rotationZ'>;
// Result: { x, y, z } with all dial annotations preserved
```
### Partial - Make Properties Optional
```tsx
type RequiredConfig = {
/** @dial config @dial-dtype string */
apiKey: string;
/** @dial config @dial-dtype string */
endpoint: string;
/** @dial config @dial-dtype number */
timeout: number;
};
// All properties become optional
type OptionalConfig = Partial<RequiredConfig>;
// Result: { apiKey?: string, endpoint?: string, timeout?: number }
```
## Property Override
Child types can override parent property annotations:
```tsx
interface BaseComponent {
/**
* Base color property
* @dial appearance
* @dial-dtype string
*/
color: string;
}
interface AdvancedComponent extends BaseComponent {
/**
* Enhanced color with picker
* @dial appearance
* @dial-dtype color
* @dial-icon Palette
*/
color: string; // Overrides with color picker
}
```
## Complex Example
Here's a real-world example combining multiple inheritance patterns:
```tsx
// Base types with group configs
type PhysicsProps = {
/** @dial physics @dial-dtype number @dial-min 0 @dial-max 100 */
mass: number;
/** @dial physics @dial-dtype number @dial-min 0 @dial-max 1 */
friction: number;
};
/**
* @dial animation @dial-no-wrap
*/
type AnimationProps = {
/** @dial animation */
duration: number;
/** @dial animation */
loop: boolean;
};
// Interface extending a type intersection
interface GameObject extends PhysicsProps, AnimationProps {
/** @dial metadata */
id: string;
/** @dial transform @dial-dtype vector3 */
position: [number, number, number];
}
// Further composition
type InteractiveGameObject = GameObject & {
/** @dial interaction */
onClick: () => void;
/** @dial interaction @dial-dtype boolean */
hoverable: boolean;
};
// Using utility types
type StaticObject = Omit<InteractiveGameObject, 'onClick' | 'hoverable'>;
type PreviewObject = Partial<InteractiveGameObject>;
```
## Generated Schema
The dial-cli correctly resolves all inheritance and generates complete schemas:
```json
{
"component": "InteractiveGameObject",
"schemas": [
{ "name": "mass", "dtype": "number", "min": 0, "max": 100, "tags": { "grouping": "physics" } },
{ "name": "friction", "dtype": "number", "min": 0, "max": 1, "tags": { "grouping": "physics" } },
{ "name": "duration", "dtype": "number", "tags": { "grouping": "animation", "noWrap": true } },
{ "name": "loop", "dtype": "boolean", "tags": { "grouping": "animation", "noWrap": true } },
{ "name": "id", "dtype": "string", "tags": { "grouping": "metadata" } },
{ "name": "position", "dtype": "vector3", "tags": { "grouping": "transform" } },
{ "name": "hoverable", "dtype": "boolean", "tags": { "grouping": "interaction" } }
],
"groups": [
{ "name": "animation", "noWrap": true }
]
}
```
## Best Practices
1. **Use Base Interfaces** for common properties across components
1. **Group Related Types** with type aliases for reusability
1. **Apply Group Configs** at the type level for consistent layout
1. **Document Overrides** when child types change parent behavior
1. **Test Inheritance** by running dial-cli with `--verbose` to see full resolution
## Limitations
- Generic types with type parameters require concrete types for dial-cli to process
- Conditional types are not fully supported
- Mapped types need explicit property definitions
## Next Steps
- Learn about [Group Configurations](/dial/annotations/grouping) for layout control
- Explore [Advanced Annotations](/dial/annotations/advanced) for complex types
- See [Complete Examples](/dial/examples) using inheritance patterns