react-anchorlist 0.2.1 → 0.2.4

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
@@ -1,8 +1,8 @@
1
1
  # react-anchorlist
2
2
 
3
- **High-performance virtualized list for React built for chat.**
3
+ **Lista virtualizada de alta performance para React, pensada para interfaces com muitos itens (como chat).**
4
4
 
5
- Zero flicker on prepend. Smooth scroll. Native pagination support. Drop-in replacement for react-virtuoso.
5
+ Sem flicker ao carregar itens anteriores. Scroll fluido. Paginação nativa.
6
6
 
7
7
  ```bash
8
8
  npm install react-anchorlist
@@ -10,22 +10,36 @@ npm install react-anchorlist
10
10
 
11
11
  ---
12
12
 
13
- ## Why not react-virtuoso?
13
+ ## O que é
14
14
 
15
- | Problem | react-virtuoso | react-anchorlist |
16
- |---|---|---|
17
- | Flicker when loading older messages | ✗ Yes | ✓ Zero — scroll anchor via `useLayoutEffect` |
18
- | Prepend hack | `firstItemIndex=10000` | Native prepend detection |
19
- | `scrollToIndex` needs double-RAF | ✗ Yes | ✓ No — direct offset calculation |
20
- | Overscan as flicker band-aid | 3000px+ needed | 5 items (no pixels) |
21
- | Bundle size | 88 kB | ~17 kB |
22
- | Pagination built-in | ✗ No | ✓ Yes — `usePagination` hook |
15
+ `react-anchorlist` é uma biblioteca para renderizar listas grandes com boa performance.
16
+ Ela mantém a navegação suave mesmo com histórico extenso, reduzindo custo de renderização e melhorando a experiência do usuário.
17
+
18
+ ---
19
+
20
+ ## Para que serve
21
+
22
+ Use quando você precisa de:
23
+
24
+ - **Renderização eficiente** em listas longas
25
+ - **Scroll estável** ao adicionar itens no topo (padrão chat)
26
+ - **Paginação incremental** ao chegar no início/fim da lista
27
+ - **Comportamento previsível** para “seguir no final” quando chegam novos itens
28
+
29
+ ---
30
+
31
+ ## Como funciona (alto nível)
32
+
33
+ - Renderiza principalmente os itens visíveis (com pequeno overscan)
34
+ - Mede altura real dos itens para manter posicionamento correto
35
+ - Preserva âncora de scroll quando itens são inseridos no topo
36
+ - Dispara callbacks ao aproximar do topo/fim para buscar mais dados
23
37
 
24
38
  ---
25
39
 
26
40
  ## Quick start
27
41
 
28
- ### Simple list (tickets, contacts, feeds)
42
+ ### Lista genérica (tickets, contatos, feed)
29
43
 
30
44
  ```tsx
31
45
  import { VirtualList } from 'react-anchorlist'
@@ -39,7 +53,7 @@ import { VirtualList } from 'react-anchorlist'
39
53
  />
40
54
  ```
41
55
 
42
- ### Chat list (messages with pagination)
56
+ ### Lista de chat (mensagens com paginação)
43
57
 
44
58
  ```tsx
45
59
  import { ChatVirtualList } from 'react-anchorlist'
@@ -52,11 +66,11 @@ const listRef = useRef<ChatVirtualListHandle>(null)
52
66
  data={messages}
53
67
  computeItemKey={(index, item) => item._id}
54
68
  itemContent={(index, item) => <Message data={item} />}
55
- // Paginationfires when scroll reaches top
69
+ // Paginaçãodispara ao chegar no topo
56
70
  onStartReached={loadOlderMessages}
57
- // Stick to bottom when new messages arrive
71
+ // Mantém no final quando chegam novas mensagens
58
72
  followOutput="auto"
59
- // Notify parent of bottom state (for scroll button)
73
+ // Informa ao componente pai se está no final
60
74
  onAtBottomChange={setIsAtBottom}
