@telus-uds/components-base 3.30.0 → 3.32.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 +31 -1
- package/lib/cjs/Autocomplete/Autocomplete.js +13 -1
- package/lib/cjs/Autocomplete/constants.js +1 -1
- package/lib/cjs/Button/ButtonGroup.js +91 -23
- package/lib/cjs/Card/CardBase.js +75 -47
- package/lib/cjs/ColourToggle/ColourBubble.js +65 -12
- package/lib/cjs/ColourToggle/ColourToggle.js +40 -11
- package/lib/cjs/ColourToggle/constants.js +15 -0
- package/lib/cjs/ColourToggle/dictionary.js +15 -0
- 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/Validator/Validator.js +5 -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/Button/ButtonGroup.js +91 -23
- package/lib/esm/Card/CardBase.js +75 -47
- package/lib/esm/ColourToggle/ColourBubble.js +66 -13
- package/lib/esm/ColourToggle/ColourToggle.js +41 -12
- package/lib/esm/ColourToggle/constants.js +8 -0
- package/lib/esm/ColourToggle/dictionary.js +9 -0
- 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/Validator/Validator.js +5 -1
- package/lib/esm/utils/useOverlaidPosition.js +83 -42
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Autocomplete/Autocomplete.jsx +11 -2
- package/src/Autocomplete/constants.js +1 -1
- package/src/Button/ButtonGroup.jsx +93 -24
- package/src/Card/CardBase.jsx +94 -75
- package/src/ColourToggle/ColourBubble.jsx +62 -7
- package/src/ColourToggle/ColourToggle.jsx +50 -10
- package/src/ColourToggle/constants.js +10 -0
- package/src/ColourToggle/dictionary.js +6 -0
- 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/Validator/Validator.jsx +5 -1
- package/src/utils/useOverlaidPosition.js +84 -34
|
@@ -12,6 +12,8 @@ var _utils = require("../utils");
|
|
|
12
12
|
var _StackView = require("../StackView");
|
|
13
13
|
var _Typography = _interopRequireDefault(require("../Typography"));
|
|
14
14
|
var _ColourBubble = _interopRequireDefault(require("./ColourBubble"));
|
|
15
|
+
var _constants = require("./constants");
|
|
16
|
+
var _dictionary = _interopRequireDefault(require("./dictionary"));
|
|
15
17
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
18
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
19
|
const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.a11yProps, _utils.viewProps]);
|
|
@@ -23,37 +25,49 @@ const ColourToggle = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
|
|
|
23
25
|
items,
|
|
24
26
|
onChange,
|
|
25
27
|
showTooltips,
|
|
28
|
+
copy = 'en',
|
|
29
|
+
dictionary = _dictionary.default,
|
|
26
30
|
...rest
|
|
27
31
|
} = _ref;
|
|
28
32
|
const [currentColourId, setCurrentColourId] = _react.default.useState(defaultColourId);
|
|
29
33
|
const getTokens = (0, _ThemeProvider.useThemeTokensCallback)('ColourToggle', tokens, variant);
|
|
34
|
+
const getCopy = (0, _utils.useCopy)({
|
|
35
|
+
dictionary,
|
|
36
|
+
copy
|
|
37
|
+
});
|
|
30
38
|
const {
|
|
31
39
|
space
|
|
32
40
|
} = getTokens();
|
|
33
|
-
const {
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
const currentItem = items.find(_ref2 => {
|
|
42
|
+
let {
|
|
43
|
+
id
|
|
44
|
+
} = _ref2;
|
|
45
|
+
return id === currentColourId;
|
|
46
|
+
});
|
|
36
47
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_View.default, {
|
|
37
48
|
ref: ref,
|
|
38
49
|
...selectProps(rest),
|
|
39
50
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Typography.default, {
|
|
40
|
-
children:
|
|
51
|
+
children: currentItem?.unavailable ? `${currentItem.colourName} ${getCopy('unavailable')}` : currentItem?.colourName ?? ''
|
|
41
52
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StackView.StackWrap, {
|
|
42
53
|
space: space,
|
|
43
54
|
accessibilityRole: "radiogroup",
|
|
44
|
-
children: items.map((
|
|
55
|
+
children: items.map((_ref3, index) => {
|
|
45
56
|
let {
|
|
46
57
|
id,
|
|
47
58
|
colourHexCode,
|
|
48
|
-
colourName
|
|
49
|
-
|
|
59
|
+
colourName,
|
|
60
|
+
unavailable,
|
|
61
|
+
disabled
|
|
62
|
+
} = _ref3;
|
|
50
63
|
const colourBubbleId = id || `ColourBubble[${index}]`;
|
|
51
64
|
const handleChangeColour = event => {
|
|
52
65
|
setCurrentColourId(id);
|
|
53
66
|
onChange?.(event, {
|
|
54
67
|
id,
|
|
55
68
|
colourHexCode,
|
|
56
|
-
colourName
|
|
69
|
+
colourName,
|
|
70
|
+
unavailable
|
|
57
71
|
});
|
|
58
72
|
};
|
|
59
73
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ColourBubble.default, {
|
|
@@ -63,7 +77,9 @@ const ColourToggle = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
|
|
|
63
77
|
colourHexCode: colourHexCode,
|
|
64
78
|
colourName: colourName,
|
|
65
79
|
onPress: handleChangeColour,
|
|
66
|
-
showTooltip: showTooltips
|
|
80
|
+
showTooltip: showTooltips,
|
|
81
|
+
unavailable: unavailable,
|
|
82
|
+
isDisabled: !!disabled
|
|
67
83
|
}, colourBubbleId);
|
|
68
84
|
})
|
|
69
85
|
})]
|
|
@@ -90,7 +106,9 @@ ColourToggle.propTypes = {
|
|
|
90
106
|
items: _propTypes.default.arrayOf(_propTypes.default.exact({
|
|
91
107
|
colourHexCode: _propTypes.default.string,
|
|
92
108
|
colourName: _propTypes.default.string,
|
|
93
|
-
id: _propTypes.default.string
|
|
109
|
+
id: _propTypes.default.string,
|
|
110
|
+
unavailable: _propTypes.default.oneOf(Object.values(_constants.UNAVAILABLE_VARIANT)),
|
|
111
|
+
disabled: _propTypes.default.bool
|
|
94
112
|
})),
|
|
95
113
|
/**
|
|
96
114
|
* If provided, this function is called when the current selection of the color is changed of all currently `items`. Receives two parameters: item object selected and the event
|
|
@@ -99,6 +117,17 @@ ColourToggle.propTypes = {
|
|
|
99
117
|
/**
|
|
100
118
|
* When true, displays each colour's name as a tooltip on hover (web only).
|
|
101
119
|
*/
|
|
102
|
-
showTooltips: _propTypes.default.bool
|
|
120
|
+
showTooltips: _propTypes.default.bool,
|
|
121
|
+
/**
|
|
122
|
+
* Select English or French copy.
|
|
123
|
+
*/
|
|
124
|
+
copy: _propTypes.default.oneOf(['en', 'fr']),
|
|
125
|
+
/**
|
|
126
|
+
* Override default labels.
|
|
127
|
+
*/
|
|
128
|
+
dictionary: _propTypes.default.shape({
|
|
129
|
+
en: _constants.DICTIONARY_CONTENT_SHAPE,
|
|
130
|
+
fr: _constants.DICTIONARY_CONTENT_SHAPE
|
|
131
|
+
})
|
|
103
132
|
};
|
|
104
133
|
var _default = exports.default = ColourToggle;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.UNAVAILABLE_VARIANT = exports.DICTIONARY_CONTENT_SHAPE = void 0;
|
|
7
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
const UNAVAILABLE_VARIANT = exports.UNAVAILABLE_VARIANT = {
|
|
10
|
+
DARK: 'dark',
|
|
11
|
+
LIGHT: 'light'
|
|
12
|
+
};
|
|
13
|
+
const DICTIONARY_CONTENT_SHAPE = exports.DICTIONARY_CONTENT_SHAPE = _propTypes.default.shape({
|
|
14
|
+
unavailable: _propTypes.default.string
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
const DEFAULT_COLOUR_TOGGLE_DICTIONARY = {
|
|
8
|
+
en: {
|
|
9
|
+
unavailable: '(Out of stock)'
|
|
10
|
+
},
|
|
11
|
+
fr: {
|
|
12
|
+
unavailable: '(En rupture de stock)'
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var _default = exports.default = DEFAULT_COLOUR_TOGGLE_DICTIONARY;
|
|
@@ -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 = _interopRequireDefault(require("../Portal"));
|
|
13
14
|
var _ThemeProvider = require("../ThemeProvider");
|
|
14
15
|
var _Card = _interopRequireDefault(require("../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 = _interopRequireDefault(require("../Portal/Portal"));
|
|
13
14
|
var _utils = require("../utils");
|
|
14
15
|
var _ViewportProvider = require("../ViewportProvider");
|
|
15
16
|
var _ThemeProvider = require("../ThemeProvider");
|
|
@@ -123,7 +124,12 @@ const ModalOverlay = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
|
|
|
123
124
|
copy
|
|
124
125
|
});
|
|
125
126
|
const closeLabel = getCopy('closeButton');
|
|
126
|
-
|
|
127
|
+
|
|
128
|
+
// On web, use the local Portal (always appends to document.body) to ensure correct
|
|
129
|
+
// absolute positioning in MFE/iframe contexts. @gorhom/portal may render its host
|
|
130
|
+
// outside the iframe's document, causing coordinate mismatches when scrolled.
|
|
131
|
+
const PortalComponent = _Platform.default.OS === 'web' ? _Portal.default : _portal.Portal;
|
|
132
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(PortalComponent, {
|
|
127
133
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
128
134
|
ref: containerRef,
|
|
129
135
|
onLayout: onLayout,
|
|
@@ -6,7 +6,6 @@ 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");
|
|
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"));
|
|
@@ -219,20 +218,6 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
219
218
|
setIsOpen(false);
|
|
220
219
|
onCancel();
|
|
221
220
|
};
|
|
222
|
-
const appRootRef = _react.default.useRef(null);
|
|
223
|
-
const [rootOffsets, setRootOffsets] = _react.default.useState(null);
|
|
224
|
-
_react.default.useEffect(() => {
|
|
225
|
-
if (rootOffsets) return;
|
|
226
|
-
appRootRef.current?.measureInWindow((x, y) => {
|
|
227
|
-
// Only set offsets if they are positive
|
|
228
|
-
// this is because we want to avoid negative offsets that could cause
|
|
229
|
-
// the dropdown to be positioned incorrectly in some situations
|
|
230
|
-
if (y > 0) setRootOffsets({
|
|
231
|
-
horizontal: x,
|
|
232
|
-
vertical: y
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}, [isOpen, rootOffsets]);
|
|
236
221
|
const {
|
|
237
222
|
align,
|
|
238
223
|
offsets
|
|
@@ -249,8 +234,7 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
249
234
|
left: 'left'
|
|
250
235
|
},
|
|
251
236
|
offsets: {
|
|
252
|
-
vertical: 4
|
|
253
|
-
horizontal: -rootOffsets?.horizontal || 0
|
|
237
|
+
vertical: 4
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
});
|
|
@@ -387,12 +371,7 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
387
371
|
})]
|
|
388
372
|
});
|
|
389
373
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
390
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
391
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
392
|
-
ref: appRootRef,
|
|
393
|
-
style: styles.appRootRef
|
|
394
|
-
})
|
|
395
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.ButtonDropdown, {
|
|
374
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.ButtonDropdown, {
|
|
396
375
|
ref: sourceRef,
|
|
397
376
|
...pressHandlers,
|
|
398
377
|
value: isOpen,
|
|
@@ -473,11 +452,6 @@ const styles = _StyleSheet.default.create({
|
|
|
473
452
|
},
|
|
474
453
|
scrollContainer: {
|
|
475
454
|
padding: 1
|
|
476
|
-
},
|
|
477
|
-
appRootRef: {
|
|
478
|
-
position: 'absolute',
|
|
479
|
-
top: 0,
|
|
480
|
-
left: 0
|
|
481
455
|
}
|
|
482
456
|
});
|
|
483
457
|
|
|
@@ -279,7 +279,8 @@ const TextInputBase = /*#__PURE__*/_react.default.forwardRef((_ref8, ref) => {
|
|
|
279
279
|
}, [element, pattern]);
|
|
280
280
|
const handleChangeText = event => {
|
|
281
281
|
const text = event.nativeEvent?.text ?? event.target?.value;
|
|
282
|
-
|
|
282
|
+
// Do NOT use `|| undefined`: empty string is falsy and would break the controlled contract
|
|
283
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') ?? '' : text;
|
|
283
284
|
if (type === 'card' && filteredText) {
|
|
284
285
|
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
|
|
285
286
|
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g');
|
|
@@ -206,7 +206,11 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
|
|
|
206
206
|
|
|
207
207
|
// Sync external value prop to internal state
|
|
208
208
|
_react.default.useEffect(() => {
|
|
209
|
-
if (value
|
|
209
|
+
if (value === '') {
|
|
210
|
+
setCodes(Array(validatorsLength).fill(''));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (Number(value).toString() !== 'NaN') {
|
|
210
214
|
const digits = value.split('').slice(0, validatorsLength);
|
|
211
215
|
const newCodes = Array(validatorsLength).fill('');
|
|
212
216
|
digits.forEach((digit, i) => {
|
|
@@ -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,
|
|
@@ -119,6 +119,7 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
119
119
|
value,
|
|
120
120
|
helpText = '',
|
|
121
121
|
loadingLabel,
|
|
122
|
+
dropdownPosition = 'bottom',
|
|
122
123
|
tokens,
|
|
123
124
|
...rest
|
|
124
125
|
} = _ref2;
|
|
@@ -174,6 +175,13 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
174
175
|
isShown: isExpanded || hintExpansionEnabled,
|
|
175
176
|
offsets: {
|
|
176
177
|
vertical: Platform.OS !== 'web' && (hint || inputLabel) ? 28 : 4
|
|
178
|
+
},
|
|
179
|
+
align: dropdownPosition === 'top' ? {
|
|
180
|
+
center: 'center',
|
|
181
|
+
bottom: 'top'
|
|
182
|
+
} : {
|
|
183
|
+
center: 'center',
|
|
184
|
+
top: 'bottom'
|
|
177
185
|
}
|
|
178
186
|
});
|
|
179
187
|
const targetRef = React.useRef(null);
|
|
@@ -526,6 +534,10 @@ Autocomplete.propTypes = {
|
|
|
526
534
|
/**
|
|
527
535
|
* Input value for controlled usage
|
|
528
536
|
*/
|
|
529
|
-
value: PropTypes.string
|
|
537
|
+
value: PropTypes.string,
|
|
538
|
+
/**
|
|
539
|
+
* Controls whether the dropdown renders above or below the input. Defaults to 'bottom'.
|
|
540
|
+
*/
|
|
541
|
+
dropdownPosition: PropTypes.oneOf(['top', 'bottom'])
|
|
530
542
|
};
|
|
531
543
|
export default Autocomplete;
|
|
@@ -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;
|
|
@@ -13,6 +13,8 @@ import { getPressHandlersWithArgs } from '../utils/pressability';
|
|
|
13
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
14
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
15
15
|
const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
|
|
16
|
+
const EQUAL_WIDTH = 'equal';
|
|
17
|
+
const RESPONSIVE_WIDTH = 'responsive';
|
|
16
18
|
const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
17
19
|
let {
|
|
18
20
|
variant,
|
|
@@ -57,31 +59,90 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
57
59
|
const isMobileNonContained = Platform.OS !== 'web' && (!variant || variant?.style !== 'contained');
|
|
58
60
|
const themeButtonTokensCallback = useThemeTokensCallback('ButtonGroupItem', tokens, variant);
|
|
59
61
|
const gapValue = useSpacingScale(gap || space);
|
|
62
|
+
const isWeb = Platform.OS === 'web';
|
|
63
|
+
const buttonWidthVariant = variant?.width;
|
|
60
64
|
const getButtonTokens = useCallback(state => {
|
|
61
65
|
const themeButtonTokens = themeButtonTokensCallback(state);
|
|
62
66
|
const shouldUseTransparentBackground = isMobileNonContained && !state.selected && !state.pressed && !state.hover && !state.focus;
|
|
67
|
+
let widthStyle;
|
|
68
|
+
switch (buttonWidthVariant) {
|
|
69
|
+
case EQUAL_WIDTH:
|
|
70
|
+
widthStyle = staticStyles.equalWidth;
|
|
71
|
+
break;
|
|
72
|
+
case RESPONSIVE_WIDTH:
|
|
73
|
+
widthStyle = staticStyles.responsiveWidth;
|
|
74
|
+
break;
|
|
75
|
+
// no default
|
|
76
|
+
}
|
|
63
77
|
return {
|
|
64
78
|
...themeButtonTokens,
|
|
65
|
-
...
|
|
79
|
+
...widthStyle,
|
|
66
80
|
...(shouldUseTransparentBackground && {
|
|
67
81
|
backgroundColor: 'transparent'
|
|
68
82
|
}),
|
|
69
83
|
alignSelf: themeButtonTokens.width ? 'flex-start' : 'center'
|
|
70
84
|
};
|
|
71
|
-
}, [themeButtonTokensCallback, isMobileNonContained,
|
|
72
|
-
const fieldsetStyles = useMemo(() =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
}, [themeButtonTokensCallback, isMobileNonContained, buttonWidthVariant]);
|
|
86
|
+
const fieldsetStyles = useMemo(() => {
|
|
87
|
+
let fieldSetBase;
|
|
88
|
+
switch (buttonWidthVariant) {
|
|
89
|
+
case EQUAL_WIDTH:
|
|
90
|
+
case RESPONSIVE_WIDTH:
|
|
91
|
+
fieldSetBase = staticStyles.fieldSetResponsive;
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
fieldSetBase = staticStyles.fieldsetBase;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
...fieldSetBase,
|
|
99
|
+
borderRadius,
|
|
100
|
+
backgroundColor: isMobileNonContained ? 'transparent' : backgroundColor || 'transparent',
|
|
101
|
+
padding
|
|
102
|
+
};
|
|
103
|
+
}, [borderRadius, backgroundColor, padding, isMobileNonContained, buttonWidthVariant]);
|
|
104
|
+
const buttonsContainerStyles = useMemo(() => {
|
|
105
|
+
let flexWrap;
|
|
106
|
+
if (isWeb) {
|
|
107
|
+
flexWrap = 'wrap';
|
|
108
|
+
} else {
|
|
109
|
+
switch (buttonWidthVariant) {
|
|
110
|
+
case EQUAL_WIDTH:
|
|
111
|
+
case RESPONSIVE_WIDTH:
|
|
112
|
+
flexWrap = 'nowrap';
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
flexWrap = 'wrap';
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let justifyContent;
|
|
120
|
+
switch (buttonWidthVariant) {
|
|
121
|
+
case EQUAL_WIDTH:
|
|
122
|
+
justifyContent = 'space-evenly';
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
justifyContent = 'flex-start';
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
let viewBaseStyle;
|
|
129
|
+
switch (buttonWidthVariant) {
|
|
130
|
+
case EQUAL_WIDTH:
|
|
131
|
+
case RESPONSIVE_WIDTH:
|
|
132
|
+
viewBaseStyle = {};
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
viewBaseStyle = staticStyles.viewBase;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...viewBaseStyle,
|
|
140
|
+
flexWrap,
|
|
141
|
+
flexDirection: direction === 'column' ? 'column' : 'row',
|
|
142
|
+
gap: gapValue || 0,
|
|
143
|
+
justifyContent
|
|
144
|
+
};
|
|
145
|
+
}, [direction, gapValue, buttonWidthVariant, isWeb]);
|
|
85
146
|
const {
|
|
86
147
|
currentValues,
|
|
87
148
|
toggleOneValue
|
|
@@ -103,7 +164,7 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
103
164
|
}
|
|
104
165
|
|
|
105
166
|
// Some web screenreaders e.g. MacOS Voiceover don't handle radiogroups properly unless radio is direct child of radiogroup
|
|
106
|
-
const
|
|
167
|
+
const buttonsWrapperRole = Platform.OS === 'web' && accessibilityRole === 'radiogroup' ? accessibilityRole : undefined;
|
|
107
168
|
return /*#__PURE__*/_jsx(Fieldset, {
|
|
108
169
|
...systemProps,
|
|
109
170
|
ref: ref,
|
|
@@ -121,8 +182,8 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
121
182
|
style: fieldsetStyles,
|
|
122
183
|
...selectProps(rest),
|
|
123
184
|
children: /*#__PURE__*/_jsx(View, {
|
|
124
|
-
accessibilityRole:
|
|
125
|
-
style:
|
|
185
|
+
accessibilityRole: buttonsWrapperRole,
|
|
186
|
+
style: buttonsContainerStyles,
|
|
126
187
|
children: items.map((_ref2, index) => {
|
|
127
188
|
let {
|
|
128
189
|
label,
|
|
@@ -290,17 +351,24 @@ ButtonGroup.propTypes = {
|
|
|
290
351
|
copy: PropTypes.oneOf(['en', 'fr'])
|
|
291
352
|
};
|
|
292
353
|
const staticStyles = StyleSheet.create({
|
|
354
|
+
equalWidth: {
|
|
355
|
+
width: '100%',
|
|
356
|
+
flex: 1
|
|
357
|
+
},
|
|
358
|
+
responsiveWidth: {
|
|
359
|
+
width: 'auto'
|
|
360
|
+
},
|
|
293
361
|
fieldsetBase: {
|
|
294
362
|
alignSelf: 'flex-start',
|
|
295
|
-
display: 'inline'
|
|
363
|
+
display: 'inline',
|
|
364
|
+
width: 'auto'
|
|
365
|
+
},
|
|
366
|
+
fieldSetResponsive: {
|
|
367
|
+
width: '100%'
|
|
296
368
|
},
|
|
297
369
|
viewBase: {
|
|
298
370
|
flexWrap: 'wrap',
|
|
299
371
|
alignItems: 'center'
|
|
300
|
-
},
|
|
301
|
-
equalWidth: {
|
|
302
|
-
width: '100%',
|
|
303
|
-
flex: 1
|
|
304
372
|
}
|
|
305
373
|
});
|
|
306
374
|
export default ButtonGroup;
|