@jetbrains/ring-ui 8.0.0-beta.3 → 8.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/checkbox/checkbox.d.ts +1 -1
- package/components/collapsible-group/collapsible-group.css +138 -0
- package/components/collapsible-group/collapsible-group.d.ts +20 -0
- package/components/collapsible-group/collapsible-group.js +73 -0
- package/components/editable-heading/editable-heading.js +14 -14
- package/components/expand/collapsible-group.css +23 -89
- package/components/expand/collapsible-group.d.ts +8 -20
- package/components/expand/collapsible-group.js +14 -73
- package/components/global/compose-refs.d.ts +2 -1
- package/components/global/compose-refs.js +24 -8
- package/components/global/focus-with-temporary-tabindex.d.ts +11 -0
- package/components/global/focus-with-temporary-tabindex.js +21 -0
- package/components/global/intersection-observer-context.d.ts +29 -9
- package/components/global/intersection-observer-context.js +43 -24
- package/components/global/is-within-interactive-element.d.ts +6 -0
- package/components/global/is-within-interactive-element.js +26 -0
- package/components/global/is-within-navigable-element.d.ts +6 -0
- package/components/global/is-within-navigable-element.js +27 -0
- package/components/global/parse-css-duration.d.ts +5 -0
- package/components/global/parse-css-duration.js +13 -0
- package/components/global/schedule-with-cleanup.d.ts +12 -0
- package/components/global/schedule-with-cleanup.js +34 -0
- package/components/global/table-selection.js +1 -1
- package/components/input/input.d.ts +1 -1
- package/components/legacy-table/row.d.ts +1 -1
- package/components/popup/popup.d.ts +2 -0
- package/components/popup/popup.js +2 -2
- package/components/select/select-popup.d.ts +1 -1
- package/components/select/select.d.ts +1 -1
- package/components/table/default-item-renderer.d.ts +23 -10
- package/components/table/default-item-renderer.js +40 -19
- package/components/table/internal/column-animation.d.ts +16 -0
- package/components/table/internal/column-animation.js +45 -0
- package/components/table/internal/table-header.d.ts +4 -0
- package/components/table/internal/table-header.js +357 -0
- package/components/table/internal/virtual-items.d.ts +34 -0
- package/components/table/{table-virtualize.js → internal/virtual-items.js} +68 -33
- package/components/table/item-virtualization.d.ts +35 -0
- package/components/table/item-virtualization.js +28 -0
- package/components/table/table-const.d.ts +40 -6
- package/components/table/table-const.js +14 -5
- package/components/table/table-primitives.d.ts +9 -16
- package/components/table/table-primitives.js +6 -60
- package/components/table/table-props.d.ts +280 -0
- package/components/table/table-props.js +1 -0
- package/components/table/table.css +171 -66
- package/components/table/table.d.ts +205 -209
- package/components/table/table.js +298 -2
- package/components/util-stories.d.ts +26 -0
- package/components/util-stories.js +61 -0
- package/package.json +29 -30
- package/components/date-picker/use-intersection-observer.d.ts +0 -6
- package/components/date-picker/use-intersection-observer.js +0 -48
- package/components/global/composeRefs.d.ts +0 -6
- package/components/global/composeRefs.js +0 -7
- package/components/table/table-component.d.ts +0 -80
- package/components/table/table-component.js +0 -130
- package/components/table/table-row-focus.d.ts +0 -4
- package/components/table/table-row-focus.js +0 -42
- package/components/table/table-virtualize.d.ts +0 -32
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { type RefObject } from 'react';
|
|
2
2
|
/**
|
|
3
|
+
* Provides access to a shared `IntersectionObserver` instance
|
|
4
|
+
* via the {@link IntersectionObserverContext} context.
|
|
5
|
+
*
|
|
6
|
+
* @see IntersectionObserverContext
|
|
7
|
+
*/
|
|
8
|
+
export interface IntersectionObserverHandle {
|
|
9
|
+
/**
|
|
10
|
+
* Starts observing an element.
|
|
11
|
+
*
|
|
12
|
+
* Returns a cleanup function that stops observing it.
|
|
13
|
+
*/
|
|
14
|
+
observe(element: Element, isIntersecting: (isIntersecting: boolean) => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Multiple components can share a single `IntersectionObserver` instance through this context.
|
|
18
|
+
*
|
|
3
19
|
* Usage:
|
|
4
20
|
*
|
|
5
21
|
* ```tsx
|
|
@@ -10,17 +26,21 @@ import { type RefObject } from 'react';
|
|
|
10
26
|
* function YourComponent() {
|
|
11
27
|
* // Contains the current isIntersecting value
|
|
12
28
|
* const isIntersecting = useIsIntersecting(elementRef);
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
29
|
+
*
|
|
30
|
+
* // Or, to manually work with the IntersectionObserverHandle:
|
|
31
|
+
* const handle = use(IntersectionObserverContext);
|
|
32
|
+
* useEffect(() => {
|
|
33
|
+
* return handle.observe(elementRef.current, isIntersecting => { ... })
|
|
16
34
|
* })
|
|
17
35
|
* }
|
|
18
36
|
* ```
|
|
19
37
|
*/
|
|
20
|
-
export declare const IntersectionObserverContext: import("react").Context<IntersectionObserverHandle
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
export declare const IntersectionObserverContext: import("react").Context<IntersectionObserverHandle>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates an IntersectionObserverHandle suitable for {@link IntersectionObserverContext}.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useIntersectionObserverHandle(rootRef?: RefObject<HTMLElement | null>, rootMargin?: number, scrollMargin?: number): IntersectionObserverHandle;
|
|
43
|
+
/**
|
|
44
|
+
* Returns whether the referenced element is currently intersecting.
|
|
45
|
+
*/
|
|
25
46
|
export declare function useIsIntersecting(elementRef: RefObject<Element | null>): boolean;
|
|
26
|
-
export declare function useIsIntersectingListener(elementRef: RefObject<Element | null>, onChange: (isIntersecting: boolean) => void): void;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createContext, use, useEffect, useState } from 'react';
|
|
2
2
|
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
const noopIntersectionObserverHandle = {
|
|
6
|
+
observe: () => () => { },
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Multiple components can share a single `IntersectionObserver` instance through this context.
|
|
10
|
+
*
|
|
3
11
|
* Usage:
|
|
4
12
|
*
|
|
5
13
|
* ```tsx
|
|
@@ -10,23 +18,28 @@ import { createContext, use, useEffect, useState } from 'react';
|
|
|
10
18
|
* function YourComponent() {
|
|
11
19
|
* // Contains the current isIntersecting value
|
|
12
20
|
* const isIntersecting = useIsIntersecting(elementRef);
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* // Or, to manually work with the IntersectionObserverHandle:
|
|
23
|
+
* const handle = use(IntersectionObserverContext);
|
|
24
|
+
* useEffect(() => {
|
|
25
|
+
* return handle.observe(elementRef.current, isIntersecting => { ... })
|
|
16
26
|
* })
|
|
17
27
|
* }
|
|
18
28
|
* ```
|
|
19
29
|
*/
|
|
20
|
-
export const IntersectionObserverContext = createContext(
|
|
30
|
+
export const IntersectionObserverContext = createContext(noopIntersectionObserverHandle);
|
|
31
|
+
/**
|
|
32
|
+
* Creates an IntersectionObserverHandle suitable for {@link IntersectionObserverContext}.
|
|
33
|
+
*/
|
|
21
34
|
export function useIntersectionObserverHandle(rootRef, rootMargin, scrollMargin) {
|
|
22
|
-
const [handle, setHandle] = useState(
|
|
35
|
+
const [handle, setHandle] = useState(noopIntersectionObserverHandle);
|
|
23
36
|
useEffect(() => {
|
|
24
37
|
const root = rootRef?.current;
|
|
25
|
-
const
|
|
38
|
+
const callbacksByElement = new Map();
|
|
26
39
|
const observer = new IntersectionObserver(entries => {
|
|
27
40
|
for (const entry of entries) {
|
|
28
|
-
const
|
|
29
|
-
|
|
41
|
+
const callbacks = callbacksByElement.get(entry.target);
|
|
42
|
+
callbacks?.forEach(cb => cb(entry.isIntersecting));
|
|
30
43
|
}
|
|
31
44
|
}, {
|
|
32
45
|
root,
|
|
@@ -35,38 +48,44 @@ export function useIntersectionObserverHandle(rootRef, rootMargin, scrollMargin)
|
|
|
35
48
|
});
|
|
36
49
|
setHandle({
|
|
37
50
|
observe(element, onChange) {
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
if (!callbacksByElement.has(element)) {
|
|
52
|
+
callbacksByElement.set(element, []);
|
|
53
|
+
observer.observe(element);
|
|
54
|
+
}
|
|
55
|
+
callbacksByElement.get(element).push(onChange);
|
|
40
56
|
return () => {
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
const callbacks = callbacksByElement.get(element);
|
|
58
|
+
if (!callbacks)
|
|
59
|
+
return;
|
|
60
|
+
const index = callbacks.indexOf(onChange);
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
callbacks.splice(index, 1);
|
|
63
|
+
}
|
|
64
|
+
if (!callbacks.length) {
|
|
65
|
+
callbacksByElement.delete(element);
|
|
66
|
+
observer.unobserve(element);
|
|
67
|
+
}
|
|
43
68
|
};
|
|
44
69
|
},
|
|
45
70
|
});
|
|
46
71
|
return () => {
|
|
47
72
|
observer.disconnect();
|
|
48
|
-
setHandle(
|
|
73
|
+
setHandle(noopIntersectionObserverHandle);
|
|
49
74
|
};
|
|
50
75
|
}, [rootRef, rootMargin, scrollMargin]);
|
|
51
76
|
return handle;
|
|
52
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns whether the referenced element is currently intersecting.
|
|
80
|
+
*/
|
|
53
81
|
export function useIsIntersecting(elementRef) {
|
|
54
82
|
const handle = use(IntersectionObserverContext);
|
|
55
83
|
const [isIntersecting, setIsIntersecting] = useState(false);
|
|
56
84
|
useEffect(() => {
|
|
57
85
|
const element = elementRef.current;
|
|
58
|
-
if (!element
|
|
86
|
+
if (!element)
|
|
59
87
|
return;
|
|
60
88
|
return handle.observe(element, setIsIntersecting);
|
|
61
|
-
}, [handle, elementRef
|
|
89
|
+
}, [handle, elementRef]);
|
|
62
90
|
return isIntersecting;
|
|
63
91
|
}
|
|
64
|
-
export function useIsIntersectingListener(elementRef, onChange) {
|
|
65
|
-
const handle = use(IntersectionObserverContext);
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
const element = elementRef.current;
|
|
68
|
-
if (!element || !handle)
|
|
69
|
-
return;
|
|
70
|
-
return handle.observe(element, onChange);
|
|
71
|
-
}, [handle, elementRef, onChange]);
|
|
72
|
-
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If this function returns `false`, the event may be interpreted as a click
|
|
3
|
+
* or tap on a "empty space" rather than on an interactive element such as
|
|
4
|
+
* a button or a link.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isWithinInteractiveElement(target: EventTarget | null): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const interactiveSelector = [
|
|
2
|
+
'a',
|
|
3
|
+
'button',
|
|
4
|
+
'details',
|
|
5
|
+
'input',
|
|
6
|
+
'label',
|
|
7
|
+
'option',
|
|
8
|
+
'select',
|
|
9
|
+
'summary',
|
|
10
|
+
'textarea',
|
|
11
|
+
'[contenteditable]',
|
|
12
|
+
'[role="button"]',
|
|
13
|
+
'[role="checkbox"]',
|
|
14
|
+
'[role="link"]',
|
|
15
|
+
'[role="radio"]',
|
|
16
|
+
'[role="switch"]',
|
|
17
|
+
'[role="tab"]',
|
|
18
|
+
].join();
|
|
19
|
+
/**
|
|
20
|
+
* If this function returns `false`, the event may be interpreted as a click
|
|
21
|
+
* or tap on a "empty space" rather than on an interactive element such as
|
|
22
|
+
* a button or a link.
|
|
23
|
+
*/
|
|
24
|
+
export function isWithinInteractiveElement(target) {
|
|
25
|
+
return target instanceof Element && target.closest(interactiveSelector) != null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns whether the event target is within an element that is expected to
|
|
3
|
+
* handle keyboard navigation itself, so container-level keyboard shortcuts
|
|
4
|
+
* should generally be ignored.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isWithinNavigableElement(target: EventTarget | null): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const navigableSelector = [
|
|
2
|
+
'input:not([type="button"]):not([type="checkbox"]):not([type="color"]):not([type="file"]):not([type="hidden"]):not([type="image"]):not([type="reset"]):not([type="submit"])',
|
|
3
|
+
'textarea',
|
|
4
|
+
'select',
|
|
5
|
+
'[contenteditable]',
|
|
6
|
+
'[role="combobox"]',
|
|
7
|
+
'[role="grid"]',
|
|
8
|
+
'[role="listbox"]',
|
|
9
|
+
'[role="menu"]',
|
|
10
|
+
'[role="menubar"]',
|
|
11
|
+
'[role="radiogroup"]',
|
|
12
|
+
'[role="searchbox"]',
|
|
13
|
+
'[role="slider"]',
|
|
14
|
+
'[role="spinbutton"]',
|
|
15
|
+
'[role="tablist"]',
|
|
16
|
+
'[role="textbox"]',
|
|
17
|
+
'[role="tree"]',
|
|
18
|
+
'[role="treegrid"]',
|
|
19
|
+
].join();
|
|
20
|
+
/**
|
|
21
|
+
* Returns whether the event target is within an element that is expected to
|
|
22
|
+
* handle keyboard navigation itself, so container-level keyboard shortcuts
|
|
23
|
+
* should generally be ignored.
|
|
24
|
+
*/
|
|
25
|
+
export function isWithinNavigableElement(target) {
|
|
26
|
+
return target instanceof Element && target.closest(navigableSelector) != null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a CSS duration string (e.g., "500ms", "2s") and returns the duration in milliseconds.
|
|
3
|
+
* Returns the `defaultVal` if the input string is not a valid CSS duration.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseCssDuration(duration: string, defaultVal?: number): number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a CSS duration string (e.g., "500ms", "2s") and returns the duration in milliseconds.
|
|
3
|
+
* Returns the `defaultVal` if the input string is not a valid CSS duration.
|
|
4
|
+
*/
|
|
5
|
+
export function parseCssDuration(duration, defaultVal = 0) {
|
|
6
|
+
const match = duration.match(/(\d+(\.\d+)?)(s|ms)/);
|
|
7
|
+
if (!match)
|
|
8
|
+
return defaultVal;
|
|
9
|
+
const value = parseFloat(match[1]);
|
|
10
|
+
const unit = match[3];
|
|
11
|
+
// eslint-disable-next-line no-magic-numbers
|
|
12
|
+
return unit === 's' ? value * 1000 : value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schedules a timeout and returns a function that cancels it.
|
|
3
|
+
*
|
|
4
|
+
* Useful from React effects.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setTimeoutWithCleanup(callback: () => void, delay?: number): () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Schedules an animation frame and returns a function that cancels it.
|
|
9
|
+
*
|
|
10
|
+
* Useful from React effects.
|
|
11
|
+
*/
|
|
12
|
+
export declare function requestAnimationFrameWithCleanup(callback: () => void): () => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schedules a timeout and returns a function that cancels it.
|
|
3
|
+
*
|
|
4
|
+
* Useful from React effects.
|
|
5
|
+
*/
|
|
6
|
+
export function setTimeoutWithCleanup(callback, delay = 0) {
|
|
7
|
+
let timerId = window.setTimeout(() => {
|
|
8
|
+
callback();
|
|
9
|
+
timerId = null;
|
|
10
|
+
}, delay);
|
|
11
|
+
return () => {
|
|
12
|
+
if (timerId != null) {
|
|
13
|
+
window.clearTimeout(timerId);
|
|
14
|
+
timerId = null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Schedules an animation frame and returns a function that cancels it.
|
|
20
|
+
*
|
|
21
|
+
* Useful from React effects.
|
|
22
|
+
*/
|
|
23
|
+
export function requestAnimationFrameWithCleanup(callback) {
|
|
24
|
+
let rafId = requestAnimationFrame(() => {
|
|
25
|
+
callback();
|
|
26
|
+
rafId = null;
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
if (rafId != null) {
|
|
30
|
+
cancelAnimationFrame(rafId);
|
|
31
|
+
rafId = null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -15,7 +15,7 @@ export default class TableSelection {
|
|
|
15
15
|
return item.id;
|
|
16
16
|
}
|
|
17
17
|
// If there's no id provided on item and no getKey supplied, fail fast with a clear message
|
|
18
|
-
throw new Error('
|
|
18
|
+
throw new Error('TableSelection: getKey is required when items have no "id" property');
|
|
19
19
|
}, getChildren = () => [], isItemSelectable = () => true, } = {}) {
|
|
20
20
|
this._rawData = data;
|
|
21
21
|
this._getChildren = getChildren;
|
|
@@ -69,7 +69,7 @@ export declare class Input extends PureComponent<InputProps> {
|
|
|
69
69
|
stretch(el: HTMLElement | null | undefined): void;
|
|
70
70
|
adapt(): void;
|
|
71
71
|
inputRef: (el: HTMLInputElement | HTMLTextAreaElement | null) => void;
|
|
72
|
-
composedInputRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement | HTMLTextAreaElement> | undefined)[]) => (value: T | null) => void>;
|
|
72
|
+
composedInputRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement | HTMLTextAreaElement> | undefined)[]) => (value: T | null) => () => void>;
|
|
73
73
|
clear: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
74
74
|
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
75
75
|
handleTextareaChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
@@ -55,7 +55,7 @@ export default class Row<T extends object> extends PureComponent<RowProps<T>> {
|
|
|
55
55
|
onDoubleClick: () => void;
|
|
56
56
|
row?: HTMLElement | null;
|
|
57
57
|
rowRef: (el: HTMLElement | null) => void;
|
|
58
|
-
composedRowRef: import("memoize-one").MemoizedFn<(...refs: (React.Ref<HTMLElement> | undefined)[]) => (value: T_1 | null) => void>;
|
|
58
|
+
composedRowRef: import("memoize-one").MemoizedFn<(...refs: (React.Ref<HTMLElement> | undefined)[]) => (value: T_1 | null) => () => void>;
|
|
59
59
|
render(): React.JSX.Element;
|
|
60
60
|
}
|
|
61
61
|
export type RowAttrs<T extends object> = React.JSX.LibraryManagedAttributes<typeof Row, RowProps<T>>;
|
|
@@ -45,6 +45,8 @@ export interface BasePopupProps {
|
|
|
45
45
|
onMouseUp?: ((e: React.MouseEvent<HTMLElement>) => void) | undefined;
|
|
46
46
|
onMouseOver?: ((e: React.SyntheticEvent<HTMLElement>) => void) | undefined;
|
|
47
47
|
onMouseOut?: ((e: React.SyntheticEvent<HTMLElement>) => void) | undefined;
|
|
48
|
+
onMouseEnter?: ((e: React.MouseEvent<HTMLElement>) => void) | undefined;
|
|
49
|
+
onMouseLeave?: ((e: React.MouseEvent<HTMLElement>) => void) | undefined;
|
|
48
50
|
onContextMenu?: ((e: React.MouseEvent<HTMLElement>) => void) | undefined;
|
|
49
51
|
onDirectionChange?: ((direction: Directions) => void) | null | undefined;
|
|
50
52
|
onShow?: (() => void) | null | undefined;
|
|
@@ -282,7 +282,7 @@ export default class Popup extends PureComponent {
|
|
|
282
282
|
esc: this._onEscPress,
|
|
283
283
|
};
|
|
284
284
|
render() {
|
|
285
|
-
const { className, style, hidden, attached, keepMounted, client, onMouseDown, onMouseUp, onMouseOver, onMouseOut, onContextMenu, 'data-test': dataTest, largeBorderRadius, } = this.props;
|
|
285
|
+
const { className, style, hidden, attached, keepMounted, client, onMouseDown, onMouseUp, onMouseOver, onMouseOut, onMouseEnter, onMouseLeave, onContextMenu, 'data-test': dataTest, largeBorderRadius, } = this.props;
|
|
286
286
|
const showing = this.state.display === Display.SHOWING;
|
|
287
287
|
const direction = (this.state.direction || '').toLowerCase().replace(/[_]/g, '-');
|
|
288
288
|
return (<ThemeContext.Consumer>
|
|
@@ -305,7 +305,7 @@ export default class Popup extends PureComponent {
|
|
|
305
305
|
|
|
306
306
|
{client !== false &&
|
|
307
307
|
(keepMounted || !hidden) &&
|
|
308
|
-
createPortal(<PopupTarget id={this.uid} ref={this.containerRef} onMouseOver={onMouseOver} onFocus={onMouseOver} onMouseOut={onMouseOut} onBlur={onMouseOut} onContextMenu={onContextMenu}>
|
|
308
|
+
createPortal(<PopupTarget id={this.uid} ref={this.containerRef} onMouseOver={onMouseOver} onFocus={onMouseOver} onMouseOut={onMouseOut} onBlur={onMouseOut} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} onContextMenu={onContextMenu}>
|
|
309
309
|
<div data-test={dataTests('ring-popup', dataTest)} data-test-shown={!hidden && !showing} data-test-direction={direction} ref={this.popupRef} className={classes} style={style} onMouseDown={onMouseDown} onMouseUp={onMouseUp}
|
|
310
310
|
// mouse handlers are used to track clicking on inner elements
|
|
311
311
|
role='presentation'>
|
|
@@ -127,7 +127,7 @@ export default class SelectPopup<T = unknown> extends PureComponent<SelectPopupP
|
|
|
127
127
|
list?: List<T> | null;
|
|
128
128
|
listRef: (el: List<T> | null) => void;
|
|
129
129
|
filterRef: (el: HTMLInputElement | null) => void;
|
|
130
|
-
composedFilterRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T_1 | null) => void>;
|
|
130
|
+
composedFilterRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T_1 | null) => () => void>;
|
|
131
131
|
shortcutsScope: string;
|
|
132
132
|
shortcutsMap: {
|
|
133
133
|
tab: (event: Event) => void;
|
|
@@ -264,7 +264,7 @@ export default class Select<T = unknown> extends Component<SelectProps<T>, Selec
|
|
|
264
264
|
private _getAvatar;
|
|
265
265
|
filter?: HTMLInputElement | null;
|
|
266
266
|
filterRef: (el: HTMLInputElement | null) => void;
|
|
267
|
-
composedFilterRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T_1 | null) => void>;
|
|
267
|
+
composedFilterRef: import("memoize-one").MemoizedFn<(...refs: (Ref<HTMLInputElement> | undefined)[]) => (value: T_1 | null) => () => void>;
|
|
268
268
|
getShortcutsMap(): {
|
|
269
269
|
enter: () => true | undefined;
|
|
270
270
|
esc: (event: KeyboardEvent) => boolean | undefined;
|
|
@@ -5,13 +5,10 @@ export interface DefaultItemRendererProps {
|
|
|
5
5
|
*/
|
|
6
6
|
index: number;
|
|
7
7
|
/**
|
|
8
|
-
* If true
|
|
8
|
+
* If `true`, the row will be focusable with up/down arrow keys.
|
|
9
9
|
* Focus is implemented using the
|
|
10
10
|
* ["roving tabindex"](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets#technique_1_roving_tabindex)
|
|
11
11
|
* technique, that is, only the focused row has `tabIndex={0}`.
|
|
12
|
-
*
|
|
13
|
-
* To focus the row on click or other user interaction, add e.g. `onClick()`
|
|
14
|
-
* and invoke `focusRow(e.currentTarget)` imported from `table-row-focus.ts`.
|
|
15
12
|
*/
|
|
16
13
|
keyboardFocusable?: boolean;
|
|
17
14
|
/**
|
|
@@ -20,17 +17,33 @@ export interface DefaultItemRendererProps {
|
|
|
20
17
|
*/
|
|
21
18
|
clickable?: boolean;
|
|
22
19
|
/**
|
|
23
|
-
* If true
|
|
24
|
-
* a different background color.
|
|
20
|
+
* If `true`, the row is highlighted as selected with a different background color.
|
|
25
21
|
*/
|
|
26
22
|
selected?: boolean;
|
|
27
23
|
/**
|
|
28
|
-
*
|
|
29
|
-
* 0
|
|
24
|
+
* The nesting level of an item. Applies an indent for columns with
|
|
25
|
+
* `Column.indent` set to `true`. `0`, negative values, and an unset value
|
|
26
|
+
* mean no indent.
|
|
30
27
|
*/
|
|
31
28
|
level?: number;
|
|
29
|
+
/**
|
|
30
|
+
* When set to `true`, does not control item virtualization.
|
|
31
|
+
* Useful when you include `DefaultItemRenderer` as a part of a custom row renderer,
|
|
32
|
+
* and track the visibility yourself.
|
|
33
|
+
*/
|
|
34
|
+
noItemVirtualization?: boolean;
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
37
|
+
* Standard component for rendering a table row.
|
|
38
|
+
*
|
|
39
|
+
* Renders an item using the table's column definitions and lets you
|
|
40
|
+
* configure item-scoped behavior such as selection, keyboard navigation,
|
|
41
|
+
* event handlers, `className`, and `ref`.
|
|
42
|
+
*
|
|
43
|
+
* Note that row-level click and keyboard handlers are not discoverable
|
|
44
|
+
* by assistive technologies. Make sure that any functionality relying on
|
|
45
|
+
* them (such as selection or expand/collapse) is also available through
|
|
46
|
+
* accessible controls, such as checkboxes or buttons with accessible
|
|
47
|
+
* labels.
|
|
35
48
|
*/
|
|
36
|
-
export declare function DefaultItemRenderer<T>({ index, keyboardFocusable, clickable, selected, level, ref: userRef, className, ...restProps }: DefaultItemRendererProps & ComponentPropsWithRef<'tr'>): import("react").JSX.Element | null;
|
|
49
|
+
export declare function DefaultItemRenderer<T>({ index, keyboardFocusable, clickable, selected, level, noItemVirtualization, ref: userRef, className, ...restProps }: DefaultItemRendererProps & ComponentPropsWithRef<'tr'>): import("react").JSX.Element | null;
|
|
@@ -1,40 +1,61 @@
|
|
|
1
|
-
import { use, useRef } from 'react';
|
|
1
|
+
import { use, useCallback, useRef } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { ColumnAnimationContext, TablePropsContext } from './table-const';
|
|
4
|
+
import { useComposedRef } from '../global/compose-refs';
|
|
5
|
+
import { useItemVirtualization } from './item-virtualization';
|
|
6
6
|
import { TableCell, TableRow } from './table-primitives';
|
|
7
7
|
import styles from './table.css';
|
|
8
|
-
const INDENT_SIZE = 24;
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
9
|
+
* Standard component for rendering a table row.
|
|
10
|
+
*
|
|
11
|
+
* Renders an item using the table's column definitions and lets you
|
|
12
|
+
* configure item-scoped behavior such as selection, keyboard navigation,
|
|
13
|
+
* event handlers, `className`, and `ref`.
|
|
14
|
+
*
|
|
15
|
+
* Note that row-level click and keyboard handlers are not discoverable
|
|
16
|
+
* by assistive technologies. Make sure that any functionality relying on
|
|
17
|
+
* them (such as selection or expand/collapse) is also available through
|
|
18
|
+
* accessible controls, such as checkboxes or buttons with accessible
|
|
19
|
+
* labels.
|
|
11
20
|
*/
|
|
12
|
-
export function DefaultItemRenderer({ index, keyboardFocusable, clickable, selected, level, ref: userRef, className, ...restProps }) {
|
|
21
|
+
export function DefaultItemRenderer({ index, keyboardFocusable, clickable, selected, level, noItemVirtualization, ref: userRef, className, ...restProps }) {
|
|
13
22
|
const localRef = useRef(null);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
const composedRef = useComposedRef(userRef, localRef);
|
|
24
|
+
useItemVirtualization({
|
|
25
|
+
index,
|
|
26
|
+
refs: localRef,
|
|
27
|
+
onIntersectionChange: useCallback(([isIntersecting], _i, [element]) => isIntersecting === false &&
|
|
28
|
+
!noItemVirtualization &&
|
|
29
|
+
element?.isConnected &&
|
|
30
|
+
!element.contains(document.activeElement) &&
|
|
31
|
+
!element.previousElementSibling?.contains(document.activeElement) &&
|
|
32
|
+
!element.nextElementSibling?.contains(document.activeElement)
|
|
33
|
+
? element.getBoundingClientRect().height
|
|
34
|
+
: undefined, [noItemVirtualization]),
|
|
19
35
|
});
|
|
20
36
|
const tableProps = use(TablePropsContext);
|
|
21
37
|
if (!tableProps) {
|
|
22
38
|
return null;
|
|
23
39
|
}
|
|
40
|
+
const animatedColumn = use(ColumnAnimationContext);
|
|
24
41
|
const { data, columns } = tableProps;
|
|
25
42
|
const item = data[index];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
const indentSize = 24;
|
|
44
|
+
return (<TableRow ref={composedRef} keyboardFocusable={keyboardFocusable} className={classNames(className, clickable && styles.clickableRow, selected && styles.selectedRow)} {...restProps}>
|
|
45
|
+
{columns.map((column, columnIndex) => {
|
|
46
|
+
const { key, tdClassName, indent } = column;
|
|
47
|
+
return (<TableCell key={key} className={classNames(columnIndex === animatedColumn?.columnIndex && animatedColumn.cellClassName, typeof tdClassName === 'function' ? tdClassName(item, index, data) : tdClassName)} style={indent && level != null && level > 0 ? { paddingInlineStart: `${level * indentSize}px` } : undefined}>
|
|
48
|
+
{column.renderCell?.(item, index, data) ?? getDefaultCellValue(item, columnIndex, key)}
|
|
49
|
+
</TableCell>);
|
|
50
|
+
})}
|
|
30
51
|
</TableRow>);
|
|
31
52
|
}
|
|
32
|
-
function getDefaultCellValue(item, columnIndex) {
|
|
53
|
+
function getDefaultCellValue(item, columnIndex, columnKey) {
|
|
33
54
|
if (Array.isArray(item)) {
|
|
34
|
-
return String(item[columnIndex]
|
|
55
|
+
return String(item[columnIndex]);
|
|
35
56
|
}
|
|
36
57
|
if (item !== null && typeof item === 'object') {
|
|
37
|
-
return String(
|
|
58
|
+
return String(item[String(columnKey)]);
|
|
38
59
|
}
|
|
39
60
|
if (columnIndex === 0) {
|
|
40
61
|
return String(item);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { Column } from '../table-props';
|
|
3
|
+
import type { ColumnAnimation } from '../table-const';
|
|
4
|
+
export interface ReorderSpec {
|
|
5
|
+
fromIndex: number;
|
|
6
|
+
insertionIndex: number;
|
|
7
|
+
}
|
|
8
|
+
export type ExpectColumnReorder = (reorderSpec: ReorderSpec) => void;
|
|
9
|
+
export declare function useColumnAnimation<T>({ disabled, tableRef, columns, }: {
|
|
10
|
+
disabled: boolean | undefined;
|
|
11
|
+
tableRef: RefObject<HTMLTableElement | null>;
|
|
12
|
+
columns: readonly Column<T>[];
|
|
13
|
+
}): {
|
|
14
|
+
columnAnimation: ColumnAnimation | null;
|
|
15
|
+
expectColumnReorder: ExpectColumnReorder;
|
|
16
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { parseCssDuration } from '../../global/parse-css-duration';
|
|
3
|
+
import { requestAnimationFrameWithCleanup, setTimeoutWithCleanup } from '../../global/schedule-with-cleanup';
|
|
4
|
+
import styles from '../table.css';
|
|
5
|
+
const reorderExpectationTimeout = 1000;
|
|
6
|
+
export function useColumnAnimation({ disabled, tableRef, columns, }) {
|
|
7
|
+
const [columnAnimation, setColumnAnimation] = useState(null);
|
|
8
|
+
const pendingColumnReorder = useRef(null);
|
|
9
|
+
const expectColumnReorder = useCallback((reorderSpec) => {
|
|
10
|
+
if (disabled)
|
|
11
|
+
return;
|
|
12
|
+
const timerId = window.setTimeout(() => {
|
|
13
|
+
pendingColumnReorder.current = null;
|
|
14
|
+
}, reorderExpectationTimeout);
|
|
15
|
+
pendingColumnReorder.current = { ...reorderSpec, timerId, columns };
|
|
16
|
+
}, [disabled, columns]);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
return () => {
|
|
19
|
+
if (pendingColumnReorder.current) {
|
|
20
|
+
window.clearTimeout(pendingColumnReorder.current.timerId);
|
|
21
|
+
pendingColumnReorder.current = null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const table = tableRef.current;
|
|
27
|
+
if (!table || !pendingColumnReorder.current || columns === pendingColumnReorder.current.columns)
|
|
28
|
+
return;
|
|
29
|
+
const { fromIndex, insertionIndex } = pendingColumnReorder.current;
|
|
30
|
+
pendingColumnReorder.current = null;
|
|
31
|
+
const columnIndex = fromIndex < insertionIndex ? insertionIndex - 1 : insertionIndex;
|
|
32
|
+
return requestAnimationFrameWithCleanup(() => setColumnAnimation(prev => prev == null ? { columnIndex, phase: 'initial', cellClassName: styles.animatedColumnInitial } : prev));
|
|
33
|
+
}, [columns, tableRef]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (columnAnimation?.phase === 'initial') {
|
|
36
|
+
return requestAnimationFrameWithCleanup(() => setColumnAnimation(prev => prev === columnAnimation ? { ...prev, phase: 'fade-out', cellClassName: styles.animatedColumnFadeOut } : prev));
|
|
37
|
+
}
|
|
38
|
+
if (columnAnimation?.phase === 'fade-out') {
|
|
39
|
+
const fadeOutMs = parseCssDuration(window.getComputedStyle(tableRef.current).getPropertyValue('--animated-column-fade-out-duration'));
|
|
40
|
+
return setTimeoutWithCleanup(() => setColumnAnimation(prev => (prev === columnAnimation ? null : prev)), fadeOutMs);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}, [columnAnimation, tableRef]);
|
|
44
|
+
return { columnAnimation, expectColumnReorder };
|
|
45
|
+
}
|