ropav 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/components/autocomplete/autocomplete-root.js +3 -1
  2. package/dist/components/autocomplete/index.d.ts +3 -0
  3. package/dist/components/avatar/avatar-image.js +13 -2
  4. package/dist/components/avatar/avatar.types.d.ts +7 -1
  5. package/dist/components/breadcrumbs/breadcrumbs-item.js +1 -0
  6. package/dist/components/breadcrumbs/index.d.ts +1 -0
  7. package/dist/components/calendar/calendar-header.js +1 -1
  8. package/dist/components/calendar/index.d.ts +5 -3
  9. package/dist/components/calendar-year-picker/index.d.ts +4 -2
  10. package/dist/components/checkbox/checkbox-content.js +1 -1
  11. package/dist/components/color-field/index.d.ts +3 -2
  12. package/dist/components/color-input-group/color-input-group-root.js +2 -0
  13. package/dist/components/color-input-group/color-input-group.types.d.ts +2 -0
  14. package/dist/components/color-input-group/index.d.ts +2 -0
  15. package/dist/components/combo-box/combo-box-root.js +9 -2
  16. package/dist/components/combo-box/combo-box.types.d.ts +3 -0
  17. package/dist/components/combo-box/index.d.ts +3 -0
  18. package/dist/components/date-field/index.d.ts +1 -0
  19. package/dist/components/date-input-group/date-input-group-root.js +2 -0
  20. package/dist/components/date-input-group/date-input-group-segment.js +1 -1
  21. package/dist/components/date-input-group/date-input-group.types.d.ts +2 -0
  22. package/dist/{composables → components/date-input-group}/use-date-segment.d.ts +2 -2
  23. package/dist/{composables → components/date-input-group}/use-date-segment.js +9 -9
  24. package/dist/components/date-picker/index.d.ts +3 -2
  25. package/dist/components/date-picker/index.js +2 -2
  26. package/dist/components/date-range-picker/date-range-picker-root.js +1 -1
  27. package/dist/components/date-range-picker/index.d.ts +18 -16
  28. package/dist/components/date-range-picker/index.js +2 -2
  29. package/dist/{composables → components/date-range-picker}/use-date-range-picker.d.ts +7 -7
  30. package/dist/{composables → components/date-range-picker}/use-date-range-picker.js +10 -10
  31. package/dist/components/drawer/drawer-dialog.js +1 -1
  32. package/dist/{composables → components/drawer}/use-drawer-drag.d.ts +1 -1
  33. package/dist/{composables → components/drawer}/use-drawer-drag.js +1 -1
  34. package/dist/components/dropdown/dropdown-menu.js +3 -1
  35. package/dist/components/dropdown/dropdown-submenu-trigger.js +1 -1
  36. package/dist/components/dropdown/dropdown.types.d.ts +3 -3
  37. package/dist/components/dropdown/index.d.ts +4 -2
  38. package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.js +1 -1
  39. package/dist/components/icons/icon-overlay-arrow.js +1 -1
  40. package/dist/components/index.d.ts +5 -0
  41. package/dist/components/input/index.d.ts +12 -4
  42. package/dist/components/input/input-root.d.ts +5 -2
  43. package/dist/components/input/input-root.js +46 -5
  44. package/dist/components/input/input.types.d.ts +11 -0
  45. package/dist/components/input-group/index.d.ts +2 -0
  46. package/dist/components/input-group/input-group-root.js +3 -0
  47. package/dist/components/input-group/input-group.types.d.ts +5 -0
  48. package/dist/components/input-otp/index.d.ts +6 -4
  49. package/dist/components/link/index.d.ts +2 -0
  50. package/dist/components/link/link-root.js +22 -5
  51. package/dist/components/link/link.types.d.ts +14 -3
  52. package/dist/{composables → components/list-box}/drag-collection.d.ts +2 -2
  53. package/dist/{composables → components/list-box}/drag-collection.js +1 -1
  54. package/dist/components/list-box/index.d.ts +4 -0
  55. package/dist/components/list-box/list-box-root.js +60 -35
  56. package/dist/components/list-box/list-box.types.d.ts +2 -0
  57. package/dist/components/menu/index.d.ts +4 -0
  58. package/dist/components/menu/menu-root.js +9 -2
  59. package/dist/components/menu/menu.types.d.ts +5 -0
  60. package/dist/components/number-field/index.d.ts +3 -0
  61. package/dist/components/number-field/number-field-root.js +2 -0
  62. package/dist/components/number-field/number-field.types.d.ts +2 -0
  63. package/dist/components/overlay/overlay-arrow.js +64 -11
  64. package/dist/components/pagination/index.d.ts +2 -0
  65. package/dist/components/pagination/pagination-root.js +8 -3
  66. package/dist/components/pagination/pagination.types.d.ts +8 -0
  67. package/dist/components/popover/popover-trigger.js +14 -7
  68. package/dist/components/radio/radio-content.js +1 -1
  69. package/dist/components/range-calendar/index.d.ts +18 -15
  70. package/dist/components/range-calendar/range-calendar-header.js +1 -1
  71. package/dist/components/router-provider/index.d.ts +5 -0
  72. package/dist/components/router-provider/index.js +3 -0
  73. package/dist/components/router-provider/router-provider.context.d.ts +40 -0
  74. package/dist/components/router-provider/router-provider.context.js +16 -0
  75. package/dist/components/router-provider/router-provider.d.ts +21 -0
  76. package/dist/components/router-provider/router-provider.js +22 -0
  77. package/dist/components/router-provider/router-provider.types.d.ts +23 -0
  78. package/dist/components/scroll-shadow/scroll-shadow-root.js +7 -1
  79. package/dist/components/scroll-shadow/use-scroll-shadow.js +25 -1
  80. package/dist/components/search-field/index.d.ts +2 -0
  81. package/dist/components/search-field/search-field-root.js +3 -1
  82. package/dist/components/search-field/search-field.types.d.ts +2 -0
  83. package/dist/{composables → components/search-field}/use-search-field.d.ts +2 -2
  84. package/dist/{composables → components/search-field}/use-search-field.js +3 -3
  85. package/dist/components/segmented-control/index.d.ts +134 -0
  86. package/dist/components/segmented-control/index.js +15 -0
  87. package/dist/components/segmented-control/segmented-control-indicator.d.ts +19 -0
  88. package/dist/components/segmented-control/segmented-control-indicator.js +36 -0
  89. package/dist/components/segmented-control/segmented-control-item.d.ts +21 -0
  90. package/dist/components/segmented-control/segmented-control-item.js +96 -0
  91. package/dist/components/segmented-control/segmented-control-root.d.ts +25 -0
  92. package/dist/components/segmented-control/segmented-control-root.js +112 -0
  93. package/dist/components/segmented-control/segmented-control-separator.d.ts +19 -0
  94. package/dist/components/segmented-control/segmented-control-separator.js +18 -0
  95. package/dist/components/segmented-control/segmented-control.context.d.ts +17 -0
  96. package/dist/components/segmented-control/segmented-control.context.js +12 -0
  97. package/dist/components/segmented-control/segmented-control.types.d.ts +67 -0
  98. package/dist/components/select/index.d.ts +3 -0
  99. package/dist/components/select/select-hidden-select.js +1 -1
  100. package/dist/components/select/select-root.js +2 -0
  101. package/dist/components/select/select.types.d.ts +2 -0
  102. package/dist/components/separator/separator-root.js +11 -5
  103. package/dist/components/separator/separator.context.d.ts +7 -0
  104. package/dist/components/sidebar/index.d.ts +544 -0
  105. package/dist/components/sidebar/index.js +47 -0
  106. package/dist/components/sidebar/sidebar-collapsible-trigger.d.ts +21 -0
  107. package/dist/components/sidebar/sidebar-collapsible-trigger.js +70 -0
  108. package/dist/components/sidebar/sidebar-collapsible.d.ts +24 -0
  109. package/dist/components/sidebar/sidebar-collapsible.js +78 -0
  110. package/dist/components/sidebar/sidebar-content.d.ts +21 -0
  111. package/dist/components/sidebar/sidebar-content.js +37 -0
  112. package/dist/components/sidebar/sidebar-footer.d.ts +21 -0
  113. package/dist/components/sidebar/sidebar-footer.js +24 -0
  114. package/dist/components/sidebar/sidebar-group-label.d.ts +21 -0
  115. package/dist/components/sidebar/sidebar-group-label.js +32 -0
  116. package/dist/components/sidebar/sidebar-group.d.ts +21 -0
  117. package/dist/components/sidebar/sidebar-group.js +48 -0
  118. package/dist/components/sidebar/sidebar-header.d.ts +21 -0
  119. package/dist/components/sidebar/sidebar-header.js +24 -0
  120. package/dist/components/sidebar/sidebar-inset.d.ts +21 -0
  121. package/dist/components/sidebar/sidebar-inset.js +24 -0
  122. package/dist/components/sidebar/sidebar-item-icon.d.ts +21 -0
  123. package/dist/components/sidebar/sidebar-item-icon.js +20 -0
  124. package/dist/components/sidebar/sidebar-item-indicator.d.ts +21 -0
  125. package/dist/components/sidebar/sidebar-item-indicator.js +48 -0
  126. package/dist/components/sidebar/sidebar-item-label.d.ts +21 -0
  127. package/dist/components/sidebar/sidebar-item-label.js +28 -0
  128. package/dist/components/sidebar/sidebar-item-tooltip.d.ts +21 -0
  129. package/dist/components/sidebar/sidebar-item-tooltip.js +38 -0
  130. package/dist/components/sidebar/sidebar-item-trailing.d.ts +21 -0
  131. package/dist/components/sidebar/sidebar-item-trailing.js +28 -0
  132. package/dist/components/sidebar/sidebar-item.d.ts +23 -0
  133. package/dist/components/sidebar/sidebar-item.js +160 -0
  134. package/dist/components/sidebar/sidebar-panel.d.ts +21 -0
  135. package/dist/components/sidebar/sidebar-panel.js +88 -0
  136. package/dist/components/sidebar/sidebar-rail.d.ts +21 -0
  137. package/dist/components/sidebar/sidebar-rail.js +210 -0
  138. package/dist/components/sidebar/sidebar-root.d.ts +31 -0
  139. package/dist/components/sidebar/sidebar-root.js +138 -0
  140. package/dist/components/sidebar/sidebar-separator.d.ts +19 -0
  141. package/dist/components/sidebar/sidebar-separator.js +16 -0
  142. package/dist/components/sidebar/sidebar-sub-menu.d.ts +23 -0
  143. package/dist/components/sidebar/sidebar-sub-menu.js +40 -0
  144. package/dist/components/sidebar/sidebar-trigger.d.ts +21 -0
  145. package/dist/components/sidebar/sidebar-trigger.js +69 -0
  146. package/dist/components/sidebar/sidebar.context.d.ts +75 -0
  147. package/dist/components/sidebar/sidebar.context.js +30 -0
  148. package/dist/components/sidebar/sidebar.state.d.ts +73 -0
  149. package/dist/components/sidebar/sidebar.state.js +76 -0
  150. package/dist/components/sidebar/sidebar.storage.d.ts +8 -0
  151. package/dist/components/sidebar/sidebar.storage.js +45 -0
  152. package/dist/components/sidebar/sidebar.types.d.ts +259 -0
  153. package/dist/components/splitter/index.d.ts +143 -0
  154. package/dist/components/splitter/index.js +13 -0
  155. package/dist/components/splitter/splitter-handle.d.ts +21 -0
  156. package/dist/components/splitter/splitter-handle.js +209 -0
  157. package/dist/components/splitter/splitter-panel.d.ts +21 -0
  158. package/dist/components/splitter/splitter-panel.js +70 -0
  159. package/dist/components/splitter/splitter-root.d.ts +31 -0
  160. package/dist/components/splitter/splitter-root.js +152 -0
  161. package/dist/components/splitter/splitter.context.d.ts +15 -0
  162. package/dist/components/splitter/splitter.context.js +5 -0
  163. package/dist/components/splitter/splitter.state.d.ts +87 -0
  164. package/dist/components/splitter/splitter.state.js +322 -0
  165. package/dist/components/splitter/splitter.storage.d.ts +15 -0
  166. package/dist/components/splitter/splitter.storage.js +56 -0
  167. package/dist/components/splitter/splitter.types.d.ts +98 -0
  168. package/dist/components/switch/switch-content.js +1 -1
  169. package/dist/components/table/index.d.ts +10 -9
  170. package/dist/components/table/table-body.js +13 -13
  171. package/dist/components/table/table-cell.js +49 -46
  172. package/dist/components/table/table-column-resizer.js +1 -1
  173. package/dist/components/table/table-column.js +1 -1
  174. package/dist/components/table/table-content.js +37 -18
  175. package/dist/{composables → components/table}/table-drag-collection.d.ts +1 -1
  176. package/dist/{composables → components/table}/table-drag-collection.js +1 -1
  177. package/dist/components/table/table-drop-indicator.js +3 -1
  178. package/dist/components/table/table-resizable-container.d.ts +4 -5
  179. package/dist/components/table/table-row.js +45 -14
  180. package/dist/components/table/table-virtualizer-item.d.ts +2 -7
  181. package/dist/components/table/table-virtualizer-item.js +20 -9
  182. package/dist/components/table/table.context.d.ts +11 -3
  183. package/dist/components/table/table.types.d.ts +1 -1
  184. package/dist/{composables → components/table}/use-grid-keyboard.d.ts +3 -3
  185. package/dist/{composables → components/table}/use-grid-keyboard.js +57 -55
  186. package/dist/{composables → components/table}/use-grid-selection-announcement.d.ts +2 -2
  187. package/dist/{composables → components/table}/use-grid-selection-announcement.js +2 -2
  188. package/dist/{composables → components/table}/use-table-collection.d.ts +2 -2
  189. package/dist/{composables → components/table}/use-table-collection.js +4 -4
  190. package/dist/{composables → components/table}/use-table-column-layout.d.ts +13 -20
  191. package/dist/components/table/use-table-column-layout.js +115 -0
  192. package/dist/components/tabs/tabs-panel.js +5 -1
  193. package/dist/components/tabs/tabs-tab.js +1 -1
  194. package/dist/components/textarea/index.d.ts +12 -4
  195. package/dist/components/textarea/textarea-root.d.ts +5 -2
  196. package/dist/components/textarea/textarea-root.js +40 -5
  197. package/dist/components/textarea/textarea.types.d.ts +11 -0
  198. package/dist/components/textfield/index.d.ts +2 -0
  199. package/dist/components/textfield/textfield-root.js +5 -1
  200. package/dist/components/textfield/textfield.context.d.ts +2 -0
  201. package/dist/components/textfield/textfield.types.d.ts +5 -0
  202. package/dist/components/time-field/index.d.ts +1 -0
  203. package/dist/components/toast/index.d.ts +1 -0
  204. package/dist/components/toast/toast-default-content.js +1 -1
  205. package/dist/components/toggle-button-group/toggle-button-group-root.js +3 -3
  206. package/dist/components/toolbar/index.d.ts +3 -2
  207. package/dist/components/toolbar/toolbar-root.js +3 -3
  208. package/dist/components/tooltip/index.js +1 -1
  209. package/dist/components/tooltip/tooltip-root.js +1 -1
  210. package/dist/components/tooltip/tooltip-trigger.js +13 -5
  211. package/dist/components/virtualizer/index.d.ts +3 -1
  212. package/dist/components/virtualizer/index.js +2 -1
  213. package/dist/components/virtualizer/use-virtualizer-item.d.ts +23 -0
  214. package/dist/components/virtualizer/use-virtualizer-item.js +45 -0
  215. package/dist/components/virtualizer/virtualizer-item.js +7 -41
  216. package/dist/components/virtualizer/virtualizer-scrollbar-track.d.ts +20 -0
  217. package/dist/components/virtualizer/virtualizer-scrollbar-track.js +141 -0
  218. package/dist/components/virtualizer/virtualizer-scrollbar.d.ts +18 -0
  219. package/dist/components/virtualizer/virtualizer-scrollbar.js +55 -0
  220. package/dist/components/virtualizer/virtualizer.context.d.ts +17 -1
  221. package/dist/composables/drag-manager.js +1 -13
  222. package/dist/composables/index.d.ts +3 -71
  223. package/dist/composables/use-collection.d.ts +22 -3
  224. package/dist/composables/use-collection.js +67 -14
  225. package/dist/composables/use-color-area.js +2 -2
  226. package/dist/composables/use-color-field.js +1 -1
  227. package/dist/composables/use-combo-box.js +1 -1
  228. package/dist/composables/use-date-field.js +1 -1
  229. package/dist/composables/use-drag-and-drop.js +1 -1
  230. package/dist/composables/use-drag.d.ts +6 -5
  231. package/dist/composables/use-draggable-item.d.ts +2 -2
  232. package/dist/composables/use-droppable-collection.js +2 -2
  233. package/dist/composables/use-droppable-item.js +1 -1
  234. package/dist/composables/use-form-reset.d.ts +6 -2
  235. package/dist/composables/use-form-reset.js +9 -5
  236. package/dist/composables/use-list-keyboard.js +64 -62
  237. package/dist/composables/use-menu.d.ts +10 -0
  238. package/dist/composables/use-menu.js +22 -0
  239. package/dist/composables/use-number-field.js +1 -1
  240. package/dist/composables/use-select.js +2 -3
  241. package/dist/composables/use-selection-manager.js +4 -4
  242. package/dist/composables/use-single-select-list-state.d.ts +37 -0
  243. package/dist/composables/use-single-select-list-state.js +110 -0
  244. package/dist/composables/use-slider-thumb.js +2 -2
  245. package/dist/composables/use-slider.js +1 -1
  246. package/dist/composables/use-tab-list-state.d.ts +20 -29
  247. package/dist/composables/use-tab-list-state.js +20 -99
  248. package/dist/composables/use-text-field.js +1 -1
  249. package/dist/composables/use-virtualizer-scroll.d.ts +52 -2
  250. package/dist/composables/use-virtualizer-scroll.js +79 -13
  251. package/dist/composables/use-virtualizer.d.ts +24 -4
  252. package/dist/composables/use-virtualizer.js +40 -6
  253. package/dist/index.js +58 -80
  254. package/dist/utils/document-order.d.ts +14 -0
  255. package/dist/utils/document-order.js +17 -0
  256. package/dist/utils/flex-sizing.d.ts +41 -0
  257. package/dist/utils/flex-sizing.js +140 -0
  258. package/dist/utils/index.d.ts +2 -0
  259. package/dist/utils/open-link.d.ts +31 -0
  260. package/dist/utils/open-link.js +32 -0
  261. package/dist/utils/story-truncation.d.ts +20 -0
  262. package/dist/utils/story-users.d.ts +11 -0
  263. package/dist/utils/virtualizer-collection.d.ts +14 -4
  264. package/dist/utils/virtualizer-collection.js +9 -14
  265. package/dist/utils/virtualizer-layout-info.js +3 -2
  266. package/dist/utils/virtualizer-list-layout.d.ts +75 -29
  267. package/dist/utils/virtualizer-list-layout.js +182 -99
  268. package/dist/utils/virtualizer-offsets.d.ts +111 -0
  269. package/dist/utils/virtualizer-offsets.js +159 -0
  270. package/dist/utils/virtualizer-overscan.d.ts +9 -15
  271. package/dist/utils/virtualizer-overscan.js +14 -33
  272. package/dist/utils/virtualizer-table-layout.d.ts +50 -21
  273. package/dist/utils/virtualizer-table-layout.js +130 -89
  274. package/dist/version.js +1 -1
  275. package/package.json +19 -3
  276. package/dist/composables/use-table-column-layout.js +0 -214
  277. /package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.d.ts +0 -0
@@ -1,6 +1,7 @@
1
1
  import { Rect, Size } from "./virtualizer-geometry.js";
2
2
  import { LayoutInfo } from "./virtualizer-layout-info.js";
3
3
  import { Layout } from "./virtualizer-layout.js";
4
+ import { RowOffsets } from "./virtualizer-offsets.js";
4
5
  //#region src/utils/virtualizer-list-layout.ts
5
6
  /**
6
7
  * A stack layout for the virtualizer, ported from React Aria's `ListLayout`.
@@ -9,14 +10,20 @@ import { Layout } from "./virtualizer-layout.js";
9
10
  * minus the padding. With `rowSize` given the geometry is arithmetic; without it each item is
10
11
  * placed at an estimate and corrected once it has been measured.
11
12
  *
12
- * The layout is lazy. It computes rows for the region it has been asked about `requestedRect`
13
- * and accounts for everything outside it arithmetically, so a thousand rows cost about as much as
14
- * a screenful. `validRect` records how much of that region is known-good, which is what lets a
15
- * measured row survive the next pass instead of being placed at an estimate again.
13
+ * Only the rows a window covers are built, and this is where it parts company with upstream. React
14
+ * Aria records a region it has computed growing it to cover everywhere the window has been, and
15
+ * walking from the first row to add up offsets on the way — which makes the rendered set a
16
+ * function of where the window has *been* rather than of where it is, and makes a pass cost more
17
+ * the further down the collection it lands.
16
18
  *
17
- * Two things are deliberately absent, both recorded as debt: the horizontal orientation, which no
18
- * collection in this library uses, and the `section`/`header`/`separator` node types, which cannot
19
- * reach a layout through the data-driven collection this build has.
19
+ * Here a {@link RowOffsets} index holds a start and a size for every row, measured or estimated,
20
+ * so an offset is a lookup and a window is a binary search. There is no region to be inside of, no
21
+ * cache that can disagree with the current scroll offset, and no row a scrollbar can reach that
22
+ * has no offset to answer with — which is what keeps a fast drag from landing on nothing.
23
+ *
24
+ * Two things are absent, both recorded as debt: the horizontal orientation, which no collection in
25
+ * this library uses, and the `section`/`header`/`separator` node types, which cannot reach a
26
+ * layout through the data-driven collection this build has.
20
27
  */
21
28
  /** The height React Aria falls back to when neither a fixed nor an estimated size is given. */
22
29
  var DEFAULT_ROW_SIZE = 48;
@@ -32,19 +39,19 @@ var ListLayout = class extends Layout {
32
39
  layoutNodes = /* @__PURE__ */ new Map();
33
40
  rootNodes = [];
34
41
  contentSize = new Size();
35
- /** The region rows have been computed for. Everything outside it is only accounted for. */
36
- requestedRect = new Rect();
37
- /** The part of `requestedRect` whose rows are known-good. */
38
- validRect = new Rect();
42
+ /** Where every root sits, whether or not it has ever been built. */
43
+ offsets = new RowOffsets();
44
+ /** The window the rendered set was built for, in content coordinates. */
45
+ window = new Rect();
39
46
  lastCollection = null;
40
47
  invalidateEverything = false;
41
48
  constructor(options = {}) {
42
49
  super();
43
- this.rowSize = options.rowSize ?? options.rowHeight ?? null;
44
- this.estimatedRowSize = options.estimatedRowSize ?? options.estimatedRowHeight ?? null;
45
- this.headingSize = options.headingSize ?? options.headingHeight ?? null;
46
- this.estimatedHeadingSize = options.estimatedHeadingSize ?? options.estimatedHeadingHeight ?? null;
47
- this.loaderSize = options.loaderSize ?? options.loaderHeight ?? null;
50
+ this.rowSize = options.rowSize ?? null;
51
+ this.estimatedRowSize = options.estimatedRowSize ?? null;
52
+ this.headingSize = options.headingSize ?? null;
53
+ this.estimatedHeadingSize = options.estimatedHeadingSize ?? null;
54
+ this.loaderSize = options.loaderSize ?? null;
48
55
  this.gap = options.gap ?? 0;
49
56
  this.padding = options.padding ?? 0;
50
57
  this.dropIndicatorThickness = options.dropIndicatorThickness ?? 2;
@@ -56,12 +63,20 @@ var ListLayout = class extends Layout {
56
63
  this.ensureLayoutInfo(key);
57
64
  return this.layoutNodes.get(key)?.layoutInfo ?? null;
58
65
  }
66
+ /** Where every root sits, for a caller that needs an offset rather than a rendered node. */
67
+ getRowOffsets() {
68
+ return this.offsets;
69
+ }
59
70
  /**
60
71
  * The rectangle grown to whole rows.
61
72
  *
62
73
  * Without this a one-pixel scroll would change which rows fall inside the rectangle, and the set
63
- * of rendered elements would churn on every frame. A table snaps differently — its own row
64
- * stride ignores the gap — so this is the one part of the walk a subclass replaces.
74
+ * of rendered elements would churn on every frame.
75
+ *
76
+ * The stride has to be the one rows are actually placed at, gap included, and the height has to
77
+ * absorb however far the top edge moved. Upstream's table snaps by the bare row height and lets
78
+ * the bottom edge fall wherever it lands, which costs a gap of coverage per screen — so a window
79
+ * arrived at by scrolling ends one gap short of where it was asked to reach.
65
80
  */
66
81
  snapVisibleRect(rect) {
67
82
  if (rect.height <= 1) return rect;
@@ -76,7 +91,7 @@ var ListLayout = class extends Layout {
76
91
  /** Every layout info inside `rect`, snapped to whole rows, parents before children. */
77
92
  getVisibleLayoutInfos(rect) {
78
93
  const searchRect = this.snapVisibleRect(rect);
79
- this.layoutIfNeeded(searchRect);
94
+ this.layoutFor(searchRect);
80
95
  const result = [];
81
96
  const addNodes = (nodes) => {
82
97
  for (const node of nodes) {
@@ -92,47 +107,47 @@ var ListLayout = class extends Layout {
92
107
  const collection = this.host.collection;
93
108
  this.invalidateEverything = this.shouldInvalidateEverything(context);
94
109
  if (this.invalidateEverything) {
95
- this.requestedRect = this.host.visibleRect.copy();
96
110
  this.layoutNodes.clear();
111
+ this.offsets.reset();
97
112
  }
98
113
  const options = context.layoutOptions;
99
- this.rowSize = options?.rowSize ?? options?.rowHeight ?? this.rowSize;
100
- this.estimatedRowSize = options?.estimatedRowSize ?? options?.estimatedRowHeight ?? this.estimatedRowSize;
101
- this.headingSize = options?.headingSize ?? options?.headingHeight ?? this.headingSize;
102
- this.estimatedHeadingSize = options?.estimatedHeadingSize ?? options?.estimatedHeadingHeight ?? this.estimatedHeadingSize;
103
- this.loaderSize = options?.loaderSize ?? options?.loaderHeight ?? this.loaderSize;
114
+ this.rowSize = options?.rowSize ?? this.rowSize;
115
+ this.estimatedRowSize = options?.estimatedRowSize ?? this.estimatedRowSize;
116
+ this.headingSize = options?.headingSize ?? this.headingSize;
117
+ this.estimatedHeadingSize = options?.estimatedHeadingSize ?? this.estimatedHeadingSize;
118
+ this.loaderSize = options?.loaderSize ?? this.loaderSize;
104
119
  this.gap = options?.gap ?? this.gap;
105
120
  this.padding = options?.padding ?? this.padding;
106
121
  this.dropIndicatorThickness = options?.dropIndicatorThickness ?? this.dropIndicatorThickness;
122
+ if (this.window.area <= 0) this.window = this.host.visibleRect.copy();
107
123
  this.rootNodes = this.buildCollection();
108
- if (this.lastCollection && collection !== this.lastCollection) {
109
- for (const key of this.lastCollection.keys) if (!collection.getNode(key)) this.layoutNodes.delete(key);
124
+ if (this.lastCollection && collection !== this.lastCollection) for (const [key, layoutNode] of this.layoutNodes) {
125
+ const owner = layoutNode.layoutInfo.type === "cell" ? layoutNode.layoutInfo.parentKey : key;
126
+ if (owner == null || !collection.getNode(owner)) this.layoutNodes.delete(key);
110
127
  }
111
128
  this.lastCollection = collection;
112
129
  this.invalidateEverything = false;
113
- this.validRect = this.requestedRect.copy();
114
130
  }
115
131
  shouldInvalidateLayoutOptions(newOptions, oldOptions) {
116
- return (newOptions?.rowSize ?? newOptions?.rowHeight) !== (oldOptions?.rowSize ?? oldOptions?.rowHeight) || (newOptions?.estimatedRowSize ?? newOptions?.estimatedRowHeight) !== (oldOptions?.estimatedRowSize ?? oldOptions?.estimatedRowHeight) || (newOptions?.headingSize ?? newOptions?.headingHeight) !== (oldOptions?.headingSize ?? oldOptions?.headingHeight) || (newOptions?.estimatedHeadingSize ?? newOptions?.estimatedHeadingHeight) !== (oldOptions?.estimatedHeadingSize ?? oldOptions?.estimatedHeadingHeight) || (newOptions?.loaderSize ?? newOptions?.loaderHeight) !== (oldOptions?.loaderSize ?? oldOptions?.loaderHeight) || newOptions?.gap !== oldOptions?.gap || newOptions?.padding !== oldOptions?.padding;
132
+ return newOptions?.rowSize !== oldOptions?.rowSize || newOptions?.estimatedRowSize !== oldOptions?.estimatedRowSize || newOptions?.headingSize !== oldOptions?.headingSize || newOptions?.estimatedHeadingSize !== oldOptions?.estimatedHeadingSize || newOptions?.loaderSize !== oldOptions?.loaderSize || newOptions?.gap !== oldOptions?.gap || newOptions?.padding !== oldOptions?.padding;
117
133
  }
118
134
  /**
119
135
  * Records a row's measured size.
120
136
  *
121
- * Returns whether anything moved, which is the virtualizer's cue to lay out again. The rows
122
- * below this one are the ones that moved, so the valid region is cut off at this row's offset
123
- * rather than thrown away entirely.
137
+ * Returns whether anything moved, which is the virtualizer's cue to lay out again. The size goes
138
+ * to the index rather than only onto the rendered node, so the rows below it move even though
139
+ * none of them is in the window, and so the row keeps its height when the window comes back.
124
140
  */
125
141
  updateItemSize(key, size) {
126
142
  const layoutNode = this.layoutNodes.get(key);
127
- if (!layoutNode) return false;
143
+ if (!layoutNode || this.rowSize != null) return false;
128
144
  const layoutInfo = layoutNode.layoutInfo;
129
145
  layoutInfo.estimatedSize = false;
130
146
  if (layoutInfo.rect.height === size.height) return false;
131
147
  const measured = layoutInfo.copy();
132
148
  measured.rect.height = size.height;
133
149
  layoutNode.layoutInfo = measured;
134
- this.validRect.height = Math.min(this.validRect.height, layoutInfo.rect.y - this.validRect.y);
135
- if (layoutNode.node?.type === "item" || layoutNode.node?.type === "row") this.requestedRect.height += measured.rect.height - layoutInfo.rect.height;
150
+ this.recordMeasuredSize(layoutNode, size.height);
136
151
  this.replaceLayoutInfo(key, layoutInfo, measured);
137
152
  let parentKey = layoutInfo.parentKey;
138
153
  while (parentKey != null) {
@@ -141,36 +156,138 @@ var ListLayout = class extends Layout {
141
156
  }
142
157
  return true;
143
158
  }
159
+ /**
160
+ * Hands a measurement to the index, when it is a root that the index places.
161
+ *
162
+ * A table's cell is measured too, but a cell does not sit among the roots — its row does, and
163
+ * the row is measured by its tallest cell, so the row's own measurement is the one that lands.
164
+ */
165
+ recordMeasuredSize(layoutNode, height) {
166
+ const node = layoutNode.node;
167
+ if (!node || node.parentKey != null) return;
168
+ this.offsets.measure(node.key, height, node.index);
169
+ }
144
170
  shouldInvalidateEverything(context) {
145
171
  const options = context.layoutOptions;
146
- return Boolean(context.sizeChanged || this.rowSize !== (options?.rowSize ?? options?.rowHeight ?? this.rowSize) || this.headingSize !== (options?.headingSize ?? options?.headingHeight ?? this.headingSize) || this.loaderSize !== (options?.loaderSize ?? options?.loaderHeight ?? this.loaderSize) || this.gap !== (options?.gap ?? this.gap) || this.padding !== (options?.padding ?? this.padding));
172
+ return Boolean(context.sizeChanged || this.rowSize !== (options?.rowSize ?? this.rowSize) || this.headingSize !== (options?.headingSize ?? this.headingSize) || this.loaderSize !== (options?.loaderSize ?? this.loaderSize) || this.gap !== (options?.gap ?? this.gap) || this.padding !== (options?.padding ?? this.padding));
173
+ }
174
+ /**
175
+ * The estimated size of a root, which is what the index places it at until it is measured.
176
+ */
177
+ estimatedSizeAt(index) {
178
+ if (this.host.collection.getNode(this.host.collection.rootKeys[index])?.type === "loader") return this.loaderSize ?? this.rowSize ?? this.estimatedRowSize ?? DEFAULT_ROW_SIZE;
179
+ return this.rowSize ?? this.estimatedRowSize ?? DEFAULT_ROW_SIZE;
180
+ }
181
+ /** How many things the index places. The roots for a list; the body's children for a table. */
182
+ get placedCount() {
183
+ return this.host.collection.rootKeys.length;
184
+ }
185
+ /** Points the index at the collection as it stands, which is what makes every offset answerable. */
186
+ configureOffsets(start = this.padding) {
187
+ const collection = this.host.collection;
188
+ const rootKeys = collection.rootKeys;
189
+ this.offsets.configure({
190
+ collection,
191
+ count: rootKeys.length,
192
+ estimate: (index) => this.estimatedSizeAt(index),
193
+ gap: this.gap,
194
+ keyAt: (index) => rootKeys[index],
195
+ start
196
+ });
197
+ }
198
+ /**
199
+ * The roots that are rendered wherever the window is.
200
+ *
201
+ * Loaders, because a sentinel that is not in the DOM can never report that it came into view,
202
+ * so the next page would never be asked for. Persisted keys, because the roving tab stop lives
203
+ * on the focused row and losing its element drops focus to the document.
204
+ */
205
+ indicesPlacedOutsideTheWindow() {
206
+ const collection = this.host.collection;
207
+ const indices = this.persistedIndices(collection.rootKeys);
208
+ collection.rootKeys.forEach((key, index) => {
209
+ if (collection.getNode(key)?.type === "loader") indices.add(index);
210
+ });
211
+ return indices;
212
+ }
213
+ /**
214
+ * Where the persisted keys sit among `keys`.
215
+ *
216
+ * A persisted key can be a cell as easily as a row — the roving tab stop lives on whichever
217
+ * element holds focus — and a cell needs its row placed or it has nowhere to live. So a key
218
+ * that is not one of these walks up until it finds the ancestor that is.
219
+ */
220
+ persistedIndices(keys) {
221
+ const collection = this.host.collection;
222
+ const indices = /* @__PURE__ */ new Set();
223
+ for (const key of this.host.persistedKeys) {
224
+ let node = collection.getNode(key);
225
+ while (node && keys[node.index] !== node.key) node = node.parentKey == null ? void 0 : collection.getNode(node.parentKey);
226
+ if (node) indices.add(node.index);
227
+ }
228
+ return indices;
229
+ }
230
+ /** Where the row at `index` sits. A lookup, for a row nobody has rendered as much as for one. */
231
+ rowOffset(index) {
232
+ return this.offsets.startOf(index);
147
233
  }
148
- /** Extends the computed region to cover `rect`, and to cover every persisted key. */
149
- layoutIfNeeded(rect) {
234
+ /**
235
+ * The root indices the pass places, in order.
236
+ *
237
+ * The window plus whatever has to exist outside it. There is nothing to walk to and nothing to
238
+ * account for afterwards: a row's offset is a lookup, so the rows above and below the window
239
+ * cost the pass nothing at all.
240
+ */
241
+ windowedIndices(rect) {
242
+ const indices = this.indicesPlacedOutsideTheWindow();
243
+ if (rect.area > 0) {
244
+ const { first, last } = this.offsets.rangeFor(rect.y, rect.height);
245
+ for (let index = first; index <= last; index += 1) indices.add(index);
246
+ }
247
+ return [...indices].filter((index) => index >= 0 && index < this.placedCount).sort((a, b) => a - b);
248
+ }
249
+ /** Builds the rendered set for a window, and drops what that window has left behind. */
250
+ layoutFor(rect) {
150
251
  if (!this.lastCollection) return;
151
- if (!this.requestedRect.containsRect(rect)) {
152
- this.requestedRect = this.requestedRect.union(rect);
153
- this.rootNodes = this.buildCollection();
252
+ this.window = rect.copy();
253
+ this.rootNodes = this.buildCollection();
254
+ this.pruneLayoutNodes();
255
+ }
256
+ /**
257
+ * Drops the rows the window has moved away from.
258
+ *
259
+ * Only rows and their cells: a table's header, its columns and its body are placed once and
260
+ * read by everything else, and a row that something still depends on — the one holding the
261
+ * roving tab stop — has to keep its element.
262
+ */
263
+ pruneLayoutNodes() {
264
+ for (const [key, layoutNode] of this.layoutNodes) {
265
+ const type = layoutNode.node?.type ?? layoutNode.layoutInfo.type;
266
+ if (type !== "item" && type !== "row" && type !== "cell") continue;
267
+ if (layoutNode.layoutInfo.rect.intersects(this.window) || this.host.persistedKeys.has(key) || layoutNode.layoutInfo.parentKey != null && this.host.persistedKeys.has(layoutNode.layoutInfo.parentKey)) continue;
268
+ this.layoutNodes.delete(key);
154
269
  }
155
- for (const key of this.host.persistedKeys) if (this.ensureLayoutInfo(key)) return;
156
270
  }
157
271
  /**
158
- * Computes the whole layout when a key is asked about that the lazy pass never reached.
272
+ * Places a key the windowed pass never reached.
159
273
  *
160
- * Pressing End, or restoring focus to a selected row, asks about a key at an arbitrary offset;
161
- * there is no way to know where it sits without laying out everything above it.
274
+ * Pressing End, or restoring focus to a selected row, asks about a key at an arbitrary offset.
275
+ * That offset is a lookup whatever the rows are, so the one row is placed and nothing else is.
162
276
  */
163
277
  ensureLayoutInfo(key) {
164
- if (this.layoutNodes.has(key) || !this.lastCollection || this.requestedRect.area >= this.contentSize.area) return false;
165
- this.requestedRect = new Rect(0, 0, Infinity, Infinity);
166
- this.rootNodes = this.buildCollection();
167
- this.requestedRect = new Rect(0, 0, this.contentSize.width, this.contentSize.height);
168
- return true;
278
+ if (this.layoutNodes.has(key) || !this.lastCollection) return;
279
+ const node = this.lastCollection.getNode(key);
280
+ const offset = node == null ? null : this.indexedOffset(node);
281
+ if (node == null || offset == null) return;
282
+ this.buildChild(node, this.padding, offset, node.parentKey);
283
+ }
284
+ /** Where the index puts a node, or `null` for one it does not place. */
285
+ indexedOffset(node) {
286
+ return node.parentKey == null ? this.rowOffset(node.index) : null;
169
287
  }
170
288
  replaceLayoutInfo(key, oldLayoutInfo, newLayoutInfo) {
171
289
  const layoutNode = this.layoutNodes.get(key);
172
290
  if (!layoutNode) return;
173
- layoutNode.validRect = layoutNode.validRect.intersection(this.validRect);
174
291
  if (layoutNode.layoutInfo === oldLayoutInfo) layoutNode.layoutInfo = newLayoutInfo;
175
292
  }
176
293
  buildCollection(offset = this.padding) {
@@ -178,39 +295,12 @@ var ListLayout = class extends Layout {
178
295
  const rootKeys = collection.rootKeys;
179
296
  const nodes = [];
180
297
  const isEmpty = collection.itemCount === 0;
181
- const rowStride = (this.rowSize ?? this.estimatedRowSize ?? DEFAULT_ROW_SIZE) + this.gap;
182
- const pendingLoaderKeys = rootKeys.filter((key) => collection.getNode(key)?.type === "loader");
183
- let y = isEmpty ? 0 : offset;
184
- for (const [index, key] of rootKeys.entries()) {
185
- const node = collection.getNode(key);
186
- if (!node) continue;
187
- const isRow = node.type === "item" || node.type === "row";
188
- if (isRow && y + rowStride < this.requestedRect.y && !this.isValid(node, y)) {
189
- y += rowStride;
190
- continue;
191
- }
192
- const layoutNode = this.buildChild(node, this.padding, y, null);
193
- y = layoutNode.layoutInfo.rect.maxY + this.gap;
194
- nodes.push(layoutNode);
195
- if (node.type === "loader") pendingLoaderKeys.splice(pendingLoaderKeys.indexOf(key), 1);
196
- if (!(isRow || node.type === "loader") || y <= this.requestedRect.maxY) continue;
197
- let lastIndex = index;
198
- for (const loaderKey of pendingLoaderKeys) {
199
- const loaderNode = collection.getNode(loaderKey);
200
- if (!loaderNode) continue;
201
- const loaderIndex = rootKeys.indexOf(loaderKey);
202
- y += (loaderIndex - lastIndex - 1) * rowStride;
203
- const loader = this.buildChild(loaderNode, this.padding, y, null);
204
- nodes.push(loader);
205
- y = loader.layoutInfo.rect.maxY;
206
- lastIndex = loaderIndex;
207
- }
208
- y += (rootKeys.length - lastIndex - 1) * rowStride;
209
- break;
298
+ this.configureOffsets(offset);
299
+ for (const index of this.windowedIndices(this.window)) {
300
+ const node = collection.getNode(rootKeys[index]);
301
+ if (node) nodes.push(this.buildChild(node, this.padding, this.offsets.startOf(index), null));
210
302
  }
211
- y = Math.max(y - this.gap, 0);
212
- y += isEmpty ? 0 : this.padding;
213
- this.contentSize = new Size(this.host.size.width, y);
303
+ this.contentSize = new Size(this.host.size.width, isEmpty ? 0 : this.offsets.total() + this.padding);
214
304
  return nodes;
215
305
  }
216
306
  buildChild(node, x, y, parentKey) {
@@ -231,37 +321,31 @@ var ListLayout = class extends Layout {
231
321
  }
232
322
  buildItem(node, x, y) {
233
323
  const width = this.host.size.width - this.padding - x;
324
+ const previous = this.layoutNodes.get(node.key);
234
325
  let height = this.rowSize;
235
326
  let isEstimated = false;
236
327
  if (height == null) {
237
- const previous = this.layoutNodes.get(node.key);
238
- if (previous) {
239
- height = previous.layoutInfo.rect.height;
240
- isEstimated = width !== previous.layoutInfo.rect.width || node !== previous.node || previous.layoutInfo.estimatedSize;
241
- } else {
242
- height = this.estimatedRowSize;
243
- isEstimated = true;
244
- }
328
+ height = this.offsets.size(node.index);
329
+ isEstimated = !this.offsets.isMeasured(node.index) || previous != null && (width !== previous.layoutInfo.rect.width || node !== previous.node);
245
330
  }
246
331
  const layoutInfo = new LayoutInfo(node.type, node.key, new Rect(x, y, width, height ?? DEFAULT_ROW_SIZE));
247
332
  layoutInfo.estimatedSize = isEstimated;
248
333
  return {
249
334
  children: [],
250
335
  layoutInfo,
251
- node,
252
- validRect: layoutInfo.rect.intersection(this.requestedRect)
336
+ node
253
337
  };
254
338
  }
255
339
  /**
256
340
  * Whether a cached row can be reused as it stands.
257
341
  *
258
- * Everything here has to hold: nothing global was invalidated, the datum is the same object,
259
- * the row is at the same offset, it sits inside the known-good region, and the part of it that
260
- * was computed covers the part now being asked about.
342
+ * Three things: nothing global was invalidated, the datum is the same object, and the row is
343
+ * still at the same offset. There is no region to be inside of any more a row's offset comes
344
+ * from the index, so being at the offset the index gives it *is* the whole of being current.
261
345
  */
262
346
  isValid(node, y) {
263
347
  const cached = this.layoutNodes.get(node.key);
264
- return !this.invalidateEverything && !!cached && cached.node === node && cached.layoutInfo.rect.y === y && cached.layoutInfo.rect.intersects(this.validRect) && cached.validRect.containsRect(cached.layoutInfo.rect.intersection(this.requestedRect));
348
+ return !this.invalidateEverything && !!cached && cached.node === node && cached.layoutInfo.rect.y === y;
265
349
  }
266
350
  buildLoader(node, x, y) {
267
351
  const width = this.host.size.width - this.padding - x;
@@ -271,8 +355,7 @@ var ListLayout = class extends Layout {
271
355
  return {
272
356
  children: [],
273
357
  layoutInfo,
274
- node,
275
- validRect: layoutInfo.rect.intersection(this.requestedRect)
358
+ node
276
359
  };
277
360
  }
278
361
  /**
@@ -0,0 +1,111 @@
1
+ import type { VirtualizerKey } from "./virtualizer-layout-info.js";
2
+ /**
3
+ * Where every row of a collection sits, whether or not anyone has rendered it.
4
+ *
5
+ * A windowed layout has to answer two questions on every scroll — which rows the window covers,
6
+ * and how tall the whole collection is — and both have to be answerable for a row that has never
7
+ * been in the DOM. React Aria answers them by walking: a row's offset is the total of the rows
8
+ * above it, so the pass adds that total up from the top, and a region records how far it got. That
9
+ * makes what is rendered a function of where the window has *been* rather than of where it is, and
10
+ * it makes a pass cost more the further down it lands.
11
+ *
12
+ * This is the other way round. Every row gets a start and a size in one flat array, taking the
13
+ * measured size where there is one and the estimate where there is not — so an index always has an
14
+ * offset, an offset always has an index, and the window is a binary search rather than a walk.
15
+ * There is no region, nothing to invalidate, and nothing that can disagree with the current scroll
16
+ * offset.
17
+ *
18
+ * The array is rebuilt from the lowest index a measurement disturbed, because a row's start is the
19
+ * previous row's end: everything above the change is still right, everything below it moved.
20
+ */
21
+ export interface RowOffsetsOptions {
22
+ /** How many rows there are. */
23
+ count: number;
24
+ /** The key of the row at an index. A measurement is held by key, so it survives a reorder. */
25
+ keyAt: (index: number) => VirtualizerKey;
26
+ /** How tall the row at an index is before anything has measured it. */
27
+ estimate: (index: number) => number;
28
+ /**
29
+ * What the rows came from. Anything at all, compared by identity: two collections of the same
30
+ * length can hold different rows, and a size held by key would then be read for the wrong index.
31
+ */
32
+ collection?: unknown;
33
+ /** Where the first row starts — the padding, or whatever is stacked above the rows. */
34
+ start?: number;
35
+ /** The space between two rows, which sits between them and not after the last. */
36
+ gap?: number;
37
+ }
38
+ /** The rows a window covers, as a closed range of indices. */
39
+ export interface RowRange {
40
+ first: number;
41
+ last: number;
42
+ }
43
+ export declare class RowOffsets {
44
+ private count;
45
+ private keyAt;
46
+ private estimate;
47
+ private start;
48
+ private gap;
49
+ /** The measured sizes, by key rather than by index so a reorder keeps them. */
50
+ private sizes;
51
+ /** `[start, size]` per row. Longer than `count` when the collection has shrunk. */
52
+ private flat;
53
+ /** The lowest index whose start may have moved, or `count` when nothing has. */
54
+ private dirtyFrom;
55
+ private collection;
56
+ /**
57
+ * Takes the shape of the collection.
58
+ *
59
+ * The measurements survive as long as the keys can still be asked for — a row that scrolled out
60
+ * of the window and back in is the same row, and re-measuring it would place it at an estimate
61
+ * for a frame first.
62
+ */
63
+ configure(options: RowOffsetsOptions): void;
64
+ /** Forgets every measurement, which is what a resize does — a row's height follows its width. */
65
+ reset(): void;
66
+ /**
67
+ * Records a measured size.
68
+ *
69
+ * Returns whether anything moved, which is the caller's cue to lay out again. Measuring the same
70
+ * height twice moves nothing, and that is what keeps measurement from looping.
71
+ */
72
+ measure(key: VirtualizerKey, size: number, index?: number): boolean;
73
+ /** Whether the row at an index has been measured, rather than placed at its estimate. */
74
+ isMeasured(index: number): boolean;
75
+ /** Marks the row at an index, and everything below it, as sitting somewhere new. */
76
+ invalidateFrom(index: number): void;
77
+ size(index: number): number;
78
+ /** Where the row at an index starts. Answers for a row nobody has rendered. */
79
+ startOf(index: number): number;
80
+ endOf(index: number): number;
81
+ /** How tall every row together comes to, the gaps between them included. */
82
+ total(): number;
83
+ /**
84
+ * The row whose band contains an offset, clamped to the collection.
85
+ *
86
+ * A row ending exactly on the offset counts as being above it, which is the edge rule the whole
87
+ * window turns on: a rectangle snapped to a row boundary would otherwise take in the row above.
88
+ */
89
+ indexAt(offset: number): number;
90
+ /**
91
+ * The rows a window covers, plus `overscan` of them either side.
92
+ *
93
+ * Closed at both ends: `last` is a row, not the one past it.
94
+ *
95
+ * Both ends are clamped into the collection, so a window that outran the data comes back with
96
+ * the rows nearest to it rather than with nothing. That is the property the whole thing turns
97
+ * on: there is no offset a scrollbar can reach that has no rows to answer with.
98
+ *
99
+ * An empty collection is the one range that holds nothing, and it says so with `last` below
100
+ * `first` rather than by naming a row that is not there.
101
+ */
102
+ rangeFor(offset: number, length: number, overscan?: number): RowRange;
103
+ /**
104
+ * Fills in the starts from the lowest one a measurement disturbed.
105
+ *
106
+ * Every row below a size that changed sits somewhere new, so there is no shortcut past them —
107
+ * but this is a loop over a typed array with no allocation in it, which is why a hundred thousand
108
+ * rows can afford to be re-added on the frame a row is measured.
109
+ */
110
+ private build;
111
+ }