lecodes-sdk 2.2.2 → 2.2.4
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/global.d.ts +1 -0
- package/dist/types/animate/tween/spec.d.ts +4 -5
- package/dist/types/server/db/fields.d.ts +6 -3
- package/dist/types/server/db/marci/query.d.ts +21 -2
- package/dist/types/server/db/types.d.ts +26 -12
- package/dist/types/server/errors.d.ts +4 -1
- package/dist/types/server/files/models.d.ts +4 -0
- package/dist/types/server/inject.d.ts +6 -0
- package/dist/types/ui/UINode.d.ts +3 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +64 -18
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +2 -2
- package/prompts/ui.md +3 -1
- package/src/animate/tween/spec.ts +8 -13
- package/src/chisel.ts +2 -2
- package/src/compile/serverSplit.ts +5 -3
- package/src/server/db/defineDb.ts +17 -21
- package/src/server/db/fields.ts +7 -4
- package/src/server/db/httpTransport.ts +19 -3
- package/src/server/db/marci/query.ts +34 -8
- package/src/server/db/types.ts +33 -14
- package/src/server/errors.ts +4 -1
- package/src/server/files/db.ts +26 -7
- package/src/server/files/host.ts +45 -17
- package/src/server/files/models.ts +4 -0
- package/src/server/host.ts +3 -3
- package/src/server/inject.ts +3 -0
- package/src/server/runtime.ts +65 -4
- package/src/ui/UINode.ts +3 -0
- package/src/ui/UIVirtualizedList.ts +159 -31
- package/src/ui/transitions.ts +1 -1
- package/src/version.ts +1 -1
package/dist/global.d.ts
CHANGED
|
@@ -586,6 +586,7 @@ declare global {
|
|
|
586
586
|
type With<N, A extends Aspect<any, any, any>> = SDK.With<N, A>
|
|
587
587
|
// ---- server globals (available in *.server.ts; using them in client code is a build error) ----
|
|
588
588
|
const ApiError: typeof SERVER.ApiError
|
|
589
|
+
const SvgSource: typeof SERVER.SvgSource
|
|
589
590
|
const channel: typeof SERVER.channel
|
|
590
591
|
const defineDb: typeof SERVER.defineDb
|
|
591
592
|
const model: typeof SERVER.model
|
|
@@ -124,11 +124,10 @@ export declare const uiValue: (v: unknown) => ({
|
|
|
124
124
|
* function is unknown (the caller falls back to matrix decomposition on the host). `scale(2)` and
|
|
125
125
|
* `translate(5px)` complete their second argument like CSS.
|
|
126
126
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
export declare const parseTransformList: (s: string, percent?: boolean) => {
|
|
127
|
+
* A `%` in a translate is a percent of the target's own box (`translateX(100%)` → `translateX%`
|
|
128
|
+
* [100]; a translate mixing the units splits into its two axes), the way the runtime reads a
|
|
129
|
+
* style's transform: the box is measured when the matrix is composed, so a track fits any size. */
|
|
130
|
+
export declare const parseTransformList: (s: string) => {
|
|
132
131
|
sig: string;
|
|
133
132
|
lanes: number[];
|
|
134
133
|
} | null;
|
|
@@ -41,7 +41,8 @@ export type FieldDef = {
|
|
|
41
41
|
kind: FieldKind;
|
|
42
42
|
scalar?: ScalarKind;
|
|
43
43
|
optional: boolean;
|
|
44
|
-
unique
|
|
44
|
+
/** `true` = `@unique`; a string = `@unique(<group>)`: the fields of a model sharing the group are unique together. */
|
|
45
|
+
unique: boolean | string;
|
|
45
46
|
index: boolean;
|
|
46
47
|
hasDefault: boolean;
|
|
47
48
|
default?: unknown;
|
|
@@ -73,8 +74,10 @@ export interface Field<T, M extends FieldMeta = FieldMeta> {
|
|
|
73
74
|
readonly def: FieldDef;
|
|
74
75
|
/** Nullable column (`String?`). */
|
|
75
76
|
optional(): Field<T, SetMeta<M, "optional", true>>;
|
|
76
|
-
/** `@unique` (on a relation: one-to-one).
|
|
77
|
-
|
|
77
|
+
/** `@unique` (on a relation: one-to-one). With a group name, the fields of the model that share it are
|
|
78
|
+
* unique TOGETHER — `user: t.one("User").unique("pair"), post: t.one("Post").unique("pair")` is one
|
|
79
|
+
* like per user and post; a null part is not constrained. */
|
|
80
|
+
unique(group?: string): Field<T, M>;
|
|
78
81
|
/** `@index` — used by `$where`, `$order`, aggregations. */
|
|
79
82
|
index(): Field<T, M>;
|
|
80
83
|
/** `@default(...)`; `"now"` on `t.date()` → `now()`. Makes the field optional on insert. */
|
|
@@ -197,6 +197,8 @@ export type MarciOp = {
|
|
|
197
197
|
query?: any;
|
|
198
198
|
data?: any;
|
|
199
199
|
id?: any;
|
|
200
|
+
key?: any;
|
|
201
|
+
onInsert?: any;
|
|
200
202
|
journal?: JournalArgs;
|
|
201
203
|
};
|
|
202
204
|
/** The arguments of a journal action (`$journalOpen` / `$journalRead` / `$journalDrop`). `wait` is in seconds. */
|
|
@@ -255,9 +257,11 @@ export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends Promise
|
|
|
255
257
|
readonly [__sel]: Effective<T, Sel>;
|
|
256
258
|
/** Filter (marcidb `$where`). Repeated calls are ANDed. */
|
|
257
259
|
where(where: T["where"]): Query<T, Sel>;
|
|
258
|
-
/** Sort
|
|
260
|
+
/** Sort: `order("age", "desc")` or `order({ age: "desc" })`. A second `order()` adds the next key — ties on
|
|
261
|
+
* the first are broken by it (`order("createdAt", "desc").order("id")`); the keys may also come as an array
|
|
262
|
+
* in one call. Several keys sort in memory. */
|
|
259
263
|
order(field: keyof T["order"] & string, direction?: "asc" | "desc"): Query<T, Sel>;
|
|
260
|
-
order(order: T["order"]): Query<T, Sel>;
|
|
264
|
+
order(order: T["order"] | T["order"][]): Query<T, Sel>;
|
|
261
265
|
limit(n: number): Query<T, Sel>;
|
|
262
266
|
skip(n: number): Query<T, Sel>;
|
|
263
267
|
/** Keyset cursor: rows strictly after this id in the current order (`$cursor`). */
|
|
@@ -287,6 +291,18 @@ export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends Promise
|
|
|
287
291
|
select: S;
|
|
288
292
|
}): Promise<Rows<T, S>>;
|
|
289
293
|
update(id: IdArg<T["id"]>, data: T["update"]): Op<void>;
|
|
294
|
+
/** Writes the row `key` names — the id, or the fields of ONE unique index (a `@unique` field, all the fields of
|
|
295
|
+
* a `@unique(<name>)` group): an update of `data` when it exists, an insert of key + data when not. `data` is
|
|
296
|
+
* in the insert form (plain values, `{ id }` for a relation; no `$` operators). `onInsert` adds fields used
|
|
297
|
+
* only when the row is created. Resolves to the id, like `insert`. */
|
|
298
|
+
upsert<K extends Partial<T["insert"]> | T["id"]>(key: K, data: Omit<T["insert"], keyof K>, options?: {
|
|
299
|
+
onInsert?: Partial<T["insert"]>;
|
|
300
|
+
}): Op<T["id"]>;
|
|
301
|
+
/** The row written, by `select`'s shape — as stored, whichever path the upsert took. */
|
|
302
|
+
upsert<K extends Partial<T["insert"]> | T["id"], S extends T["query"]>(key: K, data: Omit<T["insert"], keyof K>, options: {
|
|
303
|
+
onInsert?: Partial<T["insert"]>;
|
|
304
|
+
select: S;
|
|
305
|
+
}): Promise<Rows<T, S>>;
|
|
290
306
|
/** Applies `data` to every row the chain's `where` matches (all rows without one); resolves to the number of rows. */
|
|
291
307
|
updateMany(data: T["update"]): Op<number>;
|
|
292
308
|
/** @deprecated pass the filter through the chain: `db.user.where(w).updateMany(data)` (removed in the next minor). */
|
|
@@ -354,6 +370,9 @@ export type QueryLayerOptions = {
|
|
|
354
370
|
run(op: MarciOp): Promise<any>;
|
|
355
371
|
/** Hook for `insert` payloads (the lecodes SDK generates uuid ids here). */
|
|
356
372
|
prepareInsert?(model: string, data: any): any;
|
|
373
|
+
/** Hook for the `onInsert` part of an `upsert` — the fields used only when the row is created (the lecodes SDK
|
|
374
|
+
* puts a uuid id here). Receives what the caller gave (or undefined), returns what is sent. */
|
|
375
|
+
prepareUpsert?(model: string, onInsert: any): any;
|
|
357
376
|
};
|
|
358
377
|
/**
|
|
359
378
|
* The query builder over a set of models and a `run` function. Returns `op(descriptor)` — a lazy operation
|
|
@@ -66,20 +66,21 @@ export type Scalars<S, N extends keyof S> = Pick<Row<S, N>, (ScalarKeys<FieldsOf
|
|
|
66
66
|
export type ScalarSelect<S, N extends keyof S> = {
|
|
67
67
|
[K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]: true;
|
|
68
68
|
};
|
|
69
|
-
type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld
|
|
69
|
+
type FieldInsert<S, Fld, FW, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>, FW>> : M["kind"] extends "file" ? Nullable<M, Arr<M, FW>> : Nullable<M, Arr<M, TsOf<Fld>>>;
|
|
70
70
|
type RequiredKeys<F extends Fields> = {
|
|
71
71
|
[K in keyof F]: K extends IdKeys<F> ? never : MetaOf<F[K]>["optional"] extends true ? never : MetaOf<F[K]>["hasDefault"] extends true ? never : MetaOf<F[K]>["kind"] extends "many" | "list" ? never : MetaOf<F[K]>["kind"] extends "file" ? (MetaOf<F[K]>["array"] extends true ? never : K) : K;
|
|
72
72
|
}[keyof F];
|
|
73
73
|
type Simplify<T> = {
|
|
74
74
|
[K in keyof T]: T[K];
|
|
75
75
|
} & {};
|
|
76
|
-
type InsertOf<S, F extends Fields> = Simplify<{
|
|
77
|
-
[K in RequiredKeys<F>]: FieldInsert<S, F[K]>;
|
|
76
|
+
type InsertOf<S, F extends Fields, FW = FileWrite> = Simplify<{
|
|
77
|
+
[K in RequiredKeys<F>]: FieldInsert<S, F[K], FW>;
|
|
78
78
|
} & {
|
|
79
|
-
[K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K]>;
|
|
79
|
+
[K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K], FW>;
|
|
80
80
|
}>;
|
|
81
|
-
|
|
82
|
-
type
|
|
81
|
+
/** `FW` is what a file field is written with: an upload of the call; in a seed, a resource of the project as well. */
|
|
82
|
+
export type Insert<S, N extends keyof S, FW = FileWrite> = InsertOf<S, FieldsOf<S[N]>, FW>;
|
|
83
|
+
type StructInsert<S, F extends Fields, FW = FileWrite> = InsertOf<S, F, FW>;
|
|
83
84
|
type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>> : M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>> : M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null : M["kind"] extends "file" ? Nullable<M, Arr<M, FileWrite | StoredFile>> : M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>> : Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>;
|
|
84
85
|
type UpdateOf<S, F extends Fields> = {
|
|
85
86
|
[K in Exclude<keyof F, IdKeys<F>>]?: FieldUpdate<S, F[K]>;
|
|
@@ -103,7 +104,8 @@ type IdWhere<S, N extends keyof S> = {
|
|
|
103
104
|
export type Where<S, N extends keyof S> = WhereValue<WhereFieldsOf<S, FieldsOf<S[N]>> & IdWhere<S, N>>;
|
|
104
105
|
type StructWhereFields<S, F extends Fields> = WhereFieldsOf<S, F>;
|
|
105
106
|
type OrderKeys<S, N extends keyof S> = (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>;
|
|
106
|
-
/**
|
|
107
|
+
/** One field and its direction. Several keys are several of these, in an array (`$order: [{ createdAt: "desc" },
|
|
108
|
+
* { id: "asc" }]`) or chained `.order()` calls — an object with two keys is refused, since JSON promises no key order. */
|
|
107
109
|
export type Order<S, N extends keyof S> = {
|
|
108
110
|
[K in OrderKeys<S, N>]: {
|
|
109
111
|
[P in K]: "asc" | "desc";
|
|
@@ -114,7 +116,7 @@ export type Order<S, N extends keyof S> = {
|
|
|
114
116
|
/** The query object — marcidb's wire form: a select shape plus the `$`-clauses. */
|
|
115
117
|
export type QueryObject<S, N extends keyof S> = Select<S, N> & {
|
|
116
118
|
$where?: Where<S, N>;
|
|
117
|
-
$order?: Order<S, N
|
|
119
|
+
$order?: Order<S, N> | Order<S, N>[];
|
|
118
120
|
$limit?: number;
|
|
119
121
|
$skip?: number;
|
|
120
122
|
$cursor?: Id<S, N>;
|
|
@@ -128,7 +130,7 @@ export type AggregateQuery<S, N extends keyof S> = {
|
|
|
128
130
|
$max?: (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
|
|
129
131
|
};
|
|
130
132
|
/** The per-model type bag marcidb's generic `Query<T>` is parametrised by (what its codegen emits as `UserTypes`). */
|
|
131
|
-
export type Types<S, N extends keyof S> = {
|
|
133
|
+
export type Types<S, N extends keyof S, FW = FileWrite> = {
|
|
132
134
|
name: N & string;
|
|
133
135
|
model: Row<S, N>;
|
|
134
136
|
id: Id<S, N>;
|
|
@@ -137,7 +139,7 @@ export type Types<S, N extends keyof S> = {
|
|
|
137
139
|
query: QueryObject<S, N>;
|
|
138
140
|
where: Where<S, N>;
|
|
139
141
|
order: Order<S, N>;
|
|
140
|
-
insert: Insert<S, N>;
|
|
142
|
+
insert: Insert<S, N, FW>;
|
|
141
143
|
update: Update<S, N>;
|
|
142
144
|
aggregate: AggregateQuery<S, N>;
|
|
143
145
|
reindex: false;
|
|
@@ -148,7 +150,7 @@ export type Types<S, N extends keyof S> = {
|
|
|
148
150
|
* `updateMany(data)`/`deleteMany()` write through the chain's `where`; a query placed in another query's
|
|
149
151
|
* select is a sub-query. `Sel` is the projection: id + scalars until `select(...)` sets a shape.
|
|
150
152
|
*/
|
|
151
|
-
export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N
|
|
153
|
+
export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N>, FW = FileWrite> = MarciQuery<Types<S, N, FW>, Sel>;
|
|
152
154
|
/** The rows a query with projection `Q` resolves to (no field keys = id + scalars; sub-queries resolved). */
|
|
153
155
|
export type Result<S, N extends keyof S, Q> = Rows<Types<S, N>, Q>;
|
|
154
156
|
/** `db.<model>` — the root query of a model. */
|
|
@@ -168,7 +170,18 @@ type DbBase<S extends Schema> = {
|
|
|
168
170
|
}>;
|
|
169
171
|
/** The `.marci` schema text this db syncs with. */
|
|
170
172
|
readonly $schema: string;
|
|
173
|
+
/**
|
|
174
|
+
* The first rows of this database: run by the host ONCE, on a database that is empty, wherever the
|
|
175
|
+
* database opens (a check, a dev backend, the first deploy). Part of the definition, like sign-in:
|
|
176
|
+
* written on `defineDb(...)` itself. Inside, a file field takes a resource of the project — the path
|
|
177
|
+
* of a picture the design shows (`photo: row.photo`) — and stores it the way an upload is stored.
|
|
178
|
+
*/
|
|
179
|
+
seed(fn: (db: SeedDb<S>) => Promise<void>): Db<S>;
|
|
171
180
|
};
|
|
181
|
+
/** The db a seed writes through: the same queries, a file field taking a resource of the project (its path) too. */
|
|
182
|
+
export type SeedDb<S extends Schema> = {
|
|
183
|
+
[N in keyof S & string as Uncapitalize<N>]: Query<Full<S>, N, ScalarSelect<Full<S>, N>, FileWrite | string>;
|
|
184
|
+
} & Pick<DbBase<S>, "$transaction">;
|
|
172
185
|
export type Db<S extends Schema> = DbBase<S> & {
|
|
173
186
|
/**
|
|
174
187
|
* Sign-in for this database: `model` is the project's model of a user. Adds the platform's `Session`
|
|
@@ -180,8 +193,9 @@ export type Db<S extends Schema> = DbBase<S> & {
|
|
|
180
193
|
model: N;
|
|
181
194
|
}): DbWithAuth<S, N>;
|
|
182
195
|
};
|
|
183
|
-
export type DbWithAuth<S extends Schema, N extends keyof S & string> = DbBase<S> & {
|
|
196
|
+
export type DbWithAuth<S extends Schema, N extends keyof S & string> = Omit<DbBase<S>, "seed"> & {
|
|
184
197
|
readonly auth: Auth<S, N>;
|
|
198
|
+
seed(fn: (db: SeedDb<S>) => Promise<void>): DbWithAuth<S, N>;
|
|
185
199
|
};
|
|
186
200
|
/**
|
|
187
201
|
* The user of the current request, as a query over the project's user model: `select(shape)` picks the
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `throw new ApiError(404, "Not found")` inside an endpoint → the client's promise rejects with the
|
|
3
|
-
* same status + message.
|
|
3
|
+
* same status + message. A write the database refused for a reason a person could have caused reaches
|
|
4
|
+
* the app the same way, in the database's words: a taken unique value, a pointer at no row, a delete
|
|
5
|
+
* other rows forbid are 409, a row that is gone 404. Any other throw is a 500 (message hidden, logged
|
|
6
|
+
* to the server console).
|
|
4
7
|
*/
|
|
5
8
|
export declare class ApiError extends Error {
|
|
6
9
|
readonly status: number;
|
|
@@ -124,6 +124,10 @@ export type FilesSeam = {
|
|
|
124
124
|
/** The id of the `File` row behind an upload of the current request; undefined for anything else —
|
|
125
125
|
* a value the app sent as JSON is never an upload, whatever it looks like. */
|
|
126
126
|
uploaded(value: unknown): string | undefined;
|
|
127
|
+
/** While a seed runs: the id of a `File` row made from a resource of the project named by its path
|
|
128
|
+
* (what a path literal of the design is in the server bundle); undefined for a path that is no file
|
|
129
|
+
* of the project. Absent outside a seed: a string the app sent is never a file. */
|
|
130
|
+
resource?(path: string): Promise<string | undefined>;
|
|
127
131
|
url(id: string, name: string): string;
|
|
128
132
|
/** Make the upload `id` fit for a `t.image()` field (its bytes are rewritten in place) and say what
|
|
129
133
|
* it is now. Throws an `ApiError` for what is no picture. */
|
|
@@ -3,3 +3,9 @@ export { ApiError } from "./errors";
|
|
|
3
3
|
export { channel } from "./channel";
|
|
4
4
|
export { request } from "./context";
|
|
5
5
|
export type { StoredFile, StoredImage } from "./files/models";
|
|
6
|
+
/** The value an SVG source is to the client (`assetIcon(…)` compiles to `SvgSource(…)`): a shared module of
|
|
7
|
+
* the design that names an icon per row loads on the server too. The server draws nothing with it. */
|
|
8
|
+
export declare const SvgSource: (svg: string) => {
|
|
9
|
+
svg: string;
|
|
10
|
+
tintColor: string | null;
|
|
11
|
+
};
|
|
@@ -27,6 +27,9 @@ export type Color = ColorInput | null;
|
|
|
27
27
|
/** Style props every element accepts. */
|
|
28
28
|
export type BaseStyle = {
|
|
29
29
|
opacity?: number | `${number}`;
|
|
30
|
+
/** A CSS-style list of `translate` / `scale` / `rotate`. A `%` in a translate is a percent of the
|
|
31
|
+
* element's own box (`translate(-50%, -50%)` centres it on its corner), kept as a percent: it
|
|
32
|
+
* follows the box through every layout. */
|
|
30
33
|
transform?: string | null;
|
|
31
34
|
/** Pivot of `transform` (CSS `transform-origin` subset): one or two values, each a keyword
|
|
32
35
|
* (`left`/`center`/`right`, `top`/`center`/`bottom`), a percentage of the box, or a px length
|
|
@@ -6,20 +6,14 @@ export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & {
|
|
|
6
6
|
* matter how the history is scrolled. */
|
|
7
7
|
keyboardDismissMode?: "interactive" | "scroll" | "none";
|
|
8
8
|
};
|
|
9
|
-
|
|
10
|
-
/** The rows, read reactively: the list follows the signals this reads, the way a container's
|
|
11
|
-
* function children do. Rows are told apart by `keyOf`; a row whose item is another object
|
|
12
|
-
* re-renders. Appended rows and rows added at the start are told from a rewrite (a chat at
|
|
13
|
-
* its bottom follows what is appended; older history above moves nothing). */
|
|
14
|
-
data: () => T[];
|
|
9
|
+
interface VListBaseConfig<T> {
|
|
15
10
|
/** Stable unique key of an item. */
|
|
16
11
|
keyOf: (item: T) => string;
|
|
17
12
|
/** Subtree factory — called when the item enters the window; the subtree lives until it
|
|
18
13
|
* leaves. */
|
|
19
14
|
render: (item: T) => UINodeChild;
|
|
20
15
|
/** Height estimate (px) used before the first measurement — a number, or a per-item function
|
|
21
|
-
* (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`).
|
|
22
|
-
* added. */
|
|
16
|
+
* (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`). Read when the item is added. */
|
|
23
17
|
estimatedHeight: number | ((item: T) => number);
|
|
24
18
|
/** Extra px around the viewport kept mounted. Default: one viewport height. */
|
|
25
19
|
overscan?: number;
|
|
@@ -27,9 +21,23 @@ export interface VListConfig<T> {
|
|
|
27
21
|
* list sits at the bottom. */
|
|
28
22
|
inverted?: boolean;
|
|
29
23
|
}
|
|
30
|
-
/** A
|
|
31
|
-
*
|
|
32
|
-
export interface
|
|
24
|
+
/** A list fed by `data`: the rows are read reactively, the way a container's function children
|
|
25
|
+
* are. Such a list has no imperative feed (`append` and the rest are not on its type). */
|
|
26
|
+
export interface VListBoundConfig<T> extends VListBaseConfig<T> {
|
|
27
|
+
/** The rows, read reactively: the list follows the signals this reads. Rows are told apart by
|
|
28
|
+
* `keyOf`; a row whose item is another object re-renders. The change is told from the keys:
|
|
29
|
+
* rows gone are removals, new rows are inserts where they stand (a chat at its bottom follows
|
|
30
|
+
* an append; rows above the reader move nothing), a reorder rewrites the keys. */
|
|
31
|
+
data: () => T[];
|
|
32
|
+
}
|
|
33
|
+
/** A list fed by hand: `setData` / `append` / `prepend` / `update` / `removeByKey`. */
|
|
34
|
+
export interface VListFedConfig<T> extends VListBaseConfig<T> {
|
|
35
|
+
data?: undefined;
|
|
36
|
+
}
|
|
37
|
+
export type VListConfig<T> = VListBoundConfig<T> | VListFedConfig<T>;
|
|
38
|
+
/** What both forms of the list share: the items held, scrolling, the scroll / refresh / edge
|
|
39
|
+
* callbacks. */
|
|
40
|
+
export interface UIVirtualizedListBase<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
|
|
33
41
|
readonly type: "vlist";
|
|
34
42
|
/** Number of data items (mounted or not). */
|
|
35
43
|
readonly itemCount: number;
|
|
@@ -51,7 +59,29 @@ export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtuali
|
|
|
51
59
|
/** Same, for the top edge — chat history loading. */
|
|
52
60
|
onStartReached(thresholdPx: number, callback: () => void): this;
|
|
53
61
|
}
|
|
54
|
-
|
|
62
|
+
/** A virtualized list fed by `data` (a function over signals): write the signal, the list follows. */
|
|
63
|
+
export interface UIBoundVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {
|
|
64
|
+
}
|
|
65
|
+
/** A virtualized list fed by hand. Each call goes straight to the runtime: `append` is O(1). */
|
|
66
|
+
export interface UIFedVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {
|
|
67
|
+
/** Replace the data wholesale — diffed by key like a `data` write (vanished keys are removed,
|
|
68
|
+
* new ones inserted where they stand, a reorder rewrites). */
|
|
69
|
+
setData(items: T[]): this;
|
|
70
|
+
/** Append items to the end (auto-scrolls when `inverted` and resting at the bottom). */
|
|
71
|
+
append(...items: T[]): this;
|
|
72
|
+
/** Insert at the start WITHOUT a scroll jump (the runtime compensates the offset). */
|
|
73
|
+
prepend(...items: T[]): this;
|
|
74
|
+
/** Update items with the same keys: mounted ones re-render; the cached height persists until
|
|
75
|
+
* re-measured. */
|
|
76
|
+
update(...items: T[]): this;
|
|
77
|
+
/** Remove items by key. */
|
|
78
|
+
removeByKey(...keys: string[]): this;
|
|
79
|
+
}
|
|
80
|
+
/** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
|
|
81
|
+
* and chats. Fed by `data`, a function over signals, or by hand (`append` and the rest); which
|
|
82
|
+
* one is the config's and the type's. */
|
|
83
|
+
export type UIVirtualizedList<T = unknown> = UIBoundVirtualizedList<T> | UIFedVirtualizedList<T>;
|
|
84
|
+
export declare class VListElement<T> extends Element<"vlist"> implements UIFedVirtualizedList<T> {
|
|
55
85
|
private readonly sl;
|
|
56
86
|
private readonly erl;
|
|
57
87
|
private readonly strl;
|
|
@@ -64,15 +94,28 @@ export declare class VListElement<T> extends Element<"vlist"> implements UIVirtu
|
|
|
64
94
|
private estimate;
|
|
65
95
|
private readonly _overscan;
|
|
66
96
|
private readonly _inverted;
|
|
97
|
+
private readonly _bound;
|
|
67
98
|
private _endThreshold;
|
|
68
99
|
private _startThreshold;
|
|
69
100
|
private _inited;
|
|
70
101
|
constructor(config: VListConfig<T>);
|
|
71
102
|
private _ensure;
|
|
72
|
-
/** The
|
|
73
|
-
*
|
|
74
|
-
|
|
103
|
+
/** The two feeds are exclusive per list: a hand-made change to a list `data` feeds would be
|
|
104
|
+
* undone by the binding's next run, so it is refused and logged. */
|
|
105
|
+
private _fed;
|
|
106
|
+
/** The data as it is now against what the runtime holds. Keys gone are removals, keys new are
|
|
107
|
+
* inserts where they stand in the new order — both anchored by the runtime (a chat follows an
|
|
108
|
+
* append, rows above the reader move nothing) — as long as the keys kept keep their order;
|
|
109
|
+
* a reorder rewrites the keys (measured heights stay). A mounted row whose item is another
|
|
110
|
+
* object re-renders. */
|
|
75
111
|
private _apply;
|
|
112
|
+
/** The new rows of `items` (keys not held yet, repeats dropped), registered. */
|
|
113
|
+
private _register;
|
|
114
|
+
setData(items: T[]): this;
|
|
115
|
+
append(...items: T[]): this;
|
|
116
|
+
prepend(...items: T[]): this;
|
|
117
|
+
update(...items: T[]): this;
|
|
118
|
+
removeByKey(...keys: string[]): this;
|
|
76
119
|
get itemCount(): number;
|
|
77
120
|
getItem(key: string): T | undefined;
|
|
78
121
|
scrollTo(offset: number, animated?: boolean): void;
|
|
@@ -83,6 +126,9 @@ export declare class VListElement<T> extends Element<"vlist"> implements UIVirtu
|
|
|
83
126
|
onEndReached(thresholdPx: number, callback: () => void): this;
|
|
84
127
|
onStartReached(thresholdPx: number, callback: () => void): this;
|
|
85
128
|
}
|
|
86
|
-
/** Create a virtualized list
|
|
87
|
-
*
|
|
88
|
-
|
|
129
|
+
/** Create a virtualized list (`keyOf` + `render` + `estimatedHeight` required). With `data` the
|
|
130
|
+
* list follows the signals it reads and has no hand feed; without it, feed it with `setData` /
|
|
131
|
+
* `append` / `prepend` / `update` / `removeByKey`. Give it a size (`flexGrow: 1` as a screen body). */
|
|
132
|
+
export declare function UIVirtualizedList<T>(config: VListBoundConfig<T>): UIBoundVirtualizedList<T>;
|
|
133
|
+
export declare function UIVirtualizedList<T>(config: VListFedConfig<T>): UIFedVirtualizedList<T>;
|
|
134
|
+
export {};
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.2.
|
|
1
|
+
export declare const SDK_VERSION = "2.2.4";
|
|
2
2
|
/** The major of a semver string, or null when it is not one. */
|
|
3
3
|
export declare const sdkMajor: (version: string | null | undefined) => number | null;
|
|
4
4
|
/** The `// sdk: <version>` line of a compiled bundle's header (sdk/src/compile/header.ts), read from
|