@itwin/itwinui-react 1.37.2 → 1.38.1
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/CHANGELOG.md +28 -0
- package/cjs/core/Breadcrumbs/Breadcrumbs.js +3 -5
- package/cjs/core/ColorPicker/ColorSwatch.d.ts +1 -1
- package/cjs/core/ComboBox/ComboBox.d.ts +11 -2
- package/cjs/core/ComboBox/ComboBox.js +135 -245
- package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +8 -0
- package/cjs/core/ComboBox/ComboBoxDropdown.js +55 -0
- package/cjs/core/ComboBox/ComboBoxEndIcon.d.ts +5 -0
- package/cjs/core/ComboBox/ComboBoxEndIcon.js +54 -0
- package/cjs/core/ComboBox/ComboBoxInput.d.ts +5 -0
- package/cjs/core/ComboBox/ComboBoxInput.js +134 -0
- package/cjs/core/ComboBox/ComboBoxInputContainer.d.ts +8 -0
- package/cjs/core/ComboBox/ComboBoxInputContainer.js +45 -0
- package/cjs/core/ComboBox/ComboBoxMenu.d.ts +3 -0
- package/cjs/core/ComboBox/ComboBoxMenu.js +81 -0
- package/cjs/core/ComboBox/ComboBoxMenuItem.d.ts +21 -0
- package/cjs/core/ComboBox/ComboBoxMenuItem.js +64 -0
- package/cjs/core/ComboBox/helpers.d.ts +32 -0
- package/cjs/core/ComboBox/helpers.js +50 -0
- package/cjs/core/Modal/Modal.d.ts +1 -1
- package/cjs/core/Modal/Modal.js +6 -6
- package/cjs/core/Modal/ModalButtonBar.d.ts +1 -1
- package/cjs/core/Modal/ModalButtonBar.js +2 -2
- package/cjs/core/Modal/ModalContent.d.ts +1 -1
- package/cjs/core/Modal/ModalContent.js +2 -2
- package/cjs/core/RadioTiles/RadioTile.d.ts +1 -1
- package/cjs/core/RadioTiles/RadioTile.js +7 -9
- package/cjs/core/Select/Select.js +1 -1
- package/cjs/core/Slider/Thumb.js +15 -1
- package/cjs/core/Slider/Track.js +23 -12
- package/cjs/core/Table/Table.js +2 -2
- package/cjs/core/Table/filters/FilterToggle.js +3 -2
- package/cjs/core/Toast/ToastWrapper.d.ts +7 -5
- package/cjs/core/Toast/ToastWrapper.js +8 -4
- package/cjs/core/Toast/Toaster.d.ts +3 -0
- package/cjs/core/Toast/Toaster.js +30 -5
- package/cjs/core/utils/components/Popover.d.ts +1 -1
- package/cjs/core/utils/components/VirtualScroll.d.ts +35 -1
- package/cjs/core/utils/components/VirtualScroll.js +159 -26
- package/cjs/core/utils/hooks/index.d.ts +1 -0
- package/cjs/core/utils/hooks/index.js +1 -0
- package/cjs/core/utils/hooks/useSafeContext.d.ts +6 -0
- package/cjs/core/utils/hooks/useSafeContext.js +23 -0
- package/esm/core/Breadcrumbs/Breadcrumbs.js +3 -5
- package/esm/core/ColorPicker/ColorSwatch.d.ts +1 -1
- package/esm/core/ComboBox/ComboBox.d.ts +11 -2
- package/esm/core/ComboBox/ComboBox.js +137 -247
- package/esm/core/ComboBox/ComboBoxDropdown.d.ts +8 -0
- package/esm/core/ComboBox/ComboBoxDropdown.js +49 -0
- package/esm/core/ComboBox/ComboBoxEndIcon.d.ts +5 -0
- package/esm/core/ComboBox/ComboBoxEndIcon.js +48 -0
- package/esm/core/ComboBox/ComboBoxInput.d.ts +5 -0
- package/esm/core/ComboBox/ComboBoxInput.js +128 -0
- package/esm/core/ComboBox/ComboBoxInputContainer.d.ts +8 -0
- package/esm/core/ComboBox/ComboBoxInputContainer.js +38 -0
- package/esm/core/ComboBox/ComboBoxMenu.d.ts +3 -0
- package/esm/core/ComboBox/ComboBoxMenu.js +75 -0
- package/esm/core/ComboBox/ComboBoxMenuItem.d.ts +21 -0
- package/esm/core/ComboBox/ComboBoxMenuItem.js +58 -0
- package/esm/core/ComboBox/helpers.d.ts +32 -0
- package/esm/core/ComboBox/helpers.js +43 -0
- package/esm/core/Modal/Modal.d.ts +1 -1
- package/esm/core/Modal/Modal.js +6 -6
- package/esm/core/Modal/ModalButtonBar.d.ts +1 -1
- package/esm/core/Modal/ModalButtonBar.js +2 -2
- package/esm/core/Modal/ModalContent.d.ts +1 -1
- package/esm/core/Modal/ModalContent.js +2 -2
- package/esm/core/RadioTiles/RadioTile.d.ts +1 -1
- package/esm/core/RadioTiles/RadioTile.js +7 -9
- package/esm/core/Select/Select.js +1 -1
- package/esm/core/Slider/Thumb.js +15 -1
- package/esm/core/Slider/Track.js +23 -12
- package/esm/core/Table/Table.js +2 -2
- package/esm/core/Table/filters/FilterToggle.js +3 -2
- package/esm/core/Toast/ToastWrapper.d.ts +7 -5
- package/esm/core/Toast/ToastWrapper.js +8 -3
- package/esm/core/Toast/Toaster.d.ts +3 -0
- package/esm/core/Toast/Toaster.js +30 -5
- package/esm/core/utils/components/Popover.d.ts +1 -1
- package/esm/core/utils/components/VirtualScroll.d.ts +35 -1
- package/esm/core/utils/components/VirtualScroll.js +157 -25
- package/esm/core/utils/hooks/index.d.ts +1 -0
- package/esm/core/utils/hooks/index.js +1 -0
- package/esm/core/utils/hooks/useSafeContext.d.ts +6 -0
- package/esm/core/utils/hooks/useSafeContext.js +16 -0
- package/package.json +5 -33
package/esm/core/Slider/Thumb.js
CHANGED
|
@@ -64,7 +64,21 @@ export var Thumb = function (props) {
|
|
|
64
64
|
}, [disabled, index, onThumbActivated]);
|
|
65
65
|
var _a = React.useState(false), hasFocus = _a[0], setHasFocus = _a[1];
|
|
66
66
|
var _b = React.useState(false), isHovered = _b[0], setIsHovered = _b[1];
|
|
67
|
-
var
|
|
67
|
+
var adjustedValue = React.useMemo(function () {
|
|
68
|
+
if (value < sliderMin) {
|
|
69
|
+
return sliderMin;
|
|
70
|
+
}
|
|
71
|
+
if (value > sliderMax) {
|
|
72
|
+
return sliderMax;
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}, [sliderMax, sliderMin, value]);
|
|
76
|
+
var leftPercent = React.useMemo(function () {
|
|
77
|
+
if (sliderMax === sliderMin) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
return (100.0 * (adjustedValue - sliderMin)) / (sliderMax - sliderMin);
|
|
81
|
+
}, [adjustedValue, sliderMax, sliderMin]);
|
|
68
82
|
var _c = thumbProps || {}, style = _c.style, className = _c.className, rest = __rest(_c, ["style", "className"]);
|
|
69
83
|
return (React.createElement(Tooltip, __assign({ visible: isActive || hasFocus || isHovered, placement: 'top' }, tooltipProps),
|
|
70
84
|
React.createElement("div", __assign({}, rest, { "data-index": index, ref: thumbRef, style: __assign(__assign({}, style), { left: "".concat(leftPercent, "%") }), className: cx('iui-slider-thumb', { 'iui-active': isActive }, className), role: 'slider', tabIndex: disabled ? undefined : 0, "aria-valuemin": minVal, "aria-valuenow": value, "aria-valuemax": maxVal, "aria-disabled": disabled, onPointerDown: handlePointerDownOnThumb, onKeyDown: handleOnKeyDown, onFocus: function () { return setHasFocus(true); }, onBlur: function () { return setHasFocus(false); }, onMouseEnter: function () { return setIsHovered(true); }, onMouseLeave: function () { return setIsHovered(false); } }))));
|
package/esm/core/Slider/Track.js
CHANGED
|
@@ -23,10 +23,18 @@ function shouldDisplaySegment(segmentIndex, mode) {
|
|
|
23
23
|
}
|
|
24
24
|
function generateSegments(values, min, max) {
|
|
25
25
|
var segments = [];
|
|
26
|
+
var newValues = __spreadArray([], values, true);
|
|
27
|
+
newValues.sort(function (a, b) { return a - b; });
|
|
28
|
+
if (0 === newValues.length ||
|
|
29
|
+
newValues[0] < min ||
|
|
30
|
+
newValues[newValues.length - 1] > max ||
|
|
31
|
+
min === max) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
26
34
|
var lastValue = min;
|
|
27
|
-
for (var i = 0; i <
|
|
28
|
-
segments.push({ left: lastValue, right:
|
|
29
|
-
lastValue =
|
|
35
|
+
for (var i = 0; i < newValues.length; i++) {
|
|
36
|
+
segments.push({ left: lastValue, right: newValues[i] });
|
|
37
|
+
lastValue = newValues[i];
|
|
30
38
|
}
|
|
31
39
|
segments.push({ left: lastValue, right: max });
|
|
32
40
|
return segments;
|
|
@@ -37,18 +45,21 @@ function generateSegments(values, min, max) {
|
|
|
37
45
|
*/
|
|
38
46
|
export var Track = function (props) {
|
|
39
47
|
var trackDisplayMode = props.trackDisplayMode, sliderMin = props.sliderMin, sliderMax = props.sliderMax, values = props.values;
|
|
40
|
-
var _a = React.useState(
|
|
48
|
+
var _a = React.useState(function () {
|
|
49
|
+
return generateSegments(values, sliderMin, sliderMax);
|
|
50
|
+
}), segments = _a[0], setSegments = _a[1];
|
|
41
51
|
React.useEffect(function () {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
setCurrentValues(newValues);
|
|
45
|
-
}, [values]);
|
|
46
|
-
var segments = React.useMemo(function () { return generateSegments(currentValues, sliderMin, sliderMax); }, [currentValues, sliderMin, sliderMax]);
|
|
52
|
+
setSegments(generateSegments(values, sliderMin, sliderMax));
|
|
53
|
+
}, [values, sliderMin, sliderMax]);
|
|
47
54
|
return (React.createElement(React.Fragment, null, 'none' !== trackDisplayMode &&
|
|
48
55
|
segments.map(function (segment, index) {
|
|
49
|
-
var leftPercent =
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
var leftPercent = segment.left >= sliderMin && sliderMax !== sliderMin
|
|
57
|
+
? (100.0 * (segment.left - sliderMin)) / (sliderMax - sliderMin)
|
|
58
|
+
: 0;
|
|
59
|
+
var rightPercent = segment.right >= sliderMin && sliderMax !== sliderMin
|
|
60
|
+
? 100.0 -
|
|
61
|
+
(100.0 * (segment.right - sliderMin)) / (sliderMax - sliderMin)
|
|
62
|
+
: 100;
|
|
52
63
|
return (React.createElement(React.Fragment, { key: index }, shouldDisplaySegment(index, trackDisplayMode) ? (React.createElement("div", { className: 'iui-slider-track', style: { left: "".concat(leftPercent, "%"), right: "".concat(rightPercent, "%") } })) : null));
|
|
53
64
|
})));
|
|
54
65
|
};
|
package/esm/core/Table/Table.js
CHANGED
|
@@ -180,7 +180,7 @@ export var Table = function (props) {
|
|
|
180
180
|
}
|
|
181
181
|
return result;
|
|
182
182
|
}, {});
|
|
183
|
-
var areFiltersSet = allColumns.some(function (column) { return
|
|
183
|
+
var areFiltersSet = allColumns.some(function (column) { return column.filterValue != null && column.filterValue !== ''; });
|
|
184
184
|
var onRowClickHandler = React.useCallback(function (event, row) {
|
|
185
185
|
var isDisabled = isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original);
|
|
186
186
|
if (!isDisabled) {
|
|
@@ -296,7 +296,7 @@ export var Table = function (props) {
|
|
|
296
296
|
className: 'iui-row',
|
|
297
297
|
});
|
|
298
298
|
return (React.createElement("div", __assign({}, headerGroupProps, { key: headerGroupProps.key }), headerGroup.headers.map(function (column, index) {
|
|
299
|
-
var columnProps = column.getHeaderProps(__assign(__assign({}, column.getSortByToggleProps()), { className: cx('iui-cell', { 'iui-actionable': column.canSort }, { 'iui-sorted': column.isSorted }, column.columnClassName), style: __assign({}, getCellStyle(column, !!state.isTableResizing)) }));
|
|
299
|
+
var columnProps = column.getHeaderProps(__assign(__assign({}, column.getSortByToggleProps()), { className: cx('iui-cell', { 'iui-actionable': column.canSort }, { 'iui-sorted': column.isSorted }, column.columnClassName), style: __assign(__assign({}, getCellStyle(column, !!state.isTableResizing)), { flexWrap: 'unset' }) }));
|
|
300
300
|
return (React.createElement("div", __assign({}, columnProps, column.getDragAndDropProps(), { key: columnProps.key, title: undefined, ref: function (el) {
|
|
301
301
|
if (el && isResizable) {
|
|
302
302
|
columnRefs.current[column.id] = el;
|
|
@@ -47,10 +47,11 @@ export var FilterToggle = function (props) {
|
|
|
47
47
|
column.setFilter(undefined);
|
|
48
48
|
close();
|
|
49
49
|
}, [close, column]);
|
|
50
|
+
var isColumnFiltered = column.filterValue != null && column.filterValue !== '';
|
|
50
51
|
return (React.createElement(React.Fragment, null, column.canFilter && column.Filter && (React.createElement(Popover, { content: column.render('Filter', { close: close, setFilter: setFilter, clearFilter: clearFilter }), placement: 'bottom-start', visible: isVisible, onClickOutside: close, appendTo: ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body },
|
|
51
|
-
React.createElement(IconButton, __assign({ styleType: 'borderless', isActive: isVisible ||
|
|
52
|
+
React.createElement(IconButton, __assign({ styleType: 'borderless', isActive: isVisible || isColumnFiltered, className: cx('iui-filter-button', className), onClick: function (e) {
|
|
52
53
|
setIsVisible(function (v) { return !v; });
|
|
53
54
|
// Prevents from triggering sort
|
|
54
55
|
e.stopPropagation();
|
|
55
|
-
} }, rest),
|
|
56
|
+
} }, rest), isColumnFiltered ? React.createElement(SvgFilter, null) : React.createElement(SvgFilterHollow, null))))));
|
|
56
57
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import '@itwin/itwinui-css/css/toast-notification.css';
|
|
2
|
+
import React from 'react';
|
|
3
3
|
import { ToastProps } from './Toast';
|
|
4
4
|
import { ToasterSettings } from './Toaster';
|
|
5
|
-
declare type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
declare type ToastPlacement = NonNullable<ToasterSettings['placement']>;
|
|
6
|
+
export declare type ToastWrapperHandle = {
|
|
7
|
+
setToasts: (toasts: ToastProps[]) => void;
|
|
8
|
+
setPlacement: (placement: ToastPlacement) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare const ToastWrapper: React.ForwardRefExoticComponent<React.RefAttributes<ToastWrapperHandle>>;
|
|
9
11
|
export {};
|
|
@@ -17,10 +17,15 @@ import '@itwin/itwinui-css/css/toast-notification.css';
|
|
|
17
17
|
import React from 'react';
|
|
18
18
|
import cx from 'classnames';
|
|
19
19
|
import Toast from './Toast';
|
|
20
|
-
export var ToastWrapper = function (
|
|
21
|
-
var
|
|
20
|
+
export var ToastWrapper = React.forwardRef(function (_, ref) {
|
|
21
|
+
var _a = React.useState([]), toasts = _a[0], setToasts = _a[1];
|
|
22
|
+
var _b = React.useState('top'), placement = _b[0], setPlacement = _b[1];
|
|
22
23
|
var placementPosition = placement.startsWith('top') ? 'top' : 'bottom';
|
|
24
|
+
React.useImperativeHandle(ref, function () { return ({
|
|
25
|
+
setToasts: setToasts,
|
|
26
|
+
setPlacement: setPlacement,
|
|
27
|
+
}); }, []);
|
|
23
28
|
return (React.createElement("span", { className: cx("iui-toast-wrapper", "iui-placement-".concat(placement)) }, toasts.map(function (toastProps) {
|
|
24
29
|
return (React.createElement(Toast, __assign({ key: toastProps.id, placementPosition: placementPosition }, toastProps)));
|
|
25
30
|
})));
|
|
26
|
-
};
|
|
31
|
+
});
|
|
@@ -20,6 +20,9 @@ export default class Toaster {
|
|
|
20
20
|
private toasts;
|
|
21
21
|
private lastId;
|
|
22
22
|
private settings;
|
|
23
|
+
private toastsRef;
|
|
24
|
+
private isInitialized;
|
|
25
|
+
private asyncInit;
|
|
23
26
|
/**
|
|
24
27
|
* Set global Toaster settings for toasts order and placement.
|
|
25
28
|
* Settings will be applied to new toasts on the page.
|
|
@@ -29,24 +29,49 @@ import { ToastWrapper } from './ToastWrapper';
|
|
|
29
29
|
var TOASTS_CONTAINER_ID = 'iui-toasts-container';
|
|
30
30
|
var Toaster = /** @class */ (function () {
|
|
31
31
|
function Toaster() {
|
|
32
|
+
var _this = this;
|
|
32
33
|
this.toasts = [];
|
|
33
34
|
this.lastId = 0;
|
|
34
35
|
this.settings = {
|
|
35
36
|
order: 'descending',
|
|
36
37
|
placement: 'top',
|
|
37
38
|
};
|
|
39
|
+
this.toastsRef = React.createRef();
|
|
40
|
+
this.isInitialized = false;
|
|
41
|
+
// Create container on demand.
|
|
42
|
+
// Cannot do it in constructor, because SSG/SSR apps would fail.
|
|
43
|
+
this.asyncInit = new Promise(function (resolve) {
|
|
44
|
+
if (_this.isInitialized) {
|
|
45
|
+
resolve();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
var container = getContainer(TOASTS_CONTAINER_ID);
|
|
49
|
+
if (!container) {
|
|
50
|
+
// should never happen
|
|
51
|
+
resolve();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
_this.isInitialized = true;
|
|
55
|
+
ReactDOM.render(React.createElement(ToastWrapper, { ref: _this.toastsRef }), container);
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
38
58
|
}
|
|
39
59
|
/**
|
|
40
60
|
* Set global Toaster settings for toasts order and placement.
|
|
41
61
|
* Settings will be applied to new toasts on the page.
|
|
42
62
|
*/
|
|
43
63
|
Toaster.prototype.setSettings = function (newSettings) {
|
|
64
|
+
var _this = this;
|
|
44
65
|
var _a, _b, _c;
|
|
45
66
|
(_a = newSettings.placement) !== null && _a !== void 0 ? _a : (newSettings.placement = this.settings.placement);
|
|
46
67
|
(_b = newSettings.order) !== null && _b !== void 0 ? _b : (newSettings.order = ((_c = newSettings.placement) === null || _c === void 0 ? void 0 : _c.startsWith('bottom'))
|
|
47
68
|
? 'ascending'
|
|
48
69
|
: 'descending');
|
|
49
70
|
this.settings = newSettings;
|
|
71
|
+
this.asyncInit.then(function () {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
(_a = _this.toastsRef.current) === null || _a === void 0 ? void 0 : _a.setPlacement((_b = _this.settings.placement) !== null && _b !== void 0 ? _b : 'top');
|
|
74
|
+
});
|
|
50
75
|
};
|
|
51
76
|
Toaster.prototype.positive = function (content, options) {
|
|
52
77
|
return this.createToast(content, 'positive', options);
|
|
@@ -79,11 +104,11 @@ var Toaster = /** @class */ (function () {
|
|
|
79
104
|
this.updateView();
|
|
80
105
|
};
|
|
81
106
|
Toaster.prototype.updateView = function () {
|
|
82
|
-
var
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
var _this = this;
|
|
108
|
+
this.asyncInit.then(function () {
|
|
109
|
+
var _a;
|
|
110
|
+
(_a = _this.toastsRef.current) === null || _a === void 0 ? void 0 : _a.setToasts(_this.toasts);
|
|
111
|
+
});
|
|
87
112
|
};
|
|
88
113
|
Toaster.prototype.closeToast = function (toastId) {
|
|
89
114
|
this.toasts = this.toasts.map(function (toast) {
|
|
@@ -43,7 +43,7 @@ export declare const Popover: React.ForwardRefExoticComponent<Pick<{
|
|
|
43
43
|
* @see [tippy.js placement prop](https://atomiks.github.io/tippyjs/v6/all-props/#placement).
|
|
44
44
|
*/
|
|
45
45
|
placement?: import("@popperjs/core").Placement | undefined;
|
|
46
|
-
} & Omit<TippyProps, "placement" | "trigger" | "visible">, "disabled" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "role" | "theme" | "zIndex" | "
|
|
46
|
+
} & Omit<TippyProps, "placement" | "trigger" | "visible">, "disabled" | "children" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "role" | "theme" | "zIndex" | "className" | "singleton" | "reference"> & React.RefAttributes<unknown>>;
|
|
47
47
|
/**
|
|
48
48
|
* Plugin to hide Popover when either Esc key is pressed,
|
|
49
49
|
* or when the content inside is not tabbable and Tab key is pressed.
|
|
@@ -16,6 +16,10 @@ export declare type VirtualScrollProps = {
|
|
|
16
16
|
* @default 10
|
|
17
17
|
*/
|
|
18
18
|
bufferSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Index of the first element on initial render.
|
|
21
|
+
*/
|
|
22
|
+
scrollToIndex?: number;
|
|
19
23
|
} & React.ComponentPropsWithRef<'div'>;
|
|
20
24
|
/**
|
|
21
25
|
* `VirtualScroll` component is used to render a huge amount of items in the DOM. It renders only the ones which are visible
|
|
@@ -38,5 +42,35 @@ export declare type VirtualScrollProps = {
|
|
|
38
42
|
* />
|
|
39
43
|
* @private
|
|
40
44
|
*/
|
|
41
|
-
export declare const VirtualScroll: React.ForwardRefExoticComponent<Pick<VirtualScrollProps, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "itemsLength" | "itemRenderer" | "bufferSize"> & React.RefAttributes<HTMLDivElement>>;
|
|
45
|
+
export declare const VirtualScroll: React.ForwardRefExoticComponent<Pick<VirtualScrollProps, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "itemsLength" | "itemRenderer" | "bufferSize" | "scrollToIndex"> & React.RefAttributes<HTMLDivElement>>;
|
|
46
|
+
/**
|
|
47
|
+
* `useVirtualization` is used for efficiently rendering only the visible rows from a large list.
|
|
48
|
+
* It returns `outerProps` and `innerProps`, which need to be applied on 2 container elements and `visibleChildren` which is a list of virtualized items.
|
|
49
|
+
* @example
|
|
50
|
+
* const itemRenderer = React.useCallback((index: number) => (
|
|
51
|
+
* <li key={index}>
|
|
52
|
+
* This is my item #{index}
|
|
53
|
+
* </li>
|
|
54
|
+
* ), [])
|
|
55
|
+
*
|
|
56
|
+
* const { outerProps, innerProps, visibleChildren } = useVirtualization({itemsLength: 1000, itemRenderer: itemRenderer});
|
|
57
|
+
* return (
|
|
58
|
+
* <div {...outerProps}>
|
|
59
|
+
* <ul {...innerProps}>
|
|
60
|
+
* {visibleChildren}
|
|
61
|
+
* </ul>
|
|
62
|
+
* </div>
|
|
63
|
+
* );
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
export declare const useVirtualization: (props: VirtualScrollProps) => {
|
|
67
|
+
outerProps: React.HTMLAttributes<HTMLElement>;
|
|
68
|
+
innerProps: {
|
|
69
|
+
readonly style: {
|
|
70
|
+
readonly willChange: "transform";
|
|
71
|
+
};
|
|
72
|
+
readonly ref: (instance: HTMLElement | null) => void;
|
|
73
|
+
};
|
|
74
|
+
visibleChildren: JSX.Element[];
|
|
75
|
+
};
|
|
42
76
|
export default VirtualScroll;
|
|
@@ -25,6 +25,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
25
25
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
26
26
|
*--------------------------------------------------------------------------------------------*/
|
|
27
27
|
import React from 'react';
|
|
28
|
+
import { mergeRefs } from '../hooks';
|
|
28
29
|
import { useResizeObserver } from '../hooks/useResizeObserver';
|
|
29
30
|
var getScrollableParent = function (element, ownerDocument) {
|
|
30
31
|
if (ownerDocument === void 0) { ownerDocument = document; }
|
|
@@ -46,6 +47,14 @@ var getElementHeight = function (element) {
|
|
|
46
47
|
var _a;
|
|
47
48
|
return (_a = element === null || element === void 0 ? void 0 : element.getBoundingClientRect().height) !== null && _a !== void 0 ? _a : 0;
|
|
48
49
|
};
|
|
50
|
+
var getElementHeightWithMargins = function (element) {
|
|
51
|
+
if (!element) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
var margin = parseFloat(getElementStyle(element, 'margin-top')) +
|
|
55
|
+
parseFloat(getElementStyle(element, 'margin-bottom'));
|
|
56
|
+
return getElementHeight(element) + (isNaN(margin) ? 0 : margin);
|
|
57
|
+
};
|
|
49
58
|
var getNumberOfNodesInHeight = function (childHeight, totalHeight) {
|
|
50
59
|
if (!childHeight) {
|
|
51
60
|
return 0;
|
|
@@ -53,7 +62,10 @@ var getNumberOfNodesInHeight = function (childHeight, totalHeight) {
|
|
|
53
62
|
return Math.floor(totalHeight / childHeight);
|
|
54
63
|
};
|
|
55
64
|
var getTranslateValue = function (childHeight, startIndex) {
|
|
56
|
-
|
|
65
|
+
if (startIndex > 0) {
|
|
66
|
+
return childHeight * startIndex;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
57
69
|
};
|
|
58
70
|
var getVisibleNodeCount = function (childHeight, startIndex, childrenLength, scrollContainer) {
|
|
59
71
|
return Math.min(childrenLength - startIndex, getNumberOfNodesInHeight(childHeight, getElementHeight(scrollContainer)));
|
|
@@ -79,21 +91,55 @@ var getVisibleNodeCount = function (childHeight, startIndex, childrenLength, scr
|
|
|
79
91
|
* />
|
|
80
92
|
* @private
|
|
81
93
|
*/
|
|
82
|
-
export var VirtualScroll = React.forwardRef(function (
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
export var VirtualScroll = React.forwardRef(function (props, ref) {
|
|
95
|
+
var _a = useVirtualization(props), innerProps = _a.innerProps, outerProps = _a.outerProps, visibleChildren = _a.visibleChildren;
|
|
96
|
+
return (React.createElement("div", __assign({}, outerProps, { ref: ref }),
|
|
97
|
+
React.createElement("div", __assign({}, innerProps), visibleChildren)));
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* `useVirtualization` is used for efficiently rendering only the visible rows from a large list.
|
|
101
|
+
* It returns `outerProps` and `innerProps`, which need to be applied on 2 container elements and `visibleChildren` which is a list of virtualized items.
|
|
102
|
+
* @example
|
|
103
|
+
* const itemRenderer = React.useCallback((index: number) => (
|
|
104
|
+
* <li key={index}>
|
|
105
|
+
* This is my item #{index}
|
|
106
|
+
* </li>
|
|
107
|
+
* ), [])
|
|
108
|
+
*
|
|
109
|
+
* const { outerProps, innerProps, visibleChildren } = useVirtualization({itemsLength: 1000, itemRenderer: itemRenderer});
|
|
110
|
+
* return (
|
|
111
|
+
* <div {...outerProps}>
|
|
112
|
+
* <ul {...innerProps}>
|
|
113
|
+
* {visibleChildren}
|
|
114
|
+
* </ul>
|
|
115
|
+
* </div>
|
|
116
|
+
* );
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
export var useVirtualization = function (props) {
|
|
120
|
+
var itemsLength = props.itemsLength, itemRenderer = props.itemRenderer, _a = props.bufferSize, bufferSize = _a === void 0 ? 10 : _a, scrollToIndex = props.scrollToIndex, style = props.style, rest = __rest(props, ["itemsLength", "itemRenderer", "bufferSize", "scrollToIndex", "style"]);
|
|
121
|
+
var _b = React.useState(0), startNode = _b[0], setStartNode = _b[1];
|
|
122
|
+
var _c = React.useState(0), visibleNodeCount = _c[0], setVisibleNodeCount = _c[1];
|
|
86
123
|
var scrollContainer = React.useRef();
|
|
87
124
|
var parentRef = React.useRef(null);
|
|
88
|
-
var childHeight = React.useRef(0);
|
|
125
|
+
var childHeight = React.useRef({ first: 0, middle: 0, last: 0 });
|
|
89
126
|
var onScrollRef = React.useRef();
|
|
90
127
|
// Used only to recalculate on resize
|
|
91
|
-
var
|
|
128
|
+
var _d = React.useState(0), scrollContainerHeight = _d[0], setScrollContainerHeight = _d[1];
|
|
129
|
+
var visibleIndex = React.useRef({ start: 0, end: 0 });
|
|
130
|
+
// Used to mark when scroll container has height (updated by resize observer)
|
|
131
|
+
// because before that calculations are not right
|
|
132
|
+
var _e = React.useState(false), isMounted = _e[0], setIsMounted = _e[1];
|
|
92
133
|
var onResize = React.useCallback(function (_a) {
|
|
93
134
|
var height = _a.height;
|
|
135
|
+
// Initial value returned by resize observer is 0
|
|
136
|
+
// So wait for the next one
|
|
137
|
+
if (height > 0) {
|
|
138
|
+
setIsMounted(true);
|
|
139
|
+
}
|
|
94
140
|
setScrollContainerHeight(height);
|
|
95
141
|
}, []);
|
|
96
|
-
var
|
|
142
|
+
var _f = useResizeObserver(onResize), resizeRef = _f[0], resizeObserver = _f[1];
|
|
97
143
|
// Find scrollable parent
|
|
98
144
|
// Needed only on init
|
|
99
145
|
React.useLayoutEffect(function () {
|
|
@@ -102,6 +148,14 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
102
148
|
scrollContainer.current = scrollableParent;
|
|
103
149
|
resizeRef(scrollableParent);
|
|
104
150
|
}, [resizeRef]);
|
|
151
|
+
// Stop watching resize, when virtual scroll is unmounted
|
|
152
|
+
React.useLayoutEffect(function () {
|
|
153
|
+
return function () { return resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect(); };
|
|
154
|
+
}, [resizeObserver]);
|
|
155
|
+
var getScrollableContainer = function () {
|
|
156
|
+
var _a, _b;
|
|
157
|
+
return (_a = scrollContainer.current) !== null && _a !== void 0 ? _a : (_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.scrollingElement;
|
|
158
|
+
};
|
|
105
159
|
var visibleChildren = React.useMemo(function () {
|
|
106
160
|
var arr = [];
|
|
107
161
|
var endIndex = Math.min(itemsLength, startNode + visibleNodeCount + bufferSize * 2);
|
|
@@ -112,27 +166,42 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
112
166
|
}, [itemsLength, itemRenderer, bufferSize, startNode, visibleNodeCount]);
|
|
113
167
|
// Get child height when children available
|
|
114
168
|
React.useLayoutEffect(function () {
|
|
169
|
+
var _a, _b, _c, _d, _e, _f;
|
|
115
170
|
if (!parentRef.current || !visibleChildren.length) {
|
|
116
171
|
return;
|
|
117
172
|
}
|
|
118
173
|
var firstChild = parentRef.current.children.item(0);
|
|
119
|
-
|
|
174
|
+
var secondChild = parentRef.current.children.item(1);
|
|
175
|
+
var lastChild = parentRef.current.children.item(parentRef.current.children.length - 1);
|
|
176
|
+
var firstChildHeight = Number((_b = (_a = getElementHeightWithMargins(firstChild)) === null || _a === void 0 ? void 0 : _a.toFixed(2)) !== null && _b !== void 0 ? _b : 0);
|
|
177
|
+
childHeight.current = {
|
|
178
|
+
first: firstChildHeight,
|
|
179
|
+
middle: Number((_d = (_c = getElementHeightWithMargins(secondChild)) === null || _c === void 0 ? void 0 : _c.toFixed(2)) !== null && _d !== void 0 ? _d : firstChildHeight),
|
|
180
|
+
last: Number((_f = (_e = getElementHeightWithMargins(lastChild)) === null || _e === void 0 ? void 0 : _e.toFixed(2)) !== null && _f !== void 0 ? _f : firstChildHeight),
|
|
181
|
+
};
|
|
120
182
|
}, [visibleChildren.length]);
|
|
121
183
|
var updateVirtualScroll = React.useCallback(function () {
|
|
122
|
-
var
|
|
123
|
-
var scrollableContainer = (_a = scrollContainer.current) !== null && _a !== void 0 ? _a : (_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.scrollingElement;
|
|
184
|
+
var scrollableContainer = getScrollableContainer();
|
|
124
185
|
if (!scrollableContainer) {
|
|
125
186
|
return;
|
|
126
187
|
}
|
|
127
|
-
var start = getNumberOfNodesInHeight(childHeight.current, scrollableContainer.scrollTop);
|
|
128
|
-
var
|
|
188
|
+
var start = getNumberOfNodesInHeight(childHeight.current.middle, Math.round(scrollableContainer.scrollTop));
|
|
189
|
+
var visibleNodes = getVisibleNodeCount(childHeight.current.middle, start, itemsLength, scrollableContainer);
|
|
190
|
+
// If there are less items at the end than buffer size
|
|
191
|
+
// show more items at the start.
|
|
192
|
+
// Have boundaries for edge cases, e.g. 1 item length
|
|
193
|
+
var startIndex = Math.min(Math.max(0, start - bufferSize), Math.max(0, itemsLength - bufferSize * 2 - visibleNodes));
|
|
194
|
+
visibleIndex.current = { start: start, end: start + visibleNodes };
|
|
129
195
|
setStartNode(startIndex);
|
|
130
|
-
setVisibleNodeCount(
|
|
196
|
+
setVisibleNodeCount(visibleNodes);
|
|
131
197
|
if (!parentRef.current) {
|
|
132
198
|
return;
|
|
133
199
|
}
|
|
134
|
-
parentRef.current.style.transform = "translateY(".concat(getTranslateValue(childHeight.current, startIndex), "px)");
|
|
200
|
+
parentRef.current.style.transform = "translateY(".concat(getTranslateValue(childHeight.current.middle, startIndex), "px)");
|
|
135
201
|
}, [bufferSize, itemsLength]);
|
|
202
|
+
var onScroll = React.useCallback(function () {
|
|
203
|
+
updateVirtualScroll();
|
|
204
|
+
}, [updateVirtualScroll]);
|
|
136
205
|
var removeScrollListener = React.useCallback(function () {
|
|
137
206
|
var _a, _b;
|
|
138
207
|
if (!onScrollRef.current) {
|
|
@@ -147,22 +216,85 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
147
216
|
React.useLayoutEffect(function () {
|
|
148
217
|
var _a, _b;
|
|
149
218
|
removeScrollListener();
|
|
150
|
-
onScrollRef.current =
|
|
219
|
+
onScrollRef.current = onScroll;
|
|
151
220
|
if (!scrollContainer.current ||
|
|
152
221
|
scrollContainer.current === ((_a = parentRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.body)) {
|
|
153
|
-
(_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.addEventListener('scroll',
|
|
222
|
+
(_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.addEventListener('scroll', onScroll);
|
|
154
223
|
}
|
|
155
224
|
else {
|
|
156
|
-
scrollContainer.current.addEventListener('scroll',
|
|
225
|
+
scrollContainer.current.addEventListener('scroll', onScroll);
|
|
157
226
|
}
|
|
158
227
|
return removeScrollListener;
|
|
159
|
-
}, [
|
|
228
|
+
}, [onScroll, removeScrollListener]);
|
|
229
|
+
React.useLayoutEffect(function () {
|
|
230
|
+
if (!isMounted) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
var scrollableContainer = getScrollableContainer();
|
|
234
|
+
if (!scrollableContainer || scrollToIndex == null) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// if `scrollToIndex` is not visible, scroll to it
|
|
238
|
+
if (scrollToIndex > visibleIndex.current.end ||
|
|
239
|
+
scrollToIndex < visibleIndex.current.start) {
|
|
240
|
+
var indexDiff = scrollToIndex > visibleIndex.current.end
|
|
241
|
+
? scrollToIndex - visibleIndex.current.end
|
|
242
|
+
: scrollToIndex - visibleIndex.current.start;
|
|
243
|
+
if (scrollToIndex === 0) {
|
|
244
|
+
scrollableContainer.scrollTo({ top: 0 });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// If go down: add to the existing scrollTop needed height
|
|
248
|
+
// If go up: calculate the exact scroll top
|
|
249
|
+
scrollableContainer.scrollTo({
|
|
250
|
+
top: indexDiff > 0
|
|
251
|
+
? Math.ceil(scrollableContainer.scrollTop) +
|
|
252
|
+
indexDiff * childHeight.current.middle
|
|
253
|
+
: scrollToIndex * childHeight.current.middle,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// if `scrollToIndex` is the first visible node
|
|
257
|
+
// ensure it is fully visible
|
|
258
|
+
if (scrollToIndex === visibleIndex.current.start) {
|
|
259
|
+
var roundedScrollTop = Math.round(scrollableContainer.scrollTop);
|
|
260
|
+
var diff = roundedScrollTop % childHeight.current.middle;
|
|
261
|
+
diff > 0 &&
|
|
262
|
+
scrollableContainer.scrollTo({
|
|
263
|
+
top: roundedScrollTop - diff,
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// if `scrollToIndex` is the last visible node
|
|
268
|
+
// ensure it is fully visible
|
|
269
|
+
if (scrollToIndex === visibleIndex.current.end) {
|
|
270
|
+
var diff = (scrollableContainer.offsetHeight - childHeight.current.first) %
|
|
271
|
+
childHeight.current.middle;
|
|
272
|
+
var roundedScrollTop = Math.ceil(scrollableContainer.scrollTop);
|
|
273
|
+
var scrollTopMod = roundedScrollTop % childHeight.current.middle;
|
|
274
|
+
if (diff > 0 && scrollTopMod === 0) {
|
|
275
|
+
scrollableContainer.scrollTo({
|
|
276
|
+
top: roundedScrollTop + childHeight.current.middle - diff,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}, [scrollToIndex, isMounted]);
|
|
160
281
|
React.useLayoutEffect(function () {
|
|
282
|
+
if (!scrollContainerHeight) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
161
285
|
updateVirtualScroll();
|
|
162
|
-
}, [scrollContainerHeight,
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
286
|
+
}, [scrollContainerHeight, updateVirtualScroll]);
|
|
287
|
+
return {
|
|
288
|
+
outerProps: __assign({ style: __assign({ overflow: 'hidden', minHeight: itemsLength > 1
|
|
289
|
+
? Math.max(itemsLength - 2, 0) * childHeight.current.middle +
|
|
290
|
+
childHeight.current.first +
|
|
291
|
+
childHeight.current.last
|
|
292
|
+
: childHeight.current.middle, width: '100%' }, style) }, rest),
|
|
293
|
+
innerProps: {
|
|
294
|
+
style: { willChange: 'transform' },
|
|
295
|
+
ref: mergeRefs(parentRef), // convert object ref to callback ref for better types
|
|
296
|
+
},
|
|
297
|
+
visibleChildren: visibleChildren,
|
|
298
|
+
};
|
|
299
|
+
};
|
|
168
300
|
export default VirtualScroll;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Wrapper hook around `useContext` that throws an error if the context is not provided.
|
|
4
|
+
* @param context Context to use. Must have a `displayName` for useful errors.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useSafeContext: <T>(context: React.Context<T>) => NonNullable<T>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
3
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* Wrapper hook around `useContext` that throws an error if the context is not provided.
|
|
8
|
+
* @param context Context to use. Must have a `displayName` for useful errors.
|
|
9
|
+
*/
|
|
10
|
+
export var useSafeContext = function (context) {
|
|
11
|
+
var value = React.useContext(context);
|
|
12
|
+
if (!value) {
|
|
13
|
+
throw new Error("".concat(context.displayName, " is undefined"));
|
|
14
|
+
}
|
|
15
|
+
return value; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- we already checked for undefined
|
|
16
|
+
};
|