• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

glideapps / glide-data-grid / 7607548630

22 Jan 2024 06:58AM CUT coverage: 92.025% (+0.003%) from 92.022%
7607548630

push

github

jassmith
Start working on trailing freeze column support

2773 of 3399 branches covered (0.0%)

16756 of 18208 relevant lines covered (92.03%)

3194.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.07
/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts
1
/* eslint-disable sonarjs/no-duplicate-string */
1✔
2
/* eslint-disable unicorn/no-for-loop */
1✔
3
import { deepEqual } from "../../../common/support.js";
1✔
4
import { type Rectangle } from "../data-grid-types.js";
1✔
5
import { getStickyWidth, type MappedGridColumn, getFreezeTrailingHeight } from "./data-grid-lib.js";
1✔
6
import { walkColumns } from "./data-grid-render.walk.js";
1✔
7
import type { DrawGridArg } from "./draw-grid-arg.js";
1✔
8

1✔
9
export interface BlitData {
1✔
10
    readonly cellXOffset: number;
1✔
11
    readonly cellYOffset: number;
1✔
12
    readonly translateX: number;
1✔
13
    readonly translateY: number;
1✔
14
    readonly mustDrawFocusOnHeader: boolean;
1✔
15
    readonly mustDrawHighlightRingsOnHeader: boolean;
1✔
16
    readonly lastBuffer: "a" | "b" | undefined;
1✔
17
    aBufferScroll: [boolean, boolean] | undefined;
1✔
18
    bBufferScroll: [boolean, boolean] | undefined;
1✔
19
}
1✔
20

1✔
21
export function blitLastFrame(
1✔
22
    ctx: CanvasRenderingContext2D,
4✔
23
    blitSource: HTMLCanvasElement,
4✔
24
    blitSourceScroll: [boolean, boolean] | undefined,
4✔
25
    targetScroll: [boolean, boolean] | undefined,
4✔
26
    last: BlitData,
4✔
27
    cellXOffset: number,
4✔
28
    cellYOffset: number,
4✔
29
    translateX: number,
4✔
30
    translateY: number,
4✔
31
    freezeTrailingRows: number,
4✔
32
    width: number,
4✔
33
    height: number,
4✔
34
    rows: number,
4✔
35
    totalHeaderHeight: number,
4✔
36
    dpr: number,
4✔
37
    mappedColumns: readonly MappedGridColumn[],
4✔
38
    effectiveCols: readonly MappedGridColumn[],
4✔
39
    getRowHeight: number | ((r: number) => number),
4✔
40
    doubleBuffer: boolean
4✔
41
): {
4✔
42
    regions: Rectangle[];
4✔
43
} {
4✔
44
    const drawRegions: Rectangle[] = [];
4✔
45

4✔
46
    ctx.imageSmoothingEnabled = false;
4✔
47
    const minY = Math.min(last.cellYOffset, cellYOffset);
4✔
48
    const maxY = Math.max(last.cellYOffset, cellYOffset);
4✔
49
    let deltaY = 0;
4✔
50
    if (typeof getRowHeight === "number") {
4✔
51
        deltaY += (maxY - minY) * getRowHeight;
4✔
52
    } else {
4!
53
        for (let i = minY; i < maxY; i++) {
×
54
            deltaY += getRowHeight(i);
×
55
        }
×
56
    }
×
57
    if (cellYOffset > last.cellYOffset) {
4✔
58
        deltaY = -deltaY;
1✔
59
    }
1✔
60
    deltaY += translateY - last.translateY;
4✔
61

4✔
62
    const minX = Math.min(last.cellXOffset, cellXOffset);
4✔
63
    const maxX = Math.max(last.cellXOffset, cellXOffset);
4✔
64
    let deltaX = 0;
4✔
65
    for (let i = minX; i < maxX; i++) {
4✔
66
        deltaX += mappedColumns[i].width;
2✔
67
    }
2✔
68
    if (cellXOffset > last.cellXOffset) {
4✔
69
        deltaX = -deltaX;
1✔
70
    }
1✔
71
    deltaX += translateX - last.translateX;
4✔
72

4✔
73
    let stickyWidth = getStickyWidth(effectiveCols);
4✔
74
    if (stickyWidth > 0) stickyWidth++;
4!
75

4✔
76
    if (deltaX !== 0 && deltaY !== 0) {
4!
77
        return {
×
78
            regions: [],
×
79
        };
×
80
    }
×
81

4✔
82
    const freezeTrailingRowsHeight =
4✔
83
        freezeTrailingRows > 0 ? getFreezeTrailingHeight(rows, freezeTrailingRows, getRowHeight) : 0;
4!
84

4✔
85
    const blitWidth = width - stickyWidth - Math.abs(deltaX);
4✔
86
    const blitHeight = height - totalHeaderHeight - freezeTrailingRowsHeight - Math.abs(deltaY) - 1;
4✔
87

4✔
88
    if (blitWidth > 150 && blitHeight > 150) {
4✔
89
        const args = {
4✔
90
            sx: 0,
4✔
91
            sy: 0,
4✔
92
            sw: width * dpr,
4✔
93
            sh: height * dpr,
4✔
94
            dx: 0,
4✔
95
            dy: 0,
4✔
96
            dw: width * dpr,
4✔
97
            dh: height * dpr,
4✔
98
        };
4✔
99

4✔
100
        // blit Y
4✔
101
        if (deltaY > 0) {
4✔
102
            // scrolling up
1✔
103
            args.sy = (totalHeaderHeight + 1) * dpr;
1✔
104
            args.sh = blitHeight * dpr;
1✔
105
            args.dy = (deltaY + totalHeaderHeight + 1) * dpr;
1✔
106
            args.dh = blitHeight * dpr;
1✔
107

1✔
108
            drawRegions.push({
1✔
109
                x: 0,
1✔
110
                y: totalHeaderHeight,
1✔
111
                width: width,
1✔
112
                height: deltaY + 1,
1✔
113
            });
1✔
114
        } else if (deltaY < 0) {
4✔
115
            // scrolling down
1✔
116
            args.sy = (-deltaY + totalHeaderHeight + 1) * dpr;
1✔
117
            args.sh = blitHeight * dpr;
1✔
118
            args.dy = (totalHeaderHeight + 1) * dpr;
1✔
119
            args.dh = blitHeight * dpr;
1✔
120

1✔
121
            drawRegions.push({
1✔
122
                x: 0,
1✔
123
                y: height + deltaY - freezeTrailingRowsHeight,
1✔
124
                width: width,
1✔
125
                height: -deltaY + freezeTrailingRowsHeight,
1✔
126
            });
1✔
127
        }
1✔
128

4✔
129
        // blit X
4✔
130
        if (deltaX > 0) {
4✔
131
            // pixels moving right
1✔
132
            args.sx = stickyWidth * dpr;
1✔
133
            args.sw = blitWidth * dpr;
1✔
134
            args.dx = (deltaX + stickyWidth) * dpr;
1✔
135
            args.dw = blitWidth * dpr;
1✔
136

1✔
137
            drawRegions.push({
1✔
138
                x: stickyWidth - 1,
1✔
139
                y: 0,
1✔
140
                width: deltaX + 2, // extra width to account for first col not drawing a left side border
1✔
141
                height: height,
1✔
142
            });
1✔
143
        } else if (deltaX < 0) {
4✔
144
            // pixels moving left
1✔
145
            args.sx = (stickyWidth - deltaX) * dpr;
1✔
146
            args.sw = blitWidth * dpr;
1✔
147
            args.dx = stickyWidth * dpr;
1✔
148
            args.dw = blitWidth * dpr;
1✔
149

1✔
150
            drawRegions.push({
1✔
151
                x: width + deltaX,
1✔
152
                y: 0,
1✔
153
                width: -deltaX,
1✔
154
                height: height,
1✔
155
            });
1✔
156
        }
1✔
157

4✔
158
        ctx.setTransform(1, 0, 0, 1, 0, 0);
4✔
159
        if (doubleBuffer) {
4!
160
            if (
×
161
                stickyWidth > 0 &&
×
162
                deltaX !== 0 &&
×
163
                deltaY === 0 &&
×
164
                (targetScroll === undefined || blitSourceScroll?.[1] !== false)
×
165
            ) {
×
166
                // When double buffering the freeze columns can be offset by a couple pixels vertically between the two
×
167
                // buffers. We don't want to redraw them so we need to make sure to copy them between the buffers.
×
168
                const w = stickyWidth * dpr;
×
169
                const h = height * dpr;
×
170
                ctx.drawImage(blitSource, 0, 0, w, h, 0, 0, w, h);
×
171
            }
×
172
            if (
×
173
                freezeTrailingRowsHeight > 0 &&
×
174
                deltaX === 0 &&
×
175
                deltaY !== 0 &&
×
176
                (targetScroll === undefined || blitSourceScroll?.[0] !== false)
×
177
            ) {
×
178
                const y = (height - freezeTrailingRowsHeight) * dpr;
×
179
                const w = width * dpr;
×
180
                const h = freezeTrailingRowsHeight * dpr;
×
181
                ctx.drawImage(blitSource, 0, y, w, h, 0, y, w, h);
×
182
            }
×
183
        }
×
184
        ctx.drawImage(blitSource, args.sx, args.sy, args.sw, args.sh, args.dx, args.dy, args.dw, args.dh);
4✔
185
        ctx.scale(dpr, dpr);
4✔
186
    }
4✔
187
    ctx.imageSmoothingEnabled = true;
4✔
188

4✔
189
    return {
4✔
190
        regions: drawRegions,
4✔
191
    };
4✔
192
}
4✔
193

