@tipolox/litgrid-renderer 1.0.3 → 1.1.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.
package/README.md CHANGED
@@ -16,6 +16,7 @@ npm install @tipolox/litgrid-renderer
16
16
  - [Renderer utilities API](https://github.com/tipolox/litgrid/blob/main/docs/api-reference.md#renderer-utilities)
17
17
  - [Installation guide](https://github.com/tipolox/litgrid/blob/main/docs/installation.md)
18
18
  - [Repository](https://github.com/tipolox/litgrid)
19
+ - [v1.1.0 upgrade guide](https://github.com/tipolox/litgrid/blob/main/docs/installation.md#upgrading-to-v110)
19
20
  - [Live demo](https://tipolox.com/litgrid/demo)
20
21
 
21
22
  Licensed under the [MIT License](https://github.com/tipolox/litgrid/blob/main/LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tipolox/litgrid-renderer",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Virtualization and scroll-mapping utilities for LitGrid.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,11 @@
15
15
  "./package.json": "./package.json"
16
16
  },
17
17
  "files": [
18
- "dist"
18
+ "dist",
19
+ "src/**/*.ts",
20
+ "!src/**/*.d.ts",
21
+ "!src/**/*.test.ts",
22
+ "!src/**/*.bench.ts"
19
23
  ],
20
24
  "keywords": [
21
25
  "data-grid",
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export { createVirtualizer } from './virtualization/createVirtualizer.js'
2
+ export { createVariableVirtualizer } from './virtualization/createVariableVirtualizer.js'
3
+ export { calculateDisplayLayout, mapDisplayScrollOffset } from './viewport/scrollMapping.js'
4
+ export type {
5
+ FixedVirtualizer,
6
+ FixedVirtualRange,
7
+ VariableVirtualizerOptions,
8
+ VariableVirtualizerState,
9
+ VariableVirtualizer,
10
+ VariableVirtualRange,
11
+ VirtualRange,
12
+ VirtualizerOptions,
13
+ VirtualizerState
14
+ } from './virtualization/types.js'
15
+ export type {
16
+ DisplayLayout,
17
+ DisplayLayoutOptions,
18
+ ScrollMappingOptions,
19
+ ScrollMappingResult
20
+ } from './viewport/scrollMapping.js'
@@ -0,0 +1,13 @@
1
+ export function clamp(value: number, min: number, max: number) {
2
+ return Math.min(Math.max(value, min), max)
3
+ }
4
+
5
+ export function sumSizes(sizes: number[], startIndex: number, endIndex: number) {
6
+ let total = 0
7
+
8
+ for (let index = startIndex; index < endIndex; index += 1) {
9
+ total += Math.max(1, sizes[index] ?? 0)
10
+ }
11
+
12
+ return total
13
+ }
@@ -0,0 +1,83 @@
1
+ import { clamp } from '../utils/math.js'
2
+
3
+ export type ScrollMappingOptions = {
4
+ displayScrollOffset: number
5
+ previousDisplayScrollOffset: number
6
+ displaySize: number
7
+ totalSize: number
8
+ viewportSize: number
9
+ }
10
+
11
+ export type ScrollMappingResult = {
12
+ availableDisplaySize: number
13
+ availableVirtualSize: number
14
+ scrollOffset: number
15
+ }
16
+
17
+ export function mapDisplayScrollOffset({
18
+ displayScrollOffset,
19
+ previousDisplayScrollOffset,
20
+ displaySize,
21
+ totalSize,
22
+ viewportSize
23
+ }: ScrollMappingOptions): ScrollMappingResult {
24
+ const availableDisplaySize = Math.max(0, displaySize - viewportSize)
25
+ const availableVirtualSize = Math.max(0, totalSize - viewportSize)
26
+
27
+ if (totalSize > 0 && availableDisplaySize > 0 && availableVirtualSize > 0) {
28
+ const ratio = displayScrollOffset / availableDisplaySize
29
+ const desiredScrollOffset = ratio * availableVirtualSize
30
+ let scrollOffset = clamp(
31
+ Number.isFinite(desiredScrollOffset) ? desiredScrollOffset : 0,
32
+ 0,
33
+ availableVirtualSize
34
+ )
35
+
36
+ const bottomThreshold = 1
37
+ const isScrollingDown = displayScrollOffset >= previousDisplayScrollOffset
38
+ if (isScrollingDown && displayScrollOffset >= Math.max(0, availableDisplaySize - bottomThreshold)) {
39
+ scrollOffset = availableVirtualSize
40
+ }
41
+
42
+ return { availableDisplaySize, availableVirtualSize, scrollOffset }
43
+ }
44
+
45
+ return {
46
+ availableDisplaySize,
47
+ availableVirtualSize,
48
+ scrollOffset: clamp(displayScrollOffset, 0, availableVirtualSize)
49
+ }
50
+ }
51
+
52
+ export type DisplayLayoutOptions = {
53
+ totalSize: number
54
+ displaySize: number
55
+ virtualOffsetTop: number
56
+ visibleSize: number
57
+ bottomPadding: number
58
+ }
59
+
60
+ export type DisplayLayout = {
61
+ displayScale: number
62
+ displayOffsetTop: number
63
+ displayBottomPadding: number
64
+ }
65
+
66
+ export function calculateDisplayLayout({
67
+ totalSize,
68
+ displaySize,
69
+ virtualOffsetTop,
70
+ visibleSize,
71
+ bottomPadding
72
+ }: DisplayLayoutOptions): DisplayLayout {
73
+ const displayScale = totalSize > 0 ? displaySize / totalSize : 1
74
+
75
+ return {
76
+ displayScale,
77
+ displayOffsetTop: Math.min(
78
+ virtualOffsetTop * displayScale,
79
+ Math.max(0, displaySize - visibleSize)
80
+ ),
81
+ displayBottomPadding: bottomPadding * displayScale
82
+ }
83
+ }
@@ -0,0 +1,78 @@
1
+ import { clamp, sumSizes } from '../utils/math.js'
2
+ import type {
3
+ VariableVirtualizer,
4
+ VariableVirtualizerOptions,
5
+ VariableVirtualizerState
6
+ } from './types.js'
7
+
8
+ export function createVariableVirtualizer({
9
+ sizes,
10
+ viewportSize,
11
+ overscan = 2
12
+ }: VariableVirtualizerOptions): VariableVirtualizer {
13
+ let state: VariableVirtualizerState = {
14
+ sizes,
15
+ viewportSize,
16
+ overscan,
17
+ scrollOffset: 0
18
+ }
19
+
20
+ return {
21
+ setOptions(next: Partial<VariableVirtualizerState>) {
22
+ state = {
23
+ ...state,
24
+ ...next
25
+ }
26
+ },
27
+ setScrollOffset(scrollOffset: number) {
28
+ state.scrollOffset = scrollOffset
29
+ },
30
+ getState() {
31
+ const sizes = state.sizes.map((size) => Math.max(1, size))
32
+ const count = sizes.length
33
+ const totalSize = sumSizes(sizes, 0, count)
34
+ const safeViewportSize = Math.max(0, state.viewportSize)
35
+ const safeOverscan = Math.max(0, state.overscan)
36
+ const safeScrollOffset = clamp(state.scrollOffset, 0, totalSize)
37
+
38
+ let baseStartIndex = 0
39
+ let baseStartOffset = 0
40
+
41
+ while (
42
+ baseStartIndex < count &&
43
+ baseStartOffset + sizes[baseStartIndex] <= safeScrollOffset
44
+ ) {
45
+ baseStartOffset += sizes[baseStartIndex]
46
+ baseStartIndex += 1
47
+ }
48
+
49
+ let visibleEndIndex = baseStartIndex
50
+ let visibleEndOffset = baseStartOffset
51
+
52
+ while (
53
+ visibleEndIndex < count &&
54
+ visibleEndOffset < safeScrollOffset + safeViewportSize
55
+ ) {
56
+ visibleEndOffset += sizes[visibleEndIndex]
57
+ visibleEndIndex += 1
58
+ }
59
+
60
+ const startIndex = clamp(baseStartIndex - safeOverscan, 0, count)
61
+ const endIndex = clamp(visibleEndIndex + safeOverscan, startIndex, count)
62
+ const leftPadding = sumSizes(sizes, 0, startIndex)
63
+ const visibleSize = sumSizes(sizes, startIndex, endIndex)
64
+ const rightPadding = Math.max(0, totalSize - leftPadding - visibleSize)
65
+
66
+ return {
67
+ startIndex,
68
+ endIndex,
69
+ leftPadding,
70
+ rightPadding,
71
+ offsetTop: leftPadding,
72
+ bottomPadding: rightPadding,
73
+ totalSize,
74
+ visibleSize
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,65 @@
1
+ import { clamp } from '../utils/math.js'
2
+ import type { FixedVirtualizer, VirtualizerOptions, VirtualizerState } from './types.js'
3
+
4
+ export function createVirtualizer({
5
+ rowHeight,
6
+ viewportHeight,
7
+ overscan = 4
8
+ }: VirtualizerOptions): FixedVirtualizer {
9
+ let state: VirtualizerState = {
10
+ rowHeight,
11
+ viewportHeight,
12
+ overscan,
13
+ scrollTop: 0
14
+ }
15
+
16
+ return {
17
+ setOptions(next: Partial<VirtualizerState>) {
18
+ state = {
19
+ ...state,
20
+ ...next
21
+ }
22
+ },
23
+ setScrollTop(scrollTop: number) {
24
+ state.scrollTop = scrollTop
25
+ },
26
+ setScrollOffset(scrollTop: number) {
27
+ state.scrollTop = scrollTop
28
+ },
29
+ getState(count: number) {
30
+ const safeCount = Math.max(0, count)
31
+ const safeRowHeight = Math.max(1, state.rowHeight)
32
+ const safeViewportHeight = Math.max(0, state.viewportHeight)
33
+ const safeOverscan = Math.max(0, state.overscan)
34
+ const visibleCount = Math.max(1, Math.ceil(safeViewportHeight / safeRowHeight))
35
+ const maxStartIndex = Math.max(0, safeCount - visibleCount)
36
+
37
+ const baseStartIndex = clamp(
38
+ Math.floor(state.scrollTop / safeRowHeight),
39
+ 0,
40
+ maxStartIndex
41
+ )
42
+
43
+ const startIndex = clamp(baseStartIndex - safeOverscan, 0, safeCount)
44
+ const endIndex = clamp(
45
+ baseStartIndex + visibleCount + safeOverscan,
46
+ startIndex,
47
+ safeCount
48
+ )
49
+ const offsetTop = startIndex * safeRowHeight
50
+ const totalHeight = safeCount * safeRowHeight
51
+ const visibleSize = (endIndex - startIndex) * safeRowHeight
52
+
53
+ return {
54
+ startIndex,
55
+ endIndex,
56
+ offsetTop,
57
+ bottomPadding: Math.max(0, totalHeight - endIndex * safeRowHeight),
58
+ totalHeight,
59
+ totalSize: totalHeight,
60
+ visibleCount,
61
+ visibleSize
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,57 @@
1
+ export type VirtualizerOptions = {
2
+ rowHeight: number
3
+ viewportHeight: number
4
+ overscan?: number
5
+ }
6
+
7
+ export type VirtualizerState = {
8
+ rowHeight: number
9
+ viewportHeight: number
10
+ overscan: number
11
+ scrollTop: number
12
+ }
13
+
14
+ export type VirtualRange = {
15
+ startIndex: number
16
+ endIndex: number
17
+ offsetTop: number
18
+ bottomPadding: number
19
+ totalSize: number
20
+ visibleSize: number
21
+ }
22
+
23
+ export type FixedVirtualRange = VirtualRange & {
24
+ totalHeight: number
25
+ visibleCount: number
26
+ }
27
+
28
+ export type FixedVirtualizer = {
29
+ setOptions: (next: Partial<VirtualizerState>) => void
30
+ setScrollTop: (scrollTop: number) => void
31
+ setScrollOffset: (scrollTop: number) => void
32
+ getState: (count: number) => FixedVirtualRange
33
+ }
34
+
35
+ export type VariableVirtualizerOptions = {
36
+ sizes: number[]
37
+ viewportSize: number
38
+ overscan?: number
39
+ }
40
+
41
+ export type VariableVirtualizerState = {
42
+ sizes: number[]
43
+ viewportSize: number
44
+ overscan: number
45
+ scrollOffset: number
46
+ }
47
+
48
+ export type VariableVirtualRange = VirtualRange & {
49
+ leftPadding: number
50
+ rightPadding: number
51
+ }
52
+
53
+ export type VariableVirtualizer = {
54
+ setOptions: (next: Partial<VariableVirtualizerState>) => void
55
+ setScrollOffset: (scrollOffset: number) => void
56
+ getState: () => VariableVirtualRange
57
+ }