@protobase/ui 0.14.0 → 0.14.1

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 (30) hide show
  1. package/dist/app/displays/list-cards.js +2 -2
  2. package/dist/app/displays/virtual-cards.js +43 -65
  3. package/dist/app/list-page.js +9 -2
  4. package/dist/app-shell/app-shell.js +54 -38
  5. package/dist/app-shell/bottom-bar.d.ts +1 -0
  6. package/dist/app-shell/bottom-bar.js +3 -2
  7. package/dist/app-shell/index.d.ts +1 -0
  8. package/dist/app-shell/page-scroll.d.ts +1 -0
  9. package/dist/app-shell/page-scroll.js +56 -0
  10. package/dist/app-shell/sidebar.js +2 -2
  11. package/dist/app-shell/top-bar.js +3 -2
  12. package/dist/data-grid/data-grid.js +92 -85
  13. package/dist/data-grid/geometry.js +26 -6
  14. package/dist/data-grid/grid-footer.js +5 -12
  15. package/dist/data-grid/scroll-parent.js +35 -0
  16. package/dist/data-grid/use-virtual-rows.js +123 -18
  17. package/dist/package.js +1 -1
  18. package/dist/shell/assets/app-Dkz6z_t_.js +66 -0
  19. package/dist/shell/assets/{index-CwUFCDf9.js → index-BCLK6g9a.js} +1 -1
  20. package/dist/shell/assets/index-BQ4FIU4i.css +2 -0
  21. package/dist/shell/assets/{maplibre-gl-QGc1hPKt.js → maplibre-gl-DWx1yzYO.js} +1 -1
  22. package/dist/shell/assets/{record-map-D5vqYcYw.js → record-map-C2M8dEjy.js} +2 -2
  23. package/dist/shell/assets/{records-map-DqqaK1rm.js → records-map-huS0J9xV.js} +1 -1
  24. package/dist/shell/assets/{shared-protobase-ui-0ozDlcDr.js → shared-protobase-ui-CsJxPCx6.js} +1 -1
  25. package/dist/shell/index.html +4 -4
  26. package/dist/shell/protobase.shell.json +1 -1
  27. package/package.json +4 -4
  28. package/dist/data-grid/use-grid-scroll.js +0 -87
  29. package/dist/shell/assets/app-DtPlcCA3.js +0 -66
  30. package/dist/shell/assets/index-CYWfU_c1.css +0 -2
@@ -15,7 +15,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
15
  import { useTable } from "@tanstack/react-table";
16
16
  //#region src/data-grid/data-grid.tsx
17
17
  const headerHeight = 32;
18
- const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0, onScrollIndex, latencyMs = 150, sort: controlledSort, onSortChange, onOpenRow, rowActions, bulkActions, pinKey, defaultPinned, rowNumbers = true, compact = false, selectable = true, fit = false, className }) => {
18
+ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0, onScrollIndex, latencyMs = 150, sort: controlledSort, onSortChange, onOpenRow, rowActions, bulkActions, pinKey, defaultPinned, rowNumbers = true, compact = false, selectable = true, fit = false, scroll = "own", className }) => {
19
19
  const rowHeight = compact ? 28 : 36;
20
20
  const [pinned, setPinned] = usePinnedColumns(pinKey, defaultPinned);
21
21
  const columns = useMemo(() => {
@@ -50,16 +50,16 @@ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0,
50
50
  reverse,
51
51
  total
52
52
  ]);
53
- const { ref, onScroll, viewport, top, geometry, rows: virtualRows, first: firstIndex, last: lastIndex } = useVirtualRows({
53
+ const [startIndex] = useState(initialScrollIndex);
54
+ const { ref, top, geometry, rows: virtualRows, first: firstIndex, last: lastIndex } = useVirtualRows({
54
55
  total,
55
56
  rowHeight,
56
- initialIndex: initialScrollIndex,
57
+ initialIndex: startIndex,
57
58
  inset: headerHeight
58
59
  });
59
60
  const narrow = !useIsDesktop();
60
61
  const gutter = narrow || !rowNumbers ? 40 : gutterWidthFor(total);
61
- const bodyHeight = Math.max(0, viewport - headerHeight);
62
- const topIndex = Math.floor(top / rowHeight);
62
+ const topIndex = Math.floor(Math.max(0, top) / rowHeight);
63
63
  useEffect(() => onScrollIndex?.(topIndex), [topIndex, onScrollIndex]);
64
64
  const getRow = usePagedRows({
65
65
  total,
@@ -99,9 +99,14 @@ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0,
99
99
  const trailing = rowActions && rowActions.length > 0 ? 40 : 0;
100
100
  const minScale = narrow ? 1 : desktopMinScale;
101
101
  const horizontal = useHorizontalScroll();
102
+ const headerRef = useRef(null);
103
+ const onBodyScroll = () => {
104
+ horizontal.onScroll();
105
+ if (headerRef.current && horizontal.ref.current) headerRef.current.scrollLeft = horizontal.ref.current.scrollLeft;
106
+ };
102
107
  const [pins, setPins] = useState({});
103
108
  useLayoutEffect(() => {
104
- const header = horizontal.ref.current?.querySelector("[role=row]");
109
+ const header = headerRef.current?.querySelector("[role=row]");
105
110
  if (!header) return;
106
111
  let left = gutter;
107
112
  const next = {};
@@ -118,89 +123,91 @@ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0,
118
123
  sorted: header.column.getIsSorted(),
119
124
  onSort: (event) => header.column.getToggleSortingHandler()?.(event)
120
125
  })));
121
- const visibleRows = Math.max(1, Math.floor(bodyHeight / rowHeight));
122
- const first = Math.min(total, Math.floor(top / rowHeight) + 1);
123
- const last = Math.min(total, first + visibleRows - 1);
124
- return /* @__PURE__ */ jsxs("div", {
125
- className: cn("flex min-h-0 flex-col overflow-hidden rounded-lg border bg-background", className),
126
- style: fit ? { height: headerHeight + total * rowHeight + 2 } : void 0,
127
- children: [/* @__PURE__ */ jsx("div", {
126
+ const minWidth = gridMinWidth(columns, gutter, trailing, minScale);
127
+ const own = scroll === "own";
128
+ const header = /* @__PURE__ */ jsx("div", {
129
+ ref: headerRef,
130
+ className: cn("sticky z-20 shrink-0 overflow-hidden", own ? "top-0" : "top-[var(--pb-sticky-top,0px)]"),
131
+ children: /* @__PURE__ */ jsx("div", {
132
+ style: { minWidth },
133
+ children: /* @__PURE__ */ jsx(GridHeader, {
134
+ cells: headerCells,
135
+ specs: columns,
136
+ gutter,
137
+ trailing,
138
+ minScale,
139
+ pins,
140
+ lastPinned,
141
+ scrolled: horizontal.scrolled,
142
+ onTogglePin: togglePin,
143
+ selectable,
144
+ selectedCount,
145
+ onClearSelection: () => setRowSelection({})
146
+ })
147
+ })
148
+ });
149
+ const body = /* @__PURE__ */ jsx("div", {
150
+ ref: horizontal.attach,
151
+ onScroll: onBodyScroll,
152
+ className: "overflow-x-auto overflow-y-hidden overscroll-x-contain [-webkit-overflow-scrolling:touch]",
153
+ children: /* @__PURE__ */ jsx("div", {
128
154
  ref,
129
- onScroll,
130
- className: "relative min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
131
- role: "grid",
132
- "aria-rowcount": total,
133
- children: /* @__PURE__ */ jsx("div", {
134
- style: { height: geometry.contentHeight || void 0 },
135
- children: /* @__PURE__ */ jsx("div", {
136
- className: "sticky top-0",
137
- style: { height: viewport },
138
- children: viewport > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
139
- ref: horizontal.attach,
140
- onScroll: horizontal.onScroll,
141
- className: "h-full overflow-x-auto overflow-y-hidden overscroll-x-contain [-webkit-overflow-scrolling:touch]",
142
- children: /* @__PURE__ */ jsxs("div", {
143
- className: "flex h-full flex-col",
144
- style: { minWidth: gridMinWidth(columns, gutter, trailing, minScale) },
145
- children: [/* @__PURE__ */ jsx(GridHeader, {
146
- cells: headerCells,
147
- specs: columns,
148
- gutter,
149
- trailing,
150
- minScale,
151
- pins,
152
- lastPinned,
153
- scrolled: horizontal.scrolled,
154
- onTogglePin: togglePin,
155
- selectable,
156
- selectedCount,
157
- onClearSelection: () => setRowSelection({})
158
- }), /* @__PURE__ */ jsx("div", {
159
- className: "relative min-h-0 flex-1 overflow-clip",
160
- children: loaded.map(({ index, offset, row }) => {
161
- const frame = {
162
- specs: columns,
163
- gutter,
164
- index,
165
- offset,
166
- height: rowHeight,
167
- selectable,
168
- trailing,
169
- minScale,
170
- scrolled: horizontal.scrolled,
171
- pins,
172
- lastPinned
173
- };
174
- const tableRow = row ? tableRows.get(row.id) : void 0;
175
- if (!tableRow) return /* @__PURE__ */ jsx(SkeletonRow, { ...frame }, `skeleton-${index}`);
176
- const actions = trailing && row ? availableActions(rowActions, row) : [];
177
- return /* @__PURE__ */ jsx(DataRow, {
178
- ...frame,
179
- selected: tableRow.getIsSelected(),
180
- onToggle: () => tableRow.toggleSelected(),
181
- onOpen: onOpenRow && row ? () => onOpenRow(row) : void 0,
182
- menu: row && actions.length > 0 ? /* @__PURE__ */ jsx(RowMenu, {
183
- row,
184
- actions
185
- }) : void 0,
186
- cells: tableRow.getAllCells().map((cell) => ({
187
- spec: columns.find((spec) => spec.id === cell.column.id),
188
- content: /* @__PURE__ */ jsx(table.FlexRender, { cell })
189
- }))
190
- }, row?.id);
191
- })
192
- })]
193
- })
194
- }), horizontal.more && /* @__PURE__ */ jsx("div", {
195
- "aria-hidden": true,
196
- className: "pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l from-foreground/15 to-transparent"
197
- })] })
198
- })
155
+ className: "relative",
156
+ style: {
157
+ minWidth,
158
+ height: geometry.contentHeight
159
+ },
160
+ children: loaded.map(({ index, offset, row }) => {
161
+ const frame = {
162
+ specs: columns,
163
+ gutter,
164
+ index,
165
+ offset,
166
+ height: rowHeight,
167
+ selectable,
168
+ trailing,
169
+ minScale,
170
+ scrolled: horizontal.scrolled,
171
+ pins,
172
+ lastPinned
173
+ };
174
+ const tableRow = row ? tableRows.get(row.id) : void 0;
175
+ if (!tableRow) return /* @__PURE__ */ jsx(SkeletonRow, { ...frame }, `skeleton-${index}`);
176
+ const actions = trailing && row ? availableActions(rowActions, row) : [];
177
+ return /* @__PURE__ */ jsx(DataRow, {
178
+ ...frame,
179
+ selected: tableRow.getIsSelected(),
180
+ onToggle: () => tableRow.toggleSelected(),
181
+ onOpen: onOpenRow && row ? () => onOpenRow(row) : void 0,
182
+ menu: row && actions.length > 0 ? /* @__PURE__ */ jsx(RowMenu, {
183
+ row,
184
+ actions
185
+ }) : void 0,
186
+ cells: tableRow.getAllCells().map((cell) => ({
187
+ spec: columns.find((spec) => spec.id === cell.column.id),
188
+ content: /* @__PURE__ */ jsx(table.FlexRender, { cell })
189
+ }))
190
+ }, row?.id);
199
191
  })
192
+ })
193
+ });
194
+ return /* @__PURE__ */ jsxs("div", {
195
+ role: "grid",
196
+ "aria-rowcount": total,
197
+ className: cn("flex flex-col rounded-lg border bg-background", own ? "min-h-0 overflow-hidden" : "overflow-clip", className),
198
+ style: fit && own ? { height: headerHeight + total * rowHeight + 2 } : void 0,
199
+ children: [/* @__PURE__ */ jsxs("div", {
200
+ className: cn("relative", own && "flex min-h-0 flex-1 flex-col"),
201
+ children: [own ? /* @__PURE__ */ jsxs("div", {
202
+ className: "min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
203
+ children: [header, body]
204
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [header, body] }), horizontal.more && /* @__PURE__ */ jsx("div", {
205
+ "aria-hidden": true,
206
+ className: "pointer-events-none absolute inset-y-0 right-0 z-20 w-8 bg-gradient-to-l from-foreground/15 to-transparent"
207
+ })]
200
208
  }), !fit && /* @__PURE__ */ jsx(GridFooter, {
201
209
  total,
202
- first,
203
- last,
210
+ className: own ? void 0 : "sticky bottom-[var(--pb-sticky-bottom,0px)] z-20",
204
211
  selectedCount,
205
212
  bulkActions: bulkActions?.map((action) => ({
206
213
  label: action.label(selectedCount),
@@ -1,6 +1,7 @@
1
1
  //#region src/data-grid/geometry.ts
2
2
  /** Browsers cap element height (about 17M px in the strictest); stay well below it. */
3
3
  const maxContentHeight = 1e7;
4
+ /** The list's height in the page, `contentHeight`: its true height, or the cap when the rows need more. */
4
5
  const gridGeometry = (total, rowHeight, viewportHeight) => {
5
6
  const trueHeight = total * rowHeight;
6
7
  const contentHeight = Math.min(trueHeight, maxContentHeight);
@@ -13,13 +14,32 @@ const gridGeometry = (total, rowHeight, viewportHeight) => {
13
14
  maxScroll: Math.max(0, contentHeight - viewportHeight)
14
15
  };
15
16
  };
16
- /** Maps the native scroll position onto the virtual (true) pixel offset. */
17
- const topFromScroll = (geometry, scrollTop) => geometry.maxScroll === 0 ? 0 : scrollTop / geometry.maxScroll * geometry.maxTop;
18
- const scrollFromTop = (geometry, top) => geometry.maxTop === 0 ? 0 : top / geometry.maxTop * geometry.maxScroll;
19
- const clampTop = (geometry, top) => Math.min(Math.max(0, top), geometry.maxTop);
17
+ /**
18
+ * The offset into the rows (the virtual offset) shown at `position`, how far the top of the view is into the list.
19
+ * They are the same unless the list is capped: then the capped range maps onto all the rows, and a position before or
20
+ * after it moves one to one.
21
+ */
22
+ const topFromScroll = (geometry, position) => {
23
+ if (!geometry.scaled || position <= 0) return position;
24
+ if (position >= geometry.maxScroll) return geometry.maxTop + position - geometry.maxScroll;
25
+ return position / geometry.maxScroll * geometry.maxTop;
26
+ };
27
+ const scrollFromTop = (geometry, top) => {
28
+ if (!geometry.scaled || top <= 0) return top;
29
+ if (top >= geometry.maxTop) return geometry.maxScroll + top - geometry.maxTop;
30
+ return top / geometry.maxTop * geometry.maxScroll;
31
+ };
32
+ /**
33
+ * Where row `index` is drawn in the list when the view at `position` shows the rows from `top`: its own place, or in a
34
+ * capped list its place relative to the view, so that it scrolls with the page until the next draw.
35
+ */
36
+ const rowOffset = (geometry, index, top, position) => {
37
+ const offset = index * geometry.rowHeight;
38
+ return geometry.scaled ? offset - top + position : offset;
39
+ };
20
40
  /** The rows drawn for a scroll offset: those in a viewport of `height`, and `overscan` more on each side. */
21
41
  const rowsInView = (total, top, rowHeight, height, overscan) => {
22
- const first = Math.max(0, Math.floor(top / rowHeight) - overscan);
42
+ const first = Math.min(total, Math.max(0, Math.floor(top / rowHeight) - overscan));
23
43
  const last = Math.min(total - 1, Math.floor((top + height) / rowHeight) + overscan);
24
44
  return {
25
45
  first,
@@ -27,4 +47,4 @@ const rowsInView = (total, top, rowHeight, height, overscan) => {
27
47
  };
28
48
  };
29
49
  //#endregion
30
- export { clampTop, gridGeometry, maxContentHeight, rowsInView, scrollFromTop, topFromScroll };
50
+ export { gridGeometry, maxContentHeight, rowOffset, rowsInView, scrollFromTop, topFromScroll };
@@ -2,9 +2,10 @@ import { cn } from "../lib/cn.js";
2
2
  import { formatApproxRows, formatInt } from "../format/number.js";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/data-grid/grid-footer.tsx
5
- const GridFooter = ({ total, first, last, selectedCount, bulkActions = [] }) => /* @__PURE__ */ jsxs("div", {
6
- className: "flex h-9 shrink-0 items-center justify-between border-t bg-surface px-3 text-xs text-muted-foreground",
7
- children: [/* @__PURE__ */ jsxs("span", {
5
+ /** How many rows match and, with rows selected, how many and what can be done with them. */
6
+ const GridFooter = ({ total, selectedCount, bulkActions = [], className }) => /* @__PURE__ */ jsx("div", {
7
+ className: cn("flex h-9 shrink-0 items-center border-t bg-surface px-3 text-xs text-muted-foreground", className),
8
+ children: /* @__PURE__ */ jsxs("span", {
8
9
  className: "flex items-center tabular-nums",
9
10
  children: [
10
11
  formatApproxRows(total),
@@ -19,15 +20,7 @@ const GridFooter = ({ total, first, last, selectedCount, bulkActions = [] }) =>
19
20
  children: action.label
20
21
  }, action.label))
21
22
  ]
22
- }), /* @__PURE__ */ jsxs("span", {
23
- className: "tabular-nums",
24
- children: [
25
- "Rows ",
26
- formatInt(first),
27
- " – ",
28
- formatInt(last)
29
- ]
30
- })]
23
+ })
31
24
  });
32
25
  //#endregion
33
26
  export { GridFooter };
@@ -0,0 +1,35 @@
1
+ //#region src/data-grid/scroll-parent.ts
2
+ /**
3
+ * The element that scrolls `element` up and down: its nearest ancestor that scrolls vertically, or the page's
4
+ * scrolling element when none does.
5
+ */
6
+ const scrollParent = (element) => {
7
+ const page = document.scrollingElement ?? document.documentElement;
8
+ for (let parent = element.parentElement; parent && parent !== page && parent !== document.body; parent = parent.parentElement) if (/auto|scroll/.test(getComputedStyle(parent).overflowY)) return parent;
9
+ return page;
10
+ };
11
+ const isPage = (scroller) => scroller === (document.scrollingElement ?? document.documentElement);
12
+ /** Where scroll events of `scroller` are sent: the window for the page. */
13
+ const scrollEvents = (scroller) => isPage(scroller) ? window : scroller;
14
+ const padding = (value) => Number.parseFloat(value) || 0;
15
+ /**
16
+ * The part of `scroller` content shows in, in viewport coordinates: its box less its scroll padding, which is where
17
+ * sticky bars over it say they are.
18
+ */
19
+ const visibleArea = (scroller) => {
20
+ const style = getComputedStyle(scroller);
21
+ const box = isPage(scroller) ? {
22
+ top: 0,
23
+ height: scroller.clientHeight
24
+ } : {
25
+ top: scroller.getBoundingClientRect().top + scroller.clientTop,
26
+ height: scroller.clientHeight
27
+ };
28
+ const top = padding(style.scrollPaddingTop);
29
+ return {
30
+ top: box.top + top,
31
+ height: Math.max(0, box.height - top - padding(style.scrollPaddingBottom))
32
+ };
33
+ };
34
+ //#endregion
35
+ export { scrollEvents, scrollParent, visibleArea };
@@ -1,30 +1,135 @@
1
- import { rowsInView } from "./geometry.js";
2
- import { useGridScroll } from "./use-grid-scroll.js";
1
+ import { gridGeometry, rowOffset, rowsInView, scrollFromTop, topFromScroll } from "./geometry.js";
2
+ import { scrollEvents, scrollParent, visibleArea } from "./scroll-parent.js";
3
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
3
4
  //#region src/data-grid/use-virtual-rows.ts
4
5
  const overscan = 6;
6
+ const viewRows = (view, total, rowHeight) => {
7
+ const geometry = gridGeometry(total, rowHeight, view.height);
8
+ const top = topFromScroll(geometry, view.position);
9
+ return {
10
+ geometry,
11
+ top,
12
+ topIndex: Math.floor(Math.max(0, top) / rowHeight),
13
+ ...rowsInView(total, top, rowHeight, view.height, overscan)
14
+ };
15
+ };
5
16
  /**
6
- * Rows of a fixed height over a proxy scroller, so any number of rows scrolls at native speed: `ref` and `onScroll` go
7
- * on the scrolling element, and each drawn row sits at `offset` from the top of the viewport's rows, below `inset`.
17
+ * Rows of a fixed height in a list as tall as all of them, scrolled natively by whatever scrolls it: the nearest
18
+ * scrolling ancestor, or the page. Only the rows in view, and `overscan` more on each side, are drawn: `ref` goes on the
19
+ * list's element, `geometry.contentHeight` tall, and each drawn row sits at `offset` from its top. Nothing listens for
20
+ * wheel or touch: the list follows the scroll position, and draws again only when other rows come into view.
8
21
  * `first` and `last` are the indexes of the drawn rows, overscan included.
9
22
  */
10
- const useVirtualRows = ({ total, rowHeight, initialIndex, inset = 0, settle }) => {
11
- const scroll = useGridScroll(total, rowHeight, initialIndex);
12
- const { ref, onScroll, viewport, geometry, scrollTo } = scroll;
13
- const top = settle ? settle(scroll.top) : scroll.top;
14
- const { first, last } = rowsInView(total, top, rowHeight, Math.max(0, viewport - inset), overscan);
23
+ const useVirtualRows = ({ total, rowHeight, initialIndex, inset = 0, settle, ready = true }) => {
24
+ const ref = useRef(null);
25
+ const scroller = useRef(null);
26
+ const [view, setView] = useState({
27
+ position: 0,
28
+ height: 0
29
+ });
30
+ const options = useRef({
31
+ total,
32
+ rowHeight,
33
+ initialIndex,
34
+ inset,
35
+ ready
36
+ });
37
+ options.current = {
38
+ total,
39
+ rowHeight,
40
+ initialIndex,
41
+ inset,
42
+ ready
43
+ };
44
+ const read = useCallback(() => {
45
+ const content = ref.current;
46
+ if (!content || !scroller.current) return void 0;
47
+ const area = visibleArea(scroller.current);
48
+ const hidden = options.current.inset;
49
+ return {
50
+ position: area.top + hidden - content.getBoundingClientRect().top,
51
+ height: Math.max(0, area.height - hidden)
52
+ };
53
+ }, []);
54
+ const shown = useRef(view);
55
+ shown.current = view;
56
+ const measure = useCallback(() => {
57
+ const next = read();
58
+ if (!next) return;
59
+ const current = shown.current;
60
+ const { total, rowHeight } = options.current;
61
+ const before = viewRows(current, total, rowHeight);
62
+ const after = viewRows(next, total, rowHeight);
63
+ const sameRows = !after.geometry.scaled && before.first === after.first && before.last === after.last && before.topIndex === after.topIndex;
64
+ if (current.height === next.height && (current.position === next.position || sameRows)) return;
65
+ shown.current = next;
66
+ setView(next);
67
+ }, [read]);
68
+ const start = useRef({
69
+ pending: true,
70
+ laidOut: false
71
+ });
72
+ const begin = useCallback(() => {
73
+ if (!start.current.pending || !start.current.laidOut || !options.current.ready) return;
74
+ const view = read();
75
+ const parent = scroller.current;
76
+ if (!view || !parent) return;
77
+ start.current.pending = false;
78
+ const { total, rowHeight, initialIndex } = options.current;
79
+ if (initialIndex > 0) parent.scrollTop += scrollFromTop(gridGeometry(total, rowHeight, view.height), initialIndex * rowHeight) - view.position;
80
+ else if (view.position > 0) parent.scrollTop = 0;
81
+ }, [read]);
82
+ useLayoutEffect(() => {
83
+ const content = ref.current;
84
+ if (!content) return;
85
+ const parent = scrollParent(content);
86
+ scroller.current = parent;
87
+ const events = scrollEvents(parent);
88
+ events.addEventListener("scroll", measure, { passive: true });
89
+ window.addEventListener("resize", measure, { passive: true });
90
+ const observer = new ResizeObserver(() => {
91
+ start.current.laidOut = true;
92
+ begin();
93
+ measure();
94
+ });
95
+ observer.observe(parent);
96
+ observer.observe(content);
97
+ measure();
98
+ return () => {
99
+ events.removeEventListener("scroll", measure);
100
+ window.removeEventListener("resize", measure);
101
+ observer.disconnect();
102
+ scroller.current = null;
103
+ };
104
+ }, [measure, begin]);
105
+ useLayoutEffect(() => {
106
+ begin();
107
+ measure();
108
+ });
109
+ /** Scrolls to a virtual offset, for a layout change that keeps a row in view. */
110
+ const scrollTo = useCallback((top) => {
111
+ const current = read();
112
+ if (!current || !scroller.current) return;
113
+ const geometry = gridGeometry(options.current.total, options.current.rowHeight, current.height);
114
+ scroller.current.scrollTop += scrollFromTop(geometry, top) - current.position;
115
+ measure();
116
+ }, [read, measure]);
117
+ const drawn = viewRows(view, total, rowHeight);
118
+ const top = settle ? settle(drawn.top) : drawn.top;
119
+ const { first, last } = settle ? rowsInView(total, top, rowHeight, view.height, overscan) : drawn;
120
+ const rows = Array.from({ length: last - first + 1 }, (_, offset) => ({
121
+ index: first + offset,
122
+ offset: rowOffset(drawn.geometry, first + offset, top, view.position)
123
+ }));
15
124
  return {
16
125
  ref,
17
- onScroll,
18
- viewport,
126
+ height: view.height,
19
127
  top,
20
- geometry,
21
- scrollTo,
22
- rows: Array.from({ length: last - first + 1 }, (_, offset) => ({
23
- index: first + offset,
24
- offset: (first + offset) * rowHeight - top
25
- })),
128
+ geometry: drawn.geometry,
129
+ rows,
26
130
  first,
27
- last
131
+ last,
132
+ scrollTo
28
133
  };
29
134
  };
30
135
  //#endregion
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.14.0";
2
+ var version = "0.14.1";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };