@coffer-org/plugin-transit 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/bus_route/index.js +3 -3
- package/dist/index.js +5 -1
- package/dist/runtime/index.js +0 -4
- package/dist/schema.js +337 -28
- package/package.json +4 -4
package/dist/bus_route/index.js
CHANGED
|
@@ -15,9 +15,9 @@ export default defineShelf({
|
|
|
15
15
|
from: field.string({ label: 'transit.bus_route.fields.from', rules: { max: 100 }, ui: { span: 3 } }),
|
|
16
16
|
to: field.string({ label: 'transit.bus_route.fields.to', rules: { max: 100 }, ui: { span: 3 } }),
|
|
17
17
|
stop: field.string({ label: 'transit.bus_route.fields.stop', rules: { max: 200 }, ui: { span: 3 } }),
|
|
18
|
-
weekday_times: field.
|
|
19
|
-
saturday_times: field.
|
|
20
|
-
sunday_times: field.
|
|
18
|
+
weekday_times: field.timetable({ label: 'transit.bus_route.fields.weekday_times', ui: { span: 12 } }),
|
|
19
|
+
saturday_times: field.timetable({ label: 'transit.bus_route.fields.saturday_times', ui: { span: 6 } }),
|
|
20
|
+
sunday_times: field.timetable({ label: 'transit.bus_route.fields.sunday_times', ui: { span: 6 } }),
|
|
21
21
|
tags: field.tags({ label: 'transit.bus_route.fields.tags' }),
|
|
22
22
|
notes: field.text({ label: 'transit.bus_route.fields.notes', rules: { max: 500 } }),
|
|
23
23
|
},
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,11 @@ export default definePlugin({
|
|
|
13
13
|
id: 'transit',
|
|
14
14
|
label: 'transit.library.label',
|
|
15
15
|
icon: 'lucide:bus',
|
|
16
|
-
agent:
|
|
16
|
+
agent: {
|
|
17
|
+
description: 'Local bus routes and their departure schedules (bus_route).',
|
|
18
|
+
instructions: 'bus_route: weekday_times/saturday_times/sunday_times — departure schedule.' +
|
|
19
|
+
' When the schedule changes, update *_times via update_record.',
|
|
20
|
+
},
|
|
17
21
|
starterHint: 'the next departures on the saved routes',
|
|
18
22
|
}),
|
|
19
23
|
shelves: [bus_route],
|
package/dist/runtime/index.js
CHANGED
package/dist/schema.js
CHANGED
|
@@ -4701,6 +4701,13 @@ function wrapKey(key, opts, meta) {
|
|
|
4701
4701
|
span: opts.span
|
|
4702
4702
|
}
|
|
4703
4703
|
};
|
|
4704
|
+
if (opts.faces !== void 0) m = {
|
|
4705
|
+
...m,
|
|
4706
|
+
hints: {
|
|
4707
|
+
...m.hints,
|
|
4708
|
+
faces: opts.faces
|
|
4709
|
+
}
|
|
4710
|
+
};
|
|
4704
4711
|
if (opts.default !== void 0) m = {
|
|
4705
4712
|
...m,
|
|
4706
4713
|
default: opts.default
|
|
@@ -4800,6 +4807,7 @@ function normalizeOpts(rawIn) {
|
|
|
4800
4807
|
...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
|
|
4801
4808
|
compareWith: v.compareWith,
|
|
4802
4809
|
span: v.span,
|
|
4810
|
+
faces: v.faces,
|
|
4803
4811
|
emphasis: v.emphasis,
|
|
4804
4812
|
noLabel: v.noLabel,
|
|
4805
4813
|
role: v.role,
|
|
@@ -5708,6 +5716,63 @@ registerPreset("slug", "string", {}, {
|
|
|
5708
5716
|
};
|
|
5709
5717
|
}
|
|
5710
5718
|
});
|
|
5719
|
+
/**
|
|
5720
|
+
* Identifier — a code printed for a machine and read back by one: a serial number, an order
|
|
5721
|
+
* number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
|
|
5722
|
+
* why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
|
|
5723
|
+
* is one word on any string now, but the click that copies belongs to the kind.
|
|
5724
|
+
*
|
|
5725
|
+
* Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
|
|
5726
|
+
* it, and a pattern here would reject the next manufacturer's.
|
|
5727
|
+
*
|
|
5728
|
+
* @layer preset
|
|
5729
|
+
* @base string
|
|
5730
|
+
* @prim text
|
|
5731
|
+
* @widget identifier
|
|
5732
|
+
* @example f.identifier({ label: 'mod.fields.serialNumber' })
|
|
5733
|
+
*/
|
|
5734
|
+
function identifier(o) {
|
|
5735
|
+
return declare("identifier", o);
|
|
5736
|
+
}
|
|
5737
|
+
registerPreset("identifier", "string", {}, {
|
|
5738
|
+
kind: "identifier",
|
|
5739
|
+
widget: "identifier",
|
|
5740
|
+
build() {
|
|
5741
|
+
return {
|
|
5742
|
+
column: "text",
|
|
5743
|
+
zod: string$1(),
|
|
5744
|
+
hints: {}
|
|
5745
|
+
};
|
|
5746
|
+
}
|
|
5747
|
+
});
|
|
5748
|
+
/**
|
|
5749
|
+
* The machine-readable zone of a travel document — the identifier taken to its limit: printed
|
|
5750
|
+
* for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
|
|
5751
|
+
* line breaks preserved, because an MRZ's line structure is part of what it encodes.
|
|
5752
|
+
*
|
|
5753
|
+
* Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
|
|
5754
|
+
* a strip transcribed from a real document is worth storing even when it does not check out.
|
|
5755
|
+
*
|
|
5756
|
+
* @layer preset
|
|
5757
|
+
* @base string
|
|
5758
|
+
* @prim text
|
|
5759
|
+
* @widget mrz
|
|
5760
|
+
* @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
|
|
5761
|
+
*/
|
|
5762
|
+
function mrz(o) {
|
|
5763
|
+
return declare("mrz", o);
|
|
5764
|
+
}
|
|
5765
|
+
registerPreset("mrz", "string", {}, {
|
|
5766
|
+
kind: "mrz",
|
|
5767
|
+
widget: "mrz",
|
|
5768
|
+
build() {
|
|
5769
|
+
return {
|
|
5770
|
+
column: "text",
|
|
5771
|
+
zod: string$1(),
|
|
5772
|
+
hints: {}
|
|
5773
|
+
};
|
|
5774
|
+
}
|
|
5775
|
+
});
|
|
5711
5776
|
var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
|
5712
5777
|
/**
|
|
5713
5778
|
* Hex color — kind 'color', `#rrggbb` + swatch widget.
|
|
@@ -5992,6 +6057,38 @@ registerPreset("tag", "string", {}, {
|
|
|
5992
6057
|
}
|
|
5993
6058
|
});
|
|
5994
6059
|
/**
|
|
6060
|
+
* A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
|
|
6061
|
+
* `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
|
|
6062
|
+
* sequence — the plurality is the type, not a modifier someone remembered to add.
|
|
6063
|
+
*
|
|
6064
|
+
* Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
|
|
6065
|
+
* an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
|
|
6066
|
+
* containment — the only thing the value actually carries — is lost.
|
|
6067
|
+
*
|
|
6068
|
+
* @layer preset
|
|
6069
|
+
* @base string
|
|
6070
|
+
* @prim text
|
|
6071
|
+
* @widget path
|
|
6072
|
+
* @example f.path({ label: 'things.storage_location.fields.path' })
|
|
6073
|
+
*/
|
|
6074
|
+
function path(o) {
|
|
6075
|
+
return declare("path", {
|
|
6076
|
+
...o,
|
|
6077
|
+
multiple: true
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
6080
|
+
registerPreset("path", "string", {}, {
|
|
6081
|
+
kind: "path",
|
|
6082
|
+
widget: "path",
|
|
6083
|
+
build() {
|
|
6084
|
+
return {
|
|
6085
|
+
column: "text",
|
|
6086
|
+
zod: string$1(),
|
|
6087
|
+
hints: {}
|
|
6088
|
+
};
|
|
6089
|
+
}
|
|
6090
|
+
});
|
|
6091
|
+
/**
|
|
5995
6092
|
* Tags — `tag({ multiple: true })`.
|
|
5996
6093
|
*
|
|
5997
6094
|
* @layer preset
|
|
@@ -6184,6 +6281,56 @@ registerPreset("reminder", "date", { lead: 30 }, {
|
|
|
6184
6281
|
};
|
|
6185
6282
|
}
|
|
6186
6283
|
});
|
|
6284
|
+
/** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
|
|
6285
|
+
* kind 'timetable', prim 'time', always `multiple`.
|
|
6286
|
+
*
|
|
6287
|
+
* Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
|
|
6288
|
+
* the values MEAN. Everything it shows beyond the times themselves — when the service starts
|
|
6289
|
+
* and ends, how many runs there are, which one is next — is derived from the values at render
|
|
6290
|
+
* time, never stored: a "next departure" is wrong the moment it is written down, exactly the
|
|
6291
|
+
* argument `f.age` already makes for an age.
|
|
6292
|
+
*
|
|
6293
|
+
* Storage is a plain multiple `time`, so a field can be switched to this from
|
|
6294
|
+
* `f.time({ multiple: true })` and back with no migration.
|
|
6295
|
+
*
|
|
6296
|
+
* @layer preset
|
|
6297
|
+
* @base time
|
|
6298
|
+
* @prim time
|
|
6299
|
+
* @widget timetable
|
|
6300
|
+
* @example f.timetable({ label: 'mod.fields.weekdays' })
|
|
6301
|
+
*/
|
|
6302
|
+
function timetable(o) {
|
|
6303
|
+
return declare("timetable", {
|
|
6304
|
+
...o,
|
|
6305
|
+
multiple: true
|
|
6306
|
+
});
|
|
6307
|
+
}
|
|
6308
|
+
registerPreset("timetable", "time", {}, {
|
|
6309
|
+
kind: "timetable",
|
|
6310
|
+
widget: "timetable"
|
|
6311
|
+
});
|
|
6312
|
+
/** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
|
|
6313
|
+
* kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
|
|
6314
|
+
* `mutate`-driven computed field, it must be right on every day that passes, not only the day
|
|
6315
|
+
* the record was last written, so this is display metadata over `date`, the same shape as
|
|
6316
|
+
* `f.reminder`'s own relative-to-today note.
|
|
6317
|
+
*
|
|
6318
|
+
* @layer preset
|
|
6319
|
+
* @base date
|
|
6320
|
+
* @prim date
|
|
6321
|
+
* @widget age
|
|
6322
|
+
* @example f.age({ label: 'mod.fields.birthDate' })
|
|
6323
|
+
*/
|
|
6324
|
+
function age(o) {
|
|
6325
|
+
return declare("age", o);
|
|
6326
|
+
}
|
|
6327
|
+
registerPreset("age", "date", {}, {
|
|
6328
|
+
kind: "age",
|
|
6329
|
+
widget: "age",
|
|
6330
|
+
build(o, parts) {
|
|
6331
|
+
return typeOf("date").build(o, parts);
|
|
6332
|
+
}
|
|
6333
|
+
});
|
|
6187
6334
|
/**
|
|
6188
6335
|
* Percentage 0..100 — real with rules:{min:0,max:100}.
|
|
6189
6336
|
*
|
|
@@ -6376,6 +6523,9 @@ var presets = {
|
|
|
6376
6523
|
email,
|
|
6377
6524
|
tel,
|
|
6378
6525
|
slug,
|
|
6526
|
+
path,
|
|
6527
|
+
identifier,
|
|
6528
|
+
mrz,
|
|
6379
6529
|
color,
|
|
6380
6530
|
colorname,
|
|
6381
6531
|
title,
|
|
@@ -6389,6 +6539,8 @@ var presets = {
|
|
|
6389
6539
|
rating,
|
|
6390
6540
|
duration,
|
|
6391
6541
|
reminder,
|
|
6542
|
+
age,
|
|
6543
|
+
timetable,
|
|
6392
6544
|
percent,
|
|
6393
6545
|
year,
|
|
6394
6546
|
weight,
|
|
@@ -7046,26 +7198,38 @@ function identity(opts) {
|
|
|
7046
7198
|
}
|
|
7047
7199
|
};
|
|
7048
7200
|
}
|
|
7049
|
-
/**
|
|
7201
|
+
/** One measure out of an optional max, from ONE OR MORE named sources, with an optional verdict.
|
|
7202
|
+
*
|
|
7203
|
+
* `source` takes several entries for the same reason `f.identity`'s `channels` does — it is a
|
|
7204
|
+
* role that holds a LIST, so `view.source` is an ordered list of names rather than one name.
|
|
7205
|
+
* Two ratings of the same film out of ten are one measure read twice, and drawing them apart is
|
|
7206
|
+
* what makes them incomparable: the eye has to carry the scale between two figures instead of
|
|
7207
|
+
* reading them against a shared one. `media/title` had exactly that, two `f.score` blocks of
|
|
7208
|
+
* `max: 10` held apart inside an `f.compare`, until this took the restriction off.
|
|
7209
|
+
*
|
|
7210
|
+
* NOT `multiple`. A multiple field is an anonymous array; these are NAMED sources, and the name
|
|
7211
|
+
* is what says which reading came from where. The two are different shapes and the block wants
|
|
7212
|
+
* this one.
|
|
7050
7213
|
*
|
|
7051
7214
|
* @layer block
|
|
7052
7215
|
* @base group
|
|
7053
7216
|
* @prim —
|
|
7054
7217
|
* @widget score
|
|
7055
|
-
* @example f.score({ source: {
|
|
7218
|
+
* @example f.score({ source: { imdb: f.real({ label: '…' }), tmdb: f.real({ label: '…' }) }, max: 10 })
|
|
7056
7219
|
*/
|
|
7057
7220
|
function score(opts) {
|
|
7058
|
-
const
|
|
7221
|
+
const names = Object.keys(opts.source ?? {});
|
|
7222
|
+
if (names.length === 0) throw new Error(`[field.score] slot 'source' expects at least one entry, got 0`);
|
|
7059
7223
|
const verdict = opts.verdict !== void 0 ? slot("score", "verdict", opts.verdict) : void 0;
|
|
7060
7224
|
return {
|
|
7061
7225
|
...group({
|
|
7062
7226
|
label: opts.label,
|
|
7063
|
-
fields: merge("score", source, verdict),
|
|
7227
|
+
fields: merge("score", opts.source, verdict),
|
|
7064
7228
|
ui: { kind: "score" }
|
|
7065
7229
|
}),
|
|
7066
7230
|
view: {
|
|
7067
7231
|
kind: "score",
|
|
7068
|
-
source:
|
|
7232
|
+
source: names,
|
|
7069
7233
|
max: opts.max,
|
|
7070
7234
|
verdict: verdict && Object.keys(verdict)[0]
|
|
7071
7235
|
}
|
|
@@ -7097,11 +7261,20 @@ function status(opts) {
|
|
|
7097
7261
|
}
|
|
7098
7262
|
/** A gauge value between an optional min and max, with an optional "of" total.
|
|
7099
7263
|
*
|
|
7264
|
+
* `direction` is opt-in: when given, the gauge decides in plain JavaScript whether its own
|
|
7265
|
+
* value crossed the bound that matters (`meterTone`, `blocks/meter.tsx`) and reaches for the
|
|
7266
|
+
* tone palette — the fill, the value text and the `of` companion all move together, never
|
|
7267
|
+
* colour alone (a glyph rides along, see the renderer's own comment). Omit it and nothing
|
|
7268
|
+
* about the gauge changes from today. A `'ceiling'` gauge with no declared `max` (config or
|
|
7269
|
+
* the source field's own hints) resolves it from `of`'s own value instead — a budget's cap is
|
|
7270
|
+
* a per-record field, never a compile-time constant.
|
|
7271
|
+
*
|
|
7100
7272
|
* @layer block
|
|
7101
7273
|
* @base group
|
|
7102
7274
|
* @prim —
|
|
7103
7275
|
* @widget meter
|
|
7104
7276
|
* @example f.meter({ source: { used: f.real({ label: '…' }) }, min: 0, max: 100 })
|
|
7277
|
+
* @example f.meter({ source: { spent: f.real({ label: '…' }) }, of: { budget: f.real({ label: '…' }) }, direction: 'ceiling' })
|
|
7105
7278
|
*/
|
|
7106
7279
|
function meter(opts) {
|
|
7107
7280
|
const source = slot("meter", "source", opts.source);
|
|
@@ -7117,7 +7290,8 @@ function meter(opts) {
|
|
|
7117
7290
|
source: Object.keys(source)[0],
|
|
7118
7291
|
min: opts.min,
|
|
7119
7292
|
max: opts.max,
|
|
7120
|
-
of: of && Object.keys(of)[0]
|
|
7293
|
+
of: of && Object.keys(of)[0],
|
|
7294
|
+
direction: opts.direction
|
|
7121
7295
|
}
|
|
7122
7296
|
};
|
|
7123
7297
|
}
|
|
@@ -7177,6 +7351,11 @@ function balance(opts) {
|
|
|
7177
7351
|
* @base group
|
|
7178
7352
|
* @prim —
|
|
7179
7353
|
* @widget route
|
|
7354
|
+
* A `stub` is the part of the ticket that is TORN OFF and kept — a seat, a gate, a booking
|
|
7355
|
+
* reference. Declaring one makes the block a ticket rather than a line: the two halves are
|
|
7356
|
+
* separated by a perforation, and the fields in the stub sit below it. Without one the block
|
|
7357
|
+
* is exactly what it was, a route from here to there, so no existing call changes.
|
|
7358
|
+
*
|
|
7180
7359
|
* @example f.route({ from: { from: f.string({ label: '…' }) }, to: { to: f.string({ label: '…' }) } })
|
|
7181
7360
|
*/
|
|
7182
7361
|
function route(opts) {
|
|
@@ -7185,9 +7364,10 @@ function route(opts) {
|
|
|
7185
7364
|
const depart = opts.depart !== void 0 ? slot("route", "depart", opts.depart) : void 0;
|
|
7186
7365
|
const arrive = opts.arrive !== void 0 ? slot("route", "arrive", opts.arrive) : void 0;
|
|
7187
7366
|
const duration = opts.duration !== void 0 ? slot("route", "duration", opts.duration) : void 0;
|
|
7367
|
+
const stub = opts.stub ?? {};
|
|
7188
7368
|
return {
|
|
7189
7369
|
...group({
|
|
7190
|
-
fields: merge("route", from, to, depart, arrive, duration),
|
|
7370
|
+
fields: merge("route", from, to, depart, arrive, duration, stub),
|
|
7191
7371
|
ui: { kind: "route" }
|
|
7192
7372
|
}),
|
|
7193
7373
|
view: {
|
|
@@ -7196,7 +7376,8 @@ function route(opts) {
|
|
|
7196
7376
|
to: Object.keys(to)[0],
|
|
7197
7377
|
depart: depart && Object.keys(depart)[0],
|
|
7198
7378
|
arrive: arrive && Object.keys(arrive)[0],
|
|
7199
|
-
duration: duration && Object.keys(duration)[0]
|
|
7379
|
+
duration: duration && Object.keys(duration)[0],
|
|
7380
|
+
stub: Object.keys(stub)
|
|
7200
7381
|
}
|
|
7201
7382
|
};
|
|
7202
7383
|
}
|
|
@@ -7374,6 +7555,81 @@ function specimen(opts) {
|
|
|
7374
7555
|
};
|
|
7375
7556
|
}
|
|
7376
7557
|
/**
|
|
7558
|
+
* Measurements against the range they were supposed to fall in, rendered from a single
|
|
7559
|
+
* collection source — see `stampCollection`. `analyte`, `value`, `unit`, `low` and `high` are
|
|
7560
|
+
* part KEYS inside each collection row, NOT `LayoutEl` positions — the same reasoning as
|
|
7561
|
+
* `manifest`'s `quantity`/`item`.
|
|
7562
|
+
*
|
|
7563
|
+
* The point is the COMPARISON. A `f.table` over the same rows prints the bounds as two more
|
|
7564
|
+
* columns and leaves the reader to do it; here each value sits on its own band with the
|
|
7565
|
+
* reference span marked on it, so "outside the range" is seen rather than worked out. That is
|
|
7566
|
+
* also the only thing on the row worth a colour — it is what the reader has to act on.
|
|
7567
|
+
*
|
|
7568
|
+
* `unit`, `low` and `high` are optional: a measurement with no published range (a culture, a
|
|
7569
|
+
* description) still belongs in the same list and simply gets no band.
|
|
7570
|
+
*
|
|
7571
|
+
* @layer block
|
|
7572
|
+
* @base group
|
|
7573
|
+
* @prim —
|
|
7574
|
+
* @widget assay
|
|
7575
|
+
* @example f.assay({ source: { results: f.group({ scope: 'nest', multiple: true, fields: { analyte: f.string({ label: '…' }), value: f.real({ label: '…' }) } }) }, analyte: 'analyte', value: 'value' })
|
|
7576
|
+
*/
|
|
7577
|
+
function assay(opts) {
|
|
7578
|
+
return stampCollection("assay", opts.source, opts.label, {
|
|
7579
|
+
kind: "assay",
|
|
7580
|
+
analyte: opts.analyte,
|
|
7581
|
+
value: opts.value,
|
|
7582
|
+
...opts.unit ? { unit: opts.unit } : {},
|
|
7583
|
+
...opts.low ? { low: opts.low } : {},
|
|
7584
|
+
...opts.high ? { high: opts.high } : {}
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
/**
|
|
7588
|
+
* Value moving from one place to another, where the MOVEMENT is the subject — not one more
|
|
7589
|
+
* labelled row among the record's fields. The sum is set large in the machine voice, and the two
|
|
7590
|
+
* ends read as a path beneath it.
|
|
7591
|
+
*
|
|
7592
|
+
* amount — what moved. The one required role, and the reason the block exists
|
|
7593
|
+
* from — where it left. Absent on money that only arrived
|
|
7594
|
+
* to — where it arrived. Absent on money that only left
|
|
7595
|
+
* meta — the record's own remaining fields, under a rule: date, category, reference
|
|
7596
|
+
*
|
|
7597
|
+
* An absent end is not drawn, and that is the whole reading: money that left an account and
|
|
7598
|
+
* arrived nowhere IS an expense. It is read off which ends the record actually has, never
|
|
7599
|
+
* guessed from the shape of a value. What the movement MEANS beyond that — whether this
|
|
7600
|
+
* particular kind of transfer is good news — is carried by the classifier's own option `tone`,
|
|
7601
|
+
* the same declaration every other coloured value in the product uses.
|
|
7602
|
+
*
|
|
7603
|
+
* Distinct from `f.route`, which also has two ends: there the journey is the subject and the
|
|
7604
|
+
* ends are places, so it carries times and a duration; here the ends are accounts and the
|
|
7605
|
+
* subject is the quantity.
|
|
7606
|
+
*
|
|
7607
|
+
* @layer block
|
|
7608
|
+
* @base group
|
|
7609
|
+
* @prim —
|
|
7610
|
+
* @widget flow
|
|
7611
|
+
* @example f.flow({ amount: { amount: f.money({ label: '…' }) }, from: { source: f.relation({ label: '…' }) } })
|
|
7612
|
+
*/
|
|
7613
|
+
function flow(opts) {
|
|
7614
|
+
const amount = slot("flow", "amount", opts.amount);
|
|
7615
|
+
const from = opts.from !== void 0 ? slot("flow", "from", opts.from) : void 0;
|
|
7616
|
+
const to = opts.to !== void 0 ? slot("flow", "to", opts.to) : void 0;
|
|
7617
|
+
return {
|
|
7618
|
+
...group({
|
|
7619
|
+
label: opts.label,
|
|
7620
|
+
fields: merge("flow", amount, from, to, opts.meta),
|
|
7621
|
+
ui: { kind: "flow" }
|
|
7622
|
+
}),
|
|
7623
|
+
view: {
|
|
7624
|
+
kind: "flow",
|
|
7625
|
+
amount: Object.keys(amount)[0],
|
|
7626
|
+
from: from && Object.keys(from)[0],
|
|
7627
|
+
to: to && Object.keys(to)[0],
|
|
7628
|
+
meta: Object.keys(opts.meta ?? {})
|
|
7629
|
+
}
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
/**
|
|
7377
7633
|
* A packing/cargo manifest rendered from a single collection source — see `stampCollection`.
|
|
7378
7634
|
* `quantity` and `item` are part KEYS inside each collection row, NOT `LayoutEl` positions —
|
|
7379
7635
|
* the same reasoning as `journal`'s `date`/`text`.
|
|
@@ -7420,13 +7676,25 @@ function table(opts) {
|
|
|
7420
7676
|
} : {},
|
|
7421
7677
|
...opts.groupBy ? { groupBy: opts.groupBy } : {},
|
|
7422
7678
|
...opts.totals ? { totals: opts.totals } : {},
|
|
7679
|
+
...opts.summary ? { summary: opts.summary } : {},
|
|
7423
7680
|
...opts.numbered ? { numbered: true } : {}
|
|
7424
7681
|
});
|
|
7425
7682
|
}
|
|
7426
7683
|
/**
|
|
7427
|
-
* An
|
|
7428
|
-
*
|
|
7429
|
-
*
|
|
7684
|
+
* An identity document, drawn as the card it is: a caption and its number across the top, a
|
|
7685
|
+
* portrait beside the holder's own fields, the issuing details under a rule, and the
|
|
7686
|
+
* machine-readable strip at the foot. `view` names each role (Task 5) — see `masthead`'s own
|
|
7687
|
+
* doc comment for what an ordered name list means.
|
|
7688
|
+
*
|
|
7689
|
+
* overline — the caption at the top left ("PASSPORT", "DRIVING LICENCE")
|
|
7690
|
+
* number — the document's number, set apart at the top right
|
|
7691
|
+
* photo — the portrait. One field, rendered in a portrait frame beside the body
|
|
7692
|
+
* meta — the card's OWN fields, labelled, at each field's declared span
|
|
7693
|
+
* footer — the fields below the rule: issued, authority, record number
|
|
7694
|
+
* mrz — the machine-readable strip, set in monospace at the foot
|
|
7695
|
+
*
|
|
7696
|
+
* Every role is optional but `number`: a bank card has no `mrz`, a library card no `photo`,
|
|
7697
|
+
* and the card simply omits the part it was given nothing for.
|
|
7430
7698
|
*
|
|
7431
7699
|
* @layer block
|
|
7432
7700
|
* @base group
|
|
@@ -7437,16 +7705,21 @@ function table(opts) {
|
|
|
7437
7705
|
function idcard(opts) {
|
|
7438
7706
|
const overline = opts.overline !== void 0 ? slot("idcard", "overline", opts.overline) : void 0;
|
|
7439
7707
|
const number = slot("idcard", "number", opts.number);
|
|
7708
|
+
const photo = opts.photo !== void 0 ? slot("idcard", "photo", opts.photo) : void 0;
|
|
7709
|
+
const mrz = opts.mrz !== void 0 ? slot("idcard", "mrz", opts.mrz) : void 0;
|
|
7440
7710
|
return {
|
|
7441
7711
|
...group({
|
|
7442
|
-
fields: merge("idcard", overline, number, opts.meta),
|
|
7712
|
+
fields: merge("idcard", overline, number, photo, opts.meta, opts.footer, mrz),
|
|
7443
7713
|
ui: { kind: "idcard" }
|
|
7444
7714
|
}),
|
|
7445
7715
|
view: {
|
|
7446
7716
|
kind: "idcard",
|
|
7447
7717
|
overline: overline && Object.keys(overline)[0],
|
|
7448
7718
|
number: Object.keys(number)[0],
|
|
7449
|
-
|
|
7719
|
+
photo: photo && Object.keys(photo)[0],
|
|
7720
|
+
meta: Object.keys(opts.meta ?? {}),
|
|
7721
|
+
footer: Object.keys(opts.footer ?? {}),
|
|
7722
|
+
mrz: mrz && Object.keys(mrz)[0]
|
|
7450
7723
|
}
|
|
7451
7724
|
};
|
|
7452
7725
|
}
|
|
@@ -7531,6 +7804,8 @@ var blocks = {
|
|
|
7531
7804
|
nutrition,
|
|
7532
7805
|
specimen,
|
|
7533
7806
|
manifest,
|
|
7807
|
+
assay,
|
|
7808
|
+
flow,
|
|
7534
7809
|
table,
|
|
7535
7810
|
idcard,
|
|
7536
7811
|
properties,
|
|
@@ -8393,6 +8668,11 @@ function group(o) {
|
|
|
8393
8668
|
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`);
|
|
8394
8669
|
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`);
|
|
8395
8670
|
} else if (o.value !== void 0) throw new Error(`[field.group] value requires scope 'nest' — a hoist group owns no children to write`);
|
|
8671
|
+
const title = v.title ?? [];
|
|
8672
|
+
if (title.length > 0) {
|
|
8673
|
+
if (!o.multiple) throw new Error(`[field.group] ui.title heads a collection's ROWS — it needs multiple: true`);
|
|
8674
|
+
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`);
|
|
8675
|
+
}
|
|
8396
8676
|
return {
|
|
8397
8677
|
el: "group",
|
|
8398
8678
|
scope,
|
|
@@ -8405,6 +8685,7 @@ function group(o) {
|
|
|
8405
8685
|
display: v.display ?? "wrap",
|
|
8406
8686
|
kind: v.kind,
|
|
8407
8687
|
fixed: r.fixed,
|
|
8688
|
+
view: title.length > 0 ? { title } : void 0,
|
|
8408
8689
|
fields
|
|
8409
8690
|
};
|
|
8410
8691
|
}
|
|
@@ -8574,6 +8855,31 @@ function info(textKey) {
|
|
|
8574
8855
|
};
|
|
8575
8856
|
}
|
|
8576
8857
|
/**
|
|
8858
|
+
* What else points AT this record — the inverse of a relation, which is often the more useful
|
|
8859
|
+
* direction on a record page: a box is more usefully "what is in it" than "what it is in".
|
|
8860
|
+
*
|
|
8861
|
+
* Stores NOTHING and has no column: it declares where to look, and the server answers by asking
|
|
8862
|
+
* the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
|
|
8863
|
+
* a field — there is no value here to validate or save.
|
|
8864
|
+
*
|
|
8865
|
+
* `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
|
|
8866
|
+
* "everything that happens to point here": a shelf may point at the same target through two
|
|
8867
|
+
* fields — a transaction has a source account and a destination account — and a panel that
|
|
8868
|
+
* merged them would answer a question nobody asked.
|
|
8869
|
+
*
|
|
8870
|
+
* @layer primitive
|
|
8871
|
+
* @prim —
|
|
8872
|
+
* @widget backrefs
|
|
8873
|
+
* @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
|
|
8874
|
+
*/
|
|
8875
|
+
function backrefs(o) {
|
|
8876
|
+
return {
|
|
8877
|
+
el: "backrefs",
|
|
8878
|
+
label: o.label,
|
|
8879
|
+
from: o.from
|
|
8880
|
+
};
|
|
8881
|
+
}
|
|
8882
|
+
/**
|
|
8577
8883
|
* Action button: invokes the handler registered in actionRegistry under the key `value`.
|
|
8578
8884
|
*
|
|
8579
8885
|
* @layer primitive
|
|
@@ -8898,20 +9204,22 @@ registerType("time", {
|
|
|
8898
9204
|
prim: "time",
|
|
8899
9205
|
widget: "time",
|
|
8900
9206
|
build(o) {
|
|
8901
|
-
const granularity = o.config?.granularity ?? "
|
|
9207
|
+
const granularity = o.config?.granularity ?? "minute";
|
|
9208
|
+
const s = string$1(reqErr()).regex({
|
|
9209
|
+
hour: /^([01]\d|2[0-3])$/,
|
|
9210
|
+
minute: /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/,
|
|
9211
|
+
second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
|
9212
|
+
}[granularity], { message: vmsg("time_format") });
|
|
8902
9213
|
return {
|
|
8903
9214
|
column: "time",
|
|
8904
|
-
zod:
|
|
8905
|
-
hour: /^([01]\d|2[0-3])$/,
|
|
8906
|
-
minute: /^([01]\d|2[0-3]):[0-5]\d$/,
|
|
8907
|
-
second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
|
8908
|
-
}[granularity], { message: vmsg("time_format") }),
|
|
9215
|
+
zod: granularity === "minute" ? s.transform((v) => v.slice(0, 5)) : s,
|
|
8909
9216
|
hints: { granularity }
|
|
8910
9217
|
};
|
|
8911
9218
|
}
|
|
8912
9219
|
});
|
|
8913
9220
|
/**
|
|
8914
|
-
* Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
|
|
9221
|
+
* Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
|
|
9222
|
+
* 'minute' — the same default `f.datetime` has always had).
|
|
8915
9223
|
*
|
|
8916
9224
|
* @layer primitive
|
|
8917
9225
|
* @prim time
|
|
@@ -10156,7 +10464,8 @@ var PRIMITIVES = {
|
|
|
10156
10464
|
url,
|
|
10157
10465
|
divider,
|
|
10158
10466
|
info,
|
|
10159
|
-
button
|
|
10467
|
+
button,
|
|
10468
|
+
backrefs
|
|
10160
10469
|
};
|
|
10161
10470
|
/** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
|
|
10162
10471
|
function composeF(presets, blocks) {
|
|
@@ -11058,19 +11367,16 @@ var bus_route_default = defineShelf({
|
|
|
11058
11367
|
rules: { max: 200 },
|
|
11059
11368
|
ui: { span: 3 }
|
|
11060
11369
|
}),
|
|
11061
|
-
weekday_times: field.
|
|
11370
|
+
weekday_times: field.timetable({
|
|
11062
11371
|
label: "transit.bus_route.fields.weekday_times",
|
|
11063
|
-
multiple: true,
|
|
11064
11372
|
ui: { span: 12 }
|
|
11065
11373
|
}),
|
|
11066
|
-
saturday_times: field.
|
|
11374
|
+
saturday_times: field.timetable({
|
|
11067
11375
|
label: "transit.bus_route.fields.saturday_times",
|
|
11068
|
-
multiple: true,
|
|
11069
11376
|
ui: { span: 6 }
|
|
11070
11377
|
}),
|
|
11071
|
-
sunday_times: field.
|
|
11378
|
+
sunday_times: field.timetable({
|
|
11072
11379
|
label: "transit.bus_route.fields.sunday_times",
|
|
11073
|
-
multiple: true,
|
|
11074
11380
|
ui: { span: 6 }
|
|
11075
11381
|
}),
|
|
11076
11382
|
tags: field.tags({ label: "transit.bus_route.fields.tags" }),
|
|
@@ -11091,7 +11397,10 @@ var src_default = definePlugin({
|
|
|
11091
11397
|
id: "transit",
|
|
11092
11398
|
label: "transit.library.label",
|
|
11093
11399
|
icon: "lucide:bus",
|
|
11094
|
-
agent:
|
|
11400
|
+
agent: {
|
|
11401
|
+
description: "Local bus routes and their departure schedules (bus_route).",
|
|
11402
|
+
instructions: "bus_route: weekday_times/saturday_times/sunday_times — departure schedule. When the schedule changes, update *_times via update_record."
|
|
11403
|
+
},
|
|
11095
11404
|
starterHint: "the next departures on the saved routes"
|
|
11096
11405
|
}),
|
|
11097
11406
|
shelves: [bus_route_default]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-transit",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"test": "node --import tsx/esm --test 'src/runtime/*.test.ts'"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@coffer-org/sdk": "^7.
|
|
30
|
-
"@coffer-org/server": "^7.
|
|
31
|
-
"@coffer-org/helper-dav": "^7.
|
|
29
|
+
"@coffer-org/sdk": "^7.3.0",
|
|
30
|
+
"@coffer-org/server": "^7.3.0",
|
|
31
|
+
"@coffer-org/helper-dav": "^7.1.0"
|
|
32
32
|
},
|
|
33
33
|
"coffer": {
|
|
34
34
|
"runtime": "node",
|