@foormjs/atscript 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.
@@ -0,0 +1,299 @@
1
+ import { AnnotationSpec } from '@atscript/core';
2
+
3
+ /**
4
+ * Attempts to compile a function string with `new Function` and returns
5
+ * diagnostic errors if it fails. Used by @foorm.fn.* and @foorm.validate
6
+ * annotation validate hooks.
7
+ */
8
+ function validateFnString(fnStr, range) {
9
+ try {
10
+ // eslint-disable-next-line no-new-func
11
+ new Function('v', 'data', 'context', 'entry', `return (${fnStr})(v, data, context, entry)`);
12
+ }
13
+ catch (error) {
14
+ return [
15
+ {
16
+ severity: 1,
17
+ message: `Invalid function string: ${error.message}`,
18
+ range,
19
+ },
20
+ ];
21
+ }
22
+ return undefined;
23
+ }
24
+ function fnAnnotation(description) {
25
+ return new AnnotationSpec({
26
+ description,
27
+ nodeType: ['prop'],
28
+ argument: {
29
+ name: 'fn',
30
+ type: 'string',
31
+ description: 'JS function string: (value, data, context, entry) => result',
32
+ },
33
+ validate(token, args) {
34
+ if (args[0]) {
35
+ return validateFnString(args[0].text, args[0].range);
36
+ }
37
+ return undefined;
38
+ },
39
+ });
40
+ }
41
+ function fnTopAnnotation(description) {
42
+ return new AnnotationSpec({
43
+ description,
44
+ nodeType: ['interface'],
45
+ argument: {
46
+ name: 'fn',
47
+ type: 'string',
48
+ description: 'JS function string: (data, context) => result',
49
+ },
50
+ validate(token, args) {
51
+ if (args[0]) {
52
+ return validateFnString(args[0].text, args[0].range);
53
+ }
54
+ return undefined;
55
+ },
56
+ });
57
+ }
58
+ const annotations = {
59
+ foorm: {
60
+ // ── Form-level static annotations ────────────────────────
61
+ title: new AnnotationSpec({
62
+ description: 'Static form title',
63
+ nodeType: ['interface'],
64
+ argument: {
65
+ name: 'title',
66
+ type: 'string',
67
+ description: 'The form title text',
68
+ },
69
+ }),
70
+ submit: {
71
+ text: new AnnotationSpec({
72
+ description: 'Static submit button text',
73
+ nodeType: ['interface'],
74
+ argument: {
75
+ name: 'text',
76
+ type: 'string',
77
+ description: 'Submit button label',
78
+ },
79
+ }),
80
+ },
81
+ // ── Field-level static annotations ───────────────────────
82
+ type: new AnnotationSpec({
83
+ description: 'Field input type',
84
+ nodeType: ['prop'],
85
+ argument: {
86
+ name: 'type',
87
+ type: 'string',
88
+ values: [
89
+ 'text',
90
+ 'password',
91
+ 'number',
92
+ 'select',
93
+ 'textarea',
94
+ 'checkbox',
95
+ 'radio',
96
+ 'date',
97
+ 'paragraph',
98
+ 'action',
99
+ ],
100
+ description: 'The input type for this field',
101
+ },
102
+ }),
103
+ component: new AnnotationSpec({
104
+ description: 'Named component override for rendering this field',
105
+ nodeType: ['prop'],
106
+ argument: {
107
+ name: 'name',
108
+ type: 'string',
109
+ description: 'Component name from the components registry',
110
+ },
111
+ }),
112
+ autocomplete: new AnnotationSpec({
113
+ description: 'HTML autocomplete attribute value',
114
+ nodeType: ['prop'],
115
+ argument: {
116
+ name: 'value',
117
+ type: 'string',
118
+ description: 'Autocomplete value (e.g., "email", "given-name")',
119
+ },
120
+ }),
121
+ altAction: new AnnotationSpec({
122
+ description: 'Alternate submit action name for this field',
123
+ nodeType: ['prop'],
124
+ argument: {
125
+ name: 'action',
126
+ type: 'string',
127
+ description: 'The action name emitted on submit',
128
+ },
129
+ }),
130
+ value: new AnnotationSpec({
131
+ description: 'Default value for this field',
132
+ nodeType: ['prop'],
133
+ argument: {
134
+ name: 'value',
135
+ type: 'string',
136
+ description: 'Default value (parsed by field type at runtime)',
137
+ },
138
+ }),
139
+ order: new AnnotationSpec({
140
+ description: 'Explicit rendering order for this field',
141
+ nodeType: ['prop'],
142
+ argument: {
143
+ name: 'order',
144
+ type: 'number',
145
+ description: 'Numeric order (lower = earlier)',
146
+ },
147
+ }),
148
+ hidden: new AnnotationSpec({
149
+ description: 'Statically mark this field as hidden',
150
+ nodeType: ['prop'],
151
+ }),
152
+ disabled: new AnnotationSpec({
153
+ description: 'Statically mark this field as disabled',
154
+ nodeType: ['prop'],
155
+ }),
156
+ // ── Options annotation ──────────────────────────────────
157
+ options: new AnnotationSpec({
158
+ description: 'Static option for select/radio fields. Repeat for each option. Label is the display text, value is the key (defaults to label).',
159
+ nodeType: ['prop'],
160
+ multiple: true,
161
+ mergeStrategy: 'replace',
162
+ argument: [
163
+ {
164
+ name: 'label',
165
+ type: 'string',
166
+ description: 'Display label for the option',
167
+ },
168
+ {
169
+ name: 'value',
170
+ type: 'string',
171
+ optional: true,
172
+ description: 'Value/key for the option (defaults to label if omitted)',
173
+ },
174
+ ],
175
+ }),
176
+ // ── Validation annotation ────────────────────────────────
177
+ validate: new AnnotationSpec({
178
+ description: 'Custom JS validator function string. Returns true for pass, or an error message string.',
179
+ nodeType: ['prop'],
180
+ multiple: true,
181
+ mergeStrategy: 'append',
182
+ argument: {
183
+ name: 'fn',
184
+ type: 'string',
185
+ description: 'JS function string: (value, data, context) => boolean | string',
186
+ },
187
+ validate(token, args) {
188
+ if (args[0]) {
189
+ return validateFnString(args[0].text, args[0].range);
190
+ }
191
+ return undefined;
192
+ },
193
+ }),
194
+ // ── Computed (fn) annotations ────────────────────────────
195
+ fn: {
196
+ // Form-level computed
197
+ title: fnTopAnnotation('Computed form title: (data, context) => string'),
198
+ submit: {
199
+ text: fnTopAnnotation('Computed submit button text: (data, context) => string'),
200
+ disabled: fnTopAnnotation('Computed submit disabled state: (data, context) => boolean'),
201
+ },
202
+ // Field-level computed
203
+ label: fnAnnotation('Computed label: (value, data, context, entry) => string'),
204
+ description: fnAnnotation('Computed description: (value, data, context, entry) => string'),
205
+ hint: fnAnnotation('Computed hint: (value, data, context, entry) => string'),
206
+ placeholder: fnAnnotation('Computed placeholder: (value, data, context, entry) => string'),
207
+ disabled: fnAnnotation('Computed disabled state: (value, data, context, entry) => boolean'),
208
+ hidden: fnAnnotation('Computed hidden state: (value, data, context, entry) => boolean'),
209
+ optional: fnAnnotation('Computed optional state: (value, data, context, entry) => boolean'),
210
+ classes: fnAnnotation('Computed CSS classes: (value, data, context, entry) => string | Record<string, boolean>'),
211
+ styles: fnAnnotation('Computed inline styles: (value, data, context, entry) => string | Record<string, string>'),
212
+ options: fnAnnotation('Computed select/radio options: (value, data, context, entry) => Array'),
213
+ },
214
+ },
215
+ };
216
+
217
+ const primitives = {
218
+ foorm: {
219
+ type: 'phantom',
220
+ isContainer: true,
221
+ documentation: 'Non-data UI elements for form rendering',
222
+ extensions: {
223
+ action: {
224
+ documentation: 'Form action button — not a data field, excluded from form data. Use with @foorm.altAction to define alternate submit actions.',
225
+ },
226
+ paragraph: {
227
+ documentation: 'Read-only paragraph text — rendered as static content, not an input field. Use @meta.label for the paragraph text.',
228
+ },
229
+ select: {
230
+ type: 'string',
231
+ documentation: 'Dropdown select field. Use @foorm.options to define static choices or @foorm.fn.options for computed choices.',
232
+ },
233
+ radio: {
234
+ type: 'string',
235
+ documentation: 'Radio button group. Use @foorm.options to define static choices or @foorm.fn.options for computed choices.',
236
+ },
237
+ checkbox: {
238
+ type: 'boolean',
239
+ documentation: 'Single boolean checkbox toggle.',
240
+ },
241
+ },
242
+ },
243
+ };
244
+
245
+ const BUILTIN_TYPES = [
246
+ 'text',
247
+ 'password',
248
+ 'number',
249
+ 'select',
250
+ 'textarea',
251
+ 'checkbox',
252
+ 'radio',
253
+ 'date',
254
+ 'paragraph',
255
+ 'action',
256
+ ];
257
+ function foormPlugin(opts) {
258
+ return {
259
+ name: 'foorm',
260
+ config() {
261
+ var _a, _b, _c, _d;
262
+ if (!((_a = opts === null || opts === void 0 ? void 0 : opts.extraTypes) === null || _a === void 0 ? void 0 : _a.length) && !((_b = opts === null || opts === void 0 ? void 0 : opts.components) === null || _b === void 0 ? void 0 : _b.length)) {
263
+ return { primitives, annotations };
264
+ }
265
+ const foormNs = annotations.foorm;
266
+ const overrides = {};
267
+ if ((_c = opts.extraTypes) === null || _c === void 0 ? void 0 : _c.length) {
268
+ overrides.type = new AnnotationSpec({
269
+ description: 'Field input type',
270
+ nodeType: ['prop'],
271
+ argument: {
272
+ name: 'type',
273
+ type: 'string',
274
+ values: [...BUILTIN_TYPES, ...opts.extraTypes],
275
+ description: 'The input type for this field',
276
+ },
277
+ });
278
+ }
279
+ if ((_d = opts.components) === null || _d === void 0 ? void 0 : _d.length) {
280
+ overrides.component = new AnnotationSpec({
281
+ description: 'Named component override for rendering this field',
282
+ nodeType: ['prop'],
283
+ argument: {
284
+ name: 'name',
285
+ type: 'string',
286
+ values: opts.components,
287
+ description: 'Component name from the components registry',
288
+ },
289
+ });
290
+ }
291
+ return {
292
+ primitives,
293
+ annotations: Object.assign(Object.assign({}, annotations), { foorm: Object.assign(Object.assign({}, foormNs), overrides) }),
294
+ };
295
+ },
296
+ };
297
+ }
298
+
299
+ export { annotations, foormPlugin, primitives };
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@foormjs/atscript",
3
+ "version": "0.2.0",
4
+ "description": "ATScript plugin for foormjs — form annotations, primitives, and validation",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./plugin": {
16
+ "types": "./dist/plugin.d.ts",
17
+ "import": "./dist/plugin.mjs",
18
+ "require": "./dist/plugin.cjs"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "typesVersions": {
23
+ "*": {
24
+ "plugin": [
25
+ "dist/plugin.d.ts"
26
+ ],
27
+ "": [
28
+ "dist/index.d.ts"
29
+ ]
30
+ }
31
+ },
32
+ "build": [
33
+ {},
34
+ {
35
+ "entries": [
36
+ "src/plugin.ts"
37
+ ],
38
+ "dts": true
39
+ }
40
+ ],
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/foormjs/foormjs.git",
47
+ "directory": "packages/atscript"
48
+ },
49
+ "keywords": [
50
+ "foorm",
51
+ "foormjs",
52
+ "forms",
53
+ "validations",
54
+ "atscript"
55
+ ],
56
+ "author": "Artem Maltsev",
57
+ "license": "MIT",
58
+ "bugs": {
59
+ "url": "https://github.com/foormjs/foormjs/issues"
60
+ },
61
+ "homepage": "https://github.com/foormjs/foormjs/tree/main/packages/atscript#readme",
62
+ "dependencies": {
63
+ "@atscript/core": "^0.1.6",
64
+ "@prostojs/deserialize-fn": "^0.0.5",
65
+ "foorm": "^0.2.0"
66
+ },
67
+ "peerDependencies": {
68
+ "@atscript/typescript": "^0.1.6"
69
+ },
70
+ "scripts": {
71
+ "pub": "pnpm publish --access public --no-git-checks"
72
+ }
73
+ }