1✔
194
export function blitResizedCol(
1✔
195
    last: BlitData,
×
196
    cellXOffset: number,
×
197
    cellYOffset: number,
×
198
    translateX: number,
×
199
    translateY: number,
×
200
    width: number,
×
201
    height: number,
×
202
    totalHeaderHeight: number,
×
203
    effectiveCols: readonly MappedGridColumn[],
×
204
    resizedIndex: number
×
205
) {
×
206
    const drawRegions: Rectangle[] = [];
×
207

×
208
    // ctx.imageSmoothingEnabled = false;
×
209

×
210
    if (
×
211
        cellXOffset !== last.cellXOffset ||
×
212
        cellYOffset !== last.cellYOffset ||
×
213
        translateX !== last.translateX ||
×
214
        translateY !== last.translateY
×
215
    ) {
×
216
        return drawRegions;
×
217
    }
×
218

×
219
    walkColumns(effectiveCols, cellYOffset, translateX, translateY, totalHeaderHeight, (c, drawX, _drawY, clipX) => {
×
220
        if (c.sourceIndex === resizedIndex) {
×
221
            const x = Math.max(drawX, clipX) + 1;
×
222
            drawRegions.push({
×
223
                x,
×
224
                y: 0,
×
225
                width: width - x,
×
226
                height,
×
227
            });
×
228
            return true;
×
229
        }
×
230
    });
×
231
    return drawRegions;
×
232
}
×
233

1✔
234
export function computeCanBlit(current: DrawGridArg, last: DrawGridArg | undefined): boolean | number {
1✔
235
    if (last === undefined) return false;
482✔
236
    if (
411✔
237
        current.width !== last.width ||
411✔
238
        current.height !== last.height ||
264✔
239
        current.theme !== last.theme ||
264✔
240
        current.headerHeight !== last.headerHeight ||
264✔
241
        current.rowHeight !== last.rowHeight ||
264✔
242
        current.rows !== last.rows ||
264✔
243
        current.freezeColumns !== last.freezeColumns ||
262✔
244
        current.getRowThemeOverride !== last.getRowThemeOverride ||
262✔
245
        current.isFocused !== last.isFocused ||
262✔
246
        current.isResizing !== last.isResizing ||
219✔
247
        current.verticalBorder !== last.verticalBorder ||
205✔
248
        current.getCellContent !== last.getCellContent ||
205✔
249
        current.highlightRegions !== last.highlightRegions ||
181✔
250
        current.selection !== last.selection ||
142✔
251
        current.dragAndDropState !== last.dragAndDropState ||
16✔
252
        current.prelightCells !== last.prelightCells ||
15✔
253
        current.touchMode !== last.touchMode ||
12✔
254
        current.maxScaleFactor !== last.maxScaleFactor
4✔
255
    ) {
482✔
256
        return false;
407✔
257
    }
407✔
258
    if (current.mappedColumns !== last.mappedColumns) {
456!
259
        if (current.mappedColumns.length > 100 || current.mappedColumns.length !== last.mappedColumns.length) {
×
260
            // The array is big, let's just redraw the damned thing rather than check these all. Or the number of cols
×
261
            // changed in which case I dont want to figure out what happened.
×
262
            return false;
×
263
        }
×
264
        // We want to know if only one column has resized. If this is the case we can do a special left/right sliding
×
265
        // blit. Or just not redraw shit on the left.
×
266
        let resized: number | undefined;
×
267
        for (let i = 0; i < current.mappedColumns.length; i++) {
×
268
            const curCol = current.mappedColumns[i];
×
269
            const lastCol = last.mappedColumns[i];
×
270

×
271
            if (deepEqual(curCol, lastCol)) continue;
×
272

×
273
            // two columns changed, abort
×
274
            if (resized !== undefined) return false;
×
275

×
276
            if (curCol.width === lastCol.width) return false;
×
277

×
278
            const { width, ...curRest } = curCol;
×
279
            const { width: lastWidth, ...lastRest } = lastCol;
×
280

×
281
            // more than width changed, abort
×
282
            if (!deepEqual(curRest, lastRest)) return false;
×
283
            resized = i;
×
284
        }
×
285
        if (resized === undefined) {
×
286
            // we never found a changed column, cool, we can blit
×
287
            return true;
×
288
        }
×
289
        return resized;
×
290
    }
✔
291
    return true;
4✔
292
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc