@type32/yaml-editor-form 0.1.4 → 0.1.6
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 +384 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/YamlFormEditor.vue +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,6 +100,35 @@ const data = ref({
|
|
|
100
100
|
</template>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### With Writable Computed
|
|
104
|
+
|
|
105
|
+
The editor fully supports writable computed refs for v-model:
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<script setup lang="ts">
|
|
109
|
+
const rawData = ref({
|
|
110
|
+
title: 'Article',
|
|
111
|
+
published: false
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Writable computed with getter/setter
|
|
115
|
+
const data = computed({
|
|
116
|
+
get: () => rawData.value,
|
|
117
|
+
set: (value) => {
|
|
118
|
+
console.log('Data updated:', value)
|
|
119
|
+
rawData.value = value
|
|
120
|
+
// You can add validation, transformations, API calls, etc.
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<YamlFormEditor v-model="data" />
|
|
127
|
+
</template>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Note:** The editor properly triggers computed setters by creating new object references instead of mutating in place, ensuring full compatibility with writable computed refs.
|
|
131
|
+
|
|
103
132
|
### With Custom Field Types
|
|
104
133
|
|
|
105
134
|
```vue
|
|
@@ -112,6 +141,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
112
141
|
label: 'Image',
|
|
113
142
|
icon: 'i-lucide-image',
|
|
114
143
|
defaultValue: '',
|
|
144
|
+
baseType: 'string',
|
|
115
145
|
component: 'image'
|
|
116
146
|
}
|
|
117
147
|
]
|
|
@@ -477,6 +507,7 @@ export const DEFAULT_FIELD_TYPES: YamlFieldType[] = [
|
|
|
477
507
|
label: 'Email',
|
|
478
508
|
icon: 'i-lucide-mail',
|
|
479
509
|
defaultValue: '',
|
|
510
|
+
baseType: 'string',
|
|
480
511
|
detect: (value) => typeof value === 'string' && /^[^@]+@[^@]+/.test(value)
|
|
481
512
|
}
|
|
482
513
|
]
|
|
@@ -497,6 +528,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
497
528
|
label: 'URL',
|
|
498
529
|
icon: 'i-lucide-link',
|
|
499
530
|
defaultValue: 'https://',
|
|
531
|
+
baseType: 'string',
|
|
500
532
|
detect: (value) => typeof value === 'string' && value.startsWith('http')
|
|
501
533
|
}
|
|
502
534
|
]
|
|
@@ -545,6 +577,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
545
577
|
label: 'Rich Text',
|
|
546
578
|
icon: 'i-lucide-file-text',
|
|
547
579
|
defaultValue: '',
|
|
580
|
+
baseType: 'string',
|
|
548
581
|
component: 'richtext' // Now uses custom component
|
|
549
582
|
}
|
|
550
583
|
]
|
|
@@ -693,6 +726,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
693
726
|
label: 'Image',
|
|
694
727
|
icon: 'i-lucide-image',
|
|
695
728
|
defaultValue: '',
|
|
729
|
+
baseType: 'string',
|
|
696
730
|
component: 'image'
|
|
697
731
|
},
|
|
698
732
|
{
|
|
@@ -700,6 +734,7 @@ const customTypes: YamlFieldType[] = [
|
|
|
700
734
|
label: 'Markdown',
|
|
701
735
|
icon: 'i-lucide-file-text',
|
|
702
736
|
defaultValue: '',
|
|
737
|
+
baseType: 'string',
|
|
703
738
|
component: 'markdown'
|
|
704
739
|
}
|
|
705
740
|
]
|
|
@@ -744,13 +779,15 @@ const customTypes: YamlFieldType[] = [
|
|
|
744
779
|
label: 'UUID',
|
|
745
780
|
icon: 'i-lucide-fingerprint',
|
|
746
781
|
defaultValue: () => crypto.randomUUID(), // Function called each time
|
|
782
|
+
baseType: 'string',
|
|
747
783
|
detect: (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-/.test(v)
|
|
748
784
|
},
|
|
749
785
|
{
|
|
750
786
|
type: 'timestamp',
|
|
751
787
|
label: 'Timestamp',
|
|
752
788
|
icon: 'i-lucide-clock',
|
|
753
|
-
defaultValue: () => new Date().toISOString()
|
|
789
|
+
defaultValue: () => new Date().toISOString(),
|
|
790
|
+
baseType: 'string'
|
|
754
791
|
}
|
|
755
792
|
]
|
|
756
793
|
</script>
|
|
@@ -834,6 +871,7 @@ types/
|
|
|
834
871
|
label: 'My Type',
|
|
835
872
|
icon: 'i-lucide-my-icon',
|
|
836
873
|
defaultValue: 'default',
|
|
874
|
+
baseType: 'string', // Required: specify base type for conversions
|
|
837
875
|
detect: (value) => /* detection logic */
|
|
838
876
|
}
|
|
839
877
|
```
|
|
@@ -1078,12 +1116,42 @@ When using `baseType`, conversion rules follow this logic:
|
|
|
1078
1116
|
]
|
|
1079
1117
|
```
|
|
1080
1118
|
|
|
1119
|
+
### Writable Computed Support
|
|
1120
|
+
|
|
1121
|
+
The editor is fully compatible with writable computed refs. All mutations create new object references instead of mutating in place:
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
// ✅ Creates new object (triggers computed setter)
|
|
1125
|
+
data.value = { ...data.value, newField: 'value' }
|
|
1126
|
+
|
|
1127
|
+
// ❌ Direct mutation (doesn't trigger computed setter)
|
|
1128
|
+
data.value.newField = 'value' // Old approach - now fixed!
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
**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
|
|
1137
|
+
|
|
1138
|
+
**Example with Pinia:**
|
|
1139
|
+
```typescript
|
|
1140
|
+
const store = useMyStore()
|
|
1141
|
+
|
|
1142
|
+
const data = computed({
|
|
1143
|
+
get: () => store.formData,
|
|
1144
|
+
set: (value) => store.updateFormData(value)
|
|
1145
|
+
})
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1081
1148
|
### Performance Considerations
|
|
1082
1149
|
|
|
1083
1150
|
**Reactivity:**
|
|
1084
1151
|
- Uses Vue 3 `ref` and `computed` for optimal reactivity
|
|
1085
1152
|
- Deep watching is used only where necessary
|
|
1086
1153
|
- Recursive rendering is optimized with `v-if` conditionals
|
|
1154
|
+
- **Immutable updates** ensure computed setters are triggered properly
|
|
1087
1155
|
|
|
1088
1156
|
**Large Arrays:**
|
|
1089
1157
|
- Each array item is independently reactive
|
|
@@ -1094,6 +1162,7 @@ When using `baseType`, conversion rules follow this logic:
|
|
|
1094
1162
|
- Date helper functions are minimal
|
|
1095
1163
|
- No global state except type registry
|
|
1096
1164
|
- Components clean up properly on unmount
|
|
1165
|
+
- Immutable updates create minimal object copies (spread operator is fast)
|
|
1097
1166
|
|
|
1098
1167
|
### Validation (Future)
|
|
1099
1168
|
|
|
@@ -1169,6 +1238,320 @@ Requires:
|
|
|
1169
1238
|
- reka-ui (via Nuxt UI)
|
|
1170
1239
|
- tailwindcss (via Nuxt)
|
|
1171
1240
|
|
|
1241
|
+
## Advanced Examples
|
|
1242
|
+
|
|
1243
|
+
### Custom String Array Type
|
|
1244
|
+
|
|
1245
|
+
Here's an example of a custom string array type with autocomplete suggestions:
|
|
1246
|
+
|
|
1247
|
+
```vue
|
|
1248
|
+
<script setup lang="ts">
|
|
1249
|
+
import type { YamlFieldType } from '@type32/yaml-editor-form'
|
|
1250
|
+
|
|
1251
|
+
const customTypes: YamlFieldType[] = [
|
|
1252
|
+
{
|
|
1253
|
+
type: 'skills',
|
|
1254
|
+
label: 'Skills',
|
|
1255
|
+
icon: 'i-lucide-sparkles',
|
|
1256
|
+
defaultValue: [],
|
|
1257
|
+
baseType: 'string-array', // Inherits array conversions
|
|
1258
|
+
component: 'skills',
|
|
1259
|
+
detect: (value) => {
|
|
1260
|
+
// Auto-detect arrays with skill-like strings
|
|
1261
|
+
return Array.isArray(value) &&
|
|
1262
|
+
value.length > 0 &&
|
|
1263
|
+
value.every(v => typeof v === 'string' && v.length < 30)
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
]
|
|
1267
|
+
|
|
1268
|
+
const profile = ref({
|
|
1269
|
+
name: 'John Doe',
|
|
1270
|
+
skills: ['Vue.js', 'TypeScript', 'Nuxt']
|
|
1271
|
+
})
|
|
1272
|
+
|
|
1273
|
+
// Predefined skill suggestions
|
|
1274
|
+
const skillSuggestions = [
|
|
1275
|
+
'Vue.js', 'React', 'Angular', 'TypeScript', 'JavaScript',
|
|
1276
|
+
'Node.js', 'Python', 'Nuxt', 'Next.js', 'Tailwind CSS'
|
|
1277
|
+
]
|
|
1278
|
+
</script>
|
|
1279
|
+
|
|
1280
|
+
<template>
|
|
1281
|
+
<YamlFormEditor v-model="profile" :field-types="customTypes">
|
|
1282
|
+
<!-- Custom skills input with autocomplete -->
|
|
1283
|
+
<template #field-skills="{ modelValue, readonly, updateModelValue }">
|
|
1284
|
+
<div class="space-y-2">
|
|
1285
|
+
<!-- Display current skills as badges -->
|
|
1286
|
+
<div class="flex flex-wrap gap-2">
|
|
1287
|
+
<UBadge
|
|
1288
|
+
v-for="(skill, index) in (modelValue as string[])"
|
|
1289
|
+
:key="index"
|
|
1290
|
+
color="primary"
|
|
1291
|
+
variant="soft"
|
|
1292
|
+
>
|
|
1293
|
+
{{ skill }}
|
|
1294
|
+
<UButton
|
|
1295
|
+
v-if="!readonly"
|
|
1296
|
+
icon="i-lucide-x"
|
|
1297
|
+
size="2xs"
|
|
1298
|
+
variant="ghost"
|
|
1299
|
+
:padded="false"
|
|
1300
|
+
@click="updateModelValue((modelValue as string[]).filter((_, i) => i !== index))"
|
|
1301
|
+
/>
|
|
1302
|
+
</UBadge>
|
|
1303
|
+
</div>
|
|
1304
|
+
|
|
1305
|
+
<!-- Add new skill with autocomplete -->
|
|
1306
|
+
<UInputMenu
|
|
1307
|
+
v-if="!readonly"
|
|
1308
|
+
:options="skillSuggestions"
|
|
1309
|
+
placeholder="Add skill..."
|
|
1310
|
+
@update:model-value="(newSkill: string) => {
|
|
1311
|
+
if (newSkill && !(modelValue as string[]).includes(newSkill)) {
|
|
1312
|
+
updateModelValue([...(modelValue as string[]), newSkill])
|
|
1313
|
+
}
|
|
1314
|
+
}"
|
|
1315
|
+
/>
|
|
1316
|
+
</div>
|
|
1317
|
+
</template>
|
|
1318
|
+
</YamlFormEditor>
|
|
1319
|
+
</template>
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
### Custom Object Array Type
|
|
1323
|
+
|
|
1324
|
+
Here's an example of a custom object array type with card-based rendering:
|
|
1325
|
+
|
|
1326
|
+
```vue
|
|
1327
|
+
<script setup lang="ts">
|
|
1328
|
+
import type { YamlFieldType } from '@type32/yaml-editor-form'
|
|
1329
|
+
|
|
1330
|
+
const customTypes: YamlFieldType[] = [
|
|
1331
|
+
{
|
|
1332
|
+
type: 'contacts',
|
|
1333
|
+
label: 'Contacts',
|
|
1334
|
+
icon: 'i-lucide-users',
|
|
1335
|
+
defaultValue: [],
|
|
1336
|
+
baseType: 'array', // Inherits array conversions
|
|
1337
|
+
component: 'contacts',
|
|
1338
|
+
detect: (value) => {
|
|
1339
|
+
// Auto-detect arrays of contact-like objects
|
|
1340
|
+
return Array.isArray(value) &&
|
|
1341
|
+
value.length > 0 &&
|
|
1342
|
+
value.every(v =>
|
|
1343
|
+
v && typeof v === 'object' &&
|
|
1344
|
+
('name' in v || 'email' in v)
|
|
1345
|
+
)
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
]
|
|
1349
|
+
|
|
1350
|
+
const data = ref({
|
|
1351
|
+
projectName: 'My Project',
|
|
1352
|
+
contacts: [
|
|
1353
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Designer' },
|
|
1354
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Developer' }
|
|
1355
|
+
]
|
|
1356
|
+
})
|
|
1357
|
+
</script>
|
|
1358
|
+
|
|
1359
|
+
<template>
|
|
1360
|
+
<YamlFormEditor v-model="data" :field-types="customTypes">
|
|
1361
|
+
<!-- Custom contacts list with card UI -->
|
|
1362
|
+
<template #field-contacts="{ modelValue, readonly, updateModelValue }">
|
|
1363
|
+
<div class="space-y-3">
|
|
1364
|
+
<!-- Contact cards -->
|
|
1365
|
+
<UCard
|
|
1366
|
+
v-for="(contact, index) in (modelValue as any[])"
|
|
1367
|
+
:key="index"
|
|
1368
|
+
:ui="{ body: { padding: 'p-4' } }"
|
|
1369
|
+
>
|
|
1370
|
+
<div class="flex items-start justify-between gap-3">
|
|
1371
|
+
<div class="flex-1 space-y-2">
|
|
1372
|
+
<!-- Name -->
|
|
1373
|
+
<UInput
|
|
1374
|
+
:model-value="contact.name"
|
|
1375
|
+
placeholder="Name"
|
|
1376
|
+
:disabled="readonly"
|
|
1377
|
+
@update:model-value="(val: string) => {
|
|
1378
|
+
const updated = [...(modelValue as any[])]
|
|
1379
|
+
updated[index] = { ...contact, name: val }
|
|
1380
|
+
updateModelValue(updated)
|
|
1381
|
+
}"
|
|
1382
|
+
/>
|
|
1383
|
+
|
|
1384
|
+
<!-- Email -->
|
|
1385
|
+
<UInput
|
|
1386
|
+
:model-value="contact.email"
|
|
1387
|
+
type="email"
|
|
1388
|
+
placeholder="Email"
|
|
1389
|
+
icon="i-lucide-mail"
|
|
1390
|
+
:disabled="readonly"
|
|
1391
|
+
@update:model-value="(val: string) => {
|
|
1392
|
+
const updated = [...(modelValue as any[])]
|
|
1393
|
+
updated[index] = { ...contact, email: val }
|
|
1394
|
+
updateModelValue(updated)
|
|
1395
|
+
}"
|
|
1396
|
+
/>
|
|
1397
|
+
|
|
1398
|
+
<!-- Role -->
|
|
1399
|
+
<UInput
|
|
1400
|
+
:model-value="contact.role"
|
|
1401
|
+
placeholder="Role"
|
|
1402
|
+
icon="i-lucide-briefcase"
|
|
1403
|
+
:disabled="readonly"
|
|
1404
|
+
@update:model-value="(val: string) => {
|
|
1405
|
+
const updated = [...(modelValue as any[])]
|
|
1406
|
+
updated[index] = { ...contact, role: val }
|
|
1407
|
+
updateModelValue(updated)
|
|
1408
|
+
}"
|
|
1409
|
+
/>
|
|
1410
|
+
</div>
|
|
1411
|
+
|
|
1412
|
+
<!-- Remove button -->
|
|
1413
|
+
<UButton
|
|
1414
|
+
v-if="!readonly"
|
|
1415
|
+
icon="i-lucide-trash-2"
|
|
1416
|
+
color="red"
|
|
1417
|
+
variant="ghost"
|
|
1418
|
+
size="sm"
|
|
1419
|
+
@click="updateModelValue((modelValue as any[]).filter((_, i) => i !== index))"
|
|
1420
|
+
/>
|
|
1421
|
+
</div>
|
|
1422
|
+
</UCard>
|
|
1423
|
+
|
|
1424
|
+
<!-- Add contact button -->
|
|
1425
|
+
<UButton
|
|
1426
|
+
v-if="!readonly"
|
|
1427
|
+
icon="i-lucide-plus"
|
|
1428
|
+
label="Add Contact"
|
|
1429
|
+
variant="outline"
|
|
1430
|
+
block
|
|
1431
|
+
@click="updateModelValue([
|
|
1432
|
+
...(modelValue as any[]),
|
|
1433
|
+
{ name: '', email: '', role: '' }
|
|
1434
|
+
])"
|
|
1435
|
+
/>
|
|
1436
|
+
</div>
|
|
1437
|
+
</template>
|
|
1438
|
+
</YamlFormEditor>
|
|
1439
|
+
</template>
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
### Combined Example
|
|
1443
|
+
|
|
1444
|
+
You can use both custom array types together:
|
|
1445
|
+
|
|
1446
|
+
```vue
|
|
1447
|
+
<script setup lang="ts">
|
|
1448
|
+
import type { YamlFieldType } from '@type32/yaml-editor-form'
|
|
1449
|
+
|
|
1450
|
+
const customTypes: YamlFieldType[] = [
|
|
1451
|
+
// Custom string array
|
|
1452
|
+
{
|
|
1453
|
+
type: 'tags',
|
|
1454
|
+
label: 'Tags',
|
|
1455
|
+
icon: 'i-lucide-tag',
|
|
1456
|
+
defaultValue: [],
|
|
1457
|
+
baseType: 'string-array',
|
|
1458
|
+
component: 'tags',
|
|
1459
|
+
detect: (v) => Array.isArray(v) && v.every(i => typeof i === 'string')
|
|
1460
|
+
},
|
|
1461
|
+
// Custom object array
|
|
1462
|
+
{
|
|
1463
|
+
type: 'team',
|
|
1464
|
+
label: 'Team Members',
|
|
1465
|
+
icon: 'i-lucide-users',
|
|
1466
|
+
defaultValue: [],
|
|
1467
|
+
baseType: 'array',
|
|
1468
|
+
component: 'team',
|
|
1469
|
+
detect: (v) => Array.isArray(v) && v.every(i => i?.name || i?.email)
|
|
1470
|
+
}
|
|
1471
|
+
]
|
|
1472
|
+
|
|
1473
|
+
const project = ref({
|
|
1474
|
+
name: 'Website Redesign',
|
|
1475
|
+
tags: ['frontend', 'design', 'urgent'],
|
|
1476
|
+
team: [
|
|
1477
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
1478
|
+
{ name: 'Bob', email: 'bob@example.com' }
|
|
1479
|
+
]
|
|
1480
|
+
})
|
|
1481
|
+
</script>
|
|
1482
|
+
|
|
1483
|
+
<template>
|
|
1484
|
+
<YamlFormEditor v-model="project" :field-types="customTypes">
|
|
1485
|
+
<!-- String array implementation -->
|
|
1486
|
+
<template #field-tags="{ modelValue, readonly, updateModelValue }">
|
|
1487
|
+
<UInputTags
|
|
1488
|
+
:model-value="modelValue as string[]"
|
|
1489
|
+
:disabled="readonly"
|
|
1490
|
+
placeholder="Add tags..."
|
|
1491
|
+
@update:model-value="updateModelValue"
|
|
1492
|
+
/>
|
|
1493
|
+
</template>
|
|
1494
|
+
|
|
1495
|
+
<!-- Object array implementation -->
|
|
1496
|
+
<template #field-team="{ modelValue, readonly, updateModelValue }">
|
|
1497
|
+
<!-- Your custom team member UI here -->
|
|
1498
|
+
<div class="space-y-2">
|
|
1499
|
+
<div
|
|
1500
|
+
v-for="(member, idx) in (modelValue as any[])"
|
|
1501
|
+
:key="idx"
|
|
1502
|
+
class="flex gap-2"
|
|
1503
|
+
>
|
|
1504
|
+
<UInput
|
|
1505
|
+
:model-value="member.name"
|
|
1506
|
+
placeholder="Name"
|
|
1507
|
+
:disabled="readonly"
|
|
1508
|
+
@update:model-value="(val: string) => {
|
|
1509
|
+
const updated = [...(modelValue as any[])]
|
|
1510
|
+
updated[idx] = { ...member, name: val }
|
|
1511
|
+
updateModelValue(updated)
|
|
1512
|
+
}"
|
|
1513
|
+
/>
|
|
1514
|
+
<UButton
|
|
1515
|
+
v-if="!readonly"
|
|
1516
|
+
icon="i-lucide-x"
|
|
1517
|
+
color="red"
|
|
1518
|
+
variant="ghost"
|
|
1519
|
+
@click="updateModelValue((modelValue as any[]).filter((_, i) => i !== idx))"
|
|
1520
|
+
/>
|
|
1521
|
+
</div>
|
|
1522
|
+
<UButton
|
|
1523
|
+
v-if="!readonly"
|
|
1524
|
+
icon="i-lucide-plus"
|
|
1525
|
+
label="Add Member"
|
|
1526
|
+
size="sm"
|
|
1527
|
+
@click="updateModelValue([...(modelValue as any[]), { name: '', email: '' }])"
|
|
1528
|
+
/>
|
|
1529
|
+
</div>
|
|
1530
|
+
</template>
|
|
1531
|
+
</YamlFormEditor>
|
|
1532
|
+
</template>
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
### Key Patterns for Array Types
|
|
1536
|
+
|
|
1537
|
+
**String Arrays (`baseType: 'string-array'`):**
|
|
1538
|
+
- Use for specialized tag inputs, category lists, etc.
|
|
1539
|
+
- Can convert to/from regular arrays and strings
|
|
1540
|
+
- Good for: skills, tags, categories, keywords
|
|
1541
|
+
|
|
1542
|
+
**Object Arrays (`baseType: 'array'`):**
|
|
1543
|
+
- Use for collections with structured data
|
|
1544
|
+
- Provide custom UI for adding/editing/removing items
|
|
1545
|
+
- Good for: contacts, team members, products, events
|
|
1546
|
+
|
|
1547
|
+
**Important Notes:**
|
|
1548
|
+
1. **Type Assertions**: Use `(modelValue as string[])` or `(modelValue as any[])` for type safety
|
|
1549
|
+
2. **Immutability**: Always create new arrays when updating (spread operator `[...]`)
|
|
1550
|
+
3. **Index Management**: Track items by index for updates/deletions
|
|
1551
|
+
4. **Add Operations**: Spread existing array and add new items
|
|
1552
|
+
5. **Remove Operations**: Use `filter()` to remove by index
|
|
1553
|
+
6. **Update Operations**: Clone array, modify specific index, update entire array
|
|
1554
|
+
|
|
1172
1555
|
## License
|
|
1173
1556
|
|
|
1174
1557
|
This component is part of the Vertex project.
|
package/dist/module.json
CHANGED
|
@@ -16,11 +16,15 @@ watch(data, () => {
|
|
|
16
16
|
function addField(fieldType = "string") {
|
|
17
17
|
if (!data.value) data.value = {};
|
|
18
18
|
const newKey = `field_${Object.keys(data.value).length + 1}`;
|
|
19
|
-
data.value
|
|
19
|
+
data.value = {
|
|
20
|
+
...data.value,
|
|
21
|
+
[newKey]: getDefaultValue(fieldType)
|
|
22
|
+
};
|
|
20
23
|
}
|
|
21
24
|
function removeField(key) {
|
|
22
25
|
if (data.value) {
|
|
23
|
-
|
|
26
|
+
const { [key]: removed, ...rest } = data.value;
|
|
27
|
+
data.value = rest;
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
const addFieldOptions = computed(() => {
|
|
@@ -42,9 +46,12 @@ const addFieldOptions = computed(() => {
|
|
|
42
46
|
:size="size"
|
|
43
47
|
@remove="removeField(String(key))"
|
|
44
48
|
@update:field-key="(newKey) => {
|
|
45
|
-
if (newKey !== key) {
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
if (newKey !== key && data) {
|
|
50
|
+
const { [key]: value, ...rest } = data;
|
|
51
|
+
data = {
|
|
52
|
+
...rest,
|
|
53
|
+
[newKey]: value
|
|
54
|
+
};
|
|
48
55
|
}
|
|
49
56
|
}"
|
|
50
57
|
>
|