@aws-amplify/ui-react 6.1.13 → 6.2.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 (52) hide show
  1. package/dist/{Field-1f747369.js → Field-d47a49dc.js} +12 -23
  2. package/dist/ThemeStyle-b2dce96a.js +91 -0
  3. package/dist/esm/components/Authenticator/ConfirmSignUp/ConfirmSignUp.mjs +2 -2
  4. package/dist/esm/components/Authenticator/FederatedSignIn/FederatedSignInButtons/FederatedSignInButton.mjs +1 -1
  5. package/dist/esm/components/Authenticator/ForceNewPassword/ForceNewPassword.mjs +2 -2
  6. package/dist/esm/components/Authenticator/SignIn/SignIn.mjs +3 -3
  7. package/dist/esm/components/Authenticator/SignUp/SignUp.mjs +2 -2
  8. package/dist/esm/components/Authenticator/VerifyUser/VerifyUser.mjs +2 -2
  9. package/dist/esm/components/Authenticator/shared/ConfirmSignInFooter.mjs +2 -2
  10. package/dist/esm/components/Authenticator/shared/TwoButtonSubmitFooter.mjs +3 -2
  11. package/dist/esm/components/ThemeProvider/ThemeProvider.mjs +3 -5
  12. package/dist/esm/components/ThemeProvider/ThemeStyle.mjs +69 -0
  13. package/dist/esm/helpers/constants.mjs +9 -0
  14. package/dist/esm/primitives/Checkbox/Checkbox.mjs +4 -4
  15. package/dist/esm/primitives/CheckboxField/CheckboxField.mjs +2 -2
  16. package/dist/esm/primitives/HighlightMatch/HighlightMatch.mjs +2 -2
  17. package/dist/esm/primitives/RadioGroupField/RadioGroupField.mjs +13 -5
  18. package/dist/esm/primitives/SelectField/SelectField.mjs +12 -3
  19. package/dist/esm/primitives/SliderField/SliderField.mjs +14 -6
  20. package/dist/esm/primitives/StepperField/StepperField.mjs +12 -3
  21. package/dist/esm/primitives/Tabs/TabsContainer.mjs +4 -1
  22. package/dist/esm/primitives/Tabs/TabsContext.mjs +1 -0
  23. package/dist/esm/primitives/Tabs/TabsItem.mjs +7 -2
  24. package/dist/esm/primitives/Tabs/TabsPanel.mjs +7 -2
  25. package/dist/esm/primitives/Tabs/constants.mjs +4 -0
  26. package/dist/esm/primitives/TextAreaField/TextAreaField.mjs +12 -3
  27. package/dist/esm/primitives/TextField/TextField.mjs +12 -3
  28. package/dist/esm/primitives/utils/createSpaceSeparatedIds.mjs +13 -0
  29. package/dist/esm/primitives/utils/getUniqueComponentId.mjs +3 -0
  30. package/dist/esm/server.mjs +2 -0
  31. package/dist/esm/version.mjs +1 -1
  32. package/dist/index.js +197 -130
  33. package/dist/internal.js +2 -1
  34. package/dist/primitiveWithForwardRef-7e929242.js +36 -0
  35. package/dist/server.js +32 -0
  36. package/dist/types/components/ThemeProvider/ThemeStyle.d.ts +18 -0
  37. package/dist/types/helpers/constants.d.ts +2 -0
  38. package/dist/types/primitives/Tabs/TabsContext.d.ts +1 -0
  39. package/dist/types/primitives/Tabs/constants.d.ts +1 -0
  40. package/dist/types/primitives/shared/responsive/utils.d.ts +1 -1
  41. package/dist/types/primitives/utils/createSpaceSeparatedIds.d.ts +8 -0
  42. package/dist/types/primitives/utils/getUniqueComponentId.d.ts +1 -0
  43. package/dist/types/server.d.ts +2 -0
  44. package/dist/types/version.d.ts +1 -1
  45. package/package.json +10 -5
  46. package/dist/esm/primitives/utils/getTestId.mjs +0 -3
  47. package/dist/types/context/elements/ElementsContext.d.ts +0 -58
  48. package/dist/types/context/elements/defineBaseElement.d.ts +0 -26
  49. package/dist/types/context/elements/index.d.ts +0 -3
  50. package/dist/types/context/elements/types.d.ts +0 -31
  51. package/dist/types/context/elements/withBaseElementProps.d.ts +0 -32
  52. package/dist/types/primitives/utils/getTestId.d.ts +0 -1
@@ -5,6 +5,7 @@ var core = require('@aws-amplify/core');
5
5
  var auth = require('aws-amplify/auth');
6
6
  var uiReactCore = require('@aws-amplify/ui-react-core');
7
7
  var ui = require('@aws-amplify/ui');
8
+ var primitiveWithForwardRef = require('./primitiveWithForwardRef-7e929242.js');
8
9
 
9
10
  function _interopNamespace(e) {
10
11
  if (e && e.__esModule) return e;
@@ -635,17 +636,6 @@ const useStyles = (props, style) => {
635
636
  }), [propStyles, style, breakpoints, breakpoint, tokens]);
636
637
  };
637
638
 
638
- /**
639
- * Updates the return type for primitives wrapped in `React.forwardRef` to
640
- * `React.ReactElement`. In React 18 the return type of `React.ExoticComponent`
641
- * was changed from `React.ReactElement` to `React.ReactNode`, which breaks
642
- * clients using React 16 and 17.
643
- *
644
- * @param primitive UI Primitive to be wrapped with `React.forwardRef`
645
- * @returns ForwaredRef wrapped UI Primitive
646
- */
647
- const primitiveWithForwardRef = (primitive) => React__namespace.forwardRef(primitive);
648
-
649
639
  const ViewPrimitive = ({ as = 'div', children, testId, ariaLabel, isDisabled, style, inert, ...rest }, ref) => {
650
640
  const { propStyles, nonStyleProps } = useStyles(rest, style);
651
641
  return React__namespace.createElement(as, {
@@ -661,7 +651,7 @@ const ViewPrimitive = ({ as = 'div', children, testId, ariaLabel, isDisabled, st
661
651
  /**
662
652
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/view)
663
653
  */
664
- const View = primitiveWithForwardRef(ViewPrimitive);
654
+ const View = primitiveWithForwardRef.primitiveWithForwardRef(ViewPrimitive);
665
655
  View.displayName = 'View';
666
656
 
667
657
  const defaultViewBox = { minX: 0, minY: 0, width: 24, height: 24 };
@@ -691,7 +681,7 @@ as = 'svg', fill = 'currentColor', pathData, viewBox = defaultViewBox, children,
691
681
  /**
692
682
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/icon)
693
683
  */
694
- const Icon = primitiveWithForwardRef(IconPrimitive);
684
+ const Icon = primitiveWithForwardRef.primitiveWithForwardRef(IconPrimitive);
695
685
  Icon.displayName = 'Icon';
696
686
 
697
687
  const IconsContext = React__namespace.createContext({});
@@ -994,7 +984,7 @@ function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _
994
984
  const FieldGroupIconPrimitive = ({ className, children, isVisible = true, excludeFromTabOrder = false, ...rest }, ref) => {
995
985
  return isVisible ? (React__namespace.createElement(View, { className: ui.classNames(ui.ComponentClassName.FieldGroupIcon, className), ref: ref, tabIndex: excludeFromTabOrder ? -1 : undefined, ...rest }, children)) : null;
996
986
  };
997
- const FieldGroupIcon = primitiveWithForwardRef(FieldGroupIconPrimitive);
987
+ const FieldGroupIcon = primitiveWithForwardRef.primitiveWithForwardRef(FieldGroupIconPrimitive);
998
988
  FieldGroupIcon.displayName = 'FieldGroupIcon';
999
989
 
1000
990
  const FieldsetContext = React__namespace.createContext({
@@ -1011,7 +1001,7 @@ const FlexPrimitive = ({ className, children, ...rest }, ref) => (React__namespa
1011
1001
  /**
1012
1002
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/flex)
1013
1003
  */
1014
- const Flex = primitiveWithForwardRef(FlexPrimitive);
1004
+ const Flex = primitiveWithForwardRef.primitiveWithForwardRef(FlexPrimitive);
1015
1005
  Flex.displayName = 'Flex';
1016
1006
 
1017
1007
  const LINEAR_EMPTY = 'linear-empty';
@@ -1061,7 +1051,7 @@ const LoaderPrimitive = ({ className, filledColor, emptyColor, size, variation,
1061
1051
  /**
1062
1052
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/loader)
1063
1053
  */
1064
- const Loader = primitiveWithForwardRef(LoaderPrimitive);
1054
+ const Loader = primitiveWithForwardRef.primitiveWithForwardRef(LoaderPrimitive);
1065
1055
  Loader.displayName = 'Loader';
1066
1056
 
1067
1057
  // These variations support colorThemes. 'undefined' accounts for our
@@ -1085,11 +1075,11 @@ const ButtonPrimitive = ({ className, children, colorTheme, isFullWidth = false,
1085
1075
  /**
1086
1076
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/button)
1087
1077
  */
1088
- const Button = primitiveWithForwardRef(ButtonPrimitive);
1078
+ const Button = primitiveWithForwardRef.primitiveWithForwardRef(ButtonPrimitive);
1089
1079
  Button.displayName = 'Button';
1090
1080
 
1091
1081
  const FieldGroupIconButtonPrimitive = ({ children, className, ...rest }, ref) => (React__namespace.createElement(FieldGroupIcon, { as: Button, className: ui.classNames(ui.ComponentClassName.FieldGroupIconButton, className), ref: ref, ...rest }, children));
1092
- const FieldGroupIconButton = primitiveWithForwardRef(FieldGroupIconButtonPrimitive);
1082
+ const FieldGroupIconButton = primitiveWithForwardRef.primitiveWithForwardRef(FieldGroupIconButtonPrimitive);
1093
1083
  FieldGroupIconButton.displayName = 'FieldGroupIconButton';
1094
1084
 
1095
1085
  const ariaLabelText = ComponentText.Fields.clearButtonLabel;
@@ -1097,7 +1087,7 @@ const FieldClearButtonPrimitive = ({ ariaLabel = ariaLabelText, size, ...rest },
1097
1087
  const icons = useIcons('field');
1098
1088
  return (React__namespace.createElement(FieldGroupIconButton, { ariaLabel: ariaLabel, size: size, ref: ref, ...rest }, icons?.clear ?? React__namespace.createElement(IconClose, null)));
1099
1089
  };
1100
- const FieldClearButton = primitiveWithForwardRef(FieldClearButtonPrimitive);
1090
+ const FieldClearButton = primitiveWithForwardRef.primitiveWithForwardRef(FieldClearButtonPrimitive);
1101
1091
  FieldClearButton.displayName = 'FieldClearButton';
1102
1092
 
1103
1093
  const TextPrimitive = ({ as = 'p', className, children, isTruncated, variation, ...rest }, ref) => {
@@ -1107,7 +1097,7 @@ const TextPrimitive = ({ as = 'p', className, children, isTruncated, variation,
1107
1097
  /**
1108
1098
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/text)
1109
1099
  */
1110
- const Text = primitiveWithForwardRef(TextPrimitive);
1100
+ const Text = primitiveWithForwardRef.primitiveWithForwardRef(TextPrimitive);
1111
1101
  Text.displayName = 'Text';
1112
1102
 
1113
1103
  const QA_FIELD_DESCRIPTION = 'qa-field-description';
@@ -1126,7 +1116,7 @@ const LabelPrimitive = ({ children, className, visuallyHidden, ...rest }, ref) =
1126
1116
  [ui.ComponentClassName.VisuallyHidden]: visuallyHidden,
1127
1117
  }), ref: ref, ...rest }, children));
1128
1118
  };
1129
- const Label = primitiveWithForwardRef(LabelPrimitive);
1119
+ const Label = primitiveWithForwardRef.primitiveWithForwardRef(LabelPrimitive);
1130
1120
  Label.displayName = 'Label';
1131
1121
 
1132
1122
  const FieldPrimitive = (props, ref) => {
@@ -1137,7 +1127,7 @@ const FieldPrimitive = (props, ref) => {
1137
1127
  children,
1138
1128
  errorMessage ? (React__namespace.createElement(FieldErrorMessage, { hasError: hasError, errorMessage: errorMessage })) : null));
1139
1129
  };
1140
- const Field = primitiveWithForwardRef(FieldPrimitive);
1130
+ const Field = primitiveWithForwardRef.primitiveWithForwardRef(FieldPrimitive);
1141
1131
  Field.displayName = 'Field';
1142
1132
 
1143
1133
  exports.ARROW_DOWN = ARROW_DOWN;
@@ -1184,7 +1174,6 @@ exports.View = View;
1184
1174
  exports.getConsecutiveIntArray = getConsecutiveIntArray;
1185
1175
  exports.getStyleValue = getStyleValue;
1186
1176
  exports.getValueAtCurrentBreakpoint = getValueAtCurrentBreakpoint;
1187
- exports.primitiveWithForwardRef = primitiveWithForwardRef;
1188
1177
  exports.strHasLength = strHasLength;
1189
1178
  exports.useAuth = useAuth;
1190
1179
  exports.useBreakpoint = useBreakpoint;
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var primitiveWithForwardRef = require('./primitiveWithForwardRef-7e929242.js');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n["default"] = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
25
+
26
+ const ThemeStylePrimitive = ({ theme, nonce, ...rest }, ref) => {
27
+ if (!theme)
28
+ return null;
29
+ const { name, cssText } = theme;
30
+ /*
31
+ Only inject theme CSS variables if given a theme.
32
+ The CSS file users import already has the default theme variables in it.
33
+ This will allow users to use the provider and theme with CSS variables
34
+ without having to worry about specificity issues because this stylesheet
35
+ will likely come after a user's defined CSS.
36
+
37
+ Q: Why are we using dangerouslySetInnerHTML?
38
+ A: We need to directly inject the theme's CSS string into the <style> tag without typical HTML escaping.
39
+ For example, JSX would escape characters meaningful in CSS such as ', ", < and >, thus breaking the CSS.
40
+ Q: Why not use a sanitization library such as DOMPurify?
41
+ A: For our use case, we specifically want to purify CSS text, *not* HTML.
42
+ DOMPurify, as well as any other HTML sanitization library, would escape/encode meaningful CSS characters
43
+ and break our CSS in the same way that JSX would.
44
+
45
+ Q: Are there any security risks in this particular use case?
46
+ A: Anything set inside of a <style> tag is always interpreted as CSS text, *not* HTML.
47
+ Reference: “Restrictions on the content of raw text elements” https://html.spec.whatwg.org/dev/syntax.html#cdata-rcdata-restrictions
48
+ And in our case, we are using dangerouslySetInnerHTML to set CSS text inside of a <style> tag.
49
+
50
+ Thus, it really comes down to the question: Could a malicious user escape from the context of the <style> tag?
51
+ For example, when inserting HTML into the DOM, could someone prematurely close the </style> tag and add a <script> tag?
52
+ e.g., </style><script>alert('hello')</script>
53
+ The answer depends on whether the code is rendered on the client or server side.
54
+
55
+ Client side
56
+ - To prevent XSS inside of the <style> tag, we need to make sure it's not closed prematurely.
57
+ - This is prevented by React because React creates a style DOM node (e.g., React.createElement(‘style’, ...)), and directly sets innerHTML as a string.
58
+ - Even if the string contains a closing </style> tag, it will still be interpreted as CSS text by the browser.
59
+ - Therefore, there is not an XSS vulnerability on the client side.
60
+
61
+ Server side
62
+ - When React code is rendered on the server side (e.g., NextJS), the code is sent to the browser as HTML text.
63
+ - Therefore, it *IS* possible to insert a closing </style> tag and escape the CSS context, which opens an XSS vulnerability.
64
+
65
+ Q: How are we mitigating the potential attack vector?
66
+ A: To fix this potential attack vector on the server side, we need to filter out any closing </style> tags,
67
+ as this the only way to escape from the context of the browser interpreting the text as CSS.
68
+ We also need to catch cases where there is any kind of whitespace character </style[HERE]>, such as tabs, carriage returns, etc:
69
+ </style
70
+
71
+ >
72
+ Therefore, by only rendering CSS text which does not include a closing '</style>' tag,
73
+ we ensure that the browser will correctly interpret all the text as CSS.
74
+ */
75
+ if (/<\/style/i.test(cssText)) {
76
+ return null;
77
+ }
78
+ else {
79
+ return (React__namespace.createElement("style", { ...rest, ref: ref, id: `amplify-theme-${name}`,
80
+ // eslint-disable-next-line react/no-danger
81
+ dangerouslySetInnerHTML: { __html: cssText }, nonce: nonce }));
82
+ }
83
+ };
84
+ /**
85
+ * @experimental
86
+ * [📖 Docs](https://ui.docs.amplify.aws/react/components/theme)
87
+ */
88
+ const ThemeStyle = primitiveWithForwardRef.primitiveWithForwardRef(ThemeStylePrimitive);
89
+ ThemeStyle.displayName = 'ThemeStyle';
90
+
91
+ exports.ThemeStyle = ThemeStyle;
@@ -32,8 +32,8 @@ function ConfirmSignUp({ className, variation, }) {
32
32
  React__default.createElement(Text, { className: "amplify-authenticator__subtitle" }, getDeliveryMessageText(codeDeliveryDetails)),
33
33
  React__default.createElement(FormFields, null),
34
34
  React__default.createElement(RemoteErrorMessage, null),
35
- React__default.createElement(Button, { variation: "primary", isDisabled: isPending, type: "submit", loadingText: getConfirmingText(), isLoading: isPending, fontWeight: "normal" }, getConfirmText()),
36
- React__default.createElement(Button, { onClick: resendCode, type: "button", fontWeight: "normal" }, getResendCodeText())),
35
+ React__default.createElement(Button, { variation: "primary", isDisabled: isPending, type: "submit", loadingText: getConfirmingText(), isLoading: isPending }, getConfirmText()),
36
+ React__default.createElement(Button, { onClick: resendCode, type: "button" }, getResendCodeText())),
37
37
  React__default.createElement(Footer, null)))));
38
38
  }
39
39
  const DefaultHeader = () => {
@@ -46,7 +46,7 @@ const FederatedSignInButton = (props) => {
46
46
  else if (icon === 'apple') {
47
47
  iconComponent = React__default.createElement(AppleIcon, null);
48
48
  }
49
- return (React__default.createElement(Button, { onClick: handleClick, className: "federated-sign-in-button", fontWeight: "normal", gap: "1rem" },
49
+ return (React__default.createElement(Button, { onClick: handleClick, className: "federated-sign-in-button", gap: "1rem" },
50
50
  iconComponent,
51
51
  React__default.createElement(Text, { as: "span" }, text)));
52
52
  };
@@ -26,8 +26,8 @@ const ForceNewPassword = ({ className, variation, }) => {
26
26
  React__default.createElement(Header, null),
27
27
  React__default.createElement(FormFields, null),
28
28
  React__default.createElement(RemoteErrorMessage, null),
29
- React__default.createElement(Button, { isDisabled: isPending, type: "submit", variation: "primary", isLoading: isPending, loadingText: getChangingText(), fontWeight: "normal" }, getChangePasswordText()),
30
- React__default.createElement(Button, { onClick: toSignIn, type: "button", fontWeight: "normal", variation: "link", size: "small" }, getBackToSignInText()),
29
+ React__default.createElement(Button, { isDisabled: isPending, type: "submit", variation: "primary", isLoading: isPending, loadingText: getChangingText() }, getChangePasswordText()),
30
+ React__default.createElement(Button, { onClick: toSignIn, type: "button", variation: "link", size: "small" }, getBackToSignInText()),
31
31
  React__default.createElement(Footer, null)))));
32
32
  };
33
33
  ForceNewPassword.FormFields = function FormFields$1() {
@@ -28,15 +28,15 @@ function SignIn() {
28
28
  React__default.createElement("legend", null, getSignInText())),
29
29
  React__default.createElement(FormFields, null)),
30
30
  React__default.createElement(RemoteErrorMessage, null),
31
- React__default.createElement(Button, { isDisabled: isPending, isFullWidth: true, type: "submit", variation: "primary", isLoading: isPending, loadingText: getSigningInText() }, getSignInText()))),
32
- React__default.createElement(Footer, null)));
31
+ React__default.createElement(Button, { isDisabled: isPending, type: "submit", variation: "primary", isLoading: isPending, loadingText: getSigningInText() }, getSignInText()),
32
+ React__default.createElement(Footer, null)))));
33
33
  }
