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

glideapps / glide-data-grid / 7435539780

07 Jan 2024 02:33AM CUT coverage: 90.255% (+3.8%) from 86.42%
7435539780

Pull #810

github

web-flow
Merge c2dbff45d into 3068d54a9
Pull Request #810: 6.0.0

2629 of 3279 branches covered (0.0%)

16144 of 17887 relevant lines covered (90.26%)

2994.04 hits per line

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

55.03
/packages/core/src/common/math.ts
1
/* eslint-disable unicorn/prefer-ternary */
1✔
2
import { itemIsInRect } from "../internal/data-grid/data-grid-lib.js";
1✔
3
import type { FillHandleDirection, Rectangle } from "../internal/data-grid/data-grid-types.js";
1✔
4

1✔
5
export function getClosestRect(
1✔
6
    rect: Rectangle,
13✔
7
    px: number,
13✔
8
    py: number,
13✔
9
    allowedDirections: FillHandleDirection
13✔
10
): Rectangle | undefined {
13✔
11
    if (allowedDirections === "any") return combineRects(rect, { x: px, y: py, width: 1, height: 1 });
13!
12
    if (allowedDirections === "vertical") px = rect.x;
13!
13
    if (allowedDirections === "horizontal") py = rect.y;
13!
14
    // Check if the point is inside the rectangle
13✔
15
    if (itemIsInRect([px, py], rect)) {
13✔
16
        return undefined;
1✔
17
    }
1✔
18

12✔
19
    // Calculate distances to the closest edges
12✔
20
    const distanceToLeft = px - rect.x;
12✔
21
    const distanceToRight = rect.x + rect.width - px;
12✔
22
    const distanceToTop = py - rect.y + 1;
12✔
23
    const distanceToBottom = rect.y + rect.height - py;
12✔
24

12✔
25
    // Find the minimum distance
12✔
26
    const minDistance = Math.min(
12✔
27
        allowedDirections === "vertical" ? Number.MAX_SAFE_INTEGER : distanceToLeft,
13!
28
        allowedDirections === "vertical" ? Number.MAX_SAFE_INTEGER : distanceToRight,
13!
29
        allowedDirections === "horizontal" ? Number.MAX_SAFE_INTEGER : distanceToTop,
13!
30
        allowedDirections === "horizontal" ? Number.MAX_SAFE_INTEGER : distanceToBottom
13!
31
    );
13✔
32

13✔
33
    // eslint-disable-next-line unicorn/prefer-switch
13✔
34
    if (minDistance === distanceToBottom) {
13✔
35
        return { x: rect.x, y: rect.y + rect.height, width: rect.width, height: py - rect.y - rect.height + 1 };
7✔
36
    } else if (minDistance === distanceToTop) {
13✔
37
        return { x: rect.x, y: py, width: rect.width, height: rect.y - py };
1✔
38
    } else if (minDistance === distanceToRight) {
5✔
39
        return { x: rect.x + rect.width, y: rect.y, width: px - rect.x - rect.width + 1, height: rect.height };
2✔
40
    } else {
2✔
41
        return { x: px, y: rect.y, width: rect.x - px, height: rect.height };
2✔
42
    }
2✔
43
}
13✔
44

1✔
45
export function combineRects(a: Rectangle, b: Rectangle): Rectangle {
1✔
46
    const x = Math.min(a.x, b.x);
4✔
47
    const y = Math.min(a.y, b.y);
4✔
48
    const width = Math.max(a.x + a.width, b.x + b.width) - x;
4✔
49
    const height = Math.max(a.y + a.height, b.y + b.height) - y;
4✔
50
    return { x, y, width, height };
4✔
51
}
4✔
52

1✔
53
export function rectContains(a: Rectangle, b: Rectangle): boolean {
1✔
54
    return a.x <= b.x && a.y <= b.y && a.x + a.width >= b.x + b.width && a.y + a.height >= b.y + b.height;
64✔
55
}
64✔
56

1✔
57
/**
1✔
58
 * This function is absolutely critical for the performance of the fill handle and highlight regions. If you don't
1✔
59
 * hug rectanges when they are dashed and they are huge you will get giant GPU stalls. The reason for the mod is
1✔
60
 * if you don't respect the dash stroke size you will get weird artificts as the rectangle changes sizes (the dashes
1✔
61
 * won't line up from one frame to the next)
1✔
62
 */
