@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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@foormjs/vue",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "@foormjs/vue",
5
- "main": "dist/index.js",
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.umd.cjs",
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
- "vue": "^3.5.28",
44
- "vuiless-forms": "^0.0.3",
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.8",
50
- "@atscript/typescript": "^0.1.8"
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.8",
54
- "@atscript/typescript": "^0.1.8",
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": "^20.19.33",
58
- "@vitejs/plugin-vue": "^5.2.4",
59
- "@vue/tsconfig": "^0.5.1",
60
- "npm-run-all2": "^6.2.6",
61
- "typescript": "~5.4.5",
62
- "unplugin-atscript": "^0.1.8",
63
- "vite": "^5.4.21",
64
- "vite-plugin-dts": "^3.9.1",
65
- "vue-tsc": "^2.2.12"
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