chat-layout 1.2.0-5 → 1.2.0-7

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
@@ -67,11 +67,11 @@ list.unshiftAll([olderMessage], {
67
67
  });
68
68
  ```
69
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:
70
+ To make chat-style inserts automatically follow the latest visible edge, pass `autoFollow: true`. When the corresponding auto-follow latch is armed, the insert behaves like a conditional `jumpToTop()` / `jumpToBottom()` after the items are inserted:
71
71
 
72
72
  ```ts
73
73
  list.pushAll([nextMessage], {
74
- followIfAtBoundary: true,
74
+ autoFollow: true,
75
75
  duration: 220,
76
76
  });
77
77
  ```
package/example/chat.ts CHANGED
@@ -505,6 +505,7 @@ const renderer = new ListRenderer(ctx, {
505
505
  renderItem,
506
506
  list,
507
507
  });
508
+ renderer.padding = { top: 32, bottom: 32 };
508
509
  let nextMessageId = list.items.length + 1;
509
510
 
510
511
  function drawFrame(): void {
@@ -513,19 +514,32 @@ function drawFrame(): void {
513
514
  maxIdx: Number.NaN,
514
515
  min: Number.NaN,
515
516
  max: Number.NaN,
517
+ canAutoFollowTop: false,
518
+ canAutoFollowBottom: false,
516
519
  };
517
520
  renderer.render(feedback);
518
521
 
519
522
  ctx.save();
523
+
524
+ ctx.fillStyle = "rgba(255, 0, 255, 0.5)";
525
+ ctx.fillRect(0, 0, canvas.clientWidth, 32);
526
+ ctx.fillRect(0, canvas.clientHeight - 32, canvas.clientWidth, 32);
527
+
520
528
  ctx.textBaseline = "top";
521
529
  ctx.font = "12px system-ui";
522
530
  ctx.fillStyle = "black";
523
531
  ctx.strokeStyle = "white";
524
532
  ctx.lineWidth = 4;
525
533
  ctx.lineJoin = "round";
526
- const text = JSON.stringify(feedback);
527
- ctx.strokeText(text, 10, 10);
528
- ctx.fillText(text, 10, 10);
534
+ const lines = Object.entries(feedback).map(
535
+ ([key, value]) => `${key}: ${String(value)}`,
536
+ );
537
+ const lineHeight = 14;
538
+ for (const [index, line] of lines.entries()) {
539
+ const y = 10 + index * lineHeight;
540
+ ctx.strokeText(line, 10, y);
541
+ ctx.fillText(line, 10, y);
542
+ }
529
543
  ctx.restore();
530
544
 
531
545
  requestAnimationFrame(drawFrame);
@@ -644,7 +658,7 @@ button("unshift", () => {
644
658
  ],
645
659
  {
646
660
  duration: INSERT_ANIMATION_DURATION,
647
- followIfAtBoundary: true,
661
+ autoFollow: true,
648
662
  },
649
663
  );
650
664
  });
@@ -660,8 +674,7 @@ button("push", () => {
660
674
  },
661
675
  ],
662
676
  {
663
- distance: 24,
664
- followIfAtBoundary: true,
677
+ autoFollow: true,
665
678
  },
666
679
  );
667
680
  });
package/index.d.mts CHANGED
@@ -19,6 +19,10 @@ interface RenderFeedback {
19
19
  min: number;
20
20
  /** Largest visible continuous item position, expressed in item coordinates rather than pixels. */
21
21
  max: number;
22
+ /** Whether the current viewport may auto-follow inserts at the visual top edge. */
23
+ canAutoFollowTop: boolean;
24
+ /** Whether the current viewport may auto-follow inserts at the visual bottom edge. */
25
+ canAutoFollowBottom: boolean;
22
26
  }
23
27
  /**
24
28
  * The main axis direction used by flex containers.
@@ -520,19 +524,25 @@ interface DeleteListItemAnimationOptions {
520
524
  interface InsertListItemsAnimationOptions {
521
525
  /** Animation duration in milliseconds. */
522
526
  duration?: number;
523
- /** Enter offset in pixels measured from the final resting position. */
524
- distance?: number;
525
527
  /** Auto-follow the insertion edge when the viewport was already pinned there. */
526
- followIfAtBoundary?: boolean;
528
+ autoFollow?: boolean;
527
529
  }
528
530
  type PushListItemsAnimationOptions = InsertListItemsAnimationOptions;
529
531
  type UnshiftListItemsAnimationOptions = InsertListItemsAnimationOptions;
532
+ type ListScrollMutationSource = "external" | "internal";
533
+ type ListScrollStatePatch = {
534
+ position?: number | undefined;
535
+ offset?: number;
536
+ };
537
+ declare const WRITE_LIST_SCROLL_STATE: unique symbol;
530
538
  declare class ListState<T extends {}> {
531
539
  #private;
532
540
  /** Pixel offset from the anchored item edge. */
533
- offset: number;
541
+ get offset(): number;
542
+ set offset(value: number);
534
543
  /** Anchor item index, or `undefined` to use the renderer default. */
535
- position: number | undefined;
544
+ get position(): number | undefined;
545
+ set position(value: number | undefined);
536
546
  /** Items currently managed by the renderer. */
537
547
  get items(): T[];
538
548
  /** Replaces the full item collection while preserving scroll state. */
@@ -573,6 +583,7 @@ declare class ListState<T extends {}> {
573
583
  resetScroll(): void;
574
584
  /** Applies a relative pixel scroll delta. */
575
585
  applyScroll(delta: number): void;
586
+ [WRITE_LIST_SCROLL_STATE](patch: ListScrollStatePatch, source: ListScrollMutationSource): void;
576
587
  }
577
588
  //#endregion
578
589
  //#region src/renderer/memo.d.ts
@@ -593,6 +604,10 @@ declare function memoRenderItemBy<C extends CanvasRenderingContext2D, T, K>(keyO
593
604
  };
594
605
  //#endregion
595
606
  //#region src/renderer/virtualized/base-types.d.ts
607
+ type AutoFollowCapabilities = {
608
+ top: boolean;
609
+ bottom: boolean;
610
+ };
596
611
  /** Per-item draw/hittest callbacks produced by the resolver. */
597
612
  type VirtualizedResolvedItem = {
598
613
  draw: (y: number) => boolean;
@@ -602,13 +617,31 @@ type VirtualizedResolvedItem = {
602
617
  //#region src/renderer/virtualized/solver.d.ts
603
618
  type ListAnchorMode = "top" | "bottom";
604
619
  type ListUnderflowAlign = "top" | "bottom";
620
+ interface ListPadding {
621
+ top?: number;
622
+ bottom?: number;
623
+ }
624
+ interface ResolvedListPadding {
625
+ top: number;
626
+ bottom: number;
627
+ }
605
628
  interface ListLayoutOptions {
606
629
  anchorMode?: ListAnchorMode;
607
630
  underflowAlign?: ListUnderflowAlign;
631
+ padding?: ListPadding;
608
632
  }
609
633
  interface ResolvedListLayoutOptions {
610
634
  anchorMode: ListAnchorMode;
611
635
  underflowAlign: ListUnderflowAlign;
636
+ padding: ResolvedListPadding;
637
+ }
638
+ interface ListViewportMetrics {
639
+ outerHeight: number;
640
+ contentTop: number;
641
+ contentBottom: number;
642
+ contentHeight: number;
643
+ outerContentTop: number;
644
+ outerContentBottom: number;
612
645
  }
613
646
  interface VisibleListState {
614
647
  position?: number;
@@ -690,17 +723,30 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
690
723
  * Scrolls the viewport to the requested item index.
691
724
  */
692
725
  jumpTo(index: number, options?: JumpToOptions): void;
726
+ /**
727
+ * Scrolls the viewport to the visual top edge and arms top auto-follow immediately.
728
+ */
729
+ jumpToTop(options?: JumpToOptions): void;
730
+ /**
731
+ * Scrolls the viewport to the visual bottom edge and arms bottom auto-follow immediately.
732
+ */
733
+ jumpToBottom(options?: JumpToOptions): void;
693
734
  protected _resetRenderFeedback(feedback?: RenderFeedback): void;
694
735
  protected _accumulateRenderFeedback(feedback: RenderFeedback, idx: number, top: number, height: number): void;
695
736
  protected _renderDrawList(list: VisibleWindow<VirtualizedResolvedItem>["drawList"], shift: number, feedback?: RenderFeedback): boolean;
696
- protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback, extraShift?: number): boolean;
737
+ protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback): boolean;
738
+ protected _readAutoFollowCapabilities(window: VisibleWindow<VirtualizedResolvedItem>): AutoFollowCapabilities;
697
739
  protected _readVisibleRange(top: number, height: number): {
698
740
  top: number;
699
741
  bottom: number;
700
742
  } | undefined;
743
+ protected _readOuterVisibleRange(top: number, height: number): {
744
+ top: number;
745
+ bottom: number;
746
+ } | undefined;
701
747
  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;
748
+ protected _hittestVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, test: HitTest): boolean;
749
+ protected _captureVisibleItemSnapshot(solution: VisibleWindowResult<unknown>): void;
704
750
  protected _prepareRender(now: number): boolean;
705
751
  protected _finishRender(requestRedraw: boolean): boolean;
706
752
  protected _clampItemIndex(index: number): number;
@@ -719,6 +765,7 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
719
765
  protected abstract _applyAnchor(anchor: number): void;
720
766
  protected abstract _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
721
767
  protected abstract _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
768
+ protected _getViewportMetrics(): ListViewportMetrics;
722
769
  }
723
770
  //#endregion
724
771
  //#region src/renderer/virtualized/list.d.ts
@@ -732,6 +779,8 @@ interface ListRendererOptions<C extends CanvasRenderingContext2D, T extends {}>
732
779
  declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
733
780
  #private;
734
781
  constructor(graphics: C, options: ListRendererOptions<C, T>);
782
+ get padding(): ListPadding;
783
+ set padding(value: ListPadding);
735
784
  protected _getLayoutOptions(): ResolvedListLayoutOptions;
736
785
  protected _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
737
786
  protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
@@ -741,5 +790,5 @@ declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> ext
741
790
  protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
742
791
  }
743
792
  //#endregion
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 };
793
+ 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, ListPadding, 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 };
745
794
  //# sourceMappingURL=index.d.mts.map