gesso-framework 0.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/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/FunctionComponent-DAyf5HQ6.d.ts +716 -0
- package/dist/createComponent-3n1ZSql8.js +254 -0
- package/dist/createComponent-3n1ZSql8.js.map +1 -0
- package/dist/index-BkYPVXSJ.d.ts +7272 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8720 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx/jsx-dev-runtime.js +2 -0
- package/dist/jsx/jsx-runtime.d.ts +117 -0
- package/dist/jsx/jsx-runtime.js +153 -0
- package/dist/jsx/jsx-runtime.js.map +1 -0
- package/dist/persisted-Ddb51avc.js +2648 -0
- package/dist/persisted-Ddb51avc.js.map +1 -0
- package/dist/worker/index.d.ts +32 -0
- package/dist/worker/index.js +2 -0
- package/package.json +71 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { defer, distinctUntilChanged, map, of } from "rxjs";
|
|
2
|
+
import { isComponentLikeElement, isObservable } from "gesso-core";
|
|
3
|
+
//#region src/each.ts
|
|
4
|
+
/**
|
|
5
|
+
* A keyed list.
|
|
6
|
+
*
|
|
7
|
+
* <Each of={rows} by="id">{row => <Row row={row} />}</Each>
|
|
8
|
+
*
|
|
9
|
+
* The `map`, the change check and the keying, which every screen in
|
|
10
|
+
* both applications wrote by hand: forty blocks of
|
|
11
|
+
* `map(items => items.map(item => <Row key={item.id} />))`, each of
|
|
12
|
+
* which rebuilt every row's element on every emission of the list.
|
|
13
|
+
*
|
|
14
|
+
* A row's element is built once and kept while the row is in the list
|
|
15
|
+
* and its value has not changed, so a list that re-emits equal data
|
|
16
|
+
* builds nothing and reconciles nothing: the result is the same array,
|
|
17
|
+
* and the change check downstream ends the emission there. A row whose
|
|
18
|
+
* value changed is rebuilt alone; the key keeps its node and its
|
|
19
|
+
* component instance, so only what changed is written.
|
|
20
|
+
*
|
|
21
|
+
* `key` is set from `by` on the element the function returns, so a row
|
|
22
|
+
* component is written without one. An element that carries its own key
|
|
23
|
+
* keeps it.
|
|
24
|
+
*
|
|
25
|
+
* `LazyColumn` is the same shape for a list long enough that only the
|
|
26
|
+
* visible rows should exist. This is the short-list case, and the two
|
|
27
|
+
* read alike on purpose.
|
|
28
|
+
*/
|
|
29
|
+
function Each(props) {
|
|
30
|
+
return each(props.of, props.by, props.children);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The function form of `Each`, for a list built outside a tag:
|
|
34
|
+
*
|
|
35
|
+
* const rows = each(tracks, 'id', track => <ItemRow item={track} />);
|
|
36
|
+
*/
|
|
37
|
+
function each(source, by, render) {
|
|
38
|
+
const keyOf = keyFunction(by);
|
|
39
|
+
const dynamic = isObservable(source);
|
|
40
|
+
const items = dynamic ? source : of(source);
|
|
41
|
+
if (by === void 0 && dynamic) warnIndexKeyed(render);
|
|
42
|
+
const positional = render.length >= 2;
|
|
43
|
+
return defer(() => {
|
|
44
|
+
const held = /* @__PURE__ */ new Map();
|
|
45
|
+
let previous = null;
|
|
46
|
+
let generation = 0;
|
|
47
|
+
return items.pipe(map((list) => {
|
|
48
|
+
previous = reconcile(held, previous, ++generation, list, keyOf, render, positional);
|
|
49
|
+
return previous;
|
|
50
|
+
}), distinctUntilChanged());
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds the children for one emission, reusing every row it can.
|
|
55
|
+
*
|
|
56
|
+
* The result is the previous array itself when nothing about the list
|
|
57
|
+
* changed, so the `distinctUntilChanged` above it ends the emission and
|
|
58
|
+
* the builder is never entered. A new array is allocated at the first
|
|
59
|
+
* row that differs and not before, which is what keeps an unchanged
|
|
60
|
+
* emission free rather than merely cheap.
|
|
61
|
+
*/
|
|
62
|
+
function reconcile(held, previous, generation, list, keyOf, render, positional) {
|
|
63
|
+
let next = null;
|
|
64
|
+
for (let index = 0; index < list.length; index++) {
|
|
65
|
+
const item = list[index];
|
|
66
|
+
const key = keyOf(item, index);
|
|
67
|
+
const row = held.get(key);
|
|
68
|
+
if (row !== void 0 && row.generation === generation) throw new Error(`Duplicate key '${String(key)}' in a list: two rows claim the same identity.`);
|
|
69
|
+
let child;
|
|
70
|
+
if (row !== void 0 && Object.is(row.item, item) && (!positional || row.index === index)) {
|
|
71
|
+
child = row.child;
|
|
72
|
+
row.index = index;
|
|
73
|
+
row.generation = generation;
|
|
74
|
+
} else {
|
|
75
|
+
child = keyed$1(render(item, index), key);
|
|
76
|
+
if (row === void 0) held.set(key, {
|
|
77
|
+
item,
|
|
78
|
+
index,
|
|
79
|
+
child,
|
|
80
|
+
generation
|
|
81
|
+
});
|
|
82
|
+
else {
|
|
83
|
+
row.item = item;
|
|
84
|
+
row.index = index;
|
|
85
|
+
row.child = child;
|
|
86
|
+
row.generation = generation;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (next === null && (previous === null || index >= previous.length || previous[index] !== child)) next = previous === null ? [] : previous.slice(0, index);
|
|
90
|
+
next?.push(child);
|
|
91
|
+
}
|
|
92
|
+
for (const [key, row] of held) if (row.generation !== generation) held.delete(key);
|
|
93
|
+
if (next === null && previous !== null && previous.length !== list.length) next = previous.slice(0, list.length);
|
|
94
|
+
return next ?? previous ?? EMPTY$2;
|
|
95
|
+
}
|
|
96
|
+
const EMPTY$2 = [];
|
|
97
|
+
function keyFunction(by) {
|
|
98
|
+
if (by === void 0) return (_item, index) => index;
|
|
99
|
+
if (typeof by === "function") return by;
|
|
100
|
+
return (item) => item[by];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Puts the row's key on the element the render function returned.
|
|
104
|
+
*
|
|
105
|
+
* An element that named its own key keeps it, since the author meant
|
|
106
|
+
* something by it. An observable row cannot carry one at all: its
|
|
107
|
+
* fragment anchor is identified by position, so a list of them
|
|
108
|
+
* reorders wrongly, and it is worth saying so rather than leaving a
|
|
109
|
+
* reorder to look like corruption.
|
|
110
|
+
*/
|
|
111
|
+
function keyed$1(child, key) {
|
|
112
|
+
if (isComponentLikeElement(child)) return child.key === void 0 || child.key === null ? {
|
|
113
|
+
...child,
|
|
114
|
+
key
|
|
115
|
+
} : child;
|
|
116
|
+
if (isObservable(child)) throw new Error(`A row of a list is an observable, which cannot carry the key '${String(key)}'. Return an element or a component from the row function and bind the observable inside it.`);
|
|
117
|
+
const element = child;
|
|
118
|
+
const existing = element.props.key;
|
|
119
|
+
if (existing !== void 0 && existing !== null) return element;
|
|
120
|
+
return {
|
|
121
|
+
...element,
|
|
122
|
+
props: {
|
|
123
|
+
...element.props,
|
|
124
|
+
key
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Said once: every unkeyed list has the same problem and the same fix. */
|
|
129
|
+
let warnedAboutIndexKeys = false;
|
|
130
|
+
function warnIndexKeyed(render) {
|
|
131
|
+
if (warnedAboutIndexKeys) return;
|
|
132
|
+
warnedAboutIndexKeys = true;
|
|
133
|
+
const what = render.name === "" ? "a list" : `the list drawn by '${render.name}'`;
|
|
134
|
+
console.warn(`${what} is keyed by index, because \`each\` was given no \`by\`. A row inserted or removed anywhere but the end then shifts every row after it onto a different node: state moves between rows, a text field keeps the wrong value, and an animation runs on the wrong element. Pass \`by\` naming what identifies a row: by="id", or by={row => row.id}.`);
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/show.ts
|
|
138
|
+
/**
|
|
139
|
+
* One child, while a condition holds.
|
|
140
|
+
*
|
|
141
|
+
* <Show when={liked}>{() => <Icon path={ICONS.heart} />}</Show>
|
|
142
|
+
*
|
|
143
|
+
* `{cond && <x />}` already works when `cond` is a plain value, because
|
|
144
|
+
* JSX drops a `false` child. A reactive `cond` had to become
|
|
145
|
+
* `cond.pipe(map(on => (on ? [<x key="x" />] : [])))`, which is the
|
|
146
|
+
* fifty-six-block idiom in the two applications, and the discontinuity
|
|
147
|
+
* between the two forms is the surprise this removes.
|
|
148
|
+
*
|
|
149
|
+
* The child is built once, on the first frame it is shown, and kept
|
|
150
|
+
* while it is shown: a condition that emits `true` again draws nothing
|
|
151
|
+
* and reconciles nothing. It carries a stable key, so the node it
|
|
152
|
+
* built is the node it gets back when the condition returns, and it
|
|
153
|
+
* adds no node of its own: what a `Show` puts under its parent is the
|
|
154
|
+
* child, or nothing, and never a wrapper that layout would have to
|
|
155
|
+
* account for.
|
|
156
|
+
*
|
|
157
|
+
* Anything inside it that changes is bound as usual. The function is
|
|
158
|
+
* not re-run when the *data* changes, only when the condition does, so
|
|
159
|
+
* a `Show` whose child shows a value binds that value rather than
|
|
160
|
+
* reading it.
|
|
161
|
+
*/
|
|
162
|
+
function Show(props) {
|
|
163
|
+
return show(props.when, props.children, props.otherwise);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The function form of `Show`:
|
|
167
|
+
*
|
|
168
|
+
* {show(liked, () => <Icon path={ICONS.heart} />)}
|
|
169
|
+
*/
|
|
170
|
+
function show(when, children, otherwise) {
|
|
171
|
+
const condition = isObservable(when) ? when : of(when);
|
|
172
|
+
return defer(() => {
|
|
173
|
+
let shown = null;
|
|
174
|
+
let hidden = null;
|
|
175
|
+
return condition.pipe(map((value) => Boolean(value)), distinctUntilChanged(), map((on) => {
|
|
176
|
+
if (on) {
|
|
177
|
+
shown ??= [keyed(children(), "when")];
|
|
178
|
+
return shown;
|
|
179
|
+
}
|
|
180
|
+
hidden ??= otherwise === void 0 ? EMPTY$1 : [keyed(otherwise(), "otherwise")];
|
|
181
|
+
return hidden;
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const EMPTY$1 = [];
|
|
186
|
+
/**
|
|
187
|
+
* Gives the branch its stable key, so returning to it returns to the
|
|
188
|
+
* node it built. An element that named its own key keeps it.
|
|
189
|
+
*/
|
|
190
|
+
function keyed(child, key) {
|
|
191
|
+
if (isComponentLikeElement(child)) return child.key === void 0 || child.key === null ? {
|
|
192
|
+
...child,
|
|
193
|
+
key
|
|
194
|
+
} : child;
|
|
195
|
+
if (isObservable(child)) return child;
|
|
196
|
+
const element = child;
|
|
197
|
+
const existing = element.props.key;
|
|
198
|
+
if (existing !== void 0 && existing !== null) return element;
|
|
199
|
+
return {
|
|
200
|
+
...element,
|
|
201
|
+
props: {
|
|
202
|
+
...element.props,
|
|
203
|
+
key
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/metadata.ts
|
|
209
|
+
const metadataStore = /* @__PURE__ */ new WeakMap();
|
|
210
|
+
function getComponentMetadata(constructor) {
|
|
211
|
+
let metadata = metadataStore.get(constructor);
|
|
212
|
+
if (metadata === void 0) {
|
|
213
|
+
metadata = {
|
|
214
|
+
tag: constructor.name,
|
|
215
|
+
inputs: /* @__PURE__ */ new Set(),
|
|
216
|
+
states: /* @__PURE__ */ new Set(),
|
|
217
|
+
injects: /* @__PURE__ */ new Map(),
|
|
218
|
+
channels: /* @__PURE__ */ new Map()
|
|
219
|
+
};
|
|
220
|
+
metadataStore.set(constructor, metadata);
|
|
221
|
+
}
|
|
222
|
+
return metadata;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/createComponent.ts
|
|
226
|
+
/**
|
|
227
|
+
* Creates a ComponentElement for use as a child of a UiElement.
|
|
228
|
+
*
|
|
229
|
+
* Column(
|
|
230
|
+
* createComponent(Counter, { label: 'Count' }),
|
|
231
|
+
* createComponent(TodoItem, { todo }, todo.id)
|
|
232
|
+
* )
|
|
233
|
+
*
|
|
234
|
+
* The component may be a class extending Component or a function
|
|
235
|
+
* `(props, ctx) => UiChild`. Props are typed from the component: a
|
|
236
|
+
* class's `input()` fields, or a function's `Inputs<P>` parameter. A
|
|
237
|
+
* misspelled prop, a value of the wrong type, and a missing required
|
|
238
|
+
* prop are compile errors; props may be omitted only when none is
|
|
239
|
+
* required.
|
|
240
|
+
*/
|
|
241
|
+
function createComponent(component, ...args) {
|
|
242
|
+
const [props, key] = args;
|
|
243
|
+
return {
|
|
244
|
+
kind: "component",
|
|
245
|
+
tag: getComponentMetadata(component).tag,
|
|
246
|
+
component,
|
|
247
|
+
props: props ?? {},
|
|
248
|
+
key
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
export { Each as a, show as i, getComponentMetadata as n, each as o, Show as r, createComponent as t };
|
|
253
|
+
|
|
254
|
+
//# sourceMappingURL=createComponent-3n1ZSql8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createComponent-3n1ZSql8.js","names":["keyed","EMPTY","EMPTY"],"sources":["../src/each.ts","../src/show.ts","../src/metadata.ts","../src/createComponent.ts"],"sourcesContent":["import { Observable, defer, distinctUntilChanged, map, of } from 'rxjs';\n\nimport { isComponentLikeElement, isObservable, type UiChild, type UiElement } from 'gesso-core';\n\n/**\n * What gives a row its identity: the name of a field holding it, or a\n * function returning it. Omitting it keys by index, which is what the\n * builder falls back to and what `each` warns about.\n */\nexport type EachKey<T> = (keyof T & string) | ((item: T, index: number) => string | number);\n\nexport interface EachProps<T> {\n /** The list: a cell, any Observable of one, or a plain array. */\n readonly of: Observable<readonly T[]> | readonly T[];\n /** Identity per row. See `EachKey`. */\n readonly by?: EachKey<T>;\n /** What one row draws as. */\n readonly children: (item: T, index: number) => UiChild;\n}\n\n/**\n * A keyed list.\n *\n * <Each of={rows} by=\"id\">{row => <Row row={row} />}</Each>\n *\n * The `map`, the change check and the keying, which every screen in\n * both applications wrote by hand: forty blocks of\n * `map(items => items.map(item => <Row key={item.id} />))`, each of\n * which rebuilt every row's element on every emission of the list.\n *\n * A row's element is built once and kept while the row is in the list\n * and its value has not changed, so a list that re-emits equal data\n * builds nothing and reconciles nothing: the result is the same array,\n * and the change check downstream ends the emission there. A row whose\n * value changed is rebuilt alone; the key keeps its node and its\n * component instance, so only what changed is written.\n *\n * `key` is set from `by` on the element the function returns, so a row\n * component is written without one. An element that carries its own key\n * keeps it.\n *\n * `LazyColumn` is the same shape for a list long enough that only the\n * visible rows should exist. This is the short-list case, and the two\n * read alike on purpose.\n */\nexport function Each<T>(props: EachProps<T>): Observable<readonly UiChild[]> {\n return each(props.of, props.by, props.children);\n}\n\n/**\n * The function form of `Each`, for a list built outside a tag:\n *\n * const rows = each(tracks, 'id', track => <ItemRow item={track} />);\n */\nexport function each<T>(\n source: Observable<readonly T[]> | readonly T[],\n by: EachKey<T> | undefined,\n render: (item: T, index: number) => UiChild\n): Observable<readonly UiChild[]> {\n const keyOf = keyFunction(by);\n const dynamic = isObservable(source);\n const items = dynamic ? (source as Observable<readonly T[]>) : of(source as readonly T[]);\n if (by === undefined && dynamic) {\n warnIndexKeyed(render);\n }\n // Whether the row function took the index. One that did not is\n // unaffected by its row moving, so a reorder or a removal reuses\n // every element it already built; one that did has to be asked again\n // for a row that moved, since the index is part of what it drew.\n const positional = render.length >= 2;\n return defer(() => {\n const held = new Map<string | number, Row<T>>();\n let previous: readonly UiChild[] | null = null;\n let generation = 0;\n return items.pipe(\n map(list => {\n previous = reconcile(held, previous, ++generation, list, keyOf, render, positional);\n return previous;\n }),\n distinctUntilChanged()\n );\n });\n}\n\n/** One row's element, and what it was built from. */\ninterface Row<T> {\n item: T;\n index: number;\n child: UiChild;\n /** The pass that last saw this key; anything older has left the list. */\n generation: number;\n}\n\n/**\n * Builds the children for one emission, reusing every row it can.\n *\n * The result is the previous array itself when nothing about the list\n * changed, so the `distinctUntilChanged` above it ends the emission and\n * the builder is never entered. A new array is allocated at the first\n * row that differs and not before, which is what keeps an unchanged\n * emission free rather than merely cheap.\n */\nfunction reconcile<T>(\n held: Map<string | number, Row<T>>,\n previous: readonly UiChild[] | null,\n generation: number,\n list: readonly T[],\n keyOf: (item: T, index: number) => string | number,\n render: (item: T, index: number) => UiChild,\n positional: boolean\n): readonly UiChild[] {\n let next: UiChild[] | null = null;\n for (let index = 0; index < list.length; index++) {\n const item = list[index] as T;\n const key = keyOf(item, index);\n const row = held.get(key);\n if (row !== undefined && row.generation === generation) {\n throw new Error(`Duplicate key '${String(key)}' in a list: two rows claim the same identity.`);\n }\n let child: UiChild;\n if (row !== undefined && Object.is(row.item, item) && (!positional || row.index === index)) {\n child = row.child;\n row.index = index;\n row.generation = generation;\n } else {\n child = keyed(render(item, index), key);\n if (row === undefined) {\n held.set(key, { item, index, child, generation });\n } else {\n row.item = item;\n row.index = index;\n row.child = child;\n row.generation = generation;\n }\n }\n if (next === null && (previous === null || index >= previous.length || previous[index] !== child)) {\n next = previous === null ? [] : previous.slice(0, index);\n }\n next?.push(child);\n }\n for (const [key, row] of held) {\n if (row.generation !== generation) {\n held.delete(key);\n }\n }\n if (next === null && previous !== null && previous.length !== list.length) {\n // Nothing before the end changed and the list got shorter: the\n // rows that remain are the ones already in `previous`.\n next = previous.slice(0, list.length);\n }\n return next ?? previous ?? EMPTY;\n}\n\nconst EMPTY: readonly UiChild[] = [];\n\nfunction keyFunction<T>(by: EachKey<T> | undefined): (item: T, index: number) => string | number {\n if (by === undefined) {\n return (_item, index) => index;\n }\n if (typeof by === 'function') {\n return by;\n }\n return item => (item as Record<string, unknown>)[by] as string | number;\n}\n\n/**\n * Puts the row's key on the element the render function returned.\n *\n * An element that named its own key keeps it, since the author meant\n * something by it. An observable row cannot carry one at all: its\n * fragment anchor is identified by position, so a list of them\n * reorders wrongly, and it is worth saying so rather than leaving a\n * reorder to look like corruption.\n */\nfunction keyed(child: UiChild, key: string | number): UiChild {\n if (isComponentLikeElement(child)) {\n return child.key === undefined || child.key === null ? { ...child, key } : child;\n }\n if (isObservable(child)) {\n throw new Error(\n `A row of a list is an observable, which cannot carry the key '${String(key)}'. ` +\n `Return an element or a component from the row function and bind the observable inside it.`\n );\n }\n const element = child as UiElement;\n const existing = element.props.key;\n if (existing !== undefined && existing !== null) {\n return element;\n }\n return { ...element, props: { ...element.props, key } };\n}\n\n/** Said once: every unkeyed list has the same problem and the same fix. */\nlet warnedAboutIndexKeys = false;\n\nfunction warnIndexKeyed(render: (item: never, index: number) => UiChild): void {\n if (warnedAboutIndexKeys) {\n return;\n }\n warnedAboutIndexKeys = true;\n const what = render.name === '' ? 'a list' : `the list drawn by '${render.name}'`;\n console.warn(\n `${what} is keyed by index, because \\`each\\` was given no \\`by\\`. A row inserted or removed ` +\n `anywhere but the end then shifts every row after it onto a different node: state moves between rows, ` +\n `a text field keeps the wrong value, and an animation runs on the wrong element. ` +\n `Pass \\`by\\` naming what identifies a row: by=\"id\", or by={row => row.id}.`\n );\n}\n","import { Observable, defer, distinctUntilChanged, map, of } from 'rxjs';\n\nimport { isComponentLikeElement, isObservable, type UiChild, type UiElement } from 'gesso-core';\n\nexport interface ShowProps {\n /** The condition: a cell, any Observable, or a plain value. */\n readonly when: Observable<unknown> | unknown;\n /** What to draw while it is truthy. */\n readonly children: () => UiChild;\n /** What to draw while it is not. Nothing, by default. */\n readonly otherwise?: () => UiChild;\n}\n\n/**\n * One child, while a condition holds.\n *\n * <Show when={liked}>{() => <Icon path={ICONS.heart} />}</Show>\n *\n * `{cond && <x />}` already works when `cond` is a plain value, because\n * JSX drops a `false` child. A reactive `cond` had to become\n * `cond.pipe(map(on => (on ? [<x key=\"x\" />] : [])))`, which is the\n * fifty-six-block idiom in the two applications, and the discontinuity\n * between the two forms is the surprise this removes.\n *\n * The child is built once, on the first frame it is shown, and kept\n * while it is shown: a condition that emits `true` again draws nothing\n * and reconciles nothing. It carries a stable key, so the node it\n * built is the node it gets back when the condition returns, and it\n * adds no node of its own: what a `Show` puts under its parent is the\n * child, or nothing, and never a wrapper that layout would have to\n * account for.\n *\n * Anything inside it that changes is bound as usual. The function is\n * not re-run when the *data* changes, only when the condition does, so\n * a `Show` whose child shows a value binds that value rather than\n * reading it.\n */\nexport function Show(props: ShowProps): Observable<readonly UiChild[]> {\n return show(props.when, props.children, props.otherwise);\n}\n\n/**\n * The function form of `Show`:\n *\n * {show(liked, () => <Icon path={ICONS.heart} />)}\n */\nexport function show(\n when: Observable<unknown> | unknown,\n children: () => UiChild,\n otherwise?: () => UiChild\n): Observable<readonly UiChild[]> {\n const condition = isObservable(when) ? (when as Observable<unknown>) : of(when);\n return defer(() => {\n let shown: readonly UiChild[] | null = null;\n let hidden: readonly UiChild[] | null = null;\n return condition.pipe(\n map(value => Boolean(value)),\n distinctUntilChanged(),\n map(on => {\n if (on) {\n shown ??= [keyed(children(), 'when')];\n return shown;\n }\n hidden ??= otherwise === undefined ? EMPTY : [keyed(otherwise(), 'otherwise')];\n return hidden;\n })\n );\n });\n}\n\nconst EMPTY: readonly UiChild[] = [];\n\n/**\n * Gives the branch its stable key, so returning to it returns to the\n * node it built. An element that named its own key keeps it.\n */\nfunction keyed(child: UiChild, key: string): UiChild {\n if (isComponentLikeElement(child)) {\n return child.key === undefined || child.key === null ? { ...child, key } : child;\n }\n if (isObservable(child)) {\n return child;\n }\n const element = child as UiElement;\n const existing = element.props.key;\n if (existing !== undefined && existing !== null) {\n return element;\n }\n return { ...element, props: { ...element.props, key } };\n}\n","export const COMPONENT_METADATA_KEY = Symbol('gesso:component:metadata');\n\nexport interface ComponentMetadata {\n tag: string;\n inputs: Set<string>;\n states: Set<string>;\n injects: Map<string, Function>;\n /** Property name → the channel token it resolves to (`@Channel`). */\n channels: Map<string, { name: string }>;\n}\n\nconst metadataStore = new WeakMap<Function, ComponentMetadata>();\n\nexport function getComponentMetadata(constructor: Function): ComponentMetadata {\n let metadata = metadataStore.get(constructor);\n if (metadata === undefined) {\n metadata = { tag: constructor.name, inputs: new Set(), states: new Set(), injects: new Map(), channels: new Map() };\n metadataStore.set(constructor, metadata);\n }\n return metadata;\n}\n\nexport function hasComponentMetadata(constructor: Function): boolean {\n return metadataStore.has(constructor);\n}\n","import type { ComponentElement } from './ComponentElement';\nimport type { ComponentArgs, ComponentProps, ComponentType } from './FunctionComponent';\nimport { getComponentMetadata } from './metadata';\n\n/**\n * Creates a ComponentElement for use as a child of a UiElement.\n *\n * Column(\n * createComponent(Counter, { label: 'Count' }),\n * createComponent(TodoItem, { todo }, todo.id)\n * )\n *\n * The component may be a class extending Component or a function\n * `(props, ctx) => UiChild`. Props are typed from the component: a\n * class's `input()` fields, or a function's `Inputs<P>` parameter. A\n * misspelled prop, a value of the wrong type, and a missing required\n * prop are compile errors; props may be omitted only when none is\n * required.\n */\nexport function createComponent<C extends ComponentType>(\n component: C,\n ...args: ComponentArgs<C>\n): ComponentElement<ComponentProps<C>> {\n const [props, key] = args as [ComponentProps<C> | undefined, string | number | undefined];\n const metadata = getComponentMetadata(component);\n return {\n kind: 'component',\n tag: metadata.tag,\n component,\n props: (props ?? {}) as ComponentProps<C>,\n key\n };\n}\n"],"mappings":"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"}
|