@12-apps/payments-frontend 3.34.3 → 3.34.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/payments-frontend",
3
- "version": "3.34.3",
3
+ "version": "3.34.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Browser half of the vendor-agnostic payments platform: plug-and-play MUI components for the per-provider settings page (credential form from each provider's schema, masked hints, verify/enable) and the checkout page (PIX QR + polling, card tokenization, hosted-checkout redirect), plus the headless hooks and fetch clients they build on. Talks only to the host's payments HTTP surface — never to a provider directly. Microfrontend-ready: no app coupling, host injects theme and auth.",
@@ -19,15 +19,21 @@ import {
19
19
  CircularProgress,
20
20
  FormControl,
21
21
  FormControlLabel,
22
+ FormHelperText,
22
23
  FormLabel,
24
+ InputLabel,
25
+ OutlinedInput,
23
26
  Radio,
24
27
  RadioGroup as MuiRadioGroup,
25
28
  Step,
26
29
  StepLabel,
27
30
  Stepper as MuiStepper,
28
- TextField,
29
31
  Typography,
32
+ styled,
33
+ useThemeProps,
30
34
  } from '@mui/material';
35
+ import type { TextFieldProps } from '@mui/material';
36
+ import { useId } from 'react';
31
37
  import type { JSX } from 'react';
32
38
 
33
39
  import type {
@@ -93,25 +99,170 @@ function DefaultButton({ variant = 'solid', color = 'primary', size = 'md', full
93
99
  );
94
100
  }
95
101
 
96
- function DefaultInput({ label, type = 'text', inputMode, fullWidth, required, autoComplete, placeholder, maxLength, value, error, helperText, endAdornment, onChange, onBlur, ...rest }: CheckoutInputProps): JSX.Element {
97
- return (
98
- <TextField
99
- label={label}
100
- type={type}
101
- size="small"
102
- fullWidth={fullWidth}
103
- required={required}
104
- placeholder={placeholder}
105
- value={value}
106
- error={error}
107
- helperText={helperText}
108
- onChange={onChange}
109
- onBlur={onBlur}
110
- slotProps={{
102
+ type OwnerState = Record<string, unknown>;
103
+ type SlotProps = Record<string, unknown> | ((ownerState: OwnerState) => Record<string, unknown>) | undefined;
104
+
105
+ /**
106
+ * `TextField`'s root, by name and with its `ownerState`: a theme's
107
+ * `components.MuiTextField` (`styleOverrides`, including functions reading
108
+ * `ownerState`, and `variants`) applies exactly as it did to `TextField`.
109
+ */
110
+ const TextFieldRoot = styled(FormControl, {
111
+ name: 'MuiTextField',
112
+ slot: 'Root',
113
+ overridesResolver: (_props, styles) => styles.root,
114
+ })<{ ownerState: OwnerState }>({});
115
+
116
+ /** A theme's slot props may be a function of `ownerState`, as `useSlot` allows. */
117
+ function slotPropsOf(slotProps: SlotProps, ownerState: OwnerState): Record<string, unknown> {
118
+ return (typeof slotProps === 'function' ? slotProps(ownerState) : slotProps) ?? {};
119
+ }
120
+
121
+ /** `TextField`'s `ownerState`, for the outlined, non-select field this slot is. */
122
+ function ownerStateOf(props: TextFieldProps): OwnerState {
123
+ return {
124
+ ...props,
125
+ autoFocus: false,
126
+ color: props.color ?? 'primary',
127
+ disabled: props.disabled ?? false,
128
+ error: props.error ?? false,
129
+ fullWidth: props.fullWidth ?? false,
130
+ multiline: false,
131
+ required: props.required ?? false,
132
+ select: false,
133
+ variant: 'outlined',
134
+ };
135
+ }
136
+
137
+ /**
138
+ * The slot's own props, run through the theme's `MuiTextField` defaults the way
139
+ * `TextField` runs them (`useDefaultProps` is `useThemeProps` by another name).
140
+ */
141
+ function useTextFieldProps({ label, type = 'text', inputMode, fullWidth, required, autoComplete, placeholder, maxLength, value, error, helperText, endAdornment, onChange, onBlur, ...rest }: CheckoutInputProps): TextFieldProps {
142
+ return useThemeProps({
143
+ name: 'MuiTextField',
144
+ props: {
145
+ label,
146
+ type,
147
+ size: 'small',
148
+ fullWidth,
149
+ required,
150
+ placeholder,
151
+ value,
152
+ error,
153
+ helperText,
154
+ onChange,
155
+ onBlur,
156
+ slotProps: {
111
157
  htmlInput: { inputMode, maxLength, autoComplete, 'data-testid': rest['data-testid'] },
112
158
  input: { endAdornment },
113
- }}
114
- />
159
+ },
160
+ } as TextFieldProps,
161
+ });
162
+ }
163
+
164
+ /**
165
+ * What `TextField` consumes by name rather than handing to its root. A key LIST
166
+ * rather than a destructure, which would bind every one of them only to drop it.
167
+ */
168
+ const CONSUMED_BY_FIELD: ReadonlySet<string> = new Set([
169
+ 'autoComplete', 'autoFocus', 'children', 'className', 'color', 'defaultValue', 'disabled', 'error',
170
+ 'FormHelperTextProps', 'fullWidth', 'helperText', 'id', 'InputLabelProps', 'inputProps', 'InputProps',
171
+ 'inputRef', 'label', 'maxRows', 'minRows', 'multiline', 'name', 'onBlur', 'onChange', 'onFocus',
172
+ 'placeholder', 'required', 'rows', 'select', 'SelectProps', 'slotProps', 'slots', 'type', 'value',
173
+ 'variant',
174
+ ]);
175
+
176
+ /**
177
+ * The slot props as `TextField` resolves them: the legacy `InputProps`,
178
+ * `InputLabelProps`, `inputProps` and `FormHelperTextProps` first, then
179
+ * `slotProps` over them.
180
+ */
181
+ function slotPropsFor(props: TextFieldProps): Record<string, SlotProps> {
182
+ return {
183
+ input: props.InputProps as SlotProps,
184
+ inputLabel: props.InputLabelProps as SlotProps,
185
+ htmlInput: props.inputProps as SlotProps,
186
+ formHelperText: props.FormHelperTextProps as SlotProps,
187
+ ...((props.slotProps ?? {}) as Record<string, SlotProps>),
188
+ };
189
+ }
190
+
191
+ /** The root: what `TextField` did not consume, then the props it hands the root by name. */
192
+ function rootPropsOf(props: TextFieldProps, ownerState: OwnerState): Record<string, unknown> {
193
+ return {
194
+ ...Object.fromEntries(Object.entries(props).filter(([key]) => !CONSUMED_BY_FIELD.has(key))),
195
+ ownerState,
196
+ className: ['MuiTextField-root', (props.classes as { root?: string } | undefined)?.root, props.className]
197
+ .filter(Boolean)
198
+ .join(' '),
199
+ disabled: ownerState.disabled,
200
+ error: ownerState.error,
201
+ fullWidth: ownerState.fullWidth,
202
+ required: ownerState.required,
203
+ color: ownerState.color,
204
+ variant: 'outlined',
205
+ };
206
+ }
207
+
208
+ /** The input: the props `TextField` names, then the theme's and the slot's `input` slot props. */
209
+ function inputPropsOf(props: TextFieldProps, ownerState: OwnerState, ids: { id: string; helperTextId?: string }): Record<string, unknown> {
210
+ const slots = slotPropsFor(props);
211
+ const shrink = slotPropsOf(slots.inputLabel, ownerState).shrink;
212
+ return {
213
+ 'aria-describedby': ids.helperTextId,
214
+ autoComplete: props.autoComplete,
215
+ autoFocus: ownerState.autoFocus,
216
+ defaultValue: props.defaultValue,
217
+ fullWidth: ownerState.fullWidth,
218
+ name: props.name,
219
+ inputRef: props.inputRef,
220
+ onFocus: props.onFocus,
221
+ type: props.type,
222
+ value: props.value,
223
+ id: ids.id,
224
+ onBlur: props.onBlur,
225
+ onChange: props.onChange,
226
+ placeholder: props.placeholder,
227
+ inputProps: slotPropsOf(slots.htmlInput, ownerState),
228
+ label: props.label,
229
+ ...(shrink === undefined ? {} : { notched: shrink }),
230
+ ...slotPropsOf(slots.input, ownerState),
231
+ };
232
+ }
233
+
234
+ /**
235
+ * An outlined text field, composed from the parts MUI's `TextField` composes
236
+ * (FUT-3402). `TextField` itself imports `Select` unconditionally, and through
237
+ * it `Menu`, `MenuList`, `Popover` and `List`; this slot is in every host's
238
+ * checkout bundle whether or not the host fills it, so it paid for a dropdown
239
+ * it can never render.
240
+ *
241
+ * Same DOM and the same theme hooks — `defaultProps`, `styleOverrides`,
242
+ * `variants`, slot props — pinned by `default-input-parity.test.tsx`, with two
243
+ * deliberate exceptions: a theme default `variant` other than `outlined`, and
244
+ * `slots` replacing a part, are not honoured. Either would need the very
245
+ * components this slot exists to leave out; a host that wants them fills the
246
+ * `Input` slot.
247
+ */
248
+ function DefaultInput(slotProps: CheckoutInputProps): JSX.Element {
249
+ const props = useTextFieldProps(slotProps);
250
+ const ownerState = ownerStateOf(props);
251
+ const generatedId = useId();
252
+ const id = props.id ?? generatedId;
253
+ const helperTextId = props.helperText ? `${id}-helper-text` : undefined;
254
+ const slots = slotPropsFor(props);
255
+ const { label, helperText } = props;
256
+ return (
257
+ <TextFieldRoot {...(rootPropsOf(props, ownerState) as { ownerState: OwnerState })}>
258
+ {label != null && label !== '' ? (
259
+ <InputLabel htmlFor={id} id={`${id}-label`} {...slotPropsOf(slots.inputLabel, ownerState)}>{label}</InputLabel>
260
+ ) : null}
261
+ <OutlinedInput {...inputPropsOf(props, ownerState, { id, helperTextId })} />
262
+ {helperText ? (
263
+ <FormHelperText id={helperTextId} {...slotPropsOf(slots.formHelperText, ownerState)}>{helperText}</FormHelperText>
264
+ ) : null}
265
+ </TextFieldRoot>
115
266
  );
116
267
  }
117
268