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

glideapps / glide-data-grid / 7244578514

18 Dec 2023 06:42AM UTC coverage: 90.724% (+4.3%) from 86.42%
7244578514

Pull #810

github

jassmith
5.99.9-beta7
Pull Request #810: 6.0.0

2552 of 3197 branches covered (0.0%)

3002 of 3411 new or added lines in 61 files covered. (88.01%)

265 existing lines in 12 files now uncovered.

15581 of 17174 relevant lines covered (90.72%)

2978.07 hits per line

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

93.07
/packages/core/src/common/utils.tsx
1
import * as React from "react";
1✔
2
import debounce from "lodash/debounce.js";
1✔
3

1✔
4
export function useEventListener<K extends keyof HTMLElementEventMap>(
1✔
5
    eventName: K,
12,553✔
6
    handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
12,553✔
7
    element: HTMLElement | Window | null,
12,553✔
8
    passive: boolean,
12,553✔
9
    capture = false
12,553✔
10
) {
12,553✔
11
    // Create a ref that stores handler
12,553✔
12
    const savedHandler = React.useRef<(this: HTMLElement, ev: HTMLElementEventMap[K]) => any>();
12,553✔
13

12,553✔
14
    // Update ref.current value if handler changes.
12,553✔
15
    // This allows our effect below to always get latest handler ...
12,553✔
16
    // ... without us needing to pass it in effect deps array ...
12,553✔
17
    // ... and potentially cause effect to re-run every render.
12,553✔
18
    savedHandler.current = handler;
12,553✔
19
    React.useEffect(
12,553✔
20
        () => {
12,553✔
21
            // Make sure element supports addEventListener
3,203✔
22
            if (element === null || element.addEventListener === undefined) return;
3,203✔
23
            const el = element as HTMLElement;
2,333✔
24

2,333✔
25
            // Create event listener that calls handler function stored in ref
2,333✔
26
            const eventListener = (event: HTMLElementEventMap[K]) => {
2,333✔
27
                savedHandler.current?.call(el, event);
452✔
28
            };
452✔
29

2,333✔
30
            el.addEventListener(eventName, eventListener, { passive, capture });
2,333✔
31

2,333✔
32
            // Remove event listener on cleanup
2,333✔
33
            return () => {
2,333✔
34
                el.removeEventListener(eventName, eventListener, { capture });
2,333✔
35
            };
2,333✔
36
        },
3,203✔
37
        [eventName, element, passive, capture] // Re-run if eventName or element changes
12,553✔
38
    );
12,553✔
39
}
12,553✔
40

1✔
41
export function whenDefined<T>(obj: any, result: T) {
1✔
42
    return obj === undefined ? undefined : result;
3,906✔
43
}
3,906✔
44

1✔
45
const PI = Math.PI;
1✔
46
export function degreesToRadians(degrees: number) {
1✔
47
    return (degrees * PI) / 180;
20,428✔
48
}
20,428✔
49

1✔
50
export const getSquareBB = (posX: number, posY: number, squareSideLength: number) => ({
1✔
51
    x1: posX - squareSideLength / 2,
8,233✔
52
    y1: posY - squareSideLength / 2,
8,233✔
53
    x2: posX + squareSideLength / 2,
8,233✔
54
    y2: posY + squareSideLength / 2,
8,233✔
55
});
8,233✔
56

1✔
57
export const getSquareXPosFromAlign = (
1✔
58
    alignment: "left" | "center" | "right",
8,233✔
59
    containerX: number,
8,233✔
60
    containerWidth: number,
8,233✔
61
    horizontalPadding: number,
8,233✔
62
    squareWidth: number
8,233✔
63
) => {
8,233✔
64
    switch (alignment) {
8,233✔
65
        case "left":
8,233!
66
            return Math.floor(containerX) + horizontalPadding + squareWidth / 2;
×
67
        case "center":
8,233✔
68
            return Math.floor(containerX + containerWidth / 2);
8,233✔
69
        case "right":
8,233!
70
            return Math.floor(containerX + containerWidth) - horizontalPadding - squareWidth / 2;
×
71
    }
8,233✔
72
};
8,233✔
73
export const getSquareWidth = (maxSize: number, containerHeight: number, verticalPadding: number) =>
1✔
74
    Math.min(maxSize, containerHeight - verticalPadding * 2);
8,233✔
75

1✔
76
type BoundingBox = { x1: number; y1: number; x2: number; y2: number };
1✔
77
export const pointIsWithinBB = (x: number, y: number, bb: BoundingBox) =>
1✔
78
    bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2;
8,233✔
79

1✔
80
/**
1✔
81
 * The input provided to a sprite function.
1✔
82
 *
1✔
83
 * @category Columns
1✔
84
 */
1✔
85
export interface SpriteProps {
1✔
86
    fgColor: string;
1✔
87
    bgColor: string;
1✔
88
}
1✔
89

