@foormjs/vue 0.1.0 → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 foormjs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,33 +1,383 @@
1
- # vue
1
+ # @foormjs/vue
2
2
 
3
- This template should help get you started developing with Vue 3 in Vite.
3
+ Vue 3 components for rendering foorm models with built-in validation.
4
4
 
5
- ## Recommended IDE Setup
5
+ Most form libraries force you into their component system. `@foormjs/vue` works the other way: it gives you a form component with sensible defaults for every field type, but lets you replace any part of the rendering with your own components. Use the built-in inputs to get started, then gradually swap in your design system components -- per field, per type, or via scoped slots.
6
6
 
7
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
7
+ Forms are defined in ATScript `.as` files (via `@foormjs/atscript`), converted to a model at runtime, and rendered by `OoForm`. Validation, computed properties, and reactive state are handled automatically.
8
8
 
9
- ## Type Support for `.vue` Imports in TS
9
+ ## Install
10
10
 
11
- TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
11
+ ```bash
12
+ npm install @foormjs/vue
13
+ # or
14
+ pnpm add @foormjs/vue
15
+ ```
16
+
17
+ You'll also need the ATScript tooling in your dev dependencies:
18
+
19
+ ```bash
20
+ pnpm add -D @foormjs/atscript @atscript/core @atscript/typescript unplugin-atscript
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Define a form in ATScript
26
+
27
+ ```
28
+ // src/forms/login.as
29
+ @foorm.title 'Sign In'
30
+ @foorm.submit.text 'Log In'
31
+ export interface LoginForm {
32
+ @meta.label 'Email'
33
+ @meta.placeholder 'you@example.com'
34
+ @foorm.autocomplete 'email'
35
+ @foorm.validate '(v) => !!v || "Email is required"'
36
+ email: string
37
+
38
+ @meta.label 'Password'
39
+ @foorm.type 'password'
40
+ @foorm.validate '(v) => !!v || "Password is required"'
41
+ password: string
42
+
43
+ @meta.label 'Remember me'
44
+ rememberMe?: foorm.checkbox
45
+ }
46
+ ```
47
+
48
+ ### 2. Render it
12
49
 
13
- ## Customize configuration
50
+ ```vue
51
+ <script setup lang="ts">
52
+ import { OoForm, useFoorm } from '@foormjs/vue'
53
+ import { LoginForm } from './forms/login.as'
14
54
 
15
- See [Vite Configuration Reference](https://vitejs.dev/config/).
55
+ const { form, formData } = useFoorm(LoginForm)
16
56
 
17
- ## Project Setup
57
+ function handleSubmit(data: typeof formData) {
58
+ console.log('Submitted:', data)
59
+ }
60
+ </script>
18
61
 
19
- ```sh
20
- pnpm install
62
+ <template>
63
+ <OoForm :form="form" :form-data="formData" first-validation="on-blur" @submit="handleSubmit" />
64
+ </template>
21
65
  ```
22
66
 
23
- ### Compile and Hot-Reload for Development
67
+ That's it. The form renders with labels, placeholders, validation, and a submit button -- all derived from the `.as` file annotations.
24
68
 
25
- ```sh
26
- pnpm dev
69
+ ## Practical Examples
70
+
71
+ ### Passing context for dynamic options
72
+
73
+ Backend-provided data (option lists, user info, locale) goes through the `formContext` prop and becomes available to `@foorm.fn.*` annotations:
74
+
75
+ ```
76
+ // preferences.as
77
+ export interface PreferencesForm {
78
+ @meta.label 'City'
79
+ @meta.placeholder 'Select a city'
80
+ @foorm.fn.options '(v, data, context) => context.cityOptions || []'
81
+ city?: foorm.select
82
+ }
83
+ ```
84
+
85
+ ```vue
86
+ <script setup lang="ts">
87
+ import { OoForm, useFoorm } from '@foormjs/vue'
88
+ import { PreferencesForm } from './forms/preferences.as'
89
+
90
+ const { form, formData } = useFoorm(PreferencesForm)
91
+
92
+ const formContext = {
93
+ cityOptions: [
94
+ { key: 'nyc', label: 'New York' },
95
+ { key: 'la', label: 'Los Angeles' },
96
+ { key: 'chi', label: 'Chicago' },
97
+ ],
98
+ }
99
+ </script>
100
+
101
+ <template>
102
+ <OoForm :form="form" :form-data="formData" :form-context="formContext" @submit="handleSubmit" />
103
+ </template>
27
104
  ```
28
105
 
29
- ### Type-Check, Compile and Minify for Production
106
+ ### Custom components by name
30
107
 
31
- ```sh
32
- pnpm build
108
+ Use `@foorm.component` in your `.as` file and pass the component map:
109
+
110
+ ```
111
+ // profile.as
112
+ export interface ProfileForm {
113
+ @meta.label 'Birthday'
114
+ @foorm.component 'DatePicker'
115
+ birthday?: string
116
+ }
33
117
  ```
118
+
119
+ ```vue
120
+ <template>
121
+ <OoForm
122
+ :form="form"
123
+ :form-data="formData"
124
+ :components="{ DatePicker: MyDatePicker }"
125
+ @submit="handleSubmit"
126
+ />
127
+ </template>
128
+ ```
129
+
130
+ ### Custom components by type
131
+
132
+ Replace the default renderer for all fields of a given type:
133
+
134
+ ```vue
135
+ <template>
136
+ <OoForm
137
+ :form="form"
138
+ :form-data="formData"
139
+ :types="{ text: MyTextInput, select: MySelect }"
140
+ @submit="handleSubmit"
141
+ />
142
+ </template>
143
+ ```
144
+
145
+ ### Scoped slots
146
+
147
+ Override rendering for specific field types or form sections:
148
+
149
+ ```vue
150
+ <template>
151
+ <OoForm :form="form" :form-data="formData" @submit="handleSubmit">
152
+ <!-- Custom header -->
153
+ <template #form.header="{ title }">
154
+ <h1 class="my-title">{{ title }}</h1>
155
+ </template>
156
+
157
+ <!-- Custom text field renderer -->
158
+ <template #field:text="field">
159
+ <div class="my-field">
160
+ <label>{{ field.label }}</label>
161
+ <input
162
+ v-model="field.model.value"
163
+ @blur="field.onBlur"
164
+ :placeholder="field.placeholder"
165
+ :disabled="field.disabled"
166
+ />
167
+ <span v-if="field.error" class="error">{{ field.error }}</span>
168
+ </div>
169
+ </template>
170
+
171
+ <!-- Custom submit button -->
172
+ <template #form.submit="{ disabled, text }">
173
+ <button class="my-button" :disabled="disabled">{{ text }}</button>
174
+ </template>
175
+ </OoForm>
176
+ </template>
177
+ ```
178
+
179
+ ### Handling actions
180
+
181
+ Action fields emit events instead of submitting the form:
182
+
183
+ ```
184
+ // wizard.as
185
+ export interface WizardStep {
186
+ @meta.label 'Name'
187
+ name: string
188
+
189
+ @meta.label 'Reset Form'
190
+ @foorm.altAction 'reset'
191
+ resetBtn: foorm.action
192
+ }
193
+ ```
194
+
195
+ ```vue
196
+ <template>
197
+ <OoForm :form="form" :form-data="formData" @submit="handleSubmit" @action="handleAction" />
198
+ </template>
199
+
200
+ <script setup lang="ts">
201
+ function handleAction(name: string, data: unknown) {
202
+ if (name === 'reset') {
203
+ // Reset the form
204
+ }
205
+ }
206
+ </script>
207
+ ```
208
+
209
+ ### Server-side validation errors
210
+
211
+ Pass external errors (e.g., from an API response) via the `errors` prop:
212
+
213
+ ```vue
214
+ <template>
215
+ <OoForm :form="form" :form-data="formData" :errors="serverErrors" @submit="handleSubmit" />
216
+ </template>
217
+
218
+ <script setup lang="ts">
219
+ import { ref } from 'vue'
220
+
221
+ const serverErrors = ref<Record<string, string>>({})
222
+
223
+ async function handleSubmit(data: unknown) {
224
+ const response = await api.submit(data)
225
+ if (response.errors) {
226
+ serverErrors.value = response.errors
227
+ // e.g. { email: 'Email already exists' }
228
+ }
229
+ }
230
+ </script>
231
+ ```
232
+
233
+ ## API
234
+
235
+ ### `useFoorm(Type)`
236
+
237
+ Composable that creates a form model and reactive data from an ATScript type.
238
+
239
+ ```ts
240
+ import { useFoorm } from '@foormjs/vue'
241
+ import { MyForm } from './my-form.as'
242
+
243
+ const { form, formData } = useFoorm(MyForm)
244
+ ```
245
+
246
+ Returns:
247
+
248
+ - `form` -- `TFoormModel` object with fields, title, and submit config
249
+ - `formData` -- Vue `reactive()` object initialized from field defaults
250
+
251
+ ### `OoForm` Props
252
+
253
+ | Prop | Type | Description |
254
+ | ----------------- | --------------------------- | -------------------------------------------- |
255
+ | `form` | `TFoormModel` | Form model (required) |
256
+ | `formData` | `object` | Reactive form data (auto-created if omitted) |
257
+ | `formContext` | `object` | External context for computed functions |
258
+ | `firstValidation` | `'on-blur' \| 'on-submit'` | When to trigger first validation |
259
+ | `components` | `Record<string, Component>` | Named component map for `@foorm.component` |
260
+ | `types` | `Record<string, Component>` | Type-based component map |
261
+ | `errors` | `Record<string, string>` | External validation errors |
262
+
263
+ ### `OoForm` Events
264
+
265
+ | Event | Payload | Description |
266
+ | -------------------- | ---------------- | -------------------------------------------------------------- |
267
+ | `submit` | `formData` | Emitted when the form passes validation and is submitted |
268
+ | `action` | `name, formData` | Emitted when an action button is clicked |
269
+ | `unsupported-action` | `name, formData` | Emitted when an action is clicked but not defined in the model |
270
+
271
+ ### `OoForm` Slots
272
+
273
+ | Slot | Props | Description |
274
+ | -------------- | -------------------------------------------------- | ---------------------------------------------- |
275
+ | `form.header` | `title, clearErrors, reset, formContext, disabled` | Before all fields (default: `<h2>` with title) |
276
+ | `form.before` | `clearErrors, reset` | After header, before fields |
277
+ | `field:{type}` | All field props (see below) | Override renderer for a field type |
278
+ | `form.after` | `clearErrors, reset, disabled, formContext` | After fields, before submit |
279
+ | `form.submit` | `disabled, text, clearErrors, reset, formContext` | Submit button |
280
+ | `form.footer` | `disabled, clearErrors, reset, formContext` | After submit button |
281
+
282
+ ### Field Slot Props
283
+
284
+ Every field slot (`#field:text`, `#field:select`, etc.) receives:
285
+
286
+ | Prop | Type | Description |
287
+ | -------------- | ------------------------- | ----------------------------------------------------------- |
288
+ | `model` | `{ value: V }` | Two-way binding (use `v-model="field.model.value"`) |
289
+ | `onBlur` | `Function` | Call on blur to trigger validation |
290
+ | `error` | `string \| undefined` | Current validation error |
291
+ | `label` | `string` | Evaluated label |
292
+ | `description` | `string` | Evaluated description |
293
+ | `hint` | `string` | Evaluated hint |
294
+ | `placeholder` | `string` | Evaluated placeholder |
295
+ | `options` | `TFoormEntryOptions[]` | Options for select/radio |
296
+ | `classes` | `Record<string, boolean>` | CSS class object (includes `error`, `disabled`, `required`) |
297
+ | `styles` | `string \| Record` | Inline styles |
298
+ | `disabled` | `boolean` | Evaluated disabled state |
299
+ | `hidden` | `boolean` | Evaluated hidden state |
300
+ | `optional` | `boolean` | Evaluated optional state |
301
+ | `required` | `boolean` | Inverse of optional |
302
+ | `type` | `string` | Field type |
303
+ | `vName` | `string` | HTML `name` attribute |
304
+ | `field` | `string` | Field identifier |
305
+ | `altAction` | `string` | Action name (for action fields) |
306
+ | `autocomplete` | `string` | HTML autocomplete value |
307
+ | `maxLength` | `number` | Max length constraint |
308
+ | `formData` | `object` | Full form data |
309
+ | `formContext` | `object` | Form context |
310
+ | `attrs` | `Record` | Additional evaluated attributes |
311
+
312
+ ### `OoField`
313
+
314
+ Renderless field wrapper (used internally by `OoForm`, but available for advanced usage). Wraps `VuilessField` from `vuiless-forms`, evaluating all computed properties and exposing resolved values through its default slot.
315
+
316
+ ### `TFoormComponentProps`
317
+
318
+ TypeScript interface for custom field components. Implement this when building reusable field components:
319
+
320
+ ```ts
321
+ import type { TFoormComponentProps } from '@foormjs/vue'
322
+
323
+ // Your component receives these props:
324
+ defineProps<TFoormComponentProps<string, MyFormData, MyContext>>()
325
+ ```
326
+
327
+ ## Built-in Field Renderers
328
+
329
+ `OoForm` includes default renderers for all standard field types:
330
+
331
+ | Type | Renders as | Notes |
332
+ | ---------------------------- | ------------------------- | ----------------------------------------- |
333
+ | `text`, `password`, `number` | `<input>` | With label, description, error/hint |
334
+ | `select` | `<select>` | With placeholder as disabled first option |
335
+ | `radio` | Radio group | Vertical layout with labels |
336
+ | `checkbox` | `<input type="checkbox">` | Label beside the checkbox |
337
+ | `paragraph` | `<p>` | Description text, no input |
338
+ | `action` | `<button>` | Emits action event on click |
339
+
340
+ Includes minimal CSS that you can override or replace entirely.
341
+
342
+ ## Rendering Priority
343
+
344
+ For each field, `OoForm` tries renderers in this order:
345
+
346
+ 1. **Named component** -- `@foorm.component` + `components` prop
347
+ 2. **Type component** -- field type + `types` prop
348
+ 3. **Scoped slot** -- `#field:{type}` slot
349
+ 4. **Built-in default** -- standard HTML renderer
350
+ 5. **Error message** -- "Not supported field type" fallback
351
+
352
+ ## Vite Configuration
353
+
354
+ Add ATScript support to your Vite config:
355
+
356
+ ```ts
357
+ // vite.config.ts
358
+ import { defineConfig } from 'vite'
359
+ import vue from '@vitejs/plugin-vue'
360
+ import atscript from 'unplugin-atscript'
361
+
362
+ export default defineConfig({
363
+ plugins: [atscript.vite(), vue()],
364
+ })
365
+ ```
366
+
367
+ And configure ATScript:
368
+
369
+ ```ts
370
+ // atscript.config.ts
371
+ import { defineConfig } from '@atscript/core'
372
+ import ts from '@atscript/typescript'
373
+ import { foormPlugin } from '@foormjs/atscript/plugin'
374
+
375
+ export default defineConfig({
376
+ rootDir: 'src',
377
+ plugins: [ts(), foormPlugin()],
378
+ })
379
+ ```
380
+
381
+ ## License
382
+
383
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { TFoormEntry } from 'foorm';
1
+ import { TAnnotatedTypeLike } from '@foormjs/atscript';
2
2
  import { TFoormEntryOptions } from 'foorm';
3
+ import { TFoormField } from 'foorm';
3
4
 
4
- export declare const OoField: <TFormData, TFormContext>(__VLS_props: any, __VLS_ctx?: {
5
+ export declare const OoField: <TFormData = any, TFormContext = any>(__VLS_props: any, __VLS_ctx?: {
5
6
  attrs: any;
6
7
  emit: any;
7
8
  slots: {
@@ -26,7 +27,7 @@ export declare const OoField: <TFormData, TFormContext>(__VLS_props: any, __VLS_
26
27
  vName: any;
27
28
  field: any;
28
29
  options: any;
29
- length: any;
30
+ maxLength: any;
30
31
  required: boolean;
31
32
  autocomplete: any;
32
33
  attrs: any;
@@ -58,7 +59,7 @@ export declare const OoField: <TFormData, TFormContext>(__VLS_props: any, __VLS_
58
59
  vName: any;
59
60
  field: any;
60
61
  options: any;
61
- length: any;
62
+ maxLength: any;
62
63
  required: boolean;
63
64
  autocomplete: any;
64
65
  attrs: any;
@@ -162,10 +163,15 @@ export declare interface TFoormComponentProps<V, TFormData, TFormContext> {
162
163
  type: string;
163
164
  altAction?: string;
164
165
  name?: string;
165
- field?: TFoormEntry<V, TFormData, TFormContext, TFoormEntryOptions>;
166
- options?: unknown[];
167
- length?: number;
166
+ field?: TFoormField;
167
+ options?: TFoormEntryOptions[];
168
+ maxLength?: number;
168
169
  autocomplete?: string;
169
170
  }
170
171
 
172
+ export declare function useFoorm<T = Record<string, unknown>>(type: TAnnotatedTypeLike): {
173
+ form: any;
174
+ formData: any;
175
+ };
176
+
171
177
  export { }