chat-layout 1.1.0-4 → 1.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/README.md CHANGED
@@ -20,7 +20,7 @@ const bubble = new RoundedBox(
20
20
  new MultilineText(item.content, {
21
21
  lineHeight: 20,
22
22
  font: "16px system-ui",
23
- style: "black",
23
+ color: "black",
24
24
  align: "start",
25
25
  }),
26
26
  { top: 6, bottom: 6, left: 10, right: 10, radii: 8, fill: "#ccc" },
@@ -54,6 +54,7 @@ See [example/chat.ts](./example/chat.ts) for a full chat example.
54
54
  - `Text` and `MultilineText` default to `overflowWrap: "break-word"`, which preserves compatibility-first min-content sizing for shrink layouts.
55
55
  - Use `overflowWrap: "anywhere"` when long unspaced strings should contribute grapheme-level breakpoints to min-content sizing.
56
56
  - `Text` supports `overflow: "ellipsis"` with `ellipsisPosition: "start" | "end" | "middle"` when measured under a finite `maxWidth`.
57
+ - `Text` and `MultilineText` both accept either a plain string or `InlineSpan[]` for mixed inline styles.
57
58
  - `MultilineText` supports `overflow: "ellipsis"` together with `maxLines`; values below `1` are treated as `1`.
58
59
 
59
60
  ## Text ellipsis
@@ -61,10 +62,14 @@ See [example/chat.ts](./example/chat.ts) for a full chat example.
61
62
  Single-line `Text` can ellipsize at the start, end, or middle when a finite width constraint is present:
62
63
 
63
64
  ```ts
64
- const title = new Text("Extremely long thread title that should not blow out the row", {
65
+ const title = new Text([
66
+ { text: "Extremely long " },
67
+ { text: "thread title", font: "700 16px system-ui", color: "#0f766e" },
68
+ { text: " that should not blow out the row" },
69
+ ], {
65
70
  lineHeight: 20,
66
71
  font: "16px system-ui",
67
- style: "#111",
72
+ color: "#111",
68
73
  overflow: "ellipsis",
69
74
  ellipsisPosition: "middle",
70
75
  });
@@ -76,7 +81,7 @@ Multi-line `MultilineText` can cap the visible line count and convert the last v
76
81
  const preview = new MultilineText(reply.content, {
77
82
  lineHeight: 16,
78
83
  font: "13px system-ui",
79
- style: "#444",
84
+ color: "#444",
80
85
  align: "start",
81
86
  overflowWrap: "anywhere",
82
87
  overflow: "ellipsis",
package/example/chat.ts CHANGED
@@ -157,23 +157,23 @@ type ChatItem = MessageItem | RevokedItem;
157
157
 
158
158
  const richTextMessage: InlineSpan<C>[] = [
159
159
  { text: "现在这个 chat example 可以直接展示 " },
160
- { text: "rich text", font: "700 16px system-ui", style: "#0f766e" },
160
+ { text: "rich text", font: "700 16px system-ui", color: "#0f766e" },
161
161
  { text: " 了,支持 " },
162
- { text: "颜色", style: "#2563eb" },
162
+ { text: "颜色", color: "#2563eb" },
163
163
  { text: "、" },
164
- { text: "粗体", font: "700 16px system-ui", style: "#b91c1c" },
164
+ { text: "粗体", font: "700 16px system-ui", color: "#b91c1c" },
165
165
  { text: ",以及 " },
166
- { text: "inline code", font: "15px ui-monospace, SFMono-Regular, Consolas, monospace", style: "#7c3aed" },
166
+ { text: "inline code", font: "15px ui-monospace, SFMono-Regular, Consolas, monospace", color: "#7c3aed" },
167
167
  { text: " 这样的片段混排。" },
168
168
  ];
169
169
 
170
170
  const richReplyPreview: InlineSpan<C>[] = [
171
171
  { text: "回复预览里也能用 " },
172
- { text: "rich text", font: "700 13px system-ui", style: "#0f766e" },
172
+ { text: "rich text", font: "700 13px system-ui", color: "#0f766e" },
173
173
  { text: ",比如 " },
174
- { text: "关键词高亮", style: "#2563eb" },
174
+ { text: "关键词高亮", color: "#2563eb" },
175
175
  { text: " 和 " },
176
- { text: "code()", font: "12px ui-monospace, SFMono-Regular, Consolas, monospace", style: "#7c3aed" },
176
+ { text: "code()", font: "12px ui-monospace, SFMono-Regular, Consolas, monospace", color: "#7c3aed" },
177
177
  {
178
178
  text: ",超长内容仍然会按原来的两行省略规则收起,不需要额外处理。",
179
179
  },
@@ -225,7 +225,7 @@ const renderItem = memoRenderItem((item: ChatItem): Node<C> => {
225
225
  new Text(`${item.sender}已撤回一条消息`, {
226
226
  lineHeight: 18,
227
227
  font: "14px system-ui",
228
- style: () => (currentHover?.id === item.id ? "#525252" : "#666"),
228
+ color: () => (currentHover?.id === item.id ? "#525252" : "#666"),
229
229
  overflow: "ellipsis",
230
230
  }),
231
231
  {
@@ -262,7 +262,7 @@ const renderItem = memoRenderItem((item: ChatItem): Node<C> => {
262
262
  new Text(item.sender, {
263
263
  lineHeight: 15,
264
264
  font: "12px system-ui",
265
- style: "black",
265
+ color: "black",
266
266
  }),
267
267
  {
268
268
  top: 0,
@@ -286,7 +286,7 @@ const renderItem = memoRenderItem((item: ChatItem): Node<C> => {
286
286
  new MultilineText(item.content, {
287
287
  lineHeight: 20,
288
288
  font: "16px system-ui",
289
- style: "black",
289
+ color: "black",
290
290
  align: "start",
291
291
  overflowWrap: "anywhere",
292
292
  }),
@@ -302,12 +302,12 @@ const renderItem = memoRenderItem((item: ChatItem): Node<C> => {
302
302
  new Text(item.reply.sender, {
303
303
  lineHeight: 14,
304
304
  font: "11px system-ui",
305
- style: () => (currentHover?.id === item.id ? "#4d4d4d" : "#666"),
305
+ color: () => (currentHover?.id === item.id ? "#4d4d4d" : "#666"),
306
306
  }),
307
307
  new MultilineText(item.reply.content, {
308
308
  lineHeight: 16,
309
309
  font: "13px system-ui",
310
- style: () => (currentHover?.id === item.id ? "#222" : "#444"),
310
+ color: () => (currentHover?.id === item.id ? "#222" : "#444"),
311
311
  align: "start",
312
312
  overflow: "ellipsis",
313
313
  overflowWrap: "anywhere",
package/example/test.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type Context,
10
10
  type DynValue,
11
11
  type HitTest,
12
+ type InlineSpan,
12
13
  type Node,
13
14
  } from "chat-layout";
14
15
 
@@ -83,6 +84,14 @@ const renderer = new DebugRenderer(ctx, {});
83
84
 
84
85
  let color = "green";
85
86
 
87
+ const singleLineRichText: InlineSpan<C>[] = [
88
+ { text: "单行 " },
89
+ { text: "rich", font: "700 16px monospace", color: "#0f766e" },
90
+ { text: " text 也支持 " },
91
+ { text: "inline span", font: "700 16px monospace", color: "#2563eb" },
92
+ { text: " 省略了" },
93
+ ];
94
+
86
95
  class ClickDetect extends Wrapper<C> {
87
96
  hittest(_ctx: Context<C>, _test: HitTest): boolean {
88
97
  color = "red";
@@ -98,7 +107,7 @@ const node = new RoundedBox(
98
107
  lineHeight: 20,
99
108
  font: "400 16px monospace",
100
109
  align: "center",
101
- style: "black",
110
+ color: "black",
102
111
  }),
103
112
  { align: "center" },
104
113
  ),
@@ -108,10 +117,12 @@ const node = new RoundedBox(
108
117
  [
109
118
  new ClickDetect(
110
119
  new RoundedBox(
111
- new Text("测试3".repeat(2), {
120
+ new Text(singleLineRichText, {
112
121
  lineHeight: 20,
113
122
  font: "400 16px monospace",
114
- style: () => color,
123
+ color: () => color,
124
+ overflow: "ellipsis",
125
+ ellipsisPosition: "middle",
115
126
  }),
116
127
  {
117
128
  left: 14,
@@ -128,7 +139,7 @@ const node = new RoundedBox(
128
139
  lineHeight: 16,
129
140
  font: "400 12px monospace",
130
141
  align: "center",
131
- style: "black",
142
+ color: "black",
132
143
  }),
133
144
  {
134
145
  left: 10,
@@ -163,7 +174,7 @@ const node = new RoundedBox(
163
174
  lineHeight: 20,
164
175
  font: "400 16px monospace",
165
176
  physicalAlign: "right",
166
- style: "black",
177
+ color: "black",
167
178
  }),
168
179
  {
169
180
  left: 10,
package/index.d.mts CHANGED
@@ -72,8 +72,8 @@ interface TextStyleOptions<C extends CanvasRenderingContext2D> {
72
72
  lineHeight: number;
73
73
  /** Canvas font string used for measurement and drawing. */
74
74
  font: string;
75
- /** Fill style or resolver used when drawing the text. */
76
- style: DynValue<C, string>;
75
+ /** Color or resolver used when drawing the text. */
76
+ color: DynValue<C, string>;
77
77
  /** Default: normal; matches pretext and CSS-style collapsible whitespace behavior. */
78
78
  whiteSpace?: TextWhiteSpaceMode;
79
79
  /** Default: normal; use keep-all to match pretext's CJK-friendly line breaking mode. */
@@ -82,15 +82,15 @@ interface TextStyleOptions<C extends CanvasRenderingContext2D> {
82
82
  overflowWrap?: TextOverflowWrapMode;
83
83
  }
84
84
  /**
85
- * A span-like inline text fragment used by rich multi-line text.
85
+ * A span-like inline text fragment used by rich text nodes.
86
86
  */
87
87
  interface InlineSpan<C extends CanvasRenderingContext2D> {
88
88
  /** Source text contained in this inline fragment. */
89
89
  text: string;
90
90
  /** Canvas font string override for this fragment. Falls back to the node-level font. */
91
91
  font?: string;
92
- /** Fill style override for this fragment. Falls back to the node-level style. */
93
- style?: DynValue<C, string>;
92
+ /** Color override for this fragment. Falls back to the node-level color. */
93
+ color?: DynValue<C, string>;
94
94
  /** Optional break hint forwarded to pretext rich-inline layout. */
95
95
  break?: "normal" | "never";
96
96
  /** Optional extra occupied width forwarded to pretext rich-inline layout. */
@@ -390,13 +390,13 @@ declare class MultilineText<C extends CanvasRenderingContext2D> implements Node<
390
390
  * Draws a single line of text, clipped logically by measurement width.
391
391
  */
392
392
  declare class Text<C extends CanvasRenderingContext2D> implements Node<C> {
393
- readonly text: string;
393
+ readonly text: string | InlineSpan<C>[];
394
394
  readonly options: TextOptions<C>;
395
395
  /**
396
- * @param text Source text to measure and draw.
396
+ * @param text Source text to measure and draw. Pass an `InlineSpan[]` for mixed inline styles.
397
397
  * @param options Text layout and drawing options.
398
398
  */
399
- constructor(text: string, options: TextOptions<C>);
399
+ constructor(text: string | InlineSpan<C>[], options: TextOptions<C>);
400
400
  measure(ctx: Context<C>): Box;
401
401
  measureMinContent(ctx: Context<C>): Box;
402
402
  draw(ctx: Context<C>, x: number, y: number): boolean;