@telus-uds/components-base 3.28.1 → 3.29.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/lib/cjs/Autocomplete/Autocomplete.js +86 -32
  3. package/lib/cjs/Autocomplete/constants.js +2 -1
  4. package/lib/cjs/Card/CardBase.js +12 -0
  5. package/lib/cjs/Carousel/Carousel.js +1 -2
  6. package/lib/cjs/ColourToggle/ColourBubble.js +17 -3
  7. package/lib/cjs/ColourToggle/ColourToggle.js +8 -2
  8. package/lib/cjs/ExpandCollapse/Control.js +17 -3
  9. package/lib/cjs/ExpandCollapse/Panel.js +6 -0
  10. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
  11. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
  12. package/lib/cjs/Link/ChevronLink.js +1 -0
  13. package/lib/cjs/Link/LinkBase.js +29 -13
  14. package/lib/cjs/Link/MobileIconTextContent.js +156 -0
  15. package/lib/cjs/Listbox/ListboxOverlay.js +7 -1
  16. package/lib/cjs/Listbox/PressableItem.js +2 -2
  17. package/lib/cjs/TabBar/TabBar.js +7 -2
  18. package/lib/cjs/TextInput/TextInputBase.js +2 -2
  19. package/lib/esm/Autocomplete/Autocomplete.js +87 -33
  20. package/lib/esm/Autocomplete/constants.js +1 -0
  21. package/lib/esm/Card/CardBase.js +12 -0
  22. package/lib/esm/Carousel/Carousel.js +1 -2
  23. package/lib/esm/ColourToggle/ColourBubble.js +17 -3
  24. package/lib/esm/ColourToggle/ColourToggle.js +8 -2
  25. package/lib/esm/ExpandCollapse/Control.js +17 -3
  26. package/lib/esm/ExpandCollapse/Panel.js +6 -0
  27. package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
  28. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
  29. package/lib/esm/Link/ChevronLink.js +1 -0
  30. package/lib/esm/Link/LinkBase.js +29 -13
  31. package/lib/esm/Link/MobileIconTextContent.js +147 -0
  32. package/lib/esm/Listbox/ListboxOverlay.js +7 -1
  33. package/lib/esm/Listbox/PressableItem.js +3 -3
  34. package/lib/esm/TabBar/TabBar.js +7 -2
  35. package/lib/esm/TextInput/TextInputBase.js +2 -2
  36. package/lib/package.json +1 -1
  37. package/package.json +1 -1
  38. package/src/Autocomplete/Autocomplete.jsx +142 -77
  39. package/src/Autocomplete/constants.js +1 -0
  40. package/src/Card/CardBase.jsx +12 -0
  41. package/src/Carousel/Carousel.jsx +1 -2
  42. package/src/ColourToggle/ColourBubble.jsx +18 -3
  43. package/src/ColourToggle/ColourToggle.jsx +7 -2
  44. package/src/ExpandCollapse/Control.jsx +24 -4
  45. package/src/ExpandCollapse/Panel.jsx +6 -0
  46. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +23 -3
  47. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +14 -2
  48. package/src/Link/ChevronLink.jsx +1 -0
  49. package/src/Link/LinkBase.jsx +47 -20
  50. package/src/Link/MobileIconTextContent.jsx +129 -0
  51. package/src/Listbox/ListboxOverlay.jsx +9 -1
  52. package/src/Listbox/PressableItem.jsx +1 -1
  53. package/src/TabBar/TabBar.jsx +21 -4
  54. package/src/TextInput/TextInputBase.jsx +2 -2
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _propTypes = _interopRequireDefault(require("prop-types"));
9
+ var _Text = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Text"));
10
+ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
11
+ var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
12
+ var _Icon = _interopRequireWildcard(require("../Icon/Icon"));
13
+ var _utils = require("../utils");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
16
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
17
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
+ const MobileIconTextContent = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
19
+ let {
20
+ space = 0,
21
+ iconPosition = 'left',
22
+ icon: IconComponent,
23
+ iconProps = {},
24
+ children
25
+ } = _ref;
26
+ const [translateY, setTranslateY] = _react.default.useState(0);
27
+ const latestTranslateYRef = _react.default.useRef(0);
28
+ const layoutsRef = _react.default.useRef({
29
+ container: null,
30
+ text: null,
31
+ icon: null
32
+ });
33
+ const applyAlignment = _react.default.useCallback(() => {
34
+ const {
35
+ container,
36
+ text,
37
+ icon
38
+ } = layoutsRef.current;
39
+ if (!container || !icon || !icon.height) return;
40
+ const targetY = text ? text.y + text.height / 2 : container.height / 2;
41
+ const iconY = icon.y + icon.height / 2;
42
+ const nextTranslateY = Math.round((targetY - iconY) * 100) / 100;
43
+ if (!Number.isFinite(nextTranslateY)) return;
44
+ if (Math.abs(nextTranslateY - latestTranslateYRef.current) < 0.5) return;
45
+ latestTranslateYRef.current = nextTranslateY;
46
+ setTranslateY(nextTranslateY);
47
+ }, []);
48
+ const handleContainerLayout = _react.default.useCallback(_ref2 => {
49
+ let {
50
+ nativeEvent: {
51
+ layout
52
+ }
53
+ } = _ref2;
54
+ layoutsRef.current.container = layout;
55
+ applyAlignment();
56
+ }, [applyAlignment]);
57
+ const handleTextLayout = _react.default.useCallback(_ref3 => {
58
+ let {
59
+ nativeEvent: {
60
+ layout
61
+ }
62
+ } = _ref3;
63
+ layoutsRef.current.text = layout;
64
+ applyAlignment();
65
+ }, [applyAlignment]);
66
+ const handleIconLayout = _react.default.useCallback(_ref4 => {
67
+ let {
68
+ nativeEvent: {
69
+ layout
70
+ }
71
+ } = _ref4;
72
+ layoutsRef.current.icon = layout;
73
+ applyAlignment();
74
+ }, [applyAlignment]);
75
+ const iconContent = IconComponent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
76
+ ref: ref,
77
+ icon: IconComponent,
78
+ scalesWithText: true,
79
+ ...iconProps
80
+ }) : null;
81
+ const iconWrapper = IconComponent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
82
+ onLayout: handleIconLayout,
83
+ style: [staticStyles.iconContainer, {
84
+ transform: [{
85
+ translateY
86
+ }]
87
+ }],
88
+ children: iconContent
89
+ }) : null;
90
+ if (iconPosition === 'inline') {
91
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Text.default, {
92
+ onLayout: handleContainerLayout,
93
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.default, {
94
+ onLayout: handleTextLayout,
95
+ children: children
96
+ }), ' ', /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
97
+ style: staticStyles.inlineIconContainer,
98
+ children: iconWrapper
99
+ })]
100
+ });
101
+ }
102
+ const iconSpaceStyle = iconPosition === 'left' ? {
103
+ marginRight: space
104
+ } : {
105
+ marginLeft: space
106
+ };
107
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_View.default, {
108
+ onLayout: handleContainerLayout,
109
+ style: staticStyles.rowContainer,
110
+ children: [iconPosition === 'left' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
111
+ style: iconSpaceStyle,
112
+ children: iconWrapper
113
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
114
+ onLayout: handleTextLayout,
115
+ children: children
116
+ }), iconPosition === 'right' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
117
+ style: iconSpaceStyle,
118
+ children: iconWrapper
119
+ })]
120
+ });
121
+ });
122
+ MobileIconTextContent.displayName = 'MobileIconTextContent';
123
+ MobileIconTextContent.propTypes = {
124
+ /**
125
+ * Amount of space between text and icon. Uses the theme spacing scale.
126
+ */
127
+ space: _utils.spacingProps.types.spacingValue,
128
+ /**
129
+ * Position of the icon relative to text.
130
+ */
131
+ iconPosition: _propTypes.default.oneOf(['left', 'right', 'inline']),
132
+ /**
133
+ * A valid UDS icon component imported from a UDS palette.
134
+ */
135
+ icon: _propTypes.default.elementType,
136
+ /**
137
+ * Props passed to the icon component.
138
+ */
139
+ iconProps: _propTypes.default.exact(_Icon.iconComponentPropTypes),
140
+ /**
141
+ * Content rendered alongside the icon.
142
+ */
143
+ children: _propTypes.default.node
144
+ };
145
+ const staticStyles = _StyleSheet.default.create({
146
+ rowContainer: {
147
+ flexDirection: 'row'
148
+ },
149
+ iconContainer: {
150
+ alignSelf: 'flex-start'
151
+ },
152
+ inlineIconContainer: {
153
+ position: 'absolute'
154
+ }
155
+ });
156
+ var _default = exports.default = MobileIconTextContent;
@@ -35,6 +35,7 @@ const DropdownOverlay = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
35
35
  isReady = false,
