chat-layout 1.2.0-6 → 1.2.0-8

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
  ```
@@ -178,7 +178,7 @@ Notes:
178
178
  - `FlexItem` exposes `grow`, `shrink`, and `alignSelf`; `basis` is no longer public.
179
179
  - `MultilineText` now uses `align` / `physicalAlign` instead of `alignment`.
180
180
  - `ListState.position` uses `undefined` for the renderer default anchor.
181
- - Use `list.resetScroll()` or `list.setAnchor(index, offset)` instead of assigning `Number.NaN`.
181
+ - Use `list.applyScroll(delta)` for relative scrolling, or renderer `jumpTo()` / `jumpToTop()` / `jumpToBottom()` for absolute navigation.
182
182
 
183
183
  ## Development
184
184
 
package/example/chat.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
+ Fixed,
2
3
  Flex,
3
4
  FlexItem,
4
- Fixed,
5
5
  ListRenderer,
6
6
  ListState,
7
7
  MultilineText,
@@ -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 {
@@ -519,15 +520,26 @@ function drawFrame(): void {
519
520
  renderer.render(feedback);
520
521
 
521
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
+
522
528
  ctx.textBaseline = "top";
523
529
  ctx.font = "12px system-ui";
524
530
  ctx.fillStyle = "black";
525
531
  ctx.strokeStyle = "white";
526
532
  ctx.lineWidth = 4;
527
533
  ctx.lineJoin = "round";
528
- const text = JSON.stringify(feedback);
529
- ctx.strokeText(text, 10, 10);
530
- 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
+ }
531
543
  ctx.restore();
532
544
 
533
545
  requestAnimationFrame(drawFrame);
@@ -662,7 +674,6 @@ button("push", () => {
662
674
  },
663
675
  ],
664
676
  {
665
- distance: 24,
666
677
  autoFollow: true,
667
678
  },
668
679
  );
package/index.d.mts CHANGED
@@ -524,19 +524,24 @@ interface DeleteListItemAnimationOptions {
524
524
  interface InsertListItemsAnimationOptions {
525
525
  /** Animation duration in milliseconds. */
526
526
  duration?: number;
527
- /** Enter offset in pixels measured from the final resting position. */
528
- distance?: number;
529
527
  /** Auto-follow the insertion edge when the viewport was already pinned there. */
530
528
  autoFollow?: boolean;
531
529
  }
532
530
  type PushListItemsAnimationOptions = InsertListItemsAnimationOptions;
533
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;
538
+ declare const FINALIZE_LIST_DELETE: unique symbol;
534
539
  declare class ListState<T extends {}> {
535
540
  #private;
536
541
  /** Pixel offset from the anchored item edge. */
537
- offset: number;
542
+ get offset(): number;
538
543
  /** Anchor item index, or `undefined` to use the renderer default. */
539
- position: number | undefined;
544
+ get position(): number | undefined;
540
545
  /** Items currently managed by the renderer. */
541
546
  get items(): T[];
542
547
  /** Replaces the full item collection while preserving scroll state. */
@@ -564,19 +569,14 @@ declare class ListState<T extends {}> {
564
569
  /**
565
570
  * Finalizes a pending delete by removing the item from the list.
566
571
  */
567
- finalizeDelete(item: T): void;
568
- /**
569
- * Sets the current anchor item and pixel offset.
570
- */
571
- setAnchor(position: number, offset?: number): void;
572
+ [FINALIZE_LIST_DELETE](item: T): void;
572
573
  /**
573
574
  * Replaces all items and clears scroll state.
574
575
  */
575
576
  reset(items?: T[]): void;
576
- /** Clears the current scroll anchor while keeping the items. */
577
- resetScroll(): void;
578
577
  /** Applies a relative pixel scroll delta. */
579
578
  applyScroll(delta: number): void;
579
+ [WRITE_LIST_SCROLL_STATE](patch: ListScrollStatePatch, source: ListScrollMutationSource): void;
580
580
  }
581
581
  //#endregion
582
582
  //#region src/renderer/memo.d.ts
@@ -596,27 +596,34 @@ declare function memoRenderItemBy<C extends CanvasRenderingContext2D, T, K>(keyO
596
596
  resetKey: (key: K) => boolean;
597
597
  };
598
598
  //#endregion
599
- //#region src/renderer/virtualized/base-types.d.ts
600
- type AutoFollowCapabilities = {
601
- top: boolean;
602
- bottom: boolean;
603
- };
604
- /** Per-item draw/hittest callbacks produced by the resolver. */
605
- type VirtualizedResolvedItem = {
606
- draw: (y: number) => boolean;
607
- hittest: (test: HitTest, y: number) => boolean;
608
- };
609
- //#endregion
610
599
  //#region src/renderer/virtualized/solver.d.ts
611
600
  type ListAnchorMode = "top" | "bottom";
612
601
  type ListUnderflowAlign = "top" | "bottom";
602
+ interface ListPadding {
603
+ top?: number;
604
+ bottom?: number;
605
+ }
606
+ interface ResolvedListPadding {
607
+ top: number;
608
+ bottom: number;
609
+ }
613
610
  interface ListLayoutOptions {
614
611
  anchorMode?: ListAnchorMode;
615
612
  underflowAlign?: ListUnderflowAlign;
613
+ padding?: ListPadding;
616
614
  }
617
615
  interface ResolvedListLayoutOptions {
618
616
  anchorMode: ListAnchorMode;
619
617
  underflowAlign: ListUnderflowAlign;
618
+ padding: ResolvedListPadding;
619
+ }
620
+ interface ListViewportMetrics {
621
+ outerHeight: number;
622
+ contentTop: number;
623
+ contentBottom: number;
624
+ contentHeight: number;
625
+ outerContentTop: number;
626
+ outerContentBottom: number;
620
627
  }
621
628
  interface VisibleListState {
622
629
  position?: number;
@@ -627,7 +634,7 @@ interface NormalizedListState {
627
634
  offset: number;
628
635
  }
629
636
  interface VisibleWindowEntry<T> {
630
- idx: number;
637
+ index: number;
631
638
  value: T;
632
639
  offset: number;
633
640
  height: number;
@@ -642,6 +649,17 @@ interface VisibleWindowResult<T> {
642
649
  window: VisibleWindow<T>;
643
650
  }
644
651
  //#endregion
652
+ //#region src/renderer/virtualized/virtualized-types.d.ts
653
+ type AutoFollowCapabilities = {
654
+ top: boolean;
655
+ bottom: boolean;
656
+ };
657
+ /** Per-item draw/hittest callbacks produced by the resolver. */
658
+ type VirtualizedResolvedItem = {
659
+ draw: (y: number) => boolean;
660
+ hittest: (test: HitTest, y: number) => boolean;
661
+ };
662
+ //#endregion
645
663
  //#region src/renderer/virtualized/base.d.ts
646
664
  /**
647
665
  * Options for programmatic scrolling to a target item.
@@ -673,12 +691,8 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
673
691
  });
674
692
  /** Current anchor item index. */
675
693
  get position(): number | undefined;
676
- /** Updates the current anchor item index. */
677
- set position(value: number | undefined);
678
694
  /** Pixel offset from the anchored item edge. */
679
695
  get offset(): number;
680
- /** Updates the pixel offset from the anchored item edge. */
681
- set offset(value: number);
682
696
  /** Items currently available to the renderer. */
683
697
  get items(): T[];
684
698
  /** Replaces the current item collection. */
@@ -709,15 +723,19 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
709
723
  protected _resetRenderFeedback(feedback?: RenderFeedback): void;
710
724
  protected _accumulateRenderFeedback(feedback: RenderFeedback, idx: number, top: number, height: number): void;
711
725
  protected _renderDrawList(list: VisibleWindow<VirtualizedResolvedItem>["drawList"], shift: number, feedback?: RenderFeedback): boolean;
712
- protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback, extraShift?: number): boolean;
713
- protected _readAutoFollowCapabilities(window: VisibleWindow<VirtualizedResolvedItem>, extraShift?: number): AutoFollowCapabilities;
726
+ protected _renderVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, feedback?: RenderFeedback): boolean;
727
+ protected _readAutoFollowCapabilities(window: VisibleWindow<VirtualizedResolvedItem>): AutoFollowCapabilities;
714
728
  protected _readVisibleRange(top: number, height: number): {
715
729
  top: number;
716
730
  bottom: number;
717
731
  } | undefined;
732
+ protected _readOuterVisibleRange(top: number, height: number): {
733
+ top: number;
734
+ bottom: number;
735
+ } | undefined;
718
736
  protected _pruneTransitionAnimations(_window: VisibleWindow<unknown>, now: number): boolean;
719
- protected _hittestVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, test: HitTest, extraShift?: number): boolean;
720
- protected _captureVisibleItemSnapshot(solution: VisibleWindowResult<unknown>, extraShift?: number): void;
737
+ protected _hittestVisibleWindow(window: VisibleWindow<VirtualizedResolvedItem>, test: HitTest): boolean;
738
+ protected _captureVisibleItemSnapshot(solution: VisibleWindowResult<unknown>): void;
721
739
  protected _prepareRender(now: number): boolean;
722
740
  protected _finishRender(requestRedraw: boolean): boolean;
723
741
  protected _clampItemIndex(index: number): number;
@@ -736,6 +754,7 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
736
754
  protected abstract _applyAnchor(anchor: number): void;
737
755
  protected abstract _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
738
756
  protected abstract _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
757
+ protected _getViewportMetrics(): ListViewportMetrics;
739
758
  }
740
759
  //#endregion
741
760
  //#region src/renderer/virtualized/list.d.ts
@@ -749,6 +768,8 @@ interface ListRendererOptions<C extends CanvasRenderingContext2D, T extends {}>
749
768
  declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> extends VirtualizedRenderer<C, T> {
750
769
  #private;
751
770
  constructor(graphics: C, options: ListRendererOptions<C, T>);
771
+ get padding(): ListPadding;
772
+ set padding(value: ListPadding);
752
773
  protected _getLayoutOptions(): ResolvedListLayoutOptions;
753
774
  protected _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
754
775
  protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
@@ -758,5 +779,5 @@ declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> ext
758
779
  protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
759
780
  }
760
781
  //#endregion
761
- 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 };
782
+ 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 };
762
783
  //# sourceMappingURL=index.d.mts.map