braid-design-system 34.6.1 → 34.6.2

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,5 +1,13 @@
1
1
  # braid-design-system
2
2
 
3
+ ## 34.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - **Autosuggest:** Add `translate="no"` to the suggestion list and the ARIA live announcement region to prevent browser translations from wrapping text nodes in `<font>` elements, which breaks React DOM updates. Also key suggestion rows by index instead of `index + text`, so locale changes to suggestion labels no longer remount the list, and numeric text can no longer collide keys via string concatenation. ([#2117](https://github.com/seek-oss/braid-design-system/pull/2117))
8
+
9
+ - **Autosuggest:** Allow the suggestions list to flip above the field when there is insufficient space below, and keep keyboard highlight scrolling inside the menu so it no longer jumps the page ([#2113](https://github.com/seek-oss/braid-design-system/pull/2113))
10
+
3
11
  ## 34.6.1
4
12
 
5
13
  ### Patch Changes
@@ -43,6 +43,6 @@ declare const colorModeQueryParamCheck: string;
43
43
  *
44
44
  * @returns Braid color mode preference as query string parameter
45
45
  */
46
- declare const getColorModeQueryParam: () => "" | "_bdsdm=1" | "_bdsdm=2" | "_bdsdm=0";
46
+ declare const getColorModeQueryParam: () => "_bdsdm=0" | "_bdsdm=1" | "_bdsdm=2" | "";
47
47
  //#endregion
48
48
  export { colorModeQueryParamCheck, getColorModeQueryParam };
@@ -43,6 +43,6 @@ declare const colorModeQueryParamCheck: string;
43
43
  *
44
44
  * @returns Braid color mode preference as query string parameter
45
45
  */
46
- declare const getColorModeQueryParam: () => "" | "_bdsdm=1" | "_bdsdm=2" | "_bdsdm=0";
46
+ declare const getColorModeQueryParam: () => "_bdsdm=0" | "_bdsdm=1" | "_bdsdm=2" | "";
47
47
  //#endregion
48
48
  export { colorModeQueryParamCheck, getColorModeQueryParam };
@@ -20,6 +20,7 @@ const require_normalizeKey = require("../private/normalizeKey.cjs");
20
20
  const require_smoothScroll = require("../private/smoothScroll.cjs");
21
21
  const require_createAccessibilityProps = require("./createAccessibilityProps.cjs");
22
22
  const require_reverseMatches = require("./reverseMatches.cjs");
23
+ const require_useScrollIntoView = require("./useScrollIntoView.cjs");
23
24
  const require_Autosuggest_css = require("./Autosuggest.css.cjs");
24
25
  let dedent = require("dedent");
25
26
  dedent = require_runtime.__toESM(dedent, 1);
@@ -257,24 +258,15 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
257
258
  isFocused: false
258
259
  });
259
260
  const isOpen = showSuggestionsIfAvailable && hasItems;