34
34
  const DefaultFooter = () => {
35
35
  const { toForgotPassword } = useAuthenticator((context) => [
36
36
  context.toForgotPassword,
37
37
  ]);
38
38
  return (React__default.createElement(View, { "data-amplify-footer": "" },
39
- React__default.createElement(Button, { fontWeight: "normal", onClick: toForgotPassword, size: "small", variation: "link" }, getForgotPasswordText())));
39
+ React__default.createElement(Button, { onClick: toForgotPassword, size: "small", variation: "link" }, getForgotPasswordText())));
40
40
  };
41
41
  SignIn.Footer = DefaultFooter;
42
42
  SignIn.Header = function Header() {
@@ -28,8 +28,8 @@ function SignUp() {
28
28
  React__default.createElement(Flex, { direction: "column" },
29
29
  React__default.createElement(FormFields, null),
30
30
  React__default.createElement(RemoteErrorMessage, null)),
31
- React__default.createElement(Button, { isDisabled: hasValidationErrors || isPending, isFullWidth: true, type: "submit", variation: "primary", isLoading: isPending, loadingText: getCreatingAccountText() }, getCreateAccountText()))),
32
- React__default.createElement(Footer, null)));
31
+ React__default.createElement(Button, { isDisabled: hasValidationErrors || isPending, isFullWidth: true, type: "submit", variation: "primary", isLoading: isPending, loadingText: getCreatingAccountText() }, getCreateAccountText()),
32
+ React__default.createElement(Footer, null)))));
33
33
  }
