@aws-amplify/ui-react-core 3.0.25 → 3.0.26

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/elements.js CHANGED
@@ -4,9 +4,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
6
 
7
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
8
 
9
- var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
10
 
11
11
  const ElementsContext = React__default["default"].createContext(undefined);
12
12
  /**
@@ -0,0 +1,3 @@
1
+ export { default as defineBaseElement } from './elements/defineBaseElement.mjs';
2
+ export { default as withBaseElementProps } from './elements/withBaseElementProps.mjs';
3
+ export { ElementsProvider } from './elements/ElementsContext.mjs';
@@ -0,0 +1,65 @@
1
+ import { useState } from 'react';
2
+ import { isFunction } from '@aws-amplify/ui';
3
+ import { filterAllowedFiles } from '../utils/filterAllowedFiles.mjs';
4
+
5
+ function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _onDragLeave, onDragOver: _onDragOver, onDragStart: _onDragStart, onDrop: _onDrop, acceptedFileTypes = [], }) {
6
+ const [dragState, setDragState] = useState('inactive');
7
+ const onDragStart = (event) => {
8
+ event.dataTransfer.clearData();
9
+ if (isFunction(_onDragStart)) {
10
+ _onDragStart(event);
11
+ }
12
+ };
13
+ const onDragEnter = (event) => {
14
+ event.preventDefault();
15
+ event.stopPropagation();
16
+ if (isFunction(_onDragEnter)) {
17
+ _onDragEnter(event);
18
+ }
19
+ };
20
+ const onDragLeave = (event) => {
21
+ event.preventDefault();
22
+ event.stopPropagation();
23
+ setDragState('inactive');
24
+ if (isFunction(_onDragLeave)) {
25
+ _onDragLeave(event);
26
+ }
27
+ };
28
+ const onDragOver = (event) => {
29
+ event.preventDefault();
30
+ event.stopPropagation();
31
+ event.dataTransfer.dropEffect = 'copy';
32
+ if (isFunction(_onDragOver)) {
33
+ _onDragOver(event);
34
+ }
35
+ const files = Array.from(event.dataTransfer.items).map(({ kind, type }) => ({
36
+ kind,
37
+ type,
38
+ }));
39
+ const { rejectedFiles } = filterAllowedFiles(files, acceptedFileTypes);
40
+ setDragState(rejectedFiles.length > 0 ? 'reject' : 'accept');
41
+ };
42
+ const onDrop = (event) => {
43
+ event.preventDefault();
44
+ event.stopPropagation();
45
+ setDragState('inactive');
46
+ const files = Array.from(event.dataTransfer.files);
47
+ const { acceptedFiles, rejectedFiles } = filterAllowedFiles(files, acceptedFileTypes);
48
+ if (isFunction(_onDrop)) {
49
+ _onDrop(event);
50
+ }
51
+ if (isFunction(onDropComplete)) {
52
+ onDropComplete({ acceptedFiles, rejectedFiles });
53
+ }
54
+ };
55
+ return {
56
+ onDragStart,
57
+ onDragEnter,
58
+ onDragLeave,
59
+ onDragOver,
60
+ onDrop,
61
+ dragState,
62
+ };
63
+ }
64
+
65
+ export { useDropZone as default };
@@ -1,5 +1,5 @@
1
1
  import React__default from 'react';
2
- import { isTypedFunction } from '@aws-amplify/ui';
2
+ import { isFunction } from '@aws-amplify/ui';
3
3
 
4
4
  function useTimeout({ callback, delay, }) {
5
5
  const storedCallback = React__default.useRef(callback);
@@ -7,7 +7,7 @@ function useTimeout({ callback, delay, }) {
7
7
  storedCallback.current = callback;
8
8
  }, [callback]);
9
9
  React__default.useEffect(() => {
10
- if (!isTypedFunction(storedCallback.current) || !delay) {
10
+ if (!isFunction(storedCallback.current) || !delay) {
11
11
  return;
12
12
  }
13
13
  const timeoutId = setTimeout(() => {
@@ -16,4 +16,5 @@ export { default as useHasValueUpdated } from './hooks/useHasValueUpdated.mjs';
16
16
  export { default as usePreviousValue } from './hooks/usePreviousValue.mjs';
17
17
  export { default as useSetUserAgent } from './hooks/useSetUserAgent.mjs';
18
18
  export { default as useTimeout } from './hooks/useTimeout.mjs';
19
+ export { default as useDropZone } from './hooks/useDropZone.mjs';
19
20
  export { default as createContextUtilities } from './utils/createContextUtilities.mjs';
@@ -0,0 +1,34 @@
1
+ function filterAllowedFiles(files, acceptedFileTypes) {
2
+ // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*'
3
+ if (!acceptedFileTypes ||
4
+ acceptedFileTypes.length === 0 ||
5
+ acceptedFileTypes.includes('*')) {
6
+ return { acceptedFiles: files, rejectedFiles: [] };
7
+ }
8
+ const acceptedFiles = [];
9
+ const rejectedFiles = [];
10
+ function filterFile(file) {
11
+ const { type = '', name = '' } = file;
12
+ const mimeType = type.toLowerCase();
13
+ const baseMimeType = mimeType.split('/')[0];
14
+ return acceptedFileTypes.some((type) => {
15
+ const validType = type.trim().toLowerCase();
16
+ // if the accepted file type is a file extension
17
+ // it will start with '.', check against the file name
18
+ if (validType.charAt(0) === '.') {
19
+ return name.toLowerCase().endsWith(validType);
20
+ }
21
+ // This is something like a image/* mime type
22
+ if (validType.endsWith('/*')) {
23
+ return baseMimeType === validType.split('/')[0];
24
+ }
25
+ return mimeType === validType;
26
+ });
27
+ }
28
+ files.forEach((file) => {
29
+ (filterFile(file) ? acceptedFiles : rejectedFiles).push(file);
30
+ });
31
+ return { acceptedFiles, rejectedFiles };
32
+ }
33
+
34
+ export { filterAllowedFiles };
package/dist/index.js CHANGED
@@ -9,8 +9,6 @@ var ui = require('@aws-amplify/ui');
9
9
  var reactHookForm = require('react-hook-form');
10
10
  var storage = require('aws-amplify/storage');
11
11
 
12
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
-
14
12
  function _interopNamespace(e) {
15
13
  if (e && e.__esModule) return e;
16
14
  var n = Object.create(null);
@@ -29,7 +27,6 @@ function _interopNamespace(e) {
29
27
  return Object.freeze(n);
30
28
  }
31
29
 
32
- var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
33
30
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
31
 
35
32
  /**
@@ -37,7 +34,7 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
37
34
  *
38
35
  * https://xstate.js.org/docs/recipes/react.html#context-provider
39
36
  */
40
- const AuthenticatorContext = React__default["default"].createContext(null);
37
+ const AuthenticatorContext = React__namespace["default"].createContext(null);
41
38
 
42
39
  const createHubHandler = (options) => (data, service) => {
43
40
  ui.defaultAuthHubHandler(data, service, options);
@@ -47,9 +44,9 @@ function AuthenticatorProvider({ children, }) {
47
44
  // state machine as the machine only updates on `Authenticator` initiated events, which
48
45
  // leads to scenarios where the state machine `authStatus` gets "stuck". For exmample,
49
46
  // if a user was to sign in using `Auth.signIn` directly rather than using `Authenticator`
50
- const [authStatus, setAuthStatus] = React__default["default"].useState('configuring');
47
+ const [authStatus, setAuthStatus] = React__namespace["default"].useState('configuring');
51
48
  // only run on first render
52
- React__default["default"].useEffect(() => {
49
+ React__namespace["default"].useEffect(() => {
53
50
  auth.getCurrentUser()
54
51
  .then(() => {
55
52
  setAuthStatus('authenticated');
@@ -79,7 +76,7 @@ function AuthenticatorProvider({ children, }) {
79
76
  const unsubscribe = ui.listenToAuthHub(activeService, createHubHandler({ onSignIn, onSignOut }));
80
77
  return unsubscribe;
81
78
  }, [activeService]);
82
- return (React__default["default"].createElement(AuthenticatorContext.Provider, { value: value }, children));
79
+ return (React__namespace["default"].createElement(AuthenticatorContext.Provider, { value: value }, children));
83
80
  }
84
81
 
85
82
  const USE_AUTHENTICATOR_ERROR = '`useAuthenticator` must be used inside an `Authenticator.Provider`.';
@@ -185,7 +182,7 @@ const getMachineFields = (route, state, unverifiedUserAttributes) => {
185
182
  * [📖 Docs](https://ui.docs.amplify.aws/react/connected-components/authenticator/headless#useauthenticator-hook)
186
183
  */
187
184
  function useAuthenticator(selector) {
188
- const context = React__default["default"].useContext(AuthenticatorContext);
185
+ const context = React__namespace["default"].useContext(AuthenticatorContext);
189
186
  if (!context) {
190
187
  throw new Error(USE_AUTHENTICATOR_ERROR);
191
188
  }
@@ -243,13 +240,13 @@ function useForm(options = {}) {
243
240
  };
244
241
  // memoize `registerField` and `setFormValue` together,
245
242
  // `register` and `setValue` maintain stable references
246
- const { registerField, setFormValue } = React__default["default"].useMemo(() => {
243
+ const { registerField, setFormValue } = React__namespace["default"].useMemo(() => {
247
244
  return {
248
245
  registerField: ({ name, ...options }) => register(name, options),
249
246
  setFormValue: ({ name, value, ...options }) => setValue(name, value, options),
250
247
  };
251
248
  }, [register, setValue]);
252
- const onSubmit = React__default["default"].useCallback((event) => {
249
+ const onSubmit = React__namespace["default"].useCallback((event) => {
253
250
  const handler = _onSubmit ? handleSubmit(_onSubmit) : ui.noop;
254
251
  handler(event);
255
252
  }, [_onSubmit, handleSubmit]);
@@ -282,14 +279,14 @@ function useField(params) {
282
279
  }
283
280
 
284
281
  const DEFAULT_MODE = 'onTouched';
285
- const FormProvider = React__default["default"].forwardRef(function FormProvider({ children, defaultValues, mode = DEFAULT_MODE }, ref) {
282
+ const FormProvider = React__namespace["default"].forwardRef(function FormProvider({ children, defaultValues, mode = DEFAULT_MODE }, ref) {
286
283
  const formProviderProps = reactHookForm.useForm({
287
284
  defaultValues,
288
285
  mode,
289
286
  });
290
287
  const { getValues, reset } = formProviderProps;
291
- React__default["default"].useImperativeHandle(ref, () => ({ getValues, reset: () => reset(defaultValues) }), [defaultValues, getValues, reset]);
292
- return (React__default["default"].createElement(reactHookForm.FormProvider, { ...formProviderProps }, children));
288
+ React__namespace["default"].useImperativeHandle(ref, () => ({ getValues, reset: () => reset(defaultValues) }), [defaultValues, getValues, reset]);
289
+ return (React__namespace["default"].createElement(reactHookForm.FormProvider, { ...formProviderProps }, children));
293
290
  });
294
291
 
295
292
  /**
@@ -297,9 +294,9 @@ const FormProvider = React__default["default"].forwardRef(function FormProvider(
297
294
  * @returns Composed `Form` component exposing `FormContext` values to descendents
298
295
  */
299
296
  function withFormProvider(Child) {
300
- return React__default["default"].forwardRef(function Form({ defaultValues, mode, ...props }, ref) {
301
- return (React__default["default"].createElement(FormProvider, { defaultValues: defaultValues, mode: mode, ref: ref },
302
- React__default["default"].createElement(Child, { ...props })));
297
+ return React__namespace["default"].forwardRef(function Form({ defaultValues, mode, ...props }, ref) {
298
+ return (React__namespace["default"].createElement(FormProvider, { defaultValues: defaultValues, mode: mode, ref: ref },
299
+ React__namespace["default"].createElement(Child, { ...props })));
303
300
  });
304
301
  }
305
302
 
@@ -549,8 +546,8 @@ function useAuthenticatorRoute({ components, }) {
549
546
  const routeSelector = ({ route }) => [route];
550
547
  function useAuthenticatorInitMachine(data) {
551
548
  const { route, initializeMachine } = useAuthenticator(routeSelector);
552
- const hasInitialized = React__default["default"].useRef(false);
553
- React__default["default"].useEffect(() => {
549
+ const hasInitialized = React__namespace["default"].useRef(false);
550
+ React__namespace["default"].useEffect(() => {
554
551
  if (!hasInitialized.current && route === 'setup') {
555
552
  initializeMachine(data);
556
553
  hasInitialized.current = true;
@@ -567,12 +564,12 @@ const resolveMaybeAsync = async (value) => {
567
564
  return awaited;
568
565
  };
569
566
  function useDataState(action, initialData) {
570
- const [dataState, setDataState] = React__default["default"].useState(() => ({
567
+ const [dataState, setDataState] = React__namespace["default"].useState(() => ({
571
568
  ...INITIAL_STATE,
572
569
  data: initialData,
573
570
  }));
574
- const prevData = React__default["default"].useRef(initialData);
575
- const handleAction = React__default["default"].useCallback((...input) => {
571
+ const prevData = React__namespace["default"].useRef(initialData);
572
+ const handleAction = React__namespace["default"].useCallback((...input) => {
576
573
  setDataState(({ data }) => ({ ...LOADING_STATE, data }));
577
574
  resolveMaybeAsync(action(prevData.current, ...input))
578
575
  .then((data) => {
@@ -679,12 +676,12 @@ function useSetUserAgent({ componentName, packageName, version, }) {
679
676
  }
680
677
 
681
678
  function useTimeout({ callback, delay, }) {
682
- const storedCallback = React__default["default"].useRef(callback);
683
- React__default["default"].useLayoutEffect(() => {
679
+ const storedCallback = React__namespace["default"].useRef(callback);
680
+ React__namespace["default"].useLayoutEffect(() => {
684
681
  storedCallback.current = callback;
685
682
  }, [callback]);
686
- React__default["default"].useEffect(() => {
687
- if (!ui.isTypedFunction(storedCallback.current) || !delay) {
683
+ React__namespace["default"].useEffect(() => {
684
+ if (!ui.isFunction(storedCallback.current) || !delay) {
688
685
  return;
689
686
  }
690
687
  const timeoutId = setTimeout(() => {
@@ -696,6 +693,99 @@ function useTimeout({ callback, delay, }) {
696
693
  }, [delay]);
697
694
  }
698
695
 
696
+ function filterAllowedFiles(files, acceptedFileTypes) {
697
+ // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*'
698
+ if (!acceptedFileTypes ||
699
+ acceptedFileTypes.length === 0 ||
700
+ acceptedFileTypes.includes('*')) {
701
+ return { acceptedFiles: files, rejectedFiles: [] };
702
+ }
703
+ const acceptedFiles = [];
704
+ const rejectedFiles = [];
705
+ function filterFile(file) {
706
+ const { type = '', name = '' } = file;
707
+ const mimeType = type.toLowerCase();
708
+ const baseMimeType = mimeType.split('/')[0];
709
+ return acceptedFileTypes.some((type) => {
710
+ const validType = type.trim().toLowerCase();
711
+ // if the accepted file type is a file extension
712
+ // it will start with '.', check against the file name
713
+ if (validType.charAt(0) === '.') {
714
+ return name.toLowerCase().endsWith(validType);
715
+ }
716
+ // This is something like a image/* mime type
717
+ if (validType.endsWith('/*')) {
718
+ return baseMimeType === validType.split('/')[0];
719
+ }
720
+ return mimeType === validType;
721
+ });
722
+ }
723
+ files.forEach((file) => {
724
+ (filterFile(file) ? acceptedFiles : rejectedFiles).push(file);
725
+ });
726
+ return { acceptedFiles, rejectedFiles };
727
+ }
728
+
729
+ function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _onDragLeave, onDragOver: _onDragOver, onDragStart: _onDragStart, onDrop: _onDrop, acceptedFileTypes = [], }) {
730
+ const [dragState, setDragState] = React.useState('inactive');
731
+ const onDragStart = (event) => {
732
+ event.dataTransfer.clearData();
733
+ if (ui.isFunction(_onDragStart)) {
734
+ _onDragStart(event);
735
+ }
736
+ };
737
+ const onDragEnter = (event) => {
738
+ event.preventDefault();
739
+ event.stopPropagation();
740
+ if (ui.isFunction(_onDragEnter)) {
741
+ _onDragEnter(event);
742
+ }
743
+ };
744
+ const onDragLeave = (event) => {
745
+ event.preventDefault();
746
+ event.stopPropagation();
747
+ setDragState('inactive');
748
+ if (ui.isFunction(_onDragLeave)) {
749
+ _onDragLeave(event);
750
+ }
751
+ };
752
+ const onDragOver = (event) => {
753
+ event.preventDefault();
754
+ event.stopPropagation();
755
+ event.dataTransfer.dropEffect = 'copy';
756
+ if (ui.isFunction(_onDragOver)) {
757
+ _onDragOver(event);
758
+ }
759
+ const files = Array.from(event.dataTransfer.items).map(({ kind, type }) => ({
760
+ kind,
761
+ type,
762
+ }));
763
+ const { rejectedFiles } = filterAllowedFiles(files, acceptedFileTypes);
764
+ setDragState(rejectedFiles.length > 0 ? 'reject' : 'accept');
765
+ };
766
+ const onDrop = (event) => {
767
+ event.preventDefault();
768
+ event.stopPropagation();
769
+ setDragState('inactive');
770
+ const files = Array.from(event.dataTransfer.files);
771
+ const { acceptedFiles, rejectedFiles } = filterAllowedFiles(files, acceptedFileTypes);
772
+ if (ui.isFunction(_onDrop)) {
773
+ _onDrop(event);
774
+ }
775
+ if (ui.isFunction(onDropComplete)) {
776
+ onDropComplete({ acceptedFiles, rejectedFiles });
777
+ }
778
+ };
779
+ return {
780
+ onDragStart,
781
+ onDragEnter,
782
+ onDragLeave,
783
+ onDragOver,
784
+ onDrop,
785
+ dragState,
786
+ };
787
+ }
788
+
699
789
  const INVALID_OPTIONS_MESSAGE = 'an `errorMessage` or a `defaultValue` must be provided in `options`';
700
790
  /**
701
791
  * Uses `ContextType`/`Name` generics and `options` to create:
@@ -748,20 +838,20 @@ function createContextUtilities(options) {
748
838
  if (ui.isUndefined(defaultValue) && !ui.isString(errorMessage)) {
749
839
  throw new Error(INVALID_OPTIONS_MESSAGE);
750
840
  }
751
- const Context = React__default["default"].createContext(defaultValue);
841
+ const Context = React__namespace["default"].createContext(defaultValue);
752
842
  function Provider(props) {
753
843
  const { children, ...context } = props;
754
- const value = React__default["default"].useMemo(() => context,
844
+ const value = React__namespace["default"].useMemo(() => context,
755
845
  // Unpack `context` for the dep array; using `[context]` results in
756
846
  // evaluation on every render
757
847
  // eslint-disable-next-line react-hooks/exhaustive-deps
758
848
  Object.values(context));
759
- return React__default["default"].createElement(Context.Provider, { value: value }, children);
849
+ return React__namespace["default"].createElement(Context.Provider, { value: value }, children);
760
850
  }
761
851
  Provider.displayName = `${contextName}Provider`;
762
852
  return {
763
853
  [`use${contextName}`]: function (params) {
764
- const context = React__default["default"].useContext(Context);
854
+ const context = React__namespace["default"].useContext(Context);
765
855
  if (ui.isUndefined(context)) {
766
856
  throw new Error(params?.errorMessage ?? errorMessage);
767
857
  }
@@ -783,6 +873,7 @@ exports.useAuthenticatorInitMachine = useAuthenticatorInitMachine;
783
873
  exports.useAuthenticatorRoute = useAuthenticatorRoute;
784
874
  exports.useDataState = useDataState;
785
875
  exports.useDeprecationWarning = useDeprecationWarning;
876
+ exports.useDropZone = useDropZone;
786
877
  exports.useField = useField;
787
878
  exports.useForm = useForm;
788
879
  exports.useGetUrl = useGetUrl;
@@ -5,3 +5,4 @@ export { default as useHasValueUpdated } from './useHasValueUpdated';
5
5
  export { default as usePreviousValue } from './usePreviousValue';
6
6
  export { default as useSetUserAgent } from './useSetUserAgent';
7
7
  export { default as useTimeout } from './useTimeout';
8
+ export { default as useDropZone, UseDropZoneParams } from './useDropZone';
@@ -0,0 +1,25 @@
1
+ /// <reference types="react" />
2
+ interface DragEvents {
3
+ onDragStart: (event: React.DragEvent<HTMLDivElement>) => void;
4
+ onDragEnter: (event: React.DragEvent<HTMLDivElement>) => void;
5
+ onDragLeave: (event: React.DragEvent<HTMLDivElement>) => void;
6
+ onDragOver: (event: React.DragEvent<HTMLDivElement>) => void;
7
+ onDrop: (event: React.DragEvent<HTMLDivElement>) => void;
8
+ }
9
+ export interface UseDropZoneParams extends Partial<DragEvents> {
10
+ onDropComplete?: (props: {
11
+ acceptedFiles: File[];
12
+ rejectedFiles: File[];
13
+ }) => void;
14
+ /**
15
+ * List of accepted File types, values of `['*']` or undefined allow any files
16
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
17
+ */
18
+ acceptedFileTypes?: string[];
19
+ }
20
+ type DragState = 'accept' | 'reject' | 'inactive';
21
+ interface UseDropZoneReturn extends DragEvents {
22
+ dragState: DragState;
23
+ }
24
+ export default function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _onDragLeave, onDragOver: _onDragOver, onDragStart: _onDragStart, onDrop: _onDrop, acceptedFileTypes, }: UseDropZoneParams): UseDropZoneReturn;
25
+ export {};
@@ -1,5 +1,5 @@
1
1
  export { AuthenticatorComponentDefaults, AuthenticatorComponentDefaultProps, AuthenticatorComponentOverrides, AuthenticatorFooterComponent, AuthenticatorFormFieldsComponent, AuthenticatorHeaderComponent, AuthenticatorLegacyField, AuthenticatorMachineContext, AuthenticatorProvider, AuthenticatorRouteComponentKey, AuthenticatorRouteComponentName, isAuthenticatorComponentRouteKey, resolveAuthenticatorComponents, useAuthenticator, useAuthenticatorRoute, UseAuthenticator, useAuthenticatorInitMachine, UseAuthenticatorRoute, } from './Authenticator';
2
2
  export { FormProvider, FormProviderProps, RenderNothing, FormValues, FormHandle, useField, useForm, UseForm, Validate, Validator, withFormProvider, } from './components';
3
- export { useDeprecationWarning, UseDeprecationWarning, useGetUrl, useHasValueUpdated, usePreviousValue, useSetUserAgent, useTimeout, useDataState, DataState, } from './hooks';
3
+ export { useDeprecationWarning, UseDeprecationWarning, useGetUrl, useHasValueUpdated, usePreviousValue, useSetUserAgent, useTimeout, useDataState, DataState, useDropZone, UseDropZoneParams, } from './hooks';
4
4
  export { MergeProps } from './types';
5
5
  export { createContextUtilities } from './utils';
@@ -0,0 +1,10 @@
1
+ type DragFile = {
2
+ kind: string;
3
+ type: string;
4
+ name?: string;
5
+ } | File;
6
+ export declare function filterAllowedFiles<FileType extends DragFile = DragFile>(files: FileType[], acceptedFileTypes: string[]): {
7
+ acceptedFiles: FileType[];
8
+ rejectedFiles: FileType[];
9
+ };
10
+ export {};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/ui-react-core/elements",
3
3
  "main": "../dist/elements.js",
4
- "module": "../dist/esm/elements/elements.mjs",
4
+ "module": "../dist/esm/elements.mjs",
5
5
  "types": "../dist/types/elements/index.d.ts"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/ui-react-core",
3
- "version": "3.0.25",
3
+ "version": "3.0.26",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/esm/index.mjs",
6
6
  "react-native": "src/index.ts",
@@ -11,7 +11,7 @@
11
11
  "types": "./dist/types/index.d.ts"
12
12
  },
13
13
  "./elements": {
14
- "import": "./dist/esm/elements/elements.mjs",
14
+ "import": "./dist/esm/elements.mjs",
15
15
  "require": "./dist/elements.js",
16
16
  "react-native": "./src/elements/index.ts",
17
17
  "types": "./dist/types/elements/index.d.ts"
@@ -33,22 +33,19 @@
33
33
  "build": "rollup --config",
34
34
  "check:esm": "node --input-type=module --eval 'import \"@aws-amplify/ui-react-core\"'",
35
35
  "dev": "yarn build --watch",
36
- "lint": "yarn typecheck && eslint src",
36
+ "lint": "yarn typecheck && eslint .",
37
37
  "prebuild": "rimraf dist",
38
38
  "test": "jest",
39
39
  "test:watch": "yarn test --watch",
40
40
  "typecheck": "tsc --noEmit"
41
41
  },
42
42
  "dependencies": {
43
- "@aws-amplify/ui": "6.6.1",
43
+ "@aws-amplify/ui": "6.6.2",
44
44
  "@xstate/react": "^3.2.2",
45
45
  "lodash": "4.17.21",
46
46
  "react-hook-form": "^7.43.5",
47
47
  "xstate": "^4.33.6"
48
48
  },
49
- "devDependencies": {
50
- "@aws-amplify/core": "^6.4.0"
51
- },
52
49
  "peerDependencies": {
53
50
  "aws-amplify": "^6.6.0",
54
51
  "react": "^16.14.0 || ^17.0 || ^18.0"
@@ -6,7 +6,7 @@ import {
6
6
  UseControlledField,
7
7
  UseControlledFieldParams,
8
8
  } from './types';
9
- import { isTypedFunction } from '@aws-amplify/ui';
9
+ import { isFunction } from '@aws-amplify/ui';
10
10
 
11
11
  export const DEFAULT_ERROR_MESSAGE =
12
12
  '`useControlledField` must be used within a `FormProvider`';
@@ -44,7 +44,7 @@ export default function useControlledField<OnBlur extends FocusHandler>({
44
44
  const hasError = !!errorMessage;
45
45
 
46
46
  const handleBlur = (event: Parameters<OnBlur>[0]) => {
47
- if (isTypedFunction(_onBlur)) {
47
+ if (isFunction(_onBlur)) {
48
48
  _onBlur(event);
49
49
  }
50
50
  // `useController.onBlur` does not receive params
@@ -52,7 +52,7 @@ export default function useControlledField<OnBlur extends FocusHandler>({
52
52
  };
53
53
 
54
54
  const handleChangeText = (event: string) => {
55
- if (isTypedFunction(_onChangeText)) {
55
+ if (isFunction(_onChangeText)) {
56
56
  _onChangeText(event);
57
57
  }
58
58
  onChangeText(event);
@@ -9,3 +9,4 @@ export { default as useHasValueUpdated } from './useHasValueUpdated';
9
9
  export { default as usePreviousValue } from './usePreviousValue';
10
10
  export { default as useSetUserAgent } from './useSetUserAgent';
11
11
  export { default as useTimeout } from './useTimeout';
12
+ export { default as useDropZone, UseDropZoneParams } from './useDropZone';
@@ -0,0 +1,110 @@
1
+ import { useState } from 'react';
2
+ import { isFunction } from '@aws-amplify/ui';
3
+ import { filterAllowedFiles } from '../utils/filterAllowedFiles';
4
+
5
+ interface DragEvents {
6
+ onDragStart: (event: React.DragEvent<HTMLDivElement>) => void;
7
+ onDragEnter: (event: React.DragEvent<HTMLDivElement>) => void;
8
+ onDragLeave: (event: React.DragEvent<HTMLDivElement>) => void;
9
+ onDragOver: (event: React.DragEvent<HTMLDivElement>) => void;
10
+ onDrop: (event: React.DragEvent<HTMLDivElement>) => void;
11
+ }
12
+
13
+ export interface UseDropZoneParams extends Partial<DragEvents> {
14
+ onDropComplete?: (props: {
15
+ acceptedFiles: File[];
16
+ rejectedFiles: File[];
17
+ }) => void;
18
+ /**
19
+ * List of accepted File types, values of `['*']` or undefined allow any files
20
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
21
+ */
22
+ acceptedFileTypes?: string[];
23
+ }
24
+
25
+ type DragState = 'accept' | 'reject' | 'inactive';
26
+
27
+ interface UseDropZoneReturn extends DragEvents {
28
+ dragState: DragState;
29
+ }
30
+
31
+ export default function useDropZone({
32
+ onDropComplete,
33
+ onDragEnter: _onDragEnter,
34
+ onDragLeave: _onDragLeave,
35
+ onDragOver: _onDragOver,
36
+ onDragStart: _onDragStart,
37
+ onDrop: _onDrop,
38
+ acceptedFileTypes = [],
39
+ }: UseDropZoneParams): UseDropZoneReturn {
40
+ const [dragState, setDragState] = useState<DragState>('inactive');
41
+
42
+ const onDragStart = (event: React.DragEvent<HTMLDivElement>) => {
43
+ event.dataTransfer.clearData();
44
+ if (isFunction(_onDragStart)) {
45
+ _onDragStart(event);
46
+ }
47
+ };
48
+
49
+ const onDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
50
+ event.preventDefault();
51
+ event.stopPropagation();
52
+ if (isFunction(_onDragEnter)) {
53
+ _onDragEnter(event);
54
+ }
55
+ };
56
+
57
+ const onDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
58
+ event.preventDefault();
59
+ event.stopPropagation();
60
+ setDragState('inactive');
61
+ if (isFunction(_onDragLeave)) {
62
+ _onDragLeave(event);
63
+ }
64
+ };
65
+
66
+ const onDragOver = (event: React.DragEvent<HTMLDivElement>) => {
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ event.dataTransfer.dropEffect = 'copy';
70
+ if (isFunction(_onDragOver)) {
71
+ _onDragOver(event);
72
+ }
73
+ const files = Array.from(event.dataTransfer.items).map(
74
+ ({ kind, type }) => ({
75
+ kind,
76
+ type,
77
+ })
78
+ );
79
+
80
+ const { rejectedFiles } = filterAllowedFiles(files, acceptedFileTypes);
81
+ setDragState(rejectedFiles.length > 0 ? 'reject' : 'accept');
82
+ };
83
+
84
+ const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
85
+ event.preventDefault();
86
+ event.stopPropagation();
87
+ setDragState('inactive');
88
+ const files = Array.from(event.dataTransfer.files);
89
+ const { acceptedFiles, rejectedFiles } = filterAllowedFiles<File>(
90
+ files,
91
+ acceptedFileTypes
92
+ );
93
+
94
+ if (isFunction(_onDrop)) {
95
+ _onDrop(event);
96
+ }
97
+ if (isFunction(onDropComplete)) {
98
+ onDropComplete({ acceptedFiles, rejectedFiles });
99
+ }
100
+ };
101
+
102
+ return {
103
+ onDragStart,
104
+ onDragEnter,
105
+ onDragLeave,
106
+ onDragOver,
107
+ onDrop,
108
+ dragState,
109
+ };
110
+ }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { isTypedFunction } from '@aws-amplify/ui';
2
+ import { isFunction } from '@aws-amplify/ui';
3
3
 
4
4
  export default function useTimeout({
5
5
  callback,
@@ -15,7 +15,7 @@ export default function useTimeout({
15
15
  }, [callback]);
16
16
 
17
17
  React.useEffect(() => {
18
- if (!isTypedFunction(storedCallback.current) || !delay) {
18
+ if (!isFunction(storedCallback.current) || !delay) {
19
19
  return;
20
20
  }
21
21
 
package/src/index.ts CHANGED
@@ -44,6 +44,8 @@ export {
44
44
  useTimeout,
45
45
  useDataState,
46
46
  DataState,
47
+ useDropZone,
48
+ UseDropZoneParams,
47
49
  } from './hooks';
48
50
 
49
51
  export { MergeProps } from './types';
@@ -0,0 +1,50 @@
1
+ // Drag event file shape is different than the drop event fileshape
2
+ type DragFile =
3
+ | {
4
+ kind: string;
5
+ type: string;
6
+ name?: string;
7
+ }
8
+ | File;
9
+
10
+ export function filterAllowedFiles<FileType extends DragFile = DragFile>(
11
+ files: FileType[],
12
+ acceptedFileTypes: string[]
13
+ ): { acceptedFiles: FileType[]; rejectedFiles: FileType[] } {
14
+ // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*'
15
+ if (
16
+ !acceptedFileTypes ||
17
+ acceptedFileTypes.length === 0 ||
18
+ acceptedFileTypes.includes('*')
19
+ ) {
20
+ return { acceptedFiles: files, rejectedFiles: [] };
21
+ }
22
+ const acceptedFiles: FileType[] = [];
23
+ const rejectedFiles: FileType[] = [];
24
+
25
+ function filterFile(file: DragFile) {
26
+ const { type = '', name = '' } = file;
27
+ const mimeType = type.toLowerCase();
28
+ const baseMimeType = mimeType.split('/')[0];
29
+
30
+ return acceptedFileTypes.some((type) => {
31
+ const validType = type.trim().toLowerCase();
32
+ // if the accepted file type is a file extension
33
+ // it will start with '.', check against the file name
34
+ if (validType.charAt(0) === '.') {
35
+ return name.toLowerCase().endsWith(validType);
36
+ }
37
+ // This is something like a image/* mime type
38
+ if (validType.endsWith('/*')) {
39
+ return baseMimeType === validType.split('/')[0];
40
+ }
41
+ return mimeType === validType;
42
+ });
43
+ }
44
+
45
+ files.forEach((file) => {
46
+ (filterFile(file) ? acceptedFiles : rejectedFiles).push(file);
47
+ });
48
+
49
+ return { acceptedFiles, rejectedFiles };
50
+ }
@@ -1,3 +0,0 @@
1
- export { default as defineBaseElement } from './defineBaseElement.mjs';
2
- export { default as withBaseElementProps } from './withBaseElementProps.mjs';
3
- export { ElementsProvider } from './ElementsContext.mjs';