36
36
  overlaidPosition,
37
37
  maxWidth,
38
+ maxHeight,
38
39
  minWidth,
39
40
  onLayout,
40
41
  tokens,
@@ -50,12 +51,16 @@ const DropdownOverlay = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
50
51
  maxWidth,
51
52
  minWidth
52
53
  }, staticStyles.positioner, !isReady && staticStyles.hidden],
54
+ onMouseDown: e => {
55
+ e.preventDefault();
56
+ },
53
57
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Card.default, {
54
58
  tokens: {
55
59
  shadow: systemTokens.shadow,
56
60
  borderRadius: systemTokens.borderRadius,
57
61
  ...(_Platform.default.OS === 'web' && {
58
- overflowY: 'hidden'
62
+ maxHeight,
63
+ overflowY: 'auto'
59
64
  }),
60
65
  paddingBottom: paddingVertical,
61
66
  paddingTop: paddingVertical,
@@ -85,6 +90,7 @@ DropdownOverlay.propTypes = {
85
90
  width: _propTypes.default.number
86
91
  }),
87
92
  maxWidth: _propTypes.default.number,
93
+ maxHeight: _propTypes.default.number,
88
94
  minWidth: _propTypes.default.number,
89
95
  onLayout: _propTypes.default.func,
90
96
  tokens: _propTypes.default.object,
@@ -129,9 +129,9 @@ const PressableItem = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
129
129
  onPress(event);
130
130
  },
131
131
  children: pressableState => {
132
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Text.default, {
132
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.default, {
133
133
  style: selectTextStyles(resolveButtonTokens(pressableState)),
134
- children: [children, " "]
134
+ children: children
135
135
  });
136
136
  }
137
137
  });
@@ -58,6 +58,7 @@ const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_u
58
58
  * items={items}
59
59
  * initiallySelectedItem="1"
60
60
  * onChange={(itemId) => console.log(itemId)}
61
+ * accessibilityLabel="Main navigation"
61
62
  * />
62
63
  * )
63
64
  */
@@ -69,6 +70,7 @@ const TabBar = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
69
70
  onChange,
70
71
  variant,
71
72
  tokens,
73
+ accessibilityLabel,
72
74
  ...rest
73
75
  } = _ref3;
74
76
  const [isSelected, setIsSelected] = _react.default.useState(initiallySelectedItem);
@@ -83,6 +85,8 @@ const TabBar = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
83
85
  ...selectProps(rest),
84
86
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
85
87
  style: [styles.tabBarItem, selectTabBarItemContainerStyles(themeTokens)],
88
+ accessibilityRole: "tablist",
89
+ accessibilityLabel: accessibilityLabel,
86
90
  children: items.map((item, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_TabBarItem.default, {
87
91
  label: item.label,
88
92
  href: item.href,
@@ -91,7 +95,6 @@ const TabBar = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
91
95
  iconActive: item.iconActive,
92
96
  onPress: () => handlePress(item.id),
93
97
  id: `tab-item-${index}`,
94
- accessibilityRole: "tablist",
95
98
  tokens: item.tokens
96
99
  }, item.id))
97
100
  })
@@ -116,7 +119,9 @@ TabBar.propTypes = {
116
119
  /** Variant of TabBar for styling purposes. */
117
120
  variant: _utils.variantProp.propType,
118
121
  /** Tokens for theming and styling. */
119
- tokens: (0, _utils.getTokensPropType)('TabBar')
122
+ tokens: (0, _utils.getTokensPropType)('TabBar'),
123
+ /** Accessible label for the tab bar navigation region, used by screen readers to identify the tablist. */
124
+ accessibilityLabel: _propTypes.default.string
120
125
  };
121
126
  const styles = _StyleSheet.default.create({
122
127
  tabBar: {
@@ -278,8 +278,8 @@ const TextInputBase = /*#__PURE__*/_react.default.forwardRef((_ref8, ref) => {
278
278
  }
279
279
  }, [element, pattern]);
280
280
  const handleChangeText = event => {
281
- const text = event.nativeEvent?.text || event.target?.value;
282
- let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') : text;
281
+ const text = event.nativeEvent?.text ?? event.target?.value;
282
+ let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') || undefined : text;
283
283
  if (type === 'card' && filteredText) {
284
284
  const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
285
285
  const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g');
@@ -14,7 +14,7 @@ import { TextInput } from '../TextInput';
14
14
  import InputSupports from '../InputSupports';
15
15
  import Loading from './Loading';
16
16
  import Suggestions from './Suggestions';
17
- import { DEFAULT_MAX_SUGGESTIONS, DEFAULT_MIN_TO_SUGGESTION, INPUT_LEFT_PADDING, MIN_LISTBOX_WIDTH } from './constants';
17
+ import { DEFAULT_MAX_SUGGESTIONS, DEFAULT_MIN_TO_SUGGESTION, DEFAULT_MAX_DROPDOWN_HEIGHT, INPUT_LEFT_PADDING, MIN_LISTBOX_WIDTH } from './constants';
18
18
  import dictionary from './dictionary';
19
19
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  const staticStyles = StyleSheet.create({
@@ -44,22 +44,36 @@ const highlightAllMatches = function (str) {
44
44
  tokens: {
45
45
  color: resultsTextColor
46
46
  },
47
- children: matchIndexes.reduce((acc, matchIndex, index) => [...acc,
48
- // Add a piece of the string up to the first occurrence of the substring
49
- index === 0 && (str.slice(0, matchIndex) ?? ''),
50
- /*#__PURE__*/
51
- // Unbold the occurrence of the substring (while keeping the original casing)
52
- _jsx(Typography, {
53
- variant: {
54
- bold: true
55
- },
56
- tokens: {
57
- color: resultsTextColor
58
- },
59
- children: str.slice(matchIndex, matchIndex + substring.length)
60
- }, matchIndex),
61
- // Add the rest of the string until the next occurrence or the end of it
62
- str.slice(matchIndex + substring.length, matchIndexes[index + 1] ?? str.length)], [])
47
+ children: matchIndexes.reduce((acc, matchIndex, index) => {
48
+ const prefix = index === 0 ? str.slice(0, matchIndex) : null;
49
+ const match = str.slice(matchIndex, matchIndex + substring.length);
50
+ const suffix = str.slice(matchIndex + substring.length, matchIndexes[index + 1] ?? str.length);
51
+ return [...acc, prefix ? /*#__PURE__*/_jsx(Typography, {
52
+ variant: {
53
+ bold: false
54
+ },
55
+ tokens: {
56
+ color: resultsTextColor
57
+ },
58
+ children: prefix
59
+ }, `pre-${matchIndex}`) : null, /*#__PURE__*/_jsx(Typography, {
60
+ variant: {
61
+ bold: true
62
+ },
63
+ tokens: {
64
+ color: resultsTextColor
65
+ },
66
+ children: match
67
+ }, matchIndex), suffix ? /*#__PURE__*/_jsx(Typography, {
68
+ variant: {
69
+ bold: false
70
+ },
71
+ tokens: {
72
+ color: resultsTextColor
73
+ },
74
+ children: suffix
75
+ }, `post-${matchIndex}`) : null];
76
+ }, []).filter(Boolean)
63
77
  })
64
78
  );
65
79
  };
@@ -93,12 +107,14 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
93
107
  isLoading = false,
94
108
  items,
95
109
  maxSuggestions = DEFAULT_MAX_SUGGESTIONS,
110
+ maxDropdownHeight = DEFAULT_MAX_DROPDOWN_HEIGHT,
96
111
  minToSuggestion = DEFAULT_MIN_TO_SUGGESTION,
97
112
  noResults,
98
113
  onChange,
99
114
  onClear,
100
115
  onSelect,
101
116
  readOnly,
117
+ showOptionsOnFocus = false,
102
118
  validation,
103
119
  value,
104
120
  helpText = '',
@@ -147,7 +163,7 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
147
163
  hint,
148
164
  label: inputLabel
149
165
  } = supportsProps;
150
- const hintExpansionEnabled = isFocused && helpText && !currentValue;
166
+ const hintExpansionEnabled = isFocused && !!helpText && !currentValue;
151
167
  const {
152
168
  overlaidPosition,
153
169
  sourceRef: inputRef,
@@ -201,9 +217,10 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
201
217
  }
202
218
  };
203
219
  const handleChange = newValue => {
204
- onChange?.(newValue || '');
220
+ onChange?.(newValue);
205
221
  setCurrentValue(newValue);
206
- setIsExpanded(newValue?.length >= minToSuggestion);
222
+ const shouldExpand = newValue?.length >= minToSuggestion || showOptionsOnFocus && isFocused && newValue?.length === 0;
223
+ setIsExpanded(shouldExpand);
207
224
  if (!isControlled && initialItems !== undefined) {
208
225
  setCurrentItems(initialItems.filter(_ref3 => {
209
226
  let {
@@ -215,25 +232,29 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
215
232
  };
216
233
  const handleSelect = selectedId => {
217
234
  onSelect?.(selectedId);
218
- const {
219
- label: newValue,
220
- nested,
221
- title
222
- } = (isControlled ? items : currentItems)?.find(_ref4 => {
235
+ const selectedItem = (isControlled ? items : currentItems)?.find(_ref4 => {
223
236
  let {
224
237
  id
225
238
  } = _ref4;
226
239
  return id === selectedId;
227
240
  });
241
+ const {
242
+ label,
243
+ nested,
244
+ title
245
+ } = selectedItem;
228
246
  if (title) return;
229
247
  if (!nested) {
230
248
  setNestedSelectedValue(null);
231
- onChange?.(newValue);
249
+ onChange?.(label);
232
250
  setIsExpanded(false);
251
+ setCurrentValue(label);
252
+ }
253
+ if (!isControlled && inputRef?.current) inputRef.current.value = label;
254
+ if (nested) {
255
+ setNestedSelectedValue(label);
256
+ setCurrentValue(label);
233
257
  }
234
- setCurrentValue(newValue);
235
- if (!isControlled && inputRef?.current) inputRef.current.value = newValue;
236
- if (nested) setNestedSelectedValue(newValue);
237
258
  inputRef.current.focus();
238
259
  };
239
260
 
@@ -281,15 +302,33 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
281
302
  };
282
303
  }, [inputRef]);
283
304
  const handleClose = event => {
284
- if (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27') || event.type === 'click' && !openOverlayRef?.current?.contains(event.target) || event.type === 'touchstart' && openOverlayRef?.current && event.touches[0].target && !openOverlayRef?.current?.contains(event.touches[0].target)) {
305
+ if (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) {
285
306
  setIsExpanded(false);
286
307
  setNestedSelectedValue(null);
287
- } else if (event.type === 'keydown' && (event.key === 'ArrowDown' || event.key === 'Tab') && isExpanded && !isLoading && targetRef?.current) {
308
+ return;
309
+ }
310
+ if (event.type === 'keydown' && (event.key === 'ArrowDown' || event.key === 'Tab') && isExpanded && !isLoading && targetRef?.current) {
288
311
  event.preventDefault();
289
312
  targetRef.current.focus();
313
+ return;
314
+ }
315
+ if (event.type === 'click' || event.type === 'touchstart') {
316
+ const clickTarget = event.type === 'click' ? event.target : event.touches[0]?.target;
317
+ const isOutsideOverlay = openOverlayRef?.current && !openOverlayRef.current.contains(clickTarget);
318
+ const isOutsideInput = inputRef?.current && !inputRef.current.contains(clickTarget);
319
+ if (isOutsideOverlay && isOutsideInput) {
320
+ setIsExpanded(false);
321
+ setNestedSelectedValue(null);
322
+ }
290
323
  }
291
324
  };
292
- const itemsToShow = currentValue ? itemsToSuggest(highlight(isControlled ? items : currentItems, currentValue, resultsTextColor)) : [];
325
+ // Calculate items to show based on current state
326
+ let itemsToShow = [];
327
+ if (currentValue?.length > 0) {
328
+ itemsToShow = itemsToSuggest(highlight(isControlled ? items : currentItems, currentValue, resultsTextColor));
329
+ } else if (showOptionsOnFocus && isFocused) {
330
+ itemsToShow = itemsToSuggest(isControlled ? items : currentItems || initialItems);
331
+ }
293
332
  const helpTextToShow = isFocused && !currentValue ? helpText : noResults ?? getCopy('noResults');
294
333
  return /*#__PURE__*/_jsxs(View, {
295
334
  style: staticStyles.container,
@@ -326,9 +365,15 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
326
365
  onChange: handleChange,
327
366
  onFocus: () => {
328
367
  setisFocused(true);
368
+ if (showOptionsOnFocus && (!currentValue || currentValue.length === 0)) {
369
+ setIsExpanded(true);
370
+ }
329
371
  },
330
372
  onBlur: () => {
331
373
  setisFocused(false);
374
+ if (showOptionsOnFocus && (!currentValue || currentValue.length === 0)) {
375
+ setIsExpanded(false);
376
+ }
332
377
  },
333
378
  onClear: onClear,
334
379
  onKeyPress: handleClose,
@@ -360,12 +405,13 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
360
405
  })
361
406
  });
362
407
  }
363
- }), (isExpanded || hintExpansionEnabled) && isInputVisible && /*#__PURE__*/_jsxs(_Fragment, {
408
+ }), (isExpanded || hintExpansionEnabled) && isInputVisible && (itemsToShow.length > 0 || isExpanded || hintExpansionEnabled) && /*#__PURE__*/_jsxs(_Fragment, {
364
409
  children: [/*#__PURE__*/_jsx(Listbox.Overlay, {
365
410
  overlaidPosition: overlaidPosition,
366
411
  isReady: isReady,
367
412
  minWidth: fullWidth ? inputWidth : MIN_LISTBOX_WIDTH,
368
413
  maxWidth: inputWidth,
414
+ maxHeight: maxDropdownHeight,
369
415
  onLayout: handleMeasure,
370
416
  tokens: restOfTokens,
371
417
  ref: openOverlayRef,
@@ -449,6 +495,10 @@ Autocomplete.propTypes = {
449
495
  * Maximum number of suggestions provided at the same time
450
496
  */
451
497
  maxSuggestions: PropTypes.number,
498
+ /**
499
+ * Maximum height (in pixels) of the dropdown before scrolling is enabled
500
+ */
501
+ maxDropdownHeight: PropTypes.number,
452
502
  /**
453
503
  * Text or JSX to render when no results are available
454
504
  */
@@ -469,6 +519,10 @@ Autocomplete.propTypes = {
469
519
  * Callback function to be called when an item is selected from the list
470
520
  */
471
521
  onSelect: PropTypes.func,
522
+ /**
523
+ * When true, displays all available options when the input receives focus (even without typing)
524
+ */
525
+ showOptionsOnFocus: PropTypes.bool,
472
526
  /**
473
527
  * Input value for controlled usage
474
528
  */
@@ -1,4 +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 = 336; // Approximately 7 items (48px each)
3
4
  export const INPUT_LEFT_PADDING = 16;
4
5
  export const MIN_LISTBOX_WIDTH = 288;
@@ -57,9 +57,21 @@ const setBackgroundImage = _ref => {
57
57
  case 'left-center':
58
58
  backgroundPosition = 'left center';
59
59
  break;
60
+ case 'left-start':
61
+ backgroundPosition = 'left top';
62
+ break;
63
+ case 'left-end':
64
+ backgroundPosition = 'left bottom';
65
+ break;
60
66
  case 'right-center':
61
67
  backgroundPosition = 'right center';
62
68
  break;
69
+ case 'right-start':
70
+ backgroundPosition = 'right top';
71
+ break;
72
+ case 'right-end':
73
+ backgroundPosition = 'right bottom';
74
+ break;
63
75
  default:
64
76
  backgroundPosition = 'center center';
65
77
  }
@@ -917,8 +917,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
917
917
  // Related discussion - https://github.com/telus/universal-design-system/issues/1549
918
918
  const previousNextIconButtonVariants = {
919
919
  size: previousNextIconSize,
920
- raised: !variant?.inverse && true,
921
- inverse: variant?.inverse
920
+ raised: true
922
921
  };
923
922
  const getCopyWithPlaceholders = React.useCallback(copyKey => {
924
923
  const copyText = getCopy(copyKey).replace(/%\{title\}/g, title).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, totalItems);
@@ -6,6 +6,7 @@ import Platform from "react-native-web/dist/exports/Platform";
6
6
  import { resolvePressableTokens } from '../utils/pressability';
7
7
  import { applyShadowToken } from '../ThemeProvider';
8
8
  import { getTokensPropType } from '../utils';
9
+ import Tooltip from '../Tooltip';
9
10
  import { jsx as _jsx } from "react/jsx-runtime";
10
11
  const selectGeneralBubbleTokens = _ref => {
11
12
  let {
@@ -69,14 +70,15 @@ const ColourBubble = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
69
70
  colourHexCode,
70
71
  colourName,
71
72
  isSelected,
72
- onPress
73
+ onPress,
74
+ showTooltip
73
75
  } = _ref4;
74
76
  const defaultTokens = tokens({
75
77
  selected: isSelected
76
78
  });
77
79
  const resolveColourBubbleTokens = pressState => resolvePressableTokens(tokens, pressState, {});
78
80
  const themeTokens = React.useMemo(() => tokens(), [tokens]);
79
- return /*#__PURE__*/_jsx(Pressable, {
81
+ const pressable = /*#__PURE__*/_jsx(Pressable, {
80
82
  style: state => [selectGeneralBubbleTokens(resolveColourBubbleTokens(state)), isSelected && selectBorderBubbleTokens(defaultTokens)],
81
83
  onPress: onPress,
82
84
  accessible: true,
@@ -93,6 +95,14 @@ const ColourBubble = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
93
95
  }]
94
96
  })
95
97
  });
98
+ if (showTooltip) {
99
+ return /*#__PURE__*/_jsx(Tooltip, {
100
+ content: colourName,
101
+ activateOnHover: true,
102
+ children: pressable
103
+ });
104
+ }
105
+ return pressable;
96
106
  });
97
107
  ColourBubble.displayName = 'ColourBubble';
98
108
  ColourBubble.propTypes = {
@@ -121,6 +131,10 @@ ColourBubble.propTypes = {
121
131
  * of the color is changed of all currently `items`.
122
132
  * Receives two parameters: item object selected and the event
123
133
  */
124
- onPress: PropTypes.func
134
+ onPress: PropTypes.func,
135
+ /**
136
+ * When true, wraps the bubble in a Tooltip that displays the colourName on hover (web only).
137
+ */
138
+ showTooltip: PropTypes.bool
125
139
  };
126
140
  export default ColourBubble;
@@ -15,6 +15,7 @@ const ColourToggle = /*#__PURE__*/React.forwardRef((_ref, ref) => {
15
15
  defaultColourId,
16
16
  items,
17
17
  onChange,
18
+ showTooltips,
18
19
  ...rest
19
20
  } = _ref;
20
21
  const [currentColourId, setCurrentColourId] = React.useState(defaultColourId);
@@ -54,7 +55,8 @@ const ColourToggle = /*#__PURE__*/React.forwardRef((_ref, ref) => {
54
55
  isSelected: id === currentColourId,
55
56
  colourHexCode: colourHexCode,
56
57
  colourName: colourName,
57
- onPress: handleChangeColour
58
+ onPress: handleChangeColour,
59
+ showTooltip: showTooltips
58
60
  }, colourBubbleId);
59
61
  })
60
62
  })]
@@ -86,6 +88,10 @@ ColourToggle.propTypes = {
86
88
  /**
87
89
  * 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
88
90
  */
89
- onChange: PropTypes.func
91
+ onChange: PropTypes.func,
92
+ /**
93
+ * When true, displays each colour's name as a tooltip on hover (web only).
94
+ */
95
+ showTooltips: PropTypes.bool
90
96
  };
91
97
  export default ColourToggle;
@@ -66,11 +66,15 @@ function selectIconContainerStyles(_ref2) {
66
66
  }
67
67
  function selectTextContainerStyles(_ref3) {
68
68
  let {
69
- textLine
69
+ textLine,
70
+ controlAlign
70
71
  } = _ref3;
71
72
  return {
72
73
  textDecorationLine: textLine,
73
- flex: 1
74
+ flex: 1,
75
+ ...(controlAlign && {
76
+ alignItems: controlAlign
77
+ })
74
78
  };
75
79
  }
76
80
  function selectIconTokens(tokens) {
@@ -85,6 +89,7 @@ const ExpandCollapseControl = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
85
89
  isExpanded,
86
90
  children,
87
91
  tokens,
92
+ controlAlign,
88
93
  accessibilityRole = 'button',
89
94
  variant,
90
95
  ...rest
@@ -135,7 +140,12 @@ const ExpandCollapseControl = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
135
140
  ...selectIconTokens(themeTokens)
136
141
  })
137
142
  }), /*#__PURE__*/_jsx(View, {
138
- style: [selectTextContainerStyles(themeTokens), staticStyles.bubblePointerEvents],
143
+ style: [selectTextContainerStyles({
144
+ ...themeTokens,
145
+ ...(controlAlign && {
146
+ controlAlign
147
+ })
148
+ }), staticStyles.bubblePointerEvents],
139
149
  children: typeof children === 'function' ? children(getControlState(pressableState)) : children
140
150
  })]
141
151
  });
@@ -161,6 +171,10 @@ ExpandCollapseControl.propTypes = {
161
171
  * Whether the linked ExpandCollapsePanel is opened or closed. Allows themes to set `expanded` styles.
162
172
  */
163
173
  isExpanded: PropTypes.bool,
174
+ /**
175
+ * Optional alignment for control content. Overrides token-driven alignment.
176
+ */
177
+ controlAlign: PropTypes.oneOf(['flex-start', 'center', 'flex-end']),
164
178
  /**
165
179
  * Function called when the ExpandCollapse is pressed.
166
180
  */