chat-layout 0.1.5 → 1.0.0-2

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 CHANGED
@@ -1,27 +1,233 @@
1
1
  # chat-layout
2
2
 
3
- To install dependencies:
3
+ Canvas-based chat and timeline layout primitives with a v2 flex-style layout model.
4
+
5
+ The current recommended APIs are:
6
+
7
+ - `Flex` for row/column layout
8
+ - `FlexItem` for explicit `grow` / `shrink`
9
+ - `Place` for single-child horizontal placement
10
+ - `MultilineText` `align` / `physicalAlign` for text content alignment
11
+ - `ChatRenderer` plus `ListState` for virtualized chat rendering
12
+ - `memoRenderItem` for object items, or `memoRenderItemBy` when your stable key is primitive / explicit
13
+
14
+ **Layout Model**
15
+ `Flex` and `Place` split layout concerns more clearly than the older API:
16
+
17
+ - Use `new Flex(children, { direction: "row" | "column" })` for main-axis layout.
18
+ - Use `new FlexItem(child, { grow: 1 })` when a child should consume remaining space.
19
+ - Use `new FlexItem(child, { shrink: 1 })` when a child should participate in overflow redistribution under a finite main-axis cap.
20
+ - `Flex` shrink-wraps on the cross axis by default; `maxWidth` / `maxHeight` act as measurement caps rather than implicit fill signals.
21
+ - Use `alignItems` / `alignSelf: "stretch"` when a specific child should fill the container's computed cross axis.
22
+ - Use `new Place(child, { align: "start" | "center" | "end" })` when a single child should fill available width and then be placed left/center/right.
23
+ - Use `justifyContent`, `alignItems`, and `alignSelf` for container/item placement.
24
+ - Use `align: "start" | "center" | "end"` on `MultilineText` for logical alignment that matches `Place.align`.
25
+ - Use `physicalAlign: "left" | "center" | "right"` on `MultilineText` only when you explicitly want physical left/right semantics.
26
+ - `Text` / `MultilineText` preserve blank lines and edge whitespace by default; opt into cleanup with `whitespace: "trim-and-collapse"`.
27
+
28
+ **Example**
29
+ This is the recommended chat bubble shape used by [example/chat.ts](./example/chat.ts):
30
+
31
+ ```ts
32
+ type ChatItem = {
33
+ sender: string;
34
+ content: string;
35
+ reply?: {
36
+ sender: string;
37
+ content: string;
38
+ };
39
+ };
40
+
41
+ const renderItem = memoRenderItem((item: ChatItem): Node<C> => {
42
+ const senderLine = new Flex<C>(
43
+ [avatarDot, senderLabel],
44
+ {
45
+ direction: "row",
46
+ gap: 4,
47
+ mainAxisSize: "fit-content",
48
+ reverse: item.sender === "A",
49
+ },
50
+ );
51
+
52
+ const messageText = new FlexItem(
53
+ new MultilineText(item.content, {
54
+ lineHeight: 20,
55
+ font: "16px system-ui",
56
+ style: "black",
57
+ align: "start",
58
+ }),
59
+ { alignSelf: "start" },
60
+ );
61
+
62
+ const bubbleChildren: Node<C>[] = [];
63
+ if (item.reply != null) {
64
+ bubbleChildren.push(
65
+ new FlexItem(
66
+ new RoundedBox(
67
+ new Flex<C>(
68
+ [
69
+ new Text(item.reply.sender, {
70
+ lineHeight: 14,
71
+ font: "11px system-ui",
72
+ style: "#666",
73
+ }),
74
+ new MultilineText(item.reply.content, {
75
+ lineHeight: 16,
76
+ font: "13px system-ui",
77
+ style: "#444",
78
+ align: "start",
79
+ }),
80
+ ],
81
+ {
82
+ direction: "column",
83
+ gap: 2,
84
+ alignItems: "start",
85
+ },
86
+ ),
87
+ {
88
+ top: 5,
89
+ bottom: 5,
90
+ left: 8,
91
+ right: 8,
92
+ radii: 6,
93
+ fill: "#e2e2e2",
94
+ },
95
+ ),
96
+ { alignSelf: "stretch" },
97
+ ),
98
+ );
99
+ }
100
+ bubbleChildren.push(messageText);
101
+
102
+ const bubbleColumn = new Flex<C>(bubbleChildren, {
103
+ direction: "column",
104
+ gap: 6,
105
+ // The bubble itself stays intrinsic on the cross axis.
106
+ // Only the reply preview stretches to the bubble width.
107
+ alignItems: "start",
108
+ });
109
+
110
+ const content = new RoundedBox(
111
+ bubbleColumn,
112
+ {
113
+ top: 6,
114
+ bottom: 6,
115
+ left: 10,
116
+ right: 10,
117
+ radii: 8,
118
+ fill: "#ccc",
119
+ },
120
+ );
121
+
122
+ const body = new Flex<C>([senderLine, content], {
123
+ direction: "column",
124
+ alignItems: item.sender === "A" ? "end" : "start",
125
+ });
126
+
127
+ const alignedBody = new Place<C>(body, {
128
+ align: item.sender === "A" ? "end" : "start",
129
+ });
130
+
131
+ return new Place(
132
+ new PaddingBox(
133
+ new Flex<C>(
134
+ [
135
+ avatar,
136
+ // Opt into shrink so narrow viewports wrap the bubble body instead of overflowing the row.
137
+ new FlexItem(alignedBody, { grow: 1, shrink: 1 }),
138
+ new Fixed(32, 0),
139
+ ],
140
+ {
141
+ direction: "row",
142
+ gap: 4,
143
+ reverse: item.sender === "A",
144
+ },
145
+ ),
146
+ {
147
+ top: 4,
148
+ bottom: 4,
149
+ left: 4,
150
+ right: 4,
151
+ },
152
+ ),
153
+ {
154
+ align: item.sender === "A" ? "end" : "start",
155
+ },
156
+ );
157
+ });
158
+ ```
159
+
160
+ That combination gives you:
161
+
162
+ - explicit row/column structure
163
+ - explicit grow/shrink behavior through `FlexItem`
164
+ - left/right chat placement through `Place`
165
+ - wrapped message bubbles that respect available width without becoming full-width by default
166
+ - nested reply previews that use item-level cross-axis `stretch` to fill the bubble width
167
+ - opt-in overflow redistribution on the message body when the row runs out of main-axis space
168
+
169
+ In other words: a finite `maxWidth` / `maxHeight` limits measurement, but does not force the `Flex` container to fill the cross axis. If you want a child to fill the computed bubble width, mark that child with `alignSelf: "stretch"` (or inherit `alignItems: "stretch"` from the parent).
170
+
171
+ ## Flex shrink
172
+
173
+ - `FlexItemOptions.shrink` defaults to `0`, so existing layouts keep their old behavior unless you opt in.
174
+ - When a finite main-axis cap is present and the summed basis sizes overflow it, negative space is redistributed by `shrink * basis`.
175
+ - `basis` is still internal-only and fixed to `"auto"`; the library currently measures the natural content size first, then decides whether to grow or shrink.
176
+ - Custom nodes can implement `measureMinContent()` for accurate saturation. Nodes that do not implement it fall back to their regular `measure()` result, so shrink stays safe but may be more conservative.
177
+ - Known limitation: `column` shrink with `MultilineText` does not clip visual overflow on its own. If you need clipping, add it in your draw layer or wrap the node in a clipping primitive.
178
+
179
+ ## API notes
180
+
181
+ - `memoRenderItem()` now only accepts object items. If your list item is a primitive or you want to memoize by an explicit id, use `memoRenderItemBy(keyOf, renderItem)`.
182
+ - `FlexItemOptions` exposes `grow`, `shrink`, and `alignSelf`. `shrink` uses a compatibility-first default of `0`, while `basis` remains internal-only and fixed to `"auto"`.
183
+ - `ListState.position` now uses `undefined` as the explicit “use renderer default anchor” state. Use `list.setAnchor(position, offset)` to opt into a concrete anchor.
184
+ - `ListState` can be seeded with `new ListState(items)` and reset with `list.reset(nextItems)`.
185
+ - `MultilineText` now uses only `align` / `physicalAlign`; the old `alignment` field has been removed.
186
+
187
+ ### Migration notes
188
+
189
+ - Before:
190
+ - `memoRenderItem((item: number) => ...)`
191
+ - After:
192
+ - `memoRenderItemBy((item: number) => item, (item) => ...)`
193
+ - Before:
194
+ - `new FlexItem(node, { grow: 1, shrink: 1, basis: 100 })`
195
+ - After:
196
+ - `new FlexItem(node, { grow: 1, shrink: 1 })` if you want overflow redistribution
197
+ - `new FlexItem(node, { grow: 1 })` if you want to preserve the pre-shrink behavior
198
+ - `basis` stays internal-only (`"auto"`); unsupported sizing semantics beyond that should still be modeled explicitly in node measurement/layout
199
+ - Before:
200
+ - `new MultilineText(text, { alignment: "left" })`
201
+ - After:
202
+ - `new MultilineText(text, { align: "start" })`
203
+ - or `new MultilineText(text, { physicalAlign: "left" })` when physical left/right semantics are required
204
+ - Before:
205
+ - `list.position = Number.NaN`
206
+ - After:
207
+ - `list.resetScroll()`
208
+ - or `list.setAnchor(index, offset)` for an explicit anchor
209
+
210
+ **Development**
211
+ Install dependencies:
4
212
 