260
- (typeof highlightedIndex === "number" ? document.getElementById(require_createAccessibilityProps.getItemId(resolvedId, highlightedIndex)) : null)?.scrollIntoView({ block: "nearest" });
261
- (0, react.useEffect)(() => {
261
+ const highlightedItem = typeof highlightedIndex === "number" ? document.getElementById(require_createAccessibilityProps.getItemId(resolvedId, highlightedIndex)) : null;
262
+ (0, react.useLayoutEffect)(() => {
262
263
  dispatch({ type: HAS_SUGGESTIONS_CHANGED });
263
264
  }, [hasSuggestions]);
264
265
  const isMobile = require_useResponsiveValue.useResponsiveValue()({
265
266
  mobile: true,
266
267
  tablet: false
267
268
  });
268
- (0, react.useEffect)(() => {
269
- if (menuRef.current && isOpen && !isMobile) {
270
- const { bottom: menuBottom } = menuRef.current.getBoundingClientRect();
271
- if (menuBottom > document.documentElement.clientHeight) menuRef.current.scrollIntoView(false);
272
- }
273
- }, [
274
- isOpen,
275
- isMobile,
276
- suggestionCount
277
- ]);
269
+ require_useScrollIntoView.useScrollIntoView(highlightedItem, menuRef);
278
270
  const inputProps = {
279
271
  value: previewValue ? previewValue.text : value.text,
280
272
  type: type === "search" ? type : "text",
@@ -337,14 +329,16 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
337
329
  const clearable = Boolean(typeof onClear !== "undefined" && !restProps.disabled && typeof value !== "undefined" && value.text.length > 0);
338
330
  const announcements = [];
339
331
  const hasAutomaticSelection = automaticSelection && previewValue === null && highlightedIndex === 0;
340
- if (isFocused && isOpen && (highlightedIndex === null || hasAutomaticSelection)) if (hasSuggestions) {
341
- announcements.push(translations.suggestionsAvailableAnnouncement(suggestionCount));
342
- if (hasAutomaticSelection) announcements.push(translations.suggestionAutoSelectedAnnouncement(normalisedSuggestions[0].text));
343
- announcements.push(translations.suggestionInstructions);
344
- } else if (noSuggestionsMessage) {
345
- if (noSuggestionsMessage.title) announcements.push(noSuggestionsMessage.title);
346
- announcements.push(noSuggestionsMessage.description);
347
- } else announcements.push(translations.noSuggestionsAvailableAnnouncement);
332
+ if (isFocused && isOpen && (highlightedIndex === null || hasAutomaticSelection)) {
333
+ if (hasSuggestions) {
334
+ announcements.push(translations.suggestionsAvailableAnnouncement(suggestionCount));
335
+ if (hasAutomaticSelection) announcements.push(translations.suggestionAutoSelectedAnnouncement(normalisedSuggestions[0].text));
336
+ announcements.push(translations.suggestionInstructions);
337
+ } else if (noSuggestionsMessage) {
338
+ if (noSuggestionsMessage.title) announcements.push(noSuggestionsMessage.title);
339
+ announcements.push(noSuggestionsMessage.description);
340
+ } else announcements.push(translations.noSuggestionsAvailableAnnouncement);
341
+ }
348
342
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [showMobileBackdrop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Box.Box, {
349
343
  position: "fixed",
350
344
  zIndex: "dropdownBackdrop",
@@ -397,7 +391,6 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
397
391
  triggerRef: fieldRef,
398
392
  open: isOpen,
399
393
  width: "full",
400
- lockPlacement: true,
401
394
  offsetSpace: "xxsmall",
402
395
  modal: false,
403
396
  role: false,
@@ -408,6 +401,7 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
408
401
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_Box.Box, {
409
402
  textAlign: "left",
410
403
  component: "ul",
404
+ translate: "no",
411
405
  background: !hasSuggestions && noSuggestionsMessage ? {
412
406
  lightMode: "neutralSoft",
413
407
  darkMode: "neutral"
@@ -433,7 +427,6 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
433
427
  children: noSuggestionsMessage.description
434
428
  })]
435
429
  }) : null, hasSuggestions ? normalisedSuggestions.map((suggestion, index) => {
436
- const { text } = suggestion;
437
430
  const groupHeading = groupHeadingIndexes.get(index);
438
431
  const highlights = suggestionHighlight ? highlightSuggestions(suggestion.text, value.text, suggestionHighlight) : suggestion.highlights;
439
432
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [groupHeading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupHeading, { children: groupHeading }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionItem, {
@@ -459,7 +452,7 @@ const Autosuggest = (0, react.forwardRef)(function({ id, value = fallbackValue,
459
452
  description: suggestion.description,
460
453
  groupHeading: groupHeadingForSuggestion.get(suggestion)
461
454
  })
462
- })] }, index + text);
455
+ })] }, index);
463
456
  }) : null]
464
457
  })
465
458
  })
@@ -19,9 +19,10 @@ import { normalizeKey } from "../private/normalizeKey.mjs";
19
19
  import { smoothScroll } from "../private/smoothScroll.mjs";
20
20
  import { createAccessibilityProps, getItemId } from "./createAccessibilityProps.mjs";
21
21
  import { reverseMatches } from "./reverseMatches.mjs";
22
+ import { useScrollIntoView } from "./useScrollIntoView.mjs";
22
23
  import { backdrop, backdropVisible, menu } from "./Autosuggest.css.mjs";
23
24
  import dedent from "dedent";
24
- import { Fragment, forwardRef, useCallback, useEffect, useReducer, useRef } from "react";
25
+ import { Fragment, forwardRef, useCallback, useLayoutEffect, useReducer, useRef } from "react";
25
26
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
26
27
  import matchHighlights from "autosuggest-highlight/match/index.js";
27
28
  import parseHighlights from "autosuggest-highlight/parse/index.js";
@@ -253,24 +254,15 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
253
254
  isFocused: false
254
255
  });
255
256
  const isOpen = showSuggestionsIfAvailable && hasItems;
256
- (typeof highlightedIndex === "number" ? document.getElementById(getItemId(resolvedId, highlightedIndex)) : null)?.scrollIntoView({ block: "nearest" });
257
- useEffect(() => {
257
+ const highlightedItem = typeof highlightedIndex === "number" ? document.getElementById(getItemId(resolvedId, highlightedIndex)) : null;
258
+ useLayoutEffect(() => {
258
259
  dispatch({ type: HAS_SUGGESTIONS_CHANGED });
259
260
  }, [hasSuggestions]);
260
261
  const isMobile = useResponsiveValue()({
261
262
  mobile: true,
262
263
  tablet: false
263
264
  });
264
- useEffect(() => {
265
- if (menuRef.current && isOpen && !isMobile) {
266
- const { bottom: menuBottom } = menuRef.current.getBoundingClientRect();
267
- if (menuBottom > document.documentElement.clientHeight) menuRef.current.scrollIntoView(false);
268
- }
269
- }, [
270
- isOpen,
271
- isMobile,
272
- suggestionCount
273
- ]);
265
+ useScrollIntoView(highlightedItem, menuRef);
274
266
  const inputProps = {
275
267
  value: previewValue ? previewValue.text : value.text,
276
268
  type: type === "search" ? type : "text",
@@ -333,14 +325,16 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
333
325
  const clearable = Boolean(typeof onClear !== "undefined" && !restProps.disabled && typeof value !== "undefined" && value.text.length > 0);
334
326
  const announcements = [];
335
327
  const hasAutomaticSelection = automaticSelection && previewValue === null && highlightedIndex === 0;
336
- if (isFocused && isOpen && (highlightedIndex === null || hasAutomaticSelection)) if (hasSuggestions) {
337
- announcements.push(translations.suggestionsAvailableAnnouncement(suggestionCount));
338
- if (hasAutomaticSelection) announcements.push(translations.suggestionAutoSelectedAnnouncement(normalisedSuggestions[0].text));
339
- announcements.push(translations.suggestionInstructions);
340
- } else if (noSuggestionsMessage) {
341
- if (noSuggestionsMessage.title) announcements.push(noSuggestionsMessage.title);
342
- announcements.push(noSuggestionsMessage.description);
343
- } else announcements.push(translations.noSuggestionsAvailableAnnouncement);
328
+ if (isFocused && isOpen && (highlightedIndex === null || hasAutomaticSelection)) {
329
+ if (hasSuggestions) {
330
+ announcements.push(translations.suggestionsAvailableAnnouncement(suggestionCount));
331
+ if (hasAutomaticSelection) announcements.push(translations.suggestionAutoSelectedAnnouncement(normalisedSuggestions[0].text));
332
+ announcements.push(translations.suggestionInstructions);
333
+ } else if (noSuggestionsMessage) {
334
+ if (noSuggestionsMessage.title) announcements.push(noSuggestionsMessage.title);
335
+ announcements.push(noSuggestionsMessage.description);
336
+ } else announcements.push(translations.noSuggestionsAvailableAnnouncement);
337
+ }
344
338
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [showMobileBackdrop ? /* @__PURE__ */ jsx(Box, {
345
339
  position: "fixed",
346
340
  zIndex: "dropdownBackdrop",
@@ -393,7 +387,6 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
393
387
  triggerRef: fieldRef,
394
388
  open: isOpen,
395
389
  width: "full",
396
- lockPlacement: true,
397
390
  offsetSpace: "xxsmall",
398
391
  modal: false,
399
392
  role: false,
@@ -404,6 +397,7 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
404
397
  children: /* @__PURE__ */ jsxs(Box, {
405
398
  textAlign: "left",
406
399
  component: "ul",
400
+ translate: "no",
407
401
  background: !hasSuggestions && noSuggestionsMessage ? {
408
402
  lightMode: "neutralSoft",
409
403
  darkMode: "neutral"
@@ -429,7 +423,6 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
429
423
  children: noSuggestionsMessage.description
430
424
  })]
431
425
  }) : null, hasSuggestions ? normalisedSuggestions.map((suggestion, index) => {
432
- const { text } = suggestion;
433
426
  const groupHeading = groupHeadingIndexes.get(index);
434
427
  const highlights = suggestionHighlight ? highlightSuggestions(suggestion.text, value.text, suggestionHighlight) : suggestion.highlights;
435
428
  return /* @__PURE__ */ jsxs(Fragment, { children: [groupHeading ? /* @__PURE__ */ jsx(GroupHeading, { children: groupHeading }) : null, /* @__PURE__ */ jsx(SuggestionItem, {
@@ -455,7 +448,7 @@ const Autosuggest = forwardRef(function({ id, value = fallbackValue, suggestions
455
448
  description: suggestion.description,
456
449
  groupHeading: groupHeadingForSuggestion.get(suggestion)
457
450
  })
458
- })] }, index + text);
451
+ })] }, index);
459
452
  }) : null]
460
453
  })
461
454
  })
@@ -0,0 +1,14 @@
1
+ let react = require("react");
2
+ //#region src/lib/components/Autosuggest/useScrollIntoView.ts
3
+ function useScrollIntoView(element, scrollContainerRef) {
4
+ (0, react.useEffect)(() => {
5
+ const scrollContainer = scrollContainerRef.current;
6
+ if (!scrollContainer || !element) return;
7
+ const containerRect = scrollContainer.getBoundingClientRect();
8
+ const elementRect = element.getBoundingClientRect();
9
+ if (elementRect.top < containerRect.top) scrollContainer.scrollTop -= containerRect.top - elementRect.top;
10
+ else if (elementRect.bottom > containerRect.bottom) scrollContainer.scrollTop += elementRect.bottom - containerRect.bottom;
11
+ }, [scrollContainerRef, element]);
12
+ }
13
+ //#endregion
14
+ exports.useScrollIntoView = useScrollIntoView;
@@ -0,0 +1,14 @@
1
+ import { useEffect } from "react";
2
+ //#region src/lib/components/Autosuggest/useScrollIntoView.ts
3
+ function useScrollIntoView(element, scrollContainerRef) {
4
+ useEffect(() => {
5
+ const scrollContainer = scrollContainerRef.current;
6
+ if (!scrollContainer || !element) return;
7
+ const containerRect = scrollContainer.getBoundingClientRect();
8
+ const elementRect = element.getBoundingClientRect();
9
+ if (elementRect.top < containerRect.top) scrollContainer.scrollTop -= containerRect.top - elementRect.top;
10
+ else if (elementRect.bottom > containerRect.bottom) scrollContainer.scrollTop += elementRect.bottom - containerRect.bottom;
11
+ }, [scrollContainerRef, element]);
12
+ }
13
+ //#endregion
14
+ export { useScrollIntoView };
@@ -48,18 +48,20 @@ const Tab = ({ children, data, badge, icon, item, ...restProps }) => {
48
48
  });
