@onekeyfe/react-native-native-list 3.0.113 → 3.0.115

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.
@@ -49,7 +49,7 @@ import {
49
49
  const SECTION_INDEX_CONTENT_INSET = 16;
50
50
  const SECTION_INDEX_RAIL_WIDTH = 32;
51
51
  const SECTION_INDEX_EDGE_PADDING = 8;
52
- const SECTION_INDEX_MIN_LABEL_SPACING = 14;
52
+ const SECTION_INDEX_LABEL_SPACING = 16;
53
53
  const SECTION_INDEX_MIN_HEIGHT = 120;
54
54
  const DEFAULT_VIEWPORT_WIDTH = 320;
55
55
  const DEFAULT_VIEWPORT_HEIGHT = 640;
@@ -772,6 +772,32 @@ export function visibleWebLayoutItems(
772
772
  return visible;
773
773
  }
774
774
 
775
+ export type WebCollapsiblePagerScrollMetrics = Readonly<{
776
+ offset: number;
777
+ viewportLength: number;
778
+ }>;
779
+
780
+ export function resolveWebCollapsiblePagerScrollMetrics(
781
+ rawOffset: number,
782
+ rawViewportLength: number,
783
+ headerInset: number,
784
+ stickyInset: number
785
+ ): WebCollapsiblePagerScrollMetrics {
786
+ const header = Math.max(0, headerInset);
787
+ const sticky = Math.max(0, stickyInset);
788
+ return {
789
+ offset: Math.max(0, rawOffset - header),
790
+ viewportLength: Math.max(0, rawViewportLength - sticky),
791
+ };
792
+ }
793
+
794
+ export function resolveWebCollapsiblePagerRawOffset(
795
+ logicalOffset: number,
796
+ headerInset: number
797
+ ): number {
798
+ return Math.max(0, logicalOffset) + Math.max(0, headerInset);
799
+ }
800
+
775
801
  export function webLayoutItemsForMount(
776
802
  layout: WebListLayout,
777
803
  offset: number,
@@ -896,6 +922,11 @@ export const WEB_LIST_CSS = `
896
922
  .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
897
923
  .ok-native-list-row.ok-native-list-market-skeleton{padding:12px 20px;gap:0}.ok-native-list-skeleton-left{display:flex;align-items:center;gap:12px;flex:1}.ok-native-list-skeleton-text{display:flex;flex-direction:column;gap:4px}.ok-native-list-skeleton-right{display:flex;align-items:center;gap:8px}.ok-native-list-skeleton-mark{display:block;flex-shrink:0;border-radius:8px;animation:ok-native-list-skeleton 1.5s linear infinite alternate}@keyframes ok-native-list-skeleton{from{background-color:var(--nl-skeleton-base)}to{background-color:var(--nl-skeleton-highlight)}}.ok-native-list-market-spinner{display:block;width:20px;height:20px;flex-shrink:0;color:var(--nl-icon);animation:ok-native-list-spin .75s linear infinite}
898
924
  @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner,.ok-native-list-market-spinner{animation:none}.ok-native-list-skeleton-mark{animation:none;background:var(--nl-skeleton-base)}}
925
+ .ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
926
+ .ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
927
+ .ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
928
+ .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
929
+ @media (prefers-reduced-motion:reduce){.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
899
930
  /* OneKey patch: selector controls follow their original semantic colors and geometry. */
900
931
  .ok-native-list-checkbox[data-selector="networkSelector"]{padding:0;border-radius:4px;border-color:var(--nl-checkbox-border,var(--nl-separator));background:var(--nl-checkbox-icon,var(--nl-inverse-text))}
901
932
  .ok-native-list-checkbox[data-selector="networkSelector"]::after{display:none}
@@ -3100,7 +3131,8 @@ function createMarketRow(context: RenderContext, row: MarketRow): HTMLElement {
3100
3131
  visual.style.height = String(height) + 'px';
3101
3132
  visual.style.flexBasis = String(width) + 'px';
3102
3133
  visual.style.borderRadius = String(layoutStyle.imageCornerRadius) + 'px';
3103
- const borderColor = 'borderColor' in row.leading ? row.leading.borderColor : undefined;
3134
+ const borderColor =
3135
+ 'borderColor' in row.leading ? row.leading.borderColor : undefined;
3104
3136
  if (borderColor) {
3105
3137
  visual.style.border = '1px solid ' + borderColor;
3106
3138
  visual.style.boxSizing = 'border-box';
@@ -3111,7 +3143,8 @@ function createMarketRow(context: RenderContext, row: MarketRow): HTMLElement {
3111
3143
  image.style.height = '100%';
3112
3144
  if (borderColor) {
3113
3145
  image.style.borderRadius = '0';
3114
- image.style.clipPath = 'inset(-1px round ' + String(layoutStyle.imageCornerRadius) + 'px)';
3146
+ image.style.clipPath =
3147
+ 'inset(-1px round ' + String(layoutStyle.imageCornerRadius) + 'px)';
3115
3148
  }
3116
3149
  image.style.objectFit =
3117
3150
  style?.image?.contentFit ?? image.style.objectFit;
@@ -3277,7 +3310,6 @@ export class NativeListWebEngine {
3277
3310
  private readonly footer: HTMLElement;
3278
3311
  private readonly sticky: HTMLElement;
3279
3312
  private readonly indexRail: HTMLElement;
3280
- private readonly indexPreview: HTMLElement;
3281
3313
  private readonly refreshIndicator: HTMLElement;
3282
3314
  private readonly reorderPreview: HTMLElement;
3283
3315
  private readonly previousHostPosition: string;
@@ -3303,7 +3335,6 @@ export class NativeListWebEngine {
3303
3335
  private avatarDirection = 1;
3304
3336
  private reachedGeneration: number | undefined;
3305
3337
  private stickyKey: string | undefined;
3306
- private previewTimer: number | undefined;
3307
3338
  private sectionIndexEntries: readonly Readonly<{
3308
3339
  key: string;
3309
3340
  title: string;
@@ -3392,12 +3423,6 @@ export class NativeListWebEngine {
3392
3423
  this.indexRail.setAttribute('role', 'navigation');
3393
3424
  this.indexRail.setAttribute('aria-label', 'Section index');
3394
3425
  this.indexRail.hidden = true;
3395
- this.indexPreview = createElement(
3396
- this.document,
3397
- 'div',
3398
- 'ok-native-list-index-preview'
3399
- );
3400
- this.indexPreview.setAttribute('aria-live', 'polite');
3401
3426
  this.refreshIndicator = createElement(
3402
3427
  this.document,
3403
3428
  'div',
@@ -3418,7 +3443,6 @@ export class NativeListWebEngine {
3418
3443
  this.viewport,
3419
3444
  this.sticky,
3420
3445
  this.indexRail,
3421
- this.indexPreview,
3422
3446
  this.refreshIndicator
3423
3447
  );
3424
3448
  this.root.append(style, this.viewportFrame, this.footer);
@@ -3427,6 +3451,10 @@ export class NativeListWebEngine {
3427
3451
  this.viewport.addEventListener('scroll', this.handleScroll, {
3428
3452
  passive: true,
3429
3453
  });
3454
+ this.viewport.addEventListener(
3455
+ 'ok-collapsible-pager-insets-changed',
3456
+ this.handleCollapsiblePagerInsetsChanged
3457
+ );
3430
3458
  this.root.addEventListener('click', this.handleClick);
3431
3459
  this.root.addEventListener('pointerdown', this.handleMarketPointerDown);
3432
3460
  this.root.addEventListener('pointermove', this.handleMarketPointerMove);
@@ -3463,6 +3491,11 @@ export class NativeListWebEngine {
3463
3491
  );
3464
3492
  this.indexRail.addEventListener('pointerdown', this.handleIndexPointer);
3465
3493
  this.indexRail.addEventListener('pointermove', this.handleIndexPointer);
3494
+ this.indexRail.addEventListener('pointerup', this.handleIndexPointerEnd);
3495
+ this.indexRail.addEventListener(
3496
+ 'pointercancel',
3497
+ this.handleIndexPointerEnd
3498
+ );
3466
3499
  this.indexRail.addEventListener('click', this.handleIndexClick);
3467
3500
  this.viewport.addEventListener('pointerdown', this.handlePullStart, {
3468
3501
  passive: true,
@@ -3664,8 +3697,6 @@ export class NativeListWebEngine {
3664
3697
  this.invalidateActionAnchor('destroy');
3665
3698
  this.destroyed = true;
3666
3699
  if (this.frameHandle !== undefined) this.cancelFrame(this.frameHandle);
3667
- if (this.previewTimer !== undefined)
3668
- this.document.defaultView?.clearTimeout(this.previewTimer);
3669
3700
  if (this.reorderMovementTimer !== undefined)
3670
3701
  this.document.defaultView?.clearTimeout(this.reorderMovementTimer);
3671
3702
  this.resizeObserver?.disconnect();
@@ -3674,6 +3705,10 @@ export class NativeListWebEngine {
3674
3705
  this.handleWindowResize
3675
3706
  );
3676
3707
  this.viewport.removeEventListener('scroll', this.handleScroll);
3708
+ this.viewport.removeEventListener(
3709
+ 'ok-collapsible-pager-insets-changed',
3710
+ this.handleCollapsiblePagerInsetsChanged
3711
+ );
3677
3712
  this.root.removeEventListener('click', this.handleClick);
3678
3713
  this.cancelMarketPointer();
3679
3714
  this.root.removeEventListener('pointerdown', this.handleMarketPointerDown);
@@ -3708,6 +3743,11 @@ export class NativeListWebEngine {
3708
3743
  );
3709
3744
  this.indexRail.removeEventListener('pointerdown', this.handleIndexPointer);
3710
3745
  this.indexRail.removeEventListener('pointermove', this.handleIndexPointer);
3746
+ this.indexRail.removeEventListener('pointerup', this.handleIndexPointerEnd);
3747
+ this.indexRail.removeEventListener(
3748
+ 'pointercancel',
3749
+ this.handleIndexPointerEnd
3750
+ );
3711
3751
  this.indexRail.removeEventListener('click', this.handleIndexClick);
3712
3752
  this.viewport.removeEventListener('pointerdown', this.handlePullStart);
3713
3753
  this.viewport.removeEventListener('pointermove', this.handlePullMove);
@@ -3782,7 +3822,20 @@ export class NativeListWebEngine {
3782
3822
  private recomputeLayout = () => {
3783
3823
  if (this.destroyed) return;
3784
3824
  const viewportWidth = this.viewport.clientWidth;
3785
- const viewportHeight = this.viewport.clientHeight;
3825
+ const viewportHeight =
3826
+ this.snapshot.layout.orientation === 'horizontal'
3827
+ ? this.viewport.clientHeight
3828
+ : this.verticalScrollMetrics().viewportLength;
3829
+ if (this.snapshot.layout.orientation !== 'horizontal') {
3830
+ const stickyInset = this.collapsiblePagerInsets().sticky;
3831
+ this.sticky.style.top = String(stickyInset) + 'px';
3832
+ this.indexRail.style.top = String(stickyInset) + 'px';
3833
+ this.refreshIndicator.style.top = String(stickyInset + 8) + 'px';
3834
+ } else {
3835
+ this.sticky.style.top = '0px';
3836
+ this.indexRail.style.top = '0px';
3837
+ this.refreshIndicator.style.top = '8px';
3838
+ }
3786
3839
  if (
3787
3840
  this.lastViewportWidth >= 0 &&
3788
3841
  (viewportWidth !== this.lastViewportWidth ||
@@ -3813,7 +3866,11 @@ export class NativeListWebEngine {
3813
3866
  );
3814
3867
  this.content.style.width = String(this.layout.contentWidth) + 'px';
3815
3868
  this.content.style.height = String(this.layout.contentHeight) + 'px';
3816
- this.renderSectionIndex(viewportHeight || DEFAULT_VIEWPORT_HEIGHT);
3869
+ this.renderSectionIndex(
3870
+ this.viewport.clientHeight <= 0
3871
+ ? DEFAULT_VIEWPORT_HEIGHT
3872
+ : Math.max(1, viewportHeight)
3873
+ );
3817
3874
  if (previousHorizontal !== this.layout.horizontal) {
3818
3875
  this.viewport.scrollLeft = 0;
3819
3876
  this.viewport.scrollTop = 0;
@@ -4121,17 +4178,15 @@ export class NativeListWebEngine {
4121
4178
  ): readonly number[] {
4122
4179
  const entryCount = this.sectionIndexEntries.length;
4123
4180
  if (entryCount <= 1) return entryCount ? [0] : [];
4124
- const availableHeight = Math.max(
4125
- 0,
4126
- viewportHeight - SECTION_INDEX_EDGE_PADDING * 2
4127
- );
4181
+ const { trackHeight } = this.sectionIndexMetrics(viewportHeight);
4128
4182
  const maxVisible = Math.max(
4129
- 2,
4130
- Math.floor(availableHeight / SECTION_INDEX_MIN_LABEL_SPACING) + 1
4183
+ 1,
4184
+ Math.floor(trackHeight / SECTION_INDEX_LABEL_SPACING)
4131
4185
  );
4132
4186
  if (entryCount <= maxVisible) {
4133
4187
  return Array.from({ length: entryCount }, (_, index) => index);
4134
4188
  }
4189
+ if (maxVisible === 1) return [0];
4135
4190
  const result = new Set<number>();
4136
4191
  for (let slot = 0; slot < maxVisible; slot += 1) {
4137
4192
  result.add(Math.round((slot * (entryCount - 1)) / (maxVisible - 1)));
@@ -4139,6 +4194,21 @@ export class NativeListWebEngine {
4139
4194
  return [...result].sort((left, right) => left - right);
4140
4195
  }
4141
4196
 
4197
+ private sectionIndexMetrics(viewportHeight: number) {
4198
+ const availableHeight = Math.max(
4199
+ 0,
4200
+ viewportHeight - SECTION_INDEX_EDGE_PADDING * 2
4201
+ );
4202
+ const trackHeight = Math.min(
4203
+ availableHeight,
4204
+ SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length
4205
+ );
4206
+ return {
4207
+ originY: (viewportHeight - trackHeight) / 2,
4208
+ trackHeight,
4209
+ };
4210
+ }
4211
+
4142
4212
  private renderSectionIndex(viewportHeight: number) {
4143
4213
  this.indexRail.replaceChildren();
4144
4214
  if (!sectionIndexEnabled(this.snapshot)) {
@@ -4165,8 +4235,15 @@ export class NativeListWebEngine {
4165
4235
  'compact',
4166
4236
  visibleEntryIndices.length < this.sectionIndexEntries.length
4167
4237
  );
4238
+ const metrics = this.sectionIndexMetrics(viewportHeight);
4239
+ const visibleTrackHeight = Math.min(
4240
+ metrics.trackHeight,
4241
+ SECTION_INDEX_LABEL_SPACING * visibleEntryIndices.length
4242
+ );
4243
+ const visibleOriginY =
4244
+ metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2;
4168
4245
  const fragment = this.document.createDocumentFragment();
4169
- visibleEntryIndices.forEach((entryIndex) => {
4246
+ visibleEntryIndices.forEach((entryIndex, visibleIndex) => {
4170
4247
  const entry = this.sectionIndexEntries[entryIndex];
4171
4248
  if (!entry) return;
4172
4249
  const button = createElement(
@@ -4180,14 +4257,11 @@ export class NativeListWebEngine {
4180
4257
  setData(button, 'sectionEntryIndex', entryIndex);
4181
4258
  setData(button, 'sectionPosition', entry.position);
4182
4259
  setData(button, 'sectionKey', entry.key);
4183
- const progress =
4184
- this.sectionIndexEntries.length === 1
4185
- ? 0.5
4186
- : entryIndex / (this.sectionIndexEntries.length - 1);
4187
4260
  button.style.top =
4188
4261
  String(
4189
- SECTION_INDEX_EDGE_PADDING +
4190
- progress * (viewportHeight - SECTION_INDEX_EDGE_PADDING * 2)
4262
+ visibleOriginY +
4263
+ (visibleTrackHeight * (visibleIndex + 0.5)) /
4264
+ visibleEntryIndices.length
4191
4265
  ) + 'px';
4192
4266
  fragment.appendChild(button);
4193
4267
  });
@@ -4323,7 +4397,7 @@ export class NativeListWebEngine {
4323
4397
  }
4324
4398
  const next = this.layout.items[nextIndex];
4325
4399
  const translate = next
4326
- ? Math.min(0, next.y - this.viewport.scrollTop - item.height)
4400
+ ? Math.min(0, next.y - this.currentOffset() - item.height)
4327
4401
  : 0;
4328
4402
  this.sticky.style.transform =
4329
4403
  'translate3d(0,' + String(translate) + 'px,0)';
@@ -4399,9 +4473,11 @@ export class NativeListWebEngine {
4399
4473
  }
4400
4474
 
4401
4475
  private viewportLength(): number {
4402
- return this.layout.horizontal
4403
- ? this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH
4404
- : this.viewport.clientHeight || DEFAULT_VIEWPORT_HEIGHT;
4476
+ if (this.layout.horizontal) {
4477
+ return this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH;
4478
+ }
4479
+ if (this.viewport.clientHeight <= 0) return DEFAULT_VIEWPORT_HEIGHT;
4480
+ return Math.max(1, this.verticalScrollMetrics().viewportLength);
4405
4481
  }
4406
4482
 
4407
4483
  private contentLength(): number {
@@ -4413,18 +4489,54 @@ export class NativeListWebEngine {
4413
4489
  private currentOffset(): number {
4414
4490
  return this.layout.horizontal
4415
4491
  ? this.viewport.scrollLeft
4416
- : this.viewport.scrollTop;
4492
+ : this.verticalScrollMetrics().offset;
4493
+ }
4494
+
4495
+ private collapsiblePagerInsets(): Readonly<{
4496
+ header: number;
4497
+ sticky: number;
4498
+ }> {
4499
+ const readInset = (name: string) => {
4500
+ const parsed = Number.parseFloat(
4501
+ this.viewport.style.getPropertyValue(name)
4502
+ );
4503
+ return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
4504
+ };
4505
+ return {
4506
+ header: readInset('--ok-collapsible-pager-header-inset'),
4507
+ sticky: readInset('--ok-collapsible-pager-sticky-inset'),
4508
+ };
4417
4509
  }
4418
4510
 
4511
+ private verticalScrollMetrics(): WebCollapsiblePagerScrollMetrics {
4512
+ const insets = this.collapsiblePagerInsets();
4513
+ return resolveWebCollapsiblePagerScrollMetrics(
4514
+ this.viewport.scrollTop,
4515
+ this.viewport.clientHeight,
4516
+ insets.header,
4517
+ insets.sticky
4518
+ );
4519
+ }
4520
+
4521
+ private handleCollapsiblePagerInsetsChanged = () => {
4522
+ this.recomputeLayout();
4523
+ };
4524
+
4419
4525
  private scrollToAbsoluteOffset(offset: number, animated: boolean) {
4420
4526
  const resolved = Math.min(
4421
4527
  Math.max(0, offset),
4422
4528
  Math.max(0, this.contentLength() - this.viewportLength())
4423
4529
  );
4530
+ const rawOffset = this.layout.horizontal
4531
+ ? resolved
4532
+ : resolveWebCollapsiblePagerRawOffset(
4533
+ resolved,
4534
+ this.collapsiblePagerInsets().header
4535
+ );
4424
4536
  this.viewport.scrollTo(
4425
4537
  this.layout.horizontal
4426
- ? { left: resolved, top: 0, behavior: animated ? 'smooth' : 'auto' }
4427
- : { left: 0, top: resolved, behavior: animated ? 'smooth' : 'auto' }
4538
+ ? { left: rawOffset, top: 0, behavior: animated ? 'smooth' : 'auto' }
4539
+ : { left: 0, top: rawOffset, behavior: animated ? 'smooth' : 'auto' }
4428
4540
  );
4429
4541
  this.scheduleFrame();
4430
4542
  }
@@ -4949,78 +5061,62 @@ export class NativeListWebEngine {
4949
5061
  else view?.clearTimeout(handle);
4950
5062
  }
4951
5063
 
4952
- private selectIndexPosition(
4953
- position: number,
4954
- title: string,
4955
- previewClientY?: number
4956
- ) {
5064
+ private selectIndexPosition(position: number) {
5065
+ const activeKey = this.sectionIndexEntries.find(
5066
+ (entry) => entry.position === position
5067
+ )?.key;
5068
+ this.indexRail
5069
+ .querySelectorAll<HTMLElement>('[data-section-key]')
5070
+ .forEach((button) =>
5071
+ setData(button, 'active', button.dataset.sectionKey === activeKey)
5072
+ );
4957
5073
  this.scrollToIndex(position, {
4958
5074
  animated: false,
4959
5075
  alignment: 'start',
4960
5076
  viewPosition: 0,
4961
5077
  viewOffset: 0,
4962
5078
  });
4963
- const frame = this.viewportFrame.getBoundingClientRect();
4964
- if (previewClientY !== undefined && frame.height > 0) {
4965
- const previewY = Math.min(
4966
- frame.height - 24,
4967
- Math.max(24, previewClientY - frame.top)
4968
- );
4969
- this.indexPreview.style.top = String(previewY) + 'px';
4970
- } else {
4971
- this.indexPreview.style.top = '50%';
4972
- }
4973
- this.indexPreview.textContent = title;
4974
- setData(this.indexPreview, 'visible', true);
4975
- if (this.previewTimer !== undefined)
4976
- this.document.defaultView?.clearTimeout(this.previewTimer);
4977
- this.previewTimer = this.document.defaultView?.setTimeout(() => {
4978
- setData(this.indexPreview, 'visible', false);
4979
- }, 180);
4980
5079
  }
4981
5080
 
4982
- private sectionIndexEntryAtEvent(event: PointerEvent):
4983
- | Readonly<{
4984
- entry: NativeListWebEngine['sectionIndexEntries'][number];
4985
- previewClientY: number;
4986
- }>
4987
- | undefined {
5081
+ private sectionIndexEntryAtEvent(
5082
+ event: PointerEvent
5083
+ ): NativeListWebEngine['sectionIndexEntries'][number] | undefined {
4988
5084
  const rail = this.indexRail.getBoundingClientRect();
4989
5085
  if (this.sectionIndexEntries.length === 0 || rail.height <= 0) {
4990
5086
  return undefined;
4991
5087
  }
4992
- const availableHeight = Math.max(
4993
- 1,
4994
- rail.height - SECTION_INDEX_EDGE_PADDING * 2
4995
- );
5088
+ const metrics = this.sectionIndexMetrics(rail.height);
5089
+ if (metrics.trackHeight <= 0) return undefined;
4996
5090
  const progress = Math.min(
4997
5091
  1,
4998
5092
  Math.max(
4999
5093
  0,
5000
- (event.clientY - rail.top - SECTION_INDEX_EDGE_PADDING) /
5001
- availableHeight
5094
+ (event.clientY - rail.top - metrics.originY) / metrics.trackHeight
5002
5095
  )
5003
5096
  );
5004
- const entryIndex = Math.round(
5005
- progress * (this.sectionIndexEntries.length - 1)
5097
+ const entryIndex = Math.min(
5098
+ this.sectionIndexEntries.length - 1,
5099
+ Math.floor(progress * this.sectionIndexEntries.length)
5006
5100
  );
5007
- const entry = this.sectionIndexEntries[entryIndex];
5008
- return entry ? { entry, previewClientY: event.clientY } : undefined;
5101
+ return this.sectionIndexEntries[entryIndex];
5009
5102
  }
5010
5103
 
5011
5104
  private handleIndexPointer = (event: PointerEvent) => {
5012
5105
  if (event.type === 'pointermove' && event.buttons === 0) return;
5013
- const selection = this.sectionIndexEntryAtEvent(event);
5014
- if (!selection) return;
5106
+ const entry = this.sectionIndexEntryAtEvent(event);
5107
+ if (!entry) return;
5015
5108
  event.preventDefault();
5016
5109
  if (event.type === 'pointerdown') {
5017
5110
  this.indexRail.setPointerCapture?.(event.pointerId);
5018
5111
  }
5019
- this.selectIndexPosition(
5020
- selection.entry.position,
5021
- selection.entry.title,
5022
- selection.previewClientY
5023
- );
5112
+ this.selectIndexPosition(entry.position);
5113
+ };
5114
+
5115
+ private handleIndexPointerEnd = (event: PointerEvent) => {
5116
+ if (event.type === 'pointerup') {
5117
+ const entry = this.sectionIndexEntryAtEvent(event);
5118
+ if (entry) this.selectIndexPosition(entry.position);
5119
+ }
5024
5120
  };
5025
5121
 
5026
5122
  private handleIndexClick = (event: Event) => {
@@ -5037,12 +5133,7 @@ export class NativeListWebEngine {
5037
5133
  const entry =
5038
5134
  this.sectionIndexEntries[Number(button.dataset.sectionEntryIndex)];
5039
5135
  if (!entry) return;
5040
- const rect = button.getBoundingClientRect();
5041
- this.selectIndexPosition(
5042
- entry.position,
5043
- entry.title,
5044
- rect.top + rect.height / 2
5045
- );
5136
+ this.selectIndexPosition(entry.position);
5046
5137
  };
5047
5138
 
5048
5139
  private handlePullStart = (event: PointerEvent) => {
@@ -5245,7 +5336,12 @@ export class NativeListWebEngine {
5245
5336
  const maximum = Math.max(0, this.contentLength() - this.viewportLength());
5246
5337
  const next = Math.max(0, Math.min(maximum, offset));
5247
5338
  if (this.layout.horizontal) this.viewport.scrollLeft = next;
5248
- else this.viewport.scrollTop = next;
5339
+ else {
5340
+ this.viewport.scrollTop = resolveWebCollapsiblePagerRawOffset(
5341
+ next,
5342
+ this.collapsiblePagerInsets().header
5343
+ );
5344
+ }
5249
5345
  this.scheduleFrame();
5250
5346
  }
5251
5347
 
@@ -5327,10 +5423,15 @@ export class NativeListWebEngine {
5327
5423
  }
5328
5424
  this.reorderSettlingKey = state.sourceKey;
5329
5425
  const viewportRect = this.viewport.getBoundingClientRect();
5426
+ const insets = this.collapsiblePagerInsets();
5330
5427
  const left =
5331
5428
  viewportRect.left + destinationLayout.x - this.viewport.scrollLeft;
5332
5429
  const top =
5333
- viewportRect.top + destinationLayout.y - this.viewport.scrollTop;
5430
+ viewportRect.top +
5431
+ destinationLayout.y +
5432
+ insets.header +
5433
+ insets.sticky -
5434
+ this.viewport.scrollTop;
5334
5435
  this.reorderPreview.getBoundingClientRect();
5335
5436
  this.reorderPreview.style.transition =
5336
5437
  'transform ' +