@type32/yaml-editor-form 0.1.6 → 0.2.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 +107 -16
- package/dist/module.json +1 -1
- package/dist/runtime/components/YamlFormEditor.d.vue.ts +2 -2
- package/dist/runtime/components/YamlFormEditor.vue +20 -19
- package/dist/runtime/components/YamlFormEditor.vue.d.ts +2 -2
- package/dist/runtime/components/YamlFormField.vue +26 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1072,20 +1072,20 @@ This function captures the parent's `modelValue` ref and updates it directly, ma
|
|
|
1072
1072
|
|
|
1073
1073
|
Even if your color field is deeply nested (`data.theme.colors.primary`), the slot works identically because slots are forwarded at every level.
|
|
1074
1074
|
|
|
1075
|
-
### Type Priority
|
|
1075
|
+
### Type Priority & Detection Order
|
|
1076
1076
|
|
|
1077
|
-
**Detection Order
|
|
1077
|
+
**Detection Order:**
|
|
1078
1078
|
|
|
1079
|
-
Custom types with `detect` functions are
|
|
1079
|
+
Custom types with `detect` functions are **always** checked **before** default types:
|
|
1080
1080
|
|
|
1081
1081
|
1. **Custom types** (checked first) - Your custom types take priority
|
|
1082
1082
|
2. **Default types** (checked second) - Built-in types as fallback
|
|
1083
|
-
3. First matching type wins
|
|
1083
|
+
3. **First matching type wins** - Detection stops at first match
|
|
1084
1084
|
|
|
1085
|
-
This
|
|
1085
|
+
This ensures:
|
|
1086
1086
|
- ✅ Your `color` type will be detected before the default `string` type
|
|
1087
1087
|
- ✅ Custom types override default detection behavior
|
|
1088
|
-
- ✅
|
|
1088
|
+
- ✅ Custom types appear first in "Add Field" dropdowns
|
|
1089
1089
|
|
|
1090
1090
|
**Example:**
|
|
1091
1091
|
```typescript
|
|
@@ -1099,6 +1099,48 @@ const customTypes = [{
|
|
|
1099
1099
|
// Value '#FF0000' will match 'color' before 'string'
|
|
1100
1100
|
```
|
|
1101
1101
|
|
|
1102
|
+
**Important: Make Your Detect Functions Specific!**
|
|
1103
|
+
|
|
1104
|
+
Since detection stops at the first match, make sure your `detect` functions are specific enough:
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
// ❌ TOO BROAD - will match ALL strings
|
|
1108
|
+
{
|
|
1109
|
+
type: 'email',
|
|
1110
|
+
baseType: 'string',
|
|
1111
|
+
detect: (v) => typeof v === 'string' // Too general!
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// ✅ SPECIFIC - only matches email-like strings
|
|
1115
|
+
{
|
|
1116
|
+
type: 'email',
|
|
1117
|
+
baseType: 'string',
|
|
1118
|
+
detect: (v) => typeof v === 'string' && /^[^@]+@[^@]+\.[^@]+$/.test(v)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// ✅ SPECIFIC - only matches hex colors
|
|
1122
|
+
{
|
|
1123
|
+
type: 'color',
|
|
1124
|
+
baseType: 'string',
|
|
1125
|
+
detect: (v) => typeof v === 'string' && /^#[0-9A-Fa-f]{6}$/.test(v)
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// ✅ SPECIFIC - only matches URLs
|
|
1129
|
+
{
|
|
1130
|
+
type: 'url',
|
|
1131
|
+
baseType: 'string',
|
|
1132
|
+
detect: (v) => typeof v === 'string' && /^https?:\/\//.test(v)
|
|
1133
|
+
}
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
**Troubleshooting Detection Issues:**
|
|
1137
|
+
|
|
1138
|
+
If your custom type isn't being detected:
|
|
1139
|
+
1. Check that `detect` function is specific enough
|
|
1140
|
+
2. Ensure `detect` returns `true` for your value
|
|
1141
|
+
3. Remember: first matching type wins (order matters!)
|
|
1142
|
+
4. Test your detect function in isolation
|
|
1143
|
+
|
|
1102
1144
|
**Base Type Conversions:**
|
|
1103
1145
|
|
|
1104
1146
|
When using `baseType`, conversion rules follow this logic:
|
|
@@ -1118,22 +1160,57 @@ When using `baseType`, conversion rules follow this logic:
|
|
|
1118
1160
|
|
|
1119
1161
|
### Writable Computed Support
|
|
1120
1162
|
|
|
1121
|
-
The editor
|
|
1163
|
+
**✅ Fully Compatible** - The editor now fully supports writable computed refs through `defineModel` and nested components.
|
|
1164
|
+
|
|
1165
|
+
All internal operations use **immutable updates** to ensure computed setters are properly triggered:
|
|
1122
1166
|
|
|
1123
1167
|
```typescript
|
|
1124
|
-
// ✅
|
|
1125
|
-
data.value = { ...data.value, newField: 'value' }
|
|
1168
|
+
// ✅ All operations create new references (triggers computed setter)
|
|
1169
|
+
data.value = { ...data.value, newField: 'value' } // Add field
|
|
1170
|
+
data.value = { ...data.value, [key]: newValue } // Update field
|
|
1171
|
+
const { [key]: removed, ...rest } = data.value // Remove field
|
|
1172
|
+
data.value = rest
|
|
1126
1173
|
|
|
1127
|
-
//
|
|
1128
|
-
|
|
1174
|
+
// ✅ Array operations also use immutable patterns
|
|
1175
|
+
array.value = [...array.value, newItem] // Add item
|
|
1176
|
+
array.value = array.value.filter((_, i) => i !== index) // Remove item
|
|
1177
|
+
const newArr = [...array.value]; newArr[i] = val // Update item
|
|
1178
|
+
array.value = newArr
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
**✅ Works Through Multiple Component Layers:**
|
|
1182
|
+
|
|
1183
|
+
The editor correctly handles writable computed passed through `defineModel` in nested components:
|
|
1184
|
+
|
|
1185
|
+
```vue
|
|
1186
|
+
<!-- Parent Component -->
|
|
1187
|
+
<script setup lang="ts">
|
|
1188
|
+
const rawData = ref({ title: 'Article' })
|
|
1189
|
+
|
|
1190
|
+
// Writable computed with validation
|
|
1191
|
+
const data = computed({
|
|
1192
|
+
get: () => rawData.value,
|
|
1193
|
+
set: (value) => {
|
|
1194
|
+
console.log('Data updated:', value)
|
|
1195
|
+
// Add validation, transformations, etc.
|
|
1196
|
+
rawData.value = value
|
|
1197
|
+
}
|
|
1198
|
+
})
|
|
1199
|
+
</script>
|
|
1200
|
+
|
|
1201
|
+
<template>
|
|
1202
|
+
<!-- Works! Passes through DataEditorForm → YamlFormEditor -->
|
|
1203
|
+
<DataEditorForm v-model="data" />
|
|
1204
|
+
</template>
|
|
1129
1205
|
```
|
|
1130
1206
|
|
|
1131
1207
|
**Use Cases for Writable Computed:**
|
|
1132
|
-
- Validation before saving
|
|
1133
|
-
- Transform data on save (e.g., serialize dates)
|
|
1134
|
-
- Sync with external state management (Pinia, Vuex)
|
|
1135
|
-
- Trigger side effects on changes (API calls, logging)
|
|
1136
|
-
- Implement undo/redo functionality
|
|
1208
|
+
- ✅ Validation before saving
|
|
1209
|
+
- ✅ Transform data on save (e.g., serialize dates)
|
|
1210
|
+
- ✅ Sync with external state management (Pinia, Vuex)
|
|
1211
|
+
- ✅ Trigger side effects on changes (API calls, logging)
|
|
1212
|
+
- ✅ Implement undo/redo functionality
|
|
1213
|
+
- ✅ Complex editor integrations (TipTap, Monaco, etc.)
|
|
1137
1214
|
|
|
1138
1215
|
**Example with Pinia:**
|
|
1139
1216
|
```typescript
|
|
@@ -1145,6 +1222,20 @@ const data = computed({
|
|
|
1145
1222
|
})
|
|
1146
1223
|
```
|
|
1147
1224
|
|
|
1225
|
+
**Example with TipTap Editor Integration:**
|
|
1226
|
+
```typescript
|
|
1227
|
+
const editorInstance = defineModel('editorInstance', { required: true })
|
|
1228
|
+
const $ef = useEditorFrontmatter(editorInstance)
|
|
1229
|
+
|
|
1230
|
+
const data = computed({
|
|
1231
|
+
get: () => $ef.getFrontmatter().data || {},
|
|
1232
|
+
set: (newValue) => {
|
|
1233
|
+
// Updates editor content directly
|
|
1234
|
+
$ef.setFrontmatterProperties({ ...newValue })
|
|
1235
|
+
}
|
|
1236
|
+
})
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1148
1239
|
### Performance Considerations
|
|
1149
1240
|
|
|
1150
1241
|
**Reactivity:**
|
package/dist/module.json
CHANGED
|
@@ -26,9 +26,9 @@ type __VLS_ModelProps = {
|
|
|
26
26
|
modelValue: YamlFormData;
|
|
27
27
|
};
|
|
28
28
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
29
|
-
declare var
|
|
29
|
+
declare var __VLS_20: string, __VLS_21: any;
|
|
30
30
|
type __VLS_Slots = {} & {
|
|
31
|
-
[K in NonNullable<typeof
|
|
31
|
+
[K in NonNullable<typeof __VLS_20>]?: (props: typeof __VLS_21) => any;
|
|
32
32
|
};
|
|
33
33
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
34
34
|
"update:modelValue": (value: {
|
|
@@ -34,18 +34,20 @@ const addFieldOptions = computed(() => {
|
|
|
34
34
|
|
|
35
35
|
<template>
|
|
36
36
|
<ClientOnly>
|
|
37
|
-
<div class="space-y-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
<div class="space-y-3">
|
|
38
|
+
<YamlFormField
|
|
39
|
+
v-for="(value, key) in data"
|
|
40
|
+
:key="String(key)"
|
|
41
|
+
:model-value="data[key]"
|
|
42
|
+
:field-key="String(key)"
|
|
43
|
+
:readonly="readonly"
|
|
44
|
+
:field-types="fieldTypes"
|
|
45
|
+
:size="size"
|
|
46
|
+
@update:model-value="(newValue) => {
|
|
47
|
+
data = { ...data, [key]: newValue };
|
|
48
|
+
}"
|
|
49
|
+
@remove="removeField(String(key))"
|
|
50
|
+
@update:field-key="(newKey) => {
|
|
49
51
|
if (newKey !== key && data) {
|
|
50
52
|
const { [key]: value, ...rest } = data;
|
|
51
53
|
data = {
|
|
@@ -54,13 +56,12 @@ const addFieldOptions = computed(() => {
|
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
}"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</div>
|
|
59
|
+
>
|
|
60
|
+
<!-- Forward all slots to YamlFormField for custom field components -->
|
|
61
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
62
|
+
<slot :name="name" v-bind="slotProps" />
|
|
63
|
+
</template>
|
|
64
|
+
</YamlFormField>
|
|
64
65
|
|
|
65
66
|
<UDropdownMenu
|
|
66
67
|
v-if="!readonly"
|
|
@@ -26,9 +26,9 @@ type __VLS_ModelProps = {
|
|
|
26
26
|
modelValue: YamlFormData;
|
|
27
27
|
};
|
|
28
28
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
29
|
-
declare var
|
|
29
|
+
declare var __VLS_20: string, __VLS_21: any;
|
|
30
30
|
type __VLS_Slots = {} & {
|
|
31
|
-
[K in NonNullable<typeof
|
|
31
|
+
[K in NonNullable<typeof __VLS_20>]?: (props: typeof __VLS_21) => any;
|
|
32
32
|
};
|
|
33
33
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
34
34
|
"update:modelValue": (value: {
|
|
@@ -164,20 +164,20 @@ function convertType(newType) {
|
|
|
164
164
|
function addArrayItem(itemType) {
|
|
165
165
|
if (!Array.isArray(modelValue.value)) return;
|
|
166
166
|
if (itemType) {
|
|
167
|
-
modelValue.value.
|
|
167
|
+
modelValue.value = [...modelValue.value, getDefaultValue(itemType)];
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
if (modelValue.value.length === 0) {
|
|
171
|
-
modelValue.value.
|
|
171
|
+
modelValue.value = [...modelValue.value, {}];
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
const firstItem = modelValue.value[0];
|
|
175
175
|
const detectedType = detectFieldType(firstItem);
|
|
176
|
-
modelValue.value.
|
|
176
|
+
modelValue.value = [...modelValue.value, getDefaultValue(detectedType.type)];
|
|
177
177
|
}
|
|
178
178
|
function removeArrayItem(index) {
|
|
179
179
|
if (!Array.isArray(modelValue.value)) return;
|
|
180
|
-
modelValue.value.
|
|
180
|
+
modelValue.value = modelValue.value.filter((_, i) => i !== index);
|
|
181
181
|
}
|
|
182
182
|
function addArrayItemFromTemplate() {
|
|
183
183
|
if (!Array.isArray(modelValue.value)) return;
|
|
@@ -199,9 +199,9 @@ function addArrayItemFromTemplate() {
|
|
|
199
199
|
const detectedType = detectFieldType(value);
|
|
200
200
|
newObject[key] = getDefaultValue(detectedType.type);
|
|
201
201
|
}
|
|
202
|
-
modelValue.value.
|
|
202
|
+
modelValue.value = [...modelValue.value, newObject];
|
|
203
203
|
} else {
|
|
204
|
-
modelValue.value.
|
|
204
|
+
modelValue.value = [...modelValue.value, {}];
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
const hasObjectTemplate = computed(() => {
|
|
@@ -214,12 +214,16 @@ function addObjectField(fieldType = "string") {
|
|
|
214
214
|
if (typeof modelValue.value !== "object" || Array.isArray(modelValue.value) || !modelValue.value || isDateObject(modelValue.value)) return;
|
|
215
215
|
const obj = modelValue.value;
|
|
216
216
|
const newKey = `field_${Object.keys(obj).length + 1}`;
|
|
217
|
-
|
|
217
|
+
modelValue.value = {
|
|
218
|
+
...obj,
|
|
219
|
+
[newKey]: getDefaultValue(fieldType)
|
|
220
|
+
};
|
|
218
221
|
}
|
|
219
222
|
function removeObjectField(key) {
|
|
220
223
|
if (typeof modelValue.value !== "object" || Array.isArray(modelValue.value) || !modelValue.value || isDateObject(modelValue.value)) return;
|
|
221
224
|
const obj = modelValue.value;
|
|
222
|
-
|
|
225
|
+
const { [key]: removed, ...rest } = obj;
|
|
226
|
+
modelValue.value = rest;
|
|
223
227
|
}
|
|
224
228
|
const isOpen = ref(true);
|
|
225
229
|
const isArrayItem = computed(() => {
|
|
@@ -344,7 +348,11 @@ const addArrayItemOptions = computed(() => {
|
|
|
344
348
|
:depth="depth + 1"
|
|
345
349
|
:field-types="fieldTypes"
|
|
346
350
|
@update:model-value="(val) => {
|
|
347
|
-
if (Array.isArray(modelValue))
|
|
351
|
+
if (Array.isArray(modelValue)) {
|
|
352
|
+
const newArray = [...modelValue];
|
|
353
|
+
newArray[index] = val;
|
|
354
|
+
modelValue = newArray;
|
|
355
|
+
}
|
|
348
356
|
}"
|
|
349
357
|
@remove="removeArrayItem(index)"
|
|
350
358
|
:size="size"
|
|
@@ -404,15 +412,20 @@ const addArrayItemOptions = computed(() => {
|
|
|
404
412
|
:size="size"
|
|
405
413
|
@update:model-value="(val) => {
|
|
406
414
|
if (typeof modelValue === 'object' && !Array.isArray(modelValue) && modelValue !== null && !isDateObject(modelValue)) {
|
|
407
|
-
modelValue
|
|
415
|
+
modelValue = {
|
|
416
|
+
...modelValue,
|
|
417
|
+
[key]: val
|
|
418
|
+
};
|
|
408
419
|
}
|
|
409
420
|
}"
|
|
410
421
|
@remove="removeObjectField(String(key))"
|
|
411
422
|
@update:field-key="(newKey) => {
|
|
412
423
|
if (newKey !== key && typeof modelValue === 'object' && !Array.isArray(modelValue) && modelValue !== null && !isDateObject(modelValue) && value !== void 0) {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
const { [key]: oldValue, ...rest } = modelValue;
|
|
425
|
+
modelValue = {
|
|
426
|
+
...rest,
|
|
427
|
+
[newKey]: oldValue
|
|
428
|
+
};
|
|
416
429
|
}
|
|
417
430
|
}"
|
|
418
431
|
/>
|