chat-layout 1.2.0-4 → 1.2.0-5
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 +24 -1
- package/example/chat.ts +33 -14
- package/index.d.mts +47 -31
- package/index.mjs +1048 -487
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The current v2-style APIs are:
|
|
|
9
9
|
- `Place`: place a single child at `start` / `center` / `end`
|
|
10
10
|
- `ShrinkWrap`: search the narrowest width that keeps the current height stable
|
|
11
11
|
- `MultilineText`: text layout with logical `align` or physical `physicalAlign`
|
|
12
|
-
- `
|
|
12
|
+
- `ListRenderer` + `ListState`: virtualized chat or timeline rendering
|
|
13
13
|
- `memoRenderItem` / `memoRenderItemBy`: item render memoization
|
|
14
14
|
|
|
15
15
|
## Quick example
|
|
@@ -53,6 +53,29 @@ return row;
|
|
|
53
53
|
|
|
54
54
|
See [example/chat.ts](./example/chat.ts) for a full chat example.
|
|
55
55
|
|
|
56
|
+
## List insert animation
|
|
57
|
+
|
|
58
|
+
`pushAll()` and `unshiftAll()` can opt into short-list insertion animations. They only animate when the previous rendered frame still had spare space below the last item; otherwise they fall back to the normal hard cut:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
list.pushAll([nextMessage], {
|
|
62
|
+
distance: 24, // duration defaults to 220ms when animation options are present
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
list.unshiftAll([olderMessage], {
|
|
66
|
+
duration: 220,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To make chat-style inserts automatically follow the latest visible edge, pass `followIfAtBoundary: true`. When the viewport was already pinned to that edge, the insert behaves like a conditional `jumpTo()` instead of combining with the enter animation:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
list.pushAll([nextMessage], {
|
|
74
|
+
followIfAtBoundary: true,
|
|
75
|
+
duration: 220,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
56
79
|
## Layout notes
|
|
57
80
|
|
|
58
81
|
- `Flex` handles the main axis only. It shrink-wraps on the cross axis unless you opt into stretch behavior.
|
package/example/chat.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ChatRenderer,
|
|
3
2
|
Flex,
|
|
4
3
|
FlexItem,
|
|
5
4
|
Fixed,
|
|
5
|
+
ListRenderer,
|
|
6
6
|
ListState,
|
|
7
7
|
MultilineText,
|
|
8
8
|
PaddingBox,
|
|
@@ -221,6 +221,7 @@ const richReplyPreview: InlineSpan<C>[] = [
|
|
|
221
221
|
|
|
222
222
|
let currentHover: ChatItem | undefined;
|
|
223
223
|
const REPLACE_ANIMATION_DURATION = 320;
|
|
224
|
+
const INSERT_ANIMATION_DURATION = 220;
|
|
224
225
|
|
|
225
226
|
function revokeMessage(item: MessageItem): RevokedItem {
|
|
226
227
|
return {
|
|
@@ -498,7 +499,9 @@ const list = new ListState<ChatItem>([
|
|
|
498
499
|
},
|
|
499
500
|
{ id: 9, kind: "message", sender: "B", content: randomText(5) },
|
|
500
501
|
]);
|
|
501
|
-
const renderer = new
|
|
502
|
+
const renderer = new ListRenderer(ctx, {
|
|
503
|
+
anchorMode: "bottom",
|
|
504
|
+
underflowAlign: "top",
|
|
502
505
|
renderItem,
|
|
503
506
|
list,
|
|
504
507
|
});
|
|
@@ -630,21 +633,37 @@ function randomText(words: number): string {
|
|
|
630
633
|
}
|
|
631
634
|
|
|
632
635
|
button("unshift", () => {
|
|
633
|
-
list.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
636
|
+
list.unshiftAll(
|
|
637
|
+
[
|
|
638
|
+
{
|
|
639
|
+
id: nextMessageId++,
|
|
640
|
+
kind: "message",
|
|
641
|
+
sender: Math.random() < 0.5 ? "A" : "B",
|
|
642
|
+
content: randomText(10 + Math.floor(200 * Math.random())),
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
{
|
|
646
|
+
duration: INSERT_ANIMATION_DURATION,
|
|
647
|
+
followIfAtBoundary: true,
|
|
648
|
+
},
|
|
649
|
+
);
|
|
639
650
|
});
|
|
640
651
|
|
|
641
652
|
button("push", () => {
|
|
642
|
-
list.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
653
|
+
list.pushAll(
|
|
654
|
+
[
|
|
655
|
+
{
|
|
656
|
+
id: nextMessageId++,
|
|
657
|
+
kind: "message",
|
|
658
|
+
sender: Math.random() < 0.5 ? "A" : "B",
|
|
659
|
+
content: randomText(10 + Math.floor(200 * Math.random())),
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
{
|
|
663
|
+
distance: 24,
|
|
664
|
+
followIfAtBoundary: true,
|
|
665
|
+
},
|
|
666
|
+
);
|
|
648
667
|
});
|
|
649
668
|
|
|
650
669
|
button("jump middle", () => {
|
package/index.d.mts
CHANGED
|
@@ -517,6 +517,16 @@ interface DeleteListItemAnimationOptions {
|
|
|
517
517
|
/** Animation duration in milliseconds. */
|
|
518
518
|
duration?: number;
|
|
519
519
|
}
|
|
520
|
+
interface InsertListItemsAnimationOptions {
|
|
521
|
+
/** Animation duration in milliseconds. */
|
|
522
|
+
duration?: number;
|
|
523
|
+
/** Enter offset in pixels measured from the final resting position. */
|
|
524
|
+
distance?: number;
|
|
525
|
+
/** Auto-follow the insertion edge when the viewport was already pinned there. */
|
|
526
|
+
followIfAtBoundary?: boolean;
|
|
527
|
+
}
|
|
528
|
+
type PushListItemsAnimationOptions = InsertListItemsAnimationOptions;
|
|
529
|
+
type UnshiftListItemsAnimationOptions = InsertListItemsAnimationOptions;
|
|
520
530
|
declare class ListState<T extends {}> {
|
|
521
531
|
#private;
|
|
522
532
|
/** Pixel offset from the anchored item edge. */
|
|
@@ -534,11 +544,11 @@ declare class ListState<T extends {}> {
|
|
|
534
544
|
/** Prepends one or more items. */
|
|
535
545
|
unshift(...items: T[]): void;
|
|
536
546
|
/** Prepends an array of items. */
|
|
537
|
-
unshiftAll(items: T[]): void;
|
|
547
|
+
unshiftAll(items: T[], animation?: UnshiftListItemsAnimationOptions): void;
|
|
538
548
|
/** Appends one or more items. */
|
|
539
549
|
push(...items: T[]): void;
|
|
540
550
|
/** Appends an array of items. */
|
|
541
|
-
pushAll(items: T[]): void;
|
|
551
|
+
pushAll(items: T[], animation?: PushListItemsAnimationOptions): void;
|
|
542
552
|
/**
|
|
543
553
|
* Updates an existing item by object identity.
|
|
544
554
|
*/
|
|
@@ -590,6 +600,16 @@ type VirtualizedResolvedItem = {
|
|
|
590
600
|
};
|
|
591
601
|
//#endregion
|
|
592
602
|
//#region src/renderer/virtualized/solver.d.ts
|
|
603
|
+
type ListAnchorMode = "top" | "bottom";
|
|
604
|
+
type ListUnderflowAlign = "top" | "bottom";
|
|
605
|
+
interface ListLayoutOptions {
|
|
606
|
+
anchorMode?: ListAnchorMode;
|
|
607
|
+
underflowAlign?: ListUnderflowAlign;
|
|
608
|
+
}
|
|
609
|
+
interface ResolvedListLayoutOptions {
|
|
610
|
+
anchorMode: ListAnchorMode;
|
|
611
|
+
underflowAlign: ListUnderflowAlign;
|
|
612
|
+
}
|
|
593
613
|
interface VisibleListState {
|
|
594
614
|
position?: number;
|
|
595
615
|
offset: number;
|
|
@@ -610,6 +630,7 @@ interface VisibleWindow<T> {
|
|
|
610
630
|
}
|
|
611
631
|
interface VisibleWindowResult<T> {
|
|
612
632
|
normalizedState: NormalizedListState;
|
|
633
|
+
resolutionPath: number[];
|
|
613
634
|
window: VisibleWindow<T>;
|
|
614
635
|
}
|
|
615
636
|
//#endregion
|
|
@@ -637,7 +658,7 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
|
|
|
637
658
|
#private;
|
|
638
659
|
static readonly MIN_JUMP_DURATION = 160;
|
|
639
660
|
static readonly MAX_JUMP_DURATION = 420;
|
|
640
|
-
static readonly
|
|
661
|
+
static readonly JUMP_DURATION_PER_PIXEL = 0.7;
|
|
641
662
|
constructor(graphics: C, options: {
|
|
642
663
|
renderItem: (item: T) => Node<C>;
|
|
643
664
|
list: ListState<T>;
|
|
@@ -663,6 +684,7 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
|
|
|
663
684
|
type: "click" | "auxclick" | "hover";
|
|
664
685
|
}): boolean;
|
|
665
686
|
protected _readListState(): VisibleListState;
|
|
687
|
+
protected _resolveVisibleWindow(now: number): VisibleWindowResult<VirtualizedResolvedItem>;
|
|
666
688
|
protected _commitListState(state: NormalizedListState): void;
|
|
667
689
|
/**
|
|
668
690
|
* Scrolls the viewport to the requested item index.
|
|
@@ -671,59 +693,53 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
|
|
|
671
693
|
protected _resetRenderFeedback(feedback?: RenderFeedback): void;
|
|
672
694
|
protected _accumulateRenderFeedback(feedback: RenderFeedback, idx: number, top: number, height: number): void;
|
|
673
695
|
protected _renderDrawList(list: VisibleWindow<VirtualizedResolvedItem>["drawList"], shift: number, feedback?: RenderFeedback): boolean;
|
|
674
|
-
protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback): boolean;
|
|
696
|
+
protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback, extraShift?: number): boolean;
|
|
675
697
|
protected _readVisibleRange(top: number, height: number): {
|
|
676
698
|
top: number;
|
|
677
699
|
bottom: number;
|
|
678
700
|
} | undefined;
|
|
679
|
-
protected
|
|
680
|
-
protected _hittestVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, test: HitTest): boolean;
|
|
681
|
-
protected _captureVisibleItemSnapshot(
|
|
701
|
+
protected _pruneTransitionAnimations(_window: VisibleWindow<unknown>, now: number): boolean;
|
|
702
|
+
protected _hittestVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, test: HitTest, extraShift?: number): boolean;
|
|
703
|
+
protected _captureVisibleItemSnapshot(solution: VisibleWindowResult<unknown>, extraShift?: number): void;
|
|
682
704
|
protected _prepareRender(now: number): boolean;
|
|
683
705
|
protected _finishRender(requestRedraw: boolean): boolean;
|
|
684
706
|
protected _clampItemIndex(index: number): number;
|
|
685
707
|
protected _getItemHeight(index: number): number;
|
|
708
|
+
protected _getItemHeightAt(index: number, now: number): number;
|
|
709
|
+
protected _readAnchorAt(now: number): number | undefined;
|
|
710
|
+
protected _restoreAnchor(anchor: number): void;
|
|
686
711
|
protected _resolveItem(item: T, _index: number, now: number): {
|
|
687
712
|
value: VirtualizedResolvedItem;
|
|
688
713
|
height: number;
|
|
689
714
|
};
|
|
690
|
-
protected _getAnchorAtOffset(index: number, offset: number): number;
|
|
691
715
|
protected abstract _normalizeListState(state: VisibleListState): NormalizedListState;
|
|
692
|
-
protected abstract
|
|
693
|
-
protected abstract
|
|
716
|
+
protected abstract _getLayoutOptions(): ResolvedListLayoutOptions;
|
|
717
|
+
protected abstract _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
|
|
718
|
+
protected abstract _readAnchor(state: NormalizedListState, readItemHeight: (index: number) => number): number;
|
|
694
719
|
protected abstract _applyAnchor(anchor: number): void;
|
|
695
720
|
protected abstract _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
|
|
696
721
|
protected abstract _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
|
|
697
|
-
protected abstract _getAnimatedLayerOffset(slotHeight: number, nodeHeight: number): number;
|
|
698
722
|
}
|
|
699
723
|
//#endregion
|
|
700
|
-
//#region src/renderer/virtualized/
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
declare class ChatRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
|
|
705
|
-
protected _resolveVisibleWindow(now: number): VisibleWindowResult<VirtualizedResolvedItem>;
|
|
706
|
-
protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
|
|
707
|
-
protected _normalizeListState(state: VisibleListState): NormalizedListState;
|
|
708
|
-
protected _readAnchor(state: NormalizedListState): number;
|
|
709
|
-
protected _applyAnchor(anchor: number): void;
|
|
710
|
-
protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
|
|
711
|
-
protected _getAnimatedLayerOffset(slotHeight: number, nodeHeight: number): number;
|
|
724
|
+
//#region src/renderer/virtualized/list.d.ts
|
|
725
|
+
interface ListRendererOptions<C extends CanvasRenderingContext2D, T extends {}> extends ListLayoutOptions {
|
|
726
|
+
renderItem: (item: T) => Node<C>;
|
|
727
|
+
list: ListState<T>;
|
|
712
728
|
}
|
|
713
|
-
//#endregion
|
|
714
|
-
//#region src/renderer/virtualized/timeline.d.ts
|
|
715
729
|
/**
|
|
716
|
-
* Virtualized renderer
|
|
730
|
+
* Virtualized list renderer with configurable anchor semantics.
|
|
717
731
|
*/
|
|
718
|
-
declare class
|
|
719
|
-
|
|
732
|
+
declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
|
|
733
|
+
#private;
|
|
734
|
+
constructor(graphics: C, options: ListRendererOptions<C, T>);
|
|
735
|
+
protected _getLayoutOptions(): ResolvedListLayoutOptions;
|
|
736
|
+
protected _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
|
|
720
737
|
protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
|
|
721
738
|
protected _normalizeListState(state: VisibleListState): NormalizedListState;
|
|
722
|
-
protected _readAnchor(state: NormalizedListState): number;
|
|
739
|
+
protected _readAnchor(state: NormalizedListState, readItemHeight: (index: number) => number): number;
|
|
723
740
|
protected _applyAnchor(anchor: number): void;
|
|
724
741
|
protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
|
|
725
|
-
protected _getAnimatedLayerOffset(_slotHeight: number, _nodeHeight: number): number;
|
|
726
742
|
}
|
|
727
743
|
//#endregion
|
|
728
|
-
export { Axis, BaseRenderer, Box,
|
|
744
|
+
export { Axis, BaseRenderer, Box, ChildLayoutResult, Context, CrossAxisAlignment, DebugRenderer, DeleteListItemAnimationOptions, DynValue, Fixed, Flex, FlexContainerOptions, FlexItem, FlexItemOptions, FlexLayoutResult, Group, HitTest, InlineSpan, InsertListItemsAnimationOptions, JumpToOptions, LayoutConstraints, LayoutRect, ListAnchorMode, ListLayoutOptions, ListRenderer, ListRendererOptions, ListState, ListUnderflowAlign, MainAxisAlignment, MainAxisSize, MultilineText, MultilineTextOptions, Node, PaddingBox, PhysicalTextAlign, Place, PushListItemsAnimationOptions, RenderFeedback, RendererOptions, ShrinkWrap, Text, TextAlign, TextEllipsisPosition, TextJustifyMode, TextJustifyOptions, TextOptions, TextOverflowMode, TextOverflowWrapMode, TextStyleOptions, TextWhiteSpaceMode, TextWordBreakMode, UnshiftListItemsAnimationOptions, UpdateListItemAnimationOptions, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
729
745
|
//# sourceMappingURL=index.d.mts.map
|