chat-layout 1.2.0 → 1.3.0-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/example/chat.ts CHANGED
@@ -673,17 +673,17 @@ button("push", () => {
673
673
  });
674
674
 
675
675
  button("jump middle", () => {
676
- renderer.jumpTo(Math.floor(list.items.length / 2));
676
+ list.scrollTo(Math.floor(list.items.length / 2));
677
677
  });
678
678
 
679
679
  button("jump middle (center)", () => {
680
- renderer.jumpTo(Math.floor(list.items.length / 2), {
680
+ list.scrollTo(Math.floor(list.items.length / 2), {
681
681
  block: "center",
682
682
  });
683
683
  });
684
684
 
685
685
  button("jump latest (no anim)", () => {
686
- renderer.jumpTo(list.items.length - 1, {
686
+ list.scrollTo(list.items.length - 1, {
687
687
  animated: false,
688
688
  });
689
689
  });
package/index.d.mts CHANGED
@@ -533,6 +533,16 @@ interface InsertListItemsAnimationOptions {
533
533
  }
534
534
  type PushListItemsAnimationOptions = InsertListItemsAnimationOptions;
535
535
  type UnshiftListItemsAnimationOptions = InsertListItemsAnimationOptions;
536
+ interface ScrollToOptions {
537
+ /** Whether to animate the jump. Defaults to `true`. */
538
+ animated?: boolean;
539
+ /** Which edge of the item should align with the viewport. */
540
+ block?: "start" | "center" | "end";
541
+ /** Animation duration in milliseconds. */
542
+ duration?: number;
543
+ /** Called after the scroll completes or finishes animating. */
544
+ onComplete?: () => void;
545
+ }
536
546
  type ListScrollMutationSource = "external" | "internal";
537
547
  type ListScrollStatePatch = {
538
548
  position?: number | undefined;
@@ -580,6 +590,16 @@ declare class ListState<T extends {}> {
580
590
  reset(items?: T[]): void;
581
591
  /** Applies a relative pixel scroll delta. */
582
592
  applyScroll(delta: number): void;
593
+ /** Scrolls the viewport to the requested item index. */
594
+ scrollTo(index: number, options?: ScrollToOptions): void;
595
+ /**
596
+ * Scrolls the viewport to the visual top edge and arms top auto-follow immediately.
597
+ */
598
+ scrollToTop(options?: ScrollToOptions): void;
599
+ /**
600
+ * Scrolls the viewport to the visual bottom edge and arms bottom auto-follow immediately.
601
+ */
602
+ scrollToBottom(options?: ScrollToOptions): void;
583
603
  [WRITE_LIST_SCROLL_STATE](patch: ListScrollStatePatch, source: ListScrollMutationSource): void;
584
604
  }
585
605
  //#endregion
@@ -665,19 +685,6 @@ type VirtualizedResolvedItem = {
665
685
  };
666
686
  //#endregion
667
687
  //#region src/renderer/virtualized/base.d.ts
668
- /**
669
- * Options for programmatic scrolling to a target item.
670
- */
671
- interface JumpToOptions {
672
- /** Whether to animate the jump. Defaults to `true`. */
673
- animated?: boolean;
674
- /** Which edge of the item should align with the viewport. */
675
- block?: "start" | "center" | "end";
676
- /** Animation duration in milliseconds. */
677
- duration?: number;
678
- /** Called after the jump completes or finishes animating. */
679
- onComplete?: () => void;
680
- }
681
688
  /**
682
689
  * Shared base class for virtualized list renderers.
683
690
  */
@@ -712,18 +719,6 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
712
719
  protected _readListState(): VisibleListState;
713
720
  protected _resolveVisibleWindow(now: number): VisibleWindowResult<VirtualizedResolvedItem>;
714
721
  protected _commitListState(state: NormalizedListState): void;
715
- /**
716
- * Scrolls the viewport to the requested item index.
717
- */
718
- jumpTo(index: number, options?: JumpToOptions): void;
719
- /**
720
- * Scrolls the viewport to the visual top edge and arms top auto-follow immediately.
721
- */
722
- jumpToTop(options?: JumpToOptions): void;
723
- /**
724
- * Scrolls the viewport to the visual bottom edge and arms bottom auto-follow immediately.
725
- */
726
- jumpToBottom(options?: JumpToOptions): void;
727
722
  protected _resetRenderFeedback(feedback?: RenderFeedback): void;
728
723
  protected _accumulateRenderFeedback(feedback: RenderFeedback, idx: number, top: number, height: number): void;
729
724
  protected _renderDrawList(list: VisibleWindow<VirtualizedResolvedItem>["drawList"], shift: number, feedback?: RenderFeedback): boolean;
@@ -756,8 +751,8 @@ declare abstract class VirtualizedRenderer<C extends CanvasRenderingContext2D, T
756
751
  protected abstract _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
757
752
  protected abstract _readAnchor(state: NormalizedListState, readItemHeight: (index: number) => number): number;
758
753
  protected abstract _applyAnchor(anchor: number): void;
759
- protected abstract _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
760
- protected abstract _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
754
+ protected abstract _getDefaultJumpBlock(): NonNullable<ScrollToOptions["block"]>;
755
+ protected abstract _getTargetAnchor(index: number, block: NonNullable<ScrollToOptions["block"]>): number;
761
756
  protected _getViewportMetrics(): ListViewportMetrics;
762
757
  }
763
758
  //#endregion
@@ -776,12 +771,12 @@ declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> ext
776
771
  set padding(value: ListPadding);
777
772
  protected _getLayoutOptions(): ResolvedListLayoutOptions;
778
773
  protected _resolveVisibleWindowForState(state: VisibleListState, now: number): VisibleWindowResult<VirtualizedResolvedItem>;
779
- protected _getDefaultJumpBlock(): NonNullable<JumpToOptions["block"]>;
774
+ protected _getDefaultJumpBlock(): NonNullable<ScrollToOptions["block"]>;
780
775
  protected _normalizeListState(state: VisibleListState): NormalizedListState;
781
776
  protected _readAnchor(state: NormalizedListState, readItemHeight: (index: number) => number): number;
782
777
  protected _applyAnchor(anchor: number): void;
783
- protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
778
+ protected _getTargetAnchor(index: number, block: NonNullable<ScrollToOptions["block"]>): number;
784
779
  }
785
780
  //#endregion
786
- 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, initRenderFeedback, memoRenderItem, memoRenderItemBy };
781
+ export { Axis, BaseRenderer, Box, ChildLayoutResult, Context, CrossAxisAlignment, DebugRenderer, DeleteListItemAnimationOptions, DynValue, Fixed, Flex, FlexContainerOptions, FlexItem, FlexItemOptions, FlexLayoutResult, Group, HitTest, InlineSpan, InsertListItemsAnimationOptions, LayoutConstraints, LayoutRect, ListAnchorMode, ListLayoutOptions, ListPadding, ListRenderer, ListRendererOptions, ListState, ListUnderflowAlign, MainAxisAlignment, MainAxisSize, MultilineText, MultilineTextOptions, Node, PaddingBox, PhysicalTextAlign, Place, PushListItemsAnimationOptions, RenderFeedback, RendererOptions, ScrollToOptions, ShrinkWrap, Text, TextAlign, TextEllipsisPosition, TextJustifyMode, TextJustifyOptions, TextOptions, TextOverflowMode, TextOverflowWrapMode, TextStyleOptions, TextWhiteSpaceMode, TextWordBreakMode, UnshiftListItemsAnimationOptions, UpdateListItemAnimationOptions, VirtualizedRenderer, Wrapper, initRenderFeedback, memoRenderItem, memoRenderItemBy };
787
782
  //# sourceMappingURL=index.d.mts.map
package/index.mjs CHANGED
@@ -3576,11 +3576,13 @@ var DebugRenderer = class extends BaseRenderer {
3576
3576
  //#endregion
3577
3577
  //#region src/renderer/list-state.ts
3578
3578
  const listStateChangeQueues = /* @__PURE__ */ new WeakMap();
3579
+ const listScrollCommandQueues = /* @__PURE__ */ new WeakMap();
3579
3580
  const listScrollMutations = /* @__PURE__ */ new WeakMap();
3580
3581
  const WRITE_LIST_SCROLL_STATE = Symbol("writeListScrollState");
3581
3582
  const FINALIZE_LIST_DELETE = Symbol("finalizeListDelete");
3582
3583
  const LIST_STATE_CHANGE_TIME = Symbol("listStateChangeTime");
3583
3584
  const LIST_STATE_CHANGE_SNAPSHOT = Symbol("listStateChangeSnapshot");
3585
+ const LIST_SCROLL_COMMAND_TIME = Symbol("listScrollCommandTime");
3584
3586
  function normalizePosition(value) {
3585
3587
  return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : void 0;
3586
3588
  }
@@ -3616,6 +3618,14 @@ function writeInternalListScrollState(list, state) {
3616
3618
  function finalizeInternalListDelete(list, item) {
3617
3619
  list[FINALIZE_LIST_DELETE](item);
3618
3620
  }
3621
+ function normalizeScrollToOptions(options) {
3622
+ const normalized = {};
3623
+ if (typeof options?.animated === "boolean") normalized.animated = options.animated;
3624
+ if (options?.block === "start" || options?.block === "center" || options?.block === "end") normalized.block = options.block;
3625
+ if (options?.duration != null && Number.isFinite(options.duration)) normalized.duration = options.duration;
3626
+ if (typeof options?.onComplete === "function") normalized.onComplete = options.onComplete;
3627
+ return normalized;
3628
+ }
3619
3629
  function enqueueListStateChange(list, change) {
3620
3630
  const key = list;
3621
3631
  let queue = listStateChangeQueues.get(key);
@@ -3638,6 +3648,20 @@ function enqueueListStateChange(list, change) {
3638
3648
  });
3639
3649
  queue.push(timestampedChange);
3640
3650
  }
3651
+ function enqueueListScrollCommand(list, command) {
3652
+ const key = list;
3653
+ let queue = listScrollCommandQueues.get(key);
3654
+ if (queue == null) {
3655
+ queue = [];
3656
+ listScrollCommandQueues.set(key, queue);
3657
+ }
3658
+ const timestampedCommand = command;
3659
+ Object.defineProperty(timestampedCommand, LIST_SCROLL_COMMAND_TIME, {
3660
+ value: performance.now(),
3661
+ configurable: true
3662
+ });
3663
+ queue.push(timestampedCommand);
3664
+ }
3641
3665
  function drainInternalListStateChanges(list) {
3642
3666
  const key = list;
3643
3667
  const queue = listStateChangeQueues.get(key);
@@ -3645,12 +3669,22 @@ function drainInternalListStateChanges(list) {
3645
3669
  listStateChangeQueues.delete(key);
3646
3670
  return queue;
3647
3671
  }
3672
+ function drainInternalListScrollCommands(list) {
3673
+ const key = list;
3674
+ const queue = listScrollCommandQueues.get(key);
3675
+ if (queue == null || queue.length === 0) return [];
3676
+ listScrollCommandQueues.delete(key);
3677
+ return queue;
3678
+ }
3648
3679
  function readInternalListStateChangeTime(change) {
3649
3680
  return change[LIST_STATE_CHANGE_TIME];
3650
3681
  }
3651
3682
  function readInternalListStateChangeSnapshot(change) {
3652
3683
  return change[LIST_STATE_CHANGE_SNAPSHOT];
3653
3684
  }
3685
+ function readInternalListScrollCommandTime(command) {
3686
+ return command[LIST_SCROLL_COMMAND_TIME];
3687
+ }
3654
3688
  function isObjectIdentityCandidate(value) {
3655
3689
  return typeof value === "object" && value !== null || typeof value === "function";
3656
3690
  }
@@ -3834,6 +3868,34 @@ var ListState = class {
3834
3868
  applyScroll(delta) {
3835
3869
  this.#writeScrollState({ offset: this.#offset + delta }, "external");
3836
3870
  }
3871
+ /** Scrolls the viewport to the requested item index. */
3872
+ scrollTo(index, options = {}) {
3873
+ enqueueListScrollCommand(this, {
3874
+ type: "index",
3875
+ index,
3876
+ options: normalizeScrollToOptions(options)
3877
+ });
3878
+ }
3879
+ /**
3880
+ * Scrolls the viewport to the visual top edge and arms top auto-follow immediately.
3881
+ */
3882
+ scrollToTop(options = {}) {
3883
+ enqueueListScrollCommand(this, {
3884
+ type: "boundary",
3885
+ boundary: "top",
3886
+ options: normalizeScrollToOptions(options)
3887
+ });
3888
+ }
3889
+ /**
3890
+ * Scrolls the viewport to the visual bottom edge and arms bottom auto-follow immediately.
3891
+ */
3892
+ scrollToBottom(options = {}) {
3893
+ enqueueListScrollCommand(this, {
3894
+ type: "boundary",
3895
+ boundary: "bottom",
3896
+ options: normalizeScrollToOptions(options)
3897
+ });
3898
+ }
3837
3899
  [WRITE_LIST_SCROLL_STATE](patch, source) {
3838
3900
  this.#writeScrollState(patch, source);
3839
3901
  }
@@ -4112,16 +4174,16 @@ var JumpController = class JumpController {
4112
4174
  commit(state) {
4113
4175
  this.#lastHandledScrollMutationVersion = this.#options.readScrollMutation().version;
4114
4176
  }
4115
- jumpTo(index, options = {}) {
4177
+ jumpTo(index, options = {}, now = getNow()) {
4116
4178
  this.#clearPendingTransitionSettleReconcile();
4117
4179
  this.#clearPendingPostJumpBoundary();
4118
4180
  if (this.#options.getItemCount() === 0) {
4119
4181
  this.#cancelJumpAnimation();
4120
4182
  return;
4121
4183
  }
4122
- this.#startJumpToIndex(index, options);
4184
+ this.#startJumpToIndex(index, options, now);
4123
4185
  }
4124
- jumpToBoundary(boundary, options = {}) {
4186
+ jumpToBoundary(boundary, options = {}, now = getNow()) {
4125
4187
  this.#clearPendingTransitionSettleReconcile();
4126
4188
  this.#clearPendingPostJumpBoundary();
4127
4189
  this.#armAutoFollowBoundary(boundary, "jump-to-boundary");
@@ -4132,7 +4194,7 @@ var JumpController = class JumpController {
4132
4194
  this.#startJumpToIndex(boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
4133
4195
  ...options,
4134
4196
  block: boundary === "bottom" ? "end" : "start"
4135
- });
4197
+ }, now);
4136
4198
  }
4137
4199
  beginAutoFollowBoundaryObservation(boundary) {
4138
4200
  if (boundary === "top") {
@@ -5404,8 +5466,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5404
5466
  /** Renders the current visible window. */
5405
5467
  render(feedback) {
5406
5468
  this.#drainPendingListStateChanges();
5407
- this.#jumpController.beforeFrame();
5408
5469
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5470
+ this.#drainPendingListScrollCommands();
5471
+ this.#jumpController.beforeFrame();
5409
5472
  const now = getNow();
5410
5473
  const keepAnimating = this._prepareRender(now);
5411
5474
  const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
@@ -5428,8 +5491,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5428
5491
  /** Hit-tests the current visible window. */
5429
5492
  hittest(test) {
5430
5493
  this.#drainPendingListStateChanges();
5431
- this.#jumpController.beforeFrame();
5432
5494
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5495
+ this.#drainPendingListScrollCommands();
5496
+ this.#jumpController.beforeFrame();
5433
5497
  const now = getNow();
5434
5498
  this.#transitionController.settle(now, this.#getTransitionLifecycleAdapter());
5435
5499
  const frame = prepareFrameSession({
@@ -5454,24 +5518,6 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5454
5518
  writeInternalListScrollState(this.options.list, state);
5455
5519
  this.#jumpController.commit(state);
5456
5520
  }
5457
- /**
5458
- * Scrolls the viewport to the requested item index.
5459
- */
5460
- jumpTo(index, options = {}) {
5461
- this.#jumpController.jumpTo(index, options);
5462
- }
5463
- /**
5464
- * Scrolls the viewport to the visual top edge and arms top auto-follow immediately.
5465
- */
5466
- jumpToTop(options = {}) {
5467
- this.#jumpController.jumpToBoundary("top", options);
5468
- }
5469
- /**
5470
- * Scrolls the viewport to the visual bottom edge and arms bottom auto-follow immediately.
5471
- */
5472
- jumpToBottom(options = {}) {
5473
- this.#jumpController.jumpToBoundary("bottom", options);
5474
- }
5475
5521
  _resetRenderFeedback(feedback) {
5476
5522
  if (feedback == null) return;
5477
5523
  initRenderFeedback(feedback);
@@ -5672,6 +5718,17 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5672
5718
  for (const change of changes) this.#handleListStateChange(change, readInternalListStateChangeTime(change), readInternalListStateChangeSnapshot(change));
5673
5719
  }
5674
5720
  }
5721
+ #handleListScrollCommand(command, now) {
5722
+ if (command.type === "boundary") {
5723
+ this.#jumpController.jumpToBoundary(command.boundary, command.options, now);
5724
+ return;
5725
+ }
5726
+ this.#jumpController.jumpTo(command.index, command.options, now);
5727
+ }
5728
+ #drainPendingListScrollCommands() {
5729
+ const commands = drainInternalListScrollCommands(this.options.list);
5730
+ for (const command of commands) this.#handleListScrollCommand(command, readInternalListScrollCommandTime(command));
5731
+ }
5675
5732
  };
5676
5733
  //#endregion
5677
5734
  //#region src/renderer/virtualized/anchor-model.ts