1✔
63
export function hugRectToTarget(rect: Rectangle, width: number, height: number, mod: number): Rectangle | undefined {
1✔
64
    // Combine checks for early return
43✔
65
    if (
43✔
66
        rect.x > width ||
43✔
67
        rect.y > height ||
40✔
68
        (rect.x < 0 && rect.y < 0 && rect.x + rect.width > width && rect.y + rect.height > height)
38✔
69
    ) {
43✔
70
        return undefined;
6✔
71
    }
6✔
72

37✔
73
    // Direct return if the rectangle is completely within bounds
37✔
74
    if (rect.x >= 0 && rect.y >= 0 && rect.x + rect.width <= width && rect.y + rect.height <= height) {
43✔
75
        return rect;
35✔
76
    }
35✔
77

2✔
78
    // Pre-compute constants for boundaries, we are giving ourselves slop here because we don't want to have weird
2✔
79
    // issues when scaling is applied. 4px is more than enough slop.
2✔
80
    const leftMax = -4;
2✔
81
    const topMax = -4;
2✔
82
    const rightMax = width + 4;
2✔
83
    const bottomMax = height + 4;
2✔
84

2✔
85
    // Pre-compute boundary overflows
2✔
86
    const leftOverflow = leftMax - rect.x;
2✔
87
    const rightOverflow = rect.x + rect.width - rightMax;
2✔
88
    const topOverflow = topMax - rect.y;
2✔
89
    const bottomOverflow = rect.y + rect.height - bottomMax;
2✔
90

2✔
91
    // Adjust if necessary, using simplified calculations
2✔
92
    const left = leftOverflow > 0 ? rect.x + Math.floor(leftOverflow / mod) * mod : rect.x;
43!
93
    const right = rightOverflow > 0 ? rect.x + rect.width - Math.floor(rightOverflow / mod) * mod : rect.x + rect.width;
43!
94
    const top = topOverflow > 0 ? rect.y + Math.floor(topOverflow / mod) * mod : rect.y;
43!
95
    const bottom =
43✔
96
        bottomOverflow > 0 ? rect.y + rect.height - Math.floor(bottomOverflow / mod) * mod : rect.y + rect.height;
43!
97

43✔
98
    return { x: left, y: top, width: right - left, height: bottom - top };
43✔
99
}
43✔
100

1✔
101
interface SplitRect {
1✔
102
    rect: Rectangle;
1✔
103
    clip: Rectangle;
1✔
104
}
1✔
105

1✔
106
export function splitRectIntoRegions(
1✔
107
    rect: Rectangle,
49✔
108
    splitIndicies: readonly [number, number, number, number],
49✔
109
    width: number,
49✔
110
    height: number,
49✔
111
    splitLocations: readonly [number, number, number, number]
49✔
112
): SplitRect[] {
49✔
113
    const [lSplit, tSplit, rSplit, bSplit] = splitIndicies;
49✔
114
    const [lClip, tClip, rClip, bClip] = splitLocations;
49✔
115
    const { x: inX, y: inY, width: inW, height: inH } = rect;
49✔
116

49✔
117
    const result: SplitRect[] = [];
49✔
118

49✔
119
    if (inW <= 0 || inH <= 0) return result;
49!
120

49✔
121
    const inRight = inX + inW;
49✔
122
    const inBottom = inY + inH;
49✔
123

49✔
124
    // The goal is to split the inbound rect into up to 9 regions based on the provided split indicies which are
49✔
125
    // more or less cut lines. The cut lines are whole numbers as is the rect. We are dividing cells on a table.
49✔
126
    // In theory there can be up to 9 regions returned, so we need to be careful to make sure we get them all and
49✔
127
    // not return any empty regions.
49✔
128

49✔
129
    // compute some handy values
49✔
130
    const isOverLeft = inX < lSplit;
49✔
131
    const isOverTop = inY < tSplit;
49✔
132
    const isOverRight = inX + inW > rSplit;
49✔
133
    const isOverBottom = inY + inH > bSplit;
49✔
134

49✔
135
    const isOverCenterVert =
49✔
136
        (inX > lSplit && inX < rSplit) || (inRight > lSplit && inRight < rSplit) || (inX < lSplit && inRight > rSplit);
49!
137
    const isOverCenterHoriz =
49✔
138
        (inY > tSplit && inY < bSplit) ||
49✔
139
        (inBottom > tSplit && inBottom < bSplit) ||
20✔
140
        (inY < tSplit && inBottom > bSplit);
10!
141

49✔
142
    const isOverCenter = isOverCenterVert && isOverCenterHoriz;
49✔
143

49✔
144
    // center
49✔
145
    if (isOverCenter) {
49✔
146
        const x = Math.max(inX, lSplit);
38✔
147
        const y = Math.max(inY, tSplit);
38✔
148
        const right = Math.min(inRight, rSplit);
38✔
149
        const bottom = Math.min(inBottom, bSplit);
38✔
150
        result.push({
38✔
151
            rect: { x, y, width: right - x, height: bottom - y },
38✔
152
            clip: {
38✔
153
                x: lClip,
38✔
154
                y: tClip,
38✔
155
                width: rClip - lClip + 1,
38✔
156
                height: bClip - tClip + 1,
38✔
157
            },
38✔
158
        });
38✔
159
    }
38✔
160

49✔
161
    // top left
49✔
162
    if (isOverLeft && isOverTop) {
49!
163
        const x = inX;
×
164
        const y = inY;
×
165
        const right = Math.min(inRight, lSplit);
×
166
        const bottom = Math.min(inBottom, tSplit);
×
167
        result.push({
×
168
            rect: {
×
169
                x,
×
170
                y,
×
171
                width: right - x,
×
172
                height: bottom - y,
×
173
            },
×
174
            clip: {
×
175
                x: 0,
×
176
                y: 0,
×
177
                width: lClip + 1,
×
178
                height: tClip + 1,
×
179
            },
×
180
        });
×
181
    }
×
182

49✔
183
    // top center
49✔
184
    if (isOverTop && isOverCenterVert) {
49!
185
        const x = Math.max(inX, lSplit);
×
186
        const y = inY;
×
187
        const right = Math.min(inRight, rSplit);
×
188
        const bottom = Math.min(inBottom, tSplit);
×
189
        result.push({
×
190
            rect: {
×
191
                x,
×
192
                y,
×
193
                width: right - x,
×
194
                height: bottom - y,
×
195
            },
×
196
            clip: {
×
197
                x: lClip,
×
198
                y: 0,
×
199
                width: rClip - lClip + 1,
×
200
                height: tClip + 1,
×
201
            },
×
202
        });
×
203
    }
×
204

49✔
205
    // top right
49✔
206
    if (isOverTop && isOverRight) {
49!
207
        const x = Math.max(inX, rSplit);
×
208
        const y = inY;
×
209
        const right = inRight;
×
210
        const bottom = Math.min(inBottom, tSplit);
×
211
        result.push({
×
212
            rect: {
×
213
                x,
×
214
                y,
×
215
                width: right - x,
×
216
                height: bottom - y,
×
217
            },
×
218
            clip: {
×
219
                x: rClip,
×
220
                y: 0,
×
221
                width: width - rClip + 1,
×
222
                height: tClip + 1,
×
223
            },
×
224
        });
×
225
    }
×
226

49✔
227
    // center left
49✔
228
    if (isOverLeft && isOverCenterHoriz) {
49!
229
        const x = inX;
×
230
        const y = Math.max(inY, tSplit);
×
231
        const right = Math.min(inRight, lSplit);
×
232
        const bottom = Math.min(inBottom, bSplit);
×
233
        result.push({
×
234
            rect: {
×
235
                x,
×
236
                y,
×
237
                width: right - x,
×
238
                height: bottom - y,
×
239
            },
×
240
            clip: {
×
241
                x: 0,
×
242
                y: tClip,
×
243
                width: lClip + 1,
×
244
                height: bClip - tClip + 1,
×
245
            },
×
246
        });
×
247
    }
×
248

49✔
249
    // center right
49✔
250
    if (isOverRight && isOverCenterHoriz) {
49!
251
        const x = Math.max(inX, rSplit);
×
252
        const y = Math.max(inY, tSplit);
×
253
        const right = inRight;
×
254
        const bottom = Math.min(inBottom, bSplit);
×
255
        result.push({
×
256
            rect: {
×
257
                x,
×
258
                y,
×
259
                width: right - x,
×
260
                height: bottom - y,
×
261
            },
×
262
            clip: {
×
263
                x: rClip,
×
264
                y: tClip,
×
265
                width: width - rClip + 1,
×
266
                height: bClip - tClip + 1,
×
267
            },
×
268
        });
×
269
    }
×
270

49✔
271
    // bottom left
49✔
272
    if (isOverLeft && isOverBottom) {
49!
273
        const x = inX;
×
274
        const y = Math.max(inY, bSplit);
×
275
        const right = Math.min(inRight, lSplit);
×
276
        const bottom = inBottom;
×
277
        result.push({
×
278
            rect: {
×
279
                x,
×
280
                y,
×
281
                width: right - x,
×
282
                height: bottom - y,
×
283
            },
×
284
            clip: {
×
285
                x: 0,
×
286
                y: bClip,
×
287
                width: lClip + 1,
×
288
                height: height - bClip + 1,
×
289
            },
×
290
        });
×
291
    }
×
292

49✔
293
    // bottom center
49✔
294
    if (isOverBottom && isOverCenterVert) {
49!
295
        const x = Math.max(inX, lSplit);
×
296
        const y = Math.max(inY, bSplit);
×
297
        const right = Math.min(inRight, rSplit);
×
298
        const bottom = inBottom;
×
299
        result.push({
×
300
            rect: {
×
301
                x,
×
302
                y,
×
303
                width: right - x,
×
304
                height: bottom - y,
×
305
            },
×
306
            clip: {
×
307
                x: lClip,
×
308
                y: bClip,
×
309
                width: rClip - lClip + 1,
×
310
                height: height - bClip + 1,
×
311
            },
×
312
        });
×
313
    }
×
314

49✔
315
    // bottom right
49✔
316
    if (isOverRight && isOverBottom) {
49!
317
        const x = Math.max(inX, rSplit);
×
318
        const y = Math.max(inY, bSplit);
×
319
        const right = inRight;
×
320
        const bottom = inBottom;
×
321
        result.push({
×
322
            rect: {
×
323
                x,
×
324
                y,
×
325
                width: right - x,
×
326
                height: bottom - y,
×
327
            },
×
328
            clip: {
×
329
                x: rClip,
×
330
                y: bClip,
×
331
                width: width - rClip + 1,
×
332
                height: height - bClip + 1,
×
333
            },
×
334
        });
×
335
    }
×
336

49✔
337
    return result;
49✔
338
}
49✔
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