foldkit-crud 0.10.0 → 0.12.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/README.md +15 -4
- package/dist/display.d.ts +105 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +111 -0
- package/dist/display.js.map +1 -0
- package/dist/{index.d.mts → index.d.ts} +114 -217
- package/dist/index.d.ts.map +1 -0
- package/dist/{index.mjs → index.js} +69 -244
- package/dist/index.js.map +1 -0
- package/dist/screen.d.ts +72 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +52 -0
- package/dist/screen.js.map +1 -0
- package/dist/sort.d.ts +36 -0
- package/dist/sort.d.ts.map +1 -0
- package/dist/sort.js +50 -0
- package/dist/sort.js.map +1 -0
- package/package.json +13 -13
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -113,6 +113,9 @@ What each call does:
|
|
|
113
113
|
the selected members in the Selection's order, each with its `key`,
|
|
114
114
|
`member`, a `label` found the way a form finds one (the schema's `title`,
|
|
115
115
|
else `Form.label` metadata, else the key), and a `display`.
|
|
116
|
+
`live: true` asks for the list live (`Data.query`'s `live`): rows another
|
|
117
|
+
page writes join, change and leave it while it is shown, given a server
|
|
118
|
+
whose live source watches lists (`pollLive`).
|
|
116
119
|
- **`Authors.at`** binds it to your Remote domain and your Model. It still does
|
|
117
120
|
no I/O.
|
|
118
121
|
- **`AuthorList.active`** is the requirement: while `input` gives a value, the
|
|
@@ -127,6 +130,11 @@ What each call does:
|
|
|
127
130
|
- **`AuthorList.refresh(model)`** is the Model with the list asked for again. A
|
|
128
131
|
failed read is not retried on its own, so this is what a retry button's
|
|
129
132
|
Message returns.
|
|
133
|
+
- **`AuthorList.connection(model)`** is the connection the list reads, an
|
|
134
|
+
`Option`, none while it is not shown: what a write's optimistic insert or
|
|
135
|
+
remove names (`ConnectionChange.prepend(list, Remote.ref(Author, id))`). It
|
|
136
|
+
carries the list's input, so a change cannot name a list rebuilt from
|
|
137
|
+
another input that nothing reads.
|
|
130
138
|
|
|
131
139
|
`AuthorList.row(id)` is a Projection of one row through the list's Selection,
|
|
132
140
|
whether or not the query finds it now, and `AuthorList.choiceOf(model, id)` is
|
|
@@ -209,7 +217,8 @@ const subscriptions = Data.subscriptions({ editor: PostEditor.active })
|
|
|
209
217
|
- `onOut` for the placement: a valid submit becomes `Data.mutate`, and the
|
|
210
218
|
editor remembers the request id.
|
|
211
219
|
- `active`: while an id is open, what the form writes is fetched and
|
|
212
|
-
retained.
|
|
220
|
+
retained. Its `projectionOf(model)` reads that value, typed by the
|
|
221
|
+
members the form writes.
|
|
213
222
|
- `after(update)`: wraps `update` so the editor fills the form and
|
|
214
223
|
reconciles its status after every Message. `sync` is that Step on its own,
|
|
215
224
|
for an `update` composed another way.
|
|
@@ -291,8 +300,9 @@ what is in the form.
|
|
|
291
300
|
|
|
292
301
|
A save the server refuses with `Refusal.field(key, reason)` for one of the
|
|
293
302
|
form's keys is shown on that key, as the form's own `Refused` would show it: the
|
|
294
|
-
key reads invalid with the reason, as text, keeping what was typed, until
|
|
295
|
-
edited. It is shown once per save,
|
|
303
|
+
key reads invalid with the reason, as text, keeping what was typed, until any
|
|
304
|
+
key is edited. It is shown once per save, only while the key still holds what
|
|
305
|
+
the save sent, and the status is `SaveFailed`. A refusal
|
|
296
306
|
with `Refusal.invalid` (the row the save would leave breaks an invariant) shows
|
|
297
307
|
its message the same way beside each key its path names. A refusal of a key the
|
|
298
308
|
form does not have, or of an invariant whose path names none, is left to the
|
|
@@ -536,7 +546,8 @@ Entity, so one author list serves `authorId` and `editorId` alike. A picker
|
|
|
536
546
|
whose target no list is over, or whose list has no `choice`, throws when
|
|
537
547
|
`Crud.options` is called, not when the form is drawn. `AuthorList.choices(model)`
|
|
538
548
|
is one list's choices on its own. Both are empty until the page is loaded, and
|
|
539
|
-
hold only the rows loaded so far.
|
|
549
|
+
hold only the rows loaded so far. A refresh that fails keeps the rows it had
|
|
550
|
+
among the choices, as the list still shows them; the failure is in `page`.
|
|
540
551
|
|
|
541
552
|
#### When a picker searches
|
|
542
553
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { AnyEntity, EntityMember } from "foldkit-entity";
|
|
2
|
+
import { Metadata } from "foldkit-metadata";
|
|
3
|
+
/**
|
|
4
|
+
* How one selected member shows: the primitive every kind of Display is a value
|
|
5
|
+
* of. `Display.text()` and `Display.flag()` are values of it, made by
|
|
6
|
+
* `Display.kind`, which is what an application calls for a badge or a relative
|
|
7
|
+
* date. The kinds here are a collection, not a special case.
|
|
8
|
+
*/
|
|
9
|
+
export interface Display<Data = unknown> {
|
|
10
|
+
/** The name a renderer is found by. */
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
/** Whether a view draws it. A member read and not shown, usually the id, is `false`. */
|
|
13
|
+
readonly shown: boolean;
|
|
14
|
+
/** What this kind needs: a format, a relation's target. */
|
|
15
|
+
readonly data: Data;
|
|
16
|
+
/**
|
|
17
|
+
* The value as text: what a cell says when nothing draws it specially. It is
|
|
18
|
+
* never given `null` or `undefined`; `Display.show` says `nothing` for those.
|
|
19
|
+
*/
|
|
20
|
+
readonly text: (value: unknown, words: DisplayWords) => string;
|
|
21
|
+
}
|
|
22
|
+
/** A kind of Display: how its values are made, and how one is told from another. */
|
|
23
|
+
export interface DisplayKind<Data> {
|
|
24
|
+
readonly kind: string;
|
|
25
|
+
readonly of: (data: Data) => Display<Data>;
|
|
26
|
+
readonly is: (display: Display) => display is Display<Data>;
|
|
27
|
+
}
|
|
28
|
+
/** A selected member with its words and its Display. A list's column, a detail's line. */
|
|
29
|
+
export interface DisplayColumn<Key extends string = string> {
|
|
30
|
+
readonly key: Key;
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly member: EntityMember;
|
|
33
|
+
readonly display: Display;
|
|
34
|
+
}
|
|
35
|
+
/** The words a Display needs that are not the value's own. */
|
|
36
|
+
export interface DisplayWords {
|
|
37
|
+
/** Default `yes`. */
|
|
38
|
+
readonly yes?: string;
|
|
39
|
+
/** Default `no`. */
|
|
40
|
+
readonly no?: string;
|
|
41
|
+
/** What stands for nothing: `null`, `undefined`, an empty list. Default the empty string. */
|
|
42
|
+
readonly nothing?: string;
|
|
43
|
+
/** Between the items of a list, and between the members of a nested value. Default `, `. */
|
|
44
|
+
readonly separator?: string;
|
|
45
|
+
}
|
|
46
|
+
/** A kind of Display. The kinds below are made with it, and so is an application's. */
|
|
47
|
+
declare const kind: <Data = Record<string, never>>(name: string, spec: {
|
|
48
|
+
readonly shown?: boolean;
|
|
49
|
+
readonly text: (data: Data, value: unknown, words: DisplayWords) => string;
|
|
50
|
+
}) => DisplayKind<Data>;
|
|
51
|
+
declare const show: (display: Display, value: unknown, words?: DisplayWords) => string;
|
|
52
|
+
export declare const Display: {
|
|
53
|
+
/**
|
|
54
|
+
* A kind of Display. `Display.kind<{ tone: string }>('Badge', { text: (_, value) => String(value) })`
|
|
55
|
+
* gives `.of(data)` to make one and `.is(display)` to tell one, exactly as the
|
|
56
|
+
* kinds below were made. `text` is the floor; a view draws it specially once
|
|
57
|
+
* it is given a renderer for `Badge`.
|
|
58
|
+
*/
|
|
59
|
+
kind: typeof kind;
|
|
60
|
+
Text: DisplayKind<{
|
|
61
|
+
readonly format?: ((value: unknown) => string) | undefined;
|
|
62
|
+
}>;
|
|
63
|
+
Number: DisplayKind<{
|
|
64
|
+
readonly format?: ((value: number) => string) | undefined;
|
|
65
|
+
}>;
|
|
66
|
+
Flag: DisplayKind<Record<string, never>>;
|
|
67
|
+
Hidden: DisplayKind<Record<string, never>>;
|
|
68
|
+
Ref: DisplayKind<{
|
|
69
|
+
readonly target: AnyEntity;
|
|
70
|
+
readonly many: boolean;
|
|
71
|
+
}>;
|
|
72
|
+
Nested: DisplayKind<{
|
|
73
|
+
readonly target: AnyEntity;
|
|
74
|
+
readonly shape: 'one' | 'many' | 'page';
|
|
75
|
+
readonly columns: ReadonlyArray<DisplayColumn>;
|
|
76
|
+
}>;
|
|
77
|
+
text: (format?: (value: unknown) => string) => Display;
|
|
78
|
+
number: (format?: (value: number) => string) => Display;
|
|
79
|
+
flag: () => Display;
|
|
80
|
+
/** Read, so a row can be opened or keyed by it, and not shown: usually the id. */
|
|
81
|
+
hidden: () => Display;
|
|
82
|
+
/**
|
|
83
|
+
* Entity metadata: how a member shows wherever it is listed, e.g.
|
|
84
|
+
* `Entity.annotateMembers({ price: Display.of(Display.number(cents => ...)) })`.
|
|
85
|
+
*/
|
|
86
|
+
of: (display: Display) => Metadata;
|
|
87
|
+
/**
|
|
88
|
+
* The Display of one selected member, from the most to the least explicit
|
|
89
|
+
* source: the member's `Display.of` metadata, how the relation was selected,
|
|
90
|
+
* then the shape of its schema. `nested` is the columns of a nested Selection,
|
|
91
|
+
* with `shape` saying how many the relation yields.
|
|
92
|
+
*/
|
|
93
|
+
resolve: (member: EntityMember, nested?: {
|
|
94
|
+
readonly shape: 'one' | 'many' | 'page';
|
|
95
|
+
readonly columns: ReadonlyArray<DisplayColumn>;
|
|
96
|
+
}) => Display;
|
|
97
|
+
/**
|
|
98
|
+
* A value as the text its Display calls for: what a cell says when nothing
|
|
99
|
+
* draws it specially. A view that wants a link or a badge reads the Display
|
|
100
|
+
* and the value itself; this is the floor every view can stand on.
|
|
101
|
+
*/
|
|
102
|
+
show: typeof show;
|
|
103
|
+
};
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.ts","names":[],"sources":["../src/display.ts"],"mappings":";;;;;;;;iBAgBiB,QAAQ;;WAEd;;WAEA;;WAEA,MAAM;;;;;WAKN,OAAO,gBAAgB,OAAO;;;iBAIxB,YAAY;WAClB;WACA,KAAK,MAAM,SAAS,QAAQ;WAC5B,KAAK,SAAS,YAAY,WAAW,QAAQ;;;iBAIvC,cAAc;WACpB,KAAK;WACL;WACA,QAAQ;WACR,SAAS;;;iBAIH;;WAEN;;WAEA;;WAEA;;WAEA;;;cAoBL,OAAQ,OAAO,uBAAqB,cAC5B;WAED;WACA,OAAO,MAAM,MAAM,gBAAgB,OAAO;MAEpD,YAAY;cAYT,OAAI,SAAa,SAAO,gBAAgB,QAAS;qBAyD1C;;;;;;;;;aAtDyB,WAAE;;;aAGC,WAAE;;;;;aASP,QAAA;aAA0B;;;aAa3C,QAAA;aACD;aACE,SAAA,cAAc;;EA4ChB,OAAA,UAAC,8BAA4B;EAC3B,SAAA,UAAC,6BAA2B;EACpC,YAAA;;EAEE,cAAA;;;;;EAME,KAAA,SAAA,YAAO;;;;;;;EASX,UAAA,QAAA,cAAY;aAET;aACA,SAAS,cAAc;QAEjC"}
|
package/dist/display.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Schema, SchemaAST } from "effect";
|
|
2
|
+
import { SchemaShape } from "foldkit-entity";
|
|
3
|
+
import { Metadata } from "foldkit-metadata";
|
|
4
|
+
// src/display.ts
|
|
5
|
+
/**
|
|
6
|
+
* Displays: how one selected member shows in a list or a detail, described
|
|
7
|
+
* without a renderer. It is `foldkit-form`'s `Input` from the reading side: the
|
|
8
|
+
* member says what it is, a Display says how it reads, and which DOM draws it is
|
|
9
|
+
* the application's.
|
|
10
|
+
*/
|
|
11
|
+
const key = Metadata.key("foldkit-crud/display", {
|
|
12
|
+
merge: (displays) => displays.slice(-1),
|
|
13
|
+
summarize: (display) => display.kind
|
|
14
|
+
});
|
|
15
|
+
const fromSchema = (schema) => {
|
|
16
|
+
const members = SchemaShape.present(Schema.toType(schema).ast);
|
|
17
|
+
const [only] = members;
|
|
18
|
+
if (members.length === 1 && only !== void 0) {
|
|
19
|
+
if (SchemaAST.isNumber(only)) return Number_.of({});
|
|
20
|
+
if (SchemaAST.isBoolean(only)) return Flag.of(nothing);
|
|
21
|
+
}
|
|
22
|
+
return Text.of({});
|
|
23
|
+
};
|
|
24
|
+
/** A kind of Display. The kinds below are made with it, and so is an application's. */
|
|
25
|
+
const kind = (name, spec) => ({
|
|
26
|
+
kind: name,
|
|
27
|
+
of: (data) => Object.freeze({
|
|
28
|
+
kind: name,
|
|
29
|
+
shown: spec.shown ?? true,
|
|
30
|
+
data,
|
|
31
|
+
text: (value, words) => spec.text(data, value, words)
|
|
32
|
+
}),
|
|
33
|
+
is: (display) => display.kind === name
|
|
34
|
+
});
|
|
35
|
+
const show = (display, value, words = {}) => value === null || value === void 0 ? words.nothing ?? "" : display.text(value, words);
|
|
36
|
+
const Text = kind("Text", { text: (data, value) => data.format === void 0 ? String(value) : data.format(value) });
|
|
37
|
+
const Number_ = kind("Number", { text: (data, value) => typeof value === "number" && data.format !== void 0 ? data.format(value) : String(value) });
|
|
38
|
+
const Flag = kind("Flag", { text: (_, value, words) => value === true ? words.yes ?? "yes" : words.no ?? "no" });
|
|
39
|
+
const Hidden = kind("Hidden", {
|
|
40
|
+
shown: false,
|
|
41
|
+
text: () => ""
|
|
42
|
+
});
|
|
43
|
+
/** A relation read as refs: which one, with nothing to say about it but its id. */
|
|
44
|
+
const Ref = kind("Ref", { text: (_, value, words) => {
|
|
45
|
+
const ids = (Array.isArray(value) ? value : [value]).map((ref) => typeof ref === "object" && ref !== null && "id" in ref ? String(ref.id) : String(ref));
|
|
46
|
+
return ids.length === 0 ? words.nothing ?? "" : ids.join(words.separator ?? ", ");
|
|
47
|
+
} });
|
|
48
|
+
/**
|
|
49
|
+
* A relation read through a Selection: the target's own members, each with a
|
|
50
|
+
* Display. `shape` is how many there are: one, a list, or a page of a list.
|
|
51
|
+
*/
|
|
52
|
+
const Nested = kind("Nested", { text: (data, value, words) => {
|
|
53
|
+
const one = (item) => data.columns.filter((column) => column.display.shown).map((column) => show(column.display, item?.[column.key], words)).filter((text) => text !== "").join(" ");
|
|
54
|
+
const items = data.shape === "one" ? [value] : data.shape === "page" ? value.items ?? [] : value;
|
|
55
|
+
return items.length === 0 ? words.nothing ?? "" : items.map(one).join(words.separator ?? ", ");
|
|
56
|
+
} });
|
|
57
|
+
const nothing = Object.freeze({});
|
|
58
|
+
const Display = {
|
|
59
|
+
/**
|
|
60
|
+
* A kind of Display. `Display.kind<{ tone: string }>('Badge', { text: (_, value) => String(value) })`
|
|
61
|
+
* gives `.of(data)` to make one and `.is(display)` to tell one, exactly as the
|
|
62
|
+
* kinds below were made. `text` is the floor; a view draws it specially once
|
|
63
|
+
* it is given a renderer for `Badge`.
|
|
64
|
+
*/
|
|
65
|
+
kind,
|
|
66
|
+
Text,
|
|
67
|
+
Number: Number_,
|
|
68
|
+
Flag,
|
|
69
|
+
Hidden,
|
|
70
|
+
Ref,
|
|
71
|
+
Nested,
|
|
72
|
+
text: (format) => Text.of({ format }),
|
|
73
|
+
number: (format) => Number_.of({ format }),
|
|
74
|
+
flag: () => Flag.of(nothing),
|
|
75
|
+
/** Read, so a row can be opened or keyed by it, and not shown: usually the id. */
|
|
76
|
+
hidden: () => Hidden.of(nothing),
|
|
77
|
+
/**
|
|
78
|
+
* Entity metadata: how a member shows wherever it is listed, e.g.
|
|
79
|
+
* `Entity.annotateMembers({ price: Display.of(Display.number(cents => ...)) })`.
|
|
80
|
+
*/
|
|
81
|
+
of: (display) => key.of(display),
|
|
82
|
+
/**
|
|
83
|
+
* The Display of one selected member, from the most to the least explicit
|
|
84
|
+
* source: the member's `Display.of` metadata, how the relation was selected,
|
|
85
|
+
* then the shape of its schema. `nested` is the columns of a nested Selection,
|
|
86
|
+
* with `shape` saying how many the relation yields.
|
|
87
|
+
*/
|
|
88
|
+
resolve: (member, nested) => {
|
|
89
|
+
const [explicit] = key.get(member.metadata);
|
|
90
|
+
if (explicit !== void 0) return explicit;
|
|
91
|
+
if (member._tag !== "Relation") return fromSchema(member.schema);
|
|
92
|
+
const target = member.target();
|
|
93
|
+
return nested === void 0 ? Ref.of({
|
|
94
|
+
target,
|
|
95
|
+
many: member.cardinality === "many"
|
|
96
|
+
}) : Nested.of({
|
|
97
|
+
target,
|
|
98
|
+
shape: nested.shape,
|
|
99
|
+
columns: nested.columns
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* A value as the text its Display calls for: what a cell says when nothing
|
|
104
|
+
* draws it specially. A view that wants a link or a badge reads the Display
|
|
105
|
+
* and the value itself; this is the floor every view can stand on.
|
|
106
|
+
*/
|
|
107
|
+
show
|
|
108
|
+
};
|
|
109
|
+
export { Display };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=display.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.js","names":[],"sources":["../src/display.ts"],"sourcesContent":["/**\n * Displays: how one selected member shows in a list or a detail, described\n * without a renderer. It is `foldkit-form`'s `Input` from the reading side: the\n * member says what it is, a Display says how it reads, and which DOM draws it is\n * the application's.\n */\nimport { Schema, SchemaAST } from 'effect'\nimport { SchemaShape, type AnyEntity, type EntityMember } from 'foldkit-entity'\nimport { Metadata } from 'foldkit-metadata'\n\n/**\n * How one selected member shows: the primitive every kind of Display is a value\n * of. `Display.text()` and `Display.flag()` are values of it, made by\n * `Display.kind`, which is what an application calls for a badge or a relative\n * date. The kinds here are a collection, not a special case.\n */\nexport interface Display<Data = unknown> {\n /** The name a renderer is found by. */\n readonly kind: string\n /** Whether a view draws it. A member read and not shown, usually the id, is `false`. */\n readonly shown: boolean\n /** What this kind needs: a format, a relation's target. */\n readonly data: Data\n /**\n * The value as text: what a cell says when nothing draws it specially. It is\n * never given `null` or `undefined`; `Display.show` says `nothing` for those.\n */\n readonly text: (value: unknown, words: DisplayWords) => string\n}\n\n/** A kind of Display: how its values are made, and how one is told from another. */\nexport interface DisplayKind<Data> {\n readonly kind: string\n readonly of: (data: Data) => Display<Data>\n readonly is: (display: Display) => display is Display<Data>\n}\n\n/** A selected member with its words and its Display. A list's column, a detail's line. */\nexport interface DisplayColumn<Key extends string = string> {\n readonly key: Key\n readonly label: string\n readonly member: EntityMember\n readonly display: Display\n}\n\n/** The words a Display needs that are not the value's own. */\nexport interface DisplayWords {\n /** Default `yes`. */\n readonly yes?: string\n /** Default `no`. */\n readonly no?: string\n /** What stands for nothing: `null`, `undefined`, an empty list. Default the empty string. */\n readonly nothing?: string\n /** Between the items of a list, and between the members of a nested value. Default `, `. */\n readonly separator?: string\n}\n\nconst key = Metadata.key<Display>('foldkit-crud/display', {\n // The last Display attached wins: a later annotation refines an earlier one.\n merge: displays => displays.slice(-1),\n summarize: display => display.kind,\n})\n\nconst fromSchema = (schema: Schema.Top): Display => {\n const members = SchemaShape.present(Schema.toType(schema).ast)\n const [only] = members\n if (members.length === 1 && only !== undefined) {\n if (SchemaAST.isNumber(only)) return Number_.of({})\n if (SchemaAST.isBoolean(only)) return Flag.of(nothing)\n }\n return Text.of({})\n}\n\n/** A kind of Display. The kinds below are made with it, and so is an application's. */\nconst kind = <Data = Record<string, never>>(\n name: string,\n spec: {\n readonly shown?: boolean\n readonly text: (data: Data, value: unknown, words: DisplayWords) => string\n },\n): DisplayKind<Data> => ({\n kind: name,\n of: data =>\n Object.freeze({\n kind: name,\n shown: spec.shown ?? true,\n data,\n text: (value: unknown, words: DisplayWords) => spec.text(data, value, words),\n }),\n is: (display): display is Display<Data> => display.kind === name,\n})\n\nconst show = (display: Display, value: unknown, words: DisplayWords = {}): string =>\n value === null || value === undefined ? (words.nothing ?? '') : display.text(value, words)\n\nconst Text = kind<{ readonly format?: ((value: unknown) => string) | undefined }>('Text', {\n text: (data, value) => (data.format === undefined ? String(value) : data.format(value)),\n})\nconst Number_ = kind<{ readonly format?: ((value: number) => string) | undefined }>('Number', {\n text: (data, value) =>\n typeof value === 'number' && data.format !== undefined ? data.format(value) : String(value),\n})\nconst Flag = kind('Flag', {\n text: (_, value, words) => (value === true ? (words.yes ?? 'yes') : (words.no ?? 'no')),\n})\nconst Hidden = kind('Hidden', { shown: false, text: () => '' })\n/** A relation read as refs: which one, with nothing to say about it but its id. */\nconst Ref = kind<{ readonly target: AnyEntity; readonly many: boolean }>('Ref', {\n text: (_, value, words) => {\n const ids = (Array.isArray(value) ? value : [value]).map(ref =>\n typeof ref === 'object' && ref !== null && 'id' in ref ? String(ref.id) : String(ref),\n )\n return ids.length === 0 ? (words.nothing ?? '') : ids.join(words.separator ?? ', ')\n },\n})\n/**\n * A relation read through a Selection: the target's own members, each with a\n * Display. `shape` is how many there are: one, a list, or a page of a list.\n */\nconst Nested = kind<{\n readonly target: AnyEntity\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n}>('Nested', {\n text: (data, value, words) => {\n const one = (item: unknown): string =>\n data.columns\n .filter(column => column.display.shown)\n .map(column =>\n show(\n column.display,\n (item as Readonly<Record<string, unknown>> | null)?.[column.key],\n words,\n ),\n )\n .filter(text => text !== '')\n .join(' ')\n const items =\n data.shape === 'one'\n ? [value]\n : data.shape === 'page'\n ? ((value as { readonly items?: ReadonlyArray<unknown> }).items ?? [])\n : (value as ReadonlyArray<unknown>)\n return items.length === 0 ? (words.nothing ?? '') : items.map(one).join(words.separator ?? ', ')\n },\n})\n\nconst nothing: Record<string, never> = Object.freeze({})\n\nexport const Display = {\n /**\n * A kind of Display. `Display.kind<{ tone: string }>('Badge', { text: (_, value) => String(value) })`\n * gives `.of(data)` to make one and `.is(display)` to tell one, exactly as the\n * kinds below were made. `text` is the floor; a view draws it specially once\n * it is given a renderer for `Badge`.\n */\n kind,\n\n // The kinds this package resolves to. A view finds its renderer by `kind`.\n Text,\n Number: Number_,\n Flag,\n Hidden,\n Ref,\n Nested,\n\n text: (format?: (value: unknown) => string): Display => Text.of({ format }),\n number: (format?: (value: number) => string): Display => Number_.of({ format }),\n flag: (): Display => Flag.of(nothing),\n /** Read, so a row can be opened or keyed by it, and not shown: usually the id. */\n hidden: (): Display => Hidden.of(nothing),\n\n /**\n * Entity metadata: how a member shows wherever it is listed, e.g.\n * `Entity.annotateMembers({ price: Display.of(Display.number(cents => ...)) })`.\n */\n of: (display: Display) => key.of(display),\n\n /**\n * The Display of one selected member, from the most to the least explicit\n * source: the member's `Display.of` metadata, how the relation was selected,\n * then the shape of its schema. `nested` is the columns of a nested Selection,\n * with `shape` saying how many the relation yields.\n */\n resolve: (\n member: EntityMember,\n nested?: {\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n },\n ): Display => {\n const [explicit] = key.get(member.metadata)\n if (explicit !== undefined) return explicit\n if (member._tag !== 'Relation') return fromSchema(member.schema as Schema.Top)\n const target = member.target()\n return nested === undefined\n ? Ref.of({ target, many: member.cardinality === 'many' })\n : Nested.of({ target, shape: nested.shape, columns: nested.columns })\n },\n\n /**\n * A value as the text its Display calls for: what a cell says when nothing\n * draws it specially. A view that wants a link or a badge reads the Display\n * and the value itself; this is the floor every view can stand on.\n */\n show,\n}\n"],"mappings":"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"}
|