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

glideapps / glide-data-grid / 15751398442

19 Jun 2025 06:45AM UTC coverage: 91.287% (-0.008%) from 91.295%
15751398442

Pull #1038

github

web-flow
Merge 819601d16 into 62f422ab7
Pull Request #1038: feat: provideEditorCallback access cell location

2922 of 3612 branches covered (80.9%)

13 of 13 new or added lines in 3 files covered. (100.0%)

396 existing lines in 10 files now uncovered.

17874 of 19580 relevant lines covered (91.29%)

3105.46 hits per line

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

98.68
/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts
1
/* eslint-disable sonarjs/no-duplicate-string */
1✔
2
/* eslint-disable unicorn/no-for-loop */
1✔
3
import { type GridSelection, type InnerGridCell, type Item } from "../data-grid-types.js";
1✔
4
import { getStickyWidth, type MappedGridColumn, computeBounds, getFreezeTrailingHeight } from "./data-grid-lib.js";
1✔
5
import { type FullTheme } from "../../../common/styles.js";
1✔
6
import { blend, withAlpha } from "../color-parser.js";
1✔
7
import { hugRectToTarget, intersectRect, rectContains, splitRectIntoRegions } from "../../../common/math.js";
1✔
8
import { getSpanBounds, walkColumns, walkRowsInCol } from "./data-grid-render.walk.js";
1✔
9
import { type Highlight } from "./data-grid-render.cells.js";
1✔
10

1✔
11
export function drawHighlightRings(
1✔
12
    ctx: CanvasRenderingContext2D,
453✔
13
    width: number,
453✔
14
    height: number,
453✔
15
    cellXOffset: number,
453✔
16
    cellYOffset: number,
453✔
17
    translateX: number,
453✔
18
    translateY: number,
453✔
19
    mappedColumns: readonly MappedGridColumn[],
453✔
20
    freezeColumns: number,
453✔
21
    headerHeight: number,
453✔
22
    groupHeaderHeight: number,
453✔
23
    rowHeight: number | ((index: number) => number),
453✔
24
    freezeTrailingRows: number,
453✔
25
    rows: number,
453✔
26
    allHighlightRegions: readonly Highlight[] | undefined,
453✔
27
    theme: FullTheme
453✔
28
): (() => void) | undefined {
453✔
29
    const highlightRegions = allHighlightRegions?.filter(x => x.style !== "no-outline");
453✔
30

453✔
31
    if (highlightRegions === undefined || highlightRegions.length === 0) return undefined;
453✔
32

243✔
33
    const freezeLeft = getStickyWidth(mappedColumns);
243✔
34
    const freezeBottom = getFreezeTrailingHeight(rows, freezeTrailingRows, rowHeight);
243✔
35
    const splitIndicies = [freezeColumns, 0, mappedColumns.length, rows - freezeTrailingRows] as const;
243✔
36
    const splitLocations = [freezeLeft, 0, width, height - freezeBottom] as const;
243✔
37

243✔
38
    const drawRects = highlightRegions.map(h => {
243✔
39
        const r = h.range;
297✔
40
        const style = h.style ?? "dashed";
297✔
41

297✔
42
        return splitRectIntoRegions(r, splitIndicies, width, height, splitLocations).map(arg => {
297✔
43
            const rect = arg.rect;
297✔
44
            const topLeftBounds = computeBounds(
297✔
45
                rect.x,
297✔
46
                rect.y,
297✔
47
                width,
297✔
48
                height,
297✔
49
                groupHeaderHeight,
297✔
50
                headerHeight + groupHeaderHeight,
297✔
51
                cellXOffset,
297✔
52
                cellYOffset,
297✔
53
                translateX,
297✔
54
                translateY,
297✔
55
                rows,
297✔
56
                freezeColumns,
297✔
57
                freezeTrailingRows,
297✔
58
                mappedColumns,
297✔
59
                rowHeight
297✔
60
            );
297✔
61
            const bottomRightBounds =
297✔
62
                rect.width === 1 && rect.height === 1
297✔
63
                    ? topLeftBounds
240✔
64
                    : computeBounds(
57✔
65
                          rect.x + rect.width - 1,
57✔
66
                          rect.y + rect.height - 1,
57✔
67
                          width,
57✔
68
                          height,
57✔
69
                          groupHeaderHeight,
57✔
70
                          headerHeight + groupHeaderHeight,
57✔
71
                          cellXOffset,
57✔
72
                          cellYOffset,
57✔
73
                          translateX,
57✔
74
                          translateY,
57✔
75
                          rows,
57✔
76
                          freezeColumns,
57✔
77
                          freezeTrailingRows,
57✔
78
                          mappedColumns,
57✔
79
                          rowHeight
57✔
80
                      );
57✔
81

297✔
82
            if (rect.x + rect.width >= mappedColumns.length) {
297✔
83
                bottomRightBounds.width -= 1;
14✔
84
            }
14✔
85
            if (rect.y + rect.height >= rows) {
297✔
86
                bottomRightBounds.height -= 1;
4✔
87
            }
4✔
88
            return {
297✔
89
                color: h.color,
297✔
90
                style,
297✔
91
                clip: arg.clip,
297✔
92
                rect: hugRectToTarget(
297✔
93
                    {
297✔
94
                        x: topLeftBounds.x,
297✔
95
                        y: topLeftBounds.y,
297✔
96
                        width: bottomRightBounds.x + bottomRightBounds.width - topLeftBounds.x,
297✔
97
                        height: bottomRightBounds.y + bottomRightBounds.height - topLeftBounds.y,
297✔
98
                    },
297✔
99
                    width,
297✔
100
                    height,
297✔
101
                    8
297✔
102
                ),
297✔
103
            };
297✔
104
        });
297✔
105
    });
243✔
106

243✔
107
    const drawCb = () => {
243✔
108
        ctx.lineWidth = 1;
451✔
109

451✔
110
        let dashed = false;
451✔
111

451✔
112
        for (const dr of drawRects) {
451✔
113
            for (const s of dr) {
542✔
114
                if (
542✔
115
                    s?.rect !== undefined &&
542✔
116
                    intersectRect(0, 0, width, height, s.rect.x, s.rect.y, s.rect.width, s.rect.height)
523✔
117
                ) {
542✔
118
                    const wasDashed: boolean = dashed;
523✔
119
                    const needsClip = !rectContains(s.clip, s.rect);
523✔
120
                    ctx.beginPath();
523✔
121
                    if (needsClip) {
523✔
122
                        ctx.save();
27✔
123
                        ctx.rect(s.clip.x, s.clip.y, s.clip.width, s.clip.height);
27✔
124
                        ctx.clip();
27✔
125
                    }
27✔
126
                    if (s.style === "dashed" && !dashed) {
523✔
127
                        ctx.setLineDash([5, 3]);
24✔
128
                        dashed = true;
24✔
129
                    } else if ((s.style === "solid" || s.style === "solid-outline") && dashed) {
523✔
130
                        ctx.setLineDash([]);
18✔
131
                        dashed = false;
18✔
132
                    }
18✔
133
                    ctx.strokeStyle =
523✔
134
                        s.style === "solid-outline"
523✔
135
                            ? blend(blend(s.color, theme.borderColor), theme.bgCell)
499✔
136
                            : withAlpha(s.color, 1);
24✔
137
                    ctx.closePath();
523✔
138
                    ctx.strokeRect(s.rect.x + 0.5, s.rect.y + 0.5, s.rect.width - 1, s.rect.height - 1);
523✔
139
                    if (needsClip) {
523✔
140
                        ctx.restore();
27✔
141
                        dashed = wasDashed;
27✔
142
                    }
27✔
143
                }
523✔
144
            }
542✔
145
        }
542✔
146

451✔
147
        if (dashed) {
451✔
148
            ctx.setLineDash([]);
6✔
149
        }
6✔
150
    };
451✔
151

243✔
152
    drawCb();
243✔
153
    return drawCb;
243✔
154
}
243✔
155

1✔
156
export function drawColumnResizeOutline(
1✔
157
    ctx: CanvasRenderingContext2D,
14✔
158
    yOffset: number,
14✔
159
    xOffset: number,
14✔
160
    height: number,
14✔
161
    style: string
14✔
162
) {
14✔
163
    ctx.beginPath();
14✔
164
    ctx.moveTo(yOffset, xOffset);
14✔
165
    ctx.lineTo(yOffset, height);
14✔
166

14✔
167
    ctx.lineWidth = 2;
14✔
168
    ctx.strokeStyle = style;
14✔
169

14✔
170
    ctx.stroke();
14✔
171

14✔
172
    ctx.globalAlpha = 1;
14✔
173
}
14✔
174

1✔
175
export function drawFillHandle(
1✔
176
    ctx: CanvasRenderingContext2D,
451✔
177
    width: number,
451✔
178
    height: number,
451✔
179
    cellYOffset: number,
451✔
180
    translateX: number,
451✔
181
    translateY: number,
451✔
182
    effectiveCols: readonly MappedGridColumn[],
451✔
183
    allColumns: readonly MappedGridColumn[],
451✔
184
    theme: FullTheme,
451✔
185
    totalHeaderHeight: number,
451✔
186
    selectedCell: GridSelection,
451✔
187
    getRowHeight: (row: number) => number,
451✔
188
    getCellContent: (cell: Item) => InnerGridCell,
451✔
189
    freezeTrailingRows: number,
451✔
190
    hasAppendRow: boolean,
451✔
191
    fillHandle: boolean,
451✔
192
    rows: number
451✔
193
): (() => void) | undefined {
451✔
194
    if (selectedCell.current === undefined) return undefined;
451✔
195

238✔
196
    const range = selectedCell.current.range;
238✔
197
    const currentItem = selectedCell.current.cell;
238✔
198
    const fillHandleTarget = [range.x + range.width - 1, range.y + range.height - 1];
238✔
199

238✔
200
    // if the currentItem row greater than rows and the fill handle row is greater than rows, we dont need to draw
238✔
201
    if (currentItem[1] >= rows && fillHandleTarget[1] >= rows) return undefined;
451✔
202

237✔
203
    const mustDraw = effectiveCols.some(c => c.sourceIndex === currentItem[0] || c.sourceIndex === fillHandleTarget[0]);
237✔
204
    if (!mustDraw) return undefined;
418✔
205
    const [targetCol, targetRow] = selectedCell.current.cell;
232✔
206
    const cell = getCellContent(selectedCell.current.cell);
232✔
207
    const targetColSpan = cell.span ?? [targetCol, targetCol];
416✔
208

451✔
209
    const isStickyRow = targetRow >= rows - freezeTrailingRows;
451✔
210
    const stickRowHeight =
451✔
211
        freezeTrailingRows > 0 && !isStickyRow
451✔
212
            ? getFreezeTrailingHeight(rows, freezeTrailingRows, getRowHeight) - 1
230✔
213
            : 0;
2✔
214

451✔
215
    const fillHandleRow = fillHandleTarget[1];
451✔
216

451✔
217
    let drawHandleCb: (() => void) | undefined = undefined;
451✔
218

451✔
219
    walkColumns(
451✔
220
        effectiveCols,
451✔
221
        cellYOffset,
451✔
222
        translateX,
451✔
223
        translateY,
451✔
224
        totalHeaderHeight,
451✔
225
        (col, drawX, colDrawY, clipX, startRow) => {
451✔
226
            clipX;
2,041✔
227
            if (col.sticky && targetCol > col.sourceIndex) return;
2,041✔
228

2,022✔
229
            const isBeforeTarget = col.sourceIndex < targetColSpan[0];
2,022✔
230
            const isAfterTarget = col.sourceIndex > targetColSpan[1];
2,022✔
231

2,022✔
232
            const isFillHandleCol = col.sourceIndex === fillHandleTarget[0];
2,022✔
233

2,022✔
234
            if (!isFillHandleCol && (isBeforeTarget || isAfterTarget)) {
2,041✔
235
                // we dont need to do any drawing on this column but may yet need to draw
1,773✔
236
                return;
1,773✔
237
            }
1,773✔
238

249✔
239
            walkRowsInCol(
249✔
240
                startRow,
249✔
241
                colDrawY,
249✔
242
                height,
249✔
243
                rows,
249✔
244
                getRowHeight,
249✔
245
                freezeTrailingRows,
249✔
246
                hasAppendRow,
249✔
247
                undefined,
249✔
248
                (drawY, row, rh) => {
249✔
249
                    if (row !== targetRow && row !== fillHandleRow) return;
6,869✔
250

281✔
251
                    let cellX = drawX;
281✔
252
                    let cellWidth = col.width;
281✔
253

281✔
254
                    if (cell.span !== undefined) {
6,869✔
255
                        const areas = getSpanBounds(cell.span, drawX, drawY, col.width, rh, col, allColumns);
2✔
256
                        const area = col.sticky ? areas[0] : areas[1];
2!
257

2✔
258
                        if (area !== undefined) {
2✔
259
                            cellX = area.x;
2✔
260
                            cellWidth = area.width;
2✔
261
                        }
2✔
262
                    }
2✔
263

281✔
264
                    const doHandle = row === fillHandleRow && isFillHandleCol && fillHandle;
6,869✔
265

6,869✔
266
                    if (doHandle) {
6,869✔
267
                        drawHandleCb = () => {
32✔
268
                            if (clipX > cellX && !col.sticky) {
48!
269
                                ctx.beginPath();
×
270
                                ctx.rect(clipX, 0, width - clipX, height);
×
UNCOV
271
                                ctx.clip();
×
UNCOV
272
                            }
×
273
                            ctx.beginPath();
48✔
274
                            ctx.rect(cellX + cellWidth - 4, drawY + rh - 4, 4, 4);
48✔
275
                            ctx.fillStyle = col.themeOverride?.accentColor ?? theme.accentColor;
48!
276
                            ctx.fill();
48✔
277
                        };
48✔
278
                    }
32✔
279
                    return drawHandleCb !== undefined;
281✔
280
                }
6,869✔
281
            );
249✔
282

249✔
283
            return drawHandleCb !== undefined;
249✔
284
        }
2,041✔
285
    );
451✔
286

451✔
287
    if (drawHandleCb === undefined) return undefined;
451✔
288

32✔
289
    const result = () => {
32✔
290
        ctx.save();
48✔
291
        ctx.beginPath();
48✔
292
        ctx.rect(0, totalHeaderHeight, width, height - totalHeaderHeight - stickRowHeight);
48✔
293
        ctx.clip();
48✔
294

48✔
295
        drawHandleCb?.();
48✔
296

48✔
297
        ctx.restore();
48✔
298
    };
48✔
299

32✔
300
    result();
32✔
301

32✔
302
    return result;
32✔
303
}
32✔
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

© 2026 Coveralls, Inc