61
75
  components={{
62
76
  Header: () => loading ? <Spinner /> : null,
@@ -72,24 +86,24 @@ const listRef = useRef<ChatVirtualListHandle>(null)
72
86
 
73
87
  | Prop | Type | Default | Description |
74
88
  |---|---|---|---|
75
- | `data` | `T[]` | required | Array of items |
76
- | `itemContent` | `(index, item) => ReactNode` | required | Render function |
77
- | `computeItemKey` | `(index, item) => string \| number` | required | Stable key per item |
78
- | `estimatedItemSize` | `number` | `80` | Initial height estimate (px) |
79
- | `overscan` | `number` | `5` | Items to render beyond visible area |
80
- | `followOutput` | `"auto" \| "smooth" \| false` | `"auto"` | Stick to bottom on new items |
81
- | `atBottomThreshold` | `number` | `200` | px from bottom to consider "at bottom" |
82
- | `initialAlignment` | `"top" \| "bottom"` | `"bottom"` | Initial scroll position |
83
- | `onStartReached` | `() => void \| Promise<void>` | — | Fires when scroll nears top |
84
- | `onEndReached` | `() => void \| Promise<void>` | — | Fires when scroll nears bottom |
85
- | `startReachedThreshold` | `number` | `300` | px from top to trigger `onStartReached` |
86
- | `endReachedThreshold` | `number` | `300` | px from bottom to trigger `onEndReached` |
87
- | `onAtBottomChange` | `(isAtBottom: boolean) => void` | — | Called when bottom state changes |
88
- | `scrollToMessageKey` | `string \| number \| null` | — | Scroll to item by key |
89
- | `onScrollToMessageComplete` | `() => void` | — | Fires after scroll-to-key completes |
90
- | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slot components |
91
- | `className` | `string` | — | CSS class for the scroller |
92
- | `style` | `CSSProperties` | — | Inline style for the scroller |
89
+ | `data` | `T[]` | required | Array de itens |
90
+ | `itemContent` | `(index, item) => ReactNode` | required | Função de renderização |
91
+ | `computeItemKey` | `(index, item) => string \| number` | required | Chave estável por item |
92
+ | `estimatedItemSize` | `number` | `80` | Estimativa inicial de altura (px) |
93
+ | `overscan` | `number` | `5` | Itens renderizados além da área visível |
94
+ | `followOutput` | `"auto" \| "smooth" \| false` | `"auto"` | Seguir no final ao entrar item novo |
95
+ | `atBottomThreshold` | `number` | `200` | Distância (px) para considerar “no final” |
96
+ | `initialAlignment` | `"top" \| "bottom"` | `"bottom"` | Posição inicial do scroll |
97
+ | `onStartReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do topo |
98
+ | `onEndReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do fim |
99
+ | `startReachedThreshold` | `number` | `300` | Distância (px) do topo para disparar callback |
100
+ | `endReachedThreshold` | `number` | `300` | Distância (px) do fim para disparar callback |
101
+ | `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 |
104
+ | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slots opcionais |
105
+ | `className` | `string` | — | Classe CSS do container |
106
+ | `style` | `CSSProperties` | — | Estilo inline do container |
93
107
 
94
108
  ---
95
109
 
@@ -97,16 +111,16 @@ const listRef = useRef<ChatVirtualListHandle>(null)
97
111
 
98
112
  | Prop | Type | Default | Description |
99
113
  |---|---|---|---|
100
- | `data` | `T[]` | required | Array of items |
101
- | `itemContent` | `(index, item) => ReactNode` | required | Render function |
102
- | `computeItemKey` | `(index, item) => string \| number` | required | Stable key per item |
103
- | `estimatedItemSize` | `number` | `80` | Initial height estimate (px) |
104
- | `overscan` | `number` | `5` | Items to render beyond visible area |
105
- | `onEndReached` | `() => void \| Promise<void>` | — | Fires when scroll nears bottom |
106
- | `endReachedThreshold` | `number` | `300` | px from bottom to trigger callback |
107
- | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slot components |
108
- | `className` | `string` | — | CSS class for the scroller |
109
- | `style` | `CSSProperties` | — | Inline style for the scroller |
114
+ | `data` | `T[]` | required | Array de itens |
115
+ | `itemContent` | `(index, item) => ReactNode` | required | Função de renderização |
116
+ | `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 |
119
+ | `onEndReached` | `() => void \| Promise<void>` | — | Dispara ao aproximar do fim |
120
+ | `endReachedThreshold` | `number` | `300` | Distância (px) do fim para disparar callback |
121
+ | `components` | `{ Header, Footer, EmptyPlaceholder }` | — | Slots opcionais |
122
+ | `className` | `string` | — | Classe CSS do container |
123
+ | `style` | `CSSProperties` | — | Estilo inline do container |
110
124
 
111
125
  ---
112
126
 
@@ -120,23 +134,23 @@ listRef.current?.scrollToIndex(42, { align: 'center', behavior: 'smooth' })
120
134
  listRef.current?.scrollToKey('msg-123', { align: 'center' })
121
135
  listRef.current?.getScrollTop() // → number
122
136
  listRef.current?.isAtBottom() // → boolean
123
- listRef.current?.prepareAnchor() // call before manually prepending items
137
+ listRef.current?.prepareAnchor() // chame antes de prepend manual
124
138
  ```
125
139
 
126
140
  | Method | Description |
127
141
  |---|---|
128
- | `scrollToBottom(behavior?)` | Scroll to last item |
129
- | `scrollToIndex(index, opts?)` | Scroll to item by index |
130
- | `scrollToKey(key, opts?)` | Scroll to item by key |
131
- | `getScrollTop()` | Current scroll position |
132
- | `isAtBottom()` | Whether list is at bottom |
133
- | `prepareAnchor()` | Save scroll position before external prepend |
142
+ | `scrollToBottom(behavior?)` | Vai para o último item |
143
+ | `scrollToIndex(index, opts?)` | Vai para item por índice |
144
+ | `scrollToKey(key, opts?)` | Vai para item por chave |
145
+ | `getScrollTop()` | Posição atual do scroll |
146
+ | `isAtBottom()` | Indica se está no final |
147
+ | `prepareAnchor()` | Salva posição antes de prepend externo |
134
148
 
135
149
  ---
136
150
 
137
151
  ## usePagination hook
138
152
 
139
- For back-end driven pagination with automatic deduplication:
153
+ Para paginação orientada ao back-end com deduplicação automática:
140
154
 
141
155
  ```tsx
142
156
  import { usePagination, ChatVirtualList } from 'react-anchorlist'
@@ -151,8 +165,8 @@ const { items, loadPrevPage, hasPrevPage, loadingMore } = usePagination({
151
165
  currentPage: res.pagination.current_page,
152
166
  }
153
167
  },
154
- direction: 'prepend', // new pages go to the top (chat pattern)
155
- getKey: (msg) => msg._id, // deduplication key
168
+ direction: 'prepend', // novas páginas entram no topo
169
+ getKey: (msg) => msg._id, // chave para deduplicação
156
170
  })
157
171
 
158
172
  <ChatVirtualList
