@telus-uds/components-base 4.0.0-alpha.2 → 4.0.0-beta.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 +16 -1
- package/lib/cjs/Autocomplete/Autocomplete.js +13 -1
- package/lib/cjs/Autocomplete/constants.js +1 -1
- package/lib/cjs/Listbox/ListboxOverlay.js +7 -1
- package/lib/cjs/MultiSelectFilter/ModalOverlay.js +7 -1
- package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +2 -28
- package/lib/cjs/TextInput/TextInputBase.js +2 -1
- package/lib/cjs/utils/useOverlaidPosition.js +83 -42
- package/lib/esm/Autocomplete/Autocomplete.js +13 -1
- package/lib/esm/Autocomplete/constants.js +1 -1
- package/lib/esm/Listbox/ListboxOverlay.js +7 -1
- package/lib/esm/MultiSelectFilter/ModalOverlay.js +8 -2
- package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +2 -28
- package/lib/esm/TextInput/TextInputBase.js +2 -1
- package/lib/esm/utils/useOverlaidPosition.js +83 -42
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/src/Autocomplete/Autocomplete.jsx +11 -2
- package/src/Autocomplete/constants.js +1 -1
- package/src/Listbox/ListboxOverlay.jsx +6 -2
- package/src/MultiSelectFilter/ModalOverlay.jsx +9 -3
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +1 -31
- package/src/TextInput/TextInputBase.jsx +2 -1
- package/src/utils/useOverlaidPosition.js +84 -34
- package/types/A11yInfoProvider.d.ts +9 -0
- package/types/A11yText.d.ts +13 -0
- package/types/ActionCard.d.ts +19 -0
- package/types/ActivityIndicator.d.ts +13 -0
- package/types/Autocomplete.d.ts +27 -0
- package/types/Badge.d.ts +15 -4
- package/types/BaseProvider.d.ts +26 -0
- package/types/Box.d.ts +13 -3
- package/types/Button.d.ts +35 -0
- package/types/Card.d.ts +19 -0
- package/types/CardGroup.d.ts +23 -0
- package/types/Carousel.d.ts +25 -0
- package/types/Checkbox.d.ts +15 -2
- package/types/CheckboxCard.d.ts +20 -0
- package/types/CheckboxCardGroup.d.ts +10 -4
- package/types/ChevronLink.d.ts +9 -2
- package/types/ColourToggle.d.ts +16 -0
- package/types/Common.d.ts +7 -3
- package/types/Divider.d.ts +13 -1
- package/types/DownloadApp.d.ts +19 -0
- package/types/ExpandCollapse.d.ts +16 -9
- package/types/ExpandCollapseMini.d.ts +16 -0
- package/types/Feedback.d.ts +16 -0
- package/types/Fieldset.d.ts +17 -0
- package/types/FileUpload.d.ts +6 -0
- package/types/FlexGrid.d.ts +57 -0
- package/types/Footnote.d.ts +18 -0
- package/types/HorizontalScroll.d.ts +15 -0
- package/types/HorizontalScrollButton.d.ts +17 -1
- package/types/Icon.d.ts +13 -2
- package/types/IconButton.d.ts +19 -0
- package/types/InputLabel.d.ts +12 -0
- package/types/InputSupports.d.ts +19 -0
- package/types/Link.d.ts +20 -4
- package/types/List.d.ts +11 -4
- package/types/Listbox.d.ts +13 -3
- package/types/Modal.d.ts +25 -0
- package/types/MultiSelectFilter.d.ts +24 -0
- package/types/Notification.d.ts +19 -0
- package/types/OrderedList.d.ts +17 -0
- package/types/Pagination.d.ts +25 -0
- package/types/Portal.d.ts +5 -1
- package/types/PriceLockup.d.ts +28 -0
- package/types/ProductCard.d.ts +20 -0
- package/types/ProductCardGroup.d.ts +21 -0
- package/types/Progress.d.ts +22 -0
- package/types/QuickLinks.d.ts +27 -0
- package/types/QuickLinksFeature.d.ts +27 -0
- package/types/Radio.d.ts +22 -0
- package/types/RadioCard.d.ts +21 -0
- package/types/Responsive.d.ts +14 -0
- package/types/Scroll.d.ts +2 -2
- package/types/Search.d.ts +15 -2
- package/types/Select.d.ts +12 -4
- package/types/SideNav.d.ts +38 -0
- package/types/Skeleton.d.ts +17 -0
- package/types/SkipLink.d.ts +14 -0
- package/types/Spacer.d.ts +8 -1
- package/types/StackView.d.ts +8 -2
- package/types/Status.d.ts +2 -3
- package/types/StepTracker.d.ts +16 -0
- package/types/TabBar.d.ts +25 -0
- package/types/Tabs.d.ts +12 -3
- package/types/Tags.d.ts +24 -0
- package/types/TextButton.d.ts +11 -4
- package/types/TextInput.d.ts +38 -0
- package/types/ThemeProvider.d.ts +29 -0
- package/types/Timeline.d.ts +16 -0
- package/types/ToggleSwitch.d.ts +11 -2
- package/types/ToolTip.d.ts +7 -3
- package/types/TooltipButton.d.ts +20 -0
- package/types/Typography.d.ts +8 -2
- package/types/Validator.d.ts +16 -0
- package/types/ViewportProvider.d.ts +12 -0
- package/types/index.d.ts +197 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Thu, 21 May 2026 03:01:22 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 4.0.0-beta.0
|
|
8
|
+
|
|
9
|
+
Thu, 21 May 2026 03:01:22 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `Autocomplete`: Add scroll to overlay in mobile (david.melara1@telus.com)
|
|
14
|
+
|
|
15
|
+
### Patches
|
|
16
|
+
|
|
17
|
+
- `TextInput`: Fix `onChangeText` returning `undefined` instead of an empty string when `isNumeric` is set and no text is provided (david.melara1@telus.com)
|
|
18
|
+
- `Autocomplete`: inside modal story fixed to show the close button properly (josue.higueroscalderon@telus.com)
|
|
19
|
+
- `TypeScript`: fix types (guillermo.peitzner@telus.com)
|
|
20
|
+
- `MultiSelectFilter`: position issues fixed in MFE (josue.higueroscalderon@telus.com)
|
|
21
|
+
|
|
7
22
|
## 4.0.0-alpha.2
|
|
8
23
|
|
|
9
24
|
Fri, 17 Apr 2026 23:54:32 GMT
|
|
@@ -134,6 +134,7 @@ const Autocomplete = exports.Autocomplete = /*#__PURE__*/_react.default.forwardR
|
|
|
134
134
|
value,
|
|
135
135
|
helpText = '',
|
|
136
136
|
loadingLabel,
|
|
137
|
+
dropdownPosition = 'bottom',
|
|
137
138
|
tokens,
|
|
138
139
|
...rest
|
|
139
140
|
} = _ref2;
|
|
@@ -189,6 +190,13 @@ const Autocomplete = exports.Autocomplete = /*#__PURE__*/_react.default.forwardR
|
|
|
189
190
|
isShown: isExpanded || hintExpansionEnabled,
|
|
190
191
|
offsets: {
|
|
191
192
|
vertical: _Platform.default.OS !== 'web' && (hint || inputLabel) ? 28 : 4
|
|
193
|
+
},
|
|
194
|
+
align: dropdownPosition === 'top' ? {
|
|
195
|
+
center: 'center',
|
|
196
|
+
bottom: 'top'
|
|
197
|
+
} : {
|
|
198
|
+
center: 'center',
|
|
199
|
+
top: 'bottom'
|
|
192
200
|
}
|
|
193
201
|
});
|
|
194
202
|
const targetRef = _react.default.useRef(null);
|
|
@@ -541,5 +549,9 @@ Autocomplete.propTypes = {
|
|
|
541
549
|
/**
|
|
542
550
|
* Input value for controlled usage
|
|
543
551
|
*/
|
|
544
|
-
value: _propTypes.default.string
|
|
552
|
+
value: _propTypes.default.string,
|
|
553
|
+
/**
|
|
554
|
+
* Controls whether the dropdown renders above or below the input. Defaults to 'bottom'.
|
|
555
|
+
*/
|
|
556
|
+
dropdownPosition: _propTypes.default.oneOf(['top', 'bottom'])
|
|
545
557
|
};
|
|
@@ -6,6 +6,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.MIN_LISTBOX_WIDTH = exports.INPUT_LEFT_PADDING = exports.DEFAULT_MIN_TO_SUGGESTION = exports.DEFAULT_MAX_SUGGESTIONS = exports.DEFAULT_MAX_DROPDOWN_HEIGHT = void 0;
|
|
7
7
|
const DEFAULT_MIN_TO_SUGGESTION = exports.DEFAULT_MIN_TO_SUGGESTION = 1;
|
|
8
8
|
const DEFAULT_MAX_SUGGESTIONS = exports.DEFAULT_MAX_SUGGESTIONS = 5;
|
|
9
|
-
const DEFAULT_MAX_DROPDOWN_HEIGHT = exports.DEFAULT_MAX_DROPDOWN_HEIGHT =
|
|
9
|
+
const DEFAULT_MAX_DROPDOWN_HEIGHT = exports.DEFAULT_MAX_DROPDOWN_HEIGHT = 500;
|
|
10
10
|
const INPUT_LEFT_PADDING = exports.INPUT_LEFT_PADDING = 16;
|
|
11
11
|
const MIN_LISTBOX_WIDTH = exports.MIN_LISTBOX_WIDTH = 288;
|
|
@@ -9,6 +9,7 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
|
9
9
|
var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
|
|
10
10
|
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
11
11
|
var _Platform = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Platform"));
|
|
12
|
+
var _ScrollView = _interopRequireDefault(require("react-native-web/dist/cjs/exports/ScrollView"));
|
|
12
13
|
var _Portal = require("../Portal/Portal");
|
|
13
14
|
var _useThemeTokens = require("../ThemeProvider/useThemeTokens");
|
|
14
15
|
var _Card = require("../Card/Card");
|
|
@@ -67,7 +68,12 @@ const DropdownOverlay = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
|
|
|
67
68
|
paddingLeft: paddingHorizontal,
|
|
68
69
|
paddingRight: paddingHorizontal
|
|
69
70
|
},
|
|
70
|
-
children:
|
|
71
|
+
children: _Platform.default.OS !== 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ScrollView.default, {
|
|
72
|
+
style: {
|
|
73
|
+
maxHeight
|
|
74
|
+
},
|
|
75
|
+
children: children
|
|
76
|
+
}) : children
|
|
71
77
|
})
|
|
72
78
|
});
|
|
73
79
|
});
|
|
@@ -10,6 +10,7 @@ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Vi
|
|
|
10
10
|
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
11
11
|
var _Platform = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Platform"));
|
|
12
12
|
var _portal = require("@gorhom/portal");
|
|
13
|
+
var _Portal = require("../Portal/Portal");
|
|
13
14
|
var _useCopy = require("../utils/useCopy");
|
|
14
15
|
var _copyPropTypes = require("../utils/props/copyPropTypes");
|
|
15
16
|
var _tokens = require("../utils/props/tokens");
|
|
@@ -125,7 +126,12 @@ const ModalOverlay = exports.ModalOverlay = /*#__PURE__*/_react.default.forwardR
|
|
|
125
126
|
copy
|
|
126
127
|
});
|
|
127
128
|
const closeLabel = getCopy('closeButton');
|
|
128
|
-
|
|
129
|
+
|
|
130
|
+
// On web, use the local Portal (always appends to document.body) to ensure correct
|
|
131
|
+
// absolute positioning in MFE/iframe contexts. @gorhom/portal may render its host
|
|
132
|
+
// outside the iframe's document, causing coordinate mismatches when scrolled.
|
|
133
|
+
const PortalComponent = _Platform.default.OS === 'web' ? _Portal.Portal : _portal.Portal;
|
|
134
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(PortalComponent, {
|
|
129
135
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
130
136
|
ref: containerRef,
|
|
131
137
|
onLayout: onLayout,
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.MultiSelectFilter = void 0;
|
|
7
7
|
var _react = _interopRequireDefault(require("react"));
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
|
-
var _portal = require("@gorhom/portal");
|
|
10
9
|
var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
|
|
11
10
|
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
12
11
|
var _Dimensions = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Dimensions"));
|
|
@@ -225,20 +224,6 @@ const MultiSelectFilter = exports.MultiSelectFilter = /*#__PURE__*/_react.defaul
|
|
|
225
224
|
setIsOpen(false);
|
|
226
225
|
onCancel();
|
|
227
226
|
};
|
|
228
|
-
const appRootRef = _react.default.useRef(null);
|
|
229
|
-
const [rootOffsets, setRootOffsets] = _react.default.useState(null);
|
|
230
|
-
_react.default.useEffect(() => {
|
|
231
|
-
if (rootOffsets) return;
|
|
232
|
-
appRootRef.current?.measureInWindow((x, y) => {
|
|
233
|
-
// Only set offsets if they are positive
|
|
234
|
-
// this is because we want to avoid negative offsets that could cause
|
|
235
|
-
// the dropdown to be positioned incorrectly in some situations
|
|
236
|
-
if (y > 0) setRootOffsets({
|
|
237
|
-
horizontal: x,
|
|
238
|
-
vertical: y
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
}, [isOpen, rootOffsets]);
|
|
242
227
|
const {
|
|
243
228
|
align,
|
|
244
229
|
offsets
|
|
@@ -255,8 +240,7 @@ const MultiSelectFilter = exports.MultiSelectFilter = /*#__PURE__*/_react.defaul
|
|
|
255
240
|
left: 'left'
|
|
256
241
|
},
|
|
257
242
|
offsets: {
|
|
258
|
-
vertical: 4
|
|
259
|
-
horizontal: -rootOffsets?.horizontal || 0
|
|
243
|
+
vertical: 4
|
|
260
244
|
}
|
|
261
245
|
}
|
|
262
246
|
});
|
|
@@ -393,12 +377,7 @@ const MultiSelectFilter = exports.MultiSelectFilter = /*#__PURE__*/_react.defaul
|
|
|
393
377
|
})]
|
|
394
378
|
});
|
|
395
379
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
396
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
397
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
398
|
-
ref: appRootRef,
|
|
399
|
-
style: styles.appRootRef
|
|
400
|
-
})
|
|
401
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ButtonDropdown.ButtonDropdown, {
|
|
380
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ButtonDropdown.ButtonDropdown, {
|
|
402
381
|
ref: sourceRef,
|
|
403
382
|
...pressHandlers,
|
|
404
383
|
value: isOpen,
|
|
@@ -478,11 +457,6 @@ const styles = _StyleSheet.default.create({
|
|
|
478
457
|
},
|
|
479
458
|
scrollContainer: {
|
|
480
459
|
padding: 1
|
|
481
|
-
},
|
|
482
|
-
appRootRef: {
|
|
483
|
-
position: 'absolute',
|
|
484
|
-
top: 0,
|
|
485
|
-
left: 0
|
|
486
460
|
}
|
|
487
461
|
});
|
|
488
462
|
|
|
@@ -290,7 +290,8 @@ const TextInputBase = exports.TextInputBase = /*#__PURE__*/_react.default.forwar
|
|
|
290
290
|
}, [element, pattern]);
|
|
291
291
|
const handleChangeText = event => {
|
|
292
292
|
const text = event.nativeEvent?.text ?? event.target?.value;
|
|
293
|
-
|
|
293
|
+
// Do NOT use `|| undefined`: empty string is falsy and would break the controlled contract
|
|
294
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') ?? '' : text;
|
|
294
295
|
if (type === 'card' && filteredText) {
|
|
295
296
|
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
|
|
296
297
|
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g');
|
|
@@ -61,46 +61,45 @@ function getOverlaidPosition(_ref2) {
|
|
|
61
61
|
offsets = {},
|
|
62
62
|
align
|
|
63
63
|
} = _ref2;
|
|
64
|
-
// Web-only: this will be difficult to mimic on native because there's no global scroll position.
|
|
65
|
-
// TODO: wire something in e.g. a scroll ref accessible from a provider included in Allium provider
|
|
66
|
-
// that can be passed to the appropriate ScrollView?
|
|
67
|
-
const {
|
|
68
|
-
scrollX = 0,
|
|
69
|
-
scrollY = 0
|
|
70
|
-
} = typeof window === 'object' ? window : {};
|
|
71
|
-
|
|
72
64
|
// Will have top, bottom, left and/or right offsets depending on `align`
|
|
73
65
|
const positioning = {};
|
|
74
66
|
const verticalOffset = offsets.vertical ?? 0;
|
|
75
67
|
const horizontalOffset = offsets.horizontal ?? 0;
|
|
76
68
|
if (align.top) positioning.top = getPosition({
|
|
77
69
|
edge: getEdgeType(align, 'top'),
|
|
78
|
-
fromEdge: sourceLayout.y +
|
|
70
|
+
fromEdge: sourceLayout.y + verticalOffset,
|
|
79
71
|
sourceSize: sourceLayout.height
|
|
80
72
|
});
|
|
81
73
|
if (align.middle) positioning.top = getPosition({
|
|
82
74
|
edge: getEdgeType(align, 'middle'),
|
|
83
|
-
fromEdge: sourceLayout.y +
|
|
84
|
-
sourceSize: sourceLayout.height
|
|
85
|
-
});
|
|
86
|
-
if (align.bottom) positioning.bottom = getPosition({
|
|
87
|
-
edge: getEdgeType(align, 'bottom'),
|
|
88
|
-
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height - verticalOffset),
|
|
75
|
+
fromEdge: sourceLayout.y + verticalOffset - targetDimensions.height / 2,
|
|
89
76
|
sourceSize: sourceLayout.height
|
|
90
77
|
});
|
|
78
|
+
if (align.bottom) {
|
|
79
|
+
if (_Platform.default.OS !== 'web') {
|
|
80
|
+
// On native, position:absolute is parent-relative, so use negative top offset instead of window-based bottom math.
|
|
81
|
+
positioning.top = sourceLayout.y - targetDimensions.height - verticalOffset;
|
|
82
|
+
} else {
|
|
83
|
+
positioning.bottom = getPosition({
|
|
84
|
+
edge: getEdgeType(align, 'bottom'),
|
|
85
|
+
fromEdge: windowDimensions.height - (sourceLayout.y + sourceLayout.height - verticalOffset),
|
|
86
|
+
sourceSize: sourceLayout.height
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
91
90
|
if (align.left) positioning.left = getPosition({
|
|
92
91
|
edge: getEdgeType(align, 'left'),
|
|
93
|
-
fromEdge: sourceLayout.x +
|
|
92
|
+
fromEdge: sourceLayout.x + horizontalOffset,
|
|
94
93
|
sourceSize: sourceLayout.width
|
|
95
94
|
});
|
|
96
95
|
if (align.center) positioning.left = getPosition({
|
|
97
96
|
edge: getEdgeType(align, 'center'),
|
|
98
|
-
fromEdge: sourceLayout.x +
|
|
97
|
+
fromEdge: sourceLayout.x + horizontalOffset - targetDimensions.width / 2,
|
|
99
98
|
sourceSize: sourceLayout.width
|
|
100
99
|
});
|
|
101
100
|
if (align.right) positioning.right = getPosition({
|
|
102
101
|
edge: getEdgeType(align, 'right'),
|
|
103
|
-
fromEdge: windowDimensions.width - (sourceLayout.x +
|
|
102
|
+
fromEdge: windowDimensions.width - (sourceLayout.x + sourceLayout.width - horizontalOffset),
|
|
104
103
|
sourceSize: sourceLayout.width
|
|
105
104
|
});
|
|
106
105
|
if (!(align.left && align.right)) {
|
|
@@ -141,6 +140,32 @@ const useOverlaidPosition = _ref3 => {
|
|
|
141
140
|
const targetRef = (0, _react.useRef)(null);
|
|
142
141
|
const [targetDimensions, setTargetDimensions] = (0, _react.useState)(null);
|
|
143
142
|
const [windowDimensions, setWindowDimensions] = (0, _react.useState)(null);
|
|
143
|
+
const hasRemeasuredRef = (0, _react.useRef)(false);
|
|
144
|
+
const applySourceLayout = (0, _react.useCallback)((dims, x, y, width, height) => {
|
|
145
|
+
setWindowDimensions(dims);
|
|
146
|
+
setSourceLayout({
|
|
147
|
+
x,
|
|
148
|
+
y,
|
|
149
|
+
width,
|
|
150
|
+
height
|
|
151
|
+
});
|
|
152
|
+
}, []);
|
|
153
|
+
const measureSourceOnWeb = (0, _react.useCallback)(windowDims => {
|
|
154
|
+
const el = sourceRef.current;
|
|
155
|
+
if (!el) return;
|
|
156
|
+
const domNode = el._nativeTag ?? el;
|
|
157
|
+
const dims = windowDims ?? _Dimensions.default.get('window');
|
|
158
|
+
const rect = typeof domNode.getBoundingClientRect === 'function' ? domNode.getBoundingClientRect() : null;
|
|
159
|
+
if (rect) {
|
|
160
|
+
const x = rect.left + (typeof window.scrollX === 'number' ? window.scrollX : 0);
|
|
161
|
+
const y = rect.top + (typeof window.scrollY === 'number' ? window.scrollY : 0);
|
|
162
|
+
applySourceLayout(dims, x, y, rect.width, rect.height);
|
|
163
|
+
} else {
|
|
164
|
+
el.measureInWindow((mx, my, width, height) => {
|
|
165
|
+
applySourceLayout(dims, mx, my, width, height);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}, [applySourceLayout]);
|
|
144
169
|
const onTargetLayout = (0, _react.useCallback)(_ref4 => {
|
|
145
170
|
let {
|
|
146
171
|
nativeEvent: {
|
|
@@ -171,16 +196,19 @@ const useOverlaidPosition = _ref3 => {
|
|
|
171
196
|
let {
|
|
172
197
|
window
|
|
173
198
|
} = _ref5;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
199
|
+
if (_Platform.default.OS === 'web') {
|
|
200
|
+
measureSourceOnWeb(window);
|
|
201
|
+
} else {
|
|
202
|
+
sourceRef.current?.measure((x, y, width, height) => {
|
|
203
|
+
setWindowDimensions(window);
|
|
204
|
+
setSourceLayout({
|
|
205
|
+
x,
|
|
206
|
+
y,
|
|
207
|
+
width,
|
|
208
|
+
height
|
|
209
|
+
});
|
|
182
210
|
});
|
|
183
|
-
}
|
|
211
|
+
}
|
|
184
212
|
};
|
|
185
213
|
let subscription;
|
|
186
214
|
const unsubscribe = () => {
|
|
@@ -193,6 +221,7 @@ const useOverlaidPosition = _ref3 => {
|
|
|
193
221
|
}
|
|
194
222
|
setSourceLayout(null);
|
|
195
223
|
setTargetDimensions(null);
|
|
224
|
+
hasRemeasuredRef.current = false;
|
|
196
225
|
};
|
|
197
226
|
if (readyToShow) {
|
|
198
227
|
subscription = _Dimensions.default.addEventListener('change', handleDimensionsChange);
|
|
@@ -203,36 +232,48 @@ const useOverlaidPosition = _ref3 => {
|
|
|
203
232
|
unsubscribe();
|
|
204
233
|
}
|
|
205
234
|
return unsubscribe;
|
|
206
|
-
}, [readyToShow]);
|
|
235
|
+
}, [readyToShow, measureSourceOnWeb]);
|
|
236
|
+
|
|
237
|
+
// Re-measure source when targetDimensions first becomes available.
|
|
238
|
+
// Without this, there is a race condition: getBoundingClientRect() resolves faster than
|
|
239
|
+
// measureInWindow used to, so sourceLayout may be set before the dropdown has rendered
|
|
240
|
+
// and reported its dimensions via onTargetLayout. When targetDimensions finally arrives,
|
|
241
|
+
// sourceLayout is stale (no scroll has occurred to trigger handleScroll).
|
|
242
|
+
// Setting hasRemeasuredRef.current=true here also prevents the blink: isReady (on web)
|
|
243
|
+
// won't become true until this effect completes, guaranteeing the dropdown is always shown
|
|
244
|
+
// at its final correct position without an intermediate incorrect-position frame.
|
|
245
|
+
(0, _react.useEffect)(() => {
|
|
246
|
+
if (!isShown || !sourceRef.current || !targetDimensions || _Platform.default.OS !== 'web') return;
|
|
247
|
+
measureSourceOnWeb();
|
|
248
|
+
hasRemeasuredRef.current = true;
|
|
249
|
+
}, [targetDimensions, isShown, measureSourceOnWeb]);
|
|
207
250
|
(0, _react.useEffect)(() => {
|
|
208
251
|
if (_Platform.default.OS !== 'web') {
|
|
209
252
|
return undefined;
|
|
210
253
|
}
|
|
211
|
-
const handleScroll = (0, _lodash.default)(
|
|
212
|
-
sourceRef.current?.measureInWindow((x, y, width, height) => {
|
|
213
|
-
setWindowDimensions(window);
|
|
214
|
-
setSourceLayout({
|
|
215
|
-
x,
|
|
216
|
-
y,
|
|
217
|
-
width,
|
|
218
|
-
height
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
}, DEBOUNCE_DELAY);
|
|
254
|
+
const handleScroll = (0, _lodash.default)(measureSourceOnWeb, DEBOUNCE_DELAY);
|
|
222
255
|
window.addEventListener('scroll', handleScroll);
|
|
223
256
|
return () => {
|
|
224
257
|
window.removeEventListener('scroll', handleScroll);
|
|
225
258
|
handleScroll.cancel();
|
|
226
259
|
};
|
|
227
|
-
}, [
|
|
228
|
-
|
|
260
|
+
}, [measureSourceOnWeb]);
|
|
261
|
+
|
|
262
|
+
// On web, require hasRemeasuredRef to be true before declaring isReady. This ensures
|
|
263
|
+
// the dropdown is never shown with an intermediate stale position (the blink).
|
|
264
|
+
// On native, hasRemeasuredRef stays false (the web-only effect never runs), so we skip
|
|
265
|
+
// that check to preserve the original native behaviour.
|
|
266
|
+
const isReady = Boolean(isShown && sourceLayout && windowDimensions && targetDimensions && (_Platform.default.OS !== 'web' || hasRemeasuredRef.current));
|
|
229
267
|
const overlaidPosition = isReady ? getOverlaidPosition({
|
|
230
268
|
sourceLayout,
|
|
231
269
|
targetDimensions,
|
|
232
270
|
windowDimensions,
|
|
233
271
|
offsets,
|
|
234
272
|
align
|
|
235
|
-
}) : {
|
|
273
|
+
}) : {
|
|
274
|
+
top: 0,
|
|
275
|
+
left: 0
|
|
276
|
+
};
|
|
236
277
|
return {
|
|
237
278
|
overlaidPosition,
|
|
238
279
|
sourceRef,
|
|
@@ -127,6 +127,7 @@ export const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
127
127
|
value,
|
|
128
128
|
helpText = '',
|
|
129
129
|
loadingLabel,
|
|
130
|
+
dropdownPosition = 'bottom',
|
|
130
131
|
tokens,
|
|
131
132
|
...rest
|
|
132
133
|
} = _ref2;
|
|
@@ -182,6 +183,13 @@ export const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
182
183
|
isShown: isExpanded || hintExpansionEnabled,
|
|
183
184
|
offsets: {
|
|
184
185
|
vertical: Platform.OS !== 'web' && (hint || inputLabel) ? 28 : 4
|
|
186
|
+
},
|
|
187
|
+
align: dropdownPosition === 'top' ? {
|
|
188
|
+
center: 'center',
|
|
189
|
+
bottom: 'top'
|
|
190
|
+
} : {
|
|
191
|
+
center: 'center',
|
|
192
|
+
top: 'bottom'
|
|
185
193
|
}
|
|
186
194
|
});
|
|
187
195
|
const targetRef = React.useRef(null);
|
|
@@ -534,5 +542,9 @@ Autocomplete.propTypes = {
|
|
|
534
542
|
/**
|
|
535
543
|
* Input value for controlled usage
|
|
536
544
|
*/
|
|
537
|
-
value: PropTypes.string
|
|
545
|
+
value: PropTypes.string,
|
|
546
|
+
/**
|
|
547
|
+
* Controls whether the dropdown renders above or below the input. Defaults to 'bottom'.
|
|
548
|
+
*/
|
|
549
|
+
dropdownPosition: PropTypes.oneOf(['top', 'bottom'])
|
|
538
550
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const DEFAULT_MIN_TO_SUGGESTION = 1;
|
|
2
2
|
export const DEFAULT_MAX_SUGGESTIONS = 5;
|
|
3
|
-
export const DEFAULT_MAX_DROPDOWN_HEIGHT =
|
|
3
|
+
export const DEFAULT_MAX_DROPDOWN_HEIGHT = 500;
|
|
4
4
|
export const INPUT_LEFT_PADDING = 16;
|
|
5
5
|
export const MIN_LISTBOX_WIDTH = 288;
|
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import View from "react-native-web/dist/exports/View";
|
|
4
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
5
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
6
|
+
import ScrollView from "react-native-web/dist/exports/ScrollView";
|
|
6
7
|
import { Portal } from '../Portal/Portal';
|
|
7
8
|
import { useThemeTokens } from '../ThemeProvider/useThemeTokens';
|
|
8
9
|
import { Card } from '../Card/Card';
|
|
@@ -60,7 +61,12 @@ const DropdownOverlay = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
60
61
|
paddingLeft: paddingHorizontal,
|
|
61
62
|
paddingRight: paddingHorizontal
|
|
62
63
|
},
|
|
63
|
-
children:
|
|
64
|
+
children: Platform.OS !== 'web' ? /*#__PURE__*/_jsx(ScrollView, {
|
|
65
|
+
style: {
|
|
66
|
+
maxHeight
|
|
67
|
+
},
|
|
68
|
+
children: children
|
|
69
|
+
}) : children
|
|
64
70
|
})
|
|
65
71
|
});
|
|
66
72
|
});
|
|
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import View from "react-native-web/dist/exports/View";
|
|
4
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
5
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
6
|
-
import { Portal } from '@gorhom/portal';
|
|
6
|
+
import { Portal as GorhomPortal } from '@gorhom/portal';
|
|
7
|
+
import { Portal as LocalPortal } from '../Portal/Portal';
|
|
7
8
|
import { useCopy } from '../utils/useCopy';
|
|
8
9
|
import { copyPropTypes } from '../utils/props/copyPropTypes';
|
|
9
10
|
import { getTokensPropType } from '../utils/props/tokens';
|
|
@@ -118,7 +119,12 @@ export const ModalOverlay = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
118
119
|
copy
|
|
119
120
|
});
|
|
120
121
|
const closeLabel = getCopy('closeButton');
|
|
121
|
-
|
|
122
|
+
|
|
123
|
+
// On web, use the local Portal (always appends to document.body) to ensure correct
|
|
124
|
+
// absolute positioning in MFE/iframe contexts. @gorhom/portal may render its host
|
|
125
|
+
// outside the iframe's document, causing coordinate mismatches when scrolled.
|
|
126
|
+
const PortalComponent = Platform.OS === 'web' ? LocalPortal : GorhomPortal;
|
|
127
|
+
return /*#__PURE__*/_jsx(PortalComponent, {
|
|
122
128
|
children: /*#__PURE__*/_jsx(View, {
|
|
123
129
|
ref: containerRef,
|
|
124
130
|
onLayout: onLayout,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { Portal } from '@gorhom/portal';
|
|
4
3
|
import View from "react-native-web/dist/exports/View";
|
|
5
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
6
5
|
import Dimensions from "react-native-web/dist/exports/Dimensions";
|
|
@@ -218,20 +217,6 @@ export const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
218
217
|
setIsOpen(false);
|
|
219
218
|
onCancel();
|
|
220
219
|
};
|
|
221
|
-
const appRootRef = React.useRef(null);
|
|
222
|
-
const [rootOffsets, setRootOffsets] = React.useState(null);
|
|
223
|
-
React.useEffect(() => {
|
|
224
|
-
if (rootOffsets) return;
|
|
225
|
-
appRootRef.current?.measureInWindow((x, y) => {
|
|
226
|
-
// Only set offsets if they are positive
|
|
227
|
-
// this is because we want to avoid negative offsets that could cause
|
|
228
|
-
// the dropdown to be positioned incorrectly in some situations
|
|
229
|
-
if (y > 0) setRootOffsets({
|
|
230
|
-
horizontal: x,
|
|
231
|
-
vertical: y
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
}, [isOpen, rootOffsets]);
|
|
235
220
|
const {
|
|
236
221
|
align,
|
|
237
222
|
offsets
|
|
@@ -248,8 +233,7 @@ export const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
248
233
|
left: 'left'
|
|
249
234
|
},
|
|
250
235
|
offsets: {
|
|
251
|
-
vertical: 4
|
|
252
|
-
horizontal: -rootOffsets?.horizontal || 0
|
|
236
|
+
vertical: 4
|
|
253
237
|
}
|
|
254
238
|
}
|
|
255
239
|
});
|
|
@@ -386,12 +370,7 @@ export const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
386
370
|
})]
|
|
387
371
|
});
|
|
388
372
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
389
|
-
children: [/*#__PURE__*/_jsx(
|
|
390
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
391
|
-
ref: appRootRef,
|
|
392
|
-
style: styles.appRootRef
|
|
393
|
-
})
|
|
394
|
-
}), /*#__PURE__*/_jsx(ButtonDropdown, {
|
|
373
|
+
children: [/*#__PURE__*/_jsx(ButtonDropdown, {
|
|
395
374
|
ref: sourceRef,
|
|
396
375
|
...pressHandlers,
|
|
397
376
|
value: isOpen,
|
|
@@ -471,11 +450,6 @@ const styles = StyleSheet.create({
|
|
|
471
450
|
},
|
|
472
451
|
scrollContainer: {
|
|
473
452
|
padding: 1
|
|
474
|
-
},
|
|
475
|
-
appRootRef: {
|
|
476
|
-
position: 'absolute',
|
|
477
|
-
top: 0,
|
|
478
|
-
left: 0
|
|
479
453
|
}
|
|
480
454
|
});
|
|
481
455
|
|
|
@@ -283,7 +283,8 @@ export const TextInputBase = /*#__PURE__*/React.forwardRef((_ref8, ref) => {
|
|
|
283
283
|
}, [element, pattern]);
|
|
284
284
|
const handleChangeText = event => {
|
|
285
285
|
const text = event.nativeEvent?.text ?? event.target?.value;
|
|
286
|
-
|
|
286
|
+
// Do NOT use `|| undefined`: empty string is falsy and would break the controlled contract
|
|
287
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') ?? '' : text;
|
|
287
288
|
if (type === 'card' && filteredText) {
|
|
288
289
|
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
|
|
289
290
|
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g');
|