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 +79 -79
- package/dist/components/VirtualItem.d.ts.map +1 -1
- package/dist/hooks/useChatVirtualizer.d.ts.map +1 -1
- package/dist/hooks/useFollowOutput.d.ts +7 -3
- package/dist/hooks/useFollowOutput.d.ts.map +1 -1
- package/dist/hooks/useScrollAnchor.d.ts +5 -4
- package/dist/hooks/useScrollAnchor.d.ts.map +1 -1
- package/dist/hooks/useScrollToIndex.d.ts +6 -1
- package/dist/hooks/useScrollToIndex.d.ts.map +1 -1
- package/dist/hooks/useVirtualEngine.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +428 -378
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# react-anchorlist
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Lista virtualizada de alta performance para React, pensada para interfaces com muitos itens (como chat).**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
13
|
+
## O que é
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
//
|
|
69
|
+
// Paginação — dispara ao chegar no topo
|
|
56
70
|
onStartReached={loadOlderMessages}
|
|
57
|
-
//
|
|
71
|
+
// Mantém no final quando chegam novas mensagens
|
|
58
72
|
followOutput="auto"
|
|
59
|
-
//
|
|
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
|
|
76
|
-
| `itemContent` | `(index, item) => ReactNode` | required |
|
|
77
|
-
| `computeItemKey` | `(index, item) => string \| number` | required |
|
|
78
|
-
| `estimatedItemSize` | `number` | `80` |
|
|
79
|
-
| `overscan` | `number` | `5` |
|
|
80
|
-
| `followOutput` | `"auto" \| "smooth" \| false` | `"auto"` |
|
|
81
|
-
| `atBottomThreshold` | `number` | `200` | px
|
|
82
|
-
| `initialAlignment` | `"top" \| "bottom"` | `"bottom"` |
|
|
83
|
-
| `onStartReached` | `() => void \| Promise<void>` | — |
|
|
84
|
-
| `onEndReached` | `() => void \| Promise<void>` | — |
|
|
85
|
-
| `startReachedThreshold` | `number` | `300` | px
|
|
86
|
-
| `endReachedThreshold` | `number` | `300` | px
|
|
87
|
-
| `onAtBottomChange` | `(isAtBottom: boolean) => void` | — |
|
|
88
|
-
| `scrollToMessageKey` | `string \| number \| null` | — |
|
|
89
|
-
| `onScrollToMessageComplete` | `() => void` | — |
|
|
90
|
-
| `components` | `{ Header, Footer, EmptyPlaceholder }` | — |
|
|
91
|
-
| `className` | `string` | — | CSS
|
|
92
|
-
| `style` | `CSSProperties` | — |
|
|
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
|
|
101
|
-
| `itemContent` | `(index, item) => ReactNode` | required |
|
|
102
|
-
| `computeItemKey` | `(index, item) => string \| number` | required |
|
|
103
|
-
| `estimatedItemSize` | `number` | `80` |
|
|
104
|
-
| `overscan` | `number` | `5` |
|
|
105
|
-
| `onEndReached` | `() => void \| Promise<void>` | — |
|
|
106
|
-
| `endReachedThreshold` | `number` | `300` | px
|
|
107
|
-
| `components` | `{ Header, Footer, EmptyPlaceholder }` | — |
|
|
108
|
-
| `className` | `string` | — | CSS
|
|
109
|
-
| `style` | `CSSProperties` | — |
|
|
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() //
|
|
137
|
+
listRef.current?.prepareAnchor() // chame antes de prepend manual
|
|
124
138
|
```
|
|
125
139
|
|
|
126
140
|
| Method | Description |
|
|
127
141
|
|---|---|
|
|
128
|
-
| `scrollToBottom(behavior?)` |
|
|
129
|
-
| `scrollToIndex(index, opts?)` |
|
|
130
|
-
| `scrollToKey(key, opts?)` |
|
|
131
|
-
| `getScrollTop()` |
|
|
132
|
-
| `isAtBottom()` |
|
|
133
|
-
| `prepareAnchor()` |
|
|
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
|
-
|
|
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',
|
|
155
|
-
getKey: (msg) => msg._id, //
|
|
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
|
-
##
|
|
185
|
+
## Boas práticas
|
|
172
186
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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 só aumente se houver necessidade real
|
|
189
192
|
|
|
190
193
|
---
|
|
191
194
|
|
|
192
|
-
##
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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,
|
|
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;
|