@type32/yaml-editor-form 0.1.3 → 0.1.4
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 +207 -57
- package/dist/module.json +1 -1
- package/dist/runtime/components/YamlCollapsible.d.vue.ts +14 -27
- package/dist/runtime/components/YamlCollapsible.vue +5 -9
- package/dist/runtime/components/YamlCollapsible.vue.d.ts +14 -27
- package/dist/runtime/components/YamlFieldInput.d.vue.ts +3 -0
- package/dist/runtime/components/YamlFieldInput.vue +11 -9
- package/dist/runtime/components/YamlFieldInput.vue.d.ts +3 -0
- package/dist/runtime/components/YamlFormEditor.d.vue.ts +2 -0
- package/dist/runtime/components/YamlFormEditor.vue +5 -3
- package/dist/runtime/components/YamlFormEditor.vue.d.ts +2 -0
- package/dist/runtime/components/YamlFormField.d.vue.ts +2 -0
- package/dist/runtime/components/YamlFormField.vue +32 -19
- package/dist/runtime/components/YamlFormField.vue.d.ts +2 -0
- package/dist/runtime/composables/useYamlFieldTypes.js +13 -2
- package/dist/runtime/types/types.d.ts +27 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ const data = ref({
|
|
|
96
96
|
</script>
|
|
97
97
|
|
|
98
98
|
<template>
|
|
99
|
-
<
|
|
99
|
+
<YamlFormEditor v-model="data" />
|
|
100
100
|
</template>
|
|
101
101
|
```
|
|
102
102
|
|
|
@@ -104,7 +104,7 @@ const data = ref({
|
|
|
104
104
|
|
|
105
105
|
```vue
|
|
106
106
|
<script setup lang="ts">
|
|
107
|
-
import type { YamlFieldType } from '
|
|
107
|
+
import type { YamlFieldType } from '@type32/yaml-editor-form'
|
|
108
108
|
|
|
109
109
|
const customTypes: YamlFieldType[] = [
|
|
110
110
|
{
|
|
@@ -123,11 +123,15 @@ const data = ref({
|
|
|
123
123
|
</script>
|
|
124
124
|
|
|
125
125
|
<template>
|
|
126
|
-
<
|
|
127
|
-
<template #field-image="{ modelValue, readonly }">
|
|
128
|
-
<MyImagePicker
|
|
126
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
127
|
+
<template #field-image="{ modelValue, readonly, updateModelValue }">
|
|
128
|
+
<MyImagePicker
|
|
129
|
+
:model-value="modelValue"
|
|
130
|
+
:disabled="readonly"
|
|
131
|
+
@update:model-value="updateModelValue"
|
|
132
|
+
/>
|
|
129
133
|
</template>
|
|
130
|
-
</
|
|
134
|
+
</YamlFormEditor>
|
|
131
135
|
</template>
|
|
132
136
|
```
|
|
133
137
|
|
|
@@ -136,7 +140,7 @@ const data = ref({
|
|
|
136
140
|
### Component Hierarchy
|
|
137
141
|
|
|
138
142
|
```
|
|
139
|
-
|
|
143
|
+
YamlFormEditor.vue (Entry Point)
|
|
140
144
|
└── YamlFormField.vue (Recursive Component)
|
|
141
145
|
├── YamlFieldInput.vue (Simple Types)
|
|
142
146
|
│ ├── UInput (string)
|
|
@@ -147,7 +151,7 @@ YamlForm.vue (Entry Point)
|
|
|
147
151
|
│ ├── UInputTags (string-array)
|
|
148
152
|
│ └── Custom Slots (user-defined)
|
|
149
153
|
└── YamlFormField.vue (Complex Types - Recursive)
|
|
150
|
-
├──
|
|
154
|
+
├── YamlCollapsible (objects/arrays)
|
|
151
155
|
└── Array/Object rendering
|
|
152
156
|
```
|
|
153
157
|
|
|
@@ -160,7 +164,7 @@ YamlFieldInput (v-model)
|
|
|
160
164
|
↓
|
|
161
165
|
YamlFormField (v-model)
|
|
162
166
|
↓
|
|
163
|
-
|
|
167
|
+
YamlFormEditor (v-model)
|
|
164
168
|
↓
|
|
165
169
|
Parent Component (data binding)
|
|
166
170
|
```
|
|
@@ -179,7 +183,7 @@ Components (Rendering)
|
|
|
179
183
|
|
|
180
184
|
## Component API
|
|
181
185
|
|
|
182
|
-
###
|
|
186
|
+
### YamlFormEditor
|
|
183
187
|
|
|
184
188
|
Main entry point for the editor.
|
|
185
189
|
|
|
@@ -207,8 +211,33 @@ Main entry point for the editor.
|
|
|
207
211
|
All custom field component slots are supported:
|
|
208
212
|
|
|
209
213
|
```vue
|
|
210
|
-
<template #field-{component}="{ modelValue, readonly, valueType }">
|
|
214
|
+
<template #field-{component}="{ modelValue, readonly, valueType, updateModelValue }">
|
|
211
215
|
<!-- Your custom component -->
|
|
216
|
+
<!-- Use :model-value and @update:model-value, NOT v-model -->
|
|
217
|
+
</template>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Slot Props:**
|
|
221
|
+
- `modelValue`: Current field value (read-only prop)
|
|
222
|
+
- `readonly`: Whether field is in read-only mode
|
|
223
|
+
- `valueType`: Type identifier string
|
|
224
|
+
- `updateModelValue`: Function to update value: `(newValue) => void`
|
|
225
|
+
|
|
226
|
+
**Important:** You cannot use `v-model` on slot props (they're read-only). Use `:model-value` and `@update:model-value` instead:
|
|
227
|
+
|
|
228
|
+
```vue
|
|
229
|
+
<!-- ❌ WRONG - v-model doesn't work on slot props -->
|
|
230
|
+
<template #field-color="{ modelValue, readonly }">
|
|
231
|
+
<UColorPicker v-model="modelValue" :disabled="readonly" />
|
|
232
|
+
</template>
|
|
233
|
+
|
|
234
|
+
<!-- ✅ CORRECT - use updateModelValue function -->
|
|
235
|
+
<template #field-color="{ modelValue, readonly, updateModelValue }">
|
|
236
|
+
<UColorPicker
|
|
237
|
+
:model-value="modelValue"
|
|
238
|
+
:disabled="readonly"
|
|
239
|
+
@update:model-value="updateModelValue"
|
|
240
|
+
/>
|
|
212
241
|
</template>
|
|
213
242
|
```
|
|
214
243
|
|
|
@@ -240,7 +269,7 @@ Recursive component that handles individual fields.
|
|
|
240
269
|
|
|
241
270
|
#### Slots
|
|
242
271
|
|
|
243
|
-
Same as
|
|
272
|
+
Same as YamlFormEditor - all custom field slots are forwarded through the recursive hierarchy.
|
|
244
273
|
|
|
245
274
|
### YamlFieldInput
|
|
246
275
|
|
|
@@ -268,26 +297,73 @@ Renders input components for simple types.
|
|
|
268
297
|
#### Slots
|
|
269
298
|
|
|
270
299
|
```vue
|
|
271
|
-
<template #field-{component}="{ modelValue, readonly, valueType }">
|
|
300
|
+
<template #field-{component}="{ modelValue, readonly, valueType, updateModelValue }">
|
|
272
301
|
<!-- Custom input component -->
|
|
302
|
+
<!-- Use updateModelValue function for two-way binding -->
|
|
273
303
|
</template>
|
|
274
304
|
```
|
|
275
305
|
|
|
306
|
+
**Slot Props:**
|
|
307
|
+
- `modelValue`: Current value (read-only)
|
|
308
|
+
- `readonly`: Whether field is read-only
|
|
309
|
+
- `valueType`: Type identifier
|
|
310
|
+
- `updateModelValue`: Update function `(val) => void`
|
|
311
|
+
|
|
276
312
|
## Field Types
|
|
277
313
|
|
|
278
314
|
### Type Definition
|
|
279
315
|
|
|
280
316
|
```typescript
|
|
317
|
+
// Valid base types (type-safe!)
|
|
318
|
+
type YamlBaseType =
|
|
319
|
+
| 'string' // Text primitives
|
|
320
|
+
| 'number' // Numeric primitives
|
|
321
|
+
| 'boolean' // Boolean primitives
|
|
322
|
+
| 'date' // Date without time
|
|
323
|
+
| 'datetime' // Date with time
|
|
324
|
+
| 'string-array' // Array of strings (tags)
|
|
325
|
+
| 'array' // Generic array
|
|
326
|
+
| 'object' // Generic object
|
|
327
|
+
| 'null' // Null value
|
|
328
|
+
|
|
281
329
|
interface YamlFieldType {
|
|
282
|
-
type: string // Unique type identifier
|
|
330
|
+
type: string // Unique type identifier (e.g., 'color', 'email')
|
|
283
331
|
label: string // Display name in dropdowns
|
|
284
332
|
icon: string // Lucide icon name (i-lucide-*)
|
|
285
333
|
defaultValue: any // Default value or factory function
|
|
334
|
+
baseType: YamlBaseType // REQUIRED: Base type for conversion rules
|
|
286
335
|
component?: string // Optional: slot name for custom rendering
|
|
287
336
|
detect?: (value: any) => boolean // Optional: auto-detection function
|
|
288
337
|
}
|
|
289
338
|
```
|
|
290
339
|
|
|
340
|
+
**The `baseType` Field (Type-Safe!):**
|
|
341
|
+
|
|
342
|
+
The `baseType` field is **required** and must be one of the predefined base types. This provides:
|
|
343
|
+
- ✅ **TypeScript autocomplete** - IntelliSense suggests valid base types
|
|
344
|
+
- ✅ **Compile-time safety** - Typos are caught immediately
|
|
345
|
+
- ✅ **Conversion inheritance** - Custom types inherit conversion rules from their base
|
|
346
|
+
- ✅ **Clear semantics** - Explicit relationship between custom and base types
|
|
347
|
+
|
|
348
|
+
**Examples:**
|
|
349
|
+
```typescript
|
|
350
|
+
// ✅ Valid - 'string' is a valid YamlBaseType
|
|
351
|
+
{ type: 'color', baseType: 'string' }
|
|
352
|
+
|
|
353
|
+
// ✅ Valid - 'number' is a valid YamlBaseType
|
|
354
|
+
{ type: 'percentage', baseType: 'number' }
|
|
355
|
+
|
|
356
|
+
// ❌ Invalid - TypeScript error (not a valid base type)
|
|
357
|
+
{ type: 'custom', baseType: 'invalid' } // Type error!
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Conversion Inheritance:**
|
|
361
|
+
- A `color` type with `baseType: 'string'` can convert to/from anything a string can
|
|
362
|
+
- A `percentage` type with `baseType: 'number'` inherits number conversions
|
|
363
|
+
- Custom types can also convert directly to/from their base type
|
|
364
|
+
|
|
365
|
+
This enables powerful type hierarchies without duplicating conversion logic.
|
|
366
|
+
|
|
291
367
|
### Built-in Types
|
|
292
368
|
|
|
293
369
|
```typescript
|
|
@@ -427,7 +503,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
427
503
|
```
|
|
428
504
|
|
|
429
505
|
```vue
|
|
430
|
-
<
|
|
506
|
+
<YamlFormEditor v-model="data" :field-types="customTypes" />
|
|
431
507
|
```
|
|
432
508
|
|
|
433
509
|
### Adding a Runtime Type (With Custom Component)
|
|
@@ -439,23 +515,25 @@ const customTypes: YamlFieldType[] = [
|
|
|
439
515
|
label: 'Color',
|
|
440
516
|
icon: 'i-lucide-palette',
|
|
441
517
|
defaultValue: '#000000',
|
|
518
|
+
baseType: 'string', // Inherits string conversions
|
|
442
519
|
component: 'color', // Enables slot
|
|
443
|
-
detect: (value) => /^#[0-9A-Fa-f]{6}$/.test(value)
|
|
520
|
+
detect: (value) => typeof value === 'string' && /^#[0-9A-Fa-f]{6}$/.test(value)
|
|
444
521
|
}
|
|
445
522
|
]
|
|
446
523
|
```
|
|
447
524
|
|
|
448
525
|
```vue
|
|
449
|
-
<
|
|
450
|
-
<template #field-color="{ modelValue, readonly }">
|
|
526
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
527
|
+
<template #field-color="{ modelValue, readonly, updateModelValue }">
|
|
451
528
|
<input
|
|
452
529
|
type="color"
|
|
453
|
-
|
|
530
|
+
:value="modelValue"
|
|
454
531
|
:disabled="readonly"
|
|
532
|
+
@input="(e) => updateModelValue(e.target.value)"
|
|
455
533
|
class="w-full h-10 rounded"
|
|
456
534
|
/>
|
|
457
535
|
</template>
|
|
458
|
-
</
|
|
536
|
+
</YamlFormEditor>
|
|
459
537
|
```
|
|
460
538
|
|
|
461
539
|
### Overriding Built-in Types
|
|
@@ -473,11 +551,15 @@ const customTypes: YamlFieldType[] = [
|
|
|
473
551
|
```
|
|
474
552
|
|
|
475
553
|
```vue
|
|
476
|
-
<
|
|
477
|
-
<template #field-richtext="{ modelValue, readonly }">
|
|
478
|
-
<MyRichTextEditor
|
|
554
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
555
|
+
<template #field-richtext="{ modelValue, readonly, updateModelValue }">
|
|
556
|
+
<MyRichTextEditor
|
|
557
|
+
:model-value="modelValue"
|
|
558
|
+
:read-only="readonly"
|
|
559
|
+
@update:model-value="updateModelValue"
|
|
560
|
+
/>
|
|
479
561
|
</template>
|
|
480
|
-
</
|
|
562
|
+
</YamlFormEditor>
|
|
481
563
|
```
|
|
482
564
|
|
|
483
565
|
## Schema System
|
|
@@ -558,7 +640,7 @@ const config = ref({
|
|
|
558
640
|
</script>
|
|
559
641
|
|
|
560
642
|
<template>
|
|
561
|
-
<
|
|
643
|
+
<YamlFormEditor v-model="config" />
|
|
562
644
|
</template>
|
|
563
645
|
```
|
|
564
646
|
|
|
@@ -577,7 +659,7 @@ const article = ref({
|
|
|
577
659
|
</script>
|
|
578
660
|
|
|
579
661
|
<template>
|
|
580
|
-
<
|
|
662
|
+
<YamlFormEditor v-model="article" />
|
|
581
663
|
</template>
|
|
582
664
|
```
|
|
583
665
|
|
|
@@ -594,7 +676,7 @@ const data = ref({
|
|
|
594
676
|
</script>
|
|
595
677
|
|
|
596
678
|
<template>
|
|
597
|
-
<
|
|
679
|
+
<YamlFormEditor v-model="data" />
|
|
598
680
|
</template>
|
|
599
681
|
```
|
|
600
682
|
|
|
@@ -602,7 +684,7 @@ const data = ref({
|
|
|
602
684
|
|
|
603
685
|
```vue
|
|
604
686
|
<script setup lang="ts">
|
|
605
|
-
import type { YamlFieldType } from '
|
|
687
|
+
import type { YamlFieldType } from '@type32/yaml-editor-form'
|
|
606
688
|
|
|
607
689
|
// Define custom types
|
|
608
690
|
const customTypes: YamlFieldType[] = [
|
|
@@ -630,23 +712,25 @@ const post = ref({
|
|
|
630
712
|
</script>
|
|
631
713
|
|
|
632
714
|
<template>
|
|
633
|
-
<
|
|
715
|
+
<YamlFormEditor v-model="post" :field-types="customTypes">
|
|
634
716
|
<!-- Image picker component -->
|
|
635
|
-
<template #field-image="{ modelValue, readonly }">
|
|
717
|
+
<template #field-image="{ modelValue, readonly, updateModelValue }">
|
|
636
718
|
<MyImagePicker
|
|
637
|
-
|
|
719
|
+
:model-value="modelValue"
|
|
638
720
|
:disabled="readonly"
|
|
721
|
+
@update:model-value="updateModelValue"
|
|
639
722
|
/>
|
|
640
723
|
</template>
|
|
641
724
|
|
|
642
725
|
<!-- Markdown editor component -->
|
|
643
|
-
<template #field-markdown="{ modelValue, readonly }">
|
|
726
|
+
<template #field-markdown="{ modelValue, readonly, updateModelValue }">
|
|
644
727
|
<MyMarkdownEditor
|
|
645
|
-
|
|
728
|
+
:model-value="modelValue"
|
|
646
729
|
:read-only="readonly"
|
|
730
|
+
@update:model-value="updateModelValue"
|
|
647
731
|
/>
|
|
648
732
|
</template>
|
|
649
|
-
</
|
|
733
|
+
</YamlFormEditor>
|
|
650
734
|
</template>
|
|
651
735
|
```
|
|
652
736
|
|
|
@@ -672,7 +756,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
672
756
|
</script>
|
|
673
757
|
|
|
674
758
|
<template>
|
|
675
|
-
<
|
|
759
|
+
<YamlFormEditor v-model="data" :field-types="customTypes" />
|
|
676
760
|
</template>
|
|
677
761
|
```
|
|
678
762
|
|
|
@@ -680,7 +764,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
680
764
|
|
|
681
765
|
```vue
|
|
682
766
|
<template>
|
|
683
|
-
<
|
|
767
|
+
<YamlFormEditor v-model="data" readonly />
|
|
684
768
|
</template>
|
|
685
769
|
```
|
|
686
770
|
|
|
@@ -712,7 +796,7 @@ const complexData = ref({
|
|
|
712
796
|
</script>
|
|
713
797
|
|
|
714
798
|
<template>
|
|
715
|
-
<
|
|
799
|
+
<YamlFormEditor v-model="complexData" />
|
|
716
800
|
</template>
|
|
717
801
|
```
|
|
718
802
|
|
|
@@ -720,10 +804,10 @@ const complexData = ref({
|
|
|
720
804
|
|
|
721
805
|
```
|
|
722
806
|
components/
|
|
723
|
-
├──
|
|
807
|
+
├── YamlFormEditor.vue ← Entry component
|
|
724
808
|
├── YamlFormField.vue ← Recursive field component
|
|
725
809
|
├── YamlFieldInput.vue ← Input rendering component
|
|
726
|
-
└──
|
|
810
|
+
└── YamlCollapsible.vue ← Collapsible UI component
|
|
727
811
|
|
|
728
812
|
composables/
|
|
729
813
|
└── useYamlFieldTypes.ts ← Type registry & composable
|
|
@@ -903,27 +987,89 @@ interface YamlFieldInputProps {
|
|
|
903
987
|
|
|
904
988
|
### Slot Forwarding
|
|
905
989
|
|
|
906
|
-
Slots are automatically forwarded through the component hierarchy:
|
|
990
|
+
Slots are automatically forwarded through the component hierarchy using Vue 3's dynamic slot forwarding:
|
|
991
|
+
|
|
992
|
+
```
|
|
993
|
+
YamlFormEditor (receives slot from parent)
|
|
994
|
+
↓ forwards all slots with v-bind="slotProps"
|
|
995
|
+
YamlFormField (receives & forwards slots)
|
|
996
|
+
↓ forwards all slots with v-bind="slotProps"
|
|
997
|
+
↓ (recursively for nested structures)
|
|
998
|
+
YamlFieldInput (terminal - uses slot)
|
|
999
|
+
↓ renders slot: #field-{component}
|
|
1000
|
+
↓ provides props: { modelValue, readonly, valueType, updateModelValue }
|
|
1001
|
+
Custom Component
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**How It Works:**
|
|
1005
|
+
|
|
1006
|
+
1. **YamlFormEditor** (lines 89-92): Receives slots and forwards to YamlFormField
|
|
1007
|
+
2. **YamlFormField** (lines 634-636): Forwards slots to YamlFieldInput OR itself (for recursion)
|
|
1008
|
+
3. **YamlFieldInput** (lines 88-95): Final destination - renders slot with props
|
|
907
1009
|
|
|
1010
|
+
**Slot Props Flow:**
|
|
1011
|
+
|
|
1012
|
+
The `updateModelValue` function is created at the YamlFieldInput level and allows your custom component to update the value:
|
|
1013
|
+
|
|
1014
|
+
```typescript
|
|
1015
|
+
// In YamlFieldInput.vue
|
|
1016
|
+
:update-model-value="(val: YamlValue) => modelValue = val"
|
|
908
1017
|
```
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1018
|
+
|
|
1019
|
+
This function captures the parent's `modelValue` ref and updates it directly, maintaining reactivity throughout the hierarchy.
|
|
1020
|
+
|
|
1021
|
+
**Example with Nested Structure:**
|
|
1022
|
+
|
|
1023
|
+
```vue
|
|
1024
|
+
<!-- Works at any depth! -->
|
|
1025
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
1026
|
+
<template #field-color="{ modelValue, updateModelValue }">
|
|
1027
|
+
<UColorPicker
|
|
1028
|
+
:model-value="modelValue"
|
|
1029
|
+
@update:model-value="updateModelValue"
|
|
1030
|
+
/>
|
|
1031
|
+
</template>
|
|
1032
|
+
</YamlFormEditor>
|
|
914
1033
|
```
|
|
915
1034
|
|
|
916
|
-
|
|
1035
|
+
Even if your color field is deeply nested (`data.theme.colors.primary`), the slot works identically because slots are forwarded at every level.
|
|
917
1036
|
|
|
918
1037
|
### Type Priority
|
|
919
1038
|
|
|
920
|
-
|
|
1039
|
+
**Detection Order (NEW in v0.2.0):**
|
|
1040
|
+
|
|
1041
|
+
Custom types with `detect` functions are now checked **before** default types:
|
|
1042
|
+
|
|
1043
|
+
1. **Custom types** (checked first) - Your custom types take priority
|
|
1044
|
+
2. **Default types** (checked second) - Built-in types as fallback
|
|
1045
|
+
3. First matching type wins
|
|
1046
|
+
|
|
1047
|
+
This means:
|
|
1048
|
+
- ✅ Your `color` type will be detected before the default `string` type
|
|
1049
|
+
- ✅ Custom types override default detection behavior
|
|
1050
|
+
- ✅ More specific types should still have more specific detect functions
|
|
921
1051
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1052
|
+
**Example:**
|
|
1053
|
+
```typescript
|
|
1054
|
+
// Custom color type checked FIRST
|
|
1055
|
+
const customTypes = [{
|
|
1056
|
+
type: 'color',
|
|
1057
|
+
baseType: 'string',
|
|
1058
|
+
detect: (v) => typeof v === 'string' && /^#[0-9A-Fa-f]{6}$/.test(v)
|
|
1059
|
+
}]
|
|
1060
|
+
|
|
1061
|
+
// Value '#FF0000' will match 'color' before 'string'
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
**Base Type Conversions:**
|
|
925
1065
|
|
|
926
|
-
|
|
1066
|
+
When using `baseType`, conversion rules follow this logic:
|
|
1067
|
+
|
|
1068
|
+
1. Can convert between type and its baseType (e.g., `color` ↔ `string`)
|
|
1069
|
+
2. Can convert to anything the baseType can (e.g., `color` → `number` because `string` → `number`)
|
|
1070
|
+
3. Custom conversion rules take precedence over inherited rules
|
|
1071
|
+
|
|
1072
|
+
**Example order for default types:**
|
|
927
1073
|
```typescript
|
|
928
1074
|
[
|
|
929
1075
|
{ type: 'datetime', detect: (v) => isDateTimeString(v) }, // Specific
|
|
@@ -1046,11 +1192,11 @@ For issues, questions, or feature requests, refer to the main Vertex project doc
|
|
|
1046
1192
|
|
|
1047
1193
|
### Core Components
|
|
1048
1194
|
|
|
1049
|
-
- **
|
|
1195
|
+
- **YamlFormEditor**: Entry point component for the editor
|
|
1050
1196
|
- **YamlFormField**: Recursive component handling individual fields
|
|
1051
1197
|
- **YamlFieldInput**: Input rendering component for simple types
|
|
1198
|
+
- **YamlCollapsible**: UI component for collapsible sections
|
|
1052
1199
|
- **useYamlFieldTypes**: Composable for type registry and management
|
|
1053
|
-
- **Collapsible**: UI component for collapsible sections
|
|
1054
1200
|
|
|
1055
1201
|
### Key Concepts
|
|
1056
1202
|
|
|
@@ -1077,11 +1223,15 @@ For issues, questions, or feature requests, refer to the main Vertex project doc
|
|
|
1077
1223
|
|
|
1078
1224
|
**Add Custom Component:**
|
|
1079
1225
|
```vue
|
|
1080
|
-
<
|
|
1081
|
-
<template #field-{type}="
|
|
1082
|
-
<
|
|
1226
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
1227
|
+
<template #field-{type}="{ modelValue, readonly, updateModelValue }">
|
|
1228
|
+
<MyComponent
|
|
1229
|
+
:model-value="modelValue"
|
|
1230
|
+
:disabled="readonly"
|
|
1231
|
+
@update:model-value="updateModelValue"
|
|
1232
|
+
/>
|
|
1083
1233
|
</template>
|
|
1084
|
-
</
|
|
1234
|
+
</YamlFormEditor>
|
|
1085
1235
|
```
|
|
1086
1236
|
|
|
1087
1237
|
**Type Conversion:**
|
package/dist/module.json
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
5
|
+
};
|
|
6
|
+
declare const open: import("vue").ModelRef<boolean, string, boolean, boolean>;
|
|
7
|
+
type __VLS_ModelProps = {
|
|
8
|
+
'open'?: typeof open['value'];
|
|
9
|
+
};
|
|
10
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
1
11
|
declare var __VLS_13: {}, __VLS_15: {}, __VLS_18: {};
|
|
2
12
|
type __VLS_Slots = {} & {
|
|
3
13
|
badge?: (props: typeof __VLS_13) => any;
|
|
@@ -6,37 +16,14 @@ type __VLS_Slots = {} & {
|
|
|
6
16
|
} & {
|
|
7
17
|
default?: (props: typeof __VLS_18) => any;
|
|
8
18
|
};
|
|
9
|
-
declare const __VLS_base: import("vue").DefineComponent<import("vue").
|
|
10
|
-
label: {
|
|
11
|
-
type: StringConstructor;
|
|
12
|
-
required: true;
|
|
13
|
-
};
|
|
14
|
-
defaultOpen: {
|
|
15
|
-
type: BooleanConstructor;
|
|
16
|
-
default: boolean;
|
|
17
|
-
};
|
|
18
|
-
open: {
|
|
19
|
-
type: import("vue").PropType<boolean>;
|
|
20
|
-
};
|
|
21
|
-
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
20
|
"update:open": (value: boolean) => any;
|
|
23
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
24
|
-
label: {
|
|
25
|
-
type: StringConstructor;
|
|
26
|
-
required: true;
|
|
27
|
-
};
|
|
28
|
-
defaultOpen: {
|
|
29
|
-
type: BooleanConstructor;
|
|
30
|
-
default: boolean;
|
|
31
|
-
};
|
|
32
|
-
open: {
|
|
33
|
-
type: import("vue").PropType<boolean>;
|
|
34
|
-
};
|
|
35
|
-
}>> & Readonly<{
|
|
21
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
36
22
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
37
23
|
}>, {
|
|
38
24
|
defaultOpen: boolean;
|
|
39
|
-
|
|
25
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
26
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
40
27
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
41
28
|
declare const _default: typeof __VLS_export;
|
|
42
29
|
export default _default;
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineProps({
|
|
3
|
-
label: {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
defaultOpen: {
|
|
8
|
-
type: Boolean,
|
|
9
|
-
default: false
|
|
10
|
-
}
|
|
3
|
+
label: { type: String, required: true },
|
|
4
|
+
defaultOpen: { type: Boolean, required: false, default: false },
|
|
5
|
+
size: { type: String, required: false, default: "xs" }
|
|
11
6
|
});
|
|
12
7
|
const open = defineModel("open", { type: Boolean, default: void 0 });
|
|
13
8
|
</script>
|
|
@@ -17,6 +12,7 @@ const open = defineModel("open", { type: Boolean, default: void 0 });
|
|
|
17
12
|
v-model:open="open"
|
|
18
13
|
:default-open="defaultOpen"
|
|
19
14
|
class="group/collapsible"
|
|
15
|
+
:size="size"
|
|
20
16
|
>
|
|
21
17
|
<div class="flex items-center justify-between gap-2 w-full">
|
|
22
18
|
<div class="flex items-center gap-2">
|
|
@@ -27,7 +23,7 @@ const open = defineModel("open", { type: Boolean, default: void 0 });
|
|
|
27
23
|
class="size-2.5 text-muted transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
|
28
24
|
/>
|
|
29
25
|
</div>
|
|
30
|
-
<span class="text
|
|
26
|
+
<span :class="`text-${size}`" class="font-medium text-highlighted tracking-tight">{{ label }}</span>
|
|
31
27
|
<slot name="badge"/>
|
|
32
28
|
</div>
|
|
33
29
|
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
5
|
+
};
|
|
6
|
+
declare const open: import("vue").ModelRef<boolean, string, boolean, boolean>;
|
|
7
|
+
type __VLS_ModelProps = {
|
|
8
|
+
'open'?: typeof open['value'];
|
|
9
|
+
};
|
|
10
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
1
11
|
declare var __VLS_13: {}, __VLS_15: {}, __VLS_18: {};
|
|
2
12
|
type __VLS_Slots = {} & {
|
|
3
13
|
badge?: (props: typeof __VLS_13) => any;
|
|
@@ -6,37 +16,14 @@ type __VLS_Slots = {} & {
|
|
|
6
16
|
} & {
|
|
7
17
|
default?: (props: typeof __VLS_18) => any;
|
|
8
18
|
};
|
|
9
|
-
declare const __VLS_base: import("vue").DefineComponent<import("vue").
|
|
10
|
-
label: {
|
|
11
|
-
type: StringConstructor;
|
|
12
|
-
required: true;
|
|
13
|
-
};
|
|
14
|
-
defaultOpen: {
|
|
15
|
-
type: BooleanConstructor;
|
|
16
|
-
default: boolean;
|
|
17
|
-
};
|
|
18
|
-
open: {
|
|
19
|
-
type: import("vue").PropType<boolean>;
|
|
20
|
-
};
|
|
21
|
-
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
20
|
"update:open": (value: boolean) => any;
|
|
23
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
24
|
-
label: {
|
|
25
|
-
type: StringConstructor;
|
|
26
|
-
required: true;
|
|
27
|
-
};
|
|
28
|
-
defaultOpen: {
|
|
29
|
-
type: BooleanConstructor;
|
|
30
|
-
default: boolean;
|
|
31
|
-
};
|
|
32
|
-
open: {
|
|
33
|
-
type: import("vue").PropType<boolean>;
|
|
34
|
-
};
|
|
35
|
-
}>> & Readonly<{
|
|
21
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
36
22
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
37
23
|
}>, {
|
|
38
24
|
defaultOpen: boolean;
|
|
39
|
-
|
|
25
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
26
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
40
27
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
41
28
|
declare const _default: typeof __VLS_export;
|
|
42
29
|
export default _default;
|
|
@@ -24,6 +24,7 @@ type __VLS_Props = {
|
|
|
24
24
|
readonly?: boolean;
|
|
25
25
|
/** Optional field type definition for custom component detection */
|
|
26
26
|
fieldType?: YamlFieldType;
|
|
27
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
27
28
|
};
|
|
28
29
|
type __VLS_ModelProps = {
|
|
29
30
|
modelValue: YamlValue;
|
|
@@ -33,6 +34,7 @@ declare var __VLS_2: string, __VLS_3: {
|
|
|
33
34
|
modelValue: YamlValue;
|
|
34
35
|
readonly: boolean;
|
|
35
36
|
valueType: string;
|
|
37
|
+
updateModelValue: (val: YamlValue) => YamlValue;
|
|
36
38
|
};
|
|
37
39
|
type __VLS_Slots = {} & {
|
|
38
40
|
[K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
|
|
@@ -42,6 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
42
44
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
43
45
|
"onUpdate:modelValue"?: ((value: YamlValue) => any) | undefined;
|
|
44
46
|
}>, {
|
|
47
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
45
48
|
readonly: boolean;
|
|
46
49
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
47
50
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -5,7 +5,8 @@ const modelValue = defineModel({ type: [String, Number, Boolean, null, Date, Arr
|
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
valueType: { type: String, required: true },
|
|
7
7
|
readonly: { type: Boolean, required: false, default: false },
|
|
8
|
-
fieldType: { type: Object, required: false }
|
|
8
|
+
fieldType: { type: Object, required: false },
|
|
9
|
+
size: { type: String, required: false, default: "xs" }
|
|
9
10
|
});
|
|
10
11
|
function isDateObject(val) {
|
|
11
12
|
return val instanceof Date && !isNaN(val.getTime());
|
|
@@ -53,6 +54,7 @@ const customSlotName = computed(() => {
|
|
|
53
54
|
:model-value="modelValue"
|
|
54
55
|
:readonly="readonly"
|
|
55
56
|
:value-type="valueType"
|
|
57
|
+
:update-model-value="(val) => modelValue = val"
|
|
56
58
|
/>
|
|
57
59
|
|
|
58
60
|
<!-- Built-in Input Components -->
|
|
@@ -61,7 +63,7 @@ const customSlotName = computed(() => {
|
|
|
61
63
|
<UInput
|
|
62
64
|
v-if="valueType === 'string'"
|
|
63
65
|
:model-value="String(modelValue)"
|
|
64
|
-
size="
|
|
66
|
+
:size="size"
|
|
65
67
|
:disabled="readonly"
|
|
66
68
|
placeholder="Enter text..."
|
|
67
69
|
@update:model-value="(val) => modelValue = val"
|
|
@@ -71,7 +73,7 @@ const customSlotName = computed(() => {
|
|
|
71
73
|
<UTextarea
|
|
72
74
|
v-else-if="valueType === 'textarea'"
|
|
73
75
|
:model-value="String(modelValue)"
|
|
74
|
-
size="
|
|
76
|
+
:size="size"
|
|
75
77
|
:disabled="readonly"
|
|
76
78
|
placeholder="Enter long text..."
|
|
77
79
|
:rows="4"
|
|
@@ -83,7 +85,7 @@ const customSlotName = computed(() => {
|
|
|
83
85
|
<UInputNumber
|
|
84
86
|
v-else-if="valueType === 'number'"
|
|
85
87
|
:model-value="Number(modelValue)"
|
|
86
|
-
size="
|
|
88
|
+
:size="size"
|
|
87
89
|
:disabled="readonly"
|
|
88
90
|
placeholder="0"
|
|
89
91
|
@update:model-value="(val) => modelValue = val ?? 0"
|
|
@@ -93,7 +95,7 @@ const customSlotName = computed(() => {
|
|
|
93
95
|
<USwitch
|
|
94
96
|
v-else-if="valueType === 'boolean'"
|
|
95
97
|
:model-value="Boolean(modelValue)"
|
|
96
|
-
size="
|
|
98
|
+
:size="size"
|
|
97
99
|
:disabled="readonly"
|
|
98
100
|
@update:model-value="(val) => modelValue = val"
|
|
99
101
|
/>
|
|
@@ -102,7 +104,7 @@ const customSlotName = computed(() => {
|
|
|
102
104
|
<UInputDate
|
|
103
105
|
v-else-if="valueType === 'date'"
|
|
104
106
|
:model-value="typeof modelValue === 'string' ? stringToCalendarDate(modelValue) : jsDateToCalendarDate(/* @__PURE__ */ new Date())"
|
|
105
|
-
size="
|
|
107
|
+
:size="size"
|
|
106
108
|
:disabled="readonly"
|
|
107
109
|
@update:model-value="(val) => {
|
|
108
110
|
if (val && 'year' in val) {
|
|
@@ -115,7 +117,7 @@ const customSlotName = computed(() => {
|
|
|
115
117
|
<UInputDate
|
|
116
118
|
v-else-if="valueType === 'datetime'"
|
|
117
119
|
:model-value="typeof modelValue === 'string' ? stringToCalendarDateTime(modelValue) : jsDateToCalendarDateTime(/* @__PURE__ */ new Date())"
|
|
118
|
-
size="
|
|
120
|
+
:size="size"
|
|
119
121
|
:disabled="readonly"
|
|
120
122
|
granularity="second"
|
|
121
123
|
@update:model-value="(val) => {
|
|
@@ -134,7 +136,7 @@ const customSlotName = computed(() => {
|
|
|
134
136
|
<UInputTags
|
|
135
137
|
v-else-if="valueType === 'string-array'"
|
|
136
138
|
:model-value="Array.isArray(modelValue) ? modelValue : []"
|
|
137
|
-
size="
|
|
139
|
+
:size="size"
|
|
138
140
|
:disabled="readonly"
|
|
139
141
|
placeholder="Add tags..."
|
|
140
142
|
@update:model-value="(val) => modelValue = val"
|
|
@@ -149,7 +151,7 @@ const customSlotName = computed(() => {
|
|
|
149
151
|
<UInput
|
|
150
152
|
v-else
|
|
151
153
|
:model-value="String(modelValue || '')"
|
|
152
|
-
size="
|
|
154
|
+
:size="size"
|
|
153
155
|
:disabled="readonly"
|
|
154
156
|
placeholder="Enter value..."
|
|
155
157
|
@update:model-value="(val) => modelValue = val"
|
|
@@ -24,6 +24,7 @@ type __VLS_Props = {
|
|
|
24
24
|
readonly?: boolean;
|
|
25
25
|
/** Optional field type definition for custom component detection */
|
|
26
26
|
fieldType?: YamlFieldType;
|
|
27
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
27
28
|
};
|
|
28
29
|
type __VLS_ModelProps = {
|
|
29
30
|
modelValue: YamlValue;
|
|
@@ -33,6 +34,7 @@ declare var __VLS_2: string, __VLS_3: {
|
|
|
33
34
|
modelValue: YamlValue;
|
|
34
35
|
readonly: boolean;
|
|
35
36
|
valueType: string;
|
|
37
|
+
updateModelValue: (val: YamlValue) => YamlValue;
|
|
36
38
|
};
|
|
37
39
|
type __VLS_Slots = {} & {
|
|
38
40
|
[K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
|
|
@@ -42,6 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
42
44
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
43
45
|
"onUpdate:modelValue"?: ((value: YamlValue) => any) | undefined;
|
|
44
46
|
}>, {
|
|
47
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
45
48
|
readonly: boolean;
|
|
46
49
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
47
50
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -4,6 +4,7 @@ type __VLS_Props = {
|
|
|
4
4
|
readonly?: boolean;
|
|
5
5
|
/** Custom field type definitions (merged with defaults) */
|
|
6
6
|
fieldTypes?: YamlFieldType[];
|
|
7
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
7
8
|
};
|
|
8
9
|
type __VLS_ModelProps = {
|
|
9
10
|
/**
|
|
@@ -54,6 +55,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
54
55
|
slug?: string;
|
|
55
56
|
}) => any) | undefined;
|
|
56
57
|
}>, {
|
|
58
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
57
59
|
readonly: boolean;
|
|
58
60
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
59
61
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -5,7 +5,8 @@ const data = defineModel({ type: null, ...{ required: true } });
|
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
filePath: { type: String, required: false },
|
|
7
7
|
readonly: { type: Boolean, required: false, default: false },
|
|
8
|
-
fieldTypes: { type: Array, required: false }
|
|
8
|
+
fieldTypes: { type: Array, required: false },
|
|
9
|
+
size: { type: String, required: false, default: "xs" }
|
|
9
10
|
});
|
|
10
11
|
const { getDefaultValue, getTypeMenuItems } = useYamlFieldTypes(props.fieldTypes);
|
|
11
12
|
const isDirty = ref(false);
|
|
@@ -38,6 +39,7 @@ const addFieldOptions = computed(() => {
|
|
|
38
39
|
:field-key="String(key)"
|
|
39
40
|
:readonly="readonly"
|
|
40
41
|
:field-types="fieldTypes"
|
|
42
|
+
:size="size"
|
|
41
43
|
@remove="removeField(String(key))"
|
|
42
44
|
@update:field-key="(newKey) => {
|
|
43
45
|
if (newKey !== key) {
|
|
@@ -56,13 +58,13 @@ const addFieldOptions = computed(() => {
|
|
|
56
58
|
<UDropdownMenu
|
|
57
59
|
v-if="!readonly"
|
|
58
60
|
:items="[addFieldOptions]"
|
|
59
|
-
size="
|
|
61
|
+
:size="size"
|
|
60
62
|
>
|
|
61
63
|
<UButton
|
|
62
64
|
icon="i-lucide-plus"
|
|
63
65
|
label="Add Field"
|
|
64
66
|
variant="ghost"
|
|
65
|
-
size="
|
|
67
|
+
:size="size"
|
|
66
68
|
/>
|
|
67
69
|
</UDropdownMenu>
|
|
68
70
|
</div>
|
|
@@ -4,6 +4,7 @@ type __VLS_Props = {
|
|
|
4
4
|
readonly?: boolean;
|
|
5
5
|
/** Custom field type definitions (merged with defaults) */
|
|
6
6
|
fieldTypes?: YamlFieldType[];
|
|
7
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
7
8
|
};
|
|
8
9
|
type __VLS_ModelProps = {
|
|
9
10
|
/**
|
|
@@ -54,6 +55,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
54
55
|
slug?: string;
|
|
55
56
|
}) => any) | undefined;
|
|
56
57
|
}>, {
|
|
58
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
57
59
|
readonly: boolean;
|
|
58
60
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
59
61
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -25,6 +25,7 @@ type __VLS_Props = {
|
|
|
25
25
|
depth?: number;
|
|
26
26
|
/** Custom field type definitions (merged with defaults) */
|
|
27
27
|
fieldTypes?: YamlFieldType[];
|
|
28
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
28
29
|
};
|
|
29
30
|
type __VLS_ModelProps = {
|
|
30
31
|
modelValue: YamlValue;
|
|
@@ -43,6 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
43
44
|
"onUpdate:modelValue"?: ((value: YamlValue) => any) | undefined;
|
|
44
45
|
"onUpdate:fieldKey"?: ((newKey: string) => any) | undefined;
|
|
45
46
|
}>, {
|
|
47
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
46
48
|
readonly: boolean;
|
|
47
49
|
depth: number;
|
|
48
50
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -6,7 +6,8 @@ const props = defineProps({
|
|
|
6
6
|
fieldKey: { type: String, required: true },
|
|
7
7
|
readonly: { type: Boolean, required: false, default: false },
|
|
8
8
|
depth: { type: Number, required: false, default: 0 },
|
|
9
|
-
fieldTypes: { type: Array, required: false }
|
|
9
|
+
fieldTypes: { type: Array, required: false },
|
|
10
|
+
size: { type: String, required: false, default: "xs" }
|
|
10
11
|
});
|
|
11
12
|
const emit = defineEmits(["remove", "update:fieldKey"]);
|
|
12
13
|
const {
|
|
@@ -67,6 +68,13 @@ function saveKey() {
|
|
|
67
68
|
function isValidConversion(fromType, toType) {
|
|
68
69
|
if (fromType === toType) return true;
|
|
69
70
|
if (fromType === "null") return true;
|
|
71
|
+
const fromFieldType = getFieldType(fromType);
|
|
72
|
+
const toFieldType = getFieldType(toType);
|
|
73
|
+
if (!fromFieldType || !toFieldType) return false;
|
|
74
|
+
const fromBase = fromFieldType.baseType;
|
|
75
|
+
const toBase = toFieldType.baseType;
|
|
76
|
+
if (fromType === toBase || toType === fromBase) return true;
|
|
77
|
+
if (fromBase === toType || toBase === fromType) return true;
|
|
70
78
|
const conversionRules = {
|
|
71
79
|
// Primitives can convert to other primitives and arrays
|
|
72
80
|
"string": ["number", "boolean", "date", "datetime", "string-array", "null"],
|
|
@@ -80,10 +88,12 @@ function isValidConversion(fromType, toType) {
|
|
|
80
88
|
// Arrays can only convert to string-array or null (converting to primitives is unsafe)
|
|
81
89
|
"array": ["string-array", "null"],
|
|
82
90
|
// Objects can only convert to null (converting to primitives is useless)
|
|
83
|
-
"object": ["null"]
|
|
91
|
+
"object": ["null"],
|
|
92
|
+
// Null is always terminal
|
|
93
|
+
"null": []
|
|
84
94
|
};
|
|
85
|
-
const allowedConversions = conversionRules[
|
|
86
|
-
return allowedConversions.includes(
|
|
95
|
+
const allowedConversions = conversionRules[fromBase] || [];
|
|
96
|
+
return allowedConversions.includes(toBase);
|
|
87
97
|
}
|
|
88
98
|
function convertType(newType) {
|
|
89
99
|
const currentType = valueType.value;
|
|
@@ -255,7 +265,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
255
265
|
<UInput
|
|
256
266
|
v-if="isEditingKey"
|
|
257
267
|
v-model="editingKeyValue"
|
|
258
|
-
size="
|
|
268
|
+
:size="size"
|
|
259
269
|
autofocus
|
|
260
270
|
class="mb-1"
|
|
261
271
|
@blur="saveKey"
|
|
@@ -266,7 +276,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
266
276
|
<!-- Collapsible for non-edit mode -->
|
|
267
277
|
<YamlCollapsible v-else v-model:open="isOpen" :default-open="true" :label="fieldKey">
|
|
268
278
|
<template #badge>
|
|
269
|
-
<UBadge size="
|
|
279
|
+
<UBadge :size="size" variant="soft" color="neutral">{{ itemCount }}</UBadge>
|
|
270
280
|
</template>
|
|
271
281
|
|
|
272
282
|
<template #actions>
|
|
@@ -276,7 +286,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
276
286
|
v-if="!readonly && !isEditingKey && !isArrayItem"
|
|
277
287
|
icon="i-lucide-pencil"
|
|
278
288
|
variant="ghost"
|
|
279
|
-
size="
|
|
289
|
+
:size="size"
|
|
280
290
|
color="neutral"
|
|
281
291
|
class="text-muted"
|
|
282
292
|
@click.stop="startEditingKey"
|
|
@@ -286,12 +296,12 @@ const addArrayItemOptions = computed(() => {
|
|
|
286
296
|
<UDropdownMenu
|
|
287
297
|
:items="[typeOptions]"
|
|
288
298
|
:disabled="readonly"
|
|
289
|
-
size="
|
|
299
|
+
:size="size"
|
|
290
300
|
>
|
|
291
301
|
<UButton
|
|
292
302
|
:icon="selectedType?.icon || 'i-heroicons-circle-question-mark'"
|
|
293
303
|
variant="soft"
|
|
294
|
-
size="
|
|
304
|
+
:size="size"
|
|
295
305
|
:disabled="readonly"
|
|
296
306
|
/>
|
|
297
307
|
</UDropdownMenu>
|
|
@@ -301,7 +311,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
301
311
|
v-if="!readonly"
|
|
302
312
|
icon="i-lucide-trash"
|
|
303
313
|
variant="ghost"
|
|
304
|
-
size="
|
|
314
|
+
:size="size"
|
|
305
315
|
color="error"
|
|
306
316
|
@click.stop="emit('remove')"
|
|
307
317
|
/>
|
|
@@ -337,6 +347,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
337
347
|
if (Array.isArray(modelValue)) modelValue[index] = val;
|
|
338
348
|
}"
|
|
339
349
|
@remove="removeArrayItem(index)"
|
|
350
|
+
:size="size"
|
|
340
351
|
/>
|
|
341
352
|
</div>
|
|
342
353
|
</div>
|
|
@@ -346,13 +357,13 @@ const addArrayItemOptions = computed(() => {
|
|
|
346
357
|
<UDropdownMenu
|
|
347
358
|
v-if="!readonly"
|
|
348
359
|
:items="[addArrayItemOptions]"
|
|
349
|
-
size="
|
|
360
|
+
:size="size"
|
|
350
361
|
>
|
|
351
362
|
<UButton
|
|
352
363
|
icon="i-lucide-plus"
|
|
353
364
|
label="Add Item"
|
|
354
365
|
variant="link"
|
|
355
|
-
size="
|
|
366
|
+
:size="size"
|
|
356
367
|
color="neutral"
|
|
357
368
|
/>
|
|
358
369
|
</UDropdownMenu>
|
|
@@ -363,7 +374,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
363
374
|
icon="i-lucide-copy-plus"
|
|
364
375
|
label="From Template"
|
|
365
376
|
variant="link"
|
|
366
|
-
size="
|
|
377
|
+
:size="size"
|
|
367
378
|
color="neutral"
|
|
368
379
|
@click="addArrayItemFromTemplate"
|
|
369
380
|
/>
|
|
@@ -390,6 +401,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
390
401
|
:readonly="readonly"
|
|
391
402
|
:depth="depth + 1"
|
|
392
403
|
:field-types="fieldTypes"
|
|
404
|
+
:size="size"
|
|
393
405
|
@update:model-value="(val) => {
|
|
394
406
|
if (typeof modelValue === 'object' && !Array.isArray(modelValue) && modelValue !== null && !isDateObject(modelValue)) {
|
|
395
407
|
modelValue[key] = val;
|
|
@@ -410,13 +422,13 @@ const addArrayItemOptions = computed(() => {
|
|
|
410
422
|
<UDropdownMenu
|
|
411
423
|
v-if="!readonly"
|
|
412
424
|
:items="[addFieldOptions]"
|
|
413
|
-
size="
|
|
425
|
+
:size="size"
|
|
414
426
|
>
|
|
415
427
|
<UButton
|
|
416
428
|
icon="i-lucide-plus"
|
|
417
429
|
label="Add Field"
|
|
418
430
|
variant="link"
|
|
419
|
-
size="
|
|
431
|
+
:size="size"
|
|
420
432
|
color="neutral"
|
|
421
433
|
/>
|
|
422
434
|
</UDropdownMenu>
|
|
@@ -432,7 +444,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
432
444
|
<UInput
|
|
433
445
|
v-if="isEditingKey"
|
|
434
446
|
v-model="editingKeyValue"
|
|
435
|
-
size="
|
|
447
|
+
:size="size"
|
|
436
448
|
autofocus
|
|
437
449
|
@blur="saveKey"
|
|
438
450
|
@keydown.enter="saveKey"
|
|
@@ -454,12 +466,12 @@ const addArrayItemOptions = computed(() => {
|
|
|
454
466
|
<UDropdownMenu
|
|
455
467
|
:items="[typeOptions]"
|
|
456
468
|
:disabled="readonly"
|
|
457
|
-
size="
|
|
469
|
+
:size="size"
|
|
458
470
|
>
|
|
459
471
|
<UButton
|
|
460
472
|
:icon="selectedType?.icon || 'i-lucide-circle-question-mark'"
|
|
461
473
|
variant="soft"
|
|
462
|
-
size="
|
|
474
|
+
:size="size"
|
|
463
475
|
:disabled="readonly"
|
|
464
476
|
/>
|
|
465
477
|
</UDropdownMenu>
|
|
@@ -469,7 +481,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
469
481
|
v-if="!readonly"
|
|
470
482
|
icon="i-lucide-trash"
|
|
471
483
|
variant="ghost"
|
|
472
|
-
size="
|
|
484
|
+
:size="size"
|
|
473
485
|
color="error"
|
|
474
486
|
@click="emit('remove')"
|
|
475
487
|
/>
|
|
@@ -480,6 +492,7 @@ const addArrayItemOptions = computed(() => {
|
|
|
480
492
|
v-model="modelValue"
|
|
481
493
|
:value-type="valueType"
|
|
482
494
|
:readonly="readonly"
|
|
495
|
+
:size="size"
|
|
483
496
|
:field-type="getFieldType(valueType)"
|
|
484
497
|
>
|
|
485
498
|
<!-- Forward all custom field component slots -->
|
|
@@ -25,6 +25,7 @@ type __VLS_Props = {
|
|
|
25
25
|
depth?: number;
|
|
26
26
|
/** Custom field type definitions (merged with defaults) */
|
|
27
27
|
fieldTypes?: YamlFieldType[];
|
|
28
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs";
|
|
28
29
|
};
|
|
29
30
|
type __VLS_ModelProps = {
|
|
30
31
|
modelValue: YamlValue;
|
|
@@ -43,6 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
43
44
|
"onUpdate:modelValue"?: ((value: YamlValue) => any) | undefined;
|
|
44
45
|
"onUpdate:fieldKey"?: ((newKey: string) => any) | undefined;
|
|
45
46
|
}>, {
|
|
47
|
+
size: "xl" | "lg" | "md" | "sm" | "xs";
|
|
46
48
|
readonly: boolean;
|
|
47
49
|
depth: number;
|
|
48
50
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -20,6 +20,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
20
20
|
label: "Text",
|
|
21
21
|
icon: "i-lucide-type",
|
|
22
22
|
defaultValue: "",
|
|
23
|
+
baseType: "string",
|
|
23
24
|
detect: (value) => typeof value === "string" && !isDateString(value) && !isDateTimeString(value)
|
|
24
25
|
},
|
|
25
26
|
{
|
|
@@ -27,6 +28,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
27
28
|
label: "Long Text",
|
|
28
29
|
icon: "i-lucide-align-left",
|
|
29
30
|
defaultValue: "",
|
|
31
|
+
baseType: "string",
|
|
30
32
|
component: "textarea"
|
|
31
33
|
},
|
|
32
34
|
{
|
|
@@ -34,6 +36,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
34
36
|
label: "Number",
|
|
35
37
|
icon: "i-lucide-hash",
|
|
36
38
|
defaultValue: 0,
|
|
39
|
+
baseType: "number",
|
|
37
40
|
detect: (value) => typeof value === "number"
|
|
38
41
|
},
|
|
39
42
|
{
|
|
@@ -41,6 +44,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
41
44
|
label: "Boolean",
|
|
42
45
|
icon: "i-lucide-circle-check",
|
|
43
46
|
defaultValue: false,
|
|
47
|
+
baseType: "boolean",
|
|
44
48
|
detect: (value) => typeof value === "boolean"
|
|
45
49
|
},
|
|
46
50
|
{
|
|
@@ -48,6 +52,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
48
52
|
label: "Date",
|
|
49
53
|
icon: "i-lucide-calendar",
|
|
50
54
|
defaultValue: () => /* @__PURE__ */ new Date(),
|
|
55
|
+
baseType: "date",
|
|
51
56
|
detect: (value) => isDateObject(value) || isDateString(value)
|
|
52
57
|
},
|
|
53
58
|
{
|
|
@@ -55,6 +60,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
55
60
|
label: "Date & Time",
|
|
56
61
|
icon: "i-lucide-calendar-clock",
|
|
57
62
|
defaultValue: () => /* @__PURE__ */ new Date(),
|
|
63
|
+
baseType: "datetime",
|
|
58
64
|
detect: (value) => isDateTimeString(value)
|
|
59
65
|
},
|
|
60
66
|
{
|
|
@@ -62,6 +68,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
62
68
|
label: "Tags",
|
|
63
69
|
icon: "i-lucide-tags",
|
|
64
70
|
defaultValue: [],
|
|
71
|
+
baseType: "string-array",
|
|
65
72
|
detect: (value) => isStringArray(value)
|
|
66
73
|
},
|
|
67
74
|
{
|
|
@@ -69,6 +76,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
69
76
|
label: "Array",
|
|
70
77
|
icon: "i-lucide-list",
|
|
71
78
|
defaultValue: [],
|
|
79
|
+
baseType: "array",
|
|
72
80
|
detect: (value) => Array.isArray(value) && !isStringArray(value)
|
|
73
81
|
},
|
|
74
82
|
{
|
|
@@ -76,6 +84,7 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
76
84
|
label: "Object",
|
|
77
85
|
icon: "i-lucide-box",
|
|
78
86
|
defaultValue: {},
|
|
87
|
+
baseType: "object",
|
|
79
88
|
detect: (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !isDateObject(value)
|
|
80
89
|
},
|
|
81
90
|
{
|
|
@@ -83,22 +92,24 @@ export function useYamlFieldTypes(customTypes) {
|
|
|
83
92
|
label: "Null",
|
|
84
93
|
icon: "i-lucide-circle-slash",
|
|
85
94
|
defaultValue: null,
|
|
95
|
+
baseType: "null",
|
|
86
96
|
detect: (value) => value === null
|
|
87
97
|
}
|
|
88
98
|
];
|
|
89
99
|
const fieldTypes = computed(() => {
|
|
90
100
|
const types = [...DEFAULT_FIELD_TYPES];
|
|
101
|
+
const newCustomTypes = [];
|
|
91
102
|
if (customTypes) {
|
|
92
103
|
for (const customType of customTypes) {
|
|
93
104
|
const existingIndex = types.findIndex((t) => t.type === customType.type);
|
|
94
105
|
if (existingIndex >= 0) {
|
|
95
106
|
types[existingIndex] = customType;
|
|
96
107
|
} else {
|
|
97
|
-
|
|
108
|
+
newCustomTypes.push(customType);
|
|
98
109
|
}
|
|
99
110
|
}
|
|
100
111
|
}
|
|
101
|
-
return types;
|
|
112
|
+
return [...newCustomTypes, ...types];
|
|
102
113
|
});
|
|
103
114
|
const getFieldType = (type) => {
|
|
104
115
|
return fieldTypes.value.find((t) => t.type === type);
|
|
@@ -10,20 +10,43 @@ export type YamlFormData<T extends object = {}> = {
|
|
|
10
10
|
[key: string]: any;
|
|
11
11
|
} & T
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Base types that define conversion rules
|
|
15
|
+
* Custom types inherit conversion rules from these base types
|
|
16
|
+
*/
|
|
17
|
+
export type YamlBaseType =
|
|
18
|
+
| 'string' // Text primitives
|
|
19
|
+
| 'number' // Numeric primitives
|
|
20
|
+
| 'boolean' // Boolean primitives
|
|
21
|
+
| 'date' // Date without time
|
|
22
|
+
| 'datetime' // Date with time
|
|
23
|
+
| 'string-array' // Array of strings (tags)
|
|
24
|
+
| 'array' // Generic array
|
|
25
|
+
| 'object' // Generic object
|
|
26
|
+
| 'null' // Null value
|
|
27
|
+
|
|
13
28
|
/**
|
|
14
29
|
* YAML Field Type Definition
|
|
15
30
|
* Centralized schema for all field types supported by the YAML editor
|
|
16
31
|
*/
|
|
17
32
|
export interface YamlFieldType {
|
|
18
|
-
/** Unique type identifier */
|
|
33
|
+
/** Unique type identifier (can be custom, e.g. 'color', 'email', 'url') */
|
|
19
34
|
type: string
|
|
20
|
-
/** Display label */
|
|
35
|
+
/** Display label shown in dropdowns */
|
|
21
36
|
label: string
|
|
22
|
-
/** Lucide icon name */
|
|
37
|
+
/** Lucide icon name (e.g. 'i-lucide-palette') */
|
|
23
38
|
icon: string
|
|
24
39
|
/** Default value when creating new field of this type */
|
|
25
40
|
defaultValue: any
|
|
26
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Base type for conversion rules - determines what this type can convert to/from
|
|
43
|
+
* Custom types inherit conversion rules from their base type
|
|
44
|
+
* @example
|
|
45
|
+
* - color → 'string' (can convert to/from string, number, boolean, etc.)
|
|
46
|
+
* - percentage → 'number' (can convert to/from number, string, boolean)
|
|
47
|
+
*/
|
|
48
|
+
baseType: YamlBaseType
|
|
49
|
+
/** Optional slot name for custom component rendering (e.g. 'color' → #field-color) */
|
|
27
50
|
component?: string
|
|
28
51
|
/** Optional detection function for auto-typing existing values */
|
|
29
52
|
detect?: (value: any) => boolean
|