@type32/yaml-editor-form 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1092 -0
- package/dist/module.d.mts +8 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +25 -0
- package/dist/runtime/assets/css/main.css +1 -0
- package/dist/runtime/components/YamlCollapsible.d.vue.ts +47 -0
- package/dist/runtime/components/YamlCollapsible.vue +43 -0
- package/dist/runtime/components/YamlCollapsible.vue.d.ts +47 -0
- package/dist/runtime/components/YamlFieldInput.d.vue.ts +54 -0
- package/dist/runtime/components/YamlFieldInput.vue +158 -0
- package/dist/runtime/components/YamlFieldInput.vue.d.ts +54 -0
- package/dist/runtime/components/YamlFormEditor.d.vue.ts +66 -0
- package/dist/runtime/components/YamlFormEditor.vue +70 -0
- package/dist/runtime/components/YamlFormEditor.vue.d.ts +66 -0
- package/dist/runtime/components/YamlFormField.d.vue.ts +56 -0
- package/dist/runtime/components/YamlFormField.vue +492 -0
- package/dist/runtime/components/YamlFormField.vue.d.ts +56 -0
- package/dist/runtime/composables/useYamlFieldTypes.d.ts +13 -0
- package/dist/runtime/composables/useYamlFieldTypes.js +137 -0
- package/dist/runtime/composables/useYamlFormData.d.ts +24 -0
- package/dist/runtime/composables/useYamlFormData.js +122 -0
- package/dist/runtime/types/types.d.ts +30 -0
- package/dist/types.d.mts +3 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
# YAML Form Editor
|
|
2
|
+
|
|
3
|
+
> A powerful, schema-driven YAML/frontmatter editor for Nuxt v4 with Nuxt UI components. Supports custom field types, nested structures, and extensible type system.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx nuxi module add @type32/yaml-editor-form
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Architecture](#architecture)
|
|
17
|
+
- [Component API](#component-api)
|
|
18
|
+
- [Field Types](#field-types)
|
|
19
|
+
- [Custom Field Types](#custom-field-types)
|
|
20
|
+
- [Schema System](#schema-system)
|
|
21
|
+
- [Usage Examples](#usage-examples)
|
|
22
|
+
- [File Structure](#file-structure)
|
|
23
|
+
- [Development](#development)
|
|
24
|
+
- [Type Definitions](#type-definitions)
|
|
25
|
+
- [Advanced Topics](#advanced-topics)
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
The YAML Form Editor is a Nuxt Module that provides components for editing YAML data structures with a beautiful UI. It's built on top of Nuxt UI v4 and uses a schema-driven architecture for maximum extensibility.
|
|
30
|
+
|
|
31
|
+
### Key Characteristics
|
|
32
|
+
|
|
33
|
+
- **Schema-Driven**: All field types defined in a centralized registry
|
|
34
|
+
- **Recursive**: Handles deeply nested objects and arrays
|
|
35
|
+
- **Extensible**: Add custom field types with custom components via slots
|
|
36
|
+
- **Type-Safe**: Full TypeScript support
|
|
37
|
+
- **Auto-Detection**: Automatically detects field types from values
|
|
38
|
+
- **Conversion**: Convert between compatible types with data preservation
|
|
39
|
+
- **Validation-Ready**: Architecture supports easy validation integration
|
|
40
|
+
|
|
41
|
+
### Use Cases
|
|
42
|
+
|
|
43
|
+
- YAML/Frontmatter editing in Markdown editors
|
|
44
|
+
- Configuration file editors
|
|
45
|
+
- Form builders with dynamic schemas
|
|
46
|
+
- Admin panels with complex data structures
|
|
47
|
+
- API response editors
|
|
48
|
+
- Any structured data editing
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
### Built-in Field Types
|
|
53
|
+
|
|
54
|
+
| Type | Component | Description |
|
|
55
|
+
|------|-----------|-------------|
|
|
56
|
+
| `string` | `UInput` | Single-line text input |
|
|
57
|
+
| `textarea` | `UTextarea` | Multi-line text input (autoresizing) |
|
|
58
|
+
| `number` | `UInputNumber` | Numeric input with increment/decrement |
|
|
59
|
+
| `boolean` | `USwitch` | Toggle switch |
|
|
60
|
+
| `date` | `UInputDate` | Date picker (YYYY-MM-DD) |
|
|
61
|
+
| `datetime` | `UInputDate` | Date + time picker (ISO 8601) |
|
|
62
|
+
| `string-array` | `UInputTags` | Tag input for string arrays |
|
|
63
|
+
| `array` | Recursive | Array of any type (objects, primitives) |
|
|
64
|
+
| `object` | Recursive | Nested object with fields |
|
|
65
|
+
| `null` | Static | Displays "null" |
|
|
66
|
+
|
|
67
|
+
### Core Features
|
|
68
|
+
|
|
69
|
+
✅ **Auto Type Detection** - Automatically detects types from existing values
|
|
70
|
+
✅ **Type Conversion** - Convert between compatible types (preserves data when possible)
|
|
71
|
+
✅ **Field Renaming** - Rename object fields and array items inline
|
|
72
|
+
✅ **Add/Remove Fields** - Dynamic field management with type selection
|
|
73
|
+
✅ **Template Creation** - "Add from Template" for object arrays
|
|
74
|
+
✅ **Nested Structures** - Unlimited nesting depth for objects and arrays
|
|
75
|
+
✅ **Collapsible Sections** - Collapsible objects/arrays with item counts
|
|
76
|
+
✅ **Read-only Mode** - Disable editing for view-only scenarios
|
|
77
|
+
✅ **Custom Components** - Slot-based custom field rendering
|
|
78
|
+
✅ **Schema Extension** - Add custom field types at runtime
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### Basic Usage
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
const data = ref({
|
|
87
|
+
title: 'My Article',
|
|
88
|
+
published: false,
|
|
89
|
+
publishedDate: '2024-01-28',
|
|
90
|
+
tags: ['vue', 'nuxt', 'yaml'],
|
|
91
|
+
author: {
|
|
92
|
+
name: 'John Doe',
|
|
93
|
+
email: 'john@example.com'
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<template>
|
|
99
|
+
<YamlForm v-model="data" />
|
|
100
|
+
</template>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### With Custom Field Types
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<script setup lang="ts">
|
|
107
|
+
import type { YamlFieldType } from './useYamlFieldTypes'
|
|
108
|
+
|
|
109
|
+
const customTypes: YamlFieldType[] = [
|
|
110
|
+
{
|
|
111
|
+
type: 'image',
|
|
112
|
+
label: 'Image',
|
|
113
|
+
icon: 'i-lucide-image',
|
|
114
|
+
defaultValue: '',
|
|
115
|
+
component: 'image'
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
const data = ref({
|
|
120
|
+
title: 'Article',
|
|
121
|
+
banner: '/images/banner.jpg'
|
|
122
|
+
})
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<YamlForm v-model="data" :field-types="customTypes">
|
|
127
|
+
<template #field-image="{ modelValue, readonly }">
|
|
128
|
+
<MyImagePicker v-model="modelValue" :disabled="readonly" />
|
|
129
|
+
</template>
|
|
130
|
+
</YamlForm>
|
|
131
|
+
</template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Architecture
|
|
135
|
+
|
|
136
|
+
### Component Hierarchy
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
YamlForm.vue (Entry Point)
|
|
140
|
+
└── YamlFormField.vue (Recursive Component)
|
|
141
|
+
├── YamlFieldInput.vue (Simple Types)
|
|
142
|
+
│ ├── UInput (string)
|
|
143
|
+
│ ├── UTextarea (textarea)
|
|
144
|
+
│ ├── UInputNumber (number)
|
|
145
|
+
│ ├── USwitch (boolean)
|
|
146
|
+
│ ├── UInputDate (date, datetime)
|
|
147
|
+
│ ├── UInputTags (string-array)
|
|
148
|
+
│ └── Custom Slots (user-defined)
|
|
149
|
+
└── YamlFormField.vue (Complex Types - Recursive)
|
|
150
|
+
├── Collapsible (objects/arrays)
|
|
151
|
+
└── Array/Object rendering
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Data Flow
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
User Input
|
|
158
|
+
↓
|
|
159
|
+
YamlFieldInput (v-model)
|
|
160
|
+
↓
|
|
161
|
+
YamlFormField (v-model)
|
|
162
|
+
↓
|
|
163
|
+
YamlForm (v-model)
|
|
164
|
+
↓
|
|
165
|
+
Parent Component (data binding)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Schema System
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
useYamlFieldTypes.ts (Composable)
|
|
172
|
+
↓
|
|
173
|
+
DEFAULT_FIELD_TYPES (Registry)
|
|
174
|
+
↓
|
|
175
|
+
Type Detection → Type Conversion → Default Values
|
|
176
|
+
↓
|
|
177
|
+
Components (Rendering)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Component API
|
|
181
|
+
|
|
182
|
+
### YamlForm
|
|
183
|
+
|
|
184
|
+
Main entry point for the editor.
|
|
185
|
+
|
|
186
|
+
#### Props
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
{
|
|
190
|
+
modelValue: YamlFormData // Required: The data to edit
|
|
191
|
+
filePath?: string // Optional: File path (for display)
|
|
192
|
+
readonly?: boolean // Optional: Read-only mode
|
|
193
|
+
fieldTypes?: YamlFieldType[] // Optional: Custom field types
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### Events
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
{
|
|
201
|
+
'update:modelValue': (value: YamlFormData) => void
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Slots
|
|
206
|
+
|
|
207
|
+
All custom field component slots are supported:
|
|
208
|
+
|
|
209
|
+
```vue
|
|
210
|
+
<template #field-{component}="{ modelValue, readonly, valueType }">
|
|
211
|
+
<!-- Your custom component -->
|
|
212
|
+
</template>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### YamlFormField
|
|
216
|
+
|
|
217
|
+
Recursive component that handles individual fields.
|
|
218
|
+
|
|
219
|
+
#### Props
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
{
|
|
223
|
+
modelValue: YamlValue // Required: Field value
|
|
224
|
+
fieldKey: string // Required: Field name/key
|
|
225
|
+
readonly?: boolean // Optional: Read-only mode
|
|
226
|
+
depth?: number // Optional: Nesting depth
|
|
227
|
+
fieldTypes?: YamlFieldType[] // Optional: Custom field types
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### Events
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
{
|
|
235
|
+
'update:modelValue': (value: YamlValue) => void
|
|
236
|
+
'remove': () => void
|
|
237
|
+
'update:fieldKey': (newKey: string) => void
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Slots
|
|
242
|
+
|
|
243
|
+
Same as YamlForm - all custom field slots are forwarded.
|
|
244
|
+
|
|
245
|
+
### YamlFieldInput
|
|
246
|
+
|
|
247
|
+
Renders input components for simple types.
|
|
248
|
+
|
|
249
|
+
#### Props
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
{
|
|
253
|
+
modelValue: YamlValue // Required: Field value
|
|
254
|
+
valueType: string // Required: Type identifier
|
|
255
|
+
readonly?: boolean // Optional: Read-only mode
|
|
256
|
+
fieldType?: YamlFieldType // Optional: Field type definition
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### Events
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
{
|
|
264
|
+
'update:modelValue': (value: YamlValue) => void
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### Slots
|
|
269
|
+
|
|
270
|
+
```vue
|
|
271
|
+
<template #field-{component}="{ modelValue, readonly, valueType }">
|
|
272
|
+
<!-- Custom input component -->
|
|
273
|
+
</template>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Field Types
|
|
277
|
+
|
|
278
|
+
### Type Definition
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
interface YamlFieldType {
|
|
282
|
+
type: string // Unique type identifier
|
|
283
|
+
label: string // Display name in dropdowns
|
|
284
|
+
icon: string // Lucide icon name (i-lucide-*)
|
|
285
|
+
defaultValue: any // Default value or factory function
|
|
286
|
+
component?: string // Optional: slot name for custom rendering
|
|
287
|
+
detect?: (value: any) => boolean // Optional: auto-detection function
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Built-in Types
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
const DEFAULT_FIELD_TYPES: YamlFieldType[] = [
|
|
295
|
+
{
|
|
296
|
+
type: 'string',
|
|
297
|
+
label: 'Text',
|
|
298
|
+
icon: 'i-lucide-type',
|
|
299
|
+
defaultValue: '',
|
|
300
|
+
detect: (value) => typeof value === 'string' && !isDateString(value)
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
type: 'textarea',
|
|
304
|
+
label: 'Long Text',
|
|
305
|
+
icon: 'i-lucide-align-left',
|
|
306
|
+
defaultValue: '',
|
|
307
|
+
component: 'textarea'
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'number',
|
|
311
|
+
label: 'Number',
|
|
312
|
+
icon: 'i-lucide-hash',
|
|
313
|
+
defaultValue: 0,
|
|
314
|
+
detect: (value) => typeof value === 'number'
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: 'boolean',
|
|
318
|
+
label: 'Boolean',
|
|
319
|
+
icon: 'i-lucide-circle-check',
|
|
320
|
+
defaultValue: false,
|
|
321
|
+
detect: (value) => typeof value === 'boolean'
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
type: 'date',
|
|
325
|
+
label: 'Date',
|
|
326
|
+
icon: 'i-lucide-calendar',
|
|
327
|
+
defaultValue: () => new Date(),
|
|
328
|
+
detect: (value) => isDateObject(value) || isDateString(value)
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
type: 'datetime',
|
|
332
|
+
label: 'Date & Time',
|
|
333
|
+
icon: 'i-lucide-calendar-clock',
|
|
334
|
+
defaultValue: () => new Date(),
|
|
335
|
+
detect: (value) => isDateTimeString(value)
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
type: 'string-array',
|
|
339
|
+
label: 'Tags',
|
|
340
|
+
icon: 'i-lucide-tags',
|
|
341
|
+
defaultValue: [],
|
|
342
|
+
detect: (value) => isStringArray(value)
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
type: 'array',
|
|
346
|
+
label: 'Array',
|
|
347
|
+
icon: 'i-lucide-list',
|
|
348
|
+
defaultValue: [],
|
|
349
|
+
detect: (value) => Array.isArray(value) && !isStringArray(value)
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
type: 'object',
|
|
353
|
+
label: 'Object',
|
|
354
|
+
icon: 'i-lucide-box',
|
|
355
|
+
defaultValue: {},
|
|
356
|
+
detect: (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
type: 'null',
|
|
360
|
+
label: 'Null',
|
|
361
|
+
icon: 'i-lucide-circle-slash',
|
|
362
|
+
defaultValue: null,
|
|
363
|
+
detect: (value) => value === null
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Type Conversion Rules
|
|
369
|
+
|
|
370
|
+
Valid conversions between types:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const conversionRules = {
|
|
374
|
+
'string': ['number', 'boolean', 'date', 'datetime', 'string-array', 'null'],
|
|
375
|
+
'number': ['string', 'boolean', 'null'],
|
|
376
|
+
'boolean': ['string', 'number', 'null'],
|
|
377
|
+
'date': ['string', 'datetime', 'null'],
|
|
378
|
+
'datetime': ['string', 'date', 'null'],
|
|
379
|
+
'string-array': ['array', 'string', 'null'],
|
|
380
|
+
'array': ['string-array', 'null'],
|
|
381
|
+
'object': ['null']
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Special Behaviors:**
|
|
386
|
+
- Date ↔ DateTime: Preserves date value, adds/removes time component
|
|
387
|
+
- Array → Non-Array: Uses first item if available
|
|
388
|
+
- String-Array ↔ Array: Converts item types appropriately
|
|
389
|
+
|
|
390
|
+
## Custom Field Types
|
|
391
|
+
|
|
392
|
+
### Adding a Built-in Type
|
|
393
|
+
|
|
394
|
+
Edit the type registry composable:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
export const DEFAULT_FIELD_TYPES: YamlFieldType[] = [
|
|
398
|
+
// ... existing types ...
|
|
399
|
+
{
|
|
400
|
+
type: 'email',
|
|
401
|
+
label: 'Email',
|
|
402
|
+
icon: 'i-lucide-mail',
|
|
403
|
+
defaultValue: '',
|
|
404
|
+
detect: (value) => typeof value === 'string' && /^[^@]+@[^@]+/.test(value)
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
That's it! The type now:
|
|
410
|
+
- ✅ Appears in all "Add Field" dropdowns
|
|
411
|
+
- ✅ Auto-detects from existing values
|
|
412
|
+
- ✅ Has correct icon everywhere
|
|
413
|
+
- ✅ Uses correct default value
|
|
414
|
+
|
|
415
|
+
### Adding a Runtime Type (No Component)
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const customTypes: YamlFieldType[] = [
|
|
419
|
+
{
|
|
420
|
+
type: 'url',
|
|
421
|
+
label: 'URL',
|
|
422
|
+
icon: 'i-lucide-link',
|
|
423
|
+
defaultValue: 'https://',
|
|
424
|
+
detect: (value) => typeof value === 'string' && value.startsWith('http')
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```vue
|
|
430
|
+
<YamlForm v-model="data" :field-types="customTypes" />
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Adding a Runtime Type (With Custom Component)
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
const customTypes: YamlFieldType[] = [
|
|
437
|
+
{
|
|
438
|
+
type: 'color',
|
|
439
|
+
label: 'Color',
|
|
440
|
+
icon: 'i-lucide-palette',
|
|
441
|
+
defaultValue: '#000000',
|
|
442
|
+
component: 'color', // Enables slot
|
|
443
|
+
detect: (value) => /^#[0-9A-Fa-f]{6}$/.test(value)
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```vue
|
|
449
|
+
<YamlForm v-model="data" :field-types="customTypes">
|
|
450
|
+
<template #field-color="{ modelValue, readonly }">
|
|
451
|
+
<input
|
|
452
|
+
type="color"
|
|
453
|
+
v-model="modelValue"
|
|
454
|
+
:disabled="readonly"
|
|
455
|
+
class="w-full h-10 rounded"
|
|
456
|
+
/>
|
|
457
|
+
</template>
|
|
458
|
+
</YamlForm>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Overriding Built-in Types
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
const customTypes: YamlFieldType[] = [
|
|
465
|
+
{
|
|
466
|
+
type: 'string', // Same as built-in
|
|
467
|
+
label: 'Rich Text',
|
|
468
|
+
icon: 'i-lucide-file-text',
|
|
469
|
+
defaultValue: '',
|
|
470
|
+
component: 'richtext' // Now uses custom component
|
|
471
|
+
}
|
|
472
|
+
]
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```vue
|
|
476
|
+
<YamlForm v-model="data" :field-types="customTypes">
|
|
477
|
+
<template #field-richtext="{ modelValue, readonly }">
|
|
478
|
+
<MyRichTextEditor v-model="modelValue" :read-only="readonly" />
|
|
479
|
+
</template>
|
|
480
|
+
</YamlForm>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Schema System
|
|
484
|
+
|
|
485
|
+
### Composable: useYamlFieldTypes
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { useYamlFieldTypes } from './useYamlFieldTypes'
|
|
489
|
+
|
|
490
|
+
const {
|
|
491
|
+
fieldTypes, // Computed array of all types
|
|
492
|
+
getFieldType, // Get type definition by ID
|
|
493
|
+
detectFieldType, // Auto-detect type from value
|
|
494
|
+
getDefaultValue, // Get default value for type
|
|
495
|
+
getIcon, // Get icon for type
|
|
496
|
+
getTypeMenuItems // Get dropdown menu items
|
|
497
|
+
} = useYamlFieldTypes(customTypes)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Functions
|
|
501
|
+
|
|
502
|
+
#### getFieldType(type: string)
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
const stringType = getFieldType('string')
|
|
506
|
+
// Returns: { type: 'string', label: 'Text', icon: 'i-lucide-type', ... }
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### detectFieldType(value: any)
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
const type = detectFieldType('hello@example.com')
|
|
513
|
+
// Returns: { type: 'email', ... } if email type is defined
|
|
514
|
+
// Falls back to: { type: 'string', ... }
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### getDefaultValue(type: string)
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
const defaultDate = getDefaultValue('date')
|
|
521
|
+
// Returns: new Date() (function is called)
|
|
522
|
+
|
|
523
|
+
const defaultString = getDefaultValue('string')
|
|
524
|
+
// Returns: ''
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### getIcon(type: string)
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
const icon = getIcon('number')
|
|
531
|
+
// Returns: 'i-lucide-hash'
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
#### getTypeMenuItems(onSelect: (type: string) => void)
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
const menuItems = getTypeMenuItems((type) => {
|
|
538
|
+
console.log('Selected:', type)
|
|
539
|
+
})
|
|
540
|
+
// Returns: [
|
|
541
|
+
// { label: 'Text', icon: 'i-lucide-type', onSelect: () => ... },
|
|
542
|
+
// { label: 'Number', icon: 'i-lucide-hash', onSelect: () => ... },
|
|
543
|
+
// ...
|
|
544
|
+
// ]
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## Usage Examples
|
|
548
|
+
|
|
549
|
+
### Basic Form
|
|
550
|
+
|
|
551
|
+
```vue
|
|
552
|
+
<script setup lang="ts">
|
|
553
|
+
const config = ref({
|
|
554
|
+
siteName: 'My Site',
|
|
555
|
+
port: 3000,
|
|
556
|
+
debug: false
|
|
557
|
+
})
|
|
558
|
+
</script>
|
|
559
|
+
|
|
560
|
+
<template>
|
|
561
|
+
<YamlForm v-model="config" />
|
|
562
|
+
</template>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Nested Objects
|
|
566
|
+
|
|
567
|
+
```vue
|
|
568
|
+
<script setup lang="ts">
|
|
569
|
+
const article = ref({
|
|
570
|
+
title: 'Article Title',
|
|
571
|
+
meta: {
|
|
572
|
+
author: 'John Doe',
|
|
573
|
+
publishedAt: '2024-01-28',
|
|
574
|
+
tags: ['vue', 'nuxt']
|
|
575
|
+
}
|
|
576
|
+
})
|
|
577
|
+
</script>
|
|
578
|
+
|
|
579
|
+
<template>
|
|
580
|
+
<YamlForm v-model="article" />
|
|
581
|
+
</template>
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Arrays of Objects
|
|
585
|
+
|
|
586
|
+
```vue
|
|
587
|
+
<script setup lang="ts">
|
|
588
|
+
const data = ref({
|
|
589
|
+
users: [
|
|
590
|
+
{ name: 'Alice', role: 'admin' },
|
|
591
|
+
{ name: 'Bob', role: 'user' }
|
|
592
|
+
]
|
|
593
|
+
})
|
|
594
|
+
</script>
|
|
595
|
+
|
|
596
|
+
<template>
|
|
597
|
+
<YamlForm v-model="data" />
|
|
598
|
+
</template>
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### With Custom Types
|
|
602
|
+
|
|
603
|
+
```vue
|
|
604
|
+
<script setup lang="ts">
|
|
605
|
+
import type { YamlFieldType } from './useYamlFieldTypes'
|
|
606
|
+
|
|
607
|
+
// Define custom types
|
|
608
|
+
const customTypes: YamlFieldType[] = [
|
|
609
|
+
{
|
|
610
|
+
type: 'image',
|
|
611
|
+
label: 'Image',
|
|
612
|
+
icon: 'i-lucide-image',
|
|
613
|
+
defaultValue: '',
|
|
614
|
+
component: 'image'
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
type: 'markdown',
|
|
618
|
+
label: 'Markdown',
|
|
619
|
+
icon: 'i-lucide-file-text',
|
|
620
|
+
defaultValue: '',
|
|
621
|
+
component: 'markdown'
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
const post = ref({
|
|
626
|
+
title: 'My Post',
|
|
627
|
+
banner: '/images/banner.jpg',
|
|
628
|
+
content: '# Hello World'
|
|
629
|
+
})
|
|
630
|
+
</script>
|
|
631
|
+
|
|
632
|
+
<template>
|
|
633
|
+
<YamlForm v-model="post" :field-types="customTypes">
|
|
634
|
+
<!-- Image picker component -->
|
|
635
|
+
<template #field-image="{ modelValue, readonly }">
|
|
636
|
+
<MyImagePicker
|
|
637
|
+
v-model="modelValue"
|
|
638
|
+
:disabled="readonly"
|
|
639
|
+
/>
|
|
640
|
+
</template>
|
|
641
|
+
|
|
642
|
+
<!-- Markdown editor component -->
|
|
643
|
+
<template #field-markdown="{ modelValue, readonly }">
|
|
644
|
+
<MyMarkdownEditor
|
|
645
|
+
v-model="modelValue"
|
|
646
|
+
:read-only="readonly"
|
|
647
|
+
/>
|
|
648
|
+
</template>
|
|
649
|
+
</YamlForm>
|
|
650
|
+
</template>
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Dynamic Default Values
|
|
654
|
+
|
|
655
|
+
```vue
|
|
656
|
+
<script setup lang="ts">
|
|
657
|
+
const customTypes: YamlFieldType[] = [
|
|
658
|
+
{
|
|
659
|
+
type: 'uuid',
|
|
660
|
+
label: 'UUID',
|
|
661
|
+
icon: 'i-lucide-fingerprint',
|
|
662
|
+
defaultValue: () => crypto.randomUUID(), // Function called each time
|
|
663
|
+
detect: (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-/.test(v)
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
type: 'timestamp',
|
|
667
|
+
label: 'Timestamp',
|
|
668
|
+
icon: 'i-lucide-clock',
|
|
669
|
+
defaultValue: () => new Date().toISOString()
|
|
670
|
+
}
|
|
671
|
+
]
|
|
672
|
+
</script>
|
|
673
|
+
|
|
674
|
+
<template>
|
|
675
|
+
<YamlForm v-model="data" :field-types="customTypes" />
|
|
676
|
+
</template>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Read-only Mode
|
|
680
|
+
|
|
681
|
+
```vue
|
|
682
|
+
<template>
|
|
683
|
+
<YamlForm v-model="data" readonly />
|
|
684
|
+
</template>
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Complex Nested Structure
|
|
688
|
+
|
|
689
|
+
```vue
|
|
690
|
+
<script setup lang="ts">
|
|
691
|
+
const complexData = ref({
|
|
692
|
+
project: {
|
|
693
|
+
name: 'My Project',
|
|
694
|
+
version: '1.0.0',
|
|
695
|
+
dependencies: ['vue', 'nuxt'],
|
|
696
|
+
config: {
|
|
697
|
+
build: {
|
|
698
|
+
outDir: 'dist',
|
|
699
|
+
minify: true
|
|
700
|
+
},
|
|
701
|
+
server: {
|
|
702
|
+
port: 3000,
|
|
703
|
+
https: false
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
contributors: [
|
|
707
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
708
|
+
{ name: 'Bob', email: 'bob@example.com' }
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
})
|
|
712
|
+
</script>
|
|
713
|
+
|
|
714
|
+
<template>
|
|
715
|
+
<YamlForm v-model="complexData" />
|
|
716
|
+
</template>
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## File Structure
|
|
720
|
+
|
|
721
|
+
```
|
|
722
|
+
components/
|
|
723
|
+
├── YamlForm.vue ← Entry component
|
|
724
|
+
├── YamlFormField.vue ← Recursive field component
|
|
725
|
+
├── YamlFieldInput.vue ← Input rendering component
|
|
726
|
+
└── Collapsible.vue ← Collapsible UI component
|
|
727
|
+
|
|
728
|
+
composables/
|
|
729
|
+
└── useYamlFieldTypes.ts ← Type registry & composable
|
|
730
|
+
|
|
731
|
+
docs/
|
|
732
|
+
├── README.md ← This file (llms.txt + docs)
|
|
733
|
+
├── CUSTOM_FIELD_TYPES_GUIDE.md ← Custom types guide
|
|
734
|
+
├── SCHEMA_REFACTOR_SUMMARY.md ← Schema architecture docs
|
|
735
|
+
└── REFACTORING_SUMMARY.md ← Component refactoring docs
|
|
736
|
+
|
|
737
|
+
types/
|
|
738
|
+
└── index.d.ts ← Type definitions
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## Development
|
|
742
|
+
|
|
743
|
+
### Adding a New Built-in Type
|
|
744
|
+
|
|
745
|
+
1. **Edit the registry** in the type registry composable:
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
{
|
|
749
|
+
type: 'my-type',
|
|
750
|
+
label: 'My Type',
|
|
751
|
+
icon: 'i-lucide-my-icon',
|
|
752
|
+
defaultValue: 'default',
|
|
753
|
+
detect: (value) => /* detection logic */
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
2. **Add rendering** (if needed) in the input component:
|
|
758
|
+
|
|
759
|
+
```vue
|
|
760
|
+
<MyCustomInput
|
|
761
|
+
v-else-if="valueType === 'my-type'"
|
|
762
|
+
v-model="modelValue"
|
|
763
|
+
:disabled="readonly"
|
|
764
|
+
/>
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
3. **Done!** The type is now available everywhere.
|
|
768
|
+
|
|
769
|
+
### Adding a Custom Input Component
|
|
770
|
+
|
|
771
|
+
If you want a custom built-in component (not via slots):
|
|
772
|
+
|
|
773
|
+
1. **Add to YamlFieldInput.vue**:
|
|
774
|
+
|
|
775
|
+
```vue
|
|
776
|
+
<template>
|
|
777
|
+
<!-- ... existing inputs ... -->
|
|
778
|
+
|
|
779
|
+
<MyCustomComponent
|
|
780
|
+
v-else-if="valueType === 'custom'"
|
|
781
|
+
v-model="modelValue"
|
|
782
|
+
:disabled="readonly"
|
|
783
|
+
/>
|
|
784
|
+
</template>
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
2. **Register the type** with the matching `type` value.
|
|
788
|
+
|
|
789
|
+
### Testing
|
|
790
|
+
|
|
791
|
+
Recommended test scenarios:
|
|
792
|
+
|
|
793
|
+
**Type Detection:**
|
|
794
|
+
- [ ] Auto-detects string, number, boolean
|
|
795
|
+
- [ ] Auto-detects date strings (YYYY-MM-DD)
|
|
796
|
+
- [ ] Auto-detects datetime strings (ISO 8601)
|
|
797
|
+
- [ ] Auto-detects string arrays
|
|
798
|
+
- [ ] Auto-detects object arrays
|
|
799
|
+
|
|
800
|
+
**Type Conversion:**
|
|
801
|
+
- [ ] String ↔ Number
|
|
802
|
+
- [ ] Date ↔ DateTime (preserves date)
|
|
803
|
+
- [ ] Array ↔ String-Array
|
|
804
|
+
- [ ] Array to non-array (uses first item)
|
|
805
|
+
|
|
806
|
+
**Field Operations:**
|
|
807
|
+
- [ ] Add field with type selection
|
|
808
|
+
- [ ] Remove field
|
|
809
|
+
- [ ] Rename field (simple types)
|
|
810
|
+
- [ ] Rename field (complex types via pencil icon)
|
|
811
|
+
- [ ] Add array item with type selection
|
|
812
|
+
- [ ] Remove array item
|
|
813
|
+
- [ ] Add item from template (object arrays)
|
|
814
|
+
|
|
815
|
+
**Nested Structures:**
|
|
816
|
+
- [ ] Objects in objects (deep nesting)
|
|
817
|
+
- [ ] Arrays in objects
|
|
818
|
+
- [ ] Objects in arrays
|
|
819
|
+
- [ ] Arrays in arrays
|
|
820
|
+
|
|
821
|
+
**Custom Types:**
|
|
822
|
+
- [ ] Custom type appears in dropdowns
|
|
823
|
+
- [ ] Custom type uses correct icon
|
|
824
|
+
- [ ] Custom type uses correct default value
|
|
825
|
+
- [ ] Custom component renders via slot
|
|
826
|
+
- [ ] Slot props are correct
|
|
827
|
+
|
|
828
|
+
**Edge Cases:**
|
|
829
|
+
- [ ] Empty objects display correctly
|
|
830
|
+
- [ ] Empty arrays display correctly
|
|
831
|
+
- [ ] Null values display correctly
|
|
832
|
+
- [ ] Read-only mode disables editing
|
|
833
|
+
- [ ] Array items can't be renamed (correct)
|
|
834
|
+
- [ ] Fields inside array items CAN be renamed
|
|
835
|
+
|
|
836
|
+
## Type Definitions
|
|
837
|
+
|
|
838
|
+
### Core Types
|
|
839
|
+
|
|
840
|
+
```typescript
|
|
841
|
+
// YAML value type (recursive)
|
|
842
|
+
type YamlValue =
|
|
843
|
+
| string
|
|
844
|
+
| number
|
|
845
|
+
| boolean
|
|
846
|
+
| null
|
|
847
|
+
| Date
|
|
848
|
+
| YamlValue[]
|
|
849
|
+
| { [key: string]: YamlValue }
|
|
850
|
+
|
|
851
|
+
// Form data type
|
|
852
|
+
type YamlFormData = { [key: string]: YamlValue }
|
|
853
|
+
|
|
854
|
+
// Field type definition
|
|
855
|
+
interface YamlFieldType {
|
|
856
|
+
type: string
|
|
857
|
+
label: string
|
|
858
|
+
icon: string
|
|
859
|
+
defaultValue: any | (() => any)
|
|
860
|
+
component?: string
|
|
861
|
+
detect?: (value: any) => boolean
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Dropdown menu item
|
|
865
|
+
interface DropdownMenuItem {
|
|
866
|
+
label: string
|
|
867
|
+
icon?: string
|
|
868
|
+
onSelect?: () => void
|
|
869
|
+
disabled?: boolean
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Component Props
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
// YamlForm props
|
|
877
|
+
interface YamlFormProps {
|
|
878
|
+
modelValue: YamlFormData
|
|
879
|
+
filePath?: string
|
|
880
|
+
readonly?: boolean
|
|
881
|
+
fieldTypes?: YamlFieldType[]
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// YamlFormField props
|
|
885
|
+
interface YamlFormFieldProps {
|
|
886
|
+
modelValue: YamlValue
|
|
887
|
+
fieldKey: string
|
|
888
|
+
readonly?: boolean
|
|
889
|
+
depth?: number
|
|
890
|
+
fieldTypes?: YamlFieldType[]
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// YamlFieldInput props
|
|
894
|
+
interface YamlFieldInputProps {
|
|
895
|
+
modelValue: YamlValue
|
|
896
|
+
valueType: string
|
|
897
|
+
readonly?: boolean
|
|
898
|
+
fieldType?: YamlFieldType
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
## Advanced Topics
|
|
903
|
+
|
|
904
|
+
### Slot Forwarding
|
|
905
|
+
|
|
906
|
+
Slots are automatically forwarded through the component hierarchy:
|
|
907
|
+
|
|
908
|
+
```
|
|
909
|
+
YamlForm (defines slot)
|
|
910
|
+
↓ forwards
|
|
911
|
+
YamlFormField (forwards slot)
|
|
912
|
+
↓ forwards
|
|
913
|
+
YamlFieldInput (uses slot)
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
This allows custom components to work at any nesting level.
|
|
917
|
+
|
|
918
|
+
### Type Priority
|
|
919
|
+
|
|
920
|
+
When multiple types have `detect` functions that match:
|
|
921
|
+
|
|
922
|
+
1. Types are checked in array order
|
|
923
|
+
2. First matching type wins
|
|
924
|
+
3. More specific types should come before general types
|
|
925
|
+
|
|
926
|
+
**Example order:**
|
|
927
|
+
```typescript
|
|
928
|
+
[
|
|
929
|
+
{ type: 'datetime', detect: (v) => isDateTimeString(v) }, // Specific
|
|
930
|
+
{ type: 'date', detect: (v) => isDateString(v) }, // Less specific
|
|
931
|
+
{ type: 'string', detect: (v) => typeof v === 'string' } // General
|
|
932
|
+
]
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### Performance Considerations
|
|
936
|
+
|
|
937
|
+
**Reactivity:**
|
|
938
|
+
- Uses Vue 3 `ref` and `computed` for optimal reactivity
|
|
939
|
+
- Deep watching is used only where necessary
|
|
940
|
+
- Recursive rendering is optimized with `v-if` conditionals
|
|
941
|
+
|
|
942
|
+
**Large Arrays:**
|
|
943
|
+
- Each array item is independently reactive
|
|
944
|
+
- Adding/removing items doesn't re-render siblings
|
|
945
|
+
- Collapsible sections prevent rendering hidden content
|
|
946
|
+
|
|
947
|
+
**Memory:**
|
|
948
|
+
- Date helper functions are minimal
|
|
949
|
+
- No global state except type registry
|
|
950
|
+
- Components clean up properly on unmount
|
|
951
|
+
|
|
952
|
+
### Validation (Future)
|
|
953
|
+
|
|
954
|
+
The architecture supports easy validation integration:
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
interface YamlFieldType {
|
|
958
|
+
// ... existing fields ...
|
|
959
|
+
validate?: (value: any) => boolean | string
|
|
960
|
+
format?: (value: any) => string
|
|
961
|
+
parse?: (input: string) => any
|
|
962
|
+
}
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
### Accessibility
|
|
966
|
+
|
|
967
|
+
**Keyboard Navigation:**
|
|
968
|
+
- Tab through fields
|
|
969
|
+
- Enter to confirm edits
|
|
970
|
+
- Escape to cancel edits
|
|
971
|
+
- Arrow keys in number inputs
|
|
972
|
+
|
|
973
|
+
**Screen Readers:**
|
|
974
|
+
- Proper ARIA labels on inputs
|
|
975
|
+
- Semantic HTML structure
|
|
976
|
+
- Form field associations
|
|
977
|
+
|
|
978
|
+
**Focus Management:**
|
|
979
|
+
- Auto-focus on edit mode
|
|
980
|
+
- Focus returns to trigger after close
|
|
981
|
+
- Visible focus indicators
|
|
982
|
+
|
|
983
|
+
### Migration from Other Editors
|
|
984
|
+
|
|
985
|
+
**From JSON Editor:**
|
|
986
|
+
```typescript
|
|
987
|
+
const jsonData = JSON.parse(jsonString)
|
|
988
|
+
const yamlData = ref(jsonData)
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**To YAML:**
|
|
992
|
+
```typescript
|
|
993
|
+
import yaml from 'js-yaml'
|
|
994
|
+
const yamlString = yaml.dump(yamlData.value)
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
**From Object:**
|
|
998
|
+
```typescript
|
|
999
|
+
const yamlData = ref({ ...existingObject })
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
### Browser Support
|
|
1003
|
+
|
|
1004
|
+
- Chrome 90+
|
|
1005
|
+
- Firefox 88+
|
|
1006
|
+
- Safari 14+
|
|
1007
|
+
- Edge 90+
|
|
1008
|
+
|
|
1009
|
+
Requires:
|
|
1010
|
+
- Vue 3.3+
|
|
1011
|
+
- Nuxt 3.0+
|
|
1012
|
+
- Modern JavaScript (ES2020+)
|
|
1013
|
+
|
|
1014
|
+
### Dependencies
|
|
1015
|
+
|
|
1016
|
+
**Required:**
|
|
1017
|
+
- Vue 3
|
|
1018
|
+
- Nuxt UI v4
|
|
1019
|
+
- @internationalized/date (for date/time inputs)
|
|
1020
|
+
- Lucide Icons (for icons)
|
|
1021
|
+
|
|
1022
|
+
**Peer Dependencies:**
|
|
1023
|
+
- reka-ui (via Nuxt UI)
|
|
1024
|
+
- tailwindcss (via Nuxt)
|
|
1025
|
+
|
|
1026
|
+
## License
|
|
1027
|
+
|
|
1028
|
+
This component is part of the Vertex project.
|
|
1029
|
+
|
|
1030
|
+
## Contributing
|
|
1031
|
+
|
|
1032
|
+
When adding features:
|
|
1033
|
+
|
|
1034
|
+
1. **Update the registry** if adding types
|
|
1035
|
+
2. **Update this README** for API changes
|
|
1036
|
+
3. **Add tests** for new functionality
|
|
1037
|
+
4. **Update TypeScript types** for new props/events
|
|
1038
|
+
5. **Check linter** (must pass with 0 errors)
|
|
1039
|
+
6. **Verify backward compatibility**
|
|
1040
|
+
|
|
1041
|
+
## Support
|
|
1042
|
+
|
|
1043
|
+
For issues, questions, or feature requests, refer to the main Vertex project documentation.
|
|
1044
|
+
|
|
1045
|
+
## Quick Reference (LLM Context)
|
|
1046
|
+
|
|
1047
|
+
### Core Components
|
|
1048
|
+
|
|
1049
|
+
- **YamlForm**: Entry point component for the editor
|
|
1050
|
+
- **YamlFormField**: Recursive component handling individual fields
|
|
1051
|
+
- **YamlFieldInput**: Input rendering component for simple types
|
|
1052
|
+
- **useYamlFieldTypes**: Composable for type registry and management
|
|
1053
|
+
- **Collapsible**: UI component for collapsible sections
|
|
1054
|
+
|
|
1055
|
+
### Key Concepts
|
|
1056
|
+
|
|
1057
|
+
1. **Schema-Driven**: All types defined in centralized registry
|
|
1058
|
+
2. **Recursive**: YamlFormField calls itself for nested structures
|
|
1059
|
+
3. **Slot-Based**: Custom components via Vue 3 slots
|
|
1060
|
+
4. **Type-Safe**: Full TypeScript support throughout
|
|
1061
|
+
5. **Extensible**: Add types without modifying core code
|
|
1062
|
+
|
|
1063
|
+
### Architecture Patterns
|
|
1064
|
+
|
|
1065
|
+
- **Composition API**: All components use `<script setup>`
|
|
1066
|
+
- **v-model**: Two-way binding for data
|
|
1067
|
+
- **Emit Events**: For field operations (remove, rename)
|
|
1068
|
+
- **Slot Forwarding**: Custom components at any nesting level
|
|
1069
|
+
- **Computed Properties**: Reactive type detection and menus
|
|
1070
|
+
|
|
1071
|
+
### Common Operations
|
|
1072
|
+
|
|
1073
|
+
**Add Type:**
|
|
1074
|
+
```typescript
|
|
1075
|
+
// Edit the type registry composable → DEFAULT_FIELD_TYPES array
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
**Add Custom Component:**
|
|
1079
|
+
```vue
|
|
1080
|
+
<YamlForm>
|
|
1081
|
+
<template #field-{type}="props">
|
|
1082
|
+
<Component v-bind="props" />
|
|
1083
|
+
</template>
|
|
1084
|
+
</YamlForm>
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**Type Conversion:**
|
|
1088
|
+
```typescript
|
|
1089
|
+
// Handled automatically via convertType() function
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
This README serves as both comprehensive developer documentation and LLM context for understanding the entire YAML Form Editor system.
|