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

glideapps / glide-data-grid / 7333298370

26 Dec 2023 11:13PM UTC coverage: 90.625% (+0.04%) from 90.585%
7333298370

Pull #831

github

jassmith
Merge remote-tracking branch 'origin/6.0.0' into jason/better-keybinding
Pull Request #831: Make all keybindings rebindable

2622 of 3262 branches covered (0.0%)

402 of 428 new or added lines in 4 files covered. (93.93%)

6 existing lines in 1 file now uncovered.

15912 of 17558 relevant lines covered (90.63%)

3059.67 hits per line

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

92.66
/packages/core/src/common/utils.tsx
1
import * as React from "react";
1✔
2
import debounce from "lodash/debounce.js";
1✔
3
import { deepEqual } from "./support.js";
1✔
4

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

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

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

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

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

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

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

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

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

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

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

1✔
91
export const EditPencil: React.FunctionComponent<Partial<SpriteProps>> = (props: Partial<SpriteProps>) => {
1✔
92
    const fg = props.fgColor ?? "currentColor";
4✔
93
    return (
4✔
94
        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4✔
95
            <path
4✔
96
                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✔
97
                stroke={fg}
4✔
98
                strokeWidth="1.5"
4✔
99
                strokeLinecap="round"
4✔
100
                strokeLinejoin="round"
4✔
101
                fill="none"
4✔
102
                vectorEffect="non-scaling-stroke"
4✔
103
            />
4✔
104
            <path
4✔
105
                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✔
106
                stroke={fg}
4✔
107
                strokeWidth="1.5"
4✔
108
                strokeLinecap="round"
4✔
109
                strokeLinejoin="round"
4✔
110
                fill="none"
4✔
111
                vectorEffect="non-scaling-stroke"
4✔
112
            />
4✔
113
        </svg>
4✔
114
    );
4✔
115
};
4✔
116

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

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

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

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

812✔
145
    const debouncedSetState = React.useRef<typeof setState>(
812✔
146
        debounce(x => {
812✔
147
            if (mountedRef.current) {
229✔
148
                setState(x);
215✔
149
            }
215✔
150
        }, time)
812✔
151
    );
812✔
152

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

812✔
160
    return state;
812✔
161
}
812✔
162

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4,342✔
267
    let index = 0;
4,342✔
268
    let count = 0;
4,342✔
269
    for (const str of arr) {
4,343✔
270
        count += str.length;
14,349✔
271
        if (count > 10_000) break;
14,349✔
272
        index++;
14,347✔
273
    }
14,347✔
274
    return arr.slice(0, index).join(", ");
4,342✔
275
}
4,342✔
276

1✔
277
export function useDeepMemo<T>(value: T): T {
1✔
278
    const ref = React.useRef<T>(value);
678✔
279

678✔
280
    if (!deepEqual(value, ref.current)) {
678!
NEW
281
        ref.current = value;
×
NEW
282
    }
×
283

678✔
284
    // eslint-disable-next-line react-hooks/exhaustive-deps
678✔
285
    return React.useMemo(() => ref.current, [ref.current]);
678✔
286
}
678✔
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