react-anchorlist 0.2.5 → 0.3.2

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,7 +67,12 @@ const listRef = useRef<ChatVirtualListHandle>(null)
67
67
  computeItemKey={(index, item) => item._id}
68
68
  itemContent={(index, item) => <Message data={item} />}
69
69
  // Paginação — dispara ao chegar no topo
70
- onStartReached={loadOlderMessages}
70
+ onStartReached={() => {
71
+ setScrollModifier({ id: `prepend-${Date.now()}`, type: 'prepend' })
72
+ loadOlderMessages()
73
+ }}
74
+ // API declarativa para operações de scroll
75
+ scrollModifier={scrollModifier}
71
76
  // Mantém no final quando chegam novas mensagens
72
77
  followOutput="auto"
73
78
  // Informa ao componente pai se está no final
@@ -90,17 +95,19 @@ const listRef = useRef<ChatVirtualListHandle>(null)
90
95
  | `itemContent` | `(index, item) => ReactNode` | required | Função de renderização |
91
96
  | `computeItemKey` | `(index, item) => string \| number` | required | Chave estável por item |
92
97
  | `estimatedItemSize` | `number` | `80` | Estimativa inicial de altura (px) |
93
- | `overscan` | `number` | `5` | Itens renderizados além da área visível |
98
+ | `overscan` | `number` | `20` | Itens renderizados além da área visível |
94
99
  | `followOutput` | `"auto" \| "smooth" \| false` | `"auto"` | Seguir no final ao entrar item novo |
95
100
  | `atBottomThreshold` | `number` | `200` | Distância (px) para considerar “no final” |
101
+ | `atBottomHysteresis` | `{ enter: number; leave: number }` | `{ enter: 80, leave: 160 }` | Evita alternância excessiva do estado "at bottom" |
96
102
  | `initialAlignment` | `"top" \| "bottom"` | `"bottom"` | Posição inicial do scroll |
103
+ | `scrollModifier` | `ChatScrollModifier` | `null` | Comando declarativo para prepend/append/jump |
97
104
  | `onStartReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do topo |
98
105
  | `onEndReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do fim |
99
106
  | `startReachedThreshold` | `number` | `300` | Distância (px) do topo para disparar callback |
100
107
  | `endReachedThreshold` | `number` | `300` | Distância (px) do fim para disparar callback |
101
108
  | `onAtBottomChange` | `(isAtBottom: boolean) => void` | — | Mudança de estado “no final” |
102
- | `scrollToMessageKey` | `string \| number \| null` | — | Rola até item por chave |
103
- | `onScrollToMessageComplete` | `() => void` | — | Dispara após scroll por chave |
109
+ | `scrollToMessageKey` | `string \| number \| null` | — | **Deprecated**. Use `scrollModifier` com `type: "jump-to-key"` |
110
+ | `onScrollToMessageComplete` | `() => void` | — | **Deprecated** |
104
111
  | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slots opcionais |
105
112
  | `className` | `string` | — | Classe CSS do container |
106
113
  | `style` | `CSSProperties` | — | Estilo inline do container |
@@ -114,8 +121,8 @@ const listRef = useRef<ChatVirtualListHandle>(null)
114
121
  | `data` | `T[]` | required | Array de itens |
115
122
  | `itemContent` | `(index, item) => ReactNode` | required | Função de renderização |
116
123
  | `computeItemKey` | `(index, item) => string \| number` | required | Chave estável por item |
117
- | `estimatedItemSize` | `number` | `80` | Estimativa inicial de altura (px) |
118
- | `overscan` | `number` | `5` | Itens renderizados além da área visível |
124
+ | `estimatedItemSize` | `number` | `60` | Estimativa inicial de altura (px) |
125
+ | `overscan` | `number` | `20` | Itens renderizados além da área visível |
119
126
  | `onEndReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do fim |
120
127
  | `endReachedThreshold` | `number` | `300` | Distância (px) do fim para disparar callback |
121
128
  | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slots opcionais |
@@ -134,7 +141,7 @@ listRef.current?.scrollToIndex(42, { align: 'center', behavior: 'smooth' })
134
141
  listRef.current?.scrollToKey('msg-123', { align: 'center' })
135
142
  listRef.current?.getScrollTop() // → number
136
143
  listRef.current?.isAtBottom() // → boolean
137
- listRef.current?.prepareAnchor() // chame antes de prepend manual
144
+ listRef.current?.prepareAnchor() // deprecated
138
145
  ```
139
146
 
140
147
  | Method | Description |
@@ -144,7 +151,41 @@ listRef.current?.prepareAnchor() // chame antes de prepend manual
144
151
  | `scrollToKey(key, opts?)` | Vai para item por chave |
145
152
  | `getScrollTop()` | Posição atual do scroll |
146
153
  | `isAtBottom()` | Indica se está no final |
147
- | `prepareAnchor()` | Salva posição antes de prepend externo |
154
+ | `prepareAnchor()` | **Deprecated**. Use `scrollModifier={{ id, type: 'prepend' }}` |
155
+
156
+ ---
157
+
158
+ ## ChatScrollModifier (API declarativa)
159
+
160
+ ```ts
161
+ type ChatScrollModifier =
162
+ | { id: string | number; type: 'prepend' }
163
+ | { id: string | number; type: 'append'; behavior?: 'auto' | 'smooth'; ifAtBottomOnly?: boolean }
164
+ | { id: string | number; type: 'items-change' }
165
+ | { id: string | number; type: 'jump-to-key'; key: string | number; align?: 'start' | 'center' | 'end'; behavior?: ScrollBehavior }
166
+ ```
167
+
168
+ - `id` precisa ser único por comando.
169
+ - `prepend` prepara e restaura âncora automaticamente no próximo update de dados.
170
+ - `jump-to-key` substitui `scrollToMessageKey`.
171
+
172
+ ---
173
+
174
+ ## Migração Rápida (v0.2 -> v0.3)
175
+
176
+ Antes:
177
+
178
+ ```tsx
179
+ listRef.current?.prepareAnchor()
180
+ await loadMoreMessages()
181
+ ```
182
+
183
+ Depois:
184
+
185
+ ```tsx
186
+ setScrollModifier({ id: `prepend-${Date.now()}`, type: 'prepend' })
187
+ await loadMoreMessages()
188
+ ```
148
189
 
149
190
  ---
150
191
 
@@ -1 +1 @@
1
- {"version":3,"file":"ChatVirtualList.d.ts","sourceRoot":"","sources":["../../src/components/ChatVirtualList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAe,MAAM,UAAU,CAAA;AAkHxF,eAAO,MAAM,eAAe,EAAuC,CAAC,CAAC,EACnE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;CAAE,KACxE,KAAK,CAAC,YAAY,CAAA"}
1
+ {"version":3,"file":"ChatVirtualList.d.ts","sourceRoot":"","sources":["../../src/components/ChatVirtualList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAe,MAAM,UAAU,CAAA;AAsHxF,eAAO,MAAM,eAAe,EAAuC,CAAC,CAAC,EACnE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;CAAE,KACxE,KAAK,CAAC,YAAY,CAAA"}
@@ -1,5 +1,5 @@
1
1
  /** O(log n) — finds index of first item whose top edge is >= scrollTop */
2
2
  export declare function findFirstVisibleIndex(offsets: number[], scrollTop: number): number;
3
- /** Linear scan from end — finds the last item whose top edge is within scrollBottom */
3
+ /** O(log n) — finds the last item whose top edge is < scrollBottom */
4
4
  export declare function findLastVisibleIndex(offsets: number[], _sizes: number[], scrollBottom: number): number;
5
5
  //# sourceMappingURL=binarySearch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"binarySearch.d.ts","sourceRoot":"","sources":["../../src/core/binarySearch.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAWlF;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,MAAM,EAAE,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAMR"}
1
+ {"version":3,"file":"binarySearch.d.ts","sourceRoot":"","sources":["../../src/core/binarySearch.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAWlF;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,MAAM,EAAE,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAiBR"}
@@ -1,3 +1,16 @@
1
- /** Returns true when the scroll container is within `threshold` px of the bottom */
2
- export declare function useAtBottom(scrollerRef: React.RefObject<HTMLDivElement>, threshold: number): boolean;
1
+ import { AtBottomHysteresis } from '../types';
2
+
3
+ interface AtBottomStateInput {
4
+ previous: boolean;
5
+ distanceFromBottom: number;
6
+ threshold: number;
7
+ hysteresis?: AtBottomHysteresis;
8
+ }
9
+ export declare function resolveAtBottomState(input: AtBottomStateInput): boolean;
10
+ /** Returns true when the scroll container is near the bottom. */
11
+ export declare function useAtBottom(scrollerRef: React.RefObject<HTMLDivElement>, params: number | {
12
+ threshold?: number;
13
+ hysteresis?: AtBottomHysteresis;
14
+ }): boolean;
15
+ export {};
3
16
  //# sourceMappingURL=useAtBottom.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAtBottom.d.ts","sourceRoot":"","sources":["../../src/hooks/useAtBottom.ts"],"names":[],"mappings":"AAEA,oFAAoF;AACpF,wBAAgB,WAAW,CACzB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CA4BT"}
1
+ {"version":3,"file":"useAtBottom.d.ts","sourceRoot":"","sources":["../../src/hooks/useAtBottom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAElD,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,OAAO,CAAA;IACjB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,kBAAkB,CAAA;CAChC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAQvE;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CACzB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,kBAAkB,CAAA;CAAE,GACvE,OAAO,CAsCT"}
@@ -1,9 +1,7 @@
1
- import { UseChatVirtualizerReturn } from '../types';
1
+ import { AtBottomHysteresis, ChatScrollModifier, UseChatVirtualizerReturn } from '../types';
2
2
 
3
3
  /**
4
4
  * Composites all virtual engine hooks into a single chat-optimized hook.
5
- * Handles: virtualization, scroll anchor (no-flicker prepend), stick-to-bottom,
6
- * start/end reached callbacks, and programmatic scroll-to-key.
7
5
  */
8
6
  export declare function useChatVirtualizer<T>(options: {
9
7
  items: T[];
@@ -11,13 +9,17 @@ export declare function useChatVirtualizer<T>(options: {
11
9
  estimatedItemSize?: number;
12
10
  overscan?: number;
13
11
  atBottomThreshold?: number;
12
+ atBottomHysteresis?: AtBottomHysteresis;
14
13
  followOutput?: "auto" | "smooth" | false;
15
14
  initialAlignment?: "top" | "bottom";
15
+ scrollModifier?: ChatScrollModifier | null;
16
16
  onStartReached?: () => void | Promise<void>;
17
17
  onEndReached?: () => void | Promise<void>;
18
18
  startReachedThreshold?: number;
19
19
  endReachedThreshold?: number;
20
+ /** @deprecated Prefer `scrollModifier` with `type: "jump-to-key"` */
20
21
  scrollToMessageKey?: string | number | null;
22
+ /** @deprecated Prefer command id tracking on `scrollModifier` */
21
23
  onScrollToMessageComplete?: () => void;
22
24
  }): UseChatVirtualizerReturn<T>;
23
25
  //# sourceMappingURL=useChatVirtualizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useChatVirtualizer.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatVirtualizer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAqB,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAE3E;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE;IAC7C,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;IACxC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3C,yBAAyB,CAAC,EAAE,MAAM,IAAI,CAAA;CACvC,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAwI9B"}
1
+ {"version":3,"file":"useChatVirtualizer.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatVirtualizer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAElB,wBAAwB,EACzB,MAAM,UAAU,CAAA;AAEjB;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE;IAC7C,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;IACxC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IACnC,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC1C,cAAc,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3C,iEAAiE;IACjE,yBAAyB,CAAC,EAAE,MAAM,IAAI,CAAA;CACvC,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAoL9B"}
@@ -1,10 +1,21 @@
1
+ import { AnchorSnapshot } from '../types';
2
+
3
+ interface UseScrollAnchorOptions {
4
+ scrollerRef: React.RefObject<HTMLDivElement>;
5
+ itemCount: number;
6
+ captureAnchorSnapshot: () => AnchorSnapshot | null;
7
+ resolveAnchorTop: (key: string | number, offsetWithinItem: number) => number | null;
8
+ onRestored?: () => void;
9
+ }
1
10
  /**
2
11
  * Keeps viewport anchored when items are prepended.
3
12
  *
4
- * This hook performs restoration in multiple phases (layout + raf + timeout)
5
- * to absorb late height measurements (ResizeObserver) and avoid flicker/jumps.
13
+ * Strategy:
14
+ * - Preferred: logical anchor by key + offsetWithinItem
15
+ * - Fallback: scrollHeight delta compensation
6
16
  */
7
- export declare function useScrollAnchor(scrollerRef: React.RefObject<HTMLDivElement>, itemCount: number, onRestored?: () => void): {
17
+ export declare function useScrollAnchor(options: UseScrollAnchorOptions): {
8
18
  prepareAnchor: () => void;
9
19
  };
20
+ export {};
10
21
  //# sourceMappingURL=useScrollAnchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useScrollAnchor.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollAnchor.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,IAAI,GACtB;IAAE,aAAa,EAAE,MAAM,IAAI,CAAA;CAAE,CAmE/B"}
1
+ {"version":3,"file":"useScrollAnchor.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollAnchor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,UAAU,sBAAsB;IAC9B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,qBAAqB,EAAE,MAAM,cAAc,GAAG,IAAI,CAAA;IAClD,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACnF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAAE,aAAa,EAAE,MAAM,IAAI,CAAA;CAAE,CAyF9F"}
@@ -7,5 +7,7 @@ import { ScrollToIndexOpts } from '../types';
7
7
  * 1) scrollToIndex(index, opts?)
8
8
  * 2) scrollToIndex({ index, align, behavior, offset })
9
9
  */
10
- export declare function useScrollToIndex(scrollerRef: React.RefObject<HTMLDivElement>, offsetMapRef: React.MutableRefObject<OffsetMap | null>, innerRef?: React.RefObject<HTMLDivElement>): (index: number, opts?: ScrollToIndexOpts) => void;
10
+ export declare function useScrollToIndex(scrollerRef: React.RefObject<HTMLDivElement>, offsetMapRef: React.MutableRefObject<OffsetMap | null>, innerRef?: React.RefObject<HTMLDivElement>, options?: {
11
+ reconcile?: boolean;
12
+ }): (index: number, opts?: ScrollToIndexOpts) => void;
11
13
  //# sourceMappingURL=useScrollToIndex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useScrollToIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollToIndex.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,SAAS,GAAG,IAAI,CAAC,EACtD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,GACzC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAgDnD"}
1
+ {"version":3,"file":"useScrollToIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollToIndex.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,SAAS,GAAG,IAAI,CAAC,EACtD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC1C,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAkGnD"}
@@ -1,14 +1,12 @@
1
1
  import { UseVirtualEngineReturn } from '../types';
2
2
 
3
3
  /**
4
- * Core virtual engine — v0.2.1
4
+ * Core virtual engine — v0.3.0
5
5
  *
6
- * DESIGN:
7
- * - Scroll/layout state lives in refs, NOT React state
8
- * - A simple counter triggers re-renders
9
- * - virtualItems computed DURING render from refs (always fresh, zero lag)
10
- * - innerRef tracks the virtual container offset (accounts for Header)
11
- * - overscroll-behavior: contain prevents native bounce
6
+ * Key behavior:
7
+ * - Jump correction is accumulated and flushed once per frame.
8
+ * - Logical anchor snapshot (key + intra-item offset) is supported.
9
+ * - Settling on initial bottom align stops by stability condition.
12
10
  */
13
11
  export declare function useVirtualEngine<T>(options: {
14
12
  items: T[];
@@ -1 +1 @@
1
- {"version":3,"file":"useVirtualEngine.d.ts","sourceRoot":"","sources":["../../src/hooks/useVirtualEngine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,UAAU,CAAA;AAEnE;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE;IAC3C,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IACnD,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,KAAK,GAAG,QAAQ,CAAA;CACnC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAsQ5B"}
1
+ {"version":3,"file":"useVirtualEngine.d.ts","sourceRoot":"","sources":["../../src/hooks/useVirtualEngine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAkB,sBAAsB,EAAe,MAAM,UAAU,CAAA;AAEnF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE;IAC3C,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IACnD,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,KAAK,GAAG,QAAQ,CAAA;CACnC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAqW5B"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const M=require("react/jsx-runtime"),r=require("react");function Q(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const D=Q(r);class W{constructor(e,t){this.defaultSize=t,this.sizes=e>0?Array(e).fill(t):[],this.offsets=e>0?Array(e).fill(0):[],e>0&&this._recalcFrom(0)}_recalcFrom(e){for(let t=e;t<this.sizes.length;t++)this.offsets[t]=t===0?0:(this.offsets[t-1]??0)+(this.sizes[t-1]??this.defaultSize)}getOffset(e){return this.offsets[e]??0}getSize(e){return this.sizes[e]??this.defaultSize}setSize(e,t){return this.sizes[e]===t?!1:(this.sizes[e]=t,this._recalcFrom(e+1),!0)}prepend(e){const t=Array(e).fill(this.defaultSize);this.sizes=[...t,...this.sizes],this.offsets=Array(this.sizes.length).fill(0),this._recalcFrom(0)}append(e){const t=this.sizes.length;for(let n=0;n<e;n++)this.sizes.push(this.defaultSize),this.offsets.push(0);this._recalcFrom(t)}resize(e){const t=this.sizes.length;e>t?this.append(e-t):e<t&&(this.sizes=this.sizes.slice(0,e),this.offsets=this.offsets.slice(0,e))}totalSize(){if(this.sizes.length===0)return 0;const e=this.sizes.length-1;return(this.offsets[e]??0)+(this.sizes[e]??this.defaultSize)}get count(){return this.sizes.length}getOffsets(){return this.offsets}getSizes(){return this.sizes}}class Z{constructor(){this.cache=new Map}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}has(e){return this.cache.has(e)}delete(e){this.cache.delete(e)}clear(){this.cache.clear()}applyToOffsetMap(e,t){for(const[n,l]of this.cache){const o=t.get(n);o!==void 0&&e.setSize(o,l)}}}function ee(i,e){if(i.length===0)return 0;let t=0,n=i.length-1;for(;t<n;){const l=t+n>>1;(i[l]??0)<e?t=l+1:n=l}return Math.max(0,t>0&&(i[t]??0)>e?t-1:t)}function te(i,e,t){if(i.length===0)return 0;for(let n=i.length-1;n>=0;n--)if((i[n]??0)<t)return n;return 0}function re(i){const{firstVisible:e,lastVisible:t,itemCount:n,overscan:l}=i;return n===0?{start:0,end:-1}:{start:Math.max(0,e-l),end:Math.min(n-1,t+l)}}function ne(i,e,t){return r.useCallback((n,l)=>{var H;const o=i.current,s=e.current;if(!o||!s)return;let m,f;if(typeof n=="object"&&n!==null?(m=n.index,f={align:n.align,behavior:n.behavior,offset:n.offset}):(m=n,f=l),!Number.isFinite(m))return;const d=Math.max(0,Math.min(Math.floor(m),s.count-1)),g=((H=t==null?void 0:t.current)==null?void 0:H.offsetTop)??0,z=s.getOffset(d),S=s.getSize(d),T=(f==null?void 0:f.align)??"start",h=(f==null?void 0:f.behavior)??"auto",I=(f==null?void 0:f.offset)??0;let y;T==="start"?y=g+z+I:T==="center"?y=g+z-o.clientHeight/2+S/2+I:y=g+z-o.clientHeight+S+I,o.scrollTo({top:Math.max(0,y),behavior:h})},[i,e,t])}function X(i){var O;const{items:e,getKey:t,estimatedItemSize:n,overscan:l,initialAlignment:o}=i,s=r.useRef(null),m=r.useRef(null),f=r.useRef(null),d=r.useRef(new Z),g=r.useRef([]),z=r.useRef(!1),S=r.useRef(0),T=r.useRef(0),h=r.useRef(null),I=r.useRef(!1),y=r.useRef(null),[,H]=r.useState(0),v=r.useCallback(()=>H(c=>c+1),[]);f.current||(f.current=new W(e.length,n));const E=r.useRef(e.length);if(e.length!==E.current||e.some((c,R)=>{const b=t(c,R);return g.current[R]!==b})){const c=f.current,R=e.map((_,N)=>t(_,N)),b=g.current,A=b.length,k=R.length;k===0?c.resize(0):A===0?c.resize(k):k>A?b.length>0&&R[k-A]===b[0]?c.prepend(k-A):c.resize(k):k<A&&c.resize(k);const P=new Map;R.forEach((_,N)=>P.set(_,N)),d.current.applyToOffsetMap(c,P),g.current=R,E.current=e.length}r.useEffect(()=>{const c=s.current;if(!c)return;const R=()=>{h.current===null&&(h.current=requestAnimationFrame(()=>{h.current=null,S.current=c.scrollTop,T.current=c.clientHeight,v()}))};return c.addEventListener("scroll",R,{passive:!0}),()=>{c.removeEventListener("scroll",R),h.current!==null&&(cancelAnimationFrame(h.current),h.current=null)}},[v]),r.useEffect(()=>{const c=s.current;if(!c)return;T.current=c.clientHeight,S.current=c.scrollTop;const R=new ResizeObserver(([b])=>{b&&(T.current=b.contentRect.height,v())});return R.observe(c),()=>R.disconnect()},[v]),r.useLayoutEffect(()=>{if(z.current||e.length===0)return;const c=s.current;if(c){if(o==="bottom"){c.scrollTop=c.scrollHeight,S.current=c.scrollTop,T.current=c.clientHeight,I.current=!0,y.current!==null&&cancelAnimationFrame(y.current);let R=0;const b=30,A=()=>{if(!I.current||R>=b){I.current=!1,y.current=null;return}R++,c.scrollTop=c.scrollHeight,S.current=c.scrollTop,y.current=requestAnimationFrame(A)};y.current=requestAnimationFrame(A)}z.current=!0}},[o,e.length]),r.useEffect(()=>{e.length===0&&(z.current=!1,I.current=!1,y.current!==null&&(cancelAnimationFrame(y.current),y.current=null))},[e.length]);const w=r.useCallback((c,R)=>{var N;const b=f.current;if(!b||d.current.get(c)===R)return;d.current.set(c,R);const k=g.current.indexOf(c);if(k===-1)return;const P=s.current;if(P&&!I.current){const Y=b.getSize(k),U=R-Y,J=b.getOffset(k)+Y+(((N=m.current)==null?void 0:N.offsetTop)??0);U!==0&&J<P.scrollTop&&(P.scrollTop+=U,S.current=P.scrollTop)}b.setSize(k,R)&&v()},[v]),L=r.useCallback((c,R="auto")=>{var b;(b=s.current)==null||b.scrollTo({top:c,behavior:R})},[]),B=ne(s,f,m),F=f.current,V=F?F.totalSize():0,a=s.current,u=(a==null?void 0:a.scrollTop)??S.current,p=(a==null?void 0:a.clientHeight)??T.current,x=((O=m.current)==null?void 0:O.offsetTop)??0,K=Math.max(0,u-x);let j=[];if(F&&F.count>0&&p>0){const c=F.getOffsets(),R=F.getSizes(),b=ee(c,K),A=te(c,R,K+p),k=re({firstVisible:b,lastVisible:A,itemCount:F.count,overscan:l});for(let P=k.start;P<=k.end&&P<e.length;P++)j.push({key:g.current[P]??t(e[P],P),index:P,start:F.getOffset(P),size:F.getSize(P),data:e[P]})}else if(F&&F.count>0){const c=Math.min(e.length,l*2+1),R=o==="bottom"?Math.max(0,e.length-c):0,b=R+c-1;for(let A=R;A<=b;A++)j.push({key:g.current[A]??t(e[A],A),index:A,start:F.getOffset(A),size:F.getSize(A),data:e[A]})}const q=a?a.scrollHeight-a.scrollTop-a.clientHeight:1/0;return{scrollerRef:s,innerRef:m,virtualItems:j,totalSize:V,measureItem:w,scrollToIndex:B,scrollToOffset:L,isAtTop:u<=1,isAtBottom:q<=1,scrollTop:u}}function se(i,e,t){const n=r.useRef(0),l=r.useRef(0),o=r.useRef(!1),s=r.useRef({first:null,second:null,timeout:null}),m=r.useCallback(()=>{const{first:d,second:g,timeout:z}=s.current;d&&cancelAnimationFrame(d),g&&cancelAnimationFrame(g),z&&clearTimeout(z),s.current={first:null,second:null,timeout:null}},[]),f=r.useCallback(()=>{const d=i.current;d&&(n.current=d.scrollTop,l.current=d.scrollHeight,o.current=!0)},[i]);return r.useLayoutEffect(()=>{if(!o.current)return;const d=i.current;if(!d)return;o.current=!1;const g=()=>{const z=n.current+(d.scrollHeight-l.current);Number.isFinite(z)&&Math.abs(d.scrollTop-z)>1&&(d.scrollTop=z)};return m(),g(),t==null||t(),s.current.first=requestAnimationFrame(()=>{s.current.first=null,g(),s.current.second=requestAnimationFrame(()=>{s.current.second=null,g()})}),s.current.timeout=setTimeout(()=>{s.current.timeout=null,g()},90),()=>m()},[e,i,m,t]),{prepareAnchor:f}}function ce(i,e){const[t,n]=r.useState(!0),l=r.useRef(null);return r.useEffect(()=>{const o=i.current;if(!o)return;const s=()=>{const f=o.scrollHeight-o.scrollTop-o.clientHeight;n(f<=e)},m=()=>{l.current!==null&&cancelAnimationFrame(l.current),l.current=requestAnimationFrame(s)};return o.addEventListener("scroll",m,{passive:!0}),s(),()=>{o.removeEventListener("scroll",m),l.current!==null&&cancelAnimationFrame(l.current)}},[i,e]),t}function oe(i){const{itemCount:e,firstKey:t,lastKey:n,isAtBottom:l,scrollToIndex:o,mode:s}=i,m=r.useRef(e),f=r.useRef(t),d=r.useRef(n);r.useLayoutEffect(()=>{if(!s){m.current=e,f.current=t,d.current=n;return}const g=m.current,z=f.current,S=d.current;e>g&&t===z&&n!==S&&l&&e>0&&o(e-1,{align:"end",behavior:s==="smooth"?"smooth":"auto"}),m.current=e,f.current=t,d.current=n},[e,t,n,l,o,s])}function $(i){const{items:e,getKey:t,estimatedItemSize:n=80,overscan:l=20,atBottomThreshold:o=200,followOutput:s="auto",initialAlignment:m="bottom",onStartReached:f,onEndReached:d,startReachedThreshold:g=300,endReachedThreshold:z=300,scrollToMessageKey:S,onScrollToMessageComplete:T}=i,h=X({items:e,getKey:t,estimatedItemSize:n,overscan:l,initialAlignment:m}),I=ce(h.scrollerRef,o),[,y]=r.useState(0),H=r.useCallback(()=>y(u=>u+1),[]),{prepareAnchor:v}=se(h.scrollerRef,e.length,H),E=e.length>0?t(e[0],0):null,C=e.length>0?t(e[e.length-1],e.length-1):null;oe({itemCount:e.length,firstKey:E,lastKey:C,isAtBottom:I,scrollToIndex:h.scrollToIndex,mode:s??!1});const w=r.useRef(!1),L=r.useRef(m==="top");r.useEffect(()=>{const u=h.scrollerRef.current;if(!u||!f)return;const p=()=>{const x=u.scrollTop;!L.current&&x>g&&(L.current=!0),L.current&&x<=g&&!w.current&&(w.current=!0,Promise.resolve(f()).finally(()=>{w.current=!1}))};return u.addEventListener("scroll",p,{passive:!0}),()=>u.removeEventListener("scroll",p)},[h.scrollerRef,f,g,m]);const B=r.useRef(!1);r.useEffect(()=>{const u=h.scrollerRef.current;if(!u||!d)return;const p=()=>{u.scrollHeight-u.scrollTop-u.clientHeight<=z&&!B.current&&(B.current=!0,Promise.resolve(d()).finally(()=>{B.current=!1}))};return u.addEventListener("scroll",p,{passive:!0}),()=>u.removeEventListener("scroll",p)},[h.scrollerRef,d,z]);const F=r.useRef(null);r.useEffect(()=>{if(!S||F.current===S)return;const u=e.findIndex((p,x)=>t(p,x)===S);u!==-1&&(F.current=S,h.scrollToIndex(u,{align:"center",behavior:"smooth"}),T==null||T())},[S,e,t,h,T]);const V=r.useCallback((u="auto")=>{e.length!==0&&h.scrollToIndex(e.length-1,{align:"end",behavior:u})},[e.length,h]),a=r.useCallback((u,p)=>{const x=e.findIndex((K,j)=>t(K,j)===u);x!==-1&&h.scrollToIndex(x,p)},[e,t,h]);return{scrollerRef:h.scrollerRef,innerRef:h.innerRef,virtualItems:h.virtualItems,totalSize:h.totalSize,measureItem:h.measureItem,scrollToIndex:h.scrollToIndex,scrollToBottom:V,scrollToKey:a,isAtBottom:I,prepareAnchor:v}}function G({virtualItem:i,measureItem:e,children:t}){const n=r.useRef(null),l=r.useRef(!1);return r.useEffect(()=>{const o=n.current;if(!o)return;l.current=!1;const s=new ResizeObserver(([m])=>{m&&(l.current=!0,e(i.key,m.contentRect.height))});return s.observe(o),()=>s.disconnect()},[i.key,e]),M.jsx("div",{ref:n,style:{position:"absolute",top:0,transform:`translateY(${i.start}px)`,width:"100%",minHeight:l.current?void 0:i.size},children:t})}function ie(i,e){const{data:t,itemContent:n,computeItemKey:l,estimatedItemSize:o=80,overscan:s=20,followOutput:m="auto",atBottomThreshold:f=200,initialAlignment:d="bottom",onStartReached:g,onEndReached:z,startReachedThreshold:S=300,endReachedThreshold:T=300,scrollToMessageKey:h,onScrollToMessageComplete:I,onAtBottomChange:y,components:H={},className:v,style:E}=i,{scrollerRef:C,innerRef:w,virtualItems:L,totalSize:B,measureItem:F,scrollToIndex:V,scrollToBottom:a,scrollToKey:u,isAtBottom:p,prepareAnchor:x}=$({items:t,getKey:(c,R)=>l(R,c),estimatedItemSize:o,overscan:s,atBottomThreshold:f,followOutput:m,initialAlignment:d,onStartReached:g,onEndReached:z,startReachedThreshold:S,endReachedThreshold:T,scrollToMessageKey:h,onScrollToMessageComplete:I}),K=D.useRef(p);D.useEffect(()=>{K.current!==p&&(K.current=p,y==null||y(p))},[p,y]),r.useImperativeHandle(e,()=>({scrollToBottom:a,scrollToIndex:V,scrollToKey:u,getScrollTop:()=>{var c;return((c=C.current)==null?void 0:c.scrollTop)??0},isAtBottom:()=>p,prepareAnchor:x}),[a,V,u,C,p,x]);const{Header:j,Footer:q,EmptyPlaceholder:O}=H;return t.length===0&&O?M.jsx(O,{}):M.jsxs("div",{ref:C,className:v,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...E},children:[j&&M.jsx(j,{}),M.jsx("div",{ref:w,style:{height:B,position:"relative",width:"100%"},children:L.map(c=>M.jsx(G,{virtualItem:c,measureItem:F,children:n(c.index,c.data)},c.key))}),q&&M.jsx(q,{})]})}const le=r.forwardRef(ie);function ue({data:i,itemContent:e,computeItemKey:t,estimatedItemSize:n=60,overscan:l=20,onEndReached:o,endReachedThreshold:s=300,components:m={},className:f,style:d}){const{scrollerRef:g,innerRef:z,virtualItems:S,totalSize:T,measureItem:h}=X({items:i,getKey:(v,E)=>t(E,v),estimatedItemSize:n,overscan:l,initialAlignment:"top"});D.useEffect(()=>{const v=g.current;if(!v||!o)return;let E=!1;const C=()=>{v.scrollHeight-v.scrollTop-v.clientHeight<=s&&!E&&(E=!0,Promise.resolve(o()).finally(()=>{E=!1}))};return v.addEventListener("scroll",C,{passive:!0}),()=>v.removeEventListener("scroll",C)},[g,o,s]);const{Header:I,Footer:y,EmptyPlaceholder:H}=m;return i.length===0&&H?M.jsx(H,{}):M.jsxs("div",{ref:g,className:f,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...d},children:[I&&M.jsx(I,{}),M.jsx("div",{ref:z,style:{height:T,position:"relative",width:"100%"},children:S.map(v=>M.jsx(G,{virtualItem:v,measureItem:h,children:e(v.index,v.data)},v.key))}),y&&M.jsx(y,{})]})}function ae(i){const{fetcher:e,initialPage:t=1,direction:n="append",getKey:l,onPageLoaded:o,onError:s}=i,[m,f]=r.useState([]),[d,g]=r.useState(t),[z,S]=r.useState(!0),[T,h]=r.useState(!1),[I,y]=r.useState(!1),[H,v]=r.useState(!1),E=r.useRef(new Set),C=r.useRef(!1),w=r.useCallback(a=>l?a.filter(u=>{const p=l(u);return E.current.has(p)?!1:(E.current.add(p),!0)}):a,[l]),L=r.useCallback(async()=>{if(!(C.current||!z)){C.current=!0,v(!0);try{const a=d+1,u=await e(a),p=w(u.data);f(x=>n==="prepend"?[...p,...x]:[...x,...p]),g(a),S(u.hasNextPage),h(u.hasPrevPage),o==null||o(a,p)}catch(a){s==null||s(a instanceof Error?a:new Error(String(a)))}finally{v(!1),C.current=!1}}},[d,z,e,w,n,o,s]),B=r.useCallback(async()=>{if(!(C.current||!T)){C.current=!0,v(!0);try{const a=d-1,u=await e(a),p=w(u.data);f(x=>[...p,...x]),g(a),h(u.hasPrevPage),S(u.hasNextPage),o==null||o(a,p)}catch(a){s==null||s(a instanceof Error?a:new Error(String(a)))}finally{v(!1),C.current=!1}}},[d,T,e,w,o,s]),F=r.useCallback(async()=>{if(!C.current){C.current=!0,y(!0);try{const a=await e(t),u=a.data;if(l){const p=new Set(u.map(l));u.forEach(x=>E.current.add(l(x))),f(x=>{const K=x.filter(j=>!p.has(l(j)));return n==="prepend"?[...u,...K]:[...K,...u]})}else f(u);g(t),S(a.hasNextPage),h(a.hasPrevPage),o==null||o(t,u)}catch(a){s==null||s(a instanceof Error?a:new Error(String(a)))}finally{y(!1),C.current=!1}}},[e,t,l,n,o,s]),V=r.useCallback(()=>{f([]),g(t),S(!0),h(!1),y(!1),v(!1),E.current.clear(),C.current=!1},[t]);return{items:m,loadNextPage:L,loadPrevPage:B,hasNextPage:z,hasPrevPage:T,loading:I,loadingMore:H,refresh:F,reset:V,currentPage:d}}exports.ChatVirtualList=le;exports.VirtualList=ue;exports.useChatVirtualizer=$;exports.usePagination=ae;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=require("react/jsx-runtime"),r=require("react");function ue(l){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const t in l)if(t!=="default"){const s=Object.getOwnPropertyDescriptor(l,t);Object.defineProperty(e,t,s.get?s:{enumerable:!0,get:()=>l[t]})}}return e.default=l,Object.freeze(e)}const G=ue(r);class ae{constructor(e,t){this.defaultSize=t,this.sizes=e>0?Array(e).fill(t):[],this.offsets=e>0?Array(e).fill(0):[],e>0&&this._recalcFrom(0)}_recalcFrom(e){for(let t=e;t<this.sizes.length;t++)this.offsets[t]=t===0?0:(this.offsets[t-1]??0)+(this.sizes[t-1]??this.defaultSize)}getOffset(e){return this.offsets[e]??0}getSize(e){return this.sizes[e]??this.defaultSize}setSize(e,t){return this.sizes[e]===t?!1:(this.sizes[e]=t,this._recalcFrom(e+1),!0)}prepend(e){const t=Array(e).fill(this.defaultSize);this.sizes=[...t,...this.sizes],this.offsets=Array(this.sizes.length).fill(0),this._recalcFrom(0)}append(e){const t=this.sizes.length;for(let s=0;s<e;s++)this.sizes.push(this.defaultSize),this.offsets.push(0);this._recalcFrom(t)}resize(e){const t=this.sizes.length;e>t?this.append(e-t):e<t&&(this.sizes=this.sizes.slice(0,e),this.offsets=this.offsets.slice(0,e))}totalSize(){if(this.sizes.length===0)return 0;const e=this.sizes.length-1;return(this.offsets[e]??0)+(this.sizes[e]??this.defaultSize)}get count(){return this.sizes.length}getOffsets(){return this.offsets}getSizes(){return this.sizes}}class fe{constructor(){this.cache=new Map}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}has(e){return this.cache.has(e)}delete(e){this.cache.delete(e)}clear(){this.cache.clear()}applyToOffsetMap(e,t){for(const[s,i]of this.cache){const c=t.get(s);c!==void 0&&e.setSize(c,i)}}}function re(l,e){if(l.length===0)return 0;let t=0,s=l.length-1;for(;t<s;){const i=t+s>>1;(l[i]??0)<e?t=i+1:s=i}return Math.max(0,t>0&&(l[t]??0)>e?t-1:t)}function he(l,e,t){if(l.length===0)return 0;let s=0,i=l.length-1,c=0;for(;s<=i;){const n=s+i>>1;(l[n]??0)<t?(c=n,s=n+1):i=n-1}return c}function de(l){const{firstVisible:e,lastVisible:t,itemCount:s,overscan:i}=l;return s===0?{start:0,end:-1}:{start:Math.max(0,e-i),end:Math.min(s-1,t+i)}}function me(l,e,t,s){const i=s==null?void 0:s.reconcile,c=r.useRef(null);return r.useCallback((n,S)=>{const u=l.current,h=e.current;if(!u||!h)return;c.current!==null&&(cancelAnimationFrame(c.current),c.current=null);let y,f;if(typeof n=="object"&&n!==null?(y=n.index,f={align:n.align,behavior:n.behavior,offset:n.offset}):(y=n,f=S),!Number.isFinite(y))return;const m=Math.max(0,Math.min(Math.floor(y),h.count-1));h.getOffset(m),h.getSize(m);const F=(f==null?void 0:f.align)??"start",b=(f==null?void 0:f.behavior)??"auto",E=(f==null?void 0:f.offset)??0,d=()=>{var p;const H=e.current,a=l.current;if(!H||!a)return 0;const g=((p=t==null?void 0:t.current)==null?void 0:p.offsetTop)??0,z=H.getOffset(m),C=H.getSize(m);return F==="start"?g+z+E:F==="center"?g+z-a.clientHeight/2+C/2+E:g+z-a.clientHeight+C+E},w=d();((H,a)=>{typeof u.scrollTo=="function"?u.scrollTo({top:H,behavior:a}):u.scrollTop=H})(Math.max(0,w),b);const k=performance.now();let T=0;const j=12,_=300,L=1,K=()=>{c.current=null;const H=l.current;if(!H)return;const a=Math.max(0,d()),g=Math.abs(H.scrollTop-a),z=performance.now()-k>=_,C=T>=j;g<=L||z||C||(typeof H.scrollTo=="function"?H.scrollTo({top:a,behavior:"auto"}):H.scrollTop=a,T+=1,c.current=requestAnimationFrame(K))};c.current=requestAnimationFrame(K)},[l,e,t,i])}function ne(l){var ee;const{items:e,getKey:t,estimatedItemSize:s,overscan:i,initialAlignment:c}=l,n=r.useRef(null),S=r.useRef(null),u=r.useRef(null),h=r.useRef(new fe),y=r.useRef([]),f=r.useRef(new Map),m=r.useRef(e.length),F=r.useRef(null),b=r.useRef(null),E=r.useRef(!1),d=r.useRef(0),w=r.useRef(0),R=r.useRef(null),k=r.useRef(!1),T=r.useRef(null),j=r.useRef(0),_=r.useRef(0),L=r.useRef(0),K=r.useRef(null),H=r.useRef(!1),[,a]=r.useState(0),g=r.useCallback(()=>a(o=>o+1),[]);u.current||(u.current=new ae(e.length,s));const z=e.length>0?t(e[0],0):null,C=e.length>0?t(e[e.length-1],e.length-1):null;if(e.length!==m.current||z!==F.current||C!==b.current){const o=u.current,v=e.map((V,q)=>t(V,q)),x=y.current,A=x.length,M=v.length;if(M===0)o.resize(0);else if(A===0)o.resize(M);else if(M>A){const V=M-A;x.length>0&&V>=0&&v[V]===x[0]?o.prepend(V):o.resize(M)}else M<A,o.resize(M);const I=new Map;v.forEach((V,q)=>I.set(V,q)),f.current=I,h.current.applyToOffsetMap(o,I),y.current=v,m.current=e.length,F.current=z,b.current=C}const P=r.useCallback(()=>{K.current===null&&(K.current=requestAnimationFrame(()=>{K.current=null;const o=n.current;if(!o)return;const v=j.current;Math.abs(v)<.01||(j.current=0,_.current+=v,L.current+=v,o.scrollTop+=v,d.current=o.scrollTop,H.current=!0,g())}))},[g]);r.useEffect(()=>{const o=n.current;if(!o)return;const v=()=>{R.current===null&&(R.current=requestAnimationFrame(()=>{R.current=null,d.current=o.scrollTop,w.current=o.clientHeight,H.current&&(H.current=!1,_.current=0,L.current=0),g()}))};return o.addEventListener("scroll",v,{passive:!0}),()=>{o.removeEventListener("scroll",v),R.current!==null&&(cancelAnimationFrame(R.current),R.current=null),K.current!==null&&(cancelAnimationFrame(K.current),K.current=null)}},[g]),r.useEffect(()=>{const o=n.current;if(!o)return;w.current=o.clientHeight,d.current=o.scrollTop;const v=new ResizeObserver(([x])=>{x&&(w.current=x.contentRect.height,g())});return v.observe(o),()=>v.disconnect()},[g]),r.useLayoutEffect(()=>{if(E.current||e.length===0)return;const o=n.current;if(o){if(c==="bottom"){o.scrollTop=o.scrollHeight,d.current=o.scrollTop,w.current=o.clientHeight,k.current=!0,T.current!==null&&cancelAnimationFrame(T.current);const v=performance.now();let x=0,A=o.scrollHeight;const M=()=>{if(!k.current){T.current=null;return}const I=o.scrollHeight,V=Math.abs(I-A);A=I,V<1?x+=1:x=0,o.scrollTop=o.scrollHeight,d.current=o.scrollTop;const q=performance.now()-v;if(x>=3||q>=500){k.current=!1,T.current=null;return}T.current=requestAnimationFrame(M)};T.current=requestAnimationFrame(M)}E.current=!0}},[c,e.length]),r.useEffect(()=>{e.length===0&&(E.current=!1,k.current=!1,T.current!==null&&(cancelAnimationFrame(T.current),T.current=null))},[e.length]);const O=r.useCallback((o,v)=>{var te;const x=u.current;if(!x||h.current.get(o)===v)return;h.current.set(o,v);const M=f.current.get(o);if(M===void 0)return;const I=x.getSize(M),V=v-I,q=n.current;q&&!k.current&&V!==0&&x.getOffset(M)+I+(((te=S.current)==null?void 0:te.offsetTop)??0)<q.scrollTop&&(j.current+=V,P()),x.setSize(M,v)&&g()},[P,g]),W=r.useCallback((o,v="auto")=>{var x;(x=n.current)==null||x.scrollTo({top:o,behavior:v})},[]),Y=me(n,u,S,{reconcile:!0}),U=r.useCallback(()=>{var q;const o=n.current,v=u.current;if(!o||!v||v.count===0)return null;const x=((q=S.current)==null?void 0:q.offsetTop)??0,A=Math.max(0,o.scrollTop-x),M=v.getOffsets(),I=re(M,A);return{key:y.current[I]??null,offsetWithinItem:A-v.getOffset(I),scrollTop:o.scrollTop,scrollHeight:o.scrollHeight}},[]),D=r.useCallback((o,v)=>{var I;const x=u.current;if(!x)return null;const A=f.current.get(o);return A===void 0?null:(((I=S.current)==null?void 0:I.offsetTop)??0)+x.getOffset(A)+v},[]),B=u.current,ce=B?B.totalSize():0,J=n.current,X=(J==null?void 0:J.scrollTop)??d.current,Q=(J==null?void 0:J.clientHeight)??w.current,le=((ee=S.current)==null?void 0:ee.offsetTop)??0,Z=Math.max(0,X-le),$=[];if(B&&B.count>0&&Q>0){const o=B.getOffsets(),v=B.getSizes(),x=re(o,Z),A=he(o,v,Z+Q),M=de({firstVisible:x,lastVisible:A,itemCount:B.count,overscan:i});for(let I=M.start;I<=M.end&&I<e.length;I++)$.push({key:y.current[I]??t(e[I],I),index:I,start:B.getOffset(I),size:B.getSize(I),data:e[I]})}else if(B&&B.count>0){const o=Math.min(e.length,i*2+1),v=c==="bottom"?Math.max(0,e.length-o):0,x=v+o-1;for(let A=v;A<=x;A++)$.push({key:y.current[A]??t(e[A],A),index:A,start:B.getOffset(A),size:B.getSize(A),data:e[A]})}const ie=J?J.scrollHeight-J.scrollTop-J.clientHeight:1/0;return{scrollerRef:n,innerRef:S,virtualItems:$,totalSize:ce,measureItem:O,scrollToIndex:Y,scrollToOffset:W,captureAnchorSnapshot:U,resolveAnchorTop:D,isAtTop:X<=1,isAtBottom:ie<=1,scrollTop:X}}function ge(l){const{scrollerRef:e,itemCount:t,captureAnchorSnapshot:s,resolveAnchorTop:i,onRestored:c}=l,n=r.useRef(null),S=r.useRef(!1),u=r.useRef({first:null,second:null,timeout:null}),h=r.useCallback(()=>{const{first:f,second:m,timeout:F}=u.current;f&&cancelAnimationFrame(f),m&&cancelAnimationFrame(m),F&&clearTimeout(F),u.current={first:null,second:null,timeout:null}},[]),y=r.useCallback(()=>{const f=e.current;if(!f)return;const m=s();n.current=m??{key:null,offsetWithinItem:0,scrollTop:f.scrollTop,scrollHeight:f.scrollHeight},S.current=!0},[e,s]);return r.useLayoutEffect(()=>{if(!S.current)return;const f=e.current,m=n.current;if(!f||!m)return;S.current=!1;const F=()=>{let b=null;m.key!==null&&(b=i(m.key,m.offsetWithinItem)),b===null&&(b=m.scrollTop+(f.scrollHeight-m.scrollHeight)),Number.isFinite(b)&&Math.abs(f.scrollTop-b)>1&&(f.scrollTop=b)};return h(),F(),c==null||c(),u.current.first=requestAnimationFrame(()=>{u.current.first=null,F(),u.current.second=requestAnimationFrame(()=>{u.current.second=null,F()})}),u.current.timeout=setTimeout(()=>{u.current.timeout=null,F()},90),()=>h()},[t,e,i,h,c]),{prepareAnchor:y}}function pe(l){const{previous:e,distanceFromBottom:t,threshold:s,hysteresis:i}=l;if(!i)return t<=s;const c=Math.max(0,i.enter),n=Math.max(c,i.leave);return e?t<=n:t<=c}function ve(l,e){const t=typeof e=="number"?e:e.threshold??200,s=typeof e=="number"?void 0:e.hysteresis,[i,c]=r.useState(!0),n=r.useRef(null),S=r.useRef(!0);return r.useEffect(()=>{const u=l.current;if(!u)return;const h=()=>{const f=u.scrollHeight-u.scrollTop-u.clientHeight,m=pe({previous:S.current,distanceFromBottom:f,threshold:t,hysteresis:s});S.current=m,c(m)},y=()=>{n.current!==null&&cancelAnimationFrame(n.current),n.current=requestAnimationFrame(h)};return u.addEventListener("scroll",y,{passive:!0}),h(),()=>{u.removeEventListener("scroll",y),n.current!==null&&cancelAnimationFrame(n.current)}},[l,t,s==null?void 0:s.enter,s==null?void 0:s.leave]),i}function Re(l){const{itemCount:e,firstKey:t,lastKey:s,isAtBottom:i,scrollToIndex:c,mode:n}=l,S=r.useRef(e),u=r.useRef(t),h=r.useRef(s);r.useLayoutEffect(()=>{if(!n){S.current=e,u.current=t,h.current=s;return}const y=S.current,f=u.current,m=h.current;e>y&&t===f&&s!==m&&i&&e>0&&c(e-1,{align:"end",behavior:n==="smooth"?"smooth":"auto"}),S.current=e,u.current=t,h.current=s},[e,t,s,i,c,n])}function se(l){const{items:e,getKey:t,estimatedItemSize:s=80,overscan:i=20,atBottomThreshold:c=200,atBottomHysteresis:n,followOutput:S="auto",initialAlignment:u="bottom",scrollModifier:h=null,onStartReached:y,onEndReached:f,startReachedThreshold:m=300,endReachedThreshold:F=300,scrollToMessageKey:b,onScrollToMessageComplete:E}=l,d=ne({items:e,getKey:t,estimatedItemSize:s,overscan:i,initialAlignment:u}),w=ve(d.scrollerRef,{threshold:c,hysteresis:n??{enter:80,leave:160}}),[,R]=r.useState(0),k=r.useCallback(()=>R(p=>p+1),[]),{prepareAnchor:T}=ge({scrollerRef:d.scrollerRef,itemCount:e.length,captureAnchorSnapshot:d.captureAnchorSnapshot,resolveAnchorTop:d.resolveAnchorTop,onRestored:k}),j=e.length>0?t(e[0],0):null,_=e.length>0?t(e[e.length-1],e.length-1):null;Re({itemCount:e.length,firstKey:j,lastKey:_,isAtBottom:w,scrollToIndex:d.scrollToIndex,mode:S??!1});const L=r.useCallback((p="auto")=>{e.length!==0&&d.scrollToIndex(e.length-1,{align:"end",behavior:p})},[e.length,d]),K=r.useCallback((p,P)=>{const O=e.findIndex((W,Y)=>t(W,Y)===p);O!==-1&&d.scrollToIndex(O,P)},[e,t,d]),H=r.useRef(null);r.useLayoutEffect(()=>{if(h&&H.current!==h.id){if(H.current=h.id,h.type==="prepend"){T();return}if(h.type==="append"){if(h.ifAtBottomOnly&&!w)return;L(h.behavior??"auto");return}if(h.type==="items-change"){w&&L("auto");return}K(h.key,{align:h.align??"center",behavior:h.behavior??"auto"})}},[h,w,T,L,K]);const a=r.useRef(!1),g=r.useRef(u==="top");r.useEffect(()=>{const p=d.scrollerRef.current;if(!p||!y)return;const P=()=>{const O=p.scrollTop;g.current||(p.scrollHeight<=p.clientHeight+m||O>m)&&(g.current=!0),g.current&&O<=m&&!a.current&&(a.current=!0,Promise.resolve(y()).finally(()=>{a.current=!1}))};return p.addEventListener("scroll",P,{passive:!0}),P(),()=>p.removeEventListener("scroll",P)},[d.scrollerRef,y,m,u]);const z=r.useRef(!1);r.useEffect(()=>{const p=d.scrollerRef.current;if(!p||!f)return;const P=()=>{p.scrollHeight-p.scrollTop-p.clientHeight<=F&&!z.current&&(z.current=!0,Promise.resolve(f()).finally(()=>{z.current=!1}))};return p.addEventListener("scroll",P,{passive:!0}),()=>p.removeEventListener("scroll",P)},[d.scrollerRef,f,F]);const C=r.useRef(null);return r.useEffect(()=>{if(!b||C.current===b)return;const p=e.findIndex((P,O)=>t(P,O)===b);p!==-1&&(C.current=b,d.scrollToIndex(p,{align:"center",behavior:"auto"}),E==null||E())},[b,e,t,d,E]),{scrollerRef:d.scrollerRef,innerRef:d.innerRef,virtualItems:d.virtualItems,totalSize:d.totalSize,measureItem:d.measureItem,scrollToIndex:d.scrollToIndex,scrollToBottom:L,scrollToKey:K,isAtBottom:w,prepareAnchor:T}}function oe({virtualItem:l,measureItem:e,children:t}){const s=r.useRef(null),i=r.useRef(!1);return r.useEffect(()=>{const c=s.current;if(!c)return;i.current=!1;const n=new ResizeObserver(([S])=>{S&&(i.current=!0,e(l.key,S.contentRect.height))});return n.observe(c),()=>n.disconnect()},[l.key,e]),N.jsx("div",{ref:s,style:{position:"absolute",top:0,transform:`translateY(${l.start}px)`,width:"100%",minHeight:i.current?void 0:l.size},children:t})}function ye(l,e){const{data:t,itemContent:s,computeItemKey:i,estimatedItemSize:c=80,overscan:n=20,followOutput:S="auto",atBottomThreshold:u=200,atBottomHysteresis:h,initialAlignment:y="bottom",scrollModifier:f,onStartReached:m,onEndReached:F,startReachedThreshold:b=300,endReachedThreshold:E=300,scrollToMessageKey:d,onScrollToMessageComplete:w,onAtBottomChange:R,components:k={},className:T,style:j}=l,{scrollerRef:_,innerRef:L,virtualItems:K,totalSize:H,measureItem:a,scrollToIndex:g,scrollToBottom:z,scrollToKey:C,isAtBottom:p,prepareAnchor:P}=se({items:t,getKey:(D,B)=>i(B,D),estimatedItemSize:c,overscan:n,atBottomThreshold:u,atBottomHysteresis:h,followOutput:S,initialAlignment:y,scrollModifier:f,onStartReached:m,onEndReached:F,startReachedThreshold:b,endReachedThreshold:E,scrollToMessageKey:d,onScrollToMessageComplete:w}),O=G.useRef(p);G.useEffect(()=>{O.current!==p&&(O.current=p,R==null||R(p))},[p,R]),r.useImperativeHandle(e,()=>({scrollToBottom:z,scrollToIndex:g,scrollToKey:C,getScrollTop:()=>{var D;return((D=_.current)==null?void 0:D.scrollTop)??0},isAtBottom:()=>p,prepareAnchor:P}),[z,g,C,_,p,P]);const{Header:W,Footer:Y,EmptyPlaceholder:U}=k;return t.length===0&&U?N.jsx(U,{}):N.jsxs("div",{ref:_,className:T,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...j},children:[W&&N.jsx(W,{}),N.jsx("div",{ref:L,style:{height:H,position:"relative",width:"100%"},children:K.map(D=>N.jsx(oe,{virtualItem:D,measureItem:a,children:s(D.index,D.data)},D.key))}),Y&&N.jsx(Y,{})]})}const Te=r.forwardRef(ye);function Se({data:l,itemContent:e,computeItemKey:t,estimatedItemSize:s=60,overscan:i=20,onEndReached:c,endReachedThreshold:n=300,components:S={},className:u,style:h}){const{scrollerRef:y,innerRef:f,virtualItems:m,totalSize:F,measureItem:b}=ne({items:l,getKey:(R,k)=>t(k,R),estimatedItemSize:s,overscan:i,initialAlignment:"top"});G.useEffect(()=>{const R=y.current;if(!R||!c)return;let k=!1;const T=()=>{R.scrollHeight-R.scrollTop-R.clientHeight<=n&&!k&&(k=!0,Promise.resolve(c()).finally(()=>{k=!1}))};return R.addEventListener("scroll",T,{passive:!0}),()=>R.removeEventListener("scroll",T)},[y,c,n]);const{Header:E,Footer:d,EmptyPlaceholder:w}=S;return l.length===0&&w?N.jsx(w,{}):N.jsxs("div",{ref:y,className:u,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...h},children:[E&&N.jsx(E,{}),N.jsx("div",{ref:f,style:{height:F,position:"relative",width:"100%"},children:m.map(R=>N.jsx(oe,{virtualItem:R,measureItem:b,children:e(R.index,R.data)},R.key))}),d&&N.jsx(d,{})]})}function be(l){const{fetcher:e,initialPage:t=1,direction:s="append",getKey:i,onPageLoaded:c,onError:n}=l,[S,u]=r.useState([]),[h,y]=r.useState(t),[f,m]=r.useState(!0),[F,b]=r.useState(!1),[E,d]=r.useState(!1),[w,R]=r.useState(!1),k=r.useRef(new Set),T=r.useRef(!1),j=r.useCallback(a=>i?a.filter(g=>{const z=i(g);return k.current.has(z)?!1:(k.current.add(z),!0)}):a,[i]),_=r.useCallback(async()=>{if(!(T.current||!f)){T.current=!0,R(!0);try{const a=h+1,g=await e(a),z=j(g.data);u(C=>s==="prepend"?[...z,...C]:[...C,...z]),y(a),m(g.hasNextPage),b(g.hasPrevPage),c==null||c(a,z)}catch(a){n==null||n(a instanceof Error?a:new Error(String(a)))}finally{R(!1),T.current=!1}}},[h,f,e,j,s,c,n]),L=r.useCallback(async()=>{if(!(T.current||!F)){T.current=!0,R(!0);try{const a=h-1,g=await e(a),z=j(g.data);u(C=>[...z,...C]),y(a),b(g.hasPrevPage),m(g.hasNextPage),c==null||c(a,z)}catch(a){n==null||n(a instanceof Error?a:new Error(String(a)))}finally{R(!1),T.current=!1}}},[h,F,e,j,c,n]),K=r.useCallback(async()=>{if(!T.current){T.current=!0,d(!0);try{const a=await e(t),g=a.data;if(i){const z=new Set(g.map(i));g.forEach(C=>k.current.add(i(C))),u(C=>{const p=C.filter(P=>!z.has(i(P)));return s==="prepend"?[...g,...p]:[...p,...g]})}else u(g);y(t),m(a.hasNextPage),b(a.hasPrevPage),c==null||c(t,g)}catch(a){n==null||n(a instanceof Error?a:new Error(String(a)))}finally{d(!1),T.current=!1}}},[e,t,i,s,c,n]),H=r.useCallback(()=>{u([]),y(t),m(!0),b(!1),d(!1),R(!1),k.current.clear(),T.current=!1},[t]);return{items:S,loadNextPage:_,loadPrevPage:L,hasNextPage:f,hasPrevPage:F,loading:E,loadingMore:w,refresh:K,reset:H,currentPage:h}}exports.ChatVirtualList=Te;exports.VirtualList=Se;exports.useChatVirtualizer=se;exports.usePagination=be;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export { ChatVirtualList } from './components/ChatVirtualList';
2
2
  export { VirtualList } from './components/VirtualList';
3
3
  export { useChatVirtualizer } from './hooks/useChatVirtualizer';
4
4
  export { usePagination } from './hooks/usePagination';
5
- export type { ChatVirtualListProps, ChatVirtualListHandle, VirtualListProps, VirtualItem, ScrollToIndexOpts, VirtualListComponents, UsePaginationOptions, UsePaginationReturn, UseChatVirtualizerReturn, } from './types';
5
+ export type { ChatVirtualListProps, ChatVirtualListHandle, VirtualListProps, VirtualItem, ScrollToIndexOpts, ChatScrollModifier, AtBottomHysteresis, VirtualListComponents, UsePaginationOptions, UsePaginationReturn, UseChatVirtualizerReturn, } from './types';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,SAAS,CAAA"}