1✔
90
export const EditPencil: React.FunctionComponent<Partial<SpriteProps>> = (props: Partial<SpriteProps>) => {
1✔
91
    const fg = props.fgColor ?? "currentColor";
4✔
92
    return (
4✔
93
        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4✔
94
            <path
4✔
95
                d="M12.7073 7.05029C7.87391 11.8837 10.4544 9.30322 6.03024 13.7273C5.77392 13.9836 5.58981 14.3071 5.50189 14.6587L4.52521 18.5655C4.38789 19.1148 4.88543 19.6123 5.43472 19.475L9.34146 18.4983C9.69313 18.4104 10.0143 18.2286 10.2706 17.9722L16.9499 11.2929"
4✔
96
                stroke={fg}
4✔
97
                strokeWidth="1.5"
4✔
98
                strokeLinecap="round"
4✔
99
                strokeLinejoin="round"
4✔
100
                fill="none"
4✔
101
                vectorEffect="non-scaling-stroke"
4✔
102
            />
4✔
103
            <path
4✔
104
                d="M20.4854 4.92901L19.0712 3.5148C18.2901 2.73375 17.0238 2.73375 16.2428 3.5148L14.475 5.28257C15.5326 7.71912 16.4736 8.6278 18.7176 9.52521L20.4854 7.75744C21.2665 6.97639 21.2665 5.71006 20.4854 4.92901Z"
4✔
105
                stroke={fg}
4✔
106
                strokeWidth="1.5"
4✔
107
                strokeLinecap="round"
4✔
108
                strokeLinejoin="round"
4✔
109
                fill="none"
4✔
110
                vectorEffect="non-scaling-stroke"
4✔
111
            />
4✔
112
        </svg>
4✔
113
    );
4✔
114
};
4✔
115

1✔
116
export const Checkmark: React.FunctionComponent<Partial<SpriteProps>> = (props: Partial<SpriteProps>) => {
1✔
117
    const fg = props.fgColor ?? "currentColor";
×
UNCOV
118

×
119
    return (
×
UNCOV
120
        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
×
UNCOV
121
            <path
×
UNCOV
122
                d="M19 6L10.3802 17L5.34071 11.8758"
×
UNCOV
123
                vectorEffect="non-scaling-stroke"
×
UNCOV
124
                stroke={fg}
×
UNCOV
125
                strokeWidth="1.5"
×
UNCOV
126
                strokeLinecap="round"
×
UNCOV
127
                strokeLinejoin="round"
×
UNCOV
128
            />
×
UNCOV
129
        </svg>
×
UNCOV
130
    );
×
UNCOV
131
};
×
132

1✔
133
export function useDebouncedMemo<T>(factory: () => T, deps: React.DependencyList | undefined, time: number): T {
1✔
134
    const [state, setState] = React.useState(factory);
776✔
135

776✔
136
    const mountedRef = React.useRef(true);
776✔
137
    React.useEffect(
776✔
138
        () => () => {
776✔
139
            mountedRef.current = false;
145✔
140
        },
145✔
141
        []
776✔
142
    );
776✔
143

776✔
144
    const debouncedSetState = React.useRef<typeof setState>(
776✔
145
        debounce(x => {
776✔
146
            if (mountedRef.current) {
216✔
147
                setState(x);
202✔
148
            }
202✔
149
        }, time)
776✔
150
    );
776✔
151

776✔
152
    React.useLayoutEffect(() => {
776✔
153
        if (mountedRef.current) {
471✔
154
            debouncedSetState.current(() => factory());
471✔
155
        }
471✔
156
        // eslint-disable-next-line react-hooks/exhaustive-deps
471✔
157
    }, deps);
776✔
158

776✔
159
    return state;
776✔
160
}
776✔
161

1✔
162
// Shamelessly inline direction to avoid conflicts with 1.0 and 2.0.
1✔
163
const rtlRange = "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";
1✔
164
const ltrRange =
1✔
165
    "A-Za-z\u00C0-\u00D6\u00D8-\u00F6" +
1✔
166
    "\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C" +
1✔
167
    "\uFE00-\uFE6F\uFEFD-\uFFFF";
1✔
168

1✔
169
/* eslint-disable no-misleading-character-class */
1✔
170
const rtl = new RegExp("^[^" + ltrRange + "]*[" + rtlRange + "]");
1✔
171
const ltr = new RegExp("^[^" + rtlRange + "]*[" + ltrRange + "]");
1✔
172
/* eslint-enable no-misleading-character-class */
1✔
173

1✔
174
export function direction(value: string): "rtl" | "ltr" | "neutral" {
1✔
175
    return rtl.test(value) ? "rtl" : ltr.test(value) ? "ltr" : "neutral";
67,419✔
176
}
67,419✔
177