@@ -168,36 +182,22 @@ const { items, loadPrevPage, hasPrevPage, loadingMore } = usePagination({
168
182
 
169
183
  ---
170
184
 
171
- ## Migrating from react-virtuoso
185
+ ## Boas práticas
172
186
 
173
- | react-virtuoso | react-anchorlist |
174
- |---|---|
175
- | `<Virtuoso data={} itemContent={} />` | `<ChatVirtualList data={} itemContent={} />` |
176
- | `computeItemKey={(_index, item) => item.id}` | `computeItemKey={(_index, item) => item.id}` ✓ same |
177
- | `firstItemIndex={10000}` | Not needed automatic |
178
- | `initialTopMostItemIndex={n}` | Not needed — `initialAlignment="bottom"` |
179
- | `alignToBottom` | `initialAlignment="bottom"` (default) |
180
- | `followOutput={isAtBottom ? 'auto' : false}` | `followOutput="auto"` (default) |
181
- | `atBottomStateChange={setIsAtBottom}` | `onAtBottomChange={setIsAtBottom}` |
182
- | `atBottomThreshold={200}` | `atBottomThreshold={200}` ✓ same |
183
- | `startReached={fn}` | `onStartReached={fn}` |
184
- | `endReached={fn}` | `onEndReached={fn}` |
185
- | `overscan={{ main: 3000, reverse: 3000 }}` | `overscan={5}` (items, not pixels) |
186
- | `increaseViewportBy={{ top: 2200, bottom: 1100 }}` | Not needed |
187
- | `components={{ Header, Footer }}` | `components={{ Header, Footer }}` ✓ same |
188
- | `ref.scrollToIndex({ index, align, behavior })` | `ref.scrollToIndex(index, { align, behavior })` |
187
+ - Use **chave estável** em `computeItemKey`
188
+ - Evite lógica pesada em `itemContent`
189
+ - Padronize paginação e ordenação no back-end
190
+ - Ajuste `estimatedItemSize` para o tipo de item predominante
191
+ - Mantenha `overscan` baixo e aumente se houver necessidade real
189
192
 
190
193
  ---
191
194
 
192
- ## How it works
193
-
194
- **OffsetMap** — maintains cumulative Y offsets for all items. When an item is measured, only downstream offsets are recalculated (O(n−i), not O(n)).
195
-
196
- **Scroll Anchor** — before prepending items, saves `scrollTop` and `scrollHeight`. After the DOM updates, `useLayoutEffect` adjusts `scrollTop` by the delta — synchronously, before the browser paints. Zero flicker.
197
-
198
- **ResizeObserver per item** — each rendered item has its own observer. Real heights are measured as soon as the item enters the DOM and reported back to the engine. No estimation loops, no layout thrashing.
195
+ ## Como funciona internamente
199
196
 
200
- **Binary search** given `scrollTop`, finds the first visible item in O(log n) using the offset array.
197
+ - **OffsetMap:** calcula offsets acumulados por item
198
+ - **Âncora de scroll:** preserva posição ao prepend
199
+ - **ResizeObserver por item:** mede altura real ao renderizar
200
+ - **Busca binária:** encontra faixa visível com eficiência
201
201
 
202
202
  ---
203
203
 
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualItem.d.ts","sourceRoot":"","sources":["../../src/components/VirtualItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACjC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,gBAAgB,2CA6BlB"}
1
+ {"version":3,"file":"VirtualItem.d.ts","sourceRoot":"","sources":["../../src/components/VirtualItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACjC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,gBAAgB,2CAsClB"}
@@ -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,CA8G9B"}
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,12 +1,16 @@
1
1
  import { ScrollToIndexOpts } from '../types';
2
2
 
3
3
  /**
4
- * Automatically scrolls to the last item when new items are appended
5
- * and the user is already at the bottom.
6
- * Uses useLayoutEffect to run before paint — zero visual lag.
4
+ * Auto-follow output ONLY for appends at the end.
5
+ *
6
+ * Important behavior:
7
+ * - Appends (new message at bottom) + isAtBottom => follow
8
+ * - Prepends (older messages at top) => NEVER follow
7
9
  */
8
10
  export declare function useFollowOutput(params: {
9
11
  itemCount: number;
12
+ firstKey: string | number | null;
13
+ lastKey: string | number | null;
10
14
  isAtBottom: boolean;
11
15
  scrollToIndex: (index: number, opts?: ScrollToIndexOpts) => void;
12
16
  mode: "auto" | "smooth" | false;
@@ -1 +1 @@
1
- {"version":3,"file":"useFollowOutput.d.ts","sourceRoot":"","sources":["../../src/hooks/useFollowOutput.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAChE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;CAChC,GAAG,IAAI,CAcP"}
1
+ {"version":3,"file":"useFollowOutput.d.ts","sourceRoot":"","sources":["../../src/hooks/useFollowOutput.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC/B,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAChE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;CAChC,GAAG,IAAI,CAqCP"}
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Prevents scroll jump when items are prepended.
3
- * Call prepareAnchor() BEFORE adding items to the top.
4
- * useLayoutEffect adjusts scrollTop synchronously before the browser paints.
2
+ * Keeps viewport anchored when items are prepended.
3
+ *
4
+ * This hook performs restoration in multiple phases (layout + raf + timeout)
5
+ * to absorb late height measurements (ResizeObserver) and avoid flicker/jumps.
5
6
  */
6
- export declare function useScrollAnchor(scrollerRef: React.RefObject<HTMLDivElement>, itemCount: number): {
7
+ export declare function useScrollAnchor(scrollerRef: React.RefObject<HTMLDivElement>, itemCount: number, onRestored?: () => void): {
7
8
  prepareAnchor: () => void;
8
9
  };
9
10
  //# sourceMappingURL=useScrollAnchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useScrollAnchor.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollAnchor.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAC5C,SAAS,EAAE,MAAM,GAChB;IAAE,aAAa,EAAE,MAAM,IAAI,CAAA;CAAE,CA0B/B"}
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,6 +1,11 @@
1
1
  import { OffsetMap } from '../core/offsetMap';
2
2
  import { ScrollToIndexOpts } from '../types';
3
3
 
4
- /** Programmatic scroll to a given item index with alignment support */
4
+ /**
5
+ * Programmatic scroll to item index.
6
+ * Supports both signatures for backward compatibility:
7
+ * 1) scrollToIndex(index, opts?)
8
+ * 2) scrollToIndex({ index, align, behavior, offset })
9
+ */
5
10
  export declare function useScrollToIndex(scrollerRef: React.RefObject<HTMLDivElement>, offsetMapRef: React.MutableRefObject<OffsetMap | null>, innerRef?: React.RefObject<HTMLDivElement>): (index: number, opts?: ScrollToIndexOpts) => void;
6
11
  //# 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,uEAAuE;AACvE,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,CA6BnD"}
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 +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,CAqN5B"}
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,CAuP5B"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const k=require("react/jsx-runtime"),r=require("react");function $(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const t in n)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>n[t]})}}return e.default=n,Object.freeze(e)}const _=$(r);class G{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 i=0;i<e;i++)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 J{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[i,s]of this.cache){const c=t.get(i);c!==void 0&&e.setSize(c,s)}}}function Q(n,e){if(n.length===0)return 0;let t=0,i=n.length-1;for(;t<i;){const s=t+i>>1;(n[s]??0)<e?t=s+1:i=s}return Math.max(0,t>0&&(n[t]??0)>e?t-1:t)}function U(n,e,t){if(n.length===0)return 0;for(let i=n.length-1;i>=0;i--)if((n[i]??0)<t)return i;return 0}function W(n){const{firstVisible:e,lastVisible:t,itemCount:i,overscan:s}=n;return i===0?{start:0,end:-1}:{start:Math.max(0,e-s),end:Math.min(i-1,t+s)}}function X(n,e,t){return r.useCallback((i,s)=>{var a;const c=n.current,o=e.current;if(!c||!o)return;const S=((a=t==null?void 0:t.current)==null?void 0:a.offsetTop)??0,d=o.getOffset(i),x=o.getSize(i),p=(s==null?void 0:s.align)??"start",E=(s==null?void 0:s.behavior)??"auto",v=(s==null?void 0:s.offset)??0;let z;p==="start"?z=S+d+v:p==="center"?z=S+d-c.clientHeight/2+x/2+v:z=S+d-c.clientHeight+x+v,c.scrollTo({top:Math.max(0,z),behavior:E})},[n,e,t])}function q(n){var B;const{items:e,getKey:t,estimatedItemSize:i,overscan:s,initialAlignment:c}=n,o=r.useRef(null),S=r.useRef(null),d=r.useRef(null),x=r.useRef(new J),p=r.useRef([]),E=r.useRef(!1),v=r.useRef(0),z=r.useRef(0),a=r.useRef(null),[,j]=r.useState(0),b=r.useCallback(()=>j(l=>l+1),[]);d.current||(d.current=new G(e.length,i));const H=r.useRef(e.length);if(e.length!==H.current||e.some((l,h)=>{const m=t(l,h);return p.current[h]!==m})){const l=d.current,h=e.map((V,N)=>t(V,N)),m=p.current,M=m.length,O=h.length;O===0?l.resize(0):M===0?l.resize(O):O>M?m.length>0&&h[O-M]===m[0]?l.prepend(O-M):l.resize(O):O<M&&l.resize(O);const w=new Map;h.forEach((V,N)=>w.set(V,N)),x.current.applyToOffsetMap(l,w),p.current=h,H.current=e.length}r.useEffect(()=>{const l=o.current;if(!l)return;const h=()=>{a.current===null&&(a.current=requestAnimationFrame(()=>{a.current=null,v.current=l.scrollTop,z.current=l.clientHeight,b()}))};return l.addEventListener("scroll",h,{passive:!0}),()=>{l.removeEventListener("scroll",h),a.current!==null&&(cancelAnimationFrame(a.current),a.current=null)}},[b]),r.useEffect(()=>{const l=o.current;if(!l)return;z.current=l.clientHeight,v.current=l.scrollTop;const h=new ResizeObserver(([m])=>{m&&(z.current=m.contentRect.height,b())});return h.observe(l),()=>h.disconnect()},[b]),r.useLayoutEffect(()=>{if(E.current||e.length===0)return;const l=o.current;l&&(c==="bottom"&&(l.scrollTop=l.scrollHeight,v.current=l.scrollTop),E.current=!0)},[c,e.length]),r.useEffect(()=>{e.length===0&&(E.current=!1)},[e.length]);const P=r.useCallback((l,h)=>{const m=d.current;if(!m||x.current.get(l)===h)return;x.current.set(l,h);const O=p.current.indexOf(l);if(O===-1)return;m.setSize(O,h)&&b()},[b]),R=r.useCallback((l,h="auto")=>{var m;(m=o.current)==null||m.scrollTo({top:l,behavior:h})},[]),F=X(o,d,S),u=d.current,C=u?u.totalSize():0,I=o.current,L=(I==null?void 0:I.scrollTop)??v.current,f=(I==null?void 0:I.clientHeight)??z.current,T=((B=S.current)==null?void 0:B.offsetTop)??0,y=Math.max(0,L-T);let A=[];if(u&&u.count>0&&f>0){const l=u.getOffsets(),h=u.getSizes(),m=Q(l,y),M=U(l,h,y+f),O=W({firstVisible:m,lastVisible:M,itemCount:u.count,overscan:s});for(let w=O.start;w<=O.end&&w<e.length;w++)A.push({key:p.current[w]??t(e[w],w),index:w,start:u.getOffset(w),size:u.getSize(w),data:e[w]})}else if(u&&u.count>0){const l=Math.min(e.length-1,s*2);for(let h=0;h<=l;h++)A.push({key:p.current[h]??t(e[h],h),index:h,start:u.getOffset(h),size:u.getSize(h),data:e[h]})}const K=I?I.scrollHeight-I.scrollTop-I.clientHeight:1/0;return{scrollerRef:o,innerRef:S,virtualItems:A,totalSize:C,measureItem:P,scrollToIndex:F,scrollToOffset:R,isAtTop:L<=1,isAtBottom:K<=1,scrollTop:L}}function Z(n,e){const t=r.useRef(0),i=r.useRef(0),s=r.useRef(!1),c=r.useCallback(()=>{const o=n.current;o&&(t.current=o.scrollTop,i.current=o.scrollHeight,s.current=!0)},[n]);return r.useLayoutEffect(()=>{if(!s.current)return;const o=n.current;if(!o)return;const S=o.scrollHeight-i.current;S>0&&(o.scrollTop=t.current+S),s.current=!1},[e,n]),{prepareAnchor:c}}function ee(n,e){const[t,i]=r.useState(!0),s=r.useRef(null);return r.useEffect(()=>{const c=n.current;if(!c)return;const o=()=>{const d=c.scrollHeight-c.scrollTop-c.clientHeight;i(d<=e)},S=()=>{s.current!==null&&cancelAnimationFrame(s.current),s.current=requestAnimationFrame(o)};return c.addEventListener("scroll",S,{passive:!0}),o(),()=>{c.removeEventListener("scroll",S),s.current!==null&&cancelAnimationFrame(s.current)}},[n,e]),t}function te(n){const{itemCount:e,isAtBottom:t,scrollToIndex:i,mode:s}=n,c=r.useRef(e);r.useLayoutEffect(()=>{s&&(e>c.current&&t&&e>0&&i(e-1,{align:"end",behavior:s==="smooth"?"smooth":"auto"}),c.current=e)},[e,t,i,s])}function D(n){const{items:e,getKey:t,estimatedItemSize:i=80,overscan:s=20,atBottomThreshold:c=200,followOutput:o="auto",initialAlignment:S="bottom",onStartReached:d,onEndReached:x,startReachedThreshold:p=300,endReachedThreshold:E=300,scrollToMessageKey:v,onScrollToMessageComplete:z}=n,a=q({items:e,getKey:t,estimatedItemSize:i,overscan:s,initialAlignment:S}),j=ee(a.scrollerRef,c),{prepareAnchor:b}=Z(a.scrollerRef,e.length);te({itemCount:e.length,isAtBottom:j,scrollToIndex:a.scrollToIndex,mode:o??!1});const H=r.useRef(!1);r.useEffect(()=>{const u=a.scrollerRef.current;if(!u||!d)return;const C=()=>{u.scrollTop<=p&&!H.current&&(H.current=!0,Promise.resolve(d()).finally(()=>{H.current=!1}))};return u.addEventListener("scroll",C,{passive:!0}),()=>u.removeEventListener("scroll",C)},[a.scrollerRef,d,p]);const g=r.useRef(!1);r.useEffect(()=>{const u=a.scrollerRef.current;if(!u||!x)return;const C=()=>{u.scrollHeight-u.scrollTop-u.clientHeight<=E&&!g.current&&(g.current=!0,Promise.resolve(x()).finally(()=>{g.current=!1}))};return u.addEventListener("scroll",C,{passive:!0}),()=>u.removeEventListener("scroll",C)},[a.scrollerRef,x,E]);const P=r.useRef(null);r.useEffect(()=>{if(!v||P.current===v)return;const u=e.findIndex((C,I)=>t(C,I)===v);u!==-1&&(P.current=v,a.scrollToIndex(u,{align:"center",behavior:"smooth"}),z==null||z())},[v,e,t,a,z]);const R=r.useCallback((u="auto")=>{e.length!==0&&a.scrollToIndex(e.length-1,{align:"end",behavior:u})},[e.length,a]),F=r.useCallback((u,C)=>{const I=e.findIndex((L,f)=>t(L,f)===u);I!==-1&&a.scrollToIndex(I,C)},[e,t,a]);return{scrollerRef:a.scrollerRef,innerRef:a.innerRef,virtualItems:a.virtualItems,totalSize:a.totalSize,measureItem:a.measureItem,scrollToIndex:a.scrollToIndex,scrollToBottom:R,scrollToKey:F,isAtBottom:j,prepareAnchor:b}}function Y({virtualItem:n,measureItem:e,children:t}){const i=r.useRef(null);return r.useEffect(()=>{const s=i.current;if(!s)return;const c=new ResizeObserver(([o])=>{o&&e(n.key,o.contentRect.height)});return c.observe(s),()=>c.disconnect()},[n.key,e]),k.jsx("div",{ref:i,style:{position:"absolute",top:0,transform:`translateY(${n.start}px)`,width:"100%",minHeight:n.size},children:t})}function re(n,e){const{data:t,itemContent:i,computeItemKey:s,estimatedItemSize:c=80,overscan:o=20,followOutput:S="auto",atBottomThreshold:d=200,initialAlignment:x="bottom",onStartReached:p,onEndReached:E,startReachedThreshold:v=300,endReachedThreshold:z=300,scrollToMessageKey:a,onScrollToMessageComplete:j,onAtBottomChange:b,components:H={},className:g,style:P}=n,{scrollerRef:R,innerRef:F,virtualItems:u,totalSize:C,measureItem:I,scrollToIndex:L,scrollToBottom:f,scrollToKey:T,isAtBottom:y,prepareAnchor:A}=D({items:t,getKey:(m,M)=>s(M,m),estimatedItemSize:c,overscan:o,atBottomThreshold:d,followOutput:S,initialAlignment:x,onStartReached:p,onEndReached:E,startReachedThreshold:v,endReachedThreshold:z,scrollToMessageKey:a,onScrollToMessageComplete:j}),K=_.useRef(y);_.useEffect(()=>{K.current!==y&&(K.current=y,b==null||b(y))},[y,b]),r.useImperativeHandle(e,()=>({scrollToBottom:f,scrollToIndex:L,scrollToKey:T,getScrollTop:()=>{var m;return((m=R.current)==null?void 0:m.scrollTop)??0},isAtBottom:()=>y,prepareAnchor:A}),[f,L,T,R,y,A]);const{Header:B,Footer:l,EmptyPlaceholder:h}=H;return t.length===0&&h?k.jsx(h,{}):k.jsxs("div",{ref:R,className:g,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...P},children:[B&&k.jsx(B,{}),k.jsx("div",{ref:F,style:{height:C,position:"relative",width:"100%"},children:u.map(m=>k.jsx(Y,{virtualItem:m,measureItem:I,children:i(m.index,m.data)},m.key))}),l&&k.jsx(l,{})]})}const se=r.forwardRef(re);function ne({data:n,itemContent:e,computeItemKey:t,estimatedItemSize:i=60,overscan:s=20,onEndReached:c,endReachedThreshold:o=300,components:S={},className:d,style:x}){const{scrollerRef:p,innerRef:E,virtualItems:v,totalSize:z,measureItem:a}=q({items:n,getKey:(g,P)=>t(P,g),estimatedItemSize:i,overscan:s,initialAlignment:"top"});_.useEffect(()=>{const g=p.current;if(!g||!c)return;let P=!1;const R=()=>{g.scrollHeight-g.scrollTop-g.clientHeight<=o&&!P&&(P=!0,Promise.resolve(c()).finally(()=>{P=!1}))};return g.addEventListener("scroll",R,{passive:!0}),()=>g.removeEventListener("scroll",R)},[p,c,o]);const{Header:j,Footer:b,EmptyPlaceholder:H}=S;return n.length===0&&H?k.jsx(H,{}):k.jsxs("div",{ref:p,className:d,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...x},children:[j&&k.jsx(j,{}),k.jsx("div",{ref:E,style:{height:z,position:"relative",width:"100%"},children:v.map(g=>k.jsx(Y,{virtualItem:g,measureItem:a,children:e(g.index,g.data)},g.key))}),b&&k.jsx(b,{})]})}function ie(n){const{fetcher:e,initialPage:t=1,direction:i="append",getKey:s,onPageLoaded:c,onError:o}=n,[S,d]=r.useState([]),[x,p]=r.useState(t),[E,v]=r.useState(!0),[z,a]=r.useState(!1),[j,b]=r.useState(!1),[H,g]=r.useState(!1),P=r.useRef(new Set),R=r.useRef(!1),F=r.useCallback(f=>s?f.filter(T=>{const y=s(T);return P.current.has(y)?!1:(P.current.add(y),!0)}):f,[s]),u=r.useCallback(async()=>{if(!(R.current||!E)){R.current=!0,g(!0);try{const f=x+1,T=await e(f),y=F(T.data);d(A=>i==="prepend"?[...y,...A]:[...A,...y]),p(f),v(T.hasNextPage),a(T.hasPrevPage),c==null||c(f,y)}catch(f){o==null||o(f instanceof Error?f:new Error(String(f)))}finally{g(!1),R.current=!1}}},[x,E,e,F,i,c,o]),C=r.useCallback(async()=>{if(!(R.current||!z)){R.current=!0,g(!0);try{const f=x-1,T=await e(f),y=F(T.data);d(A=>[...y,...A]),p(f),a(T.hasPrevPage),v(T.hasNextPage),c==null||c(f,y)}catch(f){o==null||o(f instanceof Error?f:new Error(String(f)))}finally{g(!1),R.current=!1}}},[x,z,e,F,c,o]),I=r.useCallback(async()=>{if(!R.current){R.current=!0,b(!0);try{const f=await e(t),T=f.data;if(s){const y=new Set(T.map(s));T.forEach(A=>P.current.add(s(A))),d(A=>{const K=A.filter(B=>!y.has(s(B)));return i==="prepend"?[...T,...K]:[...K,...T]})}else d(T);p(t),v(f.hasNextPage),a(f.hasPrevPage),c==null||c(t,T)}catch(f){o==null||o(f instanceof Error?f:new Error(String(f)))}finally{b(!1),R.current=!1}}},[e,t,s,i,c,o]),L=r.useCallback(()=>{d([]),p(t),v(!0),a(!1),b(!1),g(!1),P.current.clear(),R.current=!1},[t]);return{items:S,loadNextPage:u,loadPrevPage:C,hasNextPage:E,hasPrevPage:z,loading:j,loadingMore:H,refresh:I,reset:L,currentPage:x}}exports.ChatVirtualList=se;exports.VirtualList=ne;exports.useChatVirtualizer=D;exports.usePagination=ie;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const M=require("react/jsx-runtime"),r=require("react");function $(o){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(o){for(const t in o)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(o,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>o[t]})}}return e.default=o,Object.freeze(e)}const D=$(r);class G{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 J{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 i=t.get(n);i!==void 0&&e.setSize(i,l)}}}function Q(o,e){if(o.length===0)return 0;let t=0,n=o.length-1;for(;t<n;){const l=t+n>>1;(o[l]??0)<e?t=l+1:n=l}return Math.max(0,t>0&&(o[t]??0)>e?t-1:t)}function W(o,e,t){if(o.length===0)return 0;for(let n=o.length-1;n>=0;n--)if((o[n]??0)<t)return n;return 0}function Z(o){const{firstVisible:e,lastVisible:t,itemCount:n,overscan:l}=o;return n===0?{start:0,end:-1}:{start:Math.max(0,e-l),end:Math.min(n-1,t+l)}}function ee(o,e,t){return r.useCallback((n,l)=>{var H;const i=o.current,c=e.current;if(!i||!c)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),c.count-1)),g=((H=t==null?void 0:t.current)==null?void 0:H.offsetTop)??0,z=c.getOffset(d),S=c.getSize(d),T=(f==null?void 0:f.align)??"start",h=(f==null?void 0:f.behavior)??"auto",F=(f==null?void 0:f.offset)??0;let y;T==="start"?y=g+z+F:T==="center"?y=g+z-i.clientHeight/2+S/2+F:y=g+z-i.clientHeight+S+F,i.scrollTo({top:Math.max(0,y),behavior:h})},[o,e,t])}function Y(o){var N;const{items:e,getKey:t,estimatedItemSize:n,overscan:l,initialAlignment:i}=o,c=r.useRef(null),m=r.useRef(null),f=r.useRef(null),d=r.useRef(new J),g=r.useRef([]),z=r.useRef(!1),S=r.useRef(0),T=r.useRef(0),h=r.useRef(null),F=r.useRef(!1),y=r.useRef(null),[,H]=r.useState(0),v=r.useCallback(()=>H(s=>s+1),[]);f.current||(f.current=new G(e.length,n));const P=r.useRef(e.length);if(e.length!==P.current||e.some((s,R)=>{const C=t(s,R);return g.current[R]!==C})){const s=f.current,R=e.map((O,q)=>t(O,q)),C=g.current,b=C.length,E=R.length;E===0?s.resize(0):b===0?s.resize(E):E>b?C.length>0&&R[E-b]===C[0]?s.prepend(E-b):s.resize(E):E<b&&s.resize(E);const k=new Map;R.forEach((O,q)=>k.set(O,q)),d.current.applyToOffsetMap(s,k),g.current=R,P.current=e.length}r.useEffect(()=>{const s=c.current;if(!s)return;const R=()=>{h.current===null&&(h.current=requestAnimationFrame(()=>{h.current=null,S.current=s.scrollTop,T.current=s.clientHeight,v()}))};return s.addEventListener("scroll",R,{passive:!0}),()=>{s.removeEventListener("scroll",R),h.current!==null&&(cancelAnimationFrame(h.current),h.current=null)}},[v]),r.useEffect(()=>{const s=c.current;if(!s)return;T.current=s.clientHeight,S.current=s.scrollTop;const R=new ResizeObserver(([C])=>{C&&(T.current=C.contentRect.height,v())});return R.observe(s),()=>R.disconnect()},[v]),r.useLayoutEffect(()=>{if(z.current||e.length===0)return;const s=c.current;if(s){if(i==="bottom"){s.scrollTop=s.scrollHeight,S.current=s.scrollTop,T.current=s.clientHeight,F.current=!0,y.current!==null&&cancelAnimationFrame(y.current);let R=0;const C=30,b=()=>{if(!F.current||R>=C){F.current=!1,y.current=null;return}R++,s.scrollTop=s.scrollHeight,S.current=s.scrollTop,y.current=requestAnimationFrame(b)};y.current=requestAnimationFrame(b)}z.current=!0}},[i,e.length]),r.useEffect(()=>{e.length===0&&(z.current=!1,F.current=!1,y.current!==null&&(cancelAnimationFrame(y.current),y.current=null))},[e.length]);const w=r.useCallback((s,R)=>{const C=f.current;if(!C||d.current.get(s)===R)return;d.current.set(s,R);const E=g.current.indexOf(s);if(E===-1)return;C.setSize(E,R)&&v()},[v]),L=r.useCallback((s,R="auto")=>{var C;(C=c.current)==null||C.scrollTo({top:s,behavior:R})},[]),B=ee(c,f,m),I=f.current,V=I?I.totalSize():0,a=c.current,u=(a==null?void 0:a.scrollTop)??S.current,p=(a==null?void 0:a.clientHeight)??T.current,x=((N=m.current)==null?void 0:N.offsetTop)??0,K=Math.max(0,u-x);let j=[];if(I&&I.count>0&&p>0){const s=I.getOffsets(),R=I.getSizes(),C=Q(s,K),b=W(s,R,K+p),E=Z({firstVisible:C,lastVisible:b,itemCount:I.count,overscan:l});for(let k=E.start;k<=E.end&&k<e.length;k++)j.push({key:g.current[k]??t(e[k],k),index:k,start:I.getOffset(k),size:I.getSize(k),data:e[k]})}else if(I&&I.count>0){const s=Math.min(e.length,l*2+1),R=i==="bottom"?Math.max(0,e.length-s):0,C=R+s-1;for(let b=R;b<=C;b++)j.push({key:g.current[b]??t(e[b],b),index:b,start:I.getOffset(b),size:I.getSize(b),data:e[b]})}const _=a?a.scrollHeight-a.scrollTop-a.clientHeight:1/0;return{scrollerRef:c,innerRef:m,virtualItems:j,totalSize:V,measureItem:w,scrollToIndex:B,scrollToOffset:L,isAtTop:u<=1,isAtBottom:_<=1,scrollTop:u}}function te(o,e,t){const n=r.useRef(0),l=r.useRef(0),i=r.useRef(!1),c=r.useRef({first:null,second:null,timeout:null}),m=r.useCallback(()=>{const{first:d,second:g,timeout:z}=c.current;d&&cancelAnimationFrame(d),g&&cancelAnimationFrame(g),z&&clearTimeout(z),c.current={first:null,second:null,timeout:null}},[]),f=r.useCallback(()=>{const d=o.current;d&&(n.current=d.scrollTop,l.current=d.scrollHeight,i.current=!0)},[o]);return r.useLayoutEffect(()=>{if(!i.current)return;const d=o.current;if(!d)return;i.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(),c.current.first=requestAnimationFrame(()=>{c.current.first=null,g(),c.current.second=requestAnimationFrame(()=>{c.current.second=null,g()})}),c.current.timeout=setTimeout(()=>{c.current.timeout=null,g()},90),()=>m()},[e,o,m,t]),{prepareAnchor:f}}function re(o,e){const[t,n]=r.useState(!0),l=r.useRef(null);return r.useEffect(()=>{const i=o.current;if(!i)return;const c=()=>{const f=i.scrollHeight-i.scrollTop-i.clientHeight;n(f<=e)},m=()=>{l.current!==null&&cancelAnimationFrame(l.current),l.current=requestAnimationFrame(c)};return i.addEventListener("scroll",m,{passive:!0}),c(),()=>{i.removeEventListener("scroll",m),l.current!==null&&cancelAnimationFrame(l.current)}},[o,e]),t}function ne(o){const{itemCount:e,firstKey:t,lastKey:n,isAtBottom:l,scrollToIndex:i,mode:c}=o,m=r.useRef(e),f=r.useRef(t),d=r.useRef(n);r.useLayoutEffect(()=>{if(!c){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&&i(e-1,{align:"end",behavior:c==="smooth"?"smooth":"auto"}),m.current=e,f.current=t,d.current=n},[e,t,n,l,i,c])}function U(o){const{items:e,getKey:t,estimatedItemSize:n=80,overscan:l=20,atBottomThreshold:i=200,followOutput:c="auto",initialAlignment:m="bottom",onStartReached:f,onEndReached:d,startReachedThreshold:g=300,endReachedThreshold:z=300,scrollToMessageKey:S,onScrollToMessageComplete:T}=o,h=Y({items:e,getKey:t,estimatedItemSize:n,overscan:l,initialAlignment:m}),F=re(h.scrollerRef,i),[,y]=r.useState(0),H=r.useCallback(()=>y(u=>u+1),[]),{prepareAnchor:v}=te(h.scrollerRef,e.length,H),P=e.length>0?t(e[0],0):null,A=e.length>0?t(e[e.length-1],e.length-1):null;ne({itemCount:e.length,firstKey:P,lastKey:A,isAtBottom:F,scrollToIndex:h.scrollToIndex,mode:c??!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 I=r.useRef(null);r.useEffect(()=>{if(!S||I.current===S)return;const u=e.findIndex((p,x)=>t(p,x)===S);u!==-1&&(I.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:F,prepareAnchor:v}}function X({virtualItem:o,measureItem:e,children:t}){const n=r.useRef(null),l=r.useRef(!1);return r.useEffect(()=>{const i=n.current;if(!i)return;l.current=!1;const c=new ResizeObserver(([m])=>{m&&(l.current=!0,e(o.key,m.contentRect.height))});return c.observe(i),()=>c.disconnect()},[o.key,e]),M.jsx("div",{ref:n,style:{position:"absolute",top:0,transform:`translateY(${o.start}px)`,width:"100%",minHeight:l.current?void 0:o.size},children:t})}function se(o,e){const{data:t,itemContent:n,computeItemKey:l,estimatedItemSize:i=80,overscan:c=20,followOutput:m="auto",atBottomThreshold:f=200,initialAlignment:d="bottom",onStartReached:g,onEndReached:z,startReachedThreshold:S=300,endReachedThreshold:T=300,scrollToMessageKey:h,onScrollToMessageComplete:F,onAtBottomChange:y,components:H={},className:v,style:P}=o,{scrollerRef:A,innerRef:w,virtualItems:L,totalSize:B,measureItem:I,scrollToIndex:V,scrollToBottom:a,scrollToKey:u,isAtBottom:p,prepareAnchor:x}=U({items:t,getKey:(s,R)=>l(R,s),estimatedItemSize:i,overscan:c,atBottomThreshold:f,followOutput:m,initialAlignment:d,onStartReached:g,onEndReached:z,startReachedThreshold:S,endReachedThreshold:T,scrollToMessageKey:h,onScrollToMessageComplete:F}),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 s;return((s=A.current)==null?void 0:s.scrollTop)??0},isAtBottom:()=>p,prepareAnchor:x}),[a,V,u,A,p,x]);const{Header:j,Footer:_,EmptyPlaceholder:N}=H;return t.length===0&&N?M.jsx(N,{}):M.jsxs("div",{ref:A,className:v,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...P},children:[j&&M.jsx(j,{}),M.jsx("div",{ref:w,style:{height:B,position:"relative",width:"100%"},children:L.map(s=>M.jsx(X,{virtualItem:s,measureItem:I,children:n(s.index,s.data)},s.key))}),_&&M.jsx(_,{})]})}const ce=r.forwardRef(se);function ie({data:o,itemContent:e,computeItemKey:t,estimatedItemSize:n=60,overscan:l=20,onEndReached:i,endReachedThreshold:c=300,components:m={},className:f,style:d}){const{scrollerRef:g,innerRef:z,virtualItems:S,totalSize:T,measureItem:h}=Y({items:o,getKey:(v,P)=>t(P,v),estimatedItemSize:n,overscan:l,initialAlignment:"top"});D.useEffect(()=>{const v=g.current;if(!v||!i)return;let P=!1;const A=()=>{v.scrollHeight-v.scrollTop-v.clientHeight<=c&&!P&&(P=!0,Promise.resolve(i()).finally(()=>{P=!1}))};return v.addEventListener("scroll",A,{passive:!0}),()=>v.removeEventListener("scroll",A)},[g,i,c]);const{Header:F,Footer:y,EmptyPlaceholder:H}=m;return o.length===0&&H?M.jsx(H,{}):M.jsxs("div",{ref:g,className:f,style:{overflow:"auto",height:"100%",position:"relative",overscrollBehaviorY:"contain",...d},children:[F&&M.jsx(F,{}),M.jsx("div",{ref:z,style:{height:T,position:"relative",width:"100%"},children:S.map(v=>M.jsx(X,{virtualItem:v,measureItem:h,children:e(v.index,v.data)},v.key))}),y&&M.jsx(y,{})]})}function oe(o){const{fetcher:e,initialPage:t=1,direction:n="append",getKey:l,onPageLoaded:i,onError:c}=o,[m,f]=r.useState([]),[d,g]=r.useState(t),[z,S]=r.useState(!0),[T,h]=r.useState(!1),[F,y]=r.useState(!1),[H,v]=r.useState(!1),P=r.useRef(new Set),A=r.useRef(!1),w=r.useCallback(a=>l?a.filter(u=>{const p=l(u);return P.current.has(p)?!1:(P.current.add(p),!0)}):a,[l]),L=r.useCallback(async()=>{if(!(A.current||!z)){A.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),i==null||i(a,p)}catch(a){c==null||c(a instanceof Error?a:new Error(String(a)))}finally{v(!1),A.current=!1}}},[d,z,e,w,n,i,c]),B=r.useCallback(async()=>{if(!(A.current||!T)){A.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),i==null||i(a,p)}catch(a){c==null||c(a instanceof Error?a:new Error(String(a)))}finally{v(!1),A.current=!1}}},[d,T,e,w,i,c]),I=r.useCallback(async()=>{if(!A.current){A.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=>P.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),i==null||i(t,u)}catch(a){c==null||c(a instanceof Error?a:new Error(String(a)))}finally{y(!1),A.current=!1}}},[e,t,l,n,i,c]),V=r.useCallback(()=>{f([]),g(t),S(!0),h(!1),y(!1),v(!1),P.current.clear(),A.current=!1},[t]);return{items:m,loadNextPage:L,loadPrevPage:B,hasNextPage:z,hasPrevPage:T,loading:F,loadingMore:H,refresh:I,reset:V,currentPage:d}}exports.ChatVirtualList=ce;exports.VirtualList=ie;exports.useChatVirtualizer=U;exports.usePagination=oe;