@thunderid/react 1.4.0 → 1.4.1
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/dist/cjs/components/presentation/auth/SignIn/BaseSignIn.cjs +1 -0
- package/dist/cjs/components/presentation/auth/SignIn/BaseSignIn.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/SignIn/SignIn.cjs +2 -3
- package/dist/cjs/components/presentation/auth/SignIn/SignIn.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/SignUp/BaseSignUp.cjs +1 -0
- package/dist/cjs/components/presentation/auth/SignUp/BaseSignUp.cjs.map +1 -1
- package/dist/cjs/components/primitives/AffixedField/AffixedField.cjs +11 -1
- package/dist/cjs/components/primitives/AffixedField/AffixedField.cjs.map +1 -1
- package/dist/cjs/components/primitives/Checkbox/Checkbox.cjs +11 -1
- package/dist/cjs/components/primitives/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/cjs/components/primitives/DatePicker/DatePicker.cjs +11 -1
- package/dist/cjs/components/primitives/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/cjs/components/primitives/FormControl/FormControl.cjs +2 -1
- package/dist/cjs/components/primitives/FormControl/FormControl.cjs.map +1 -1
- package/dist/cjs/components/primitives/MultiInput/MultiInput.cjs +11 -2
- package/dist/cjs/components/primitives/MultiInput/MultiInput.cjs.map +1 -1
- package/dist/cjs/components/primitives/OtpField/OtpField.cjs +9 -0
- package/dist/cjs/components/primitives/OtpField/OtpField.cjs.map +1 -1
- package/dist/cjs/components/primitives/Select/Select.cjs +8 -0
- package/dist/cjs/components/primitives/Select/Select.cjs.map +1 -1
- package/dist/cjs/components/primitives/TextField/TextField.cjs +11 -1
- package/dist/cjs/components/primitives/TextField/TextField.cjs.map +1 -1
- package/dist/cjs/components/primitives/Toggle/Toggle.cjs +10 -1
- package/dist/cjs/components/primitives/Toggle/Toggle.cjs.map +1 -1
- package/dist/cjs/contexts/Theme/ThemeProvider.cjs +1 -1
- package/dist/cjs/contexts/Theme/ThemeProvider.cjs.map +1 -1
- package/dist/cjs/contexts/ThunderID/ThunderIDContext.cjs.map +1 -1
- package/dist/cjs/contexts/ThunderID/ThunderIDProvider.cjs +1 -1
- package/dist/cjs/contexts/ThunderID/ThunderIDProvider.cjs.map +1 -1
- package/dist/components/presentation/auth/SignIn/BaseSignIn.d.ts +4 -0
- package/dist/components/presentation/auth/SignIn/BaseSignIn.d.ts.map +1 -1
- package/dist/components/presentation/auth/SignIn/BaseSignIn.js +1 -0
- package/dist/components/presentation/auth/SignIn/BaseSignIn.js.map +1 -1
- package/dist/components/presentation/auth/SignIn/SignIn.d.ts.map +1 -1
- package/dist/components/presentation/auth/SignIn/SignIn.js +2 -3
- package/dist/components/presentation/auth/SignIn/SignIn.js.map +1 -1
- package/dist/components/presentation/auth/SignUp/BaseSignUp.d.ts +4 -0
- package/dist/components/presentation/auth/SignUp/BaseSignUp.d.ts.map +1 -1
- package/dist/components/presentation/auth/SignUp/BaseSignUp.js +1 -0
- package/dist/components/presentation/auth/SignUp/BaseSignUp.js.map +1 -1
- package/dist/components/primitives/AffixedField/AffixedField.d.ts.map +1 -1
- package/dist/components/primitives/AffixedField/AffixedField.js +11 -1
- package/dist/components/primitives/AffixedField/AffixedField.js.map +1 -1
- package/dist/components/primitives/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/primitives/Checkbox/Checkbox.js +10 -1
- package/dist/components/primitives/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/primitives/DatePicker/DatePicker.d.ts.map +1 -1
- package/dist/components/primitives/DatePicker/DatePicker.js +10 -1
- package/dist/components/primitives/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/primitives/FormControl/FormControl.d.ts +4 -0
- package/dist/components/primitives/FormControl/FormControl.d.ts.map +1 -1
- package/dist/components/primitives/FormControl/FormControl.js +2 -1
- package/dist/components/primitives/FormControl/FormControl.js.map +1 -1
- package/dist/components/primitives/MultiInput/MultiInput.d.ts.map +1 -1
- package/dist/components/primitives/MultiInput/MultiInput.js +12 -3
- package/dist/components/primitives/MultiInput/MultiInput.js.map +1 -1
- package/dist/components/primitives/OtpField/OtpField.d.ts.map +1 -1
- package/dist/components/primitives/OtpField/OtpField.js +10 -1
- package/dist/components/primitives/OtpField/OtpField.js.map +1 -1
- package/dist/components/primitives/Select/Select.d.ts.map +1 -1
- package/dist/components/primitives/Select/Select.js +9 -1
- package/dist/components/primitives/Select/Select.js.map +1 -1
- package/dist/components/primitives/TextField/TextField.d.ts.map +1 -1
- package/dist/components/primitives/TextField/TextField.js +10 -1
- package/dist/components/primitives/TextField/TextField.js.map +1 -1
- package/dist/components/primitives/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/primitives/Toggle/Toggle.js +9 -1
- package/dist/components/primitives/Toggle/Toggle.js.map +1 -1
- package/dist/contexts/Theme/ThemeProvider.d.ts.map +1 -1
- package/dist/contexts/Theme/ThemeProvider.js +1 -1
- package/dist/contexts/Theme/ThemeProvider.js.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDContext.d.ts +4 -3
- package/dist/contexts/ThunderID/ThunderIDContext.d.ts.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDContext.js.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.d.ts.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.js +1 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiInput.cjs","names":["MultiInput: FC<MultiInputProps>","useTheme","canAddMore: boolean","canRemove: boolean","styles: Record<string, string>","useStyles","PlusIcon: FC<IconProps>","cx","BinIcon: FC<IconProps>","handleAddValue: ReturnType<typeof useCallback>","handleRemoveValue: ReturnType<typeof useCallback>","renderInputField: ReturnType<typeof useCallback>","commonProps: Record<string, unknown>","DatePicker","Checkbox","TextField","handleInputSubmit: ReturnType<typeof useCallback>","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/MultiInput/MultiInput.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {CSSProperties, FC, KeyboardEvent, ReactElement, ReactNode, useCallback, useState} from 'react';\nimport useStyles from './MultiInput.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport Checkbox from '../Checkbox/Checkbox';\nimport DatePicker from '../DatePicker/DatePicker';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\nimport TextField from '../TextField/TextField';\n\nexport type MultiInputType = 'text' | 'email' | 'tel' | 'url' | 'password' | 'date' | 'boolean';\nexport type MultiInputFieldType = 'STRING' | 'DATE_TIME' | 'BOOLEAN';\n\nexport interface MultiInputProps {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Icon to display at the end (right) of each input (in addition to add/remove buttons)\n */\n endIcon?: ReactNode;\n /**\n * Error message to display below the inputs\n */\n error?: string;\n /**\n * Field type for different input components\n */\n fieldType?: MultiInputFieldType;\n /**\n * Helper text to display below the inputs\n */\n helperText?: string;\n /**\n * Label text to display above the inputs\n */\n label?: string;\n /**\n * Maximum number of fields to allow (default: unlimited)\n */\n maxFields?: number;\n /**\n * Minimum number of fields to show (default: 1)\n */\n minFields?: number;\n /**\n * Callback when values change\n */\n onChange: (values: string[]) => void;\n /**\n * Placeholder text for input fields\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Icon to display at the start (left) of each input\n */\n startIcon?: ReactNode;\n /**\n * Custom style object\n */\n style?: CSSProperties;\n /**\n * Input type\n */\n type?: MultiInputType;\n /**\n * Array of values\n */\n values: string[];\n}\n\ninterface IconProps {\n iconClassName?: string;\n}\n\nconst MultiInput: FC<MultiInputProps> = ({\n label,\n error,\n required,\n className,\n disabled,\n helperText,\n placeholder = 'Enter value',\n values = [],\n onChange,\n type = 'text',\n fieldType = 'STRING',\n startIcon,\n endIcon,\n minFields = 1,\n maxFields,\n style = {},\n}: MultiInputProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const canAddMore: boolean = !maxFields || values.length < maxFields;\n const canRemove: boolean = values.length > minFields;\n const styles: Record<string, string> = useStyles(theme, colorScheme, !!disabled, !!error, canAddMore, canRemove);\n\n const PlusIcon: FC<IconProps> = ({iconClassName}: IconProps): ReactElement => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" className={cx(styles['icon'], iconClassName)}>\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n );\n\n const BinIcon: FC<IconProps> = ({iconClassName}: IconProps): ReactElement => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" className={cx(styles['icon'], iconClassName)}>\n <path d=\"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14ZM10 11v6M14 11v6\" />\n </svg>\n );\n\n const handleAddValue: ReturnType<typeof useCallback> = useCallback(\n (newValue: string) => {\n if (newValue.trim() !== '' && (!maxFields || values.length < maxFields)) {\n onChange([...values, newValue.trim()]);\n }\n },\n [values, onChange, maxFields],\n );\n\n const handleRemoveValue: ReturnType<typeof useCallback> = useCallback(\n (index: number) => {\n if (values.length > minFields) {\n const updatedValues: string[] = values.filter((_: string, i: number) => i !== index);\n onChange(updatedValues);\n }\n },\n [values, onChange, minFields],\n );\n\n const renderInputField: ReturnType<typeof useCallback> = useCallback(\n (\n value: string,\n onValueChange: (value: string) => void,\n attachedEndIcon?: ReactNode,\n onEndIconClick?: () => void,\n ): ReactElement => {\n const handleInputChange = (e: any): void => {\n const newValue: string = e.target ? e.target.value : e;\n onValueChange(newValue);\n };\n\n const handleKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' && onEndIconClick) {\n e.preventDefault();\n onEndIconClick();\n }\n };\n\n const finalEndIcon: ReactNode = attachedEndIcon || endIcon;\n\n const commonProps: Record<string, unknown> = {\n disabled,\n endIcon: finalEndIcon,\n error,\n onChange: handleInputChange,\n onEndIconClick,\n onKeyDown: handleKeyDown,\n placeholder,\n startIcon,\n value,\n };\n\n switch (fieldType) {\n case 'DATE_TIME':\n return <DatePicker {...commonProps} />;\n case 'BOOLEAN':\n return (\n <Checkbox\n {...commonProps}\n checked={value === 'true' || Boolean(value)}\n onChange={(e: any): void => onValueChange(e.target.checked ? 'true' : 'false')}\n />\n );\n default:\n return <TextField {...commonProps} type={type} />;\n }\n },\n [placeholder, disabled, startIcon, endIcon, error, fieldType, type],\n );\n\n // State for the current input value\n const [currentInputValue, setCurrentInputValue] = useState('');\n\n const handleInputSubmit: ReturnType<typeof useCallback> = useCallback((): void => {\n if (currentInputValue.trim() !== '') {\n handleAddValue(currentInputValue);\n setCurrentInputValue('');\n }\n }, [currentInputValue, handleAddValue]);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('multi-input')), className)}\n style={style}\n >\n {label && (\n <InputLabel required={required} error={!!error}>\n {label}\n </InputLabel>\n )}\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'container')), styles['container'])}>\n {/* Input field at the top */}\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'input-row')), styles['inputRow'])}>\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'input-wrapper')), styles['inputWrapper'])}>\n {renderInputField(\n currentInputValue,\n setCurrentInputValue,\n canAddMore ? <PlusIcon iconClassName={styles['plusIcon']} /> : undefined,\n canAddMore ? handleInputSubmit : undefined,\n )}\n </div>\n </div>\n\n {/* List of added items */}\n {values.length > 0 && (\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-container')), styles['listContainer'])}>\n {values.map((value: string, index: number) => (\n <div\n key={index}\n className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-item')), styles['listItem'])}\n >\n <span\n className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-item-text')), styles['listItemText'])}\n >\n {value}\n </span>\n {canRemove && (\n <button\n type=\"button\"\n onClick={(): void => handleRemoveValue(index)}\n disabled={disabled}\n className={cx(\n withVendorCSSClassPrefix(bem('multi-input', 'remove-button')),\n styles['removeButton'],\n )}\n title=\"Remove value\"\n >\n <BinIcon iconClassName={styles['icon']} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </FormControl>\n );\n};\n\nexport default MultiInput;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MultiInput.cjs","names":["MultiInput: FC<MultiInputProps>","useTheme","inputId: string","helperTextId: string | undefined","canAddMore: boolean","canRemove: boolean","styles: Record<string, string>","useStyles","PlusIcon: FC<IconProps>","cx","BinIcon: FC<IconProps>","handleAddValue: ReturnType<typeof useCallback>","handleRemoveValue: ReturnType<typeof useCallback>","renderInputField: ReturnType<typeof useCallback>","finalEndIcon: ReactNode","commonProps: Record<string, unknown>","DatePicker","Checkbox","TextField","handleInputSubmit: ReturnType<typeof useCallback>","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/MultiInput/MultiInput.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {CSSProperties, FC, KeyboardEvent, ReactElement, ReactNode, useCallback, useId, useState} from 'react';\nimport useStyles from './MultiInput.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport Checkbox from '../Checkbox/Checkbox';\nimport DatePicker from '../DatePicker/DatePicker';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\nimport TextField from '../TextField/TextField';\n\nexport type MultiInputType = 'text' | 'email' | 'tel' | 'url' | 'password' | 'date' | 'boolean';\nexport type MultiInputFieldType = 'STRING' | 'DATE_TIME' | 'BOOLEAN';\n\nexport interface MultiInputProps {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Icon to display at the end (right) of each input (in addition to add/remove buttons)\n */\n endIcon?: ReactNode;\n /**\n * Error message to display below the inputs\n */\n error?: string;\n /**\n * Field type for different input components\n */\n fieldType?: MultiInputFieldType;\n /**\n * Helper text to display below the inputs\n */\n helperText?: string;\n /**\n * Label text to display above the inputs\n */\n label?: string;\n /**\n * Maximum number of fields to allow (default: unlimited)\n */\n maxFields?: number;\n /**\n * Minimum number of fields to show (default: 1)\n */\n minFields?: number;\n /**\n * Callback when values change\n */\n onChange: (values: string[]) => void;\n /**\n * Placeholder text for input fields\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Icon to display at the start (left) of each input\n */\n startIcon?: ReactNode;\n /**\n * Custom style object\n */\n style?: CSSProperties;\n /**\n * Input type\n */\n type?: MultiInputType;\n /**\n * Array of values\n */\n values: string[];\n}\n\ninterface IconProps {\n iconClassName?: string;\n}\n\nconst MultiInput: FC<MultiInputProps> = ({\n label,\n error,\n required,\n className,\n disabled,\n helperText,\n placeholder = 'Enter value',\n values = [],\n onChange,\n type = 'text',\n fieldType = 'STRING',\n startIcon,\n endIcon,\n minFields = 1,\n maxFields,\n style = {},\n}: MultiInputProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const inputId: string = useId();\n // The inner field renders its own helper text with `${inputId}-helper-text`, so this one needs a distinct id.\n const helperTextId: string | undefined = error || helperText ? `${inputId}-description` : undefined;\n const canAddMore: boolean = !maxFields || values.length < maxFields;\n const canRemove: boolean = values.length > minFields;\n const styles: Record<string, string> = useStyles(theme, colorScheme, !!disabled, !!error, canAddMore, canRemove);\n\n const PlusIcon: FC<IconProps> = ({iconClassName}: IconProps): ReactElement => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" className={cx(styles['icon'], iconClassName)}>\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n );\n\n const BinIcon: FC<IconProps> = ({iconClassName}: IconProps): ReactElement => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" className={cx(styles['icon'], iconClassName)}>\n <path d=\"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14ZM10 11v6M14 11v6\" />\n </svg>\n );\n\n const handleAddValue: ReturnType<typeof useCallback> = useCallback(\n (newValue: string) => {\n if (newValue.trim() !== '' && (!maxFields || values.length < maxFields)) {\n onChange([...values, newValue.trim()]);\n }\n },\n [values, onChange, maxFields],\n );\n\n const handleRemoveValue: ReturnType<typeof useCallback> = useCallback(\n (index: number) => {\n if (values.length > minFields) {\n const updatedValues: string[] = values.filter((_: string, i: number) => i !== index);\n onChange(updatedValues);\n }\n },\n [values, onChange, minFields],\n );\n\n const renderInputField: ReturnType<typeof useCallback> = useCallback(\n (\n value: string,\n onValueChange: (value: string) => void,\n attachedEndIcon?: ReactNode,\n onEndIconClick?: () => void,\n ): ReactElement => {\n const handleInputChange = (e: any): void => {\n const newValue: string = e.target ? e.target.value : e;\n onValueChange(newValue);\n };\n\n const handleKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' && onEndIconClick) {\n e.preventDefault();\n onEndIconClick();\n }\n };\n\n const finalEndIcon: ReactNode = attachedEndIcon || endIcon;\n\n const commonProps: Record<string, unknown> = {\n 'aria-describedby': helperTextId,\n disabled,\n endIcon: finalEndIcon,\n error,\n id: inputId,\n onChange: handleInputChange,\n onEndIconClick,\n onKeyDown: handleKeyDown,\n placeholder,\n startIcon,\n value,\n };\n\n switch (fieldType) {\n case 'DATE_TIME':\n return <DatePicker {...commonProps} />;\n case 'BOOLEAN':\n return (\n <Checkbox\n {...commonProps}\n checked={value === 'true' || Boolean(value)}\n onChange={(e: any): void => onValueChange(e.target.checked ? 'true' : 'false')}\n />\n );\n default:\n return <TextField {...commonProps} type={type} />;\n }\n },\n [placeholder, disabled, startIcon, endIcon, error, fieldType, type, inputId, helperTextId],\n );\n\n // State for the current input value\n const [currentInputValue, setCurrentInputValue] = useState('');\n\n const handleInputSubmit: ReturnType<typeof useCallback> = useCallback((): void => {\n if (currentInputValue.trim() !== '') {\n handleAddValue(currentInputValue);\n setCurrentInputValue('');\n }\n }, [currentInputValue, handleAddValue]);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n helperTextId={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('multi-input')), className)}\n style={style}\n >\n {label && (\n <InputLabel htmlFor={inputId} required={required} error={!!error}>\n {label}\n </InputLabel>\n )}\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'container')), styles['container'])}>\n {/* Input field at the top */}\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'input-row')), styles['inputRow'])}>\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'input-wrapper')), styles['inputWrapper'])}>\n {renderInputField(\n currentInputValue,\n setCurrentInputValue,\n canAddMore ? <PlusIcon iconClassName={styles['plusIcon']} /> : undefined,\n canAddMore ? handleInputSubmit : undefined,\n )}\n </div>\n </div>\n\n {/* List of added items */}\n {values.length > 0 && (\n <div className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-container')), styles['listContainer'])}>\n {values.map((value: string, index: number) => (\n <div\n key={index}\n className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-item')), styles['listItem'])}\n >\n <span\n className={cx(withVendorCSSClassPrefix(bem('multi-input', 'list-item-text')), styles['listItemText'])}\n >\n {value}\n </span>\n {canRemove && (\n <button\n type=\"button\"\n onClick={(): void => handleRemoveValue(index)}\n disabled={disabled}\n className={cx(\n withVendorCSSClassPrefix(bem('multi-input', 'remove-button')),\n styles['removeButton'],\n )}\n title=\"Remove value\"\n >\n <BinIcon iconClassName={styles['icon']} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </FormControl>\n );\n};\n\nexport default MultiInput;\n"],"mappings":";;;;;;;;;;;;;;;;;AAwFA,MAAMA,cAAmC,EACvC,OACA,OACA,UACA,WACA,UACA,YACA,cAAc,eACd,SAAS,EAAE,EACX,UACA,OAAO,QACP,YAAY,UACZ,WACA,SACA,YAAY,GACZ,WACA,QAAQ,EAAE,OACW;CACrB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAMC,4BAAyB;CAE/B,MAAMC,eAAmC,SAAS,aAAa,GAAG,QAAQ,gBAAgB;CAC1F,MAAMC,aAAsB,CAAC,aAAa,OAAO,SAAS;CAC1D,MAAMC,YAAqB,OAAO,SAAS;CAC3C,MAAMC,SAAiCC,kCAAU,OAAO,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,OAAO,YAAY,UAAU;CAEhH,MAAMC,YAA2B,EAAC,oBAChC,2CAAC;EAAI,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,WAAWC,mBAAG,OAAO,SAAS,cAAc;YAC1F,2CAAC,UAAK,GAAE,qBAAqB;GACzB;CAGR,MAAMC,WAA0B,EAAC,oBAC/B,2CAAC;EAAI,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,WAAWD,mBAAG,OAAO,SAAS,cAAc;YAC1F,2CAAC,UAAK,GAAE,6GAA6G;GACjH;CAGR,MAAME,yCACH,aAAqB;AACpB,MAAI,SAAS,MAAM,KAAK,OAAO,CAAC,aAAa,OAAO,SAAS,WAC3D,UAAS,CAAC,GAAG,QAAQ,SAAS,MAAM,CAAC,CAAC;IAG1C;EAAC;EAAQ;EAAU;EAAU,CAC9B;CAED,MAAMC,4CACH,UAAkB;AACjB,MAAI,OAAO,SAAS,UAElB,UADgC,OAAO,QAAQ,GAAW,MAAc,MAAM,MAAM,CAC7D;IAG3B;EAAC;EAAQ;EAAU;EAAU,CAC9B;CAED,MAAMC,2CAEF,OACA,eACA,iBACA,mBACiB;EACjB,MAAM,qBAAqB,MAAiB;AAE1C,iBADyB,EAAE,SAAS,EAAE,OAAO,QAAQ,EAC9B;;EAGzB,MAAM,iBAAiB,MAA2B;AAChD,OAAI,EAAE,QAAQ,WAAW,gBAAgB;AACvC,MAAE,gBAAgB;AAClB,oBAAgB;;;EAIpB,MAAMC,eAA0B,mBAAmB;EAEnD,MAAMC,cAAuC;GAC3C,oBAAoB;GACpB;GACA,SAAS;GACT;GACA,IAAI;GACJ,UAAU;GACV;GACA,WAAW;GACX;GACA;GACA;GACD;AAED,UAAQ,WAAR;GACE,KAAK,YACH,QAAO,2CAACC,8BAAW,GAAI,cAAe;GACxC,KAAK,UACH,QACE,2CAACC;IACC,GAAI;IACJ,SAAS,UAAU,UAAU,QAAQ,MAAM;IAC3C,WAAW,MAAiB,cAAc,EAAE,OAAO,UAAU,SAAS,QAAQ;KAC9E;GAEN,QACE,QAAO,2CAACC;IAAU,GAAI;IAAmB;KAAQ;;IAGvD;EAAC;EAAa;EAAU;EAAW;EAAS;EAAO;EAAW;EAAM;EAAS;EAAa,CAC3F;CAGD,MAAM,CAAC,mBAAmB,4CAAiC,GAAG;CAE9D,MAAMC,iDAA4E;AAChF,MAAI,kBAAkB,MAAM,KAAK,IAAI;AACnC,kBAAe,kBAAkB;AACjC,wBAAqB,GAAG;;IAEzB,CAAC,mBAAmB,eAAe,CAAC;AAEvC,QACE,4CAACC;EACQ;EACK;EACE;EACd,WAAWX,kGAAgC,cAAc,CAAC,EAAE,UAAU;EAC/D;aAEN,SACC,2CAACY;GAAW,SAAS;GAAmB;GAAU,OAAO,CAAC,CAAC;aACxD;IACU,EAEf,4CAAC;GAAI,WAAWZ,kGAAgC,eAAe,YAAY,CAAC,EAAE,OAAO,aAAa;cAEhG,2CAAC;IAAI,WAAWA,kGAAgC,eAAe,YAAY,CAAC,EAAE,OAAO,YAAY;cAC/F,2CAAC;KAAI,WAAWA,kGAAgC,eAAe,gBAAgB,CAAC,EAAE,OAAO,gBAAgB;eACtG,iBACC,mBACA,sBACA,aAAa,2CAAC,YAAS,eAAe,OAAO,cAAe,GAAG,QAC/D,aAAa,oBAAoB,OAClC;MACG;KACF,EAGL,OAAO,SAAS,KACf,2CAAC;IAAI,WAAWA,kGAAgC,eAAe,iBAAiB,CAAC,EAAE,OAAO,iBAAiB;cACxG,OAAO,KAAK,OAAe,UAC1B,4CAAC;KAEC,WAAWA,kGAAgC,eAAe,YAAY,CAAC,EAAE,OAAO,YAAY;gBAE5F,2CAAC;MACC,WAAWA,kGAAgC,eAAe,iBAAiB,CAAC,EAAE,OAAO,gBAAgB;gBAEpG;OACI,EACN,aACC,2CAAC;MACC,MAAK;MACL,eAAqB,kBAAkB,MAAM;MACnC;MACV,WAAWA,kGACoB,eAAe,gBAAgB,CAAC,EAC7D,OAAO,gBACR;MACD,OAAM;gBAEN,2CAAC,WAAQ,eAAe,OAAO,UAAW;OACnC;OApBN,MAsBD,CACN;KACE;IAEJ;GACM;;AAIlB,yBAAe"}
|
|
@@ -17,6 +17,9 @@ const OtpField = ({ label, error, className, required, disabled, helperText, len
|
|
|
17
17
|
const styles = require_OtpField_styles.default(theme, colorScheme, !!disabled, !!error, length);
|
|
18
18
|
const [otp, setOtp] = (0, react.useState)(Array(length).fill(""));
|
|
19
19
|
const inputRefs = (0, react.useRef)([]);
|
|
20
|
+
const fieldId = (0, react.useId)();
|
|
21
|
+
const labelId = `${fieldId}-label`;
|
|
22
|
+
const helperTextId = error || helperText ? `${fieldId}-helper-text` : void 0;
|
|
20
23
|
(0, react.useEffect)(() => {
|
|
21
24
|
inputRefs.current = inputRefs.current.slice(0, length);
|
|
22
25
|
}, [length]);
|
|
@@ -86,14 +89,19 @@ const OtpField = ({ label, error, className, required, disabled, helperText, len
|
|
|
86
89
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_FormControl.default, {
|
|
87
90
|
error,
|
|
88
91
|
helperText,
|
|
92
|
+
helperTextId,
|
|
89
93
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("otp-field")), className),
|
|
90
94
|
helperTextAlign: "center",
|
|
91
95
|
style,
|
|
92
96
|
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputLabel.default, {
|
|
97
|
+
id: labelId,
|
|
93
98
|
required,
|
|
94
99
|
error: !!error,
|
|
95
100
|
children: label
|
|
96
101
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
102
|
+
role: "group",
|
|
103
|
+
"aria-labelledby": label ? labelId : void 0,
|
|
104
|
+
"aria-describedby": helperTextId,
|
|
97
105
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("otp-field", "input-container")), styles["inputContainer"]),
|
|
98
106
|
children: Array.from({ length }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
99
107
|
ref: (el) => {
|
|
@@ -117,6 +125,7 @@ const OtpField = ({ label, error, className, required, disabled, helperText, len
|
|
|
117
125
|
"aria-label": `${label || "OTP"} digit ${index + 1}`,
|
|
118
126
|
"aria-invalid": !!error,
|
|
119
127
|
"aria-required": required,
|
|
128
|
+
"aria-describedby": helperTextId,
|
|
120
129
|
autoComplete: "one-time-code"
|
|
121
130
|
}, index))
|
|
122
131
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OtpField.cjs","names":["OtpField: FC<OtpInputProps>","useTheme","styles: Record<string, string>","useStyles","inputRefs: MutableRefObject<HTMLInputElement[]>","newOtp: string[]","newValue: string","otpValue: string","rawData: string","pastedData: string","nextEmptyIndex: number","focusIndex: number","FormControl","cx","InputLabel"],"sources":["../../../../../src/components/primitives/OtpField/OtpField.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {\n ClipboardEvent,\n FC,\n KeyboardEvent,\n ChangeEvent,\n MutableRefObject,\n useRef,\n useEffect,\n useState,\n CSSProperties,\n} from 'react';\nimport useStyles from './OtpField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport type OtpFieldType = 'text' | 'number' | 'password';\n\nexport interface OtpInputProps {\n /**\n * Auto focus the first input on mount\n */\n autoFocus?: boolean;\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Error message to display below the OTP input\n */\n error?: string;\n /**\n * Helper text to display below the OTP input\n */\n helperText?: string;\n /**\n * Label text to display above the OTP input\n */\n label?: string;\n /**\n * Number of OTP input fields\n */\n length?: number;\n /**\n * Callback function called when OTP value changes\n */\n onChange?: (event: {target: {value: string}}) => void;\n /**\n * Callback function called when OTP input is complete\n */\n onComplete?: (value: string) => void;\n /**\n * Pattern for numeric input validation\n */\n pattern?: string;\n /**\n * Placeholder character for each input field\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Custom container style\n */\n style?: CSSProperties;\n /**\n * Type of input (text, number, password)\n */\n type?: OtpFieldType;\n /**\n * Upper-case entered characters before emitting them\n */\n uppercase?: boolean;\n /**\n * Current OTP value\n */\n value?: string;\n}\n\nconst OtpField: FC<OtpInputProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n length = 6,\n value = '',\n onChange,\n onComplete,\n type = 'text',\n placeholder = '',\n style = {},\n autoFocus = false,\n pattern,\n uppercase = false,\n}: OtpInputProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const styles: Record<string, string> = useStyles(theme, colorScheme, !!disabled, !!error, length);\n const [otp, setOtp] = useState<string[]>(Array(length).fill(''));\n const inputRefs: MutableRefObject<HTMLInputElement[]> = useRef<HTMLInputElement[]>([]);\n\n useEffect(() => {\n inputRefs.current = inputRefs.current.slice(0, length);\n }, [length]);\n\n useEffect(() => {\n if (value) {\n const newOtp: string[] = value.split('').slice(0, length);\n while (newOtp.length < length) {\n newOtp.push('');\n }\n setOtp(newOtp);\n } else {\n setOtp(Array(length).fill(''));\n }\n }, [value, length]);\n\n useEffect(() => {\n if (autoFocus && inputRefs.current[0]) {\n inputRefs.current[0].focus();\n }\n }, [autoFocus]);\n\n const handleChange = (index: number, event: ChangeEvent<HTMLInputElement>): void => {\n const newValue: string = uppercase ? event.target.value.toUpperCase() : event.target.value;\n\n if (newValue.length > 1) return;\n\n if (type === 'number' && newValue && !/^\\d$/.test(newValue)) return;\n\n if (pattern && newValue && !new RegExp(pattern).test(newValue)) return;\n\n const newOtp: string[] = [...otp];\n newOtp[index] = newValue;\n setOtp(newOtp);\n\n const otpValue: string = newOtp.join('');\n\n onChange?.({target: {value: otpValue}});\n\n if (newValue && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n\n if (newOtp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(otpValue);\n }\n };\n\n const handleKeyDown = (index: number, event: KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'Backspace') {\n if (!otp[index] && index > 0) {\n const newOtp: string[] = [...otp];\n\n newOtp[index - 1] = '';\n setOtp(newOtp);\n inputRefs.current[index - 1]?.focus();\n onChange?.({target: {value: newOtp.join('')}});\n } else if (otp[index]) {\n const newOtp: string[] = [...otp];\n\n newOtp[index] = '';\n setOtp(newOtp);\n onChange?.({target: {value: newOtp.join('')}});\n }\n } else if (event.key === 'ArrowLeft' && index > 0) {\n inputRefs.current[index - 1]?.focus();\n } else if (event.key === 'ArrowRight' && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n } else if (event.key === 'Enter') {\n event.preventDefault();\n\n if (otp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(otp.join(''));\n }\n }\n };\n\n const handlePaste = (event: ClipboardEvent<HTMLInputElement>): void => {\n event.preventDefault();\n\n const rawData: string = event.clipboardData.getData('text');\n // Filter first and let the copy loop below bound the result, so surrounding text such as\n // \"Your code is 123456\" does not push the code itself past the cut-off.\n const pastedData: string = uppercase ? rawData.toUpperCase() : rawData;\n\n let validData = '';\n\n Array.from(pastedData).forEach((char: string) => {\n if (type === 'number' && !/^\\d$/.test(char)) return;\n if (pattern && !new RegExp(pattern).test(char)) return;\n validData += char;\n });\n\n const newOtp: string[] = Array(length).fill('');\n\n for (let i = 0; i < Math.min(validData.length, length); i += 1) {\n newOtp[i] = validData[i];\n }\n\n setOtp(newOtp);\n onChange?.({target: {value: newOtp.join('')}});\n\n const nextEmptyIndex: number = newOtp.findIndex((digit: string) => digit === '');\n const focusIndex: number = nextEmptyIndex !== -1 ? nextEmptyIndex : length - 1;\n\n inputRefs.current[focusIndex]?.focus();\n\n if (newOtp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(newOtp.join(''));\n }\n };\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('otp-field')), className)}\n helperTextAlign=\"center\"\n style={style}\n >\n {label && (\n <InputLabel required={required} error={!!error}>\n {label}\n </InputLabel>\n )}\n <div className={cx(withVendorCSSClassPrefix(bem('otp-field', 'input-container')), styles['inputContainer'])}>\n {Array.from({length}, (_: unknown, index: number) => (\n <input\n key={index}\n ref={(el: HTMLInputElement | null): void => {\n if (el) inputRefs.current[index] = el;\n }}\n type={type === 'password' ? 'password' : 'text'}\n inputMode={type === 'number' ? 'numeric' : 'text'}\n value={otp[index] || ''}\n onChange={(event: ChangeEvent<HTMLInputElement>): void => handleChange(index, event)}\n onKeyDown={(event: KeyboardEvent<HTMLInputElement>): void => handleKeyDown(index, event)}\n onPaste={handlePaste}\n className={cx(withVendorCSSClassPrefix(bem('otp-field', 'input')), styles['input'], {\n [withVendorCSSClassPrefix(bem('otp-field', 'input', 'error'))]: !!error,\n [styles['inputError']]: !!error,\n [withVendorCSSClassPrefix(bem('otp-field', 'input', 'disabled'))]: !!disabled,\n [styles['inputDisabled']]: !!disabled,\n })}\n maxLength={1}\n placeholder={placeholder}\n disabled={disabled}\n aria-label={`${label || 'OTP'} digit ${index + 1}`}\n aria-invalid={!!error}\n aria-required={required}\n autoComplete=\"one-time-code\"\n />\n ))}\n </div>\n </FormControl>\n );\n};\n\nexport default OtpField;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OtpField.cjs","names":["OtpField: FC<OtpInputProps>","useTheme","styles: Record<string, string>","useStyles","inputRefs: MutableRefObject<HTMLInputElement[]>","fieldId: string","helperTextId: string | undefined","newOtp: string[]","newValue: string","otpValue: string","rawData: string","pastedData: string","nextEmptyIndex: number","focusIndex: number","FormControl","cx","InputLabel"],"sources":["../../../../../src/components/primitives/OtpField/OtpField.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {\n ClipboardEvent,\n FC,\n KeyboardEvent,\n ChangeEvent,\n MutableRefObject,\n useRef,\n useEffect,\n useState,\n CSSProperties,\n useId,\n} from 'react';\nimport useStyles from './OtpField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport type OtpFieldType = 'text' | 'number' | 'password';\n\nexport interface OtpInputProps {\n /**\n * Auto focus the first input on mount\n */\n autoFocus?: boolean;\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Error message to display below the OTP input\n */\n error?: string;\n /**\n * Helper text to display below the OTP input\n */\n helperText?: string;\n /**\n * Label text to display above the OTP input\n */\n label?: string;\n /**\n * Number of OTP input fields\n */\n length?: number;\n /**\n * Callback function called when OTP value changes\n */\n onChange?: (event: {target: {value: string}}) => void;\n /**\n * Callback function called when OTP input is complete\n */\n onComplete?: (value: string) => void;\n /**\n * Pattern for numeric input validation\n */\n pattern?: string;\n /**\n * Placeholder character for each input field\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Custom container style\n */\n style?: CSSProperties;\n /**\n * Type of input (text, number, password)\n */\n type?: OtpFieldType;\n /**\n * Upper-case entered characters before emitting them\n */\n uppercase?: boolean;\n /**\n * Current OTP value\n */\n value?: string;\n}\n\nconst OtpField: FC<OtpInputProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n length = 6,\n value = '',\n onChange,\n onComplete,\n type = 'text',\n placeholder = '',\n style = {},\n autoFocus = false,\n pattern,\n uppercase = false,\n}: OtpInputProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const styles: Record<string, string> = useStyles(theme, colorScheme, !!disabled, !!error, length);\n const [otp, setOtp] = useState<string[]>(Array(length).fill(''));\n const inputRefs: MutableRefObject<HTMLInputElement[]> = useRef<HTMLInputElement[]>([]);\n const fieldId: string = useId();\n const labelId = `${fieldId}-label`;\n const helperTextId: string | undefined = error || helperText ? `${fieldId}-helper-text` : undefined;\n\n useEffect(() => {\n inputRefs.current = inputRefs.current.slice(0, length);\n }, [length]);\n\n useEffect(() => {\n if (value) {\n const newOtp: string[] = value.split('').slice(0, length);\n while (newOtp.length < length) {\n newOtp.push('');\n }\n setOtp(newOtp);\n } else {\n setOtp(Array(length).fill(''));\n }\n }, [value, length]);\n\n useEffect(() => {\n if (autoFocus && inputRefs.current[0]) {\n inputRefs.current[0].focus();\n }\n }, [autoFocus]);\n\n const handleChange = (index: number, event: ChangeEvent<HTMLInputElement>): void => {\n const newValue: string = uppercase ? event.target.value.toUpperCase() : event.target.value;\n\n if (newValue.length > 1) return;\n\n if (type === 'number' && newValue && !/^\\d$/.test(newValue)) return;\n\n if (pattern && newValue && !new RegExp(pattern).test(newValue)) return;\n\n const newOtp: string[] = [...otp];\n newOtp[index] = newValue;\n setOtp(newOtp);\n\n const otpValue: string = newOtp.join('');\n\n onChange?.({target: {value: otpValue}});\n\n if (newValue && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n\n if (newOtp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(otpValue);\n }\n };\n\n const handleKeyDown = (index: number, event: KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'Backspace') {\n if (!otp[index] && index > 0) {\n const newOtp: string[] = [...otp];\n\n newOtp[index - 1] = '';\n setOtp(newOtp);\n inputRefs.current[index - 1]?.focus();\n onChange?.({target: {value: newOtp.join('')}});\n } else if (otp[index]) {\n const newOtp: string[] = [...otp];\n\n newOtp[index] = '';\n setOtp(newOtp);\n onChange?.({target: {value: newOtp.join('')}});\n }\n } else if (event.key === 'ArrowLeft' && index > 0) {\n inputRefs.current[index - 1]?.focus();\n } else if (event.key === 'ArrowRight' && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n } else if (event.key === 'Enter') {\n event.preventDefault();\n\n if (otp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(otp.join(''));\n }\n }\n };\n\n const handlePaste = (event: ClipboardEvent<HTMLInputElement>): void => {\n event.preventDefault();\n\n const rawData: string = event.clipboardData.getData('text');\n // Filter first and let the copy loop below bound the result, so surrounding text such as\n // \"Your code is 123456\" does not push the code itself past the cut-off.\n const pastedData: string = uppercase ? rawData.toUpperCase() : rawData;\n\n let validData = '';\n\n Array.from(pastedData).forEach((char: string) => {\n if (type === 'number' && !/^\\d$/.test(char)) return;\n if (pattern && !new RegExp(pattern).test(char)) return;\n validData += char;\n });\n\n const newOtp: string[] = Array(length).fill('');\n\n for (let i = 0; i < Math.min(validData.length, length); i += 1) {\n newOtp[i] = validData[i];\n }\n\n setOtp(newOtp);\n onChange?.({target: {value: newOtp.join('')}});\n\n const nextEmptyIndex: number = newOtp.findIndex((digit: string) => digit === '');\n const focusIndex: number = nextEmptyIndex !== -1 ? nextEmptyIndex : length - 1;\n\n inputRefs.current[focusIndex]?.focus();\n\n if (newOtp.every((digit: string) => digit !== '') && onComplete) {\n onComplete(newOtp.join(''));\n }\n };\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n helperTextId={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('otp-field')), className)}\n helperTextAlign=\"center\"\n style={style}\n >\n {label && (\n <InputLabel id={labelId} required={required} error={!!error}>\n {label}\n </InputLabel>\n )}\n <div\n role=\"group\"\n aria-labelledby={label ? labelId : undefined}\n aria-describedby={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('otp-field', 'input-container')), styles['inputContainer'])}\n >\n {Array.from({length}, (_: unknown, index: number) => (\n <input\n key={index}\n ref={(el: HTMLInputElement | null): void => {\n if (el) inputRefs.current[index] = el;\n }}\n type={type === 'password' ? 'password' : 'text'}\n inputMode={type === 'number' ? 'numeric' : 'text'}\n value={otp[index] || ''}\n onChange={(event: ChangeEvent<HTMLInputElement>): void => handleChange(index, event)}\n onKeyDown={(event: KeyboardEvent<HTMLInputElement>): void => handleKeyDown(index, event)}\n onPaste={handlePaste}\n className={cx(withVendorCSSClassPrefix(bem('otp-field', 'input')), styles['input'], {\n [withVendorCSSClassPrefix(bem('otp-field', 'input', 'error'))]: !!error,\n [styles['inputError']]: !!error,\n [withVendorCSSClassPrefix(bem('otp-field', 'input', 'disabled'))]: !!disabled,\n [styles['inputDisabled']]: !!disabled,\n })}\n maxLength={1}\n placeholder={placeholder}\n disabled={disabled}\n aria-label={`${label || 'OTP'} digit ${index + 1}`}\n aria-invalid={!!error}\n aria-required={required}\n aria-describedby={helperTextId}\n autoComplete=\"one-time-code\"\n />\n ))}\n </div>\n </FormControl>\n );\n};\n\nexport default OtpField;\n"],"mappings":"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"}
|
|
@@ -17,6 +17,10 @@ const Select = ({ label, error, className, required, disabled, helperText, place
|
|
|
17
17
|
const { theme, colorScheme } = require_useTheme.default();
|
|
18
18
|
const messages = require_useSelectMessages.default();
|
|
19
19
|
const hasError = !!error;
|
|
20
|
+
const generatedId = (0, react.useId)();
|
|
21
|
+
const inputId = rest.id ?? generatedId;
|
|
22
|
+
const helperTextId = error || helperText ? `${inputId}-helper-text` : void 0;
|
|
23
|
+
const describedBy = [rest["aria-describedby"], helperTextId].filter(Boolean).join(" ") || void 0;
|
|
20
24
|
const styles = require_Select_styles.default(theme, colorScheme, disabled ?? false, hasError);
|
|
21
25
|
const items = (0, react.useMemo)(() => (0, __thunderid_browser.normalizeSelectOptions)(options), [options]);
|
|
22
26
|
const selectRef = (0, react.useRef)(null);
|
|
@@ -44,10 +48,12 @@ const Select = ({ label, error, className, required, disabled, helperText, place
|
|
|
44
48
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_FormControl.default, {
|
|
45
49
|
error,
|
|
46
50
|
helperText,
|
|
51
|
+
helperTextId,
|
|
47
52
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("select")), className),
|
|
48
53
|
style,
|
|
49
54
|
children: [
|
|
50
55
|
label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputLabel.default, {
|
|
56
|
+
htmlFor: inputId,
|
|
51
57
|
required,
|
|
52
58
|
error: hasError,
|
|
53
59
|
children: label
|
|
@@ -59,6 +65,8 @@ const Select = ({ label, error, className, required, disabled, helperText, place
|
|
|
59
65
|
"aria-required": required,
|
|
60
66
|
"aria-busy": loading || void 0,
|
|
61
67
|
...rest,
|
|
68
|
+
id: inputId,
|
|
69
|
+
"aria-describedby": describedBy,
|
|
62
70
|
ref: selectRef,
|
|
63
71
|
onChange: onLoadMore ? handleChange : onChange,
|
|
64
72
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.cjs","names":["Select: FC<SelectProps>","useTheme","messages: SelectMessages","useSelectMessages","styles: Record<string, string>","useStyles","items: SelectOption[]","selectRef: RefObject<HTMLSelectElement | null>","chosenValueRef: RefObject<string>","current: string | undefined","SelectConstants","selectClassName: string","cx","showsSelectedOption: boolean","statusText: string | undefined","hasStatus: boolean","showsLoadMore: boolean","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/Select/Select.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {bem, normalizeSelectOptions, SelectConstants, withVendorCSSClassPrefix} from '@thunderid/browser';\nimport type {SelectOption, SelectOptionInput} from '@thunderid/browser';\nimport {ChangeEvent, FC, RefObject, SelectHTMLAttributes, useEffect, useMemo, useRef} from 'react';\nimport useStyles from './Select.styles';\nimport useSelectMessages, {SelectMessages} from './useSelectMessages';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport type {SelectOption, SelectOptionInput};\n\nexport interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Text shown below the select when it has no options, nothing is loading and there is nothing more to load\n */\n emptyText?: string;\n /**\n * Error message to display below the select\n */\n error?: string;\n /**\n * Helper text to display below the select\n */\n helperText?: string;\n /**\n * Label text to display above the select\n */\n label?: string;\n /**\n * Whether options are being fetched. The load more option waits, and a select with no options shows a loading message\n */\n loading?: boolean;\n /**\n * Called when the load more option, listed after the last one, is chosen. Choosing it does not change the selection.\n * The option is listed only when this is set, so pass it only while more options may be available\n */\n onLoadMore?: () => void;\n /**\n * The options to display: strings, numbers, or `{label, value, disabled?}` objects\n */\n options?: readonly (SelectOptionInput | null | undefined)[] | null;\n /**\n * Placeholder text for the default/empty option. While the field is not required, choosing it clears the selection\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Label of the selected option while `value` is not among `options`, such as a value loaded from saved data\n */\n selectedOption?: SelectOption;\n}\n\nconst Select: FC<SelectProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n placeholder,\n options = undefined,\n emptyText = undefined,\n loading = false,\n onLoadMore = undefined,\n onChange = undefined,\n selectedOption = undefined,\n style = {},\n ...rest\n}: SelectProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const messages: SelectMessages = useSelectMessages();\n const hasError = !!error;\n const styles: Record<string, string> = useStyles(theme, colorScheme, disabled ?? false, hasError);\n const items: SelectOption[] = useMemo(() => normalizeSelectOptions(options), [options]);\n const selectRef: RefObject<HTMLSelectElement | null> = useRef<HTMLSelectElement>(null);\n const chosenValueRef: RefObject<string> = useRef<string>('');\n\n // The value to put back when the load more option is chosen, since choosing it is not a selection.\n useEffect(() => {\n const current: string | undefined = selectRef.current?.value;\n\n if (current !== undefined && current !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = current;\n }\n });\n\n const handleChange = (event: ChangeEvent<HTMLSelectElement>): void => {\n if (event.target.value !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = event.target.value;\n onChange?.(event);\n\n return;\n }\n\n event.target.value = chosenValueRef.current;\n onLoadMore?.();\n };\n\n const selectClassName: string = cx(\n withVendorCSSClassPrefix(bem('select', 'input')),\n styles['select'],\n hasError && styles['selectError'],\n disabled && styles['selectDisabled'],\n );\n\n const showsSelectedOption: boolean =\n selectedOption !== undefined &&\n rest.value !== undefined &&\n String(rest.value) === selectedOption.value &&\n !items.some((item: SelectOption) => item.value === selectedOption.value);\n\n let statusText: string | undefined;\n\n if (loading) {\n statusText = items.length === 0 ? messages.loading : messages.loadingMore;\n } else if (items.length === 0 && !onLoadMore) {\n statusText = emptyText;\n }\n\n const hasStatus: boolean = onLoadMore !== undefined || emptyText !== undefined || loading;\n const showsLoadMore: boolean = onLoadMore !== undefined && (items.length > 0 || !loading);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('select')), className)}\n style={style}\n >\n {label && (\n <InputLabel required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <select\n className={selectClassName}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n aria-busy={loading || undefined}\n {...rest}\n ref={selectRef}\n onChange={onLoadMore ? handleChange : onChange}\n >\n {placeholder && (\n <option value=\"\" disabled={Boolean(required)}>\n {placeholder}\n </option>\n )}\n {showsSelectedOption && <option value={selectedOption?.value}>{selectedOption?.label}</option>}\n {items.map((option: SelectOption) => (\n <option key={option.value} value={option.value} disabled={option.disabled} className={styles['option']}>\n {option.label}\n </option>\n ))}\n {showsLoadMore && (\n <option\n value={SelectConstants.LOAD_MORE_OPTION_VALUE}\n disabled={loading}\n className={cx(withVendorCSSClassPrefix(bem('select', 'load-more')), styles['option'], styles['loadMore'])}\n >\n {loading ? messages.loadingMore : messages.loadMore}\n </option>\n )}\n </select>\n {hasStatus && (\n <span role=\"status\" className={styles['status']}>\n {statusText}\n </span>\n )}\n </FormControl>\n );\n};\n\nexport default Select;\n"],"mappings":";;;;;;;;;;;;;;;AAmEA,MAAMA,UAA2B,EAC/B,OACA,OACA,WACA,UACA,UACA,YACA,aACA,UAAU,QACV,YAAY,QACZ,UAAU,OACV,aAAa,QACb,WAAW,QACX,iBAAiB,QACjB,QAAQ,EAAE,CACV,GAAG,WACc;CACjB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAMC,WAA2BC,mCAAmB;CACpD,MAAM,WAAW,CAAC,CAAC;CACnB,MAAMC,SAAiCC,8BAAU,OAAO,aAAa,YAAY,OAAO,SAAS;CACjG,MAAMC,iFAA6D,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACvF,MAAMC,8BAA2E,KAAK;CACtF,MAAMC,mCAAmD,GAAG;AAG5D,4BAAgB;EACd,MAAMC,UAA8B,UAAU,SAAS;AAEvD,MAAI,YAAY,UAAa,YAAYC,oCAAgB,uBACvD,gBAAe,UAAU;GAE3B;CAEF,MAAM,gBAAgB,UAAgD;AACpE,MAAI,MAAM,OAAO,UAAUA,oCAAgB,wBAAwB;AACjE,kBAAe,UAAU,MAAM,OAAO;AACtC,cAAW,MAAM;AAEjB;;AAGF,QAAM,OAAO,QAAQ,eAAe;AACpC,gBAAc;;CAGhB,MAAMC,kBAA0BC,kGACD,UAAU,QAAQ,CAAC,EAChD,OAAO,WACP,YAAY,OAAO,gBACnB,YAAY,OAAO,kBACpB;CAED,MAAMC,sBACJ,mBAAmB,UACnB,KAAK,UAAU,UACf,OAAO,KAAK,MAAM,KAAK,eAAe,SACtC,CAAC,MAAM,MAAM,SAAuB,KAAK,UAAU,eAAe,MAAM;CAE1E,IAAIC;AAEJ,KAAI,QACF,cAAa,MAAM,WAAW,IAAI,SAAS,UAAU,SAAS;UACrD,MAAM,WAAW,KAAK,CAAC,WAChC,cAAa;CAGf,MAAMC,YAAqB,eAAe,UAAa,cAAc,UAAa;CAClF,MAAMC,gBAAyB,eAAe,WAAc,MAAM,SAAS,KAAK,CAAC;AAEjF,QACE,4CAACC;EACQ;EACK;
|
|
1
|
+
{"version":3,"file":"Select.cjs","names":["Select: FC<SelectProps>","useTheme","messages: SelectMessages","useSelectMessages","generatedId: string","inputId: string","helperTextId: string | undefined","describedBy: string | undefined","styles: Record<string, string>","useStyles","items: SelectOption[]","selectRef: RefObject<HTMLSelectElement | null>","chosenValueRef: RefObject<string>","current: string | undefined","SelectConstants","selectClassName: string","cx","showsSelectedOption: boolean","statusText: string | undefined","hasStatus: boolean","showsLoadMore: boolean","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/Select/Select.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {bem, normalizeSelectOptions, SelectConstants, withVendorCSSClassPrefix} from '@thunderid/browser';\nimport type {SelectOption, SelectOptionInput} from '@thunderid/browser';\nimport {ChangeEvent, FC, RefObject, SelectHTMLAttributes, useEffect, useId, useMemo, useRef} from 'react';\nimport useStyles from './Select.styles';\nimport useSelectMessages, {SelectMessages} from './useSelectMessages';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport type {SelectOption, SelectOptionInput};\n\nexport interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Text shown below the select when it has no options, nothing is loading and there is nothing more to load\n */\n emptyText?: string;\n /**\n * Error message to display below the select\n */\n error?: string;\n /**\n * Helper text to display below the select\n */\n helperText?: string;\n /**\n * Label text to display above the select\n */\n label?: string;\n /**\n * Whether options are being fetched. The load more option waits, and a select with no options shows a loading message\n */\n loading?: boolean;\n /**\n * Called when the load more option, listed after the last one, is chosen. Choosing it does not change the selection.\n * The option is listed only when this is set, so pass it only while more options may be available\n */\n onLoadMore?: () => void;\n /**\n * The options to display: strings, numbers, or `{label, value, disabled?}` objects\n */\n options?: readonly (SelectOptionInput | null | undefined)[] | null;\n /**\n * Placeholder text for the default/empty option. While the field is not required, choosing it clears the selection\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Label of the selected option while `value` is not among `options`, such as a value loaded from saved data\n */\n selectedOption?: SelectOption;\n}\n\nconst Select: FC<SelectProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n placeholder,\n options = undefined,\n emptyText = undefined,\n loading = false,\n onLoadMore = undefined,\n onChange = undefined,\n selectedOption = undefined,\n style = {},\n ...rest\n}: SelectProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const messages: SelectMessages = useSelectMessages();\n const hasError = !!error;\n const generatedId: string = useId();\n const inputId: string = rest.id ?? generatedId;\n const helperTextId: string | undefined = error || helperText ? `${inputId}-helper-text` : undefined;\n const describedBy: string | undefined =\n [rest['aria-describedby'], helperTextId].filter(Boolean).join(' ') || undefined;\n const styles: Record<string, string> = useStyles(theme, colorScheme, disabled ?? false, hasError);\n const items: SelectOption[] = useMemo(() => normalizeSelectOptions(options), [options]);\n const selectRef: RefObject<HTMLSelectElement | null> = useRef<HTMLSelectElement>(null);\n const chosenValueRef: RefObject<string> = useRef<string>('');\n\n // The value to put back when the load more option is chosen, since choosing it is not a selection.\n useEffect(() => {\n const current: string | undefined = selectRef.current?.value;\n\n if (current !== undefined && current !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = current;\n }\n });\n\n const handleChange = (event: ChangeEvent<HTMLSelectElement>): void => {\n if (event.target.value !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = event.target.value;\n onChange?.(event);\n\n return;\n }\n\n event.target.value = chosenValueRef.current;\n onLoadMore?.();\n };\n\n const selectClassName: string = cx(\n withVendorCSSClassPrefix(bem('select', 'input')),\n styles['select'],\n hasError && styles['selectError'],\n disabled && styles['selectDisabled'],\n );\n\n const showsSelectedOption: boolean =\n selectedOption !== undefined &&\n rest.value !== undefined &&\n String(rest.value) === selectedOption.value &&\n !items.some((item: SelectOption) => item.value === selectedOption.value);\n\n let statusText: string | undefined;\n\n if (loading) {\n statusText = items.length === 0 ? messages.loading : messages.loadingMore;\n } else if (items.length === 0 && !onLoadMore) {\n statusText = emptyText;\n }\n\n const hasStatus: boolean = onLoadMore !== undefined || emptyText !== undefined || loading;\n const showsLoadMore: boolean = onLoadMore !== undefined && (items.length > 0 || !loading);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n helperTextId={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('select')), className)}\n style={style}\n >\n {label && (\n <InputLabel htmlFor={inputId} required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <select\n className={selectClassName}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n aria-busy={loading || undefined}\n {...rest}\n id={inputId}\n aria-describedby={describedBy}\n ref={selectRef}\n onChange={onLoadMore ? handleChange : onChange}\n >\n {placeholder && (\n <option value=\"\" disabled={Boolean(required)}>\n {placeholder}\n </option>\n )}\n {showsSelectedOption && <option value={selectedOption?.value}>{selectedOption?.label}</option>}\n {items.map((option: SelectOption) => (\n <option key={option.value} value={option.value} disabled={option.disabled} className={styles['option']}>\n {option.label}\n </option>\n ))}\n {showsLoadMore && (\n <option\n value={SelectConstants.LOAD_MORE_OPTION_VALUE}\n disabled={loading}\n className={cx(withVendorCSSClassPrefix(bem('select', 'load-more')), styles['option'], styles['loadMore'])}\n >\n {loading ? messages.loadingMore : messages.loadMore}\n </option>\n )}\n </select>\n {hasStatus && (\n <span role=\"status\" className={styles['status']}>\n {statusText}\n </span>\n )}\n </FormControl>\n );\n};\n\nexport default Select;\n"],"mappings":";;;;;;;;;;;;;;;AAmEA,MAAMA,UAA2B,EAC/B,OACA,OACA,WACA,UACA,UACA,YACA,aACA,UAAU,QACV,YAAY,QACZ,UAAU,OACV,aAAa,QACb,WAAW,QACX,iBAAiB,QACjB,QAAQ,EAAE,CACV,GAAG,WACc;CACjB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAMC,WAA2BC,mCAAmB;CACpD,MAAM,WAAW,CAAC,CAAC;CACnB,MAAMC,gCAA6B;CACnC,MAAMC,UAAkB,KAAK,MAAM;CACnC,MAAMC,eAAmC,SAAS,aAAa,GAAG,QAAQ,gBAAgB;CAC1F,MAAMC,cACJ,CAAC,KAAK,qBAAqB,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;CACxE,MAAMC,SAAiCC,8BAAU,OAAO,aAAa,YAAY,OAAO,SAAS;CACjG,MAAMC,iFAA6D,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACvF,MAAMC,8BAA2E,KAAK;CACtF,MAAMC,mCAAmD,GAAG;AAG5D,4BAAgB;EACd,MAAMC,UAA8B,UAAU,SAAS;AAEvD,MAAI,YAAY,UAAa,YAAYC,oCAAgB,uBACvD,gBAAe,UAAU;GAE3B;CAEF,MAAM,gBAAgB,UAAgD;AACpE,MAAI,MAAM,OAAO,UAAUA,oCAAgB,wBAAwB;AACjE,kBAAe,UAAU,MAAM,OAAO;AACtC,cAAW,MAAM;AAEjB;;AAGF,QAAM,OAAO,QAAQ,eAAe;AACpC,gBAAc;;CAGhB,MAAMC,kBAA0BC,kGACD,UAAU,QAAQ,CAAC,EAChD,OAAO,WACP,YAAY,OAAO,gBACnB,YAAY,OAAO,kBACpB;CAED,MAAMC,sBACJ,mBAAmB,UACnB,KAAK,UAAU,UACf,OAAO,KAAK,MAAM,KAAK,eAAe,SACtC,CAAC,MAAM,MAAM,SAAuB,KAAK,UAAU,eAAe,MAAM;CAE1E,IAAIC;AAEJ,KAAI,QACF,cAAa,MAAM,WAAW,IAAI,SAAS,UAAU,SAAS;UACrD,MAAM,WAAW,KAAK,CAAC,WAChC,cAAa;CAGf,MAAMC,YAAqB,eAAe,UAAa,cAAc,UAAa;CAClF,MAAMC,gBAAyB,eAAe,WAAc,MAAM,SAAS,KAAK,CAAC;AAEjF,QACE,4CAACC;EACQ;EACK;EACE;EACd,WAAWL,kGAAgC,SAAS,CAAC,EAAE,UAAU;EAC1D;;GAEN,SACC,2CAACM;IAAW,SAAS;IAAmB;IAAU,OAAO;cACtD;KACU;GAEf,4CAAC;IACC,WAAW;IACD;IACV,gBAAc;IACd,iBAAe;IACf,aAAW,WAAW;IACtB,GAAI;IACJ,IAAI;IACJ,oBAAkB;IAClB,KAAK;IACL,UAAU,aAAa,eAAe;;KAErC,eACC,2CAAC;MAAO,OAAM;MAAG,UAAU,QAAQ,SAAS;gBACzC;OACM;KAEV,uBAAuB,2CAAC;MAAO,OAAO,gBAAgB;gBAAQ,gBAAgB;OAAe;KAC7F,MAAM,KAAK,WACV,2CAAC;MAA0B,OAAO,OAAO;MAAO,UAAU,OAAO;MAAU,WAAW,OAAO;gBAC1F,OAAO;QADG,OAAO,MAEX,CACT;KACD,iBACC,2CAAC;MACC,OAAOR,oCAAgB;MACvB,UAAU;MACV,WAAWE,kGAAgC,UAAU,YAAY,CAAC,EAAE,OAAO,WAAW,OAAO,YAAY;gBAExG,UAAU,SAAS,cAAc,SAAS;OACpC;;KAEJ;GACR,aACC,2CAAC;IAAK,MAAK;IAAS,WAAW,OAAO;cACnC;KACI;;GAEG;;AAIlB,qBAAe"}
|
|
@@ -6,6 +6,8 @@ const require_InputLabel = require('../InputLabel/InputLabel.cjs');
|
|
|
6
6
|
const require_TextField_styles = require('./TextField.styles.cjs');
|
|
7
7
|
let __thunderid_browser = require("@thunderid/browser");
|
|
8
8
|
__thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
|
|
9
|
+
let react = require("react");
|
|
10
|
+
react = require_rolldown_runtime.__toESM(react);
|
|
9
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
12
|
react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
|
|
11
13
|
|
|
@@ -21,6 +23,10 @@ const activateOnKey = (activate) => (event) => {
|
|
|
21
23
|
};
|
|
22
24
|
const TextField = ({ label, error, required, className, disabled, helperText, startIcon, endAdornment, endIcon, endIconLabel = "End icon", onStartIconClick, onEndIconClick, type = "text", style = {},...rest }) => {
|
|
23
25
|
const { theme, colorScheme } = require_useTheme.default();
|
|
26
|
+
const generatedId = (0, react.useId)();
|
|
27
|
+
const inputId = rest.id ?? generatedId;
|
|
28
|
+
const helperTextId = error || helperText ? `${inputId}-helper-text` : void 0;
|
|
29
|
+
const describedBy = [rest["aria-describedby"], helperTextId].filter(Boolean).join(" ") || void 0;
|
|
24
30
|
const hasError = !!error;
|
|
25
31
|
const styles = require_TextField_styles.default(theme, colorScheme, disabled ?? false, hasError, !!startIcon, !!endIcon, !!endAdornment);
|
|
26
32
|
const inputClassName = require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("text-field", "input")), styles["input"], hasError && styles["inputError"], disabled && styles["inputDisabled"]);
|
|
@@ -31,9 +37,11 @@ const TextField = ({ label, error, required, className, disabled, helperText, st
|
|
|
31
37
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_FormControl.default, {
|
|
32
38
|
error,
|
|
33
39
|
helperText,
|
|
40
|
+
helperTextId,
|
|
34
41
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("text-field")), className),
|
|
35
42
|
style,
|
|
36
43
|
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputLabel.default, {
|
|
44
|
+
htmlFor: inputId,
|
|
37
45
|
required,
|
|
38
46
|
error: hasError,
|
|
39
47
|
children: label
|
|
@@ -55,7 +63,9 @@ const TextField = ({ label, error, required, className, disabled, helperText, st
|
|
|
55
63
|
disabled,
|
|
56
64
|
"aria-invalid": hasError,
|
|
57
65
|
"aria-required": required,
|
|
58
|
-
...rest
|
|
66
|
+
...rest,
|
|
67
|
+
id: inputId,
|
|
68
|
+
"aria-describedby": describedBy
|
|
59
69
|
}),
|
|
60
70
|
endIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
61
71
|
className: endIconClassName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextField.cjs","names":["TextField: FC<TextFieldProps>","useTheme","styles: Record<string, string>","useStyles","inputClassName: string","cx","containerClassName: string","startIconClassName: string","endIconClassName: string","endAdornmentClassName: string","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/TextField/TextField.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {FC, InputHTMLAttributes, KeyboardEvent, ReactNode} from 'react';\nimport useStyles from './TextField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Extra content shown inside the field, at its end after the end icon, such as an action button\n */\n endAdornment?: ReactNode;\n /**\n * Icon to display at the end (right) of the input\n */\n endIcon?: ReactNode;\n /**\n * Accessible name of the end icon when it is a button, that is when `onEndIconClick` is set\n */\n endIconLabel?: string;\n /**\n * Error message to display below the input\n */\n error?: string;\n /**\n * Helper text to display below the input\n */\n helperText?: string;\n /**\n * Label text to display above the input\n */\n label?: string;\n /**\n * Click handler for the end icon\n */\n onEndIconClick?: () => void;\n /**\n * Click handler for the start icon\n */\n onStartIconClick?: () => void;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Icon to display at the start (left) of the input\n */\n startIcon?: ReactNode;\n}\n\n/**\n * Makes an element with `role=\"button\"` respond to Enter and Space, as a native button does.\n */\nconst activateOnKey =\n (activate: () => void) =>\n (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n activate();\n }\n };\n\nconst TextField: FC<TextFieldProps> = ({\n label,\n error,\n required,\n className,\n disabled,\n helperText,\n startIcon,\n endAdornment,\n endIcon,\n endIconLabel = 'End icon',\n onStartIconClick,\n onEndIconClick,\n type = 'text',\n style = {},\n ...rest\n}: TextFieldProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const hasError = !!error;\n const hasStartIcon = !!startIcon;\n const hasEndIcon = !!endIcon;\n const hasEndAdornment = !!endAdornment;\n const styles: Record<string, string> = useStyles(\n theme,\n colorScheme,\n disabled ?? false,\n hasError,\n hasStartIcon,\n hasEndIcon,\n hasEndAdornment,\n );\n\n const inputClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'input')),\n styles['input'],\n hasError && styles['inputError'],\n disabled && styles['inputDisabled'],\n );\n\n const containerClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'container')),\n styles['inputContainer'],\n );\n\n const startIconClassName: string = cx(withVendorCSSClassPrefix(bem('text-field', 'start-icon')), styles['startIcon']);\n\n const endIconClassName: string = cx(withVendorCSSClassPrefix(bem('text-field', 'end-icon')), styles['endIcon']);\n\n const endAdornmentClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'end-adornment')),\n styles['endAdornment'],\n );\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('text-field')), className)}\n style={style}\n >\n {label && (\n <InputLabel required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <div className={containerClassName}>\n {startIcon && (\n <div\n className={startIconClassName}\n onClick={onStartIconClick}\n role={onStartIconClick ? 'button' : undefined}\n tabIndex={onStartIconClick && !disabled ? 0 : undefined}\n onKeyDown={onStartIconClick && !disabled ? activateOnKey(onStartIconClick) : undefined}\n aria-label=\"Start icon\"\n >\n {startIcon}\n </div>\n )}\n <input\n className={inputClassName}\n type={type}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n {...rest}\n />\n {endIcon && (\n <div\n className={endIconClassName}\n onClick={onEndIconClick}\n role={onEndIconClick ? 'button' : undefined}\n tabIndex={onEndIconClick && !disabled ? 0 : undefined}\n onKeyDown={onEndIconClick && !disabled ? activateOnKey(onEndIconClick) : undefined}\n aria-label={endIconLabel}\n >\n {endIcon}\n </div>\n )}\n {endAdornment && <div className={endAdornmentClassName}>{endAdornment}</div>}\n </div>\n </FormControl>\n );\n};\n\nexport default TextField;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"TextField.cjs","names":["TextField: FC<TextFieldProps>","useTheme","generatedId: string","inputId: string","helperTextId: string | undefined","describedBy: string | undefined","styles: Record<string, string>","useStyles","inputClassName: string","cx","containerClassName: string","startIconClassName: string","endIconClassName: string","endAdornmentClassName: string","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/TextField/TextField.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {FC, InputHTMLAttributes, KeyboardEvent, ReactNode, useId} from 'react';\nimport useStyles from './TextField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Extra content shown inside the field, at its end after the end icon, such as an action button\n */\n endAdornment?: ReactNode;\n /**\n * Icon to display at the end (right) of the input\n */\n endIcon?: ReactNode;\n /**\n * Accessible name of the end icon when it is a button, that is when `onEndIconClick` is set\n */\n endIconLabel?: string;\n /**\n * Error message to display below the input\n */\n error?: string;\n /**\n * Helper text to display below the input\n */\n helperText?: string;\n /**\n * Label text to display above the input\n */\n label?: string;\n /**\n * Click handler for the end icon\n */\n onEndIconClick?: () => void;\n /**\n * Click handler for the start icon\n */\n onStartIconClick?: () => void;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Icon to display at the start (left) of the input\n */\n startIcon?: ReactNode;\n}\n\n/**\n * Makes an element with `role=\"button\"` respond to Enter and Space, as a native button does.\n */\nconst activateOnKey =\n (activate: () => void) =>\n (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n activate();\n }\n };\n\nconst TextField: FC<TextFieldProps> = ({\n label,\n error,\n required,\n className,\n disabled,\n helperText,\n startIcon,\n endAdornment,\n endIcon,\n endIconLabel = 'End icon',\n onStartIconClick,\n onEndIconClick,\n type = 'text',\n style = {},\n ...rest\n}: TextFieldProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const generatedId: string = useId();\n const inputId: string = rest.id ?? generatedId;\n const helperTextId: string | undefined = error || helperText ? `${inputId}-helper-text` : undefined;\n const describedBy: string | undefined =\n [rest['aria-describedby'], helperTextId].filter(Boolean).join(' ') || undefined;\n const hasError = !!error;\n const hasStartIcon = !!startIcon;\n const hasEndIcon = !!endIcon;\n const hasEndAdornment = !!endAdornment;\n const styles: Record<string, string> = useStyles(\n theme,\n colorScheme,\n disabled ?? false,\n hasError,\n hasStartIcon,\n hasEndIcon,\n hasEndAdornment,\n );\n\n const inputClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'input')),\n styles['input'],\n hasError && styles['inputError'],\n disabled && styles['inputDisabled'],\n );\n\n const containerClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'container')),\n styles['inputContainer'],\n );\n\n const startIconClassName: string = cx(withVendorCSSClassPrefix(bem('text-field', 'start-icon')), styles['startIcon']);\n\n const endIconClassName: string = cx(withVendorCSSClassPrefix(bem('text-field', 'end-icon')), styles['endIcon']);\n\n const endAdornmentClassName: string = cx(\n withVendorCSSClassPrefix(bem('text-field', 'end-adornment')),\n styles['endAdornment'],\n );\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n helperTextId={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('text-field')), className)}\n style={style}\n >\n {label && (\n <InputLabel htmlFor={inputId} required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <div className={containerClassName}>\n {startIcon && (\n <div\n className={startIconClassName}\n onClick={onStartIconClick}\n role={onStartIconClick ? 'button' : undefined}\n tabIndex={onStartIconClick && !disabled ? 0 : undefined}\n onKeyDown={onStartIconClick && !disabled ? activateOnKey(onStartIconClick) : undefined}\n aria-label=\"Start icon\"\n >\n {startIcon}\n </div>\n )}\n <input\n className={inputClassName}\n type={type}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n {...rest}\n id={inputId}\n aria-describedby={describedBy}\n />\n {endIcon && (\n <div\n className={endIconClassName}\n onClick={onEndIconClick}\n role={onEndIconClick ? 'button' : undefined}\n tabIndex={onEndIconClick && !disabled ? 0 : undefined}\n onKeyDown={onEndIconClick && !disabled ? activateOnKey(onEndIconClick) : undefined}\n aria-label={endIconLabel}\n >\n {endIcon}\n </div>\n )}\n {endAdornment && <div className={endAdornmentClassName}>{endAdornment}</div>}\n </div>\n </FormControl>\n );\n};\n\nexport default TextField;\n"],"mappings":";;;;;;;;;;;;;;;;;AAiEA,MAAM,iBACH,cACA,UAA+C;AAC9C,KAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,QAAM,gBAAgB;AACtB,YAAU;;;AAIhB,MAAMA,aAAiC,EACrC,OACA,OACA,UACA,WACA,UACA,YACA,WACA,cACA,SACA,eAAe,YACf,kBACA,gBACA,OAAO,QACP,QAAQ,EAAE,CACV,GAAG,WACiB;CACpB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAMC,gCAA6B;CACnC,MAAMC,UAAkB,KAAK,MAAM;CACnC,MAAMC,eAAmC,SAAS,aAAa,GAAG,QAAQ,gBAAgB;CAC1F,MAAMC,cACJ,CAAC,KAAK,qBAAqB,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;CACxE,MAAM,WAAW,CAAC,CAAC;CAInB,MAAMC,SAAiCC,iCACrC,OACA,aACA,YAAY,OACZ,UAPmB,CAAC,CAAC,WACJ,CAAC,CAAC,SACG,CAAC,CAAC,aASzB;CAED,MAAMC,iBAAyBC,kGACA,cAAc,QAAQ,CAAC,EACpD,OAAO,UACP,YAAY,OAAO,eACnB,YAAY,OAAO,iBACpB;CAED,MAAMC,qBAA6BD,kGACJ,cAAc,YAAY,CAAC,EACxD,OAAO,kBACR;CAED,MAAME,qBAA6BF,kGAAgC,cAAc,aAAa,CAAC,EAAE,OAAO,aAAa;CAErH,MAAMG,mBAA2BH,kGAAgC,cAAc,WAAW,CAAC,EAAE,OAAO,WAAW;CAE/G,MAAMI,wBAAgCJ,kGACP,cAAc,gBAAgB,CAAC,EAC5D,OAAO,gBACR;AAED,QACE,4CAACK;EACQ;EACK;EACE;EACd,WAAWL,kGAAgC,aAAa,CAAC,EAAE,UAAU;EAC9D;aAEN,SACC,2CAACM;GAAW,SAAS;GAAmB;GAAU,OAAO;aACtD;IACU,EAEf,4CAAC;GAAI,WAAW;;IACb,aACC,2CAAC;KACC,WAAW;KACX,SAAS;KACT,MAAM,mBAAmB,WAAW;KACpC,UAAU,oBAAoB,CAAC,WAAW,IAAI;KAC9C,WAAW,oBAAoB,CAAC,WAAW,cAAc,iBAAiB,GAAG;KAC7E,cAAW;eAEV;MACG;IAER,2CAAC;KACC,WAAW;KACL;KACI;KACV,gBAAc;KACd,iBAAe;KACf,GAAI;KACJ,IAAI;KACJ,oBAAkB;MAClB;IACD,WACC,2CAAC;KACC,WAAW;KACX,SAAS;KACT,MAAM,iBAAiB,WAAW;KAClC,UAAU,kBAAkB,CAAC,WAAW,IAAI;KAC5C,WAAW,kBAAkB,CAAC,WAAW,cAAc,eAAe,GAAG;KACzE,cAAY;eAEX;MACG;IAEP,gBAAgB,2CAAC;KAAI,WAAW;eAAwB;MAAmB;;IACxE;GACM;;AAIlB,wBAAe"}
|
|
@@ -6,6 +6,8 @@ const require_FormControl = require('../FormControl/FormControl.cjs');
|
|
|
6
6
|
const require_InputLabel = require('../InputLabel/InputLabel.cjs');
|
|
7
7
|
let __thunderid_browser = require("@thunderid/browser");
|
|
8
8
|
__thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
|
|
9
|
+
let react = require("react");
|
|
10
|
+
react = require_rolldown_runtime.__toESM(react);
|
|
9
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
12
|
react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
|
|
11
13
|
|
|
@@ -23,10 +25,15 @@ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
|
|
|
23
25
|
const Toggle = ({ label, error, className, required, helperText, style = {},...rest }) => {
|
|
24
26
|
const { theme, colorScheme } = require_useTheme.default();
|
|
25
27
|
const hasError = !!error;
|
|
28
|
+
const generatedId = (0, react.useId)();
|
|
29
|
+
const inputId = rest.id ?? generatedId;
|
|
30
|
+
const helperTextId = error || helperText ? `${inputId}-helper-text` : void 0;
|
|
31
|
+
const describedBy = [rest["aria-describedby"], helperTextId].filter(Boolean).join(" ") || void 0;
|
|
26
32
|
const styles = require_Toggle_styles.default(theme, colorScheme, hasError, !!required);
|
|
27
33
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_FormControl.default, {
|
|
28
34
|
error,
|
|
29
35
|
helperText,
|
|
36
|
+
helperTextId,
|
|
30
37
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("toggle")), className),
|
|
31
38
|
helperTextMarginLeft: `calc(${theme.vars.spacing.unit} * 5.5)`,
|
|
32
39
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -39,7 +46,9 @@ const Toggle = ({ label, error, className, required, helperText, style = {},...r
|
|
|
39
46
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("toggle", "input")), styles["input"]),
|
|
40
47
|
"aria-invalid": hasError,
|
|
41
48
|
"aria-required": required,
|
|
42
|
-
...rest
|
|
49
|
+
...rest,
|
|
50
|
+
id: inputId,
|
|
51
|
+
"aria-describedby": describedBy
|
|
43
52
|
}),
|
|
44
53
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
45
54
|
className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("toggle", "track")), styles["track"]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toggle.cjs","names":["Toggle: FC<ToggleProps>","useTheme","styles: Record<string, string>","useStyles","FormControl","cx","InputLabel"],"sources":["../../../../../src/components/primitives/Toggle/Toggle.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {FC, InputHTMLAttributes} from 'react';\nimport useStyles from './Toggle.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\n/**\n * Props for the Toggle component.\n */\nexport interface ToggleProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'type'> {\n className?: string;\n error?: string;\n helperText?: string;\n label?: string;\n required?: boolean;\n}\n\n/**\n * A Toggle component that represents a boolean input. It is built on top of a hidden checkbox input\n * and styled to look like a switch.\n *\n * The component is wrapped in a FormControl to display error messages and helper text.\n * The label is associated with the input for accessibility.\n *\n * @param props - Props for the Toggle component\n * @returns A JSX element representing the Toggle\n */\nconst Toggle: FC<ToggleProps> = ({label, error, className, required, helperText, style = {}, ...rest}: ToggleProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const hasError = !!error;\n const styles: Record<string, string> = useStyles(theme, colorScheme, hasError, !!required);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('toggle')), className)}\n helperTextMarginLeft={`calc(${theme.vars.spacing.unit} * 5.5)`}\n >\n <label style={style} className={cx(withVendorCSSClassPrefix(bem('toggle', 'container')), styles['container'])}>\n <input\n type=\"checkbox\"\n role=\"switch\"\n className={cx(withVendorCSSClassPrefix(bem('toggle', 'input')), styles['input'])}\n aria-invalid={hasError}\n aria-required={required}\n {...rest}\n />\n <div className={cx(withVendorCSSClassPrefix(bem('toggle', 'track')), styles['track'])}>\n <span className={cx(withVendorCSSClassPrefix(bem('toggle', 'thumb')), styles['thumb'])} />\n </div>\n {label && (\n <InputLabel\n required={required}\n error={hasError}\n variant=\"inline\"\n className={cx(withVendorCSSClassPrefix(bem('toggle', 'label')), styles['label'], styles['errorLabel'], {\n [withVendorCSSClassPrefix(bem('toggle', 'label', 'error'))]: hasError,\n })}\n >\n {label}\n </InputLabel>\n )}\n </label>\n </FormControl>\n );\n};\n\nexport default Toggle;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Toggle.cjs","names":["Toggle: FC<ToggleProps>","useTheme","generatedId: string","inputId: string","helperTextId: string | undefined","describedBy: string | undefined","styles: Record<string, string>","useStyles","FormControl","cx","InputLabel"],"sources":["../../../../../src/components/primitives/Toggle/Toggle.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {FC, InputHTMLAttributes, useId} from 'react';\nimport useStyles from './Toggle.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\n/**\n * Props for the Toggle component.\n */\nexport interface ToggleProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'type'> {\n className?: string;\n error?: string;\n helperText?: string;\n label?: string;\n required?: boolean;\n}\n\n/**\n * A Toggle component that represents a boolean input. It is built on top of a hidden checkbox input\n * and styled to look like a switch.\n *\n * The component is wrapped in a FormControl to display error messages and helper text.\n * The label is associated with the input for accessibility.\n *\n * @param props - Props for the Toggle component\n * @returns A JSX element representing the Toggle\n */\nconst Toggle: FC<ToggleProps> = ({label, error, className, required, helperText, style = {}, ...rest}: ToggleProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const hasError = !!error;\n const generatedId: string = useId();\n const inputId: string = rest.id ?? generatedId;\n const helperTextId: string | undefined = error || helperText ? `${inputId}-helper-text` : undefined;\n const describedBy: string | undefined =\n [rest['aria-describedby'], helperTextId].filter(Boolean).join(' ') || undefined;\n const styles: Record<string, string> = useStyles(theme, colorScheme, hasError, !!required);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n helperTextId={helperTextId}\n className={cx(withVendorCSSClassPrefix(bem('toggle')), className)}\n helperTextMarginLeft={`calc(${theme.vars.spacing.unit} * 5.5)`}\n >\n <label style={style} className={cx(withVendorCSSClassPrefix(bem('toggle', 'container')), styles['container'])}>\n <input\n type=\"checkbox\"\n role=\"switch\"\n className={cx(withVendorCSSClassPrefix(bem('toggle', 'input')), styles['input'])}\n aria-invalid={hasError}\n aria-required={required}\n {...rest}\n id={inputId}\n aria-describedby={describedBy}\n />\n <div className={cx(withVendorCSSClassPrefix(bem('toggle', 'track')), styles['track'])}>\n <span className={cx(withVendorCSSClassPrefix(bem('toggle', 'thumb')), styles['thumb'])} />\n </div>\n {label && (\n <InputLabel\n required={required}\n error={hasError}\n variant=\"inline\"\n className={cx(withVendorCSSClassPrefix(bem('toggle', 'label')), styles['label'], styles['errorLabel'], {\n [withVendorCSSClassPrefix(bem('toggle', 'label', 'error'))]: hasError,\n })}\n >\n {label}\n </InputLabel>\n )}\n </label>\n </FormControl>\n );\n};\n\nexport default Toggle;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAMA,UAA2B,EAAC,OAAO,OAAO,WAAW,UAAU,YAAY,QAAQ,EAAE,CAAE,GAAG,WAAuB;CACrH,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAM,WAAW,CAAC,CAAC;CACnB,MAAMC,gCAA6B;CACnC,MAAMC,UAAkB,KAAK,MAAM;CACnC,MAAMC,eAAmC,SAAS,aAAa,GAAG,QAAQ,gBAAgB;CAC1F,MAAMC,cACJ,CAAC,KAAK,qBAAqB,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;CACxE,MAAMC,SAAiCC,8BAAU,OAAO,aAAa,UAAU,CAAC,CAAC,SAAS;AAE1F,QACE,2CAACC;EACQ;EACK;EACE;EACd,WAAWC,kGAAgC,SAAS,CAAC,EAAE,UAAU;EACjE,sBAAsB,QAAQ,MAAM,KAAK,QAAQ,KAAK;YAEtD,4CAAC;GAAa;GAAO,WAAWA,kGAAgC,UAAU,YAAY,CAAC,EAAE,OAAO,aAAa;;IAC3G,2CAAC;KACC,MAAK;KACL,MAAK;KACL,WAAWA,kGAAgC,UAAU,QAAQ,CAAC,EAAE,OAAO,SAAS;KAChF,gBAAc;KACd,iBAAe;KACf,GAAI;KACJ,IAAI;KACJ,oBAAkB;MAClB;IACF,2CAAC;KAAI,WAAWA,kGAAgC,UAAU,QAAQ,CAAC,EAAE,OAAO,SAAS;eACnF,2CAAC,UAAK,WAAWA,kGAAgC,UAAU,QAAQ,CAAC,EAAE,OAAO,SAAS,GAAI;MACtF;IACL,SACC,2CAACC;KACW;KACV,OAAO;KACP,SAAQ;KACR,WAAWD,kGAAgC,UAAU,QAAQ,CAAC,EAAE,OAAO,UAAU,OAAO,eAAe,kFACvE,UAAU,SAAS,QAAQ,CAAC,GAAG,UAC9D,CAAC;eAED;MACU;;IAET;GACI;;AAIlB,qBAAe"}
|
|
@@ -70,7 +70,7 @@ const ThemeProvider = ({ children, mode, theme: themeOverrideProp }) => {
|
|
|
70
70
|
}
|
|
71
71
|
const mediaQuery = (0, __thunderid_browser.createMediaQueryListener)(handleThemeChange);
|
|
72
72
|
return () => {
|
|
73
|
-
|
|
73
|
+
mediaQuery?.disconnect();
|
|
74
74
|
};
|
|
75
75
|
}, [mode]);
|
|
76
76
|
const toggleTheme = (0, react.useCallback)(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeProvider.cjs","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","value: any","ThemeContext"],"sources":["../../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, useCallback, useContext, useEffect, useMemo, useState} from 'react';\nimport ThemeContext from './ThemeContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * Initial color scheme override. Overrides the server default when provided.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n
|
|
1
|
+
{"version":3,"file":"ThemeProvider.cjs","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","value: any","ThemeContext"],"sources":["../../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, useCallback, useContext, useEffect, useMemo, useState} from 'react';\nimport ThemeContext from './ThemeContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * Initial color scheme override. Overrides the server default when provided.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n mediaQuery?.disconnect();\n };\n }, [mode]);\n\n const toggleTheme: () => void = useCallback(() => {\n setColorScheme((prev: 'light' | 'dark') => (prev === 'light' ? 'dark' : 'light'));\n }, []);\n\n // Build the resolved ThemeConfig: flow meta base → user overrides on top.\n const finalThemeConfig: RecursivePartial<ThemeConfig> | undefined = useMemo(() => {\n if (!flowMetaTheme) {\n return themeOverride;\n }\n\n const metaConfig: RecursivePartial<ThemeConfig> = buildThemeConfigFromFlowMeta(flowMetaTheme, colorScheme);\n\n if (!themeOverride) {\n return metaConfig;\n }\n\n return {\n ...metaConfig,\n ...themeOverride,\n borderRadius: {\n ...metaConfig.borderRadius,\n ...themeOverride.borderRadius,\n },\n colors: {\n ...metaConfig.colors,\n ...themeOverride.colors,\n },\n ...(metaConfig.typography || themeOverride.typography\n ? {\n typography: {\n ...(metaConfig as any).typography,\n ...themeOverride.typography,\n },\n }\n : {}),\n };\n }, [flowMetaTheme, colorScheme, themeOverride]);\n\n const theme: Theme = useMemo(\n () => createTheme(finalThemeConfig, colorScheme === 'dark'),\n [finalThemeConfig, colorScheme],\n );\n\n const direction: 'ltr' | 'rtl' = flowMetaTheme?.direction ?? 'ltr';\n\n // Apply CSS variables to the document root.\n useEffect(() => {\n applyThemeToDOM(theme);\n }, [theme]);\n\n // Apply text direction to the document root.\n useEffect(() => {\n if (typeof document !== 'undefined') {\n document.documentElement.dir = direction;\n }\n }, [direction]);\n\n const value: any = {\n colorScheme,\n direction,\n theme,\n toggleTheme,\n };\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\nexport default ThemeProvider;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,MAAMA,iBAA4D,EAChE,UACA,MACA,OAAO,wBACkD;CACzD,MAAMC,gBAA2DC,qCAAqB,kBAAkB;CACxG,MAAMC,wCAA0DC,gCAAgB;CAEhF,MAAMC,gBAAsC,iBAAiB,MAAM,QAAQ,SAAS;AACzD,kBAAiB;AAChB,kBAAiB;CAK7C,MAAM,8BAAgD;AACpD,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI,SAAS,YAAY,SAAS,QAAS,iDAAuB,KAAK;AACvE,SAAO,eAAe,sBAAsB;;CAG9C,MAAM,CAAC,aAAa,sCAA6C,sBAAsB;AAGvF,4BAAgB;AACd,MAAI,SAAS,UAAa,eAAe,mBACvC,gBAAe,cAAc,mBAAmB;IAEjD,CAAC,MAAM,eAAe,mBAAmB,CAAC;AAG7C,4BAAgB;AACd,MAAI,SAAS,YAAY,SAAS,QAAS,QAAO;EAElD,MAAM,qBAAqB,WAA0B;AACnD,kBAAe,SAAS,SAAS,QAAQ;;AAG3C,MAAI,SAAS,SAAS;AACpB,OAAI,OAAO,aAAa,YAAa,QAAO;GAC5C,MAAM,wDAA+B,SAAS,iBAAiB,kBAAkB;AACjF,gBAAa;AACX,aAAS,YAAY;;;EAIzB,MAAM,+DAAsC,kBAAkB;AAC9D,eAAa;AACX,eAAY,YAAY;;IAEzB,CAAC,KAAK,CAAC;CAEV,MAAMC,2CAA4C;AAChD,kBAAgB,SAA4B,SAAS,UAAU,SAAS,QAAS;IAChF,EAAE,CAAC;CAGN,MAAMC,4CAA4E;AAChF,MAAI,CAAC,cACH,QAAO;EAGT,MAAMC,aAA4CC,6CAA6B,eAAe,YAAY;AAE1G,MAAI,CAAC,cACH,QAAO;AAGT,SAAO;GACL,GAAG;GACH,GAAG;GACH,cAAc;IACZ,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,QAAQ;IACN,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,GAAI,WAAW,cAAc,cAAc,aACvC,EACE,YAAY;IACV,GAAI,WAAmB;IACvB,GAAG,cAAc;IAClB,EACF,GACD,EAAE;GACP;IACA;EAAC;EAAe;EAAa;EAAc,CAAC;CAE/C,MAAMC,sEACc,kBAAkB,gBAAgB,OAAO,EAC3D,CAAC,kBAAkB,YAAY,CAChC;CAED,MAAMC,YAA2B,eAAe,aAAa;AAG7D,4BAAgB;AACd,kCAAgB,MAAM;IACrB,CAAC,MAAM,CAAC;AAGX,4BAAgB;AACd,MAAI,OAAO,aAAa,YACtB,UAAS,gBAAgB,MAAM;IAEhC,CAAC,UAAU,CAAC;CAEf,MAAMC,QAAa;EACjB;EACA;EACA;EACA;EACD;AAED,QAAO,2CAACC,6BAAa;EAAgB;EAAQ;GAAiC;;AAGhF,4BAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDContext.cjs","names":["ThunderIDContext: Context<ThunderIDContextProps | null>","fallbackResourceInvalidator","VendorConstants"],"sources":["../../../../src/contexts/ThunderID/ThunderIDContext.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n ApiFetcher,\n FlowMetadataResponse,\n HttpRequestConfig,\n HttpResponse,\n IdToken,\n OIDCDiscoveryApiResponse,\n SignInOptions,\n TokenExchangeRequestConfig,\n TokenResponse,\n VendorConstants,\n AttributeSchema,\n} from '@thunderid/browser';\nimport {Context, createContext} from 'react';\nimport {ThunderIDReactConfig} from '../../models/config';\nimport ThunderIDReactClient from '../../ThunderIDReactClient';\nimport {ResourceInvalidator, fallbackResourceInvalidator} from '../../utils/createResourceInvalidator';\n\n/**\n * Props interface of {@link ThunderIDContext}\n */\nexport type ThunderIDContextProps = {\n afterSignInUrl: string | undefined;\n applicationId: string | undefined;\n baseUrl: string | undefined;\n clientId: string | undefined;\n /**\n * Optional endpoint URL overrides from the client config. Used to resolve resource-server\n * endpoints (e.g. flow/user-management) independently of `baseUrl` — see `resolveResourceEndpoint`.\n */\n endpoints?: ThunderIDReactConfig['endpoints'];\n preferences?: ThunderIDReactConfig['preferences'];\n scopes: string | string[] | undefined;\n /**\n * OIDC discovery data.\n */\n discovery: {\n /**\n * The response from the `.well-known/openid-configuration` endpoint.\n * Contains server capabilities, supported endpoints, and metadata.\n * `null` while loading or when discovery has not been fetched.\n */\n wellKnown: OIDCDiscoveryApiResponse | null;\n };\n /**\n * Swaps the current access token with a new one based on the provided configuration (with a grant type).\n * @param config - Configuration for the token exchange request.\n * @returns A promise that resolves to the token response or the raw response.\n */\n exchangeToken: (config: TokenExchangeRequestConfig) => Promise<TokenResponse | Response>;\n /**\n * Retrieves the access token stored in the storage.\n * This function retrieves the access token and returns it.\n * @remarks This does not work in the `webWorker` or any other worker environment.\n * @returns A promise that resolves to the access token.\n */\n getAccessToken: () => Promise<string>;\n /**\n * Function to retrieve the decoded ID token.\n * This function decodes the ID token and returns its payload.\n * It can be used to access user claims and other information contained in the ID token.\n *\n * @returns A promise that resolves to the decoded ID token payload.\n */\n getDecodedIdToken: () => Promise<IdToken>;\n /**\n * Function to retrieve the ID token.\n * This function retrieves the ID token and returns it.\n *\n * @returns A promise that resolves to the ID token.\n */\n getIdToken: () => Promise<string>;\n /**\n * Returns the underlying StorageManager instance for reading and writing SDK-managed storage.\n */\n getStorageManager: () => Promise<any>;\n /**\n * HTTP request function to make API calls.\n * @param requestConfig - Configuration for the HTTP request.\n * @returns A promise that resolves to the HTTP response.\n */\n http: {\n /**\n * Transport supplied through `ThunderIDProvider`'s `http.fetcher` prop, used by the management\n * hooks. `undefined` means the hooks fall back to the SDK's authenticated HTTP client.\n */\n fetcher?: ApiFetcher;\n /**\n * Makes an HTTP request using the provided configuration.\n * @param requestConfig - Configuration for the HTTP request.\n * @returns A promise that resolves to the HTTP response.\n */\n request: (requestConfig?: HttpRequestConfig) => Promise<HttpResponse<any>>;\n /**\n * Makes multiple HTTP requests based on the provided configuration.\n * @param requestConfigs - Set of configurations for the HTTP requests.\n * @returns A promise that resolves to an array of HTTP responses.\n */\n requestAll: (requestConfigs?: HttpRequestConfig[]) => Promise<HttpResponse<any>[]>;\n };\n /**\n * Coordinates refetching between the management hooks: a successful mutation invalidates the\n * queries whose data it changed.\n */\n invalidator?: ResourceInvalidator;\n /**\n * Instance ID for multi-instance support.\n */\n instanceId: number;\n isInitialized: boolean;\n /**\n * Flag indicating whether the SDK is working in the background.\n */\n isLoading: boolean;\n /**\n * Flag indicating whether flow metadata is currently being fetched.\n */\n isMetaLoading: boolean;\n /**\n * Flag indicating whether the user is signed in or not.\n */\n isSignedIn: boolean;\n\n /**\n * Flow metadata returned by `GET /flow/meta` (v2 platform only).\n * `null` while loading or when `resolveFromMeta` is disabled.\n */\n meta: FlowMetadataResponse | null;\n\n organizationHandle: string | undefined;\n\n /**\n * Re-initializes the client with a new configuration.\n *\n * @remarks\n * This can be partial configuration to update only specific fields.\n *\n * @param config - New configuration to re-initialize the client with.\n * @returns Promise resolving to boolean indicating success.\n */\n reInitialize: (config: Partial<ThunderIDReactConfig>) => Promise<boolean>;\n\n /**\n * Recovery function to initiate the account/password recovery flow.\n */\n recover: (...args: any[]) => Promise<any>;\n\n /**\n * Resolves `{{ t(key) }}` and `{{ meta(path) }}` template expressions in a string,\n * using the current i18n translation function and flow metadata from context.\n *\n * Useful in render-props patterns where consumers need to expand template strings\n * that come from the server (e.g. component labels, placeholders, headings).\n *\n * @example\n * const {resolveFlowTemplateLiterals} = useThunderID();\n * resolveFlowTemplateLiterals('{{ t(signin.heading.label) }}') // → 'Sign In'\n * resolveFlowTemplateLiterals('Login to {{ meta(application.name) }}') // → 'Login to My App'\n */\n resolveFlowTemplateLiterals: (text: string | undefined) => string;\n\n /**\n * Sign-in function to initiate the authentication process.\n * @remark This is the programmatic version of the `SignInButton` component.\n * TODO: Fix the types.\n */\n signIn: (...args: any) => Promise<any>;\n\n /**\n * Optional additional parameters to be sent in the sign-in request.\n * This can include custom parameters that your authorization server supports.\n * These parameters will be included in the authorization request sent to the server.\n * If not provided, no additional parameters will be sent.\n *\n * @example\n * signInOptions: { prompt: \"login\", fidp: \"OrganizationSSO\" }\n */\n signInOptions?: SignInOptions;\n\n /**\n * Optional token request configuration. params are appended to the token endpoint POST body.\n *\n * @example\n * tokenRequest: { params: { resource: \"https://api.example.com\" } }\n */\n tokenRequest?: {\n params?: Record<string, unknown>;\n };\n\n /**\n * Silent sign-in function to re-authenticate the user without user interaction.\n * @remark This is the programmatic version of the `SilentSignIn` component.\n */\n signInSilently: ThunderIDReactClient['signInSilently'];\n\n signInUrl: string | undefined;\n /**\n * Sign-out function to terminate the authentication session.\n * @remark This is the programmatic version of the `SignOutButton` component.\n * FIXME: Fix the types.\n */\n signOut: any;\n\n /**\n * Sign-up function to initiate the registration process.\n * @remark This is the programmatic version of the `SignUpButton` component.\n */\n signUp: (...args: any[]) => Promise<any>;\n\n signUpUrl: string | undefined;\n\n user: any;\n\n /**\n * Schema metadata for the user type returned by GET /users/me/meta.\n */\n userSchema: Record<string, AttributeSchema> | null;\n\n /**\n * Vendor/brand namespace used to prefix storage keys, cookie names, and CSS class names.\n * Resolved from the `vendor` config option, defaulting to `'thunderid'`.\n */\n vendor: string;\n} & Pick<ThunderIDReactClient, 'clearSession'>;\n\n/**\n * Context object for managing the Authentication flow builder core context.\n */\nconst ThunderIDContext: Context<ThunderIDContextProps | null> = createContext<null | ThunderIDContextProps>({\n afterSignInUrl: undefined,\n applicationId: undefined,\n baseUrl: undefined,\n clearSession: () => {},\n clientId: undefined,\n preferences: undefined,\n scopes: undefined,\n discovery: {\n wellKnown: null,\n },\n exchangeToken: null as unknown as ThunderIDContextProps['exchangeToken'],\n getAccessToken: null as unknown as ThunderIDContextProps['getAccessToken'],\n getDecodedIdToken: null as unknown as ThunderIDContextProps['getDecodedIdToken'],\n getIdToken: null as unknown as ThunderIDContextProps['getIdToken'],\n getStorageManager: () => Promise.resolve(null),\n http: {\n request: () => null as unknown as Promise<HttpResponse<any>>,\n requestAll: () => null as unknown as Promise<HttpResponse<any>[]>,\n },\n instanceId: 0,\n invalidator: fallbackResourceInvalidator,\n isInitialized: false,\n isLoading: true,\n isMetaLoading: false,\n isSignedIn: false,\n meta: null,\n organizationHandle: undefined,\n reInitialize: null as unknown as ThunderIDContextProps['reInitialize'],\n recover: () => Promise.resolve({} as any),\n resolveFlowTemplateLiterals: (text: string | undefined) => text ?? '',\n signIn: () => Promise.resolve({} as any),\n signInSilently: () => Promise.resolve({} as any),\n signInUrl: undefined,\n signOut: () => Promise.resolve({} as any),\n signUp: () => Promise.resolve({} as any),\n signUpUrl: undefined,\n user: null,\n userSchema: null,\n vendor: VendorConstants.VENDOR_PREFIX,\n});\n\nThunderIDContext.displayName = 'ThunderIDContext';\n\nexport default ThunderIDContext;\n"],"mappings":";;;;;;;;;;;AAuOA,MAAMA,4CAAsG;CAC1G,gBAAgB;CAChB,eAAe;CACf,SAAS;CACT,oBAAoB;CACpB,UAAU;CACV,aAAa;CACb,QAAQ;CACR,WAAW,EACT,WAAW,MACZ;CACD,eAAe;CACf,gBAAgB;CAChB,mBAAmB;CACnB,YAAY;CACZ,yBAAyB,QAAQ,QAAQ,KAAK;CAC9C,MAAM;EACJ,eAAe;EACf,kBAAkB;EACnB;CACD,YAAY;CACZ,aAAaC;CACb,eAAe;CACf,WAAW;CACX,eAAe;CACf,YAAY;CACZ,MAAM;CACN,oBAAoB;CACpB,cAAc;CACd,eAAe,QAAQ,QAAQ,EAAE,CAAQ;CACzC,8BAA8B,SAA6B,QAAQ;CACnE,cAAc,QAAQ,QAAQ,EAAE,CAAQ;CACxC,sBAAsB,QAAQ,QAAQ,EAAE,CAAQ;CAChD,WAAW;CACX,eAAe,QAAQ,QAAQ,EAAE,CAAQ;CACzC,cAAc,QAAQ,QAAQ,EAAE,CAAQ;CACxC,WAAW;CACX,MAAM;CACN,YAAY;CACZ,QAAQC,oCAAgB;CACzB,CAAC;AAEF,iBAAiB,cAAc;AAE/B,+BAAe"}
|
|
1
|
+
{"version":3,"file":"ThunderIDContext.cjs","names":["ThunderIDContext: Context<ThunderIDContextProps | null>","fallbackResourceInvalidator","VendorConstants"],"sources":["../../../../src/contexts/ThunderID/ThunderIDContext.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n ApiFetcher,\n EmbeddedRecoveryFlowRequest,\n EmbeddedRecoveryFlowResponse,\n FlowMetadataResponse,\n HttpRequestConfig,\n HttpResponse,\n IdToken,\n OIDCDiscoveryApiResponse,\n SignInOptions,\n StorageManager,\n TokenExchangeRequestConfig,\n TokenResponse,\n VendorConstants,\n AttributeSchema,\n} from '@thunderid/browser';\nimport {Context, createContext} from 'react';\nimport {ThunderIDReactConfig} from '../../models/config';\nimport ThunderIDReactClient from '../../ThunderIDReactClient';\nimport {ResourceInvalidator, fallbackResourceInvalidator} from '../../utils/createResourceInvalidator';\n\n/**\n * Props interface of {@link ThunderIDContext}\n */\nexport type ThunderIDContextProps = {\n afterSignInUrl: string | undefined;\n applicationId: string | undefined;\n baseUrl: string | undefined;\n clientId: string | undefined;\n /**\n * Optional endpoint URL overrides from the client config. Used to resolve resource-server\n * endpoints (e.g. flow/user-management) independently of `baseUrl` — see `resolveResourceEndpoint`.\n */\n endpoints?: ThunderIDReactConfig['endpoints'];\n preferences?: ThunderIDReactConfig['preferences'];\n scopes: string | string[] | undefined;\n /**\n * OIDC discovery data.\n */\n discovery: {\n /**\n * The response from the `.well-known/openid-configuration` endpoint.\n * Contains server capabilities, supported endpoints, and metadata.\n * `null` while loading or when discovery has not been fetched.\n */\n wellKnown: OIDCDiscoveryApiResponse | null;\n };\n /**\n * Swaps the current access token with a new one based on the provided configuration (with a grant type).\n * @param config - Configuration for the token exchange request.\n * @returns A promise that resolves to the token response or the raw response.\n */\n exchangeToken: (config: TokenExchangeRequestConfig) => Promise<TokenResponse | Response>;\n /**\n * Retrieves the access token stored in the storage.\n * This function retrieves the access token and returns it.\n * @remarks This does not work in the `webWorker` or any other worker environment.\n * @returns A promise that resolves to the access token.\n */\n getAccessToken: () => Promise<string>;\n /**\n * Function to retrieve the decoded ID token.\n * This function decodes the ID token and returns its payload.\n * It can be used to access user claims and other information contained in the ID token.\n *\n * @returns A promise that resolves to the decoded ID token payload.\n */\n getDecodedIdToken: () => Promise<IdToken>;\n /**\n * Function to retrieve the ID token.\n * This function retrieves the ID token and returns it.\n *\n * @returns A promise that resolves to the ID token.\n */\n getIdToken: () => Promise<string>;\n /**\n * Returns the underlying StorageManager instance for reading and writing SDK-managed storage.\n * Resolves to `null` where the SDK has no browser storage (the context default, and `@thunderid/nextjs`).\n */\n getStorageManager: () => Promise<StorageManager<ThunderIDReactConfig> | null>;\n /**\n * HTTP request function to make API calls.\n * @param requestConfig - Configuration for the HTTP request.\n * @returns A promise that resolves to the HTTP response.\n */\n http: {\n /**\n * Transport supplied through `ThunderIDProvider`'s `http.fetcher` prop, used by the management\n * hooks. `undefined` means the hooks fall back to the SDK's authenticated HTTP client.\n */\n fetcher?: ApiFetcher;\n /**\n * Makes an HTTP request using the provided configuration.\n * @param requestConfig - Configuration for the HTTP request.\n * @returns A promise that resolves to the HTTP response.\n */\n request: (requestConfig?: HttpRequestConfig) => Promise<HttpResponse<any>>;\n /**\n * Makes multiple HTTP requests based on the provided configuration.\n * @param requestConfigs - Set of configurations for the HTTP requests.\n * @returns A promise that resolves to an array of HTTP responses.\n */\n requestAll: (requestConfigs?: HttpRequestConfig[]) => Promise<HttpResponse<any>[]>;\n };\n /**\n * Coordinates refetching between the management hooks: a successful mutation invalidates the\n * queries whose data it changed.\n */\n invalidator?: ResourceInvalidator;\n /**\n * Instance ID for multi-instance support.\n */\n instanceId: number;\n isInitialized: boolean;\n /**\n * Flag indicating whether the SDK is working in the background.\n */\n isLoading: boolean;\n /**\n * Flag indicating whether flow metadata is currently being fetched.\n */\n isMetaLoading: boolean;\n /**\n * Flag indicating whether the user is signed in or not.\n */\n isSignedIn: boolean;\n\n /**\n * Flow metadata returned by `GET /flow/meta` (v2 platform only).\n * `null` while loading or when `resolveFromMeta` is disabled.\n */\n meta: FlowMetadataResponse | null;\n\n organizationHandle: string | undefined;\n\n /**\n * Re-initializes the client with a new configuration.\n *\n * @remarks\n * This can be partial configuration to update only specific fields.\n *\n * @param config - New configuration to re-initialize the client with.\n * @returns Promise resolving to boolean indicating success.\n */\n reInitialize: (config: Partial<ThunderIDReactConfig>) => Promise<boolean>;\n\n /**\n * Recovery function to initiate the account/password recovery flow.\n */\n recover: (payload?: EmbeddedRecoveryFlowRequest) => Promise<EmbeddedRecoveryFlowResponse>;\n\n /**\n * Resolves `{{ t(key) }}` and `{{ meta(path) }}` template expressions in a string,\n * using the current i18n translation function and flow metadata from context.\n *\n * Useful in render-props patterns where consumers need to expand template strings\n * that come from the server (e.g. component labels, placeholders, headings).\n *\n * @example\n * const {resolveFlowTemplateLiterals} = useThunderID();\n * resolveFlowTemplateLiterals('{{ t(signin.heading.label) }}') // → 'Sign In'\n * resolveFlowTemplateLiterals('Login to {{ meta(application.name) }}') // → 'Login to My App'\n */\n resolveFlowTemplateLiterals: (text: string | undefined) => string;\n\n /**\n * Sign-in function to initiate the authentication process.\n * @remark This is the programmatic version of the `SignInButton` component.\n * TODO: Fix the types.\n */\n signIn: (...args: any) => Promise<any>;\n\n /**\n * Optional additional parameters to be sent in the sign-in request.\n * This can include custom parameters that your authorization server supports.\n * These parameters will be included in the authorization request sent to the server.\n * If not provided, no additional parameters will be sent.\n *\n * @example\n * signInOptions: { prompt: \"login\", fidp: \"OrganizationSSO\" }\n */\n signInOptions?: SignInOptions;\n\n /**\n * Optional token request configuration. params are appended to the token endpoint POST body.\n *\n * @example\n * tokenRequest: { params: { resource: \"https://api.example.com\" } }\n */\n tokenRequest?: {\n params?: Record<string, unknown>;\n };\n\n /**\n * Silent sign-in function to re-authenticate the user without user interaction.\n * @remark This is the programmatic version of the `SilentSignIn` component.\n */\n signInSilently: ThunderIDReactClient['signInSilently'];\n\n signInUrl: string | undefined;\n /**\n * Sign-out function to terminate the authentication session.\n * @remark This is the programmatic version of the `SignOutButton` component.\n * FIXME: Fix the types.\n */\n signOut: any;\n\n /**\n * Sign-up function to initiate the registration process.\n * @remark This is the programmatic version of the `SignUpButton` component.\n */\n signUp: (...args: any[]) => Promise<any>;\n\n signUpUrl: string | undefined;\n\n user: any;\n\n /**\n * Schema metadata for the user type returned by GET /users/me/meta.\n */\n userSchema: Record<string, AttributeSchema> | null;\n\n /**\n * Vendor/brand namespace used to prefix storage keys, cookie names, and CSS class names.\n * Resolved from the `vendor` config option, defaulting to `'thunderid'`.\n */\n vendor: string;\n} & Pick<ThunderIDReactClient, 'clearSession'>;\n\n/**\n * Context object for managing the Authentication flow builder core context.\n */\nconst ThunderIDContext: Context<ThunderIDContextProps | null> = createContext<null | ThunderIDContextProps>({\n afterSignInUrl: undefined,\n applicationId: undefined,\n baseUrl: undefined,\n clearSession: () => {},\n clientId: undefined,\n preferences: undefined,\n scopes: undefined,\n discovery: {\n wellKnown: null,\n },\n exchangeToken: null as unknown as ThunderIDContextProps['exchangeToken'],\n getAccessToken: null as unknown as ThunderIDContextProps['getAccessToken'],\n getDecodedIdToken: null as unknown as ThunderIDContextProps['getDecodedIdToken'],\n getIdToken: null as unknown as ThunderIDContextProps['getIdToken'],\n getStorageManager: () => Promise.resolve(null),\n http: {\n request: () => null as unknown as Promise<HttpResponse<any>>,\n requestAll: () => null as unknown as Promise<HttpResponse<any>[]>,\n },\n instanceId: 0,\n invalidator: fallbackResourceInvalidator,\n isInitialized: false,\n isLoading: true,\n isMetaLoading: false,\n isSignedIn: false,\n meta: null,\n organizationHandle: undefined,\n reInitialize: null as unknown as ThunderIDContextProps['reInitialize'],\n recover: () => Promise.resolve({} as any),\n resolveFlowTemplateLiterals: (text: string | undefined) => text ?? '',\n signIn: () => Promise.resolve({} as any),\n signInSilently: () => Promise.resolve({} as any),\n signInUrl: undefined,\n signOut: () => Promise.resolve({} as any),\n signUp: () => Promise.resolve({} as any),\n signUpUrl: undefined,\n user: null,\n userSchema: null,\n vendor: VendorConstants.VENDOR_PREFIX,\n});\n\nThunderIDContext.displayName = 'ThunderIDContext';\n\nexport default ThunderIDContext;\n"],"mappings":";;;;;;;;;;;AA2OA,MAAMA,4CAAsG;CAC1G,gBAAgB;CAChB,eAAe;CACf,SAAS;CACT,oBAAoB;CACpB,UAAU;CACV,aAAa;CACb,QAAQ;CACR,WAAW,EACT,WAAW,MACZ;CACD,eAAe;CACf,gBAAgB;CAChB,mBAAmB;CACnB,YAAY;CACZ,yBAAyB,QAAQ,QAAQ,KAAK;CAC9C,MAAM;EACJ,eAAe;EACf,kBAAkB;EACnB;CACD,YAAY;CACZ,aAAaC;CACb,eAAe;CACf,WAAW;CACX,eAAe;CACf,YAAY;CACZ,MAAM;CACN,oBAAoB;CACpB,cAAc;CACd,eAAe,QAAQ,QAAQ,EAAE,CAAQ;CACzC,8BAA8B,SAA6B,QAAQ;CACnE,cAAc,QAAQ,QAAQ,EAAE,CAAQ;CACxC,sBAAsB,QAAQ,QAAQ,EAAE,CAAQ;CAChD,WAAW;CACX,eAAe,QAAQ,QAAQ,EAAE,CAAQ;CACzC,cAAc,QAAQ,QAAQ,EAAE,CAAQ;CACxC,WAAW;CACX,MAAM;CACN,YAAY;CACZ,QAAQC,oCAAgB;CACzB,CAAC;AAEF,iBAAiB,cAAc;AAE/B,+BAAe"}
|
|
@@ -265,7 +265,7 @@ const ThunderIDProvider = ({ afterSignInUrl, afterSignOutUrl, baseUrl: initialBa
|
|
|
265
265
|
const requestAll = (0, react.useCallback)(async (...args) => client.requestAll(...args), [client]);
|
|
266
266
|
const exchangeToken = (0, react.useCallback)(async (exchangeConfig) => client.exchangeToken(exchangeConfig), [client]);
|
|
267
267
|
const signOut = (0, react.useCallback)(async (...args) => client.signOut(...args), [client]);
|
|
268
|
-
const recover = (0, react.useCallback)(async (payload) => client.recover(payload), [client]);
|
|
268
|
+
const recover = (0, react.useCallback)(async (payload) => await client.recover(payload), [client]);
|
|
269
269
|
const signUp = (0, react.useCallback)(async (...args) => client.signUp(...args), [client]);
|
|
270
270
|
const clearSession = (0, react.useCallback)(async (...args) => client.clearSession(...args), [client]);
|
|
271
271
|
const reInitialize = (0, react.useCallback)(async (reInitConfig) => {
|