1✔
178
let scrollbarWidthCache: number | undefined = undefined;
1✔
179
export function getScrollBarWidth(): number {
1✔
180
    if (typeof document === "undefined") return 0;
148!
181
    if (scrollbarWidthCache !== undefined) return scrollbarWidthCache;
148✔
182
    const inner = document.createElement("p");
2✔
183
    inner.style.width = "100%";
2✔
184
    inner.style.height = "200px";
2✔
185

2✔
186
    const outer = document.createElement("div");
2✔
187
    outer.id = "testScrollbar";
2✔
188

2✔
189
    outer.style.position = "absolute";
2✔
190
    outer.style.top = "0px";
2✔
191
    outer.style.left = "0px";
2✔
192
    outer.style.visibility = "hidden";
2✔
193
    outer.style.width = "200px";
2✔
194
    outer.style.height = "150px";
2✔
195
    outer.style.overflow = "hidden";
2✔
196
    outer.append(inner);
2✔
197

2✔
198
    document.body.append(outer);
2✔
199
    const w1 = inner.offsetWidth;
2✔
200
    outer.style.overflow = "scroll";
2✔
201
    let w2 = inner.offsetWidth;
2✔
202
    if (w1 === w2) {
2✔
203
        w2 = outer.clientWidth;
2✔
204
    }
2✔
205

2✔
206
    outer.remove();
2✔
207

2✔
208
    scrollbarWidthCache = w1 - w2;
2✔
209
    return scrollbarWidthCache;
2✔
210
}
2✔
211

1✔
212
// Dear future reader,
1✔
213
// This dumb hook is to make sure if the inputState changes, that effectively behaves like an instant "setState" call.
1✔
214
// This is useful in a wide variety of situations. I'm too dumb to know if this is a good idea or a really dumb one.
1✔
215
// I can't tell. It's like poes law but for code.
1✔
216
//
1✔
217
// I'm sorry.
1✔
218
const empty = Symbol();
1✔
219
export function useStateWithReactiveInput<T>(inputState: T): [T, React.Dispatch<React.SetStateAction<T>>, () => void] {
1✔
220
    // When [0] is not empty we will return it, [1] is always the last value we saw
670✔
221
    const inputStateRef = React.useRef<[T | typeof empty, T]>([empty, inputState]);
670✔
222
    if (inputStateRef.current[1] !== inputState) {
670✔
223
        // it changed, we must use thee!
4✔
224
        inputStateRef.current[0] = inputState;
4✔
225
    }
4✔
226
    inputStateRef.current[1] = inputState;
670✔
227

670✔
228
    const [state, setState] = React.useState(inputState);
670✔
229
    // crimes against humanity here
670✔
230
    const [, forceRender] = React.useState<{} | undefined>();
670✔
231
    const setStateOuter = React.useCallback<typeof setState>(nv => {
670✔
232
        // this takes care of the case where the inputState was set, then setState gets called again but back to what
146✔
233
        // the state was before the inputState changed. Since the useState effect wont trigger a render in this case
146✔
234
        // we need to be very naughty and force it to see the change. Technically this may not be needed some chunk of
146✔
235
        // the time (in fact most of it) but checking for it is likely to be more expensive than just over-doing it
146✔
236
        const s = inputStateRef.current[0];
146✔
237
        if (s !== empty) {
146✔
238
            nv = typeof nv === "function" ? (nv as (pv: T) => T)(s) : nv;
3✔
239
            if (nv === s) return; // they are setting it to what the inputState is anyway so we can just do nothing
3✔
240
        }
3✔
241
        if (s !== empty) forceRender({});
146✔
242
        setState(pv => {
145✔
243
            if (typeof nv === "function") {
145✔
244
                return (nv as (pv: T) => T)(s === empty ? pv : s);
2!
245
            }
2✔
246
            return nv;
143✔
247
        });
145✔
248
        inputStateRef.current[0] = empty;
145✔
249
    }, []);
670✔
250

670✔
251
    const onEmpty = React.useCallback(() => {
670✔
252
        inputStateRef.current[0] = empty;
×
253
        forceRender({});
×
254
    }, []);
670✔
255

670✔
256
    return [inputStateRef.current[0] === empty ? state : inputStateRef.current[0], setStateOuter, onEmpty];
670✔
257
}
670✔
258

1✔
259
export function makeAccessibilityStringForArray(arr: readonly string[]): string {
1✔
260
    // this is basically just .join(", ") but checks to make sure it is not going to allocate
3,767✔
261
    // a string that is so large it might crash the browser
3,767✔
262
    if (arr.length === 0) {
3,767✔
263
        return "";
1✔
264
    }
1✔
265

3,766✔
266
    let index = 0;
3,766✔
267
    let count = 0;
3,766✔
268
    for (const str of arr) {
3,767✔
269
        count += str.length;
13,773✔
270
        if (count > 10_000) break;
13,773✔
271
        index++;
13,771✔
272
    }
13,771✔
273
    return arr.slice(0, index).join(", ");
3,766✔
274
}
3,766✔
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