@coinbase/cds-web 9.26.1 → 9.27.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 CHANGED
@@ -1,3 +1,24 @@
1
+ ## 9.27.0 (2026-09-18)
2
+
3
+ ### 🚀 Features
4
+
5
+ - Feat: add native-style typeahead keyboard selection to the web `Select (Alpha)`, supporting a multi-character search buffer, repeated-key cycling, and matching both when the listbox is open and closed.
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - Fix: constrain the MCP server's `get-cds-doc` tool to files inside its bundled `mcp-docs` directory. Routes that resolve outside of it are now reported as not found.
10
+
11
+ ## 9.26.2 (2026-09-11)
12
+
13
+ ### 🩹 Fixes
14
+
15
+ - Fix: vertically align the helper-text error icon with the text in web and mobile inputs.
16
+ - Fix: dismiss the Android soft keyboard when the mobile `TextInput` unmounts while focused.
17
+
18
+ ### 🧱 Updated Dependencies
19
+
20
+ - Updated illustrations to 4.48.1
21
+
1
22
  ## 9.26.1 (2026-09-03)
2
23
 
3
24
  ### 🩹 Fixes
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/alpha/select/Select.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAiC,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAgB/F,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,mCAAmC,EACnC,+BAA+B,EAC/B,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,eAAO,MAAM,yBAAyB,EAAE,mBAAmB,CAAC,oBAAoB,CAG/E,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,WAAW,GACjC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG;IACtC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEJ,KAAK,eAAe,GAAG,CACrB,IAAI,SAAS,UAAU,GAAG,QAAQ,EAClC,iBAAiB,SAAS,MAAM,GAAG,MAAM,EAEzC,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;CAAE,KACzE,KAAK,CAAC,YAAY,CAAC;AAoUxB,eAAO,MAAM,MAAM,EAAiB,eAAe,CAAC"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/alpha/select/Select.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAgB/F,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AAIjB,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,mCAAmC,EACnC,+BAA+B,EAC/B,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,eAAO,MAAM,yBAAyB,EAAE,mBAAmB,CAAC,oBAAoB,CAG/E,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,WAAW,GACjC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG;IACtC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEJ,KAAK,eAAe,GAAG,CACrB,IAAI,SAAS,UAAU,GAAG,QAAQ,EAClC,iBAAiB,SAAS,MAAM,GAAG,MAAM,EAEzC,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;CAAE,KACzE,KAAK,CAAC,YAAY,CAAC;AAySxB,eAAO,MAAM,MAAM,EAAiB,eAAe,CAAC"}
@@ -0,0 +1,45 @@
1
+ export declare const TYPEAHEAD_RESET_MS = 500;
2
+ export declare function isPrintableTypeaheadKey(key: string): boolean;
3
+ export declare function isTypeaheadKeyEvent(event: React.KeyboardEvent | KeyboardEvent): boolean;
4
+ export declare function normalizeOptionText(text: string | null | undefined): string;
5
+ export declare function getTypeaheadMatchIndex(
6
+ labels: string[],
7
+ search: string,
8
+ currentIndex: number,
9
+ ): number;
10
+ type ElementRef = {
11
+ current: HTMLElement | null;
12
+ };
13
+ export type UseTypeaheadOptions = {
14
+ /** Whether the items container is visible (open). */
15
+ areItemsVisible: boolean;
16
+ /** Setter used to reveal items when typing while hidden. */
17
+ setAreItemsVisible: (visible: boolean) => void;
18
+ /** Ref to the trigger element. */
19
+ triggerRef: ElementRef;
20
+ /** Ref to the container element that hosts the items. */
21
+ itemsContainerRef: ElementRef;
22
+ /** Role used to query item elements within the container. */
23
+ itemRole: string;
24
+ disabled?: boolean;
25
+ readOnly?: boolean;
26
+ };
27
+ export type UseTypeaheadResult = {
28
+ /** Attach to the control's `onKeyDown`; handles the closed-state type-to-open path. */
29
+ onControlKeyDown: (event: React.KeyboardEvent) => void;
30
+ };
31
+ /**
32
+ * Native `<select>`-style typeahead for the Select (Alpha). Printable keys build a short-lived
33
+ * buffer that focuses the matching option, both while closed (type-to-open) and open.
34
+ */
35
+ export declare function useTypeahead({
36
+ areItemsVisible,
37
+ setAreItemsVisible,
38
+ triggerRef,
39
+ itemsContainerRef,
40
+ itemRole,
41
+ disabled,
42
+ readOnly,
43
+ }: UseTypeaheadOptions): UseTypeaheadResult;
44
+ export {};
45
+ //# sourceMappingURL=useTypeahead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTypeahead.d.ts","sourceRoot":"","sources":["../../../src/alpha/select/useTypeahead.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAItC,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5D;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,GAAG,aAAa,GAAG,OAAO,CAGvF;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE3E;AAID,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EAAE,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,MAAM,CAgBR;AAED,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qDAAqD;IACrD,eAAe,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,kCAAkC;IAClC,UAAU,EAAE,UAAU,CAAC;IACvB,yDAAyD;IACzD,iBAAiB,EAAE,UAAU,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uFAAuF;IACvF,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,mBAAmB,GAAG,kBAAkB,CA+F1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"HelperText.d.ts","sourceRoot":"","sources":["../../src/controls/HelperText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAMjE,OAAO,EAAQ,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEnF,MAAM,MAAM,eAAe,GAAG;IAC5B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;IACxB,sDAAsD;IACtD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,iCAAiC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,MAAM,CAAC,EAAE;QACP,wBAAwB;QACxB,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAC3B,yBAAyB;QACzB,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;KAC5B,CAAC;IACF,6EAA6E;IAC7E,UAAU,CAAC,EAAE;QACX,wBAAwB;QACxB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,yBAAyB;QACzB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,GAAG,SAAS,CAAC,kBAAkB,CAAC,CAAC;AAOlC,eAAO,MAAM,UAAU,6CAoDrB,CAAC"}
1
+ {"version":3,"file":"HelperText.d.ts","sourceRoot":"","sources":["../../src/controls/HelperText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAKjE,OAAO,EAAQ,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEnF,MAAM,MAAM,eAAe,GAAG;IAC5B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;IACxB,sDAAsD;IACtD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,iCAAiC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,MAAM,CAAC,EAAE;QACP,wBAAwB;QACxB,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAC3B,yBAAyB;QACzB,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;KAC5B,CAAC;IACF,6EAA6E;IAC7E,UAAU,CAAC,EAAE;QACX,wBAAwB;QACxB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,yBAAyB;QACzB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,GAAG,SAAS,CAAC,kBAAkB,CAAC,CAAC;AAElC,eAAO,MAAM,UAAU,6CA8DrB,CAAC"}
@@ -3,7 +3,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
3
3
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4
4
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5
5
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
- import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
6
+ import { forwardRef, memo, useImperativeHandle, useMemo, useRef, useState } from 'react';
7
7
  import { autoUpdate, flip, useFloating } from '@floating-ui/react-dom';
8
8
  import { cx } from '../../cx';
9
9
  import { useClickOutside } from '../../hooks/useClickOutside';
@@ -19,6 +19,7 @@ import { DefaultSelectEmptyDropdownContents } from './DefaultSelectEmptyDropdown
19
19
  import { DefaultSelectOption } from './DefaultSelectOption';
20
20
  import { DefaultSelectOptionGroup } from './DefaultSelectOptionGroup';
21
21
  import { defaultSelectSize } from './types';
22
+ import { useTypeahead } from './useTypeahead';
22
23
 
23
24
  // Re-export all types for backward compatibility
24
25
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -29,6 +30,7 @@ export const defaultAccessibilityRoles = {
29
30
  option: 'option'
30
31
  };
31
32
  const SelectBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
33
+ var _accessibilityRoles$o;
32
34
  const mergedProps = useComponentConfig('Select', _props);
33
35
  const {
34
36
  value,
@@ -85,9 +87,7 @@ const SelectBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
85
87
  testID
86
88
  } = mergedProps;
87
89
  const hasMounted = useHasMounted();
88
- // The dropdown keeps a binary density toggle instead of the t-shirt scale, so Select owns
89
- // the translation: only the smallest control size renders a compact dropdown. The control
90
- // still receives the raw `compact` because it needs it for legacy label placement.
90
+ // Dropdown density is binary, so only the smallest size renders compact.
91
91
  const dropdownCompact = (size !== null && size !== void 0 ? size : compact ? 's' : defaultSelectSize) === 's';
92
92
  const [openInternal, setOpenInternal] = useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
93
93
  const open = openProp !== null && openProp !== void 0 ? openProp : openInternal;
@@ -106,34 +106,18 @@ const SelectBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
106
106
  ref: refs.floating,
107
107
  excludeRefs: [refs.reference]
108
108
  });
109
- const pendingTypeAheadKeyRef = useRef(null);
110
- const handleControlKeyDown = useCallback(event => {
111
- if (disabled || readOnly || open) return;
112
- if (event.ctrlKey || event.metaKey || event.altKey) return;
113
- const key = event.key;
114
- if (/^[a-z]$/.test(key)) {
115
- pendingTypeAheadKeyRef.current = key;
116
- setOpen(true);
117
- }
118
- }, [disabled, readOnly, open, setOpen]);
119
- useEffect(() => {
120
- var _accessibilityRoles$o;
121
- if (!open || !pendingTypeAheadKeyRef.current) return;
122
- const key = pendingTypeAheadKeyRef.current;
123
- pendingTypeAheadKeyRef.current = null;
124
- const floatingEl = refs.floating.current;
125
- if (!floatingEl) return;
126
- const optionRole = (_accessibilityRoles$o = accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option) !== null && _accessibilityRoles$o !== void 0 ? _accessibilityRoles$o : 'option';
127
- const options = floatingEl.querySelectorAll("[role=\"".concat(optionRole, "\"]"));
128
- const matchingOption = Array.from(options).find(option => {
129
- var _option$textContent, _firstLetterMatch$;
130
- const firstLetterMatch = (_option$textContent = option.textContent) === null || _option$textContent === void 0 ? void 0 : _option$textContent.match(/[a-z]/i);
131
- return (firstLetterMatch === null || firstLetterMatch === void 0 || (_firstLetterMatch$ = firstLetterMatch[0]) === null || _firstLetterMatch$ === void 0 ? void 0 : _firstLetterMatch$.toLowerCase()) === key;
132
- });
133
- if (matchingOption) {
134
- matchingOption.focus();
135
- }
136
- }, [open, refs.floating, accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option]);
109
+ const optionRole = (_accessibilityRoles$o = accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option) !== null && _accessibilityRoles$o !== void 0 ? _accessibilityRoles$o : 'option';
110
+ const {
111
+ onControlKeyDown
112
+ } = useTypeahead({
113
+ areItemsVisible: open,
114
+ setAreItemsVisible: setOpen,
115
+ triggerRef: refs.reference,
116
+ itemsContainerRef: refs.floating,
117
+ itemRole: optionRole,
118
+ disabled,
119
+ readOnly
120
+ });
137
121
  const rootStyles = useMemo(() => _objectSpread(_objectSpread({}, style), styles === null || styles === void 0 ? void 0 : styles.root), [style, styles === null || styles === void 0 ? void 0 : styles.root]);
138
122
  const controlStyles = useMemo(() => ({
139
123
  controlStartNode: styles === null || styles === void 0 ? void 0 : styles.controlStartNode,
@@ -212,7 +196,7 @@ const SelectBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
212
196
  labelVariant: labelVariant,
213
197
  maxSelectedOptionsToShow: maxSelectedOptionsToShow,
214
198
  onChange: onChange,
215
- onKeyDown: handleControlKeyDown,
199
+ onKeyDown: onControlKeyDown,
216
200
  open: open,
217
201
  options: options,
218
202
  placeholder: placeholder,
@@ -0,0 +1,121 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { getBrowserGlobals } from '../../utils/browser';
3
+
4
+ // ~500ms buffer reset, matching native `<select>`.
5
+ export const TYPEAHEAD_RESET_MS = 500;
6
+ const printableTypeaheadKeyRegex = /^[a-z0-9]$/i;
7
+ export function isPrintableTypeaheadKey(key) {
8
+ return printableTypeaheadKeyRegex.test(key);
9
+ }
10
+
11
+ // Union accepts both React's synthetic event (control handler) and the native event (window listener).
12
+ // Bare printable key, no modifier (shared by closed and open paths).
13
+ export function isTypeaheadKeyEvent(event) {
14
+ if (event.ctrlKey || event.metaKey || event.altKey) return false;
15
+ return isPrintableTypeaheadKey(event.key);
16
+ }
17
+
18
+ // Strip leading non-alphanumerics (e.g. icon/checkbox glyphs) so prefixes match.
19
+ export function normalizeOptionText(text) {
20
+ return (text !== null && text !== void 0 ? text : '').toLowerCase().replace(/^[^a-z0-9]+/, '');
21
+ }
22
+
23
+ // Native `<select>` semantics: a single (or repeated) char cycles options by first letter;
24
+ // a multi-char buffer keeps the focused option while it still prefix-matches.
25
+ export function getTypeaheadMatchIndex(labels, search, currentIndex) {
26
+ const total = labels.length;
27
+ if (!search || total === 0) return -1;
28
+ const isRepeatedChar = search.length > 1 && [...search].every(char => char === search[0]);
29
+ const query = isRepeatedChar ? search[0] : search;
30
+ const startOffset = query.length === 1 ? 1 : 0;
31
+ const startFrom = currentIndex < 0 ? 0 : currentIndex + startOffset;
32
+ for (let i = 0; i < total; i++) {
33
+ const index = (startFrom + i) % total;
34
+ if (labels[index].startsWith(query)) return index;
35
+ }
36
+ return -1;
37
+ }
38
+ /**
39
+ * Native `<select>`-style typeahead for the Select (Alpha). Printable keys build a short-lived
40
+ * buffer that focuses the matching option, both while closed (type-to-open) and open.
41
+ */
42
+ export function useTypeahead(_ref) {
43
+ let {
44
+ areItemsVisible,
45
+ setAreItemsVisible,
46
+ triggerRef,
47
+ itemsContainerRef,
48
+ itemRole,
49
+ disabled,
50
+ readOnly
51
+ } = _ref;
52
+ const typeaheadBufferRef = useRef('');
53
+ const typeaheadResetTimeoutRef = useRef(null);
54
+ const pendingTypeaheadRef = useRef(false);
55
+ const appendToTypeaheadBuffer = useCallback(key => {
56
+ typeaheadBufferRef.current += key.toLowerCase();
57
+ if (typeaheadResetTimeoutRef.current) clearTimeout(typeaheadResetTimeoutRef.current);
58
+ typeaheadResetTimeoutRef.current = setTimeout(() => {
59
+ typeaheadBufferRef.current = '';
60
+ }, TYPEAHEAD_RESET_MS);
61
+ }, []);
62
+ const focusTypeaheadMatch = useCallback(() => {
63
+ var _getBrowserGlobals;
64
+ const search = typeaheadBufferRef.current;
65
+ if (!search) return;
66
+ const containerEl = itemsContainerRef.current;
67
+ if (!containerEl) return;
68
+ const optionElements = Array.from(containerEl.querySelectorAll("[role=\"".concat(itemRole, "\"]"))).filter(option => !option.disabled && option.getAttribute('aria-disabled') !== 'true');
69
+ if (optionElements.length === 0) return;
70
+ const labels = optionElements.map(option => normalizeOptionText(option.textContent));
71
+ const activeElement = (_getBrowserGlobals = getBrowserGlobals()) === null || _getBrowserGlobals === void 0 ? void 0 : _getBrowserGlobals.document.activeElement;
72
+ const currentIndex = activeElement ? optionElements.indexOf(activeElement) : -1;
73
+ const matchIndex = getTypeaheadMatchIndex(labels, search, currentIndex);
74
+ if (matchIndex >= 0) optionElements[matchIndex].focus();
75
+ }, [itemsContainerRef, itemRole]);
76
+ const onControlKeyDown = useCallback(event => {
77
+ // When items are visible, the window listener owns typeahead.
78
+ if (disabled || readOnly || areItemsVisible || !isTypeaheadKeyEvent(event)) return;
79
+ appendToTypeaheadBuffer(event.key);
80
+ pendingTypeaheadRef.current = true;
81
+ setAreItemsVisible(true);
82
+ }, [disabled, readOnly, areItemsVisible, setAreItemsVisible, appendToTypeaheadBuffer]);
83
+
84
+ // After a type-to-open, focus the match once the dropdown (and its options) have rendered.
85
+ useEffect(() => {
86
+ if (!areItemsVisible || !pendingTypeaheadRef.current) return;
87
+ pendingTypeaheadRef.current = false;
88
+ focusTypeaheadMatch();
89
+ }, [areItemsVisible, focusTypeaheadMatch]);
90
+
91
+ // Window listener needed: focus moves into the portaled dropdown, past the control's handler.
92
+ useEffect(() => {
93
+ if (!areItemsVisible || disabled || readOnly) return;
94
+ const globals = getBrowserGlobals();
95
+ if (!globals) return;
96
+ const {
97
+ window: browserWindow,
98
+ document: browserDocument
99
+ } = globals;
100
+ const handleWindowKeyDown = event => {
101
+ if (!isTypeaheadKeyEvent(event)) return;
102
+ const triggerElement = triggerRef.current;
103
+ const containerElement = itemsContainerRef.current;
104
+ const activeElement = browserDocument.activeElement;
105
+ const withinSelect = !!triggerElement && triggerElement.contains(activeElement) || !!containerElement && containerElement.contains(activeElement);
106
+ if (!withinSelect) return;
107
+ appendToTypeaheadBuffer(event.key);
108
+ focusTypeaheadMatch();
109
+ };
110
+ browserWindow.addEventListener('keydown', handleWindowKeyDown);
111
+ // A reset timeout is only ever pending during an open interaction, so clearing it here also
112
+ // covers unmount (no dedicated cleanup-only effect needed).
113
+ return () => {
114
+ browserWindow.removeEventListener('keydown', handleWindowKeyDown);
115
+ if (typeaheadResetTimeoutRef.current) clearTimeout(typeaheadResetTimeoutRef.current);
116
+ };
117
+ }, [areItemsVisible, disabled, readOnly, triggerRef, itemsContainerRef, appendToTypeaheadBuffer, focusTypeaheadMatch]);
118
+ return {
119
+ onControlKeyDown
120
+ };
121
+ }
@@ -1,4 +1,4 @@
1
- const _excluded = ["color", "id", "errorIconAccessibilityLabel", "errorIconTestID", "children", "dangerouslySetColor", "textAlign", "style", "styles", "className", "classNames"];
1
+ const _excluded = ["color", "errorIconAccessibilityLabel", "errorIconTestID", "styles", "classNames"];
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -6,60 +6,64 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
6
6
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
7
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
8
8
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
9
- import React, { memo } from 'react';
9
+ import React, { memo, useMemo } from 'react';
10
10
  import { cx } from '../cx';
11
11
  import { Icon } from '../icons/Icon';
12
- import { Box } from '../layout/Box';
12
+ import { HStack } from '../layout/HStack';
13
13
  import { Text } from '../typography/Text';
14
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
- const iconCss = "iconCss-i1ej5caw";
16
15
  export const HelperText = /*#__PURE__*/memo(function HelperTex(_ref) {
17
16
  let {
18
17
  color,
19
- id,
20
18
  errorIconAccessibilityLabel,
21
19
  errorIconTestID,
22
- children,
23
- dangerouslySetColor,
24
- textAlign = 'start',
25
- style,
26
20
  styles,
27
- className,
28
21
  classNames
29
22
  } = _ref,
30
23
  props = _objectWithoutProperties(_ref, _excluded);
31
- const rootStyle = _objectSpread(_objectSpread({}, style), styles === null || styles === void 0 ? void 0 : styles.root);
24
+ const rootStyle = _objectSpread(_objectSpread({}, props.style), styles === null || styles === void 0 ? void 0 : styles.root);
32
25
  // TODO: when we actually remove dangerouslySetColor:
33
26
  // when migrating from dangerouslySetColor to style.color,
34
27
  // root style/className color will not automatically style the error icon like dangerouslySetColor.
35
28
  // Consumers must set both styles.root and styles.icon (or classNames equivalents).
36
29
  // We need to have a migrator handle this or document in future migration guide.
37
- const iconStyle = styles === null || styles === void 0 ? void 0 : styles.icon;
38
- return /*#__PURE__*/_jsxs(Text, _objectSpread(_objectSpread({
39
- className: cx(className, classNames === null || classNames === void 0 ? void 0 : classNames.root),
40
- color: color,
41
- dangerouslySetColor: dangerouslySetColor,
42
- display: "block",
43
- font: "label2",
44
- id: id,
45
- style: rootStyle,
46
- textAlign: textAlign
47
- }, props), {}, {
48
- children: [color === 'fgNegative' && /*#__PURE__*/_jsx(Box, {
49
- as: "span",
50
- className: iconCss,
51
- children: /*#__PURE__*/_jsx(Icon, {
30
+ const iconStyle = useMemo(() => _objectSpread({
31
+ // Sit on the first line of label2, matching nested-text alignment.
32
+ marginTop: 'calc((var(--lineHeight-label2) - var(--iconSize-xs)) / 2)'
33
+ }, styles === null || styles === void 0 ? void 0 : styles.icon), [styles === null || styles === void 0 ? void 0 : styles.icon]);
34
+ const justifyContent = props.textAlign === 'end' ? 'flex-end' : props.textAlign === 'center' ? 'center' : 'flex-start';
35
+ if (color === 'fgNegative') {
36
+ return /*#__PURE__*/_jsxs(HStack, {
37
+ alignItems: "flex-start",
38
+ gap: 0.5,
39
+ justifyContent: justifyContent,
40
+ children: [/*#__PURE__*/_jsx(Icon, {
52
41
  active: true,
53
42
  accessibilityLabel: errorIconAccessibilityLabel,
54
43
  className: classNames === null || classNames === void 0 ? void 0 : classNames.icon,
55
44
  color: "fgNegative",
56
- dangerouslySetColor: dangerouslySetColor,
45
+ dangerouslySetColor: props.dangerouslySetColor,
57
46
  name: "info",
58
47
  size: "xs",
59
48
  style: iconStyle,
60
49
  testID: errorIconTestID
61
- })
62
- }), children]
50
+ }), /*#__PURE__*/_jsx(Text, _objectSpread(_objectSpread({
51
+ color: color,
52
+ display: "block",
53
+ font: "label2"
54
+ }, props), {}, {
55
+ className: cx(props.className, classNames === null || classNames === void 0 ? void 0 : classNames.root),
56
+ flexShrink: 1,
57
+ style: rootStyle
58
+ }))]
59
+ });
60
+ }
61
+ return /*#__PURE__*/_jsx(Text, _objectSpread(_objectSpread({
62
+ color: color,
63
+ display: "block",
64
+ font: "label2"
65
+ }, props), {}, {
66
+ className: cx(props.className, classNames === null || classNames === void 0 ? void 0 : classNames.root),
67
+ style: rootStyle
63
68
  }));
64
- });
65
- import "./HelperText.css";
69
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-web",
3
- "version": "9.26.1",
3
+ "version": "9.27.0",
4
4
  "description": "Coinbase Design System - Web",
5
5
  "repository": {
6
6
  "type": "git",
@@ -227,9 +227,9 @@
227
227
  "react-dom": "^18.0.0 || ~19.1.2"
228
228
  },
229
229
  "dependencies": {
230
- "@coinbase/cds-common": "^9.26.1",
231
- "@coinbase/cds-icons": "^5.22.0",
232
- "@coinbase/cds-illustrations": "^4.48.0",
230
+ "@coinbase/cds-common": "^9.27.0",
231
+ "@coinbase/cds-icons": "^5.25.0",
232
+ "@coinbase/cds-illustrations": "^4.48.1",
233
233
  "@coinbase/cds-lottie-files": "^3.3.4",
234
234
  "@coinbase/cds-utils": "^2.3.5",
235
235
  "@floating-ui/react-dom": "^2.1.1",
@@ -1 +0,0 @@
1
- @layer cds{.iconCss-i1ej5caw{display:inline-block;padding-inline-end:var(--space-0_5);}}