34
34
  SignUp.Header = function Header() {
35
35
  // @ts-ignore
@@ -13,9 +13,9 @@ import { RouteContainer } from '../RouteContainer/RouteContainer.mjs';
13
13
 
14
14
  const { getSkipText, getVerifyText, getVerifyContactText, getAccountRecoveryInfoText, } = authenticatorTextUtil;
15
15
  const generateRadioGroup = (attributes) => {
16
- return Object.entries(attributes).map(([key, value]) => {
16
+ return Object.entries(attributes).map(([key, value], index) => {
17
17
  const verificationType = defaultFormFieldOptions[key].label;
18
- return (React__default.createElement(Radio, { name: "unverifiedAttr", value: key, key: key },
18
+ return (React__default.createElement(Radio, { name: "unverifiedAttr", value: key, key: key, defaultChecked: index === 0 },
19
19
  translate(verificationType),
20
20
  ":",
21
21
  ' ',
@@ -11,8 +11,8 @@ const ConfirmSignInFooter = () => {
11
11
  context.toSignIn,
12
12
  ]);
13
13
  return (React__default.createElement(Flex, { direction: "column" },
14
- React__default.createElement(Button, { isDisabled: isPending, type: "submit", variation: "primary", fontWeight: "normal", isLoading: isPending, loadingText: getConfirmingText() }, getConfirmText()),
15
- React__default.createElement(Button, { onClick: toSignIn, type: "button", variation: "link", fontWeight: "normal", size: "small" }, getBackToSignInText())));
14
+ React__default.createElement(Button, { isDisabled: isPending, type: "submit", variation: "primary", isLoading: isPending, loadingText: getConfirmingText() }, getConfirmText()),
15
+ React__default.createElement(Button, { onClick: toSignIn, type: "button", variation: "link", size: "small" }, getBackToSignInText())));
16
16
  };
17
17
 
18
18
  export { ConfirmSignInFooter };
@@ -28,8 +28,9 @@ const TwoButtonSubmitFooter = (props) => {
28
28
  "\u2026")) : (React__default.createElement(React__default.Fragment, null, getSubmitText()));
29
29
  const submitText = submitButtonText ?? defaultSubmitText;
30
30
  return (React__default.createElement(Flex, { direction: "column" },
31
- React__default.createElement(Button, { fontWeight: "normal", variation: "primary", isDisabled: isPending, type: "submit" }, submitText),
32
- React__default.createElement(Button, { onClick: onClick, type: "button", variation: "link", fontWeight: "normal", size: "small" }, cancelButtonText)));
31
+ React__default.createElement(Button, { variation: "primary", isDisabled: isPending, type: "submit" }, submitText),
32
+ React__default.createElement(Flex, { direction: "column", alignItems: "center" },
33
+ React__default.createElement(Button, { onClick: onClick, type: "button", variation: "link", size: "small" }, cancelButtonText))));
33
34
  };
34
35
 
35
36
  export { TwoButtonSubmitFooter };
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import * as RadixDirection from '@radix-ui/react-direction';
3
3
  import { sanitizeNamespaceImport, createTheme } from '@aws-amplify/ui';
4
4
  import { ThemeContext } from './ThemeContext.mjs';
5
+ import { ThemeStyle } from './ThemeStyle.mjs';
5
6
 
6
7
  // Radix packages don't support ESM in Node, in some scenarios(e.g. SSR)
7
8
  // We have to use namespace import and sanitize it to ensure the interoperablity between ESM and CJS
@@ -11,13 +12,10 @@ const { DirectionProvider } = sanitizeNamespaceImport(RadixDirection);
11
12
  */
12
13
  function ThemeProvider({ children, colorMode, direction = 'ltr', nonce, theme, }) {
13
14
  const value = React.useMemo(() => ({ theme: createTheme(theme), colorMode }), [theme, colorMode]);
14
- const { theme: { name, cssText }, } = value;
15
15
  return (React.createElement(ThemeContext.Provider, { value: value },
16
16
  React.createElement(DirectionProvider, { dir: direction },
17
- React.createElement("div", { "data-amplify-theme": name, "data-amplify-color-mode": colorMode, dir: direction }, children),
18
- typeof theme === 'undefined' || /<\/style/i.test(cssText) ? null : (React.createElement("style", { id: `amplify-theme-${name}`,
19
- // eslint-disable-next-line react/no-danger
20
- dangerouslySetInnerHTML: { __html: cssText }, nonce: nonce })))));
17
+ React.createElement("div", { "data-amplify-theme": value.theme.name, "data-amplify-color-mode": colorMode, dir: direction }, children),
18
+ theme ? React.createElement(ThemeStyle, { theme: value.theme, nonce: nonce }) : null)));
21
19
  }
