@foormjs/vue 0.2.4 → 0.2.5
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 +385 -236
- package/dist/index.cjs +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +446 -216
- package/dist/index.js +1554 -371
- package/package.json +29 -25
- package/scripts/setup-skills.js +78 -0
- package/skills/foormjs-vue/.placeholder +0 -0
- package/skills/foormjs-vue/SKILL.md +53 -0
- package/skills/foormjs-vue/composables.md +189 -0
- package/skills/foormjs-vue/core.md +279 -0
- package/skills/foormjs-vue/custom-components.md +677 -0
- package/skills/foormjs-vue/defaults.md +266 -0
- package/skills/foormjs-vue/rendering.md +175 -0
- package/dist/index.umd.cjs +0 -1
- package/dist/style.css +0 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foormjs/vue",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "@foormjs/vue",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"sideEffects": false,
|
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
"./package.json": "./package.json",
|
|
12
12
|
"./styles": "./dist/style.css",
|
|
13
13
|
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
14
15
|
"import": "./dist/index.js",
|
|
15
|
-
"require": "./dist/index.
|
|
16
|
-
"types": "./dist/index.d.ts"
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"setup-skills": "./scripts/setup-skills.js"
|
|
22
|
+
},
|
|
20
23
|
"files": [
|
|
21
|
-
"dist"
|
|
24
|
+
"dist",
|
|
25
|
+
"skills",
|
|
26
|
+
"scripts/setup-skills.js"
|
|
22
27
|
],
|
|
23
28
|
"repository": {
|
|
24
29
|
"type": "git",
|
|
@@ -26,8 +31,6 @@
|
|
|
26
31
|
"directory": "packages/vue"
|
|
27
32
|
},
|
|
28
33
|
"keywords": [
|
|
29
|
-
"foorm",
|
|
30
|
-
"foormjs",
|
|
31
34
|
"foorm",
|
|
32
35
|
"foormjs",
|
|
33
36
|
"forms",
|
|
@@ -40,29 +43,29 @@
|
|
|
40
43
|
},
|
|
41
44
|
"homepage": "https://github.com/foormjs/foormjs/tree/main/packages/vue#readme",
|
|
42
45
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"@foormjs/atscript": "^0.2.4",
|
|
46
|
-
"foorm": "^0.2.4"
|
|
46
|
+
"@foormjs/atscript": "^0.2.5",
|
|
47
|
+
"@foormjs/composables": "^0.2.5"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"@atscript/core": "^0.1.
|
|
50
|
-
"@atscript/typescript": "^0.1.
|
|
50
|
+
"@atscript/core": "^0.1.22",
|
|
51
|
+
"@atscript/typescript": "^0.1.22",
|
|
52
|
+
"vue": "^3.5.0"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
|
-
"@atscript/core": "^0.1.
|
|
54
|
-
"@atscript/typescript": "^0.1.
|
|
55
|
+
"@atscript/core": "^0.1.22",
|
|
56
|
+
"@atscript/typescript": "^0.1.22",
|
|
55
57
|
"@playwright/test": "^1.58.2",
|
|
58
|
+
"vue": "^3.5.28",
|
|
56
59
|
"@tsconfig/node20": "^20.1.9",
|
|
57
|
-
"@types/node": "^
|
|
58
|
-
"@vitejs/plugin-vue": "^
|
|
59
|
-
"@vue/tsconfig": "^0.
|
|
60
|
-
"npm-run-all2": "^
|
|
61
|
-
"typescript": "~5.
|
|
62
|
-
"unplugin-atscript": "^0.1.
|
|
63
|
-
"vite": "^
|
|
64
|
-
"vite-plugin-dts": "^
|
|
65
|
-
"vue-tsc": "^
|
|
60
|
+
"@types/node": "^22.15.33",
|
|
61
|
+
"@vitejs/plugin-vue": "^6.0.4",
|
|
62
|
+
"@vue/tsconfig": "^0.8.1",
|
|
63
|
+
"npm-run-all2": "^8.0.4",
|
|
64
|
+
"typescript": "~5.9.3",
|
|
65
|
+
"unplugin-atscript": "^0.1.22",
|
|
66
|
+
"vite": "^7.3.1",
|
|
67
|
+
"vite-plugin-dts": "^4.5.4",
|
|
68
|
+
"vue-tsc": "^3.2.4"
|
|
66
69
|
},
|
|
67
70
|
"scripts": {
|
|
68
71
|
"dev": "vite",
|
|
@@ -71,6 +74,7 @@
|
|
|
71
74
|
"build-only": "vite build",
|
|
72
75
|
"type-check": "vue-tsc --build --force",
|
|
73
76
|
"pub": "pnpm publish --access public --no-git-checks",
|
|
74
|
-
"test:e2e": "playwright test"
|
|
77
|
+
"test:e2e": "playwright test",
|
|
78
|
+
"setup-skills": "node ./scripts/setup-skills.js"
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
const SKILL_NAME = 'foormjs-vue'
|
|
11
|
+
const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(SKILL_SRC)) {
|
|
14
|
+
console.error(`No skills found at ${SKILL_SRC}`)
|
|
15
|
+
console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AGENTS = {
|
|
20
|
+
'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
|
|
21
|
+
'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
|
|
22
|
+
'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
|
|
23
|
+
'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
|
|
24
|
+
'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const args = process.argv.slice(2)
|
|
28
|
+
const isGlobal = args.includes('--global') || args.includes('-g')
|
|
29
|
+
const isPostinstall = args.includes('--postinstall')
|
|
30
|
+
let installed = 0, skipped = 0
|
|
31
|
+
const installedDirs = []
|
|
32
|
+
|
|
33
|
+
for (const [agentName, cfg] of Object.entries(AGENTS)) {
|
|
34
|
+
const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
|
|
35
|
+
const agentRootDir = path.dirname(cfg.global) // Check if the agent has ever been installed globally
|
|
36
|
+
|
|
37
|
+
// In postinstall mode: silently skip agents that aren't set up globally
|
|
38
|
+
if (isPostinstall || isGlobal) {
|
|
39
|
+
if (!fs.existsSync(agentRootDir)) { skipped++; continue }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dest = path.join(targetBase, SKILL_NAME)
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
45
|
+
fs.cpSync(SKILL_SRC, dest, { recursive: true })
|
|
46
|
+
console.log(`✅ ${agentName}: installed to ${dest}`)
|
|
47
|
+
installed++
|
|
48
|
+
if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.warn(`⚠️ ${agentName}: failed — ${err.message}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add locally-installed skill dirs to .gitignore
|
|
55
|
+
if (!isGlobal && installedDirs.length > 0) {
|
|
56
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
57
|
+
let gitignoreContent = ''
|
|
58
|
+
try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
|
|
59
|
+
const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
|
|
60
|
+
if (linesToAdd.length > 0) {
|
|
61
|
+
const hasHeader = gitignoreContent.includes('# AI agent skills')
|
|
62
|
+
const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
|
|
63
|
+
+ (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
|
|
64
|
+
+ linesToAdd.join('\n') + '\n'
|
|
65
|
+
fs.appendFileSync(gitignorePath, block)
|
|
66
|
+
console.log(`📝 Added ${linesToAdd.length} entries to .gitignore`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (installed === 0 && isPostinstall) {
|
|
71
|
+
// Silence is fine — no agents present, nothing to do
|
|
72
|
+
} else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
|
|
73
|
+
console.log('No agent directories detected. Try --global or run without it for project-local install.')
|
|
74
|
+
} else if (installed === 0) {
|
|
75
|
+
console.log('Nothing installed. Run without --global to install project-locally.')
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`\n✨ Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
|
|
78
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: foormjs-vue
|
|
3
|
+
description: '@foormjs/vue — Vue 3 form rendering via OoForm. Use when building forms with OoForm component, creating custom type components or named components, using useFoormArray/useFoormUnion composables, or working with the types/components props.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @foormjs/vue
|
|
7
|
+
|
|
8
|
+
Vue 3 form rendering powered by OoForm. You provide a `types` map (field type → Vue component) and optionally a `components` map (name → Vue component), and OoForm renders the entire form tree through a unified OoField renderer.
|
|
9
|
+
|
|
10
|
+
## How to use this skill
|
|
11
|
+
|
|
12
|
+
Read the domain file that matches the task. Do not load all files — only what you need.
|
|
13
|
+
|
|
14
|
+
| Domain | File | Load when... |
|
|
15
|
+
| -------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| OoForm setup & API | [core.md](core.md) | Setting up OoForm, `types` vs `components` props, events, slots, `useFoorm`, `createDefaultTypes`, validation timing |
|
|
17
|
+
| Creating custom components | [custom-components.md](custom-components.md) | Building custom type components for any field kind — responsibility matrix, `TFoormComponentProps`, complete examples for leaf/structural/phantom fields |
|
|
18
|
+
| Array & union composables | [composables.md](composables.md) | `useFoormArray`, `useFoormUnion`, `useConsumeUnionContext`, `formatIndexedLabel` |
|
|
19
|
+
| Rendering architecture | [rendering.md](rendering.md) | OoField internals, component resolution, nesting levels, provide/inject keys, allStatic optimization |
|
|
20
|
+
| Default components | [defaults.md](defaults.md) | Built-in OoInput, OoSelect, OoObject, OoArray, OoUnion — what each renders, internal helpers, overriding patterns |
|
|
21
|
+
|
|
22
|
+
## Quick reference
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { OoForm, useFoorm, createDefaultTypes } from '@foormjs/vue'
|
|
26
|
+
import type { TFoormComponentProps, TFoormTypeComponents } from '@foormjs/vue'
|
|
27
|
+
|
|
28
|
+
const { def, formData } = useFoorm(MyForm)
|
|
29
|
+
|
|
30
|
+
// Use defaults
|
|
31
|
+
const types = createDefaultTypes()
|
|
32
|
+
|
|
33
|
+
// Or override specific types
|
|
34
|
+
const types = { ...createDefaultTypes(), text: MyInput, select: MySelect }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```vue
|
|
38
|
+
<OoForm
|
|
39
|
+
:def="def"
|
|
40
|
+
:form-data="formData"
|
|
41
|
+
:types="types"
|
|
42
|
+
:components="{ StarRating: MyStarRating }"
|
|
43
|
+
@submit="onSubmit"
|
|
44
|
+
@change="onChange"
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Two ways to provide custom components:**
|
|
49
|
+
|
|
50
|
+
- `types` prop — maps field type strings (`@foorm.type` or auto-inferred) to components. Every field always has a type.
|
|
51
|
+
- `components` prop — maps named components (`@foorm.component`) to components. Per-field override, takes priority over `types`.
|
|
52
|
+
|
|
53
|
+
**Writing `.as` form schemas:** Forms are defined in `.as` files with `@foorm.*` annotations. See the `@foormjs/atscript` skill's `schema.md` for the full guide — field types, validation, options, arrays, unions, nested groups, computed properties.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Array & Union Composables — @foormjs/vue
|
|
2
|
+
|
|
3
|
+
> `useFoormArray`, `useFoormUnion`, `useConsumeUnionContext`, `formatIndexedLabel`, and `useDropdown`.
|
|
4
|
+
|
|
5
|
+
## useFoormArray
|
|
6
|
+
|
|
7
|
+
Manages array field state: stable keys, add/remove with constraints, per-index field caching, and union variant support.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { useFoormArray } from '@foormjs/vue'
|
|
11
|
+
import type { FoormArrayFieldDef } from '@foormjs/atscript'
|
|
12
|
+
import { isArrayField } from '@foormjs/atscript'
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
|
|
15
|
+
const arrayField = isArrayField(props.field!) ? (props.field as FoormArrayFieldDef) : undefined
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
arrayValue, // ComputedRef<unknown[]> — current array items
|
|
19
|
+
itemKeys, // string[] (reactive) — stable keys for v-for
|
|
20
|
+
getItemField, // (index: number) => FoormFieldDef
|
|
21
|
+
isUnion, // boolean — items are union types?
|
|
22
|
+
unionVariants, // FoormUnionVariant[] — variants (if union)
|
|
23
|
+
addItem, // (variantIndex?: number) => void
|
|
24
|
+
removeItem, // (index: number) => void
|
|
25
|
+
canAdd, // ComputedRef<boolean> — respects @expect.maxLength
|
|
26
|
+
canRemove, // ComputedRef<boolean> — respects @expect.minLength
|
|
27
|
+
addLabel, // string — from @foorm.array.add.label
|
|
28
|
+
removeLabel, // string — from @foorm.array.remove.label
|
|
29
|
+
} = useFoormArray(
|
|
30
|
+
arrayField!,
|
|
31
|
+
computed(() => props.disabled ?? false)
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Parameters:**
|
|
36
|
+
|
|
37
|
+
- `field: FoormArrayFieldDef` — array field definition (use type guard first)
|
|
38
|
+
- `disabled?: ComputedRef<boolean>` — reactive disabled state
|
|
39
|
+
|
|
40
|
+
**Key behaviors:**
|
|
41
|
+
|
|
42
|
+
- `itemKeys` — stable string keys for Vue `v-for` tracking; auto-synced on array mutations
|
|
43
|
+
- `getItemField(index)` — returns a `FoormFieldDef` for the item at index; caches and reuses across calls
|
|
44
|
+
- `addItem(variantIndex?)` — for non-union arrays, pass `0`; for union arrays, pass the variant index
|
|
45
|
+
- `removeItem(index)` — removes item and its key; invalidates cached field defs
|
|
46
|
+
- `canAdd` / `canRemove` — computed from `@expect.maxLength` / `@expect.minLength`
|
|
47
|
+
- Emits `'array-add'` and `'array-remove'` change events via injected `__foorm_change_handler`
|
|
48
|
+
|
|
49
|
+
**Union arrays:**
|
|
50
|
+
|
|
51
|
+
- `isUnion` is `true` when the array item type is a union
|
|
52
|
+
- `unionVariants` lists available variants
|
|
53
|
+
- `addItem(variantIndex)` creates a new item from the selected variant via `createItemData(variant)`
|
|
54
|
+
|
|
55
|
+
## useFoormUnion
|
|
56
|
+
|
|
57
|
+
Manages union variant state with data stashing — switching away saves data, switching back restores it.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { useFoormUnion } from '@foormjs/vue'
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
unionField, // ComputedRef<FoormUnionFieldDef | undefined>
|
|
64
|
+
hasMultipleVariants, // ComputedRef<boolean>
|
|
65
|
+
localUnionIndex, // Ref<number> — current variant index
|
|
66
|
+
currentVariant, // ComputedRef<FoormUnionVariant>
|
|
67
|
+
innerField, // ComputedRef<FoormFieldDef | undefined>
|
|
68
|
+
changeVariant, // (newIndex: number) => void
|
|
69
|
+
optionalEnabled, // ComputedRef<boolean>
|
|
70
|
+
dropdownRef, // Ref<HTMLElement | null> — for variant picker dropdown
|
|
71
|
+
isOpen, // Ref<boolean> — dropdown open state
|
|
72
|
+
toggle, // () => void — toggle dropdown
|
|
73
|
+
select, // (callback) => void — select and close
|
|
74
|
+
handleNaClick, // () => void — handle N/A click for optional unions
|
|
75
|
+
} = useFoormUnion(props)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Parameters:**
|
|
79
|
+
|
|
80
|
+
- `props: TFoormComponentProps` — the union component's props
|
|
81
|
+
|
|
82
|
+
**Key behaviors:**
|
|
83
|
+
|
|
84
|
+
- **Data stashing:** when switching from variant A to B, A's data is saved; switching back restores it
|
|
85
|
+
- **Variant detection:** auto-detects the initial variant from existing data using `detectUnionVariant()`
|
|
86
|
+
- **Provides `__foorm_union` context:** `{ variants, currentIndex, changeVariant }` — consumed by child components (e.g., inline variant picker in headers)
|
|
87
|
+
- **Emits `'union-switch'`** change event when variant changes
|
|
88
|
+
- **Dropdown state** (`dropdownRef`, `isOpen`, `toggle`, `select`) — for optional N/A variant picker UI
|
|
89
|
+
|
|
90
|
+
## useConsumeUnionContext
|
|
91
|
+
|
|
92
|
+
Reads the `__foorm_union` injection and immediately clears it. Prevents nested children from inheriting stale union context.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { useConsumeUnionContext } from '@foormjs/vue'
|
|
96
|
+
|
|
97
|
+
const unionCtx = useConsumeUnionContext()
|
|
98
|
+
// unionCtx?.variants — FoormUnionVariant[]
|
|
99
|
+
// unionCtx?.currentIndex — Ref<number>
|
|
100
|
+
// unionCtx?.changeVariant — (index: number) => void
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Must call in:** custom object, array, tuple, and field shell components. Without this, deeply nested components would incorrectly see a parent union's context.
|
|
104
|
+
|
|
105
|
+
**Return type: `TFoormUnionContext | undefined`**
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
interface TFoormUnionContext {
|
|
109
|
+
variants: FoormUnionVariant[]
|
|
110
|
+
currentIndex: Ref<number>
|
|
111
|
+
changeVariant: (index: number) => void
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## formatIndexedLabel
|
|
116
|
+
|
|
117
|
+
Formats a label with an array index prefix.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { formatIndexedLabel } from '@foormjs/vue'
|
|
121
|
+
|
|
122
|
+
formatIndexedLabel('Address', 0) // "Address #1"
|
|
123
|
+
formatIndexedLabel('Address', 2) // "Address #3"
|
|
124
|
+
formatIndexedLabel(undefined, 0) // "#1"
|
|
125
|
+
formatIndexedLabel('Name', undefined) // "Name"
|
|
126
|
+
formatIndexedLabel(undefined, undefined) // undefined
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Used by default OoObject and OoFieldShell components for array item labels.
|
|
130
|
+
|
|
131
|
+
## Common Patterns
|
|
132
|
+
|
|
133
|
+
### Pattern: Custom array with drag-and-drop
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<script setup lang="ts">
|
|
137
|
+
import type { TFoormComponentProps } from '@foormjs/vue'
|
|
138
|
+
import type { FoormArrayFieldDef } from '@foormjs/atscript'
|
|
139
|
+
import { isArrayField } from '@foormjs/atscript'
|
|
140
|
+
import { OoField, useFoormArray } from '@foormjs/vue'
|
|
141
|
+
import { computed } from 'vue'
|
|
142
|
+
|
|
143
|
+
const props = defineProps<TFoormComponentProps>()
|
|
144
|
+
const arrayField = isArrayField(props.field!) ? (props.field as FoormArrayFieldDef) : undefined
|
|
145
|
+
|
|
146
|
+
const {
|
|
147
|
+
arrayValue,
|
|
148
|
+
itemKeys,
|
|
149
|
+
getItemField,
|
|
150
|
+
addItem,
|
|
151
|
+
removeItem,
|
|
152
|
+
canAdd,
|
|
153
|
+
canRemove,
|
|
154
|
+
addLabel,
|
|
155
|
+
removeLabel,
|
|
156
|
+
} = useFoormArray(
|
|
157
|
+
arrayField!,
|
|
158
|
+
computed(() => props.disabled ?? false)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
// Your drag-and-drop logic here — reorder arrayValue items and itemKeys
|
|
162
|
+
</script>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Pattern: Union with inline variant picker in object header
|
|
166
|
+
|
|
167
|
+
The default `OoUnion` provides `__foorm_union` context. The default `OoStructuredHeader` (used by `OoObject`) consumes it to render a variant picker dropdown. For custom components, use `useConsumeUnionContext()`:
|
|
168
|
+
|
|
169
|
+
```vue
|
|
170
|
+
<script setup lang="ts">
|
|
171
|
+
import { useConsumeUnionContext } from '@foormjs/vue'
|
|
172
|
+
|
|
173
|
+
const unionCtx = useConsumeUnionContext()
|
|
174
|
+
|
|
175
|
+
// Render a variant picker if unionCtx is present
|
|
176
|
+
// unionCtx.variants — show as dropdown
|
|
177
|
+
// unionCtx.changeVariant(index) — switch variant
|
|
178
|
+
// unionCtx.currentIndex — highlight active
|
|
179
|
+
</script>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Gotchas
|
|
183
|
+
|
|
184
|
+
- `useFoormArray` must receive a `FoormArrayFieldDef` — always use `isArrayField()` type guard first
|
|
185
|
+
- `itemKeys` is a reactive array, not a ref — it mutates in place
|
|
186
|
+
- `getItemField(index)` caches field defs — after `removeItem`, cached entries at removed indices are invalidated
|
|
187
|
+
- `changeVariant()` in `useFoormUnion` triggers data stashing — don't manually modify form data before/after
|
|
188
|
+
- `useConsumeUnionContext()` has a side effect: it `provide`s `undefined` for `__foorm_union` to clear the injection for children. Always call it even if you don't use the return value.
|
|
189
|
+
- `addItem()` for non-union arrays always takes `0` as the variant index argument
|