@jarenjs/forms 0.43.3 → 0.46.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 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
@@ -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>;
@@ -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.43.3",
4
+ "version": "0.46.5",
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.43.3",
51
- "@jarenjs/formats": "^0.43.3",
52
- "@jarenjs/json": "^0.43.3",
53
- "@jarenjs/validate": "^0.43.3"
50
+ "@jarenjs/core": "^0.46.5",
51
+ "@jarenjs/formats": "^0.46.5",
52
+ "@jarenjs/json": "^0.46.5",
53
+ "@jarenjs/validate": "^0.46.5"
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,