22
20
 
23
21
  export { ThemeProvider };
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+ import { primitiveWithForwardRef } from '../../primitives/utils/primitiveWithForwardRef.mjs';
3
+
4
+ const ThemeStylePrimitive = ({ theme, nonce, ...rest }, ref) => {
5
+ if (!theme)
6
+ return null;
7
+ const { name, cssText } = theme;
8
+ /*
9
+ Only inject theme CSS variables if given a theme.
10
+ The CSS file users import already has the default theme variables in it.
11
+ This will allow users to use the provider and theme with CSS variables
12
+ without having to worry about specificity issues because this stylesheet
13
+ will likely come after a user's defined CSS.
14
+
15
+ Q: Why are we using dangerouslySetInnerHTML?
16
+ A: We need to directly inject the theme's CSS string into the <style> tag without typical HTML escaping.
17
+ For example, JSX would escape characters meaningful in CSS such as ', ", < and >, thus breaking the CSS.
18
+ Q: Why not use a sanitization library such as DOMPurify?
19
+ A: For our use case, we specifically want to purify CSS text, *not* HTML.
20
+ DOMPurify, as well as any other HTML sanitization library, would escape/encode meaningful CSS characters
21
+ and break our CSS in the same way that JSX would.
22
+
23
+ Q: Are there any security risks in this particular use case?
24
+ A: Anything set inside of a <style> tag is always interpreted as CSS text, *not* HTML.
25
+ Reference: “Restrictions on the content of raw text elements” https://html.spec.whatwg.org/dev/syntax.html#cdata-rcdata-restrictions
26
+ And in our case, we are using dangerouslySetInnerHTML to set CSS text inside of a <style> tag.
27
+
28
+ Thus, it really comes down to the question: Could a malicious user escape from the context of the <style> tag?
29
+ For example, when inserting HTML into the DOM, could someone prematurely close the </style> tag and add a <script> tag?
30
+ e.g., </style><script>alert('hello')</script>
31
+ The answer depends on whether the code is rendered on the client or server side.
32
+
33
+ Client side
34
+ - To prevent XSS inside of the <style> tag, we need to make sure it's not closed prematurely.
35
+ - This is prevented by React because React creates a style DOM node (e.g., React.createElement(‘style’, ...)), and directly sets innerHTML as a string.
36
+ - Even if the string contains a closing </style> tag, it will still be interpreted as CSS text by the browser.
37
+ - Therefore, there is not an XSS vulnerability on the client side.
38
+
39
+ Server side
40
+ - When React code is rendered on the server side (e.g., NextJS), the code is sent to the browser as HTML text.
41
+ - Therefore, it *IS* possible to insert a closing </style> tag and escape the CSS context, which opens an XSS vulnerability.
42
+
43
+ Q: How are we mitigating the potential attack vector?
44
+ A: To fix this potential attack vector on the server side, we need to filter out any closing </style> tags,
45
+ as this the only way to escape from the context of the browser interpreting the text as CSS.
46
+ We also need to catch cases where there is any kind of whitespace character </style[HERE]>, such as tabs, carriage returns, etc:
47
+ </style
48
+
49
+ >
50
+ Therefore, by only rendering CSS text which does not include a closing '</style>' tag,
51
+ we ensure that the browser will correctly interpret all the text as CSS.
52
+ */
53
+ if (/<\/style/i.test(cssText)) {
54
+ return null;
55
+ }
56
+ else {
57
+ return (React.createElement("style", { ...rest, ref: ref, id: `amplify-theme-${name}`,
58
+ // eslint-disable-next-line react/no-danger
59
+ dangerouslySetInnerHTML: { __html: cssText }, nonce: nonce }));
60
+ }
61
+ };
62
+ /**
63
+ * @experimental
64
+ * [📖 Docs](https://ui.docs.amplify.aws/react/components/theme)
65
+ */
66
+ const ThemeStyle = primitiveWithForwardRef(ThemeStylePrimitive);
67
+ ThemeStyle.displayName = 'ThemeStyle';
68
+
69
+ export { ThemeStyle };
@@ -0,0 +1,9 @@
1
+ import { isFunction } from '@aws-amplify/ui';
2
+
3
+ (typeof Symbol !== 'undefined' && isFunction(Symbol.for)
4
+ ? Symbol.for('amplify_default')
5
+ : '@@amplify_default');
6
+ const ERROR_SUFFIX = 'error';
7
+ const DESCRIPTION_SUFFIX = 'description';
8
+
9
+ export { DESCRIPTION_SUFFIX, ERROR_SUFFIX };
@@ -10,7 +10,7 @@ import { IconIndeterminate } from '../Icon/icons/IconIndeterminate.mjs';
10
10
  import { Input } from '../Input/Input.mjs';