49
49
  (0, react.useEffect)(() => {
50
50
  if (!tabRef.current || !scrollContainer) return;
51
- if (isSelected || isFocused) if (isMobile) require_smoothScroll.smoothScroll(tabRef.current, {
52
- scrollContainer,
53
- direction: "horizontal",
54
- offset: space[paddingXForTabSize[size]] * grid * 3,
55
- ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
56
- });
57
- else require_smoothScroll.smoothScrollIntoView(tabRef.current, {
58
- scrollContainer,
59
- direction: "horizontal",
60
- offset: space[paddingXForTabSize[size]] * grid * 6,
61
- ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
62
- });
51
+ if (isSelected || isFocused) {
52
+ if (isMobile) require_smoothScroll.smoothScroll(tabRef.current, {
53
+ scrollContainer,
54
+ direction: "horizontal",
55
+ offset: space[paddingXForTabSize[size]] * grid * 3,
56
+ ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
57
+ });
58
+ else require_smoothScroll.smoothScrollIntoView(tabRef.current, {
59
+ scrollContainer,
60
+ direction: "horizontal",
61
+ offset: space[paddingXForTabSize[size]] * grid * 6,
62
+ ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
63
+ });
64
+ }
63
65
  firstRenderRef.current = false;
64
66
  }, [
65
67
  isSelected,
@@ -46,18 +46,20 @@ const Tab = ({ children, data, badge, icon, item, ...restProps }) => {
46
46
  });
47
47
  useEffect(() => {
48
48
  if (!tabRef.current || !scrollContainer) return;
49
- if (isSelected || isFocused) if (isMobile) smoothScroll(tabRef.current, {
50
- scrollContainer,
51
- direction: "horizontal",
52
- offset: space[paddingXForTabSize[size]] * grid * 3,
53
- ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
54
- });
55
- else smoothScrollIntoView(tabRef.current, {
56
- scrollContainer,
57
- direction: "horizontal",
58
- offset: space[paddingXForTabSize[size]] * grid * 6,
59
- ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
60
- });
49
+ if (isSelected || isFocused) {
50
+ if (isMobile) smoothScroll(tabRef.current, {
51
+ scrollContainer,
52
+ direction: "horizontal",
53
+ offset: space[paddingXForTabSize[size]] * grid * 3,
54
+ ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
55
+ });
56
+ else smoothScrollIntoView(tabRef.current, {
57
+ scrollContainer,
58
+ direction: "horizontal",
59
+ offset: space[paddingXForTabSize[size]] * grid * 6,
60
+ ...firstRenderRef.current ? { duration: 0 } : { speed: .7 }
61
+ });
62
+ }
61
63
  firstRenderRef.current = false;
62
64
  }, [
63
65
  isSelected,
@@ -20,6 +20,7 @@ const Announcement = ({ children }) => {
20
20
  element.setAttribute("class", className);
21
21
  element.setAttribute("aria-live", "polite");
22
22
  element.setAttribute("aria-atomic", "true");
23
+ element.setAttribute("translate", "no");
23
24
  document.body.appendChild(element);
24
25
  setElement(element);
25
26
  return () => {
@@ -20,6 +20,7 @@ const Announcement = ({ children }) => {
20
20
  element.setAttribute("class", className);
21
21
  element.setAttribute("aria-live", "polite");
22
22
  element.setAttribute("aria-atomic", "true");
23
+ element.setAttribute("translate", "no");
23
24
  document.body.appendChild(element);
24
25
  setElement(element);
25
26
  return () => {
@@ -55,8 +55,10 @@ const useFlipList = (expanded) => {
55
55
  };
56
56
  const activeToasts = [];
57
57
  Array.from(refs.entries()).forEach(([toastKey, element]) => {
58
- if (element !== null) if (toastStates.get(toastKey) === "exiting") refs.delete(toastKey);
59
- else activeToasts.push([toastKey, element]);
58
+ if (element !== null) {
59
+ if (toastStates.get(toastKey) === "exiting") refs.delete(toastKey);
60
+ else activeToasts.push([toastKey, element]);
61
+ }
60
62
  });
61
63
  activeToasts.forEach(([toastKey, element], index) => {
62
64
  const position = activeToasts.length - index - 1;
@@ -55,8 +55,10 @@ const useFlipList = (expanded) => {
55
55
  };
56
56
  const activeToasts = [];
57
57
  Array.from(refs.entries()).forEach(([toastKey, element]) => {
58
- if (element !== null) if (toastStates.get(toastKey) === "exiting") refs.delete(toastKey);
59
- else activeToasts.push([toastKey, element]);
58
+ if (element !== null) {
59
+ if (toastStates.get(toastKey) === "exiting") refs.delete(toastKey);
60
+ else activeToasts.push([toastKey, element]);
61
+ }
60
62
  });
61
63
  activeToasts.forEach(([toastKey, element], index) => {
62
64
  const position = activeToasts.length - index - 1;
@@ -4,7 +4,7 @@ import { ConditionalValue, RequiredConditionalValue } from "@vanilla-extract/spr
4
4
  declare const colorAtomicProperties: {
5
5
  conditions: {
6
6
  defaultCondition: "lightMode";
7
- conditionNames: ("lightMode" | "darkMode")[];
7
+ conditionNames: ("darkMode" | "lightMode")[];
8
8
  };
9
9
  styles: {
10
10
  readonly background: {
@@ -4,7 +4,7 @@ import { ConditionalValue, RequiredConditionalValue } from "@vanilla-extract/spr
4
4
  declare const colorAtomicProperties: {
5
5
  conditions: {
6
6
  defaultCondition: "lightMode";
7
- conditionNames: ("lightMode" | "darkMode")[];
7
+ conditionNames: ("darkMode" | "lightMode")[];
8
8
  };
9
9
  styles: {
10
10
  readonly background: {
@@ -15,7 +15,7 @@ const isLight = (inputColor, foregroundColor = "#000") => {
15
15
  const colors = /^linear-gradient/.test(inputColor) ? getLinearGradientColors(inputColor) : [inputColor];
16
16
  const foregroundYiq = calculateYiq(foregroundColor);
17
17
  return colors.some((color) => {
18
- return calculateYiq(color) >= 256 / 2 + foregroundYiq / 2;
18
+ return calculateYiq(color) >= 128 + foregroundYiq / 2;
19
19
  });
20
20
  };
21
21
  //#endregion
@@ -15,7 +15,7 @@ const isLight = (inputColor, foregroundColor = "#000") => {
15
15
  const colors = /^linear-gradient/.test(inputColor) ? getLinearGradientColors(inputColor) : [inputColor];
16
16
  const foregroundYiq = calculateYiq(foregroundColor);
17
17
  return colors.some((color) => {
18
- return calculateYiq(color) >= 256 / 2 + foregroundYiq / 2;
18
+ return calculateYiq(color) >= 128 + foregroundYiq / 2;
19
19
  });
20
20
  };
21
21
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-design-system",
3
- "version": "34.6.1",
3
+ "version": "34.6.2",
4
4
  "description": "Themeable design system for the SEEK Group",
5
5
  "homepage": "https://seek-oss.github.io/braid-design-system/",
6
6
  "bugs": {