@telus-uds/components-base 3.14.1 → 3.15.0
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 +9 -4
- package/lib/cjs/ExpandCollapse/Panel.js +16 -3
- package/lib/cjs/FlexGrid/FlexGrid.js +5 -2
- package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +28 -2
- package/lib/cjs/Select/Select.js +10 -1
- package/lib/cjs/utils/useOverlaidPosition.js +9 -7
- package/lib/esm/ExpandCollapse/Panel.js +16 -3
- package/lib/esm/FlexGrid/FlexGrid.js +5 -2
- package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +28 -2
- package/lib/esm/Select/Select.js +10 -1
- package/lib/esm/utils/useOverlaidPosition.js +9 -7
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/src/ExpandCollapse/Panel.jsx +14 -3
- package/src/FlexGrid/FlexGrid.jsx +5 -2
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +31 -2
- package/src/Select/Select.jsx +11 -1
- package/src/utils/useOverlaidPosition.js +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 10 Sep 2025 05:55:42 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
-
## 3.
|
|
7
|
+
## 3.15.0
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Wed, 10 Sep 2025 05:55:42 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `MultiSelectFilter`: Positioned fixed instead of absolute on web now (oscar.palencia@telus.com)
|
|
10
14
|
|
|
11
15
|
### Patches
|
|
12
16
|
|
|
13
|
-
- `
|
|
17
|
+
- `Select`: incorrect position rendering dropdown in mobile devices fixed (35577399+JoshHC@users.noreply.github.com)
|
|
18
|
+
- `Expand-Collapse`: Fixing content rendering problems when nesting panels (oscar.palencia@telus.com)
|
|
14
19
|
|
|
15
20
|
## 3.13.0
|
|
16
21
|
|
|
@@ -128,6 +128,10 @@ const ExpandCollapsePanel = /*#__PURE__*/_react.default.forwardRef((_ref5, ref)
|
|
|
128
128
|
const themeTokens = (0, _ThemeProvider.useThemeTokens)('ExpandCollapsePanel', tokens, variant, {
|
|
129
129
|
expanded: isExpanded
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
// on mobile devices we require a scroll buffer equal to the font size
|
|
133
|
+
// to avoid triggering scrolling unnecessarily
|
|
134
|
+
const mobileScrollBuffer = _Platform.default.OS === 'web' ? 0 : selectTextStyles(themeTokens)?.fontSize;
|
|
131
135
|
const handleControlPress = event => {
|
|
132
136
|
onToggle?.(panelId, event);
|
|
133
137
|
if (onPress) onPress(panelId, event);
|
|
@@ -139,7 +143,7 @@ const ExpandCollapsePanel = /*#__PURE__*/_react.default.forwardRef((_ref5, ref)
|
|
|
139
143
|
} = {}
|
|
140
144
|
} = event.nativeEvent;
|
|
141
145
|
if (_Platform.default.OS === 'web' || _Platform.default.OS !== 'web' && containerHeight === null) {
|
|
142
|
-
setContainerHeight(height);
|
|
146
|
+
setContainerHeight(height + mobileScrollBuffer);
|
|
143
147
|
}
|
|
144
148
|
};
|
|
145
149
|
const [animatedStyles, animatedRef] = (0, _utils.useVerticalExpandAnimation)({
|
|
@@ -203,7 +207,7 @@ const ExpandCollapsePanel = /*#__PURE__*/_react.default.forwardRef((_ref5, ref)
|
|
|
203
207
|
children: children
|
|
204
208
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_ScrollView.default, {
|
|
205
209
|
onContentSizeChange: (_, height) => {
|
|
206
|
-
setContainerHeight(height);
|
|
210
|
+
setContainerHeight(height + mobileScrollBuffer);
|
|
207
211
|
},
|
|
208
212
|
style: selectContainerStyles(themeTokens),
|
|
209
213
|
accessibilityLabel: subPanelAccessibilityLabel,
|
|
@@ -216,7 +220,16 @@ const ExpandCollapsePanel = /*#__PURE__*/_react.default.forwardRef((_ref5, ref)
|
|
|
216
220
|
ExpandCollapsePanel.displayName = 'ExpandCollapsePanel';
|
|
217
221
|
const staticStyles = _StyleSheet.default.create({
|
|
218
222
|
container: {
|
|
219
|
-
|
|
223
|
+
..._Platform.default.select({
|
|
224
|
+
web: {
|
|
225
|
+
flexGrow: 1,
|
|
226
|
+
flexshrink: 1,
|
|
227
|
+
flexbasis: 'auto'
|
|
228
|
+
},
|
|
229
|
+
default: {
|
|
230
|
+
flex: 1
|
|
231
|
+
}
|
|
232
|
+
}),
|
|
220
233
|
justifyContent: 'flex-start'
|
|
221
234
|
},
|
|
222
235
|
panelContainer: {
|
|
@@ -52,12 +52,15 @@ const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
|
52
52
|
*/
|
|
53
53
|
const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWidth) => {
|
|
54
54
|
if (limitWidth) {
|
|
55
|
-
|
|
55
|
+
if (viewportKey === 'xs') {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return _systemConstants.viewports.map.get(viewportKey);
|
|
56
59
|
}
|
|
57
60
|
if (contentMinWidth) {
|
|
58
61
|
return maxWidth;
|
|
59
62
|
}
|
|
60
|
-
return
|
|
63
|
+
return null;
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
/**
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _react = _interopRequireDefault(require("react"));
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
|
+
var _portal = require("@gorhom/portal");
|
|
9
10
|
var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
|
|
10
11
|
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
11
12
|
var _Dimensions = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Dimensions"));
|
|
@@ -214,6 +215,20 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
214
215
|
setIsOpen(false);
|
|
215
216
|
onCancel();
|
|
216
217
|
};
|
|
218
|
+
const appRootRef = _react.default.useRef(null);
|
|
219
|
+
const [rootOffsets, setRootOffsets] = _react.default.useState(null);
|
|
220
|
+
_react.default.useEffect(() => {
|
|
221
|
+
if (rootOffsets) return;
|
|
222
|
+
appRootRef.current?.measureInWindow((x, y) => {
|
|
223
|
+
// Only set offsets if they are positive
|
|
224
|
+
// this is because we want to avoid negative offsets that could cause
|
|
225
|
+
// the dropdown to be positioned incorrectly in some situations
|
|
226
|
+
if (y > 0) setRootOffsets({
|
|
227
|
+
horizontal: x,
|
|
228
|
+
vertical: y
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}, [isOpen, rootOffsets]);
|
|
217
232
|
const {
|
|
218
233
|
align,
|
|
219
234
|
offsets
|
|
@@ -230,7 +245,8 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
230
245
|
left: 'left'
|
|
231
246
|
},
|
|
232
247
|
offsets: {
|
|
233
|
-
vertical: 4
|
|
248
|
+
vertical: 4 - (rootOffsets?.vertical || 0),
|
|
249
|
+
horizontal: -rootOffsets?.horizontal || 0
|
|
234
250
|
}
|
|
235
251
|
}
|
|
236
252
|
});
|
|
@@ -367,7 +383,12 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
367
383
|
})]
|
|
368
384
|
});
|
|
369
385
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
370
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
386
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_portal.Portal, {
|
|
387
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
388
|
+
ref: appRootRef,
|
|
389
|
+
style: styles.appRootRef
|
|
390
|
+
})
|
|
391
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.ButtonDropdown, {
|
|
371
392
|
ref: sourceRef,
|
|
372
393
|
...pressHandlers,
|
|
373
394
|
value: isOpen,
|
|
@@ -448,6 +469,11 @@ const styles = _StyleSheet.default.create({
|
|
|
448
469
|
},
|
|
449
470
|
scrollContainer: {
|
|
450
471
|
padding: 1
|
|
472
|
+
},
|
|
473
|
+
appRootRef: {
|
|
474
|
+
position: 'absolute',
|
|
475
|
+
top: 0,
|
|
476
|
+
left: 0
|
|
451
477
|
}
|
|
452
478
|
});
|
|
453
479
|
|
package/lib/cjs/Select/Select.js
CHANGED
|
@@ -9,6 +9,7 @@ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Vi
|
|
|
9
9
|
var _Platform = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Platform"));
|
|
10
10
|
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
11
11
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
12
|
+
var _systemConstants = require("@telus-uds/system-constants");
|
|
12
13
|
var _ThemeProvider = require("../ThemeProvider");
|
|
13
14
|
var _utils = require("../utils");
|
|
14
15
|
var _Picker = _interopRequireDefault(require("./Picker"));
|
|
@@ -55,7 +56,15 @@ const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
|
55
56
|
// since iOS Safari needs a prefix
|
|
56
57
|
outline: 'none',
|
|
57
58
|
cursor: inactive ? 'not-allowed' : undefined,
|
|
58
|
-
opacity: inactive ? 1 : undefined
|
|
59
|
+
opacity: inactive ? 1 : undefined,
|
|
60
|
+
// override Chrome's default fadeout of a disabled select
|
|
61
|
+
// Enhanced fix for mobile dropdown positioning: restore native appearance on touch devices
|
|
62
|
+
// Using multiple media queries and higher specificity to ensure it works in all contexts
|
|
63
|
+
[`@media (pointer: coarse), (maxWidth: ${_systemConstants.viewports.map.get(_systemConstants.viewports.md)}px) and (hover: none)`]: {
|
|
64
|
+
appearance: 'auto !important',
|
|
65
|
+
WebkitAppearance: 'auto !important',
|
|
66
|
+
backgroundImage: 'none !important'
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
});
|
|
61
70
|
let paddingWithIcons = paddingRight;
|
|
@@ -9,7 +9,7 @@ var _Dimensions = _interopRequireDefault(require("react-native-web/dist/cjs/expo
|
|
|
9
9
|
var _Platform = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Platform"));
|
|
10
10
|
var _lodash = _interopRequireDefault(require("lodash.debounce"));
|
|
11
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
-
const DEBOUNCE_DELAY =
|
|
12
|
+
const DEBOUNCE_DELAY = 300;
|
|
13
13
|
const adjustHorizontalToFit = (initialOffset, windowWidth, sourceWidth) => {
|
|
14
14
|
const offset = Math.max(0, initialOffset);
|
|
15
15
|
const otherEdgeOverflow = Math.max(0, offset + sourceWidth - windowWidth);
|
|
@@ -71,34 +71,36 @@ function getOverlaidPosition(_ref2) {
|
|
|
71
71
|
|
|
72
72
|
// Will have top, bottom, left and/or right offsets depending on `align`
|
|
73
73
|
const positioning = {};
|
|
74
|
+
const verticalOffset = offsets.vertical ?? 0;
|
|
75
|
+
const horizontalOffset = offsets.horizontal ?? 0;
|
|
74
76
|
if (align.top) positioning.top = getPosition({
|
|
75
77
|
edge: getEdgeType(align, 'top'),
|
|
76
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
78
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset,
|
|
77
79
|
sourceSize: sourceLayout.height
|
|
78
80
|
});
|
|
79
81
|
if (align.middle) positioning.top = getPosition({
|
|
80
82
|
edge: getEdgeType(align, 'middle'),
|
|
81
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
83
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset - targetDimensions.height / 2,
|
|
82
84
|
sourceSize: sourceLayout.height
|
|
83
85
|
});
|
|
84
86
|
if (align.bottom) positioning.bottom = getPosition({
|
|
85
87
|
edge: getEdgeType(align, 'bottom'),
|
|
86
|
-
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height -
|
|
88
|
+
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height - verticalOffset),
|
|
87
89
|
sourceSize: sourceLayout.height
|
|
88
90
|
});
|
|
89
91
|
if (align.left) positioning.left = getPosition({
|
|
90
92
|
edge: getEdgeType(align, 'left'),
|
|
91
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
93
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset,
|
|
92
94
|
sourceSize: sourceLayout.width
|
|
93
95
|
});
|
|
94
96
|
if (align.center) positioning.left = getPosition({
|
|
95
97
|
edge: getEdgeType(align, 'center'),
|
|
96
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
98
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset - targetDimensions.width / 2,
|
|
97
99
|
sourceSize: sourceLayout.width
|
|
98
100
|
});
|
|
99
101
|
if (align.right) positioning.right = getPosition({
|
|
100
102
|
edge: getEdgeType(align, 'right'),
|
|
101
|
-
fromEdge: windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width -
|
|
103
|
+
fromEdge: windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width - horizontalOffset),
|
|
102
104
|
sourceSize: sourceLayout.width
|
|
103
105
|
});
|
|
104
106
|
if (!(align.left && align.right)) {
|
|
@@ -121,6 +121,10 @@ const ExpandCollapsePanel = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
|
|
|
121
121
|
const themeTokens = useThemeTokens('ExpandCollapsePanel', tokens, variant, {
|
|
122
122
|
expanded: isExpanded
|
|
123
123
|
});
|
|
124
|
+
|
|
125
|
+
// on mobile devices we require a scroll buffer equal to the font size
|
|
126
|
+
// to avoid triggering scrolling unnecessarily
|
|
127
|
+
const mobileScrollBuffer = Platform.OS === 'web' ? 0 : selectTextStyles(themeTokens)?.fontSize;
|
|
124
128
|
const handleControlPress = event => {
|
|
125
129
|
onToggle?.(panelId, event);
|
|
126
130
|
if (onPress) onPress(panelId, event);
|
|
@@ -132,7 +136,7 @@ const ExpandCollapsePanel = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
|
|
|
132
136
|
} = {}
|
|
133
137
|
} = event.nativeEvent;
|
|
134
138
|
if (Platform.OS === 'web' || Platform.OS !== 'web' && containerHeight === null) {
|
|
135
|
-
setContainerHeight(height);
|
|
139
|
+
setContainerHeight(height + mobileScrollBuffer);
|
|
136
140
|
}
|
|
137
141
|
};
|
|
138
142
|
const [animatedStyles, animatedRef] = useVerticalExpandAnimation({
|
|
@@ -196,7 +200,7 @@ const ExpandCollapsePanel = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
|
|
|
196
200
|
children: children
|
|
197
201
|
}) : /*#__PURE__*/_jsx(ScrollView, {
|
|
198
202
|
onContentSizeChange: (_, height) => {
|
|
199
|
-
setContainerHeight(height);
|
|
203
|
+
setContainerHeight(height + mobileScrollBuffer);
|
|
200
204
|
},
|
|
201
205
|
style: selectContainerStyles(themeTokens),
|
|
202
206
|
accessibilityLabel: subPanelAccessibilityLabel,
|
|
@@ -209,7 +213,16 @@ const ExpandCollapsePanel = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
|
|
|
209
213
|
ExpandCollapsePanel.displayName = 'ExpandCollapsePanel';
|
|
210
214
|
const staticStyles = StyleSheet.create({
|
|
211
215
|
container: {
|
|
212
|
-
|
|
216
|
+
...Platform.select({
|
|
217
|
+
web: {
|
|
218
|
+
flexGrow: 1,
|
|
219
|
+
flexshrink: 1,
|
|
220
|
+
flexbasis: 'auto'
|
|
221
|
+
},
|
|
222
|
+
default: {
|
|
223
|
+
flex: 1
|
|
224
|
+
}
|
|
225
|
+
}),
|
|
213
226
|
justifyContent: 'flex-start'
|
|
214
227
|
},
|
|
215
228
|
panelContainer: {
|
|
@@ -45,12 +45,15 @@ const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
|
45
45
|
*/
|
|
46
46
|
const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWidth) => {
|
|
47
47
|
if (limitWidth) {
|
|
48
|
-
|
|
48
|
+
if (viewportKey === 'xs') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return viewports.map.get(viewportKey);
|
|
49
52
|
}
|
|
50
53
|
if (contentMinWidth) {
|
|
51
54
|
return maxWidth;
|
|
52
55
|
}
|
|
53
|
-
return
|
|
56
|
+
return null;
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { Portal } from '@gorhom/portal';
|
|
3
4
|
import View from "react-native-web/dist/exports/View";
|
|
4
5
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
6
|
import Dimensions from "react-native-web/dist/exports/Dimensions";
|
|
@@ -207,6 +208,20 @@ const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
207
208
|
setIsOpen(false);
|
|
208
209
|
onCancel();
|
|
209
210
|
};
|
|
211
|
+
const appRootRef = React.useRef(null);
|
|
212
|
+
const [rootOffsets, setRootOffsets] = React.useState(null);
|
|
213
|
+
React.useEffect(() => {
|
|
214
|
+
if (rootOffsets) return;
|
|
215
|
+
appRootRef.current?.measureInWindow((x, y) => {
|
|
216
|
+
// Only set offsets if they are positive
|
|
217
|
+
// this is because we want to avoid negative offsets that could cause
|
|
218
|
+
// the dropdown to be positioned incorrectly in some situations
|
|
219
|
+
if (y > 0) setRootOffsets({
|
|
220
|
+
horizontal: x,
|
|
221
|
+
vertical: y
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}, [isOpen, rootOffsets]);
|
|
210
225
|
const {
|
|
211
226
|
align,
|
|
212
227
|
offsets
|
|
@@ -223,7 +238,8 @@ const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
223
238
|
left: 'left'
|
|
224
239
|
},
|
|
225
240
|
offsets: {
|
|
226
|
-
vertical: 4
|
|
241
|
+
vertical: 4 - (rootOffsets?.vertical || 0),
|
|
242
|
+
horizontal: -rootOffsets?.horizontal || 0
|
|
227
243
|
}
|
|
228
244
|
}
|
|
229
245
|
});
|
|
@@ -360,7 +376,12 @@ const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
360
376
|
})]
|
|
361
377
|
});
|
|
362
378
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
363
|
-
children: [/*#__PURE__*/_jsx(
|
|
379
|
+
children: [/*#__PURE__*/_jsx(Portal, {
|
|
380
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
381
|
+
ref: appRootRef,
|
|
382
|
+
style: styles.appRootRef
|
|
383
|
+
})
|
|
384
|
+
}), /*#__PURE__*/_jsx(ButtonDropdown, {
|
|
364
385
|
ref: sourceRef,
|
|
365
386
|
...pressHandlers,
|
|
366
387
|
value: isOpen,
|
|
@@ -441,6 +462,11 @@ const styles = StyleSheet.create({
|
|
|
441
462
|
},
|
|
442
463
|
scrollContainer: {
|
|
443
464
|
padding: 1
|
|
465
|
+
},
|
|
466
|
+
appRootRef: {
|
|
467
|
+
position: 'absolute',
|
|
468
|
+
top: 0,
|
|
469
|
+
left: 0
|
|
444
470
|
}
|
|
445
471
|
});
|
|
446
472
|
|
package/lib/esm/Select/Select.js
CHANGED
|
@@ -3,6 +3,7 @@ import View from "react-native-web/dist/exports/View";
|
|
|
3
3
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
4
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
5
|
import PropTypes from 'prop-types';
|
|
6
|
+
import { viewports } from '@telus-uds/system-constants';
|
|
6
7
|
import { applyTextStyles, useThemeTokens, applyOuterBorder, useTheme } from '../ThemeProvider';
|
|
7
8
|
import { a11yProps, componentPropType, getTokensPropType, inputSupportsProps, selectSystemProps, useInputValue, variantProp, viewProps, htmlAttrs } from '../utils';
|
|
8
9
|
import Picker from './Picker';
|
|
@@ -48,7 +49,15 @@ const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
|
48
49
|
// since iOS Safari needs a prefix
|
|
49
50
|
outline: 'none',
|
|
50
51
|
cursor: inactive ? 'not-allowed' : undefined,
|
|
51
|
-
opacity: inactive ? 1 : undefined
|
|
52
|
+
opacity: inactive ? 1 : undefined,
|
|
53
|
+
// override Chrome's default fadeout of a disabled select
|
|
54
|
+
// Enhanced fix for mobile dropdown positioning: restore native appearance on touch devices
|
|
55
|
+
// Using multiple media queries and higher specificity to ensure it works in all contexts
|
|
56
|
+
[`@media (pointer: coarse), (maxWidth: ${viewports.map.get(viewports.md)}px) and (hover: none)`]: {
|
|
57
|
+
appearance: 'auto !important',
|
|
58
|
+
WebkitAppearance: 'auto !important',
|
|
59
|
+
backgroundImage: 'none !important'
|
|
60
|
+
}
|
|
52
61
|
}
|
|
53
62
|
});
|
|
54
63
|
let paddingWithIcons = paddingRight;
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import Dimensions from "react-native-web/dist/exports/Dimensions";
|
|
3
3
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
4
4
|
import debounce from 'lodash.debounce';
|
|
5
|
-
const DEBOUNCE_DELAY =
|
|
5
|
+
const DEBOUNCE_DELAY = 300;
|
|
6
6
|
const adjustHorizontalToFit = (initialOffset, windowWidth, sourceWidth) => {
|
|
7
7
|
const offset = Math.max(0, initialOffset);
|
|
8
8
|
const otherEdgeOverflow = Math.max(0, offset + sourceWidth - windowWidth);
|
|
@@ -64,34 +64,36 @@ function getOverlaidPosition(_ref2) {
|
|
|
64
64
|
|
|
65
65
|
// Will have top, bottom, left and/or right offsets depending on `align`
|
|
66
66
|
const positioning = {};
|
|
67
|
+
const verticalOffset = offsets.vertical ?? 0;
|
|
68
|
+
const horizontalOffset = offsets.horizontal ?? 0;
|
|
67
69
|
if (align.top) positioning.top = getPosition({
|
|
68
70
|
edge: getEdgeType(align, 'top'),
|
|
69
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
71
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset,
|
|
70
72
|
sourceSize: sourceLayout.height
|
|
71
73
|
});
|
|
72
74
|
if (align.middle) positioning.top = getPosition({
|
|
73
75
|
edge: getEdgeType(align, 'middle'),
|
|
74
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
76
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset - targetDimensions.height / 2,
|
|
75
77
|
sourceSize: sourceLayout.height
|
|
76
78
|
});
|
|
77
79
|
if (align.bottom) positioning.bottom = getPosition({
|
|
78
80
|
edge: getEdgeType(align, 'bottom'),
|
|
79
|
-
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height -
|
|
81
|
+
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height - verticalOffset),
|
|
80
82
|
sourceSize: sourceLayout.height
|
|
81
83
|
});
|
|
82
84
|
if (align.left) positioning.left = getPosition({
|
|
83
85
|
edge: getEdgeType(align, 'left'),
|
|
84
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
86
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset,
|
|
85
87
|
sourceSize: sourceLayout.width
|
|
86
88
|
});
|
|
87
89
|
if (align.center) positioning.left = getPosition({
|
|
88
90
|
edge: getEdgeType(align, 'center'),
|
|
89
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
91
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset - targetDimensions.width / 2,
|
|
90
92
|
sourceSize: sourceLayout.width
|
|
91
93
|
});
|
|
92
94
|
if (align.right) positioning.right = getPosition({
|
|
93
95
|
edge: getEdgeType(align, 'right'),
|
|
94
|
-
fromEdge: windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width -
|
|
96
|
+
fromEdge: windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width - horizontalOffset),
|
|
95
97
|
sourceSize: sourceLayout.width
|
|
96
98
|
});
|
|
97
99
|
if (!(align.left && align.right)) {
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
|
@@ -116,6 +116,10 @@ const ExpandCollapsePanel = React.forwardRef(
|
|
|
116
116
|
expanded: isExpanded
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
+
// on mobile devices we require a scroll buffer equal to the font size
|
|
120
|
+
// to avoid triggering scrolling unnecessarily
|
|
121
|
+
const mobileScrollBuffer = Platform.OS === 'web' ? 0 : selectTextStyles(themeTokens)?.fontSize
|
|
122
|
+
|
|
119
123
|
const handleControlPress = (event) => {
|
|
120
124
|
onToggle?.(panelId, event)
|
|
121
125
|
if (onPress) onPress(panelId, event)
|
|
@@ -124,7 +128,7 @@ const ExpandCollapsePanel = React.forwardRef(
|
|
|
124
128
|
const onContainerLayout = (event) => {
|
|
125
129
|
const { layout: { height = 0 } = {} } = event.nativeEvent
|
|
126
130
|
if (Platform.OS === 'web' || (Platform.OS !== 'web' && containerHeight === null)) {
|
|
127
|
-
setContainerHeight(height)
|
|
131
|
+
setContainerHeight(height + mobileScrollBuffer)
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -208,7 +212,7 @@ const ExpandCollapsePanel = React.forwardRef(
|
|
|
208
212
|
) : (
|
|
209
213
|
<ScrollView
|
|
210
214
|
onContentSizeChange={(_, height) => {
|
|
211
|
-
setContainerHeight(height)
|
|
215
|
+
setContainerHeight(height + mobileScrollBuffer)
|
|
212
216
|
}}
|
|
213
217
|
style={selectContainerStyles(themeTokens)}
|
|
214
218
|
accessibilityLabel={subPanelAccessibilityLabel}
|
|
@@ -226,7 +230,14 @@ ExpandCollapsePanel.displayName = 'ExpandCollapsePanel'
|
|
|
226
230
|
|
|
227
231
|
const staticStyles = StyleSheet.create({
|
|
228
232
|
container: {
|
|
229
|
-
|
|
233
|
+
...Platform.select({
|
|
234
|
+
web: {
|
|
235
|
+
flexGrow: 1,
|
|
236
|
+
flexshrink: 1,
|
|
237
|
+
flexbasis: 'auto'
|
|
238
|
+
},
|
|
239
|
+
default: { flex: 1 }
|
|
240
|
+
}),
|
|
230
241
|
justifyContent: 'flex-start'
|
|
231
242
|
},
|
|
232
243
|
panelContainer: {
|
|
@@ -60,14 +60,17 @@ const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
|
60
60
|
*/
|
|
61
61
|
const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWidth) => {
|
|
62
62
|
if (limitWidth) {
|
|
63
|
-
|
|
63
|
+
if (viewportKey === 'xs') {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
return viewports.map.get(viewportKey)
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
if (contentMinWidth) {
|
|
67
70
|
return maxWidth
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
return
|
|
73
|
+
return null
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
|
|
3
|
+
import { Portal } from '@gorhom/portal'
|
|
4
4
|
import { View, StyleSheet, Dimensions, SafeAreaView, Platform, ScrollView } from 'react-native'
|
|
5
5
|
import { useThemeTokens, useThemeTokensCallback, applyTextStyles } from '../ThemeProvider'
|
|
6
6
|
import {
|
|
@@ -215,11 +215,27 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
215
215
|
onCancel()
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
const appRootRef = React.useRef(null)
|
|
219
|
+
const [rootOffsets, setRootOffsets] = React.useState(null)
|
|
220
|
+
|
|
221
|
+
React.useEffect(() => {
|
|
222
|
+
if (rootOffsets) return
|
|
223
|
+
appRootRef.current?.measureInWindow((x, y) => {
|
|
224
|
+
// Only set offsets if they are positive
|
|
225
|
+
// this is because we want to avoid negative offsets that could cause
|
|
226
|
+
// the dropdown to be positioned incorrectly in some situations
|
|
227
|
+
if (y > 0) setRootOffsets({ horizontal: x, vertical: y })
|
|
228
|
+
})
|
|
229
|
+
}, [isOpen, rootOffsets])
|
|
230
|
+
|
|
218
231
|
const { align, offsets } = useResponsiveProp({
|
|
219
232
|
xs: { align: { top: 'top', left: 'left' } },
|
|
220
233
|
sm: {
|
|
221
234
|
align: { top: 'bottom', left: 'left' },
|
|
222
|
-
offsets: {
|
|
235
|
+
offsets: {
|
|
236
|
+
vertical: 4 - (rootOffsets?.vertical || 0),
|
|
237
|
+
horizontal: -rootOffsets?.horizontal || 0
|
|
238
|
+
}
|
|
223
239
|
}
|
|
224
240
|
})
|
|
225
241
|
|
|
@@ -345,6 +361,14 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
345
361
|
|
|
346
362
|
return (
|
|
347
363
|
<>
|
|
364
|
+
{/*
|
|
365
|
+
This View is rendered inside a Portal to determine the application's root position.
|
|
366
|
+
Capturing it is crucial to determine offsets to position the Modal correctly, preventing
|
|
367
|
+
misalignment that can occur when the Portal does not render the Modal at the body level.
|
|
368
|
+
*/}
|
|
369
|
+
<Portal>
|
|
370
|
+
<View ref={appRootRef} style={styles.appRootRef} />
|
|
371
|
+
</Portal>
|
|
348
372
|
<ButtonDropdown
|
|
349
373
|
ref={sourceRef}
|
|
350
374
|
key={id}
|
|
@@ -434,6 +458,11 @@ const styles = StyleSheet.create({
|
|
|
434
458
|
},
|
|
435
459
|
scrollContainer: {
|
|
436
460
|
padding: 1
|
|
461
|
+
},
|
|
462
|
+
appRootRef: {
|
|
463
|
+
position: 'absolute',
|
|
464
|
+
top: 0,
|
|
465
|
+
left: 0
|
|
437
466
|
}
|
|
438
467
|
})
|
|
439
468
|
|
package/src/Select/Select.jsx
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { View, Platform, StyleSheet } from 'react-native'
|
|
4
4
|
import PropTypes from 'prop-types'
|
|
5
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
5
6
|
import { applyTextStyles, useThemeTokens, applyOuterBorder, useTheme } from '../ThemeProvider'
|
|
6
7
|
import {
|
|
7
8
|
a11yProps,
|
|
@@ -69,7 +70,16 @@ const selectInputStyles = (
|
|
|
69
70
|
WebkitAppearance: 'none', // since iOS Safari needs a prefix
|
|
70
71
|
outline: 'none',
|
|
71
72
|
cursor: inactive ? 'not-allowed' : undefined,
|
|
72
|
-
opacity: inactive ? 1 : undefined // override Chrome's default fadeout of a disabled select
|
|
73
|
+
opacity: inactive ? 1 : undefined, // override Chrome's default fadeout of a disabled select
|
|
74
|
+
// Enhanced fix for mobile dropdown positioning: restore native appearance on touch devices
|
|
75
|
+
// Using multiple media queries and higher specificity to ensure it works in all contexts
|
|
76
|
+
[`@media (pointer: coarse), (maxWidth: ${viewports.map.get(
|
|
77
|
+
viewports.md
|
|
78
|
+
)}px) and (hover: none)`]: {
|
|
79
|
+
appearance: 'auto !important',
|
|
80
|
+
WebkitAppearance: 'auto !important',
|
|
81
|
+
backgroundImage: 'none !important'
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
})
|
|
75
85
|
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { Dimensions, Platform } from 'react-native'
|
|
3
3
|
import debounce from 'lodash.debounce'
|
|
4
4
|
|
|
5
|
-
const DEBOUNCE_DELAY =
|
|
5
|
+
const DEBOUNCE_DELAY = 300
|
|
6
6
|
|
|
7
7
|
const adjustHorizontalToFit = (initialOffset, windowWidth, sourceWidth) => {
|
|
8
8
|
const offset = Math.max(0, initialOffset)
|
|
@@ -62,45 +62,46 @@ function getOverlaidPosition({
|
|
|
62
62
|
// Will have top, bottom, left and/or right offsets depending on `align`
|
|
63
63
|
const positioning = {}
|
|
64
64
|
|
|
65
|
+
const verticalOffset = offsets.vertical ?? 0
|
|
66
|
+
const horizontalOffset = offsets.horizontal ?? 0
|
|
67
|
+
|
|
65
68
|
if (align.top)
|
|
66
69
|
positioning.top = getPosition({
|
|
67
70
|
edge: getEdgeType(align, 'top'),
|
|
68
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
71
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset,
|
|
69
72
|
sourceSize: sourceLayout.height
|
|
70
73
|
})
|
|
71
74
|
if (align.middle)
|
|
72
75
|
positioning.top = getPosition({
|
|
73
76
|
edge: getEdgeType(align, 'middle'),
|
|
74
|
-
fromEdge: sourceLayout.y + scrollY +
|
|
77
|
+
fromEdge: sourceLayout.y + scrollY + verticalOffset - targetDimensions.height / 2,
|
|
75
78
|
sourceSize: sourceLayout.height
|
|
76
79
|
})
|
|
77
80
|
if (align.bottom)
|
|
78
81
|
positioning.bottom = getPosition({
|
|
79
82
|
edge: getEdgeType(align, 'bottom'),
|
|
80
83
|
fromEdge:
|
|
81
|
-
windowDimensions.height -
|
|
82
|
-
(sourceLayout.y + scrollY + sourceLayout.height - (offsets.vertical ?? 0)),
|
|
84
|
+
windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height - verticalOffset),
|
|
83
85
|
sourceSize: sourceLayout.height
|
|
84
86
|
})
|
|
85
87
|
|
|
86
88
|
if (align.left)
|
|
87
89
|
positioning.left = getPosition({
|
|
88
90
|
edge: getEdgeType(align, 'left'),
|
|
89
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
91
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset,
|
|
90
92
|
sourceSize: sourceLayout.width
|
|
91
93
|
})
|
|
92
94
|
if (align.center)
|
|
93
95
|
positioning.left = getPosition({
|
|
94
96
|
edge: getEdgeType(align, 'center'),
|
|
95
|
-
fromEdge: sourceLayout.x + scrollX +
|
|
97
|
+
fromEdge: sourceLayout.x + scrollX + horizontalOffset - targetDimensions.width / 2,
|
|
96
98
|
sourceSize: sourceLayout.width
|
|
97
99
|
})
|
|
98
100
|
if (align.right)
|
|
99
101
|
positioning.right = getPosition({
|
|
100
102
|
edge: getEdgeType(align, 'right'),
|
|
101
103
|
fromEdge:
|
|
102
|
-
windowDimensions.width -
|
|
103
|
-
(sourceLayout.x + scrollX + sourceLayout.width - (offsets.horizontal ?? 0)),
|
|
104
|
+
windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width - horizontalOffset),
|
|
104
105
|
sourceSize: sourceLayout.width
|
|
105
106
|
})
|
|
106
107
|
|