11
11
  import { Text } from '../Text/Text.mjs';
12
12
  import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.mjs';
13
- import { getTestId } from '../utils/getTestId.mjs';
13
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
14
14
  import { useStableId } from '../utils/useStableId.mjs';
15
15
  import { splitPrimitiveProps } from '../utils/splitPrimitiveProps.mjs';
16
16
  import { useFieldset } from '../Fieldset/useFieldset.mjs';
@@ -57,9 +57,9 @@ const CheckboxPrimitive = ({ checked: controlledChecked, className, defaultCheck
57
57
  isIndeterminate;
58
58
  }
59
59
  }, [dataId, isIndeterminate]);
60
- const buttonTestId = getTestId(testId, ComponentClassName.CheckboxButton);
61
- const iconTestId = getTestId(testId, ComponentClassName.CheckboxIcon);
62
- const labelTestId = getTestId(testId, ComponentClassName.CheckboxLabel);
60
+ const buttonTestId = getUniqueComponentId(testId, ComponentClassName.CheckboxButton);
61
+ const iconTestId = getUniqueComponentId(testId, ComponentClassName.CheckboxIcon);
62
+ const labelTestId = getUniqueComponentId(testId, ComponentClassName.CheckboxLabel);
63
63
  const flexClasses = classNames(ComponentClassName.CheckboxButton, classNameModifierByFlag(ComponentClassName.CheckboxButton, 'disabled', shouldBeDisabled), classNameModifierByFlag(ComponentClassName.CheckboxButton, 'error', hasError), classNameModifierByFlag(ComponentClassName.CheckboxButton, 'focused', focused));
64
64
  const iconClasses = classNames(ComponentClassName.CheckboxIcon, classNameModifierByFlag(ComponentClassName.CheckboxIcon, 'checked', checked), classNameModifierByFlag(ComponentClassName.CheckboxIcon, 'disabled', shouldBeDisabled), classNameModifierByFlag(ComponentClassName.CheckboxIcon, 'indeterminate', isIndeterminate));
