@coffer-org/plugin-webchat 7.0.0 → 7.1.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/dist/ChatWidget-BhGpxS9f.js +2179 -0
- package/dist/index.js +2 -0
- package/dist/runtime/actions.d.ts +18 -2
- package/dist/runtime/actions.js +91 -12
- package/dist/runtime/chain-store.d.ts +20 -4
- package/dist/runtime/chain-store.js +86 -9
- package/dist/runtime/channel-registry.d.ts +16 -0
- package/dist/runtime/channel-registry.js +126 -0
- package/dist/runtime/config.js +0 -1
- package/dist/runtime/connector.d.ts +4 -7
- package/dist/runtime/connector.js +42 -48
- package/dist/runtime/format.d.ts +0 -1
- package/dist/runtime/format.js +2 -11
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +11 -9
- package/dist/runtime/send.d.ts +10 -7
- package/dist/runtime/send.js +93 -36
- package/dist/schema.js +463 -105
- package/dist/web.js +1 -1
- package/locales/en.json +3 -0
- package/locales/ru.json +3 -0
- package/locales/uk.json +3 -0
- package/package.json +5 -6
- package/dist/ChatWidget-DFVDexpD.js +0 -24553
package/dist/schema.js
CHANGED
|
@@ -24,6 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
//#region ../sdk/src/plugin.ts
|
|
25
25
|
function definePlugin(p) {
|
|
26
26
|
if (!p.id) throw new Error("[plugin] missing id");
|
|
27
|
+
if (!p.label) throw new Error(`[plugin] ${p.id}: missing label`);
|
|
28
|
+
if (!p.description) throw new Error(`[plugin] ${p.id}: missing description`);
|
|
27
29
|
if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
|
|
28
30
|
return p;
|
|
29
31
|
}
|
|
@@ -4134,16 +4136,18 @@ function jsonRefined(inner, code) {
|
|
|
4134
4136
|
});
|
|
4135
4137
|
});
|
|
4136
4138
|
}
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4139
|
+
/**
|
|
4140
|
+
* An empty value is no value: `''` and `null` become `undefined`, and the field's own schema
|
|
4141
|
+
* never sees them. Every field is optional, so this is the ONLY shape — a field cannot demand
|
|
4142
|
+
* a value, and the rules it carries judge what was entered rather than whether anything was.
|
|
4143
|
+
*
|
|
4144
|
+
* This used to have a second branch, for `required` fields, which turned the same emptiness
|
|
4145
|
+
* into a `required` issue through a `z.unknown()` guard piped into the schema. That branch is
|
|
4146
|
+
* why a `.min(1)` on a required string was unreachable: the empty string had already been
|
|
4147
|
+
* converted to the issue before the string schema ran.
|
|
4148
|
+
*/
|
|
4149
|
+
function optionalize(schema) {
|
|
4150
|
+
return preprocess((v) => v === "" || v === null ? void 0 : v, schema.optional());
|
|
4147
4151
|
}
|
|
4148
4152
|
//#endregion
|
|
4149
4153
|
//#region ../sdk/src/fields/meta.ts
|
|
@@ -4225,7 +4229,7 @@ function applyMultiple(base, multiple) {
|
|
|
4225
4229
|
multiple: true
|
|
4226
4230
|
},
|
|
4227
4231
|
json: true,
|
|
4228
|
-
zod: optionalize(s
|
|
4232
|
+
zod: optionalize(s)
|
|
4229
4233
|
};
|
|
4230
4234
|
}
|
|
4231
4235
|
/** Inline option entry → OptionItem (plain string = value and label at once). */
|
|
@@ -4340,13 +4344,6 @@ function wrapKey(key, opts, meta) {
|
|
|
4340
4344
|
noLabel: true
|
|
4341
4345
|
}
|
|
4342
4346
|
};
|
|
4343
|
-
if (opts.role) m = {
|
|
4344
|
-
...m,
|
|
4345
|
-
hints: {
|
|
4346
|
-
...m.hints,
|
|
4347
|
-
role: opts.role
|
|
4348
|
-
}
|
|
4349
|
-
};
|
|
4350
4347
|
if (opts.editor) m = {
|
|
4351
4348
|
...m,
|
|
4352
4349
|
hints: {
|
|
@@ -4368,6 +4365,13 @@ function wrapKey(key, opts, meta) {
|
|
|
4368
4365
|
span: opts.span
|
|
4369
4366
|
}
|
|
4370
4367
|
};
|
|
4368
|
+
if (opts.faces !== void 0) m = {
|
|
4369
|
+
...m,
|
|
4370
|
+
hints: {
|
|
4371
|
+
...m.hints,
|
|
4372
|
+
faces: opts.faces
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4371
4375
|
if (opts.default !== void 0) m = {
|
|
4372
4376
|
...m,
|
|
4373
4377
|
default: opts.default
|
|
@@ -4795,9 +4799,9 @@ function normalizeOpts(rawIn) {
|
|
|
4795
4799
|
...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
|
|
4796
4800
|
compareWith: v.compareWith,
|
|
4797
4801
|
span: v.span,
|
|
4802
|
+
faces: v.faces,
|
|
4798
4803
|
emphasis: v.emphasis,
|
|
4799
4804
|
noLabel: v.noLabel,
|
|
4800
|
-
role: v.role,
|
|
4801
4805
|
editor: v.editor,
|
|
4802
4806
|
activate: v.activate
|
|
4803
4807
|
};
|
|
@@ -4960,13 +4964,10 @@ function email(o) {
|
|
|
4960
4964
|
registerPreset("email", "string", {}, {
|
|
4961
4965
|
kind: "email",
|
|
4962
4966
|
widget: "email",
|
|
4963
|
-
build(
|
|
4964
|
-
const required = o.required ?? false;
|
|
4965
|
-
let s = string$1().email({ message: vmsg("email") });
|
|
4966
|
-
if (required) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
|
|
4967
|
+
build() {
|
|
4967
4968
|
return {
|
|
4968
4969
|
column: "text",
|
|
4969
|
-
zod:
|
|
4970
|
+
zod: string$1().email({ message: vmsg("email") }),
|
|
4970
4971
|
hints: {
|
|
4971
4972
|
format: "email",
|
|
4972
4973
|
inputType: "email"
|
|
@@ -4989,12 +4990,10 @@ function tel(o) {
|
|
|
4989
4990
|
registerPreset("tel", "string", {}, {
|
|
4990
4991
|
kind: "tel",
|
|
4991
4992
|
widget: "tel",
|
|
4992
|
-
build(
|
|
4993
|
-
const required = o.required ?? false;
|
|
4994
|
-
const base_z = string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) });
|
|
4993
|
+
build() {
|
|
4995
4994
|
return {
|
|
4996
4995
|
column: "text",
|
|
4997
|
-
zod:
|
|
4996
|
+
zod: string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) }),
|
|
4998
4997
|
hints: {
|
|
4999
4998
|
format: "tel",
|
|
5000
4999
|
inputType: "tel"
|
|
@@ -5018,10 +5017,10 @@ registerPreset("password", "string", {}, {
|
|
|
5018
5017
|
kind: "password",
|
|
5019
5018
|
widget: "password",
|
|
5020
5019
|
selfManages: /* @__PURE__ */ new Set(["multiple"]),
|
|
5021
|
-
build(
|
|
5020
|
+
build() {
|
|
5022
5021
|
return {
|
|
5023
5022
|
column: "text",
|
|
5024
|
-
zod:
|
|
5023
|
+
zod: string$1(),
|
|
5025
5024
|
hints: {
|
|
5026
5025
|
format: "password",
|
|
5027
5026
|
inputType: "password"
|
|
@@ -5083,6 +5082,31 @@ function internalOauthGrants(o) {
|
|
|
5083
5082
|
}
|
|
5084
5083
|
});
|
|
5085
5084
|
}
|
|
5085
|
+
/**
|
|
5086
|
+
* The account page's linked-accounts section. Same trick as `internalApiToken`: a container
|
|
5087
|
+
* with a custom `ui.kind`, drawn by its own renderer, which reads `/api/auth/links` itself.
|
|
5088
|
+
* The declared children are what one ROW holds — the renderer reads them through the slot
|
|
5089
|
+
* contract, so this is a declaration, not a layout.
|
|
5090
|
+
*
|
|
5091
|
+
* @layer preset
|
|
5092
|
+
* @base group
|
|
5093
|
+
* @prim —
|
|
5094
|
+
* @widget internalIdentityLinks
|
|
5095
|
+
* @example f.internalIdentityLinks({ label: 'auth.linksTitle' })
|
|
5096
|
+
*/
|
|
5097
|
+
function internalIdentityLinks(o) {
|
|
5098
|
+
return group({
|
|
5099
|
+
scope: "nest",
|
|
5100
|
+
label: o.label,
|
|
5101
|
+
multiple: true,
|
|
5102
|
+
ui: { kind: "internalIdentityLinks" },
|
|
5103
|
+
fields: {
|
|
5104
|
+
provider: string({}),
|
|
5105
|
+
externalId: string({}),
|
|
5106
|
+
linkedAt: string({})
|
|
5107
|
+
}
|
|
5108
|
+
});
|
|
5109
|
+
}
|
|
5086
5110
|
var SLUG_RE = /^[a-z0-9-]+$/;
|
|
5087
5111
|
/**
|
|
5088
5112
|
* Slug — kind 'slug', `^[a-z0-9-]+$`.
|
|
@@ -5107,6 +5131,63 @@ registerPreset("slug", "string", {}, {
|
|
|
5107
5131
|
};
|
|
5108
5132
|
}
|
|
5109
5133
|
});
|
|
5134
|
+
/**
|
|
5135
|
+
* Identifier — a code printed for a machine and read back by one: a serial number, an order
|
|
5136
|
+
* number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
|
|
5137
|
+
* why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
|
|
5138
|
+
* is one word on any string now, but the click that copies belongs to the kind.
|
|
5139
|
+
*
|
|
5140
|
+
* Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
|
|
5141
|
+
* it, and a pattern here would reject the next manufacturer's.
|
|
5142
|
+
*
|
|
5143
|
+
* @layer preset
|
|
5144
|
+
* @base string
|
|
5145
|
+
* @prim text
|
|
5146
|
+
* @widget identifier
|
|
5147
|
+
* @example f.identifier({ label: 'mod.fields.serialNumber' })
|
|
5148
|
+
*/
|
|
5149
|
+
function identifier(o) {
|
|
5150
|
+
return declare("identifier", o);
|
|
5151
|
+
}
|
|
5152
|
+
registerPreset("identifier", "string", {}, {
|
|
5153
|
+
kind: "identifier",
|
|
5154
|
+
widget: "identifier",
|
|
5155
|
+
build() {
|
|
5156
|
+
return {
|
|
5157
|
+
column: "text",
|
|
5158
|
+
zod: string$1(),
|
|
5159
|
+
hints: {}
|
|
5160
|
+
};
|
|
5161
|
+
}
|
|
5162
|
+
});
|
|
5163
|
+
/**
|
|
5164
|
+
* The machine-readable zone of a travel document — the identifier taken to its limit: printed
|
|
5165
|
+
* for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
|
|
5166
|
+
* line breaks preserved, because an MRZ's line structure is part of what it encodes.
|
|
5167
|
+
*
|
|
5168
|
+
* Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
|
|
5169
|
+
* a strip transcribed from a real document is worth storing even when it does not check out.
|
|
5170
|
+
*
|
|
5171
|
+
* @layer preset
|
|
5172
|
+
* @base string
|
|
5173
|
+
* @prim text
|
|
5174
|
+
* @widget mrz
|
|
5175
|
+
* @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
|
|
5176
|
+
*/
|
|
5177
|
+
function mrz(o) {
|
|
5178
|
+
return declare("mrz", o);
|
|
5179
|
+
}
|
|
5180
|
+
registerPreset("mrz", "string", {}, {
|
|
5181
|
+
kind: "mrz",
|
|
5182
|
+
widget: "mrz",
|
|
5183
|
+
build() {
|
|
5184
|
+
return {
|
|
5185
|
+
column: "text",
|
|
5186
|
+
zod: string$1(),
|
|
5187
|
+
hints: {}
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5190
|
+
});
|
|
5110
5191
|
var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
|
5111
5192
|
/**
|
|
5112
5193
|
* Hex color — kind 'color', `#rrggbb` + swatch widget.
|
|
@@ -5146,10 +5227,10 @@ function colorname(o) {
|
|
|
5146
5227
|
registerPreset("colorname", "string", {}, {
|
|
5147
5228
|
kind: "colorname",
|
|
5148
5229
|
widget: "colorname",
|
|
5149
|
-
build(
|
|
5230
|
+
build() {
|
|
5150
5231
|
return {
|
|
5151
5232
|
column: "text",
|
|
5152
|
-
zod:
|
|
5233
|
+
zod: string$1().refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
|
|
5153
5234
|
hints: {}
|
|
5154
5235
|
};
|
|
5155
5236
|
}
|
|
@@ -5169,10 +5250,10 @@ function title(o) {
|
|
|
5169
5250
|
registerPreset("title", "string", {}, {
|
|
5170
5251
|
kind: "title",
|
|
5171
5252
|
widget: "title",
|
|
5172
|
-
build(
|
|
5253
|
+
build() {
|
|
5173
5254
|
return {
|
|
5174
5255
|
column: "text",
|
|
5175
|
-
zod:
|
|
5256
|
+
zod: string$1(),
|
|
5176
5257
|
hints: {}
|
|
5177
5258
|
};
|
|
5178
5259
|
}
|
|
@@ -5193,12 +5274,10 @@ function link(o) {
|
|
|
5193
5274
|
registerPreset("link", "string", {}, {
|
|
5194
5275
|
kind: "link",
|
|
5195
5276
|
widget: "link",
|
|
5196
|
-
build(
|
|
5197
|
-
const required = o.required ?? false;
|
|
5198
|
-
const base_z = string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) });
|
|
5277
|
+
build() {
|
|
5199
5278
|
return {
|
|
5200
5279
|
column: "text",
|
|
5201
|
-
zod:
|
|
5280
|
+
zod: string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) }),
|
|
5202
5281
|
hints: {
|
|
5203
5282
|
format: "url",
|
|
5204
5283
|
inputType: "url"
|
|
@@ -5391,6 +5470,38 @@ registerPreset("tag", "string", {}, {
|
|
|
5391
5470
|
}
|
|
5392
5471
|
});
|
|
5393
5472
|
/**
|
|
5473
|
+
* A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
|
|
5474
|
+
* `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
|
|
5475
|
+
* sequence — the plurality is the type, not a modifier someone remembered to add.
|
|
5476
|
+
*
|
|
5477
|
+
* Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
|
|
5478
|
+
* an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
|
|
5479
|
+
* containment — the only thing the value actually carries — is lost.
|
|
5480
|
+
*
|
|
5481
|
+
* @layer preset
|
|
5482
|
+
* @base string
|
|
5483
|
+
* @prim text
|
|
5484
|
+
* @widget path
|
|
5485
|
+
* @example f.path({ label: 'things.storage_location.fields.path' })
|
|
5486
|
+
*/
|
|
5487
|
+
function path(o) {
|
|
5488
|
+
return declare("path", {
|
|
5489
|
+
...o,
|
|
5490
|
+
multiple: true
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
registerPreset("path", "string", {}, {
|
|
5494
|
+
kind: "path",
|
|
5495
|
+
widget: "path",
|
|
5496
|
+
build() {
|
|
5497
|
+
return {
|
|
5498
|
+
column: "text",
|
|
5499
|
+
zod: string$1(),
|
|
5500
|
+
hints: {}
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
});
|
|
5504
|
+
/**
|
|
5394
5505
|
* Tags — `tag({ multiple: true })`.
|
|
5395
5506
|
*
|
|
5396
5507
|
* @layer preset
|
|
@@ -5583,6 +5694,56 @@ registerPreset("reminder", "date", { lead: 30 }, {
|
|
|
5583
5694
|
};
|
|
5584
5695
|
}
|
|
5585
5696
|
});
|
|
5697
|
+
/** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
|
|
5698
|
+
* kind 'timetable', prim 'time', always `multiple`.
|
|
5699
|
+
*
|
|
5700
|
+
* Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
|
|
5701
|
+
* the values MEAN. Everything it shows beyond the times themselves — when the service starts
|
|
5702
|
+
* and ends, how many runs there are, which one is next — is derived from the values at render
|
|
5703
|
+
* time, never stored: a "next departure" is wrong the moment it is written down, exactly the
|
|
5704
|
+
* argument `f.age` already makes for an age.
|
|
5705
|
+
*
|
|
5706
|
+
* Storage is a plain multiple `time`, so a field can be switched to this from
|
|
5707
|
+
* `f.time({ multiple: true })` and back with no migration.
|
|
5708
|
+
*
|
|
5709
|
+
* @layer preset
|
|
5710
|
+
* @base time
|
|
5711
|
+
* @prim time
|
|
5712
|
+
* @widget timetable
|
|
5713
|
+
* @example f.timetable({ label: 'mod.fields.weekdays' })
|
|
5714
|
+
*/
|
|
5715
|
+
function timetable(o) {
|
|
5716
|
+
return declare("timetable", {
|
|
5717
|
+
...o,
|
|
5718
|
+
multiple: true
|
|
5719
|
+
});
|
|
5720
|
+
}
|
|
5721
|
+
registerPreset("timetable", "time", {}, {
|
|
5722
|
+
kind: "timetable",
|
|
5723
|
+
widget: "timetable"
|
|
5724
|
+
});
|
|
5725
|
+
/** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
|
|
5726
|
+
* kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
|
|
5727
|
+
* `mutate`-driven computed field, it must be right on every day that passes, not only the day
|
|
5728
|
+
* the record was last written, so this is display metadata over `date`, the same shape as
|
|
5729
|
+
* `f.reminder`'s own relative-to-today note.
|
|
5730
|
+
*
|
|
5731
|
+
* @layer preset
|
|
5732
|
+
* @base date
|
|
5733
|
+
* @prim date
|
|
5734
|
+
* @widget age
|
|
5735
|
+
* @example f.age({ label: 'mod.fields.birthDate' })
|
|
5736
|
+
*/
|
|
5737
|
+
function age(o) {
|
|
5738
|
+
return declare("age", o);
|
|
5739
|
+
}
|
|
5740
|
+
registerPreset("age", "date", {}, {
|
|
5741
|
+
kind: "age",
|
|
5742
|
+
widget: "age",
|
|
5743
|
+
build(o, parts) {
|
|
5744
|
+
return typeOf("date").build(o, parts);
|
|
5745
|
+
}
|
|
5746
|
+
});
|
|
5586
5747
|
/**
|
|
5587
5748
|
* Percentage 0..100 — real with rules:{min:0,max:100}.
|
|
5588
5749
|
*
|
|
@@ -5775,6 +5936,9 @@ var presets = {
|
|
|
5775
5936
|
email,
|
|
5776
5937
|
tel,
|
|
5777
5938
|
slug,
|
|
5939
|
+
path,
|
|
5940
|
+
identifier,
|
|
5941
|
+
mrz,
|
|
5778
5942
|
color,
|
|
5779
5943
|
colorname,
|
|
5780
5944
|
title,
|
|
@@ -5788,6 +5952,8 @@ var presets = {
|
|
|
5788
5952
|
rating,
|
|
5789
5953
|
duration,
|
|
5790
5954
|
reminder,
|
|
5955
|
+
age,
|
|
5956
|
+
timetable,
|
|
5791
5957
|
percent,
|
|
5792
5958
|
year,
|
|
5793
5959
|
weight,
|
|
@@ -5796,6 +5962,7 @@ var presets = {
|
|
|
5796
5962
|
currency,
|
|
5797
5963
|
money,
|
|
5798
5964
|
internalApiToken,
|
|
5965
|
+
internalIdentityLinks,
|
|
5799
5966
|
internalOauthGrants
|
|
5800
5967
|
};
|
|
5801
5968
|
//#endregion
|
|
@@ -6445,26 +6612,38 @@ function identity(opts) {
|
|
|
6445
6612
|
}
|
|
6446
6613
|
};
|
|
6447
6614
|
}
|
|
6448
|
-
/**
|
|
6615
|
+
/** One measure out of an optional max, from ONE OR MORE named sources, with an optional verdict.
|
|
6616
|
+
*
|
|
6617
|
+
* `source` takes several entries for the same reason `f.identity`'s `channels` does — it is a
|
|
6618
|
+
* role that holds a LIST, so `view.source` is an ordered list of names rather than one name.
|
|
6619
|
+
* Two ratings of the same film out of ten are one measure read twice, and drawing them apart is
|
|
6620
|
+
* what makes them incomparable: the eye has to carry the scale between two figures instead of
|
|
6621
|
+
* reading them against a shared one. `media/title` had exactly that, two `f.score` blocks of
|
|
6622
|
+
* `max: 10` held apart inside an `f.compare`, until this took the restriction off.
|
|
6623
|
+
*
|
|
6624
|
+
* NOT `multiple`. A multiple field is an anonymous array; these are NAMED sources, and the name
|
|
6625
|
+
* is what says which reading came from where. The two are different shapes and the block wants
|
|
6626
|
+
* this one.
|
|
6449
6627
|
*
|
|
6450
6628
|
* @layer block
|
|
6451
6629
|
* @base group
|
|
6452
6630
|
* @prim —
|
|
6453
6631
|
* @widget score
|
|
6454
|
-
* @example f.score({ source: {
|
|
6632
|
+
* @example f.score({ source: { imdb: f.real({ label: '…' }), tmdb: f.real({ label: '…' }) }, max: 10 })
|
|
6455
6633
|
*/
|
|
6456
6634
|
function score(opts) {
|
|
6457
|
-
const
|
|
6635
|
+
const names = Object.keys(opts.source ?? {});
|
|
6636
|
+
if (names.length === 0) throw new Error(`[field.score] slot 'source' expects at least one entry, got 0`);
|
|
6458
6637
|
const verdict = opts.verdict !== void 0 ? slot("score", "verdict", opts.verdict) : void 0;
|
|
6459
6638
|
return {
|
|
6460
6639
|
...group({
|
|
6461
6640
|
label: opts.label,
|
|
6462
|
-
fields: merge("score", source, verdict),
|
|
6641
|
+
fields: merge("score", opts.source, verdict),
|
|
6463
6642
|
ui: { kind: "score" }
|
|
6464
6643
|
}),
|
|
6465
6644
|
view: {
|
|
6466
6645
|
kind: "score",
|
|
6467
|
-
source:
|
|
6646
|
+
source: names,
|
|
6468
6647
|
max: opts.max,
|
|
6469
6648
|
verdict: verdict && Object.keys(verdict)[0]
|
|
6470
6649
|
}
|
|
@@ -6496,11 +6675,20 @@ function status(opts) {
|
|
|
6496
6675
|
}
|
|
6497
6676
|
/** A gauge value between an optional min and max, with an optional "of" total.
|
|
6498
6677
|
*
|
|
6678
|
+
* `direction` is opt-in: when given, the gauge decides in plain JavaScript whether its own
|
|
6679
|
+
* value crossed the bound that matters (`meterTone`, `blocks/meter.tsx`) and reaches for the
|
|
6680
|
+
* tone palette — the fill, the value text and the `of` companion all move together, never
|
|
6681
|
+
* colour alone (a glyph rides along, see the renderer's own comment). Omit it and nothing
|
|
6682
|
+
* about the gauge changes from today. A `'ceiling'` gauge with no declared `max` (config or
|
|
6683
|
+
* the source field's own hints) resolves it from `of`'s own value instead — a budget's cap is
|
|
6684
|
+
* a per-record field, never a compile-time constant.
|
|
6685
|
+
*
|
|
6499
6686
|
* @layer block
|
|
6500
6687
|
* @base group
|
|
6501
6688
|
* @prim —
|
|
6502
6689
|
* @widget meter
|
|
6503
6690
|
* @example f.meter({ source: { used: f.real({ label: '…' }) }, min: 0, max: 100 })
|
|
6691
|
+
* @example f.meter({ source: { spent: f.real({ label: '…' }) }, of: { budget: f.real({ label: '…' }) }, direction: 'ceiling' })
|
|
6504
6692
|
*/
|
|
6505
6693
|
function meter(opts) {
|
|
6506
6694
|
const source = slot("meter", "source", opts.source);
|
|
@@ -6516,7 +6704,8 @@ function meter(opts) {
|
|
|
6516
6704
|
source: Object.keys(source)[0],
|
|
6517
6705
|
min: opts.min,
|
|
6518
6706
|
max: opts.max,
|
|
6519
|
-
of: of && Object.keys(of)[0]
|
|
6707
|
+
of: of && Object.keys(of)[0],
|
|
6708
|
+
direction: opts.direction
|
|
6520
6709
|
}
|
|
6521
6710
|
};
|
|
6522
6711
|
}
|
|
@@ -6576,6 +6765,11 @@ function balance(opts) {
|
|
|
6576
6765
|
* @base group
|
|
6577
6766
|
* @prim —
|
|
6578
6767
|
* @widget route
|
|
6768
|
+
* A `stub` is the part of the ticket that is TORN OFF and kept — a seat, a gate, a booking
|
|
6769
|
+
* reference. Declaring one makes the block a ticket rather than a line: the two halves are
|
|
6770
|
+
* separated by a perforation, and the fields in the stub sit below it. Without one the block
|
|
6771
|
+
* is exactly what it was, a route from here to there, so no existing call changes.
|
|
6772
|
+
*
|
|
6579
6773
|
* @example f.route({ from: { from: f.string({ label: '…' }) }, to: { to: f.string({ label: '…' }) } })
|
|
6580
6774
|
*/
|
|
6581
6775
|
function route(opts) {
|
|
@@ -6584,9 +6778,10 @@ function route(opts) {
|
|
|
6584
6778
|
const depart = opts.depart !== void 0 ? slot("route", "depart", opts.depart) : void 0;
|
|
6585
6779
|
const arrive = opts.arrive !== void 0 ? slot("route", "arrive", opts.arrive) : void 0;
|
|
6586
6780
|
const duration = opts.duration !== void 0 ? slot("route", "duration", opts.duration) : void 0;
|
|
6781
|
+
const stub = opts.stub ?? {};
|
|
6587
6782
|
return {
|
|
6588
6783
|
...group({
|
|
6589
|
-
fields: merge("route", from, to, depart, arrive, duration),
|
|
6784
|
+
fields: merge("route", from, to, depart, arrive, duration, stub),
|
|
6590
6785
|
ui: { kind: "route" }
|
|
6591
6786
|
}),
|
|
6592
6787
|
view: {
|
|
@@ -6595,7 +6790,8 @@ function route(opts) {
|
|
|
6595
6790
|
to: Object.keys(to)[0],
|
|
6596
6791
|
depart: depart && Object.keys(depart)[0],
|
|
6597
6792
|
arrive: arrive && Object.keys(arrive)[0],
|
|
6598
|
-
duration: duration && Object.keys(duration)[0]
|
|
6793
|
+
duration: duration && Object.keys(duration)[0],
|
|
6794
|
+
stub: Object.keys(stub)
|
|
6599
6795
|
}
|
|
6600
6796
|
};
|
|
6601
6797
|
}
|
|
@@ -6773,6 +6969,81 @@ function specimen(opts) {
|
|
|
6773
6969
|
};
|
|
6774
6970
|
}
|
|
6775
6971
|
/**
|
|
6972
|
+
* Measurements against the range they were supposed to fall in, rendered from a single
|
|
6973
|
+
* collection source — see `stampCollection`. `analyte`, `value`, `unit`, `low` and `high` are
|
|
6974
|
+
* part KEYS inside each collection row, NOT `LayoutEl` positions — the same reasoning as
|
|
6975
|
+
* `manifest`'s `quantity`/`item`.
|
|
6976
|
+
*
|
|
6977
|
+
* The point is the COMPARISON. A `f.table` over the same rows prints the bounds as two more
|
|
6978
|
+
* columns and leaves the reader to do it; here each value sits on its own band with the
|
|
6979
|
+
* reference span marked on it, so "outside the range" is seen rather than worked out. That is
|
|
6980
|
+
* also the only thing on the row worth a colour — it is what the reader has to act on.
|
|
6981
|
+
*
|
|
6982
|
+
* `unit`, `low` and `high` are optional: a measurement with no published range (a culture, a
|
|
6983
|
+
* description) still belongs in the same list and simply gets no band.
|
|
6984
|
+
*
|
|
6985
|
+
* @layer block
|
|
6986
|
+
* @base group
|
|
6987
|
+
* @prim —
|
|
6988
|
+
* @widget assay
|
|
6989
|
+
* @example f.assay({ source: { results: f.group({ scope: 'nest', multiple: true, fields: { analyte: f.string({ label: '…' }), value: f.real({ label: '…' }) } }) }, analyte: 'analyte', value: 'value' })
|
|
6990
|
+
*/
|
|
6991
|
+
function assay(opts) {
|
|
6992
|
+
return stampCollection("assay", opts.source, opts.label, {
|
|
6993
|
+
kind: "assay",
|
|
6994
|
+
analyte: opts.analyte,
|
|
6995
|
+
value: opts.value,
|
|
6996
|
+
...opts.unit ? { unit: opts.unit } : {},
|
|
6997
|
+
...opts.low ? { low: opts.low } : {},
|
|
6998
|
+
...opts.high ? { high: opts.high } : {}
|
|
6999
|
+
});
|
|
7000
|
+
}
|
|
7001
|
+
/**
|
|
7002
|
+
* Value moving from one place to another, where the MOVEMENT is the subject — not one more
|
|
7003
|
+
* labelled row among the record's fields. The sum is set large in the machine voice, and the two
|
|
7004
|
+
* ends read as a path beneath it.
|
|
7005
|
+
*
|
|
7006
|
+
* amount — what moved. The one required role, and the reason the block exists
|
|
7007
|
+
* from — where it left. Absent on money that only arrived
|
|
7008
|
+
* to — where it arrived. Absent on money that only left
|
|
7009
|
+
* meta — the record's own remaining fields, under a rule: date, category, reference
|
|
7010
|
+
*
|
|
7011
|
+
* An absent end is not drawn, and that is the whole reading: money that left an account and
|
|
7012
|
+
* arrived nowhere IS an expense. It is read off which ends the record actually has, never
|
|
7013
|
+
* guessed from the shape of a value. What the movement MEANS beyond that — whether this
|
|
7014
|
+
* particular kind of transfer is good news — is carried by the classifier's own option `tone`,
|
|
7015
|
+
* the same declaration every other coloured value in the product uses.
|
|
7016
|
+
*
|
|
7017
|
+
* Distinct from `f.route`, which also has two ends: there the journey is the subject and the
|
|
7018
|
+
* ends are places, so it carries times and a duration; here the ends are accounts and the
|
|
7019
|
+
* subject is the quantity.
|
|
7020
|
+
*
|
|
7021
|
+
* @layer block
|
|
7022
|
+
* @base group
|
|
7023
|
+
* @prim —
|
|
7024
|
+
* @widget flow
|
|
7025
|
+
* @example f.flow({ amount: { amount: f.money({ label: '…' }) }, from: { source: f.relation({ label: '…' }) } })
|
|
7026
|
+
*/
|
|
7027
|
+
function flow(opts) {
|
|
7028
|
+
const amount = slot("flow", "amount", opts.amount);
|
|
7029
|
+
const from = opts.from !== void 0 ? slot("flow", "from", opts.from) : void 0;
|
|
7030
|
+
const to = opts.to !== void 0 ? slot("flow", "to", opts.to) : void 0;
|
|
7031
|
+
return {
|
|
7032
|
+
...group({
|
|
7033
|
+
label: opts.label,
|
|
7034
|
+
fields: merge("flow", amount, from, to, opts.meta),
|
|
7035
|
+
ui: { kind: "flow" }
|
|
7036
|
+
}),
|
|
7037
|
+
view: {
|
|
7038
|
+
kind: "flow",
|
|
7039
|
+
amount: Object.keys(amount)[0],
|
|
7040
|
+
from: from && Object.keys(from)[0],
|
|
7041
|
+
to: to && Object.keys(to)[0],
|
|
7042
|
+
meta: Object.keys(opts.meta ?? {})
|
|
7043
|
+
}
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7046
|
+
/**
|
|
6776
7047
|
* A packing/cargo manifest rendered from a single collection source — see `stampCollection`.
|
|
6777
7048
|
* `quantity` and `item` are part KEYS inside each collection row, NOT `LayoutEl` positions —
|
|
6778
7049
|
* the same reasoning as `journal`'s `date`/`text`.
|
|
@@ -6819,13 +7090,25 @@ function table(opts) {
|
|
|
6819
7090
|
} : {},
|
|
6820
7091
|
...opts.groupBy ? { groupBy: opts.groupBy } : {},
|
|
6821
7092
|
...opts.totals ? { totals: opts.totals } : {},
|
|
7093
|
+
...opts.summary ? { summary: opts.summary } : {},
|
|
6822
7094
|
...opts.numbered ? { numbered: true } : {}
|
|
6823
7095
|
});
|
|
6824
7096
|
}
|
|
6825
7097
|
/**
|
|
6826
|
-
* An
|
|
6827
|
-
*
|
|
6828
|
-
*
|
|
7098
|
+
* An identity document, drawn as the card it is: a caption and its number across the top, a
|
|
7099
|
+
* portrait beside the holder's own fields, the issuing details under a rule, and the
|
|
7100
|
+
* machine-readable strip at the foot. `view` names each role (Task 5) — see `masthead`'s own
|
|
7101
|
+
* doc comment for what an ordered name list means.
|
|
7102
|
+
*
|
|
7103
|
+
* overline — the caption at the top left ("PASSPORT", "DRIVING LICENCE")
|
|
7104
|
+
* number — the document's number, set apart at the top right
|
|
7105
|
+
* photo — the portrait. One field, rendered in a portrait frame beside the body
|
|
7106
|
+
* meta — the card's OWN fields, labelled, at each field's declared span
|
|
7107
|
+
* footer — the fields below the rule: issued, authority, record number
|
|
7108
|
+
* mrz — the machine-readable strip, set in monospace at the foot
|
|
7109
|
+
*
|
|
7110
|
+
* Every role is optional but `number`: a bank card has no `mrz`, a library card no `photo`,
|
|
7111
|
+
* and the card simply omits the part it was given nothing for.
|
|
6829
7112
|
*
|
|
6830
7113
|
* @layer block
|
|
6831
7114
|
* @base group
|
|
@@ -6836,16 +7119,21 @@ function table(opts) {
|
|
|
6836
7119
|
function idcard(opts) {
|
|
6837
7120
|
const overline = opts.overline !== void 0 ? slot("idcard", "overline", opts.overline) : void 0;
|
|
6838
7121
|
const number = slot("idcard", "number", opts.number);
|
|
7122
|
+
const photo = opts.photo !== void 0 ? slot("idcard", "photo", opts.photo) : void 0;
|
|
7123
|
+
const mrz = opts.mrz !== void 0 ? slot("idcard", "mrz", opts.mrz) : void 0;
|
|
6839
7124
|
return {
|
|
6840
7125
|
...group({
|
|
6841
|
-
fields: merge("idcard", overline, number, opts.meta),
|
|
7126
|
+
fields: merge("idcard", overline, number, photo, opts.meta, opts.footer, mrz),
|
|
6842
7127
|
ui: { kind: "idcard" }
|
|
6843
7128
|
}),
|
|
6844
7129
|
view: {
|
|
6845
7130
|
kind: "idcard",
|
|
6846
7131
|
overline: overline && Object.keys(overline)[0],
|
|
6847
7132
|
number: Object.keys(number)[0],
|
|
6848
|
-
|
|
7133
|
+
photo: photo && Object.keys(photo)[0],
|
|
7134
|
+
meta: Object.keys(opts.meta ?? {}),
|
|
7135
|
+
footer: Object.keys(opts.footer ?? {}),
|
|
7136
|
+
mrz: mrz && Object.keys(mrz)[0]
|
|
6849
7137
|
}
|
|
6850
7138
|
};
|
|
6851
7139
|
}
|
|
@@ -6930,6 +7218,8 @@ var blocks = {
|
|
|
6930
7218
|
nutrition,
|
|
6931
7219
|
specimen,
|
|
6932
7220
|
manifest,
|
|
7221
|
+
assay,
|
|
7222
|
+
flow,
|
|
6933
7223
|
table,
|
|
6934
7224
|
idcard,
|
|
6935
7225
|
properties,
|
|
@@ -7777,18 +8067,23 @@ function group(o) {
|
|
|
7777
8067
|
for (const f of fields) if (!("key" in f && f.key !== void 0) && !hasChildren(f)) throw new Error(`[field.group] a 'nest' group's children must be fields or groups, not layout elements`);
|
|
7778
8068
|
for (const k of r.unique ?? []) if (!fields.some((f) => "key" in f && f.key === k)) throw new Error(`[field.group] unique key '${k}' is not a subfield`);
|
|
7779
8069
|
} else if (o.value !== void 0) throw new Error(`[field.group] value requires scope 'nest' — a hoist group owns no children to write`);
|
|
8070
|
+
const title = v.title ?? [];
|
|
8071
|
+
if (title.length > 0) {
|
|
8072
|
+
if (!o.multiple) throw new Error(`[field.group] ui.title heads a collection's ROWS — it needs multiple: true`);
|
|
8073
|
+
for (const name of title) if (!fields.some((f) => "key" in f && f.key === name)) throw new Error(`[field.group] ui.title '${name}' is not one of its fields`);
|
|
8074
|
+
}
|
|
7780
8075
|
return {
|
|
7781
8076
|
el: "group",
|
|
7782
8077
|
scope,
|
|
7783
8078
|
multiple: o.multiple,
|
|
7784
8079
|
compute: o.value,
|
|
7785
|
-
required: o.required,
|
|
7786
8080
|
unique: r.unique,
|
|
7787
8081
|
label: o.label,
|
|
7788
8082
|
icon: o.icon,
|
|
7789
8083
|
display: v.display ?? "wrap",
|
|
7790
8084
|
kind: v.kind,
|
|
7791
8085
|
fixed: r.fixed,
|
|
8086
|
+
view: title.length > 0 ? { title } : void 0,
|
|
7792
8087
|
fields
|
|
7793
8088
|
};
|
|
7794
8089
|
}
|
|
@@ -7814,7 +8109,6 @@ function row(o) {
|
|
|
7814
8109
|
icon: o.icon,
|
|
7815
8110
|
fields: o.fields,
|
|
7816
8111
|
multiple: o.multiple,
|
|
7817
|
-
required: o.required,
|
|
7818
8112
|
rules: o.rules,
|
|
7819
8113
|
ui: { display: "scroll" }
|
|
7820
8114
|
});
|
|
@@ -7862,7 +8156,6 @@ function url(o) {
|
|
|
7862
8156
|
return group({
|
|
7863
8157
|
scope: "nest",
|
|
7864
8158
|
label: o.label,
|
|
7865
|
-
required: o.required,
|
|
7866
8159
|
ui: { kind: "url" },
|
|
7867
8160
|
fields: {
|
|
7868
8161
|
scheme: string({}),
|
|
@@ -7914,7 +8207,6 @@ function keyed(o) {
|
|
|
7914
8207
|
}),
|
|
7915
8208
|
scope: "nest",
|
|
7916
8209
|
multiple: true,
|
|
7917
|
-
required: o.required,
|
|
7918
8210
|
unique: o.unique ?? [role],
|
|
7919
8211
|
fixed: o.fixed,
|
|
7920
8212
|
view: { by: role }
|
|
@@ -7955,6 +8247,31 @@ function info(textKey) {
|
|
|
7955
8247
|
};
|
|
7956
8248
|
}
|
|
7957
8249
|
/**
|
|
8250
|
+
* What else points AT this record — the inverse of a relation, which is often the more useful
|
|
8251
|
+
* direction on a record page: a box is more usefully "what is in it" than "what it is in".
|
|
8252
|
+
*
|
|
8253
|
+
* Stores NOTHING and has no column: it declares where to look, and the server answers by asking
|
|
8254
|
+
* the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
|
|
8255
|
+
* a field — there is no value here to validate or save.
|
|
8256
|
+
*
|
|
8257
|
+
* `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
|
|
8258
|
+
* "everything that happens to point here": a shelf may point at the same target through two
|
|
8259
|
+
* fields — a transaction has a source account and a destination account — and a panel that
|
|
8260
|
+
* merged them would answer a question nobody asked.
|
|
8261
|
+
*
|
|
8262
|
+
* @layer primitive
|
|
8263
|
+
* @prim —
|
|
8264
|
+
* @widget backrefs
|
|
8265
|
+
* @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
|
|
8266
|
+
*/
|
|
8267
|
+
function backrefs(o) {
|
|
8268
|
+
return {
|
|
8269
|
+
el: "backrefs",
|
|
8270
|
+
label: o.label,
|
|
8271
|
+
from: o.from
|
|
8272
|
+
};
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
7958
8275
|
* Action button: invokes the handler registered in actionRegistry under the key `value`.
|
|
7959
8276
|
*
|
|
7960
8277
|
* @layer primitive
|
|
@@ -8010,7 +8327,7 @@ registerType("string", {
|
|
|
8010
8327
|
widget: "text",
|
|
8011
8328
|
build(o) {
|
|
8012
8329
|
const cfg = o.config ?? {};
|
|
8013
|
-
const min = cfg.min ??
|
|
8330
|
+
const min = cfg.min ?? 0;
|
|
8014
8331
|
return {
|
|
8015
8332
|
column: "text",
|
|
8016
8333
|
zod: stringContent((str) => {
|
|
@@ -8038,9 +8355,9 @@ registerType("string", {
|
|
|
8038
8355
|
function string(o) {
|
|
8039
8356
|
return declare("string", o);
|
|
8040
8357
|
}
|
|
8041
|
-
/** Shared by localDir/localFile's build(): a server path,
|
|
8042
|
-
function pathZod(
|
|
8043
|
-
return stringContent((s) =>
|
|
8358
|
+
/** Shared by localDir/localFile's build(): a server path, judged only when one was entered. */
|
|
8359
|
+
function pathZod(_o) {
|
|
8360
|
+
return stringContent((s) => s);
|
|
8044
8361
|
}
|
|
8045
8362
|
registerType("localDir", {
|
|
8046
8363
|
kind: "localDir",
|
|
@@ -8279,20 +8596,22 @@ registerType("time", {
|
|
|
8279
8596
|
prim: "time",
|
|
8280
8597
|
widget: "time",
|
|
8281
8598
|
build(o) {
|
|
8282
|
-
const granularity = o.config?.granularity ?? "
|
|
8599
|
+
const granularity = o.config?.granularity ?? "minute";
|
|
8600
|
+
const s = string$1(reqErr()).regex({
|
|
8601
|
+
hour: /^([01]\d|2[0-3])$/,
|
|
8602
|
+
minute: /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/,
|
|
8603
|
+
second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
|
8604
|
+
}[granularity], { message: vmsg("time_format") });
|
|
8283
8605
|
return {
|
|
8284
8606
|
column: "time",
|
|
8285
|
-
zod:
|
|
8286
|
-
hour: /^([01]\d|2[0-3])$/,
|
|
8287
|
-
minute: /^([01]\d|2[0-3]):[0-5]\d$/,
|
|
8288
|
-
second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
|
8289
|
-
}[granularity], { message: vmsg("time_format") }),
|
|
8607
|
+
zod: granularity === "minute" ? s.transform((v) => v.slice(0, 5)) : s,
|
|
8290
8608
|
hints: { granularity }
|
|
8291
8609
|
};
|
|
8292
8610
|
}
|
|
8293
8611
|
});
|
|
8294
8612
|
/**
|
|
8295
|
-
* Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
|
|
8613
|
+
* Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
|
|
8614
|
+
* 'minute' — the same default `f.datetime` has always had).
|
|
8296
8615
|
*
|
|
8297
8616
|
* @layer primitive
|
|
8298
8617
|
* @prim time
|
|
@@ -8697,10 +9016,9 @@ var MEASURED_ROLES = (opts) => {
|
|
|
8697
9016
|
accepts: ENUMERATED,
|
|
8698
9017
|
normalize: (d) => {
|
|
8699
9018
|
if (d && accepts(unitRole, d)) return d;
|
|
8700
|
-
const { label,
|
|
9019
|
+
const { label, value } = d?.opts ?? {};
|
|
8701
9020
|
return declare("string", {
|
|
8702
9021
|
label,
|
|
8703
|
-
required,
|
|
8704
9022
|
value,
|
|
8705
9023
|
noSearch: d?.opts.noSearch,
|
|
8706
9024
|
ui: uiOf(d?.opts),
|
|
@@ -8859,7 +9177,7 @@ registerType("geo", {
|
|
|
8859
9177
|
label: string$1().nullish()
|
|
8860
9178
|
}),
|
|
8861
9179
|
structureCode: "geo_structure",
|
|
8862
|
-
|
|
9180
|
+
acceptsEmptyRow: true
|
|
8863
9181
|
});
|
|
8864
9182
|
/**
|
|
8865
9183
|
* Geographic point composite {lat, lng, label?}; lat/lng validated to valid coordinate
|
|
@@ -8937,7 +9255,7 @@ registerType("illustrated", {
|
|
|
8937
9255
|
roles: ILLUSTRATED_ROLES,
|
|
8938
9256
|
roleZod: () => defaultRoleZod(ILLUSTRATED_ROLES),
|
|
8939
9257
|
structureCode: "illustrated_structure",
|
|
8940
|
-
|
|
9258
|
+
acceptsEmptyRow: true,
|
|
8941
9259
|
collectionView: "illustratedList",
|
|
8942
9260
|
hints: (o) => o.config ? { config: o.config } : {}
|
|
8943
9261
|
});
|
|
@@ -8981,7 +9299,7 @@ registerType("attachment", {
|
|
|
8981
9299
|
roles: ATTACHMENT_ROLES,
|
|
8982
9300
|
roleZod: () => defaultRoleZod(ATTACHMENT_ROLES),
|
|
8983
9301
|
structureCode: "attachment_structure",
|
|
8984
|
-
|
|
9302
|
+
acceptsEmptyRow: true,
|
|
8985
9303
|
collectionView: "attachmentList"
|
|
8986
9304
|
});
|
|
8987
9305
|
/**
|
|
@@ -9082,11 +9400,10 @@ function periodRole(key, label, mkSubDecl) {
|
|
|
9082
9400
|
if (d && accepts(def, d)) return d;
|
|
9083
9401
|
const canonical = mkSubDecl(label);
|
|
9084
9402
|
if (!d) return canonical;
|
|
9085
|
-
const { label: l,
|
|
9403
|
+
const { label: l, value } = d.opts;
|
|
9086
9404
|
return declare(canonical.factory, {
|
|
9087
9405
|
...canonical.opts,
|
|
9088
9406
|
label: l ?? canonical.opts.label,
|
|
9089
|
-
required,
|
|
9090
9407
|
value,
|
|
9091
9408
|
noSearch: d.opts.noSearch,
|
|
9092
9409
|
ui: uiOf(d.opts)
|
|
@@ -9370,8 +9687,7 @@ function keyValue(raw) {
|
|
|
9370
9687
|
type: valueType
|
|
9371
9688
|
}
|
|
9372
9689
|
},
|
|
9373
|
-
by: "key"
|
|
9374
|
-
required: o.required
|
|
9690
|
+
by: "key"
|
|
9375
9691
|
});
|
|
9376
9692
|
}
|
|
9377
9693
|
/** Roles of a range: the two endpoints, integer or real depending on `isInt`. */
|
|
@@ -9537,7 +9853,8 @@ var PRIMITIVES = {
|
|
|
9537
9853
|
url,
|
|
9538
9854
|
divider,
|
|
9539
9855
|
info,
|
|
9540
|
-
button
|
|
9856
|
+
button,
|
|
9857
|
+
backrefs
|
|
9541
9858
|
};
|
|
9542
9859
|
/** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
|
|
9543
9860
|
function composeF(presets, blocks) {
|
|
@@ -9557,12 +9874,11 @@ var field = new Proxy({}, {
|
|
|
9557
9874
|
has: (_t, k) => k in composedField()
|
|
9558
9875
|
});
|
|
9559
9876
|
function toClient(field) {
|
|
9560
|
-
const { kind, label, agent,
|
|
9877
|
+
const { kind, label, agent, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
|
|
9561
9878
|
return {
|
|
9562
9879
|
kind,
|
|
9563
9880
|
label,
|
|
9564
9881
|
...agent !== void 0 && { agent },
|
|
9565
|
-
required,
|
|
9566
9882
|
prim,
|
|
9567
9883
|
hints,
|
|
9568
9884
|
options,
|
|
@@ -9640,7 +9956,7 @@ function accepts(def, d) {
|
|
|
9640
9956
|
}
|
|
9641
9957
|
/**
|
|
9642
9958
|
* The correction every role gets for free: keep what the author said ABOUT the field
|
|
9643
|
-
* (`label`, `
|
|
9959
|
+
* (`label`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
|
|
9644
9960
|
* drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
|
|
9645
9961
|
* picture — and `multiple`, because a composite role owns exactly one column). A role whose
|
|
9646
9962
|
* own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
|
|
@@ -9648,10 +9964,9 @@ function accepts(def, d) {
|
|
|
9648
9964
|
*/
|
|
9649
9965
|
function retypeTo(factory) {
|
|
9650
9966
|
return (d) => {
|
|
9651
|
-
const { label,
|
|
9967
|
+
const { label, value } = d?.opts ?? {};
|
|
9652
9968
|
return declare(factory, {
|
|
9653
9969
|
label,
|
|
9654
|
-
required,
|
|
9655
9970
|
value,
|
|
9656
9971
|
noSearch: d?.opts.noSearch,
|
|
9657
9972
|
ui: uiOf(d?.opts)
|
|
@@ -9837,13 +10152,69 @@ function partColumns(parts) {
|
|
|
9837
10152
|
}
|
|
9838
10153
|
return cols;
|
|
9839
10154
|
}
|
|
9840
|
-
|
|
10155
|
+
/**
|
|
10156
|
+
* A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
|
|
10157
|
+
* the composite's DEFAULT schema per role — the literal the factory writes by hand
|
|
10158
|
+
* (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
|
|
10159
|
+
* schema guards its (role-named) key:
|
|
10160
|
+
*
|
|
10161
|
+
* - a plain STORED part is keyed by its role name, matching the value object and the
|
|
10162
|
+
* column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
|
|
10163
|
+
* to its default filling keeps the default schema — byte-identical to the hand-written
|
|
10164
|
+
* literal, so every existing structure/unit/range code and message is preserved.
|
|
10165
|
+
* - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
|
|
10166
|
+
* default schema for that role. This is what makes a RETYPED filling real: an
|
|
10167
|
+
* `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
|
|
10168
|
+
* and an `f.relation({…})` currency accepts a record id instead of being measured
|
|
10169
|
+
* against an ISO-4217 string rule it was never meant to satisfy. The filling also
|
|
10170
|
+
* brings its own optionality and its own min/max — an author replacing a filling
|
|
10171
|
+
* takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
|
|
10172
|
+
* replaced magnitude keeps a bound.
|
|
10173
|
+
* - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
|
|
10174
|
+
* other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
|
|
10175
|
+
* whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
|
|
10176
|
+
* moment later and the write path recomputes the column. It is stripped rather than
|
|
10177
|
+
* `z.never()`-rejected because a read hands the client that key (it is a real column,
|
|
10178
|
+
* present in every response), so rejecting it would break a read → PATCH-the-whole-object
|
|
10179
|
+
* round trip.
|
|
10180
|
+
* - a CLIENT-OWNED stored part of a composite also accepts
|
|
10181
|
+
* an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
|
|
10182
|
+
* for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
|
|
10183
|
+
* magnitude column is set and whose unit column is empty reads back as
|
|
10184
|
+
* `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
|
|
10185
|
+
* empty — `0` is a value), and feeding that exact object back used to fail with
|
|
10186
|
+
* `measured_structure`: the write path refused the shape its own read produced. The null
|
|
10187
|
+
* slot now parses through and is written back as NULL, so the round trip is closed and
|
|
10188
|
+
* clearing ONE role of a filled composite works instead of erroring.
|
|
10189
|
+
* NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
|
|
10190
|
+
* how "this record has no value there" arrives, while an ABSENT key is a writer that
|
|
10191
|
+
* never mentioned a role it owns — which stays the structure error it has always been
|
|
10192
|
+
* (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
|
|
10193
|
+
* all-or-nothing rule for a DECLARATION, and it is untouched.
|
|
10194
|
+
* A composite used to be able to override this: `required: true` kept every client-owned
|
|
10195
|
+
* slot MANDATORY, null included, so that "tolerate null" could not turn a demanded field
|
|
10196
|
+
* optional. Nothing demands a value now, so there is no override and the rule above is the
|
|
10197
|
+
* only one.
|
|
10198
|
+
*/
|
|
10199
|
+
/**
|
|
10200
|
+
* How much a CLIENT-OWNED slot forgives in the row shape: exactly one thing, an explicit
|
|
10201
|
+
* `null`. `.nullable()`, never `.nullish()` — a part that is MISSING entirely is still a
|
|
10202
|
+
* malformed row (`{lat: 5.5}` is half a coordinate), which is the one rule about composites
|
|
10203
|
+
* that judges CORRECTNESS rather than presence.
|
|
10204
|
+
*
|
|
10205
|
+
* There used to be a `RowTolerance` of three levels, and the field's `required` is what chose
|
|
10206
|
+
* between them: 'strict' when the field demanded a value, 'null-or-missing' when the value was
|
|
10207
|
+
* ABSENT under that demand — the third existing only so an absent required composite did not
|
|
10208
|
+
* report the same absence twice, once as `required` and once as a structure code. With nothing
|
|
10209
|
+
* able to demand a value there is no first issue to restate, so both levels and the type
|
|
10210
|
+
* itself are gone.
|
|
10211
|
+
*/
|
|
10212
|
+
function partsRowShape(shape, parts) {
|
|
9841
10213
|
const out = { ...shape };
|
|
9842
10214
|
for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
|
|
9843
10215
|
else {
|
|
9844
10216
|
const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
|
|
9845
|
-
|
|
9846
|
-
out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
|
|
10217
|
+
out[p.key] = own.nullable();
|
|
9847
10218
|
}
|
|
9848
10219
|
return out;
|
|
9849
10220
|
}
|
|
@@ -9872,7 +10243,7 @@ function stripServerOwnedParts(value, parts) {
|
|
|
9872
10243
|
* dimensions, check(single)) and differed only in the validation code and the per-role
|
|
9873
10244
|
* default zod.
|
|
9874
10245
|
*
|
|
9875
|
-
* raw → jsonValue →
|
|
10246
|
+
* raw → jsonValue → carries nothing? (passthrough, when `acceptsEmptyRow`)
|
|
9876
10247
|
* → carries nothing at all? (pass through, there is nothing to judge)
|
|
9877
10248
|
* → rowSchema (partsRowShape) → stripServerOwnedParts → optional per-type refine
|
|
9878
10249
|
*
|
|
@@ -9882,28 +10253,16 @@ function stripServerOwnedParts(value, parts) {
|
|
|
9882
10253
|
* exactly once.
|
|
9883
10254
|
*/
|
|
9884
10255
|
function buildComposite(def, opts, parts) {
|
|
9885
|
-
const
|
|
9886
|
-
const roleZod = def.roleZod(opts, parts);
|
|
9887
|
-
const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
|
|
9888
|
-
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
|
|
10256
|
+
const rowSchema = object(partsRowShape(def.roleZod(opts, parts), parts));
|
|
9889
10257
|
const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
|
|
9890
|
-
const
|
|
9891
|
-
const clientKeys = def.absenceRoles ? roleKeys() : void 0;
|
|
10258
|
+
const clientKeys = def.acceptsEmptyRow ? roleKeys() : void 0;
|
|
9892
10259
|
const zod = unknown().transform((raw, ctx) => {
|
|
9893
10260
|
const parsed = jsonValue(raw);
|
|
9894
10261
|
const p = parsed;
|
|
9895
10262
|
const isRow = p == null || typeof p === "object" && !Array.isArray(p);
|
|
9896
10263
|
/** Every one of `keys` empty in this value — `null`/absent alike. */
|
|
9897
10264
|
const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
|
|
9898
|
-
|
|
9899
|
-
if (absenceKeys && clientKeys) {
|
|
9900
|
-
absent = allEmpty(absenceKeys);
|
|
9901
|
-
if (absent && required) ctx.addIssue({
|
|
9902
|
-
code: ZodIssueCode.custom,
|
|
9903
|
-
message: vmsg("required")
|
|
9904
|
-
});
|
|
9905
|
-
if (allEmpty(clientKeys)) return raw;
|
|
9906
|
-
}
|
|
10265
|
+
if (clientKeys && allEmpty(clientKeys)) return raw;
|
|
9907
10266
|
if (typeof parsed === "string") {
|
|
9908
10267
|
ctx.addIssue({
|
|
9909
10268
|
code: ZodIssueCode.custom,
|
|
@@ -9911,7 +10270,7 @@ function buildComposite(def, opts, parts) {
|
|
|
9911
10270
|
});
|
|
9912
10271
|
return NEVER;
|
|
9913
10272
|
}
|
|
9914
|
-
const r =
|
|
10273
|
+
const r = rowSchema.safeParse(parsed);
|
|
9915
10274
|
if (!r.success) {
|
|
9916
10275
|
const message = typeof def.structureCode === "function" ? def.structureCode(r.error) : vmsg(def.structureCode);
|
|
9917
10276
|
ctx.addIssue({
|
|
@@ -9964,12 +10323,10 @@ function materialize(decl, name) {
|
|
|
9964
10323
|
if (isComposite(type) && (opts.multiple ?? false) && parts.length > 0 && !type.selfManages?.has("multiple")) return collectionGroup(name, opts, parts, type.collectionView);
|
|
9965
10324
|
const built = isComposite(type) ? buildComposite(type, opts, parts) : type.build(opts, parts, name);
|
|
9966
10325
|
const selfOptional = type.selfManages?.has("optional") ?? false;
|
|
9967
|
-
const required = selfOptional ? false : opts.required ?? false;
|
|
9968
10326
|
const base = {
|
|
9969
10327
|
kind: type.kind,
|
|
9970
10328
|
label: opts.label ?? "",
|
|
9971
10329
|
...opts.agent !== void 0 && { agent: opts.agent },
|
|
9972
|
-
required,
|
|
9973
10330
|
prim: type.prim,
|
|
9974
10331
|
column: built.column,
|
|
9975
10332
|
hints: built.hints ?? {},
|
|
@@ -9980,7 +10337,7 @@ function materialize(decl, name) {
|
|
|
9980
10337
|
columns: built.columns,
|
|
9981
10338
|
parts
|
|
9982
10339
|
},
|
|
9983
|
-
zod: selfOptional ? built.zod : optionalize(built.zod
|
|
10340
|
+
zod: selfOptional ? built.zod : optionalize(built.zod)
|
|
9984
10341
|
};
|
|
9985
10342
|
return wrapKey(name, opts, type.selfManages?.has("multiple") ? base : applyMultiple(base, opts.multiple ?? false));
|
|
9986
10343
|
}
|
|
@@ -10010,7 +10367,6 @@ function collectionGroup(key, opts, parts, collectionView) {
|
|
|
10010
10367
|
key,
|
|
10011
10368
|
scope: "nest",
|
|
10012
10369
|
multiple: true,
|
|
10013
|
-
required: opts.required ?? false,
|
|
10014
10370
|
label: opts.label ?? key,
|
|
10015
10371
|
...opts.agent !== void 0 && { agent: opts.agent },
|
|
10016
10372
|
display: "wrap",
|
|
@@ -10139,6 +10495,8 @@ var src_default = definePlugin({
|
|
|
10139
10495
|
id: "webchat",
|
|
10140
10496
|
version: "1.0.0",
|
|
10141
10497
|
dependsOn: [],
|
|
10498
|
+
label: "webchat.plugin.label",
|
|
10499
|
+
description: "webchat.plugin.description",
|
|
10142
10500
|
settings: defineSettings({
|
|
10143
10501
|
label: "webchat.settings.label",
|
|
10144
10502
|
fields: { reasoning_display: field.string({
|