@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 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.