@jetbrains/ring-ui 8.0.0-beta.2 → 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/babel.config.js +1 -1
- package/components/auth-dialog-service/auth-dialog-service.js +2 -2
- package/components/checkbox/checkbox.d.ts +1 -1
- package/components/collapse/collapse-content.js +2 -2
- package/components/collapse/collapse-control.js +2 -2
- package/components/collapse/collapse.js +2 -2
- 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/confirm-service/confirm-service.js +2 -2
- package/components/data-list/data-list.d.ts +1 -1
- package/components/data-list/data-list.mock.d.ts +1 -1
- package/components/data-list/item.d.ts +1 -1
- package/components/data-list/selection.d.ts +1 -1
- package/components/data-list/selection.js +1 -1
- package/components/date-picker/months.js +2 -2
- package/components/date-picker/use-scroll-behavior.js +5 -6
- package/components/date-picker/years.js +2 -2
- package/components/dialog/dialog.d.ts +2 -2
- package/components/dialog/dialog.js +2 -2
- package/components/dropdown-menu/dropdown-menu.d.ts +4 -4
- package/components/dropdown-menu/dropdown-menu.js +4 -4
- package/components/editable-heading/editable-heading.d.ts +1 -2
- package/components/editable-heading/editable-heading.js +18 -19
- package/components/expand/collapsible-group.css +23 -89
- package/components/expand/collapsible-group.d.ts +8 -16
- package/components/expand/collapsible-group.js +13 -72
- package/components/global/compose-refs.d.ts +2 -1
- package/components/global/compose-refs.js +24 -8
- package/components/global/create-stateful-context.js +5 -5
- 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 +31 -11
- package/components/global/intersection-observer-context.js +47 -28
- 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/rerender-hoc.d.ts +4 -2
- package/components/global/rerender-hoc.js +4 -4
- package/components/global/schedule-with-cleanup.d.ts +12 -0
- package/components/global/schedule-with-cleanup.js +34 -0
- package/components/{legacy-table/selection.d.ts → global/table-selection.d.ts} +14 -14
- package/components/{legacy-table/selection.js → global/table-selection.js} +2 -2
- package/components/global/theme.d.ts +4 -3
- package/components/global/theme.js +8 -8
- package/components/i18n/i18n-context.js +1 -1
- package/components/input/input.d.ts +1 -1
- package/components/island/adaptive-island-hoc.js +4 -4
- package/components/island/content.d.ts +7 -2
- package/components/island/content.js +5 -5
- package/components/legacy-table/cell.js +1 -1
- package/components/legacy-table/header-cell.js +1 -1
- package/components/legacy-table/header.js +1 -1
- package/components/legacy-table/multitable.d.ts +1 -1
- package/components/legacy-table/row.d.ts +1 -1
- package/components/legacy-table/row.js +1 -1
- package/components/legacy-table/selection-adapter.d.ts +3 -3
- package/components/legacy-table/selection-shortcuts-hoc.d.ts +5 -5
- package/components/legacy-table/simple-table.d.ts +2 -2
- package/components/legacy-table/simple-table.js +3 -3
- package/components/legacy-table/smart-table.d.ts +5 -5
- package/components/legacy-table/smart-table.js +3 -3
- package/components/legacy-table/table.js +1 -1
- package/components/login-dialog/service.js +2 -2
- package/components/popup/popup.d.ts +2 -0
- package/components/popup/popup.js +2 -2
- package/components/popup/popup.target.d.ts +3 -2
- package/components/popup/popup.target.js +4 -6
- package/components/query-assist/query-assist.d.ts +3 -1
- package/components/query-assist/query-assist.js +2 -2
- package/components/radio/radio-item.d.ts +3 -3
- package/components/radio/radio-item.js +3 -4
- package/components/radio/radio.d.ts +2 -2
- package/components/radio/radio.js +1 -1
- package/components/select/select-popup.d.ts +1 -1
- package/components/select/select.d.ts +4 -2
- package/components/slider/slider.js +4 -5
- package/components/tab-trap/tab-trap.d.ts +3 -3
- package/components/tab-trap/tab-trap.js +3 -5
- package/components/table/default-item-renderer.d.ts +35 -11
- package/components/table/default-item-renderer.js +47 -47
- 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} +78 -44
- 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 +21 -0
- package/components/table/table-primitives.js +25 -0
- 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 -224
- package/components/table/table.js +298 -2
- package/components/tags-input/tags-input.d.ts +3 -1
- package/components/tooltip/tooltip.js +2 -2
- package/components/upload/upload.d.ts +4 -3
- package/components/upload/upload.js +3 -7
- package/components/user-agreement/service.js +2 -2
- package/components/util-stories.d.ts +26 -0
- package/components/util-stories.js +61 -0
- package/package.json +41 -41
- 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/global/use-event-callback.d.ts +0 -1
- package/components/global/use-event-callback.js +0 -15
- package/components/table/table-base.d.ts +0 -24
- package/components/table/table-base.js +0 -79
- package/components/table/table-component.d.ts +0 -53
- package/components/table/table-component.js +0 -101
- package/components/table/table-virtualize.d.ts +0 -32
- /package/components/legacy-table/{table.css → legacy-table.css} +0 -0
|
@@ -1,138 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
}
|
|
1
|
+
/* Deprecated alias of `../collapsible-group/collapsible-group.css` (removed in Ring UI 8.0).
|
|
2
|
+
Class names are forwarded via `composes` so the canonical stylesheet stays the single source of
|
|
3
|
+
the actual rules, while `import styles from '.../components/expand/collapsible-group.css'` keeps
|
|
4
|
+
returning the full token map. */
|
|
7
5
|
|
|
8
6
|
.expand {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
box-sizing: border-box;
|
|
7
|
+
composes: expand from '../collapsible-group/collapsible-group.css';
|
|
8
|
+
}
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
outline: none;
|
|
17
|
-
background: var(--ring-content-background-color);
|
|
10
|
+
.collapseRoot {
|
|
11
|
+
composes: collapseRoot from '../collapsible-group/collapsible-group.css';
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
.hovered {
|
|
21
|
-
|
|
15
|
+
composes: hovered from '../collapsible-group/collapsible-group.css';
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
.expanded {
|
|
25
|
-
|
|
19
|
+
composes: expanded from '../collapsible-group/collapsible-group.css';
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
.focused {
|
|
29
|
-
|
|
23
|
+
composes: focused from '../collapsible-group/collapsible-group.css';
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
.header {
|
|
33
|
-
|
|
34
|
-
align-items: center;
|
|
35
|
-
gap: calc(var(--ring-unit) * 1.5);
|
|
36
|
-
|
|
37
|
-
height: 28px;
|
|
27
|
+
composes: header from '../collapsible-group/collapsible-group.css';
|
|
38
28
|
}
|
|
39
29
|
|
|
40
30
|
.headerButton {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
box-sizing: border-box;
|
|
44
|
-
width: 100%;
|
|
45
|
-
padding: calc(var(--ring-unit) * 1.5);
|
|
46
|
-
|
|
47
|
-
cursor: pointer;
|
|
48
|
-
text-align: left;
|
|
49
|
-
|
|
50
|
-
color: inherit;
|
|
51
|
-
|
|
52
|
-
border: 0;
|
|
53
|
-
background: transparent;
|
|
54
|
-
|
|
55
|
-
font: inherit;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.headerButton:focus-visible {
|
|
59
|
-
outline: none;
|
|
31
|
+
composes: headerButton from '../collapsible-group/collapsible-group.css';
|
|
60
32
|
}
|
|
61
33
|
|
|
62
34
|
.headerStatic {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
padding: calc(var(--ring-unit) * 1.5);
|
|
35
|
+
composes: headerStatic from '../collapsible-group/collapsible-group.css';
|
|
66
36
|
}
|
|
67
37
|
|
|
68
38
|
.headerContent {
|
|
69
|
-
|
|
70
|
-
align-items: center;
|
|
71
|
-
gap: var(--ring-unit);
|
|
72
|
-
|
|
73
|
-
flex: 1;
|
|
74
|
-
|
|
75
|
-
min-width: 0;
|
|
39
|
+
composes: headerContent from '../collapsible-group/collapsible-group.css';
|
|
76
40
|
}
|
|
77
41
|
|
|
78
42
|
.avatarGroup {
|
|
79
|
-
|
|
80
|
-
align-items: center;
|
|
81
|
-
gap: var(--ring-unit);
|
|
82
|
-
|
|
83
|
-
flex-shrink: 0;
|
|
43
|
+
composes: avatarGroup from '../collapsible-group/collapsible-group.css';
|
|
84
44
|
}
|
|
85
45
|
|
|
86
46
|
.title {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
font-size: var(--ring-font-size);
|
|
90
|
-
font-weight: var(--ring-font-weight-bold);
|
|
91
|
-
line-height: var(--ring-line-height);
|
|
47
|
+
composes: title from '../collapsible-group/collapsible-group.css';
|
|
92
48
|
}
|
|
93
49
|
|
|
94
50
|
.subtitleGroup {
|
|
95
|
-
|
|
96
|
-
align-items: center;
|
|
97
|
-
gap: var(--ring-unit);
|
|
98
|
-
|
|
99
|
-
min-width: 0;
|
|
51
|
+
composes: subtitleGroup from '../collapsible-group/collapsible-group.css';
|
|
100
52
|
}
|
|
101
53
|
|
|
102
54
|
.subtitle {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
font-size: var(--ring-font-size);
|
|
106
|
-
line-height: var(--ring-line-height);
|
|
55
|
+
composes: subtitle from '../collapsible-group/collapsible-group.css';
|
|
107
56
|
}
|
|
108
57
|
|
|
109
58
|
.subtitleChevron {
|
|
110
|
-
|
|
59
|
+
composes: subtitleChevron from '../collapsible-group/collapsible-group.css';
|
|
111
60
|
}
|
|
112
61
|
|
|
113
62
|
.toggle {
|
|
114
|
-
|
|
115
|
-
align-items: center;
|
|
116
|
-
|
|
117
|
-
flex-shrink: 0;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
|
|
120
|
-
width: calc(var(--ring-unit) * 3);
|
|
121
|
-
height: calc(var(--ring-unit) * 3);
|
|
122
|
-
|
|
123
|
-
border-radius: calc(var(--ring-unit) / 2);
|
|
124
|
-
background: var(--ring-secondary-background-color);
|
|
63
|
+
composes: toggle from '../collapsible-group/collapsible-group.css';
|
|
125
64
|
}
|
|
126
65
|
|
|
127
66
|
.toggleIcon {
|
|
128
|
-
|
|
67
|
+
composes: toggleIcon from '../collapsible-group/collapsible-group.css';
|
|
129
68
|
}
|
|
130
69
|
|
|
131
70
|
.body {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
color: var(--ring-text-color);
|
|
135
|
-
|
|
136
|
-
font-size: var(--ring-font-size);
|
|
137
|
-
line-height: var(--ring-line-height);
|
|
71
|
+
composes: body from '../collapsible-group/collapsible-group.css';
|
|
138
72
|
}
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
expanded?: boolean | null | undefined;
|
|
10
|
-
onChange?: (expanded: boolean) => void;
|
|
11
|
-
disableAnimation?: boolean;
|
|
12
|
-
interactive?: boolean;
|
|
13
|
-
'data-test'?: string | null | undefined;
|
|
14
|
-
}
|
|
15
|
-
declare const CollapsibleGroup: React.ForwardRefExoticComponent<CollapsibleGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
-
export default CollapsibleGroup;
|
|
1
|
+
import { type CollapsibleGroupProps } from '../collapsible-group/collapsible-group';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated The `expand` module has been renamed to `collapsible-group`. This re-export will be removed in
|
|
4
|
+
* Ring UI 8.0. Use `CollapsibleGroup` from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.
|
|
5
|
+
*/
|
|
6
|
+
declare const DeprecatedCollapsibleGroup: import("react").ForwardRefExoticComponent<Omit<CollapsibleGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export type { CollapsibleGroupProps };
|
|
8
|
+
export default DeprecatedCollapsibleGroup;
|
|
@@ -1,73 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return (<span className={styles.header}>
|
|
13
|
-
<span className={styles.headerContent}>
|
|
14
|
-
<span className={styles.avatarGroup}>
|
|
15
|
-
{avatar}
|
|
16
|
-
<span className={styles.title}>{titleContent}</span>
|
|
17
|
-
</span>
|
|
18
|
-
{subtitle ? (<span className={styles.subtitleGroup}>
|
|
19
|
-
<Icon className={styles.subtitleChevron} glyph={chevronRightIcon} aria-hidden/>
|
|
20
|
-
<span className={styles.subtitle}>{subtitle}</span>
|
|
21
|
-
</span>) : null}
|
|
22
|
-
</span>
|
|
23
|
-
<span className={styles.toggle} aria-hidden>
|
|
24
|
-
<Icon className={styles.toggleIcon} glyph={collapsed ? chevronRightIcon : chevronDownIcon}/>
|
|
25
|
-
</span>
|
|
26
|
-
</span>);
|
|
27
|
-
}
|
|
28
|
-
function CollapsibleGroupHeader({ avatar, titleContent, subtitle, ...buttonProps }) {
|
|
29
|
-
const { setCollapsed, collapsed, id } = useContext(CollapseContext);
|
|
30
|
-
return (<button type='button' {...buttonProps} className={styles.headerButton} onClick={setCollapsed} aria-controls={`collapse-content-${id}`} aria-expanded={!collapsed}>
|
|
31
|
-
<CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
|
|
32
|
-
</button>);
|
|
33
|
-
}
|
|
34
|
-
function CollapsibleGroupHeaderStatic({ avatar, titleContent, subtitle }) {
|
|
35
|
-
return (<span className={styles.headerStatic}>
|
|
36
|
-
<CollapsibleGroupHeaderContent avatar={avatar} titleContent={titleContent} subtitle={subtitle}/>
|
|
37
|
-
</span>);
|
|
38
|
-
}
|
|
39
|
-
const CollapsibleGroup = forwardRef(({ avatar, title, subtitle, children, className, defaultExpanded = false, expanded = null, onChange = () => { }, disableAnimation = false, interactive = true, 'data-test': dataTest, }, ref) => {
|
|
40
|
-
const [innerExpanded, setInnerExpanded] = useState(defaultExpanded);
|
|
41
|
-
const [hovered, setHovered] = useState(false);
|
|
42
|
-
const [focused, setFocused] = useState(false);
|
|
43
|
-
const isExpanded = expanded ?? innerExpanded;
|
|
44
|
-
const handleChange = (collapsed) => {
|
|
45
|
-
const nextExpanded = !collapsed;
|
|
46
|
-
if (expanded == null) {
|
|
47
|
-
setInnerExpanded(nextExpanded);
|
|
48
|
-
}
|
|
49
|
-
onChange(nextExpanded);
|
|
50
|
-
};
|
|
51
|
-
const onBlur = (event) => {
|
|
52
|
-
const nextTarget = event.relatedTarget;
|
|
53
|
-
if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
setFocused(false);
|
|
57
|
-
};
|
|
58
|
-
const classes = classNames(styles.expand, className, {
|
|
59
|
-
[styles.hovered]: hovered,
|
|
60
|
-
[styles.expanded]: isExpanded,
|
|
61
|
-
[styles.focused]: focused,
|
|
62
|
-
});
|
|
63
|
-
return (<div ref={ref} className={classes} data-test={dataTest}>
|
|
64
|
-
<Collapse defaultCollapsed={!defaultExpanded} collapsed={expanded == null ? null : !expanded} onChange={handleChange} disableAnimation={disableAnimation} className={styles.collapseRoot}>
|
|
65
|
-
{interactive ? (<CollapsibleGroupHeader avatar={avatar} titleContent={title} subtitle={subtitle} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} onFocus={() => setFocused(true)} onBlur={onBlur}/>) : (<CollapsibleGroupHeaderStatic avatar={avatar} titleContent={title} subtitle={subtitle}/>)}
|
|
66
|
-
<CollapseContent>
|
|
67
|
-
<div className={styles.body}>{children}</div>
|
|
68
|
-
</CollapseContent>
|
|
69
|
-
</Collapse>
|
|
70
|
-
</div>);
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import deprecate from 'util-deprecate';
|
|
3
|
+
import CollapsibleGroup from '../collapsible-group/collapsible-group';
|
|
4
|
+
const warnDeprecation = deprecate(() => { }, '`CollapsibleGroup` from `@jetbrains/ring-ui/components/expand/collapsible-group` is deprecated and will be removed in Ring UI 8.0. Import it from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.');
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated The `expand` module has been renamed to `collapsible-group`. This re-export will be removed in
|
|
7
|
+
* Ring UI 8.0. Use `CollapsibleGroup` from `@jetbrains/ring-ui/components/collapsible-group/collapsible-group` instead.
|
|
8
|
+
*/
|
|
9
|
+
const DeprecatedCollapsibleGroup = forwardRef((props, ref) => {
|
|
10
|
+
warnDeprecation();
|
|
11
|
+
return <CollapsibleGroup ref={ref} {...props}/>;
|
|
71
12
|
});
|
|
72
|
-
|
|
73
|
-
export default
|
|
13
|
+
DeprecatedCollapsibleGroup.displayName = 'CollapsibleGroup';
|
|
14
|
+
export default DeprecatedCollapsibleGroup;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type Ref } from 'react';
|
|
2
|
-
export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => void>;
|
|
2
|
+
export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => () => void>;
|
|
3
|
+
export declare function useComposedRef<T>(...refs: (Ref<T> | undefined)[]): Ref<T>;
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
1
2
|
import memoizeOne from 'memoize-one';
|
|
2
3
|
function composeRefs(...refs) {
|
|
3
|
-
return (value) =>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
return (value) => {
|
|
5
|
+
const cleanups = [];
|
|
6
|
+
refs.forEach(ref => {
|
|
7
|
+
if (typeof ref === 'function') {
|
|
8
|
+
const cleanup = ref(value);
|
|
9
|
+
if (typeof cleanup === 'function') {
|
|
10
|
+
cleanups.push(cleanup);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
else if (ref) {
|
|
14
|
+
ref.current = value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return () => cleanups.forEach(cleanup => cleanup());
|
|
18
|
+
};
|
|
11
19
|
}
|
|
12
20
|
export function createComposedRef() {
|
|
13
21
|
return memoizeOne((composeRefs));
|
|
14
22
|
}
|
|
23
|
+
export function useComposedRef(...refs) {
|
|
24
|
+
/**
|
|
25
|
+
* The React Compiler doesn't allow non-literal arrays in useMemo
|
|
26
|
+
* dependency lists, so we still use memoizeOne under the hood.
|
|
27
|
+
*/
|
|
28
|
+
const composer = useMemo(() => createComposedRef(), []);
|
|
29
|
+
return composer(...refs);
|
|
30
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { createContext, memo,
|
|
1
|
+
import { createContext, memo, use, useEffect, useState } from 'react';
|
|
2
2
|
export default function createStatefulContext(initialValue, name = '') {
|
|
3
3
|
const ValueContext = createContext(initialValue);
|
|
4
4
|
const UpdateContext = createContext(() => { });
|
|
5
5
|
function Provider({ children }) {
|
|
6
6
|
const [value, update] = useState(initialValue);
|
|
7
|
-
return (<ValueContext
|
|
8
|
-
<UpdateContext
|
|
9
|
-
</ValueContext
|
|
7
|
+
return (<ValueContext value={value}>
|
|
8
|
+
<UpdateContext value={update}>{children}</UpdateContext>
|
|
9
|
+
</ValueContext>);
|
|
10
10
|
}
|
|
11
11
|
Provider.displayName = `${name}Provider`;
|
|
12
12
|
function useUpdate(value, skipUpdate) {
|
|
13
|
-
const update =
|
|
13
|
+
const update = use(UpdateContext);
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (!skipUpdate) {
|
|
16
16
|
update(value);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focuses an element, temporarily adding `tabindex="0"` if necessary.
|
|
3
|
+
*
|
|
4
|
+
* If the element does not already have a `tabindex` attribute, one is
|
|
5
|
+
* added before focusing and automatically removed when the element loses
|
|
6
|
+
* focus.
|
|
7
|
+
*
|
|
8
|
+
* This is useful when implementing roving tabindex patterns on elements
|
|
9
|
+
* that are not normally focusable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function focusWithTemporaryTabIndex(element: HTMLElement): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focuses an element, temporarily adding `tabindex="0"` if necessary.
|
|
3
|
+
*
|
|
4
|
+
* If the element does not already have a `tabindex` attribute, one is
|
|
5
|
+
* added before focusing and automatically removed when the element loses
|
|
6
|
+
* focus.
|
|
7
|
+
*
|
|
8
|
+
* This is useful when implementing roving tabindex patterns on elements
|
|
9
|
+
* that are not normally focusable.
|
|
10
|
+
*/
|
|
11
|
+
export function focusWithTemporaryTabIndex(element) {
|
|
12
|
+
if (!element.hasAttribute('tabindex')) {
|
|
13
|
+
element.tabIndex = 0;
|
|
14
|
+
function onBlur() {
|
|
15
|
+
element.removeAttribute('tabindex');
|
|
16
|
+
element.removeEventListener('blur', onBlur);
|
|
17
|
+
}
|
|
18
|
+
element.addEventListener('blur', onBlur);
|
|
19
|
+
}
|
|
20
|
+
element.focus();
|
|
21
|
+
}
|
|
@@ -1,26 +1,46 @@
|
|
|
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
|
|
6
|
-
* <IntersectionObserverContext
|
|
22
|
+
* <IntersectionObserverContext value={useIntersectionObserverHandle()}>
|
|
7
23
|
* <YourComponent />
|
|
8
|
-
* </IntersectionObserverContext
|
|
24
|
+
* </IntersectionObserverContext>
|
|
9
25
|
*
|
|
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,32 +1,45 @@
|
|
|
1
|
-
import { createContext,
|
|
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
|
|
6
|
-
* <IntersectionObserverContext
|
|
14
|
+
* <IntersectionObserverContext value={useIntersectionObserverHandle()}>
|
|
7
15
|
* <YourComponent />
|
|
8
|
-
* </IntersectionObserverContext
|
|
16
|
+
* </IntersectionObserverContext>
|
|
9
17
|
*
|
|
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
|
-
const handle =
|
|
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 = useContext(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
|
+
}
|