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 +212 -6
- package/index.d.mts +199 -107
- package/index.mjs +1479 -584
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,233 @@
|
|
|
1
1
|
# chat-layout
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
217
|
+
Type-check:
|
|
10
218
|
|
|
11
219
|
```bash
|
|
12
220
|
bun run typecheck
|
|
13
221
|
```
|
|
14
222
|
|
|
15
|
-
|
|
223
|
+
Build distributable files:
|
|
16
224
|
|
|
17
225
|
```bash
|
|
18
226
|
bun run dist
|
|
19
227
|
```
|
|
20
228
|
|
|
21
|
-
|
|
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
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
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
|
-
|
|
168
|
+
align?: TextAlign;
|
|
169
|
+
expand?: boolean;
|
|
109
170
|
};
|
|
110
|
-
constructor(inner: Node<C>, options
|
|
111
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|