65
65
  const iconProps = {
@@ -5,12 +5,12 @@ import '../Field/FieldClearButton.mjs';
5
5
  import '../Field/FieldDescription.mjs';
6
6
  import { FieldErrorMessage } from '../Field/FieldErrorMessage.mjs';
7
7
  import '../Field/Field.mjs';
8
- import { getTestId } from '../utils/getTestId.mjs';
8
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
9
9
  import { primitiveWithForwardRef } from '../utils/primitiveWithForwardRef.mjs';
10
10
  import { Flex } from '../Flex/Flex.mjs';
11
11
 
12
12
  const CheckboxFieldPrimitive = ({ className, errorMessage, hasError = false, labelHidden = false, labelPosition, testId, size, ...rest }, ref) => {
13
- const checkboxTestId = getTestId(testId, ComponentClassName.Checkbox);
13
+ const checkboxTestId = getUniqueComponentId(testId, ComponentClassName.Checkbox);
14
14
  return (React.createElement(Flex, { className: classNames(ComponentClassName.Field, ComponentClassName.CheckboxField, classNameModifier(ComponentClassName.Field, size), className), testId: testId },
15
15
  React.createElement(Checkbox, { hasError: hasError, labelHidden: labelHidden, testId: checkboxTestId, labelPosition: labelPosition, ref: ref, ...rest }),
16
16
  React.createElement(FieldErrorMessage, { hasError: hasError, errorMessage: errorMessage })));
@@ -2,11 +2,11 @@ import { classNames, ComponentClassName } from '@aws-amplify/ui';
2
2
  import * as React from 'react';
3
3
  import { View } from '../View/View.mjs';
4
4
  import { strHasLength } from '../shared/utils.mjs';
5
- import { getTestId } from '../utils/getTestId.mjs';
5
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
6
6
  import { primitiveWithForwardRef } from '../utils/primitiveWithForwardRef.mjs';
7
7
 
8
8
  const HighlightMatchPrimitive = ({ children, className, query, testId, ...rest }, ref) => {
9
- const matchTestId = getTestId(testId, 'match');
9
+ const matchTestId = getUniqueComponentId(testId, 'match');
10
10
  const startIndex = children
11
11
  ?.toLocaleLowerCase()
12
12
  .indexOf(query?.toLocaleLowerCase());
@@ -8,15 +8,23 @@ import { Fieldset } from '../Fieldset/Fieldset.mjs';
8
8
  import '../Fieldset/useFieldset.mjs';
9
9
  import { Flex } from '../Flex/Flex.mjs';
10
10
  import { RadioGroupContext } from './context.mjs';
11
- import { getTestId } from '../utils/getTestId.mjs';
11
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
12
12
  import { useStableId } from '../utils/useStableId.mjs';
13
13
  import { primitiveWithForwardRef } from '../utils/primitiveWithForwardRef.mjs';
14
+ import { createSpaceSeparatedIds } from '../utils/createSpaceSeparatedIds.mjs';
15
+ import { DESCRIPTION_SUFFIX, ERROR_SUFFIX } from '../../helpers/constants.mjs';
14
16
 
15
17
  const RadioGroupFieldPrimitive = ({ children, className, defaultValue, descriptiveText, errorMessage, hasError = false, id, isDisabled, isRequired, isReadOnly, legend, legendHidden = false, labelPosition, onChange, name, size, testId, value, variation, ...rest }, ref) => {
16
18
  const fieldId = useStableId(id);
17
- const descriptionId = useStableId();
18
- const ariaDescribedBy = descriptiveText ? descriptionId : undefined;
19
- const radioGroupTestId = getTestId(testId, ComponentClassName.RadioGroup);
19
+ const stableId = useStableId();
20
+ const descriptionId = descriptiveText
21
+ ? getUniqueComponentId(stableId, DESCRIPTION_SUFFIX)
22
+ : undefined;
23
+ const errorId = hasError
24
+ ? getUniqueComponentId(stableId, ERROR_SUFFIX)
25
+ : undefined;
26
+ const ariaDescribedBy = createSpaceSeparatedIds([errorId, descriptionId]);
27
+ const radioGroupTestId = getUniqueComponentId(testId, ComponentClassName.RadioGroup);
20
28
  const radioGroupContextValue = React.useMemo(() => ({
21
29
  currentValue: value,
22
30
  defaultValue,
@@ -44,7 +52,7 @@ const RadioGroupFieldPrimitive = ({ children, className, defaultValue, descripti
44
52
  React.createElement(FieldDescription, { id: descriptionId, labelHidden: legendHidden, descriptiveText: descriptiveText }),
45
53
  React.createElement(Flex, { "aria-describedby": ariaDescribedBy, className: ComponentClassName.RadioGroup, id: fieldId, testId: radioGroupTestId },
46
54
  React.createElement(RadioGroupContext.Provider, { value: radioGroupContextValue }, children)),
47
- React.createElement(FieldErrorMessage, { hasError: hasError, errorMessage: errorMessage })));
55
+ React.createElement(FieldErrorMessage, { id: errorId, hasError: hasError, errorMessage: errorMessage })));
48
56
  };
49
57
  /**
50
58
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/radiogroupfield)
@@ -10,6 +10,9 @@ import { Select } from '../Select/Select.mjs';
10
10
  import { splitPrimitiveProps } from '../utils/splitPrimitiveProps.mjs';
11
11
  import { useStableId } from '../utils/useStableId.mjs';
12
12
  import { primitiveWithForwardRef } from '../utils/primitiveWithForwardRef.mjs';
13
+ import { createSpaceSeparatedIds } from '../utils/createSpaceSeparatedIds.mjs';
14
+ import { DESCRIPTION_SUFFIX, ERROR_SUFFIX } from '../../helpers/constants.mjs';
15
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
13
16
 
14
17
  const selectFieldChildren = ({ children, options, }) => {
15
18
  if (children) {
@@ -24,14 +27,20 @@ const selectFieldChildren = ({ children, options, }) => {
24
27
  const SelectFieldPrimitive = (props, ref) => {
25
28
  const { children, className, descriptiveText, errorMessage, hasError = false, id, label, labelHidden = false, options, size, testId, inputStyles, ..._rest } = props;
26
29
  const fieldId = useStableId(id);
27
- const descriptionId = useStableId();
28
- const ariaDescribedBy = descriptiveText ? descriptionId : undefined;
30
+ const stableId = useStableId();
31
+ const descriptionId = descriptiveText
32
+ ? getUniqueComponentId(stableId, DESCRIPTION_SUFFIX)
33
+ : undefined;
34
+ const errorId = hasError
35
+ ? getUniqueComponentId(stableId, ERROR_SUFFIX)
36
+ : undefined;
37
+ const ariaDescribedBy = createSpaceSeparatedIds([errorId, descriptionId]);
29
38
  const { styleProps, rest } = splitPrimitiveProps(_rest);
30
39
  return (React.createElement(Flex, { className: classNames(ComponentClassName.Field, classNameModifier(ComponentClassName.Field, size), ComponentClassName.SelectField, className), testId: testId, ...styleProps },
31
40
  React.createElement(Label, { htmlFor: fieldId, visuallyHidden: labelHidden }, label),
32
41
  React.createElement(FieldDescription, { id: descriptionId, labelHidden: labelHidden, descriptiveText: descriptiveText }),
33
42
  React.createElement(Select, { "aria-describedby": ariaDescribedBy, hasError: hasError, id: fieldId, ref: ref, size: size, ...rest, ...inputStyles }, selectFieldChildren({ children, options })),
34
- React.createElement(FieldErrorMessage, { hasError: hasError, errorMessage: errorMessage })));
43
+ React.createElement(FieldErrorMessage, { id: errorId, hasError: hasError, errorMessage: errorMessage })));
35
44
  };
36
45
  /**
37
46
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/selectfield)
@@ -13,6 +13,9 @@ import { View } from '../View/View.mjs';
13
13
  import { useStableId } from '../utils/useStableId.mjs';
14
14
  import { useFieldset } from '../Fieldset/useFieldset.mjs';
15
15
  import { primitiveWithForwardRef } from '../utils/primitiveWithForwardRef.mjs';
16
+ import { createSpaceSeparatedIds } from '../utils/createSpaceSeparatedIds.mjs';
17
+ import { DESCRIPTION_SUFFIX, ERROR_SUFFIX } from '../../helpers/constants.mjs';
18
+ import { getUniqueComponentId } from '../utils/getUniqueComponentId.mjs';
16
19
 
17
20
  // Radix packages don't support ESM in Node, in some scenarios(e.g. SSR)
18
21
  // We have to use namespace import and sanitize it to ensure the interoperablity between ESM and CJS
@@ -24,9 +27,14 @@ const SLIDER_RANGE_TEST_ID = 'slider-range';
24
27
  const SliderFieldPrimitive = ({ ariaValuetext, className, defaultValue = 0, descriptiveText, emptyTrackColor, errorMessage, filledTrackColor, formatValue, hasError = false, id, isDisabled, isValueHidden = false, label, labelHidden = false, onChange, orientation = 'horizontal', outerEndComponent, outerStartComponent, testId, thumbColor, trackSize, value, size, ..._rest }, ref) => {
25
28
  const { isFieldsetDisabled } = useFieldset();
26
29
  const fieldId = useStableId(id);
27
- const labelId = useStableId();
28
- const descriptionId = useStableId();
29
- const ariaDescribedBy = descriptiveText ? descriptionId : undefined;
30
+ const stableId = useStableId();
31
+ const descriptionId = descriptiveText
32
+ ? getUniqueComponentId(stableId, DESCRIPTION_SUFFIX)
33
+ : undefined;
34
+ const errorId = hasError
35
+ ? getUniqueComponentId(stableId, ERROR_SUFFIX)
36
+ : undefined;
37
+ const ariaDescribedBy = createSpaceSeparatedIds([errorId, descriptionId]);
30
38
  const disabled = isFieldsetDisabled ? isFieldsetDisabled : isDisabled;
31
39
  const { styleProps, rest } = splitPrimitiveProps(_rest);
32
40
  const isControlled = value !== undefined;
@@ -54,7 +62,7 @@ const SliderFieldPrimitive = ({ ariaValuetext, className, defaultValue = 0, desc
54
62
  , {
55
63
  // Custom classnames will be added to Root below
56
64
  className: classNames(ComponentClassName.Field, classNameModifier(ComponentClassName.Field, size), ComponentClassName.SliderField), testId: testId, ...styleProps },
57
- React.createElement(Label, { className: ComponentClassName.SliderFieldLabel, id: labelId, testId: SLIDER_LABEL_TEST_ID, visuallyHidden: labelHidden },
65
+ React.createElement(Label, { className: ComponentClassName.SliderFieldLabel, id: stableId, testId: SLIDER_LABEL_TEST_ID, visuallyHidden: labelHidden },
58
66
  React.createElement(View, { as: "span" }, label),
59
67
  !isValueHidden ? renderedValue : null),
60
68
  React.createElement(FieldDescription, { id: descriptionId, labelHidden: labelHidden, descriptiveText: descriptiveText }),
@@ -65,8 +73,8 @@ const SliderFieldPrimitive = ({ ariaValuetext, className, defaultValue = 0, desc
65
73
  [`${isVertical ? 'width' : 'height'}`]: trackSize,
66
74
  } },
67
75
  React.createElement(Range, { className: classNames(ComponentClassName.SliderFieldRange, classNameModifier(ComponentClassName.SliderFieldRange, orientation), classNameModifierByFlag(ComponentClassName.SliderFieldRange, 'disabled', disabled)), "data-testid": SLIDER_RANGE_TEST_ID, style: { backgroundColor: String(filledTrackColor) } })),
68
- React.createElement(Thumb, { "aria-describedby": ariaDescribedBy, "aria-labelledby": labelId, "aria-valuetext": ariaValuetext, className: classNames(ComponentClassName.SliderFieldThumb, classNameModifier(ComponentClassName.SliderFieldThumb, size), classNameModifierByFlag(ComponentClassName.SliderFieldThumb, 'disabled', disabled)), style: { backgroundColor: String(thumbColor) } }))),
69
- React.createElement(FieldErrorMessage, { hasError: hasError, errorMessage: errorMessage })));
76
+ React.createElement(Thumb, { "aria-describedby": ariaDescribedBy, "aria-labelledby": stableId, "aria-valuetext": ariaValuetext, className: classNames(ComponentClassName.SliderFieldThumb, classNameModifier(ComponentClassName.SliderFieldThumb, size), classNameModifierByFlag(ComponentClassName.SliderFieldThumb, 'disabled', disabled)), style: { backgroundColor: String(thumbColor) } }))),
77
+ React.createElement(FieldErrorMessage, { id: errorId, hasError: hasError, errorMessage: errorMessage })));
70
78
  };
71
79
  /**
72
80
  * [📖 Docs](https://ui.docs.amplify.aws/react/components/sliderfield)