5
213
  ```bash
6
214
  bun install
7
215
  ```
8
216
 
9
- To type-check:
217
+ Type-check:
10
218
 
11
219
  ```bash
12
220
  bun run typecheck
13
221
  ```
14
222
 
15
- To build distributable files:
223
+ Build distributable files:
16
224
 
17
225
  ```bash
18
226
  bun run dist
19
227
  ```
20
228
 
21
- To build example:
229
+ Build the chat example:
22
230
 
23
231
  ```bash
24
232
  bun run example
25
233
  ```
26
-
27
- This project was created using `bun init` in bun v1.1.37. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/index.d.mts CHANGED
@@ -11,13 +11,52 @@ interface RenderFeedback {
11
11
  /** Largest visible continuous item position, expressed in item coordinates rather than pixels. */
12
12
  max: number;
13
13
  }
14
- type Alignment = "left" | "center" | "right";
14
+ type Axis = "row" | "column";
15
+ type MainAxisAlignment = "start" | "center" | "end" | "space-between" | "space-around" | "space-evenly";
16
+ type CrossAxisAlignment = "start" | "center" | "end" | "stretch";
17
+ type MainAxisSize = "fill" | "fit-content";
18
+ type TextAlign = "start" | "center" | "end";
19
+ type PhysicalTextAlign = "left" | "center" | "right";
20
+ type TextWhitespaceMode = "preserve" | "trim-and-collapse";
21
+ interface TextStyleOptions<C extends CanvasRenderingContext2D> {
22
+ lineHeight: number;
23
+ font: string;
24
+ style: DynValue<C, string>;
25
+ /** Default: preserve input whitespace, including blank lines and edge spaces. */
26
+ whitespace?: TextWhitespaceMode;
27
+ }
28
+ interface MultilineTextOptions<C extends CanvasRenderingContext2D> extends TextStyleOptions<C> {
29
+ /** Logical alignment that matches `Place.align`. */
30
+ align?: TextAlign;
31
+ /** Explicit physical alignment when left/right semantics are required. */
32
+ physicalAlign?: PhysicalTextAlign;
33
+ }
34
+ interface TextOptions<C extends CanvasRenderingContext2D> extends TextStyleOptions<C> {}
35
+ interface LayoutConstraints {
36
+ minWidth?: number;
37
+ maxWidth?: number;
38
+ minHeight?: number;
39
+ maxHeight?: number;
40
+ }
41
+ interface FlexItemOptions {
42
+ grow?: number;
43
+ /** Compatibility-first default: 0 (opt-in shrink). */
44
+ shrink?: number;
45
+ alignSelf?: CrossAxisAlignment | "auto";
46
+ }
47
+ interface FlexContainerOptions {
48
+ direction?: Axis;
49
+ gap?: number;
50
+ justifyContent?: MainAxisAlignment;
51
+ alignItems?: CrossAxisAlignment;
52
+ reverse?: boolean;
53
+ mainAxisSize?: MainAxisSize;
54
+ }
15
55
  interface Context<C extends CanvasRenderingContext2D> {
16
56
  graphics: C;
17
- remainingWidth: number;
18
- alignment: Alignment;
19
- reverse: boolean;
20
- measureNode(node: Node<C>): Box;
57
+ /** v2: 显式布局约束 */
58
+ constraints?: LayoutConstraints;
59
+ measureNode(node: Node<C>, constraints?: LayoutConstraints): Box;
21
60
  invalidateNode(node: Node<C>): void;
22
61
  resolveDynValue<T>(value: DynValue<C, T>): T;
23
62
  with<T>(this: Context<C>, cb: (g: C) => T): T;
@@ -32,58 +71,54 @@ interface HitTest {
32
71
  type: "click" | "auxclick" | "hover";
33
72
  }
34
73
  interface Node<C extends CanvasRenderingContext2D> {
74
+ /** Measure the node under the current layout constraints. */
35
75
  measure(ctx: Context<C>): Box;
76
+ /** Optional intrinsic lower bound used by flex-shrink saturation. */
77
+ measureMinContent?(ctx: Context<C>): Box;
36
78
  draw(ctx: Context<C>, x: number, y: number): boolean;
37
79
  hittest(ctx: Context<C>, test: HitTest): boolean;
38
- readonly flex: boolean;
80
+ }
81
+ interface LayoutRect {
82
+ x: number;
83
+ y: number;
84
+ width: number;
85
+ height: number;
86
+ }
87
+ interface ChildLayoutResult<C extends CanvasRenderingContext2D> {
88
+ node: Node<C>;
89
+ rect: LayoutRect;
90
+ contentBox: LayoutRect;
91
+ constraints?: LayoutConstraints;
92
+ }
93
+ interface FlexLayoutResult<C extends CanvasRenderingContext2D> {
94
+ containerBox: LayoutRect;
95
+ contentBox: LayoutRect;
96
+ children: ChildLayoutResult<C>[];
97
+ constraints?: LayoutConstraints;
39
98
  }
40
99
  //#endregion
41
- //#region src/nodes.d.ts
100
+ //#region src/nodes/base.d.ts
42
101
  declare abstract class Group<C extends CanvasRenderingContext2D> implements Node<C> {
43
- readonly children: Node<C>[];
102
+ #private;
44
103
  constructor(children: Node<C>[]);
104
+ get children(): readonly Node<C>[];
105
+ replaceChildren(nextChildren: Node<C>[]): void;
45
106
  abstract measure(ctx: Context<C>): Box;
46
107
  abstract draw(ctx: Context<C>, x: number, y: number): boolean;
47
108
  abstract hittest(ctx: Context<C>, test: HitTest): boolean;
48
- get flex(): boolean;
49
- }
50
- declare class VStack<C extends CanvasRenderingContext2D> extends Group<C> {
51
- readonly options: {
52
- gap?: number;
53
- alignment?: "left" | "center" | "right";
54
- };
55
- constructor(children: Node<C>[], options?: {
56
- gap?: number;
57
- alignment?: "left" | "center" | "right";
58
- });
59
- measure(ctx: Context<C>): Box;
60
- draw(ctx: Context<C>, x: number, y: number): boolean;
61
- hittest(ctx: Context<C>, test: HitTest): boolean;
62
- }
63
- declare class HStack<C extends CanvasRenderingContext2D> extends Group<C> {
64
- readonly children: Node<C>[];
65
- readonly options: {
66
- reverse?: boolean;
67
- gap?: number;
68
- };
69
- constructor(children: Node<C>[], options?: {
70
- reverse?: boolean;
71
- gap?: number;
72
- });
73
- measure(ctx: Context<C>): Box;
74
- draw(ctx: Context<C>, x: number, y: number): boolean;
75
- hittest(ctx: Context<C>, test: HitTest): boolean;
76
109
  }
77
110
  declare class Wrapper<C extends CanvasRenderingContext2D> implements Node<C> {
78
111
  #private;
79
112
  constructor(inner: Node<C>);
80
113
  get inner(): Node<C>;
81
114
  set inner(newNode: Node<C>);
82
- get flex(): boolean;
83
115
  measure(ctx: Context<C>): Box;
116
+ measureMinContent(ctx: Context<C>): Box;
84
117
  draw(ctx: Context<C>, x: number, y: number): boolean;
85
118
  hittest(ctx: Context<C>, test: HitTest): boolean;
86
119
  }
120
+ //#endregion
121
+ //#region src/nodes/box.d.ts
87
122
  declare class PaddingBox<C extends CanvasRenderingContext2D> extends Wrapper<C> {
88
123
  #private;
89
124
  readonly padding: {
@@ -99,104 +134,148 @@ declare class PaddingBox<C extends CanvasRenderingContext2D> extends Wrapper<C>
99
134
  right?: number;
100
135
  });
101
136
  measure(ctx: Context<C>): Box;
137
+ measureMinContent(ctx: Context<C>): Box;
102
138
  draw(ctx: Context<C>, x: number, y: number): boolean;
103
139
  hittest(ctx: Context<C>, test: HitTest): boolean;
104
140
  }
105
- declare class AlignBox<C extends CanvasRenderingContext2D> extends Wrapper<C> {
106
- #private;
141
+ declare class Fixed<C extends CanvasRenderingContext2D> implements Node<C> {
142
+ readonly width: number;
143
+ readonly height: number;
144
+ constructor(width: number, height: number);
145
+ measure(_ctx: Context<C>): Box;
146
+ measureMinContent(_ctx: Context<C>): Box;
147
+ draw(_ctx: Context<C>, _x: number, _y: number): boolean;
148
+ hittest(_ctx: Context<C>, _test: HitTest): boolean;
149
+ }
150
+ //#endregion
151
+ //#region src/nodes/flex.d.ts
152
+ declare class FlexItem<C extends CanvasRenderingContext2D> extends Wrapper<C> {
153
+ readonly item: FlexItemOptions;
154
+ constructor(inner: Node<C>, item?: FlexItemOptions);
155
+ }
156
+ declare class Flex<C extends CanvasRenderingContext2D> extends Group<C> {
157
+ readonly options: FlexContainerOptions;
158
+ constructor(children: Node<C>[], options?: FlexContainerOptions);
159
+ measure(ctx: Context<C>): Box;
160
+ measureMinContent(ctx: Context<C>): Box;
161
+ draw(ctx: Context<C>, x: number, y: number): boolean;
162
+ hittest(ctx: Context<C>, test: HitTest): boolean;
163
+ }
164
+ //#endregion
165
+ //#region src/nodes/place.d.ts
166
+ declare class Place<C extends CanvasRenderingContext2D> extends Wrapper<C> {
107
167
  readonly options: {
108
- alignment: Alignment;
168
+ align?: TextAlign;
169
+ expand?: boolean;
109
170
  };
110
- constructor(inner: Node<C>, options: {
111
- alignment: Alignment;
171
+ constructor(inner: Node<C>, options?: {
172
+ align?: TextAlign;
173
+ expand?: boolean;
112
174
  });
113
175
  measure(ctx: Context<C>): Box;
176
+ measureMinContent(ctx: Context<C>): Box;
114
177
  draw(ctx: Context<C>, x: number, y: number): boolean;
115
178
  hittest(ctx: Context<C>, test: HitTest): boolean;
116
179
  }
180
+ //#endregion
181
+ //#region src/nodes/text.d.ts
117
182
  declare class MultilineText<C extends CanvasRenderingContext2D> implements Node<C> {
118
- #private;
119
183
  readonly text: string;
120
- readonly options: {
121
- lineHeight: number;
122
- font: string;
123
- alignment: "left" | "center" | "right";
124
- style: DynValue<C, string>;
125
- };
126
- constructor(text: string, options: {
127
- lineHeight: number;
128
- font: string;
129
- alignment: "left" | "center" | "right";
130
- style: DynValue<C, string>;
131
- });
132
- get flex(): boolean;
184
+ readonly options: MultilineTextOptions<C>;
185
+ constructor(text: string, options: MultilineTextOptions<C>);
133
186
  measure(ctx: Context<C>): Box;
187
+ measureMinContent(ctx: Context<C>): Box;
134
188
  draw(ctx: Context<C>, x: number, y: number): boolean;
135
- hittest(_ctx: Context<C>, _test: HitTest): boolean;
189
+ hittest(_ctx: Context<C>, _test: {
190
+ x: number;
191
+ y: number;
192
+ type: "click" | "auxclick" | "hover";
193
+ }): boolean;
136
194
  }
137
195
  declare class Text<C extends CanvasRenderingContext2D> implements Node<C> {
138
- #private;
139
196
  readonly text: string;
140
- readonly options: {
141
- lineHeight: number;
142
- font: string;
143
- style: DynValue<C, string>;
144
- };
145
- constructor(text: string, options: {
146
- lineHeight: number;
147
- font: string;
148
- style: DynValue<C, string>;
149
- });
150
- get flex(): boolean;
197
+ readonly options: TextOptions<C>;
198
+ constructor(text: string, options: TextOptions<C>);
151
199
  measure(ctx: Context<C>): Box;
200
+ measureMinContent(ctx: Context<C>): Box;
152
201
  draw(ctx: Context<C>, x: number, y: number): boolean;
153
- hittest(_ctx: Context<C>, _test: HitTest): boolean;
154
- }
155
- declare class Fixed<C extends CanvasRenderingContext2D> implements Node<C> {
156
- readonly width: number;
157
- readonly height: number;
158
- constructor(width: number, height: number);
159
- get flex(): boolean;
160
- measure(_ctx: Context<C>): Box;
161
- draw(_ctx: Context<C>, _x: number, _y: number): boolean;
162
- hittest(_ctx: Context<C>, _test: HitTest): boolean;
202
+ hittest(_ctx: Context<C>, _test: {
203
+ x: number;
204
+ y: number;
205
+ type: "click" | "auxclick" | "hover";
206
+ }): boolean;
163
207
  }
164
208
  //#endregion
165
- //#region src/renderer.d.ts
209
+ //#region src/renderer/base.d.ts
166
210
  declare class BaseRenderer<C extends CanvasRenderingContext2D, O extends {} = {}> {
167
211
  #private;
168
212
  readonly options: RendererOptions & O;
169
213
  graphics: C;
170
214
  protected get context(): Context<C>;
171
215
  constructor(graphics: C, options: RendererOptions & O);
216
+ protected getRootConstraints(): LayoutConstraints;
217
+ protected getRootContext(): Context<C>;
218
+ protected measureRootNode(node: Node<C>): Box;
219
+ protected drawRootNode(node: Node<C>, x?: number, y?: number): boolean;
220
+ protected hittestRootNode(node: Node<C>, test: HitTest): boolean;
172
221
  invalidateNode(node: Node<C>): void;
173
- measureNode(node: Node<C>, ctx?: Context<C>): Box;
222
+ getLayoutResult(node: Node<C>, constraints?: LayoutConstraints): FlexLayoutResult<C> | undefined;
223
+ setLayoutResult(node: Node<C>, result: FlexLayoutResult<C>, constraints?: LayoutConstraints): void;
224
+ protected getTextLayout<T>(node: Node<C>, key: string): T | undefined;
225
+ protected setTextLayout<T>(node: Node<C>, key: string, layout: T): void;
226
+ measureNode(node: Node<C>, constraints?: LayoutConstraints): Box;
174
227
  }
175
228
  declare class DebugRenderer<C extends CanvasRenderingContext2D> extends BaseRenderer<C> {
176
229
  draw(node: Node<C>): boolean;
177
230
  hittest(node: Node<C>, test: HitTest): boolean;
178
231
  }
179
- declare function memoRenderItem<C extends CanvasRenderingContext2D, T extends {}>(renderItem: (item: T) => Node<C>): ((item: T) => Node<C>) & {
180
- reset: (key: T) => boolean;
181
- };
232
+ //#endregion
233
+ //#region src/renderer/list-state.d.ts
182
234
  declare class ListState<T extends {}> {
183
235
  offset: number;
184
- position: number;
236
+ position: number | undefined;
185
237
  items: T[];
238
+ constructor(items?: T[]);
186
239
  unshift(...items: T[]): void;
187
240
  unshiftAll(items: T[]): void;
188
241
  push(...items: T[]): void;
189
242
  pushAll(items: T[]): void;
190
- reset(): void;
243
+ setAnchor(position: number, offset?: number): void;
244
+ reset(items?: T[]): void;
191
245
  resetScroll(): void;
192
246
  applyScroll(delta: number): void;
193
247
  }
194
- type DrawItem<C extends CanvasRenderingContext2D> = {
248
+ //#endregion
249
+ //#region src/renderer/memo.d.ts
250
+ declare function memoRenderItem<C extends CanvasRenderingContext2D, T extends object>(renderItem: (item: T) => Node<C>): ((item: T) => Node<C>) & {
251
+ reset: (key: T) => boolean;
252
+ };
253
+ declare function memoRenderItemBy<C extends CanvasRenderingContext2D, T, K>(keyOf: (item: T) => K, renderItem: (item: T) => Node<C>): ((item: T) => Node<C>) & {
254
+ reset: (item: T) => boolean;
255
+ resetKey: (key: K) => boolean;
256
+ };
257
+ //#endregion
258
+ //#region src/renderer/virtualized/solver.d.ts
259
+ interface VisibleListState {
260
+ position?: number;
261
+ offset: number;
262
+ }
263
+ interface NormalizedListState {
264
+ position: number;
265
+ offset: number;
266
+ }
267
+ interface VisibleWindowEntry<T> {
195
268
  idx: number;
196
- node: Node<C>;
269
+ value: T;
197
270
  offset: number;
198
271
  height: number;
199
- };
272
+ }
273
+ interface VisibleWindow<T> {
274
+ drawList: VisibleWindowEntry<T>[];
275
+ shift: number;
276
+ }
277
+ //#endregion
278
+ //#region src/renderer/virtualized/base.d.ts
200
279
  interface JumpToOptions {
201
280
  animated?: boolean;
202
281
  block?: "start" | "center" | "end";
@@ -211,52 +290,65 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
211
290
  static readonly MIN_JUMP_DURATION = 160;
212
291
  static readonly MAX_JUMP_DURATION = 420;
213
292
  static readonly JUMP_DURATION_PER_ITEM = 28;
214
- get position(): number;
215
- set position(value: number);
293
+ get position(): number | undefined;
294
+ set position(value: number | undefined);
216
295
  get offset(): number;
217
296
  set offset(value: number);
218
297
  get items(): T[];
219
298
  set items(value: T[]);
220
299
  abstract render(feedback?: RenderFeedback): boolean;
221
- abstract hittest(test: HitTest): boolean;
300
+ abstract hittest(test: {
301
+ x: number;
302
+ y: number;
303
+ type: "click" | "auxclick" | "hover";
304
+ }): boolean;
305
+ protected _readListState(): VisibleListState;
306
+ protected _commitListState(state: NormalizedListState): void;
222
307
  jumpTo(index: number, options?: JumpToOptions): void;
223
308
  protected _resetRenderFeedback(feedback?: RenderFeedback): void;
224
309
  protected _accumulateRenderFeedback(feedback: RenderFeedback, idx: number, top: number, height: number): void;
225
- protected _renderDrawList(list: DrawItem<C>[], shift: number, feedback?: RenderFeedback): boolean;
310
+ protected _renderDrawList(list: VisibleWindow<Node<C>>["drawList"], shift: number, feedback?: RenderFeedback): boolean;
311
+ protected _renderVisibleWindow(window: VisibleWindow<Node<C>>, feedback?: RenderFeedback): boolean;
312
+ protected _hittestVisibleWindow(window: VisibleWindow<Node<C>>, test: {
313
+ x: number;
314
+ y: number;
315
+ type: "click" | "auxclick" | "hover";
316
+ }): boolean;
226
317
  protected _prepareRender(): boolean;
227
318
  protected _finishRender(requestRedraw: boolean): boolean;
228
319
  protected _clampItemIndex(index: number): number;
229
320
  protected _getItemHeight(index: number): number;
230
321
  protected _getAnchorAtOffset(index: number, offset: number): number;
231
- protected abstract _prepareAnchorState(): void;
232
- protected abstract _readAnchor(): number;
322
+ protected abstract _normalizeListState(state: VisibleListState): NormalizedListState;
323
+ protected abstract _readAnchor(state: NormalizedListState): number;
233
324
  protected abstract _applyAnchor(anchor: number): void;
234
325
  protected abstract _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
235
326
  protected abstract _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
236
327
  }
237
- declare class TimelineRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
328
+ //#endregion
329
+ //#region src/renderer/virtualized/chat.d.ts
330
+ declare class ChatRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
331
+ #private;
238
332
  protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
239
- protected _prepareAnchorState(): void;
240
- protected _readAnchor(): number;
333
+ protected _normalizeListState(state: VisibleListState): NormalizedListState;
334
+ protected _readAnchor(state: NormalizedListState): number;
241
335
  protected _applyAnchor(anchor: number): void;
242
336
  protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
243
337
  render(feedback?: RenderFeedback): boolean;
244
338
  hittest(test: HitTest): boolean;
245
339
  }
246
- declare class ChatRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
340
+ //#endregion
341
+ //#region src/renderer/virtualized/timeline.d.ts
342
+ declare class TimelineRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
343
+ #private;
247
344
  protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
248
- protected _prepareAnchorState(): void;
249
- protected _readAnchor(): number;
345
+ protected _normalizeListState(state: VisibleListState): NormalizedListState;
346
+ protected _readAnchor(state: NormalizedListState): number;
250
347
  protected _applyAnchor(anchor: number): void;
251
348
  protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
252
349
  render(feedback?: RenderFeedback): boolean;
253
350
  hittest(test: HitTest): boolean;
254
351
  }
255
352
  //#endregion
256
- //#region src/registry.d.ts
257
- declare function registerNodeParent<C extends CanvasRenderingContext2D>(node: Node<C>, parent: Node<C>): void;
258
- declare function unregisterNodeParent<C extends CanvasRenderingContext2D>(node: Node<C>): void;
259
- declare function getNodeParent<C extends CanvasRenderingContext2D>(node: Node<C>): Node<C> | undefined;
260
- //#endregion
261
- export { AlignBox, Alignment, BaseRenderer, Box, ChatRenderer, Context, DebugRenderer, DynValue, Fixed, Group, HStack, HitTest, JumpToOptions, ListState, MultilineText, Node, PaddingBox, RenderFeedback, RendererOptions, Text, TimelineRenderer, VStack, VirtualizedRenderer, Wrapper, getNodeParent, memoRenderItem, registerNodeParent, unregisterNodeParent };
353
+ export { Axis, BaseRenderer, Box, ChatRenderer, ChildLayoutResult, Context, CrossAxisAlignment, DebugRenderer, DynValue, Fixed, Flex, FlexContainerOptions, FlexItem, FlexItemOptions, FlexLayoutResult, Group, HitTest, JumpToOptions, LayoutConstraints, LayoutRect, ListState, MainAxisAlignment, MainAxisSize, MultilineText, MultilineTextOptions, Node, PaddingBox, PhysicalTextAlign, Place, RenderFeedback, RendererOptions, Text, TextAlign, TextOptions, TextStyleOptions, TextWhitespaceMode, TimelineRenderer, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
262
354
  //# sourceMappingURL=index.d.mts.map