@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 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 (NEW in v0.2.0):**
1077
+ **Detection Order:**
1078
1078
 
1079
- Custom types with `detect` functions are now checked **before** default types:
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 means:
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
- - ✅ More specific types should still have more specific detect functions
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 is fully compatible with writable computed refs. All mutations create new object references instead of mutating in place:
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
- // ✅ Creates new object (triggers computed setter)
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
- // Direct mutation (doesn't trigger computed setter)
1128
- data.value.newField = 'value' // Old approach - now fixed!
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@type32/yaml-editor-form",
3
3
  "configKey": "yamlEditorForm",
4
- "version": "0.1.6",
4
+ "version": "0.2.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -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 __VLS_19: string, __VLS_20: any;
29
+ declare var __VLS_20: string, __VLS_21: any;
30
30
  type __VLS_Slots = {} & {
31
- [K in NonNullable<typeof __VLS_19>]?: (props: typeof __VLS_20) => any;
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-4">
38
- <div class="space-y-3">
39
- <YamlFormField
40
- v-for="(value, key) in data"
41
- :key="String(key)"
42
- v-model="data[key]"
43
- :field-key="String(key)"
44
- :readonly="readonly"
45
- :field-types="fieldTypes"
46
- :size="size"
47
- @remove="removeField(String(key))"
48
- @update:field-key="(newKey) => {
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
- <!-- Forward all slots to YamlFormField for custom field components -->
59
- <template v-for="(_, name) in $slots" #[name]="slotProps">
60
- <slot :name="name" v-bind="slotProps" />
61
- </template>
62
- </YamlFormField>
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 __VLS_19: string, __VLS_20: any;
29
+ declare var __VLS_20: string, __VLS_21: any;
30
30
  type __VLS_Slots = {} & {
31
- [K in NonNullable<typeof __VLS_19>]?: (props: typeof __VLS_20) => any;
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.push(getDefaultValue(itemType));
167
+ modelValue.value = [...modelValue.value, getDefaultValue(itemType)];
168
168
  return;
169
169
  }
170
170
  if (modelValue.value.length === 0) {
171
- modelValue.value.push({});
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.push(getDefaultValue(detectedType.type));
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.splice(index, 1);
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.push(newObject);
202
+ modelValue.value = [...modelValue.value, newObject];
203
203
  } else {
204
- modelValue.value.push({});
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
- obj[newKey] = getDefaultValue(fieldType);
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
- delete obj[key];
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)) modelValue[index] = val;
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[key] = val;
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 obj = modelValue;
414
- obj[newKey] = value;
415
- delete obj[key];
424
+ const { [key]: oldValue, ...rest } = modelValue;
425
+ modelValue = {
426
+ ...rest,
427
+ [newKey]: oldValue
428
+ };
416
429
  }
417
430
  }"
418
431
  />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@type32/yaml-editor-form",
3
- "version": "0.1.6",
3
+ "version": "0.2.2",
4
4
  "description": "YAML Editor Form Component for Nuxt.",
5
5
  "repository": "https://github.com/CTRL-Neo-Studios/yaml-editor-form",
6
6
  "license": "MIT",