@jarenjs/forms 0.43.3 → 0.46.4
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 +17 -0
- package/dist/types/formats.d.ts +12 -0
- package/dist/types/model.d.ts +4 -0
- package/dist/types/viewmodel.d.ts +9 -0
- package/package.json +5 -5
- package/src/formats.js +22 -1
- package/src/model.js +2 -0
- package/src/viewmodel.js +4 -0
package/README.md
CHANGED
|
@@ -66,11 +66,28 @@ const result = validate(data); // { valid, errors: [{ instancePath, keyword, mes
|
|
|
66
66
|
| `constraints` | `minLength`/`maxLength`/`pattern`/`format`/`minimum`/`maximum`/`multipleOf`/`minItems`/... |
|
|
67
67
|
| `rules` | The raw `x-form` rules annotation, if any (see below) |
|
|
68
68
|
| `enumValues` / `constValue` / `defaultValue` / `placeholder` | Values for the UI |
|
|
69
|
+
| `preview` | A preview hint, or `null`: what a host MAY draw beside the control, as data (`{ kind: 'map' }` for `geojson`) — see below |
|
|
69
70
|
| `children` | Child fields (object kinds) |
|
|
70
71
|
| `item` / `tuple` | Item template and tuple prefix fields (array kinds) |
|
|
71
72
|
|
|
72
73
|
Field kinds are inferred from structural keywords when `type` is absent, and `format` maps to input controls and placeholders through the same registry the preemptive validation uses (`getFormatInfo`).
|
|
73
74
|
|
|
75
|
+
### The preview hint — a renderer described as data
|
|
76
|
+
|
|
77
|
+
A `geojson` field is a `textarea` whose text is validated by parsing it and judging the
|
|
78
|
+
object (`isValidGeoJson`), and that is where a text control stops: the value can be valid
|
|
79
|
+
GeoJSON long before it is the shape the user meant, and no textarea can show the
|
|
80
|
+
difference. The format registry therefore carries a **preview hint** —
|
|
81
|
+
`getFormatInfo('geojson').preview` is `{ kind: 'map' }` — and the field and the view node
|
|
82
|
+
carry it through as `preview`. It is a description, not a renderer: `@jarenjs/forms`
|
|
83
|
+
imports no chart, and the dependency arrow forbids it (`@jarenjs/charts` sits above this
|
|
84
|
+
package). A **host that understands** `preview.kind === 'map'` parses the field's text and
|
|
85
|
+
hands it to a map renderer — `@jarenjs/charts` draws a `FeatureCollection`, a `Feature` or a
|
|
86
|
+
geometry through `compileChart({ type: 'map' }, { features })` — beside the control. A host
|
|
87
|
+
that does **not** ignores the member, and the field is exactly the textarea it always was:
|
|
88
|
+
same control, same placeholder, same test. Both paths are tests; the `@jarenjs/app` form
|
|
89
|
+
stylesheet is the second kind of host, and the website's data studio the first.
|
|
90
|
+
|
|
74
91
|
Which date formats get a **native** control is decided by the offset, not by convenience. HTML's `datetime-local` and `time` inputs cannot produce one, and RFC 3339 requires one — binding them to `date-time`/`time` would make the control emit values its own schema rejects, so those stay text inputs. The `iso-date-time`/`iso-time` formats leave the offset optional and are exactly what those inputs spell, so they map losslessly. `formatMinimum`/`formatMaximum` reach the field as constraints and become the control's `min`/`max`, so the picker itself refuses an out-of-range date; HTML has no exclusive date bounds, so `formatExclusive*` stays a submit-time check.
|
|
75
92
|
|
|
76
93
|
## Layer 1 — preemptive per-field validation
|
package/dist/types/formats.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ export type FormatInfo = {
|
|
|
11
11
|
* - Suggested placeholder text
|
|
12
12
|
*/
|
|
13
13
|
placeholder?: string;
|
|
14
|
+
/**
|
|
15
|
+
* - What a host MAY render beside the
|
|
16
|
+
* control, as data (see {@link FormatPreview}); absent for formats a
|
|
17
|
+
* text control already shows in full
|
|
18
|
+
*/
|
|
19
|
+
preview?: FormatPreview;
|
|
20
|
+
};
|
|
21
|
+
export type FormatPreview = {
|
|
22
|
+
/**
|
|
23
|
+
* - The renderer family the host is asked for ('map')
|
|
24
|
+
*/
|
|
25
|
+
kind: string;
|
|
14
26
|
};
|
|
15
27
|
/** @type {Record<string, FormatInfo>} */
|
|
16
28
|
export declare const FORM_FORMATS: Record<string, FormatInfo>;
|
package/dist/types/model.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export type FormField = {
|
|
|
47
47
|
constValue: any;
|
|
48
48
|
defaultValue: any;
|
|
49
49
|
placeholder: string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* - The format's preview hint (formats.js), for a host with a renderer for it; null otherwise
|
|
52
|
+
*/
|
|
53
|
+
preview: import('./formats.js').FormatPreview | null;
|
|
50
54
|
/**
|
|
51
55
|
* - minLength/maxLength/pattern/minimum/... extracted for the UI
|
|
52
56
|
*/
|
|
@@ -44,6 +44,12 @@ export type FormViewNode = {
|
|
|
44
44
|
*/
|
|
45
45
|
enabled: boolean;
|
|
46
46
|
placeholder: string | null;
|
|
47
|
+
/**
|
|
48
|
+
* - The
|
|
49
|
+
* format's preview hint, carried through for a host that renders it;
|
|
50
|
+
* a host without a renderer for `preview.kind` ignores it.
|
|
51
|
+
*/
|
|
52
|
+
preview: import('./formats.js').FormatPreview | null;
|
|
47
53
|
/**
|
|
48
54
|
* - The current value (`x-form.computed` wins);
|
|
49
55
|
* `null` when the field is absent from the data.
|
|
@@ -246,6 +252,9 @@ export type FormViewModelOptions = {
|
|
|
246
252
|
* @property {boolean} readOnly
|
|
247
253
|
* @property {boolean} enabled - `x-form.enabled`, defaulting true.
|
|
248
254
|
* @property {string|null} placeholder
|
|
255
|
+
* @property {import('./formats.js').FormatPreview|null} preview - The
|
|
256
|
+
* format's preview hint, carried through for a host that renders it;
|
|
257
|
+
* a host without a renderer for `preview.kind` ignores it.
|
|
249
258
|
* @property {any} value - The current value (`x-form.computed` wins);
|
|
250
259
|
* `null` when the field is absent from the data.
|
|
251
260
|
* @property {Array<{value: any, key: string, label: string, selected: boolean}>|null} options
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/forms",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.46.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"prepack": "npm run build:types"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@jarenjs/core": "^0.
|
|
51
|
-
"@jarenjs/formats": "^0.
|
|
52
|
-
"@jarenjs/json": "^0.
|
|
53
|
-
"@jarenjs/validate": "^0.
|
|
50
|
+
"@jarenjs/core": "^0.46.4",
|
|
51
|
+
"@jarenjs/formats": "^0.46.4",
|
|
52
|
+
"@jarenjs/json": "^0.46.4",
|
|
53
|
+
"@jarenjs/validate": "^0.46.4"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/formats.js
CHANGED
|
@@ -26,6 +26,21 @@ import {
|
|
|
26
26
|
* @property {(value: string) => boolean} test - Synchronous validity test
|
|
27
27
|
* @property {string} control - Suggested HTML input control
|
|
28
28
|
* @property {string} [placeholder] - Suggested placeholder text
|
|
29
|
+
* @property {FormatPreview} [preview] - What a host MAY render beside the
|
|
30
|
+
* control, as data (see {@link FormatPreview}); absent for formats a
|
|
31
|
+
* text control already shows in full
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A preview hint: a description of what to draw from the field's value,
|
|
36
|
+
* for a host that has a renderer for it. It is DATA, not a renderer —
|
|
37
|
+
* `@jarenjs/forms` imports no chart, and a host that does not recognise
|
|
38
|
+
* `kind` ignores the hint and the field behaves exactly as without it.
|
|
39
|
+
* The one kind shipped is `map`: the host feeds the field's parsed
|
|
40
|
+
* GeoJSON to a map renderer (`@jarenjs/charts`' `map` type takes a
|
|
41
|
+
* `FeatureCollection`, a `Feature` or a geometry as `features`).
|
|
42
|
+
* @typedef {object} FormatPreview
|
|
43
|
+
* @property {string} kind - The renderer family the host is asked for ('map')
|
|
29
44
|
*/
|
|
30
45
|
|
|
31
46
|
/**
|
|
@@ -34,7 +49,7 @@ import {
|
|
|
34
49
|
* also override the canonical `test` when what a form field holds is not
|
|
35
50
|
* what the validator's format judges (a field holds `geojson` as text,
|
|
36
51
|
* the format applies to the parsed object).
|
|
37
|
-
* @type {Record<string, { control?: string, placeholder?: string, test?: (value: string) => boolean }>}
|
|
52
|
+
* @type {Record<string, { control?: string, placeholder?: string, test?: (value: string) => boolean, preview?: FormatPreview }>}
|
|
38
53
|
*/
|
|
39
54
|
const FORM_HINTS = {
|
|
40
55
|
// -- date and time. Which formats get a native control is decided by
|
|
@@ -101,11 +116,17 @@ const FORM_HINTS = {
|
|
|
101
116
|
|
|
102
117
|
// -- geospatial. The geojson tester judges the OBJECT, but a form
|
|
103
118
|
// field holds its text, so the field-level test parses first.
|
|
119
|
+
// The text a user types is valid GeoJSON long before it is the
|
|
120
|
+
// shape they meant, which no textarea can show: `preview` tells a
|
|
121
|
+
// host that has a map renderer to draw the parsed value. It is a
|
|
122
|
+
// hint in the same sense `control` is — a host without one
|
|
123
|
+
// ignores it and the field is exactly this textarea.
|
|
104
124
|
'geohash': { placeholder: 'u173z' },
|
|
105
125
|
'wkt': { placeholder: 'POINT (4.9041 52.3676)' },
|
|
106
126
|
'geojson': {
|
|
107
127
|
control: 'textarea',
|
|
108
128
|
placeholder: '{"type":"Point","coordinates":[4.9,52.4]}',
|
|
129
|
+
preview: { kind: 'map' },
|
|
109
130
|
test: (text) => {
|
|
110
131
|
try {
|
|
111
132
|
return isValidGeoJson(JSON.parse(text));
|
package/src/model.js
CHANGED
|
@@ -46,6 +46,7 @@ const DEFAULT_MAX_DEPTH = 24;
|
|
|
46
46
|
* @property {any} constValue - Fixed value when the schema is a const
|
|
47
47
|
* @property {any} defaultValue
|
|
48
48
|
* @property {string|undefined} placeholder
|
|
49
|
+
* @property {import('./formats.js').FormatPreview|null} preview - The format's preview hint (formats.js), for a host with a renderer for it; null otherwise
|
|
49
50
|
* @property {object} constraints - minLength/maxLength/pattern/minimum/... extracted for the UI
|
|
50
51
|
* @property {object|null} rules - The raw `x-form` rules annotation, if any (see rules.js)
|
|
51
52
|
* @property {Array<FormField>|null} children - Child fields for object kinds
|
|
@@ -327,6 +328,7 @@ function buildField(rawSchema, rootSchema, pointer, key, required, depth, t) {
|
|
|
327
328
|
constValue: kind === 'const' ? effective.const : undefined,
|
|
328
329
|
defaultValue: effective.default,
|
|
329
330
|
placeholder: t(`${base}#placeholder`, placeholder),
|
|
331
|
+
preview: formatInfo?.preview ?? null,
|
|
330
332
|
constraints: getConstraints(effective),
|
|
331
333
|
// The raw `x-form` annotation only - compiling its query documents is
|
|
332
334
|
// rules.js territory, so model building stays query-engine-free.
|
package/src/viewmodel.js
CHANGED
|
@@ -43,6 +43,9 @@ const EMPTY_STATE = Object.freeze({});
|
|
|
43
43
|
* @property {boolean} readOnly
|
|
44
44
|
* @property {boolean} enabled - `x-form.enabled`, defaulting true.
|
|
45
45
|
* @property {string|null} placeholder
|
|
46
|
+
* @property {import('./formats.js').FormatPreview|null} preview - The
|
|
47
|
+
* format's preview hint, carried through for a host that renders it;
|
|
48
|
+
* a host without a renderer for `preview.kind` ignores it.
|
|
46
49
|
* @property {any} value - The current value (`x-form.computed` wins);
|
|
47
50
|
* `null` when the field is absent from the data.
|
|
48
51
|
* @property {Array<{value: any, key: string, label: string, selected: boolean}>|null} options
|
|
@@ -321,6 +324,7 @@ function buildNode(field, pointer, data, ruleState, fieldErrors, element, remova
|
|
|
321
324
|
readOnly: field.readOnly === true,
|
|
322
325
|
enabled: rs === undefined || rs.enabled !== false,
|
|
323
326
|
placeholder: field.placeholder ?? null,
|
|
327
|
+
preview: field.preview ?? null,
|
|
324
328
|
value: value === undefined ? null : value,
|
|
325
329
|
options: null,
|
|
326
330
|
errors,
|