@transferwise/components 0.0.0-experimental-e9526a8 → 0.0.0-experimental-9a61b0a
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/build/i18n/en.json +8 -0
- package/build/index.esm.js +388 -241
- package/build/index.esm.js.map +1 -1
- package/build/index.js +387 -240
- package/build/index.js.map +1 -1
- package/build/main.css +1 -1
- package/build/styles/dateLookup/DateLookup.css +1 -1
- package/build/styles/main.css +1 -1
- package/build/types/common/focusBoundary/FocusBoundary.d.ts +2 -2
- package/build/types/common/focusBoundary/FocusBoundary.d.ts.map +1 -1
- package/build/types/common/focusBoundary/utils/getFocusableElements.d.ts +2 -0
- package/build/types/common/focusBoundary/utils/getFocusableElements.d.ts.map +1 -0
- package/build/types/common/focusBoundary/utils/index.d.ts +3 -0
- package/build/types/common/focusBoundary/utils/index.d.ts.map +1 -0
- package/build/types/common/focusBoundary/utils/resetFocus.d.ts +2 -0
- package/build/types/common/focusBoundary/utils/resetFocus.d.ts.map +1 -0
- package/build/types/dateLookup/DateLookup.messages.d.ts +65 -0
- package/build/types/dateLookup/DateLookup.messages.d.ts.map +1 -0
- package/build/types/dateLookup/dateHeader/DateHeader.d.ts +3 -1
- package/build/types/dateLookup/dateHeader/DateHeader.d.ts.map +1 -1
- package/build/types/dateLookup/tableLink/TableLink.d.ts +4 -26
- package/build/types/dateLookup/tableLink/TableLink.d.ts.map +1 -1
- package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts +4 -29
- package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts.map +1 -1
- package/build/types/upload/steps/completeStep/completeStep.d.ts.map +1 -1
- package/build/types/upload/steps/processingStep/processingStep.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/common/bottomSheet/__snapshots__/BottomSheet.spec.tsx.snap +1 -8
- package/src/common/focusBoundary/FocusBoundary.spec.tsx +66 -0
- package/src/common/focusBoundary/FocusBoundary.tsx +36 -6
- package/src/common/focusBoundary/__snapshots__/FocusBoundary.spec.tsx.snap +16 -0
- package/src/common/focusBoundary/utils/getFocusableElements.js +25 -0
- package/src/common/focusBoundary/utils/getFocusableElements.spec.js +51 -0
- package/src/common/focusBoundary/utils/index.js +2 -0
- package/src/common/focusBoundary/utils/resetFocus.js +23 -0
- package/src/common/focusBoundary/utils/resetFocus.spec.js +103 -0
- package/src/dateLookup/DateLookup.css +1 -1
- package/src/dateLookup/DateLookup.keyboardEvents.spec.js +3 -3
- package/src/dateLookup/DateLookup.less +4 -0
- package/src/dateLookup/DateLookup.messages.js +44 -0
- package/src/dateLookup/DateLookup.testingLibrary.spec.js +39 -0
- package/src/dateLookup/dateHeader/DateHeader.js +48 -26
- package/src/dateLookup/dateHeader/DateHeader.spec.js +37 -0
- package/src/dateLookup/dayCalendar/DayCalendar.js +3 -1
- package/src/dateLookup/dayCalendar/DayCalendar.spec.js +7 -1
- package/src/dateLookup/dayCalendar/table/DayCalendarTable.js +7 -3
- package/src/dateLookup/dayCalendar/table/DayCalendarTable.spec.js +1 -0
- package/src/dateLookup/monthCalendar/MonthCalendar.js +3 -1
- package/src/dateLookup/monthCalendar/MonthCalendar.spec.js +7 -1
- package/src/dateLookup/monthCalendar/table/MonthCalendarTable.spec.js +4 -5
- package/src/dateLookup/tableLink/TableLink.js +24 -3
- package/src/dateLookup/tableLink/TableLink.spec.js +60 -4
- package/src/dateLookup/yearCalendar/YearCalendar.js +16 -3
- package/src/dateLookup/yearCalendar/YearCalendar.spec.js +14 -1
- package/src/dateLookup/yearCalendar/table/YearCalendarTable.spec.js +4 -5
- package/src/i18n/en.json +8 -0
- package/src/main.css +1 -1
- package/src/upload/Upload.js +1 -1
- package/src/upload/steps/completeStep/completeStep.js +4 -1
- package/src/upload/steps/processingStep/processingStep.js +1 -0
- package/src/uploadInput/uploadItem/UploadItem.tsx +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
2
|
type FocusBoundaryProps = {
|
|
3
|
-
children:
|
|
3
|
+
children: ReactNode;
|
|
4
4
|
};
|
|
5
5
|
declare const FocusBoundary: ({ children }: FocusBoundaryProps) => import("react").JSX.Element;
|
|
6
6
|
export default FocusBoundary;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FocusBoundary.d.ts","sourceRoot":"","sources":["../../../../src/common/focusBoundary/FocusBoundary.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"FocusBoundary.d.ts","sourceRoot":"","sources":["../../../../src/common/focusBoundary/FocusBoundary.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAA+B,MAAM,OAAO,CAAC;AAS/D,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,QAAA,MAAM,aAAa,iBAAkB,kBAAkB,gCA6BtD,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getFocusableElements.d.ts","sourceRoot":"","sources":["../../../../../src/common/focusBoundary/utils/getFocusableElements.js"],"names":[],"mappings":"AAOO,6DAJI,IAAI,GACF,MAAM,CAoBlB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/common/focusBoundary/utils/index.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resetFocus.d.ts","sourceRoot":"","sources":["../../../../../src/common/focusBoundary/utils/resetFocus.js"],"names":[],"mappings":"AAOO,qEAJI,MAAM,QAmBhB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
namespace next {
|
|
3
|
+
const id: string;
|
|
4
|
+
const defaultMessage: string;
|
|
5
|
+
const description: string;
|
|
6
|
+
}
|
|
7
|
+
namespace previous {
|
|
8
|
+
const id_1: string;
|
|
9
|
+
export { id_1 as id };
|
|
10
|
+
const defaultMessage_1: string;
|
|
11
|
+
export { defaultMessage_1 as defaultMessage };
|
|
12
|
+
const description_1: string;
|
|
13
|
+
export { description_1 as description };
|
|
14
|
+
}
|
|
15
|
+
namespace day {
|
|
16
|
+
const id_2: string;
|
|
17
|
+
export { id_2 as id };
|
|
18
|
+
const defaultMessage_2: string;
|
|
19
|
+
export { defaultMessage_2 as defaultMessage };
|
|
20
|
+
const description_2: string;
|
|
21
|
+
export { description_2 as description };
|
|
22
|
+
}
|
|
23
|
+
namespace month {
|
|
24
|
+
const id_3: string;
|
|
25
|
+
export { id_3 as id };
|
|
26
|
+
const defaultMessage_3: string;
|
|
27
|
+
export { defaultMessage_3 as defaultMessage };
|
|
28
|
+
const description_3: string;
|
|
29
|
+
export { description_3 as description };
|
|
30
|
+
}
|
|
31
|
+
namespace year {
|
|
32
|
+
const id_4: string;
|
|
33
|
+
export { id_4 as id };
|
|
34
|
+
const defaultMessage_4: string;
|
|
35
|
+
export { defaultMessage_4 as defaultMessage };
|
|
36
|
+
const description_4: string;
|
|
37
|
+
export { description_4 as description };
|
|
38
|
+
}
|
|
39
|
+
namespace twentyYears {
|
|
40
|
+
const id_5: string;
|
|
41
|
+
export { id_5 as id };
|
|
42
|
+
const defaultMessage_5: string;
|
|
43
|
+
export { defaultMessage_5 as defaultMessage };
|
|
44
|
+
const description_5: string;
|
|
45
|
+
export { description_5 as description };
|
|
46
|
+
}
|
|
47
|
+
namespace selected {
|
|
48
|
+
const id_6: string;
|
|
49
|
+
export { id_6 as id };
|
|
50
|
+
const defaultMessage_6: string;
|
|
51
|
+
export { defaultMessage_6 as defaultMessage };
|
|
52
|
+
const description_6: string;
|
|
53
|
+
export { description_6 as description };
|
|
54
|
+
}
|
|
55
|
+
namespace goTo20YearView {
|
|
56
|
+
const id_7: string;
|
|
57
|
+
export { id_7 as id };
|
|
58
|
+
const defaultMessage_7: string;
|
|
59
|
+
export { defaultMessage_7 as defaultMessage };
|
|
60
|
+
const description_7: string;
|
|
61
|
+
export { description_7 as description };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export default _default;
|
|
65
|
+
//# sourceMappingURL=DateLookup.messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DateLookup.messages.d.ts","sourceRoot":"","sources":["../../../src/dateLookup/DateLookup.messages.js"],"names":[],"mappings":""}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export default DateHeader;
|
|
2
|
-
declare function DateHeader({ label, onLabelClick, onPreviousClick, onNextClick }: {
|
|
2
|
+
declare function DateHeader({ label, onLabelClick, onPreviousClick, onNextClick, dateMode }: {
|
|
3
3
|
label: any;
|
|
4
4
|
onLabelClick: any;
|
|
5
5
|
onPreviousClick: any;
|
|
6
6
|
onNextClick: any;
|
|
7
|
+
dateMode: any;
|
|
7
8
|
}): import("react").JSX.Element;
|
|
8
9
|
declare namespace DateHeader {
|
|
9
10
|
namespace propTypes {
|
|
@@ -11,6 +12,7 @@ declare namespace DateHeader {
|
|
|
11
12
|
const onLabelClick: any;
|
|
12
13
|
const onPreviousClick: any;
|
|
13
14
|
const onNextClick: any;
|
|
15
|
+
const dateMode: any;
|
|
14
16
|
}
|
|
15
17
|
namespace defaultProps {
|
|
16
18
|
const label_1: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateHeader.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/dateHeader/DateHeader.js"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"DateHeader.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/dateHeader/DateHeader.js"],"names":[],"mappings":";AASA;;;;;;gCA6CC;;;;;;;;;;;;QAYe,uCAAQ"}
|
|
@@ -1,27 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
onClick: (event: any) => void;
|
|
6
|
-
render(): import("react").JSX.Element;
|
|
7
|
-
}
|
|
8
|
-
declare namespace TableLink {
|
|
9
|
-
namespace propTypes {
|
|
10
|
-
const item: any;
|
|
11
|
-
const type: any;
|
|
12
|
-
const title: any;
|
|
13
|
-
const longTitle: any;
|
|
14
|
-
const active: any;
|
|
15
|
-
const disabled: any;
|
|
16
|
-
const today: any;
|
|
17
|
-
const onClick: any;
|
|
18
|
-
}
|
|
19
|
-
namespace defaultProps {
|
|
20
|
-
const title_1: null;
|
|
21
|
-
export { title_1 as title };
|
|
22
|
-
const longTitle_1: null;
|
|
23
|
-
export { longTitle_1 as longTitle };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
import { PureComponent } from "react";
|
|
1
|
+
declare const _default: import("react").FC<import("react-intl").WithIntlProps<any>> & {
|
|
2
|
+
WrappedComponent: import("react").ComponentType<any>;
|
|
3
|
+
};
|
|
4
|
+
export default _default;
|
|
27
5
|
//# sourceMappingURL=TableLink.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableLink.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/tableLink/TableLink.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TableLink.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/tableLink/TableLink.js"],"names":[],"mappings":""}
|
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
onYearSelect: (year: any) => void;
|
|
6
|
-
selectPreviousYears: () => void;
|
|
7
|
-
selectNextYears: () => void;
|
|
8
|
-
render(): import("react").JSX.Element;
|
|
9
|
-
}
|
|
10
|
-
declare namespace YearCalendar {
|
|
11
|
-
namespace propTypes {
|
|
12
|
-
const selectedDate: any;
|
|
13
|
-
const min: any;
|
|
14
|
-
const max: any;
|
|
15
|
-
const viewYear: any;
|
|
16
|
-
const placeholder: any;
|
|
17
|
-
const onSelect: any;
|
|
18
|
-
const onViewDateUpdate: any;
|
|
19
|
-
}
|
|
20
|
-
namespace defaultProps {
|
|
21
|
-
const selectedDate_1: null;
|
|
22
|
-
export { selectedDate_1 as selectedDate };
|
|
23
|
-
const min_1: null;
|
|
24
|
-
export { min_1 as min };
|
|
25
|
-
const max_1: null;
|
|
26
|
-
export { max_1 as max };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
import { PureComponent } from "react";
|
|
1
|
+
declare const _default: import("react").FC<import("react-intl").WithIntlProps<any>> & {
|
|
2
|
+
WrappedComponent: import("react").ComponentType<any>;
|
|
3
|
+
};
|
|
4
|
+
export default _default;
|
|
30
5
|
//# sourceMappingURL=YearCalendar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YearCalendar.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/yearCalendar/YearCalendar.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"YearCalendar.d.ts","sourceRoot":"","sources":["../../../../src/dateLookup/yearCalendar/YearCalendar.js"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completeStep.d.ts","sourceRoot":"","sources":["../../../../../src/upload/steps/completeStep/completeStep.js"],"names":[],"mappings":";AAYA,
|
|
1
|
+
{"version":3,"file":"completeStep.d.ts","sourceRoot":"","sources":["../../../../../src/upload/steps/completeStep/completeStep.js"],"names":[],"mappings":";AAYA,uEAoEC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processingStep.d.ts","sourceRoot":"","sources":["../../../../../src/upload/steps/processingStep/processingStep.js"],"names":[],"mappings":";AASA,
|
|
1
|
+
{"version":3,"file":"processingStep.d.ts","sourceRoot":"","sources":["../../../../../src/upload/steps/processingStep/processingStep.js"],"names":[],"mappings":";AASA,yEAkDC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-9a61b0a",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -103,7 +103,6 @@
|
|
|
103
103
|
"@headlessui/react": "^1.7.17",
|
|
104
104
|
"@popperjs/core": "^2.6.0",
|
|
105
105
|
"@radix-ui/react-id": "^1.0.0",
|
|
106
|
-
"@react-aria/focus": "^3.14.2",
|
|
107
106
|
"@react-aria/overlays": "^3.17.0",
|
|
108
107
|
"@react-spring/web": "~9.6.1",
|
|
109
108
|
"@transferwise/formatting": "^2.1.0",
|
|
@@ -6,11 +6,8 @@ exports[`BottomSheet renders content when open 1`] = `
|
|
|
6
6
|
<div
|
|
7
7
|
class="np-theme-light"
|
|
8
8
|
>
|
|
9
|
-
<span
|
|
10
|
-
data-focus-scope-start="true"
|
|
11
|
-
hidden=""
|
|
12
|
-
/>
|
|
13
9
|
<div
|
|
10
|
+
class="np-focus-boundary outline-none"
|
|
14
11
|
tabindex="-1"
|
|
15
12
|
>
|
|
16
13
|
<div
|
|
@@ -67,10 +64,6 @@ exports[`BottomSheet renders content when open 1`] = `
|
|
|
67
64
|
</div>
|
|
68
65
|
</div>
|
|
69
66
|
</div>
|
|
70
|
-
<span
|
|
71
|
-
data-focus-scope-end="true"
|
|
72
|
-
hidden=""
|
|
73
|
-
/>
|
|
74
67
|
</div>
|
|
75
68
|
</body>
|
|
76
69
|
`;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, screen, userEvent } from '../../test-utils';
|
|
2
|
+
|
|
3
|
+
import FocusBoundary from './FocusBoundary';
|
|
4
|
+
|
|
5
|
+
const props = {
|
|
6
|
+
onClose: jest.fn(),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe('FocusBoundary', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
it('renders component', () => {
|
|
14
|
+
const { container } = render(
|
|
15
|
+
<FocusBoundary {...props}>
|
|
16
|
+
<a href="test">Test</a>
|
|
17
|
+
</FocusBoundary>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(container).toMatchSnapshot();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('focus on container by default', () => {
|
|
24
|
+
render(
|
|
25
|
+
<>
|
|
26
|
+
<a href="test">not focusable</a>
|
|
27
|
+
<FocusBoundary {...props}>
|
|
28
|
+
<a href="test">Test</a>
|
|
29
|
+
</FocusBoundary>
|
|
30
|
+
</>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(focusBoundary()).toHaveFocus();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('traps the focus when user presses tab', () => {
|
|
37
|
+
render(
|
|
38
|
+
<>
|
|
39
|
+
<a href="test">not focusable</a>
|
|
40
|
+
<FocusBoundary {...props}>
|
|
41
|
+
<a href="test">firstFocusable</a>
|
|
42
|
+
<a href="test">lastFocusable</a>
|
|
43
|
+
</FocusBoundary>
|
|
44
|
+
</>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
userEvent.tab();
|
|
48
|
+
expect(firstFocusableElement()).toHaveFocus();
|
|
49
|
+
|
|
50
|
+
userEvent.tab();
|
|
51
|
+
expect(lastFocusableElement()).toHaveFocus();
|
|
52
|
+
|
|
53
|
+
userEvent.tab();
|
|
54
|
+
expect(firstFocusableElement()).toHaveFocus();
|
|
55
|
+
|
|
56
|
+
userEvent.tab({ shift: true });
|
|
57
|
+
expect(lastFocusableElement()).toHaveFocus();
|
|
58
|
+
|
|
59
|
+
userEvent.tab({ shift: true });
|
|
60
|
+
expect(firstFocusableElement()).toHaveFocus();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const firstFocusableElement = () => screen.getByText('firstFocusable');
|
|
64
|
+
const lastFocusableElement = () => screen.getByText('lastFocusable');
|
|
65
|
+
const focusBoundary = () => document.querySelector('.np-focus-boundary');
|
|
66
|
+
});
|
|
@@ -1,15 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isKey, isUndefined } from '@transferwise/neptune-validation';
|
|
2
|
+
import { ReactNode, useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useConditionalListener } from '../hooks';
|
|
5
|
+
import { Key } from '../key';
|
|
6
|
+
|
|
7
|
+
import { getFocusableElements, resetFocus } from './utils';
|
|
8
|
+
|
|
9
|
+
const { TAB } = Key;
|
|
2
10
|
|
|
3
11
|
type FocusBoundaryProps = {
|
|
4
|
-
children:
|
|
12
|
+
children: ReactNode;
|
|
5
13
|
};
|
|
6
14
|
|
|
7
15
|
const FocusBoundary = ({ children }: FocusBoundaryProps) => {
|
|
16
|
+
const boundaryReference = useRef<HTMLDivElement | null>(null);
|
|
17
|
+
const parent = isUndefined(document) ? undefined : document;
|
|
18
|
+
const [focusableEls, setFocusableEls] = useState({});
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (boundaryReference?.current) {
|
|
22
|
+
boundaryReference.current.focus({ preventScroll: true });
|
|
23
|
+
setFocusableEls(getFocusableElements(boundaryReference.current));
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// If event type is Tab the resetFocus will force the focus to either the first focusable or last in boundaryRef .
|
|
28
|
+
useConditionalListener({
|
|
29
|
+
eventType: 'keydown',
|
|
30
|
+
callback: (event) => {
|
|
31
|
+
if (isKey({ keyType: TAB, event })) {
|
|
32
|
+
resetFocus({ event, focusableEls });
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
attachListener: true,
|
|
36
|
+
parent,
|
|
37
|
+
});
|
|
38
|
+
|
|
8
39
|
return (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
</FocusScope>
|
|
40
|
+
<div ref={boundaryReference} tabIndex={-1} className="np-focus-boundary outline-none">
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
13
43
|
);
|
|
14
44
|
};
|
|
15
45
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function returns the first and the last focusable elements within a node.
|
|
3
|
+
*
|
|
4
|
+
* @param {Node} focusBoundaryContainer - the area that contains focused elements.
|
|
5
|
+
* @returns {object} focusableEls - which contains the first focusable element and the last focusable element. First and last can be the same element if area contains one or none focusable element.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const getFocusableElements = (focusBoundaryContainer) => {
|
|
9
|
+
const focusableEls = { first: focusBoundaryContainer, last: focusBoundaryContainer };
|
|
10
|
+
|
|
11
|
+
if (focusBoundaryContainer?.querySelectorAll) {
|
|
12
|
+
const allEls = [
|
|
13
|
+
...focusBoundaryContainer.querySelectorAll(
|
|
14
|
+
'a, button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
15
|
+
),
|
|
16
|
+
].filter((element) => !element.hasAttribute('disabled'));
|
|
17
|
+
|
|
18
|
+
if (allEls.length > 0) {
|
|
19
|
+
[focusableEls.first] = allEls;
|
|
20
|
+
focusableEls.last = allEls[allEls.length - 1];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return focusableEls;
|
|
25
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cleanup, render, screen } from '../../../test-utils';
|
|
2
|
+
|
|
3
|
+
import { getFocusableElements } from './getFocusableElements';
|
|
4
|
+
|
|
5
|
+
describe('getFocusableElements', () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
cleanup();
|
|
8
|
+
});
|
|
9
|
+
it('returns first and last focusable element', () => {
|
|
10
|
+
const { container } = render(
|
|
11
|
+
<>
|
|
12
|
+
<div>another element</div>
|
|
13
|
+
<a href="test">first</a>
|
|
14
|
+
<div>another element</div>
|
|
15
|
+
<a href="test">middle</a>
|
|
16
|
+
<div>another element</div>
|
|
17
|
+
<a href="test">last</a>
|
|
18
|
+
<div>another element</div>
|
|
19
|
+
</>,
|
|
20
|
+
);
|
|
21
|
+
getFocusableElements(container);
|
|
22
|
+
expect(getFocusableElements(container)).toStrictEqual({
|
|
23
|
+
first: first(),
|
|
24
|
+
last: last(),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns container if there are no focusable elements', () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<>
|
|
31
|
+
<div>another element</div>
|
|
32
|
+
</>,
|
|
33
|
+
);
|
|
34
|
+
getFocusableElements(container);
|
|
35
|
+
expect(getFocusableElements(container)).toStrictEqual({
|
|
36
|
+
first: container,
|
|
37
|
+
last: container,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns undefined if container is not provided', () => {
|
|
42
|
+
getFocusableElements();
|
|
43
|
+
expect(getFocusableElements()).toStrictEqual({
|
|
44
|
+
first: undefined,
|
|
45
|
+
last: undefined,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const first = () => screen.getByText('first');
|
|
50
|
+
const last = () => screen.getByText('last');
|
|
51
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function resets the focus to either last of first focusable elements within a node.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} focusableEls - contains the first last of first focusable elements within a node.
|
|
5
|
+
* @param {object} event - the triggered event
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const resetFocus = ({ focusableEls: { first, last }, event }) => {
|
|
9
|
+
const { activeElement } = document;
|
|
10
|
+
if (event.shiftKey && activeElement === first) {
|
|
11
|
+
if (last) {
|
|
12
|
+
last.focus();
|
|
13
|
+
}
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!event.shiftKey && activeElement === last) {
|
|
18
|
+
if (first) {
|
|
19
|
+
first.focus();
|
|
20
|
+
}
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { cleanup, render, screen, userEvent } from '../../../test-utils';
|
|
2
|
+
import { fakeKeyDownEvent } from '../../fakeEvents';
|
|
3
|
+
|
|
4
|
+
import { resetFocus } from './resetFocus';
|
|
5
|
+
|
|
6
|
+
describe('resetFocus', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
render(
|
|
9
|
+
<>
|
|
10
|
+
<a href="test">first</a>
|
|
11
|
+
<a href="test">middle</a>
|
|
12
|
+
<a href="test">last</a>
|
|
13
|
+
</>,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
cleanup();
|
|
18
|
+
});
|
|
19
|
+
it('set focus to first element when last element is focused and event is tab', () => {
|
|
20
|
+
focusOnLast();
|
|
21
|
+
|
|
22
|
+
resetFocus({
|
|
23
|
+
focusableEls: { first: first(), last: last() },
|
|
24
|
+
event: fakeKeyDownEvent({ shiftKey: false }),
|
|
25
|
+
});
|
|
26
|
+
expect(first()).toHaveFocus();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('set focus to last element when first element is focused and event is shift + tab', () => {
|
|
30
|
+
focusOnFirst();
|
|
31
|
+
|
|
32
|
+
resetFocus({
|
|
33
|
+
focusableEls: { first: first(), last: last() },
|
|
34
|
+
event: fakeKeyDownEvent({ shiftKey: true }),
|
|
35
|
+
});
|
|
36
|
+
expect(last()).toHaveFocus();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it(`doesn't change focus when first element is focused and event is tab`, () => {
|
|
40
|
+
focusOnFirst();
|
|
41
|
+
|
|
42
|
+
resetFocus({
|
|
43
|
+
focusableEls: { first: first(), last: last() },
|
|
44
|
+
event: fakeKeyDownEvent(),
|
|
45
|
+
});
|
|
46
|
+
expect(first()).toHaveFocus();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it(`doesn't change focus when last element is focused and event is shift+tab`, () => {
|
|
50
|
+
focusOnLast();
|
|
51
|
+
|
|
52
|
+
resetFocus({
|
|
53
|
+
focusableEls: { first: first(), last: last() },
|
|
54
|
+
event: fakeKeyDownEvent({ shiftKey: true }),
|
|
55
|
+
});
|
|
56
|
+
expect(last()).toHaveFocus();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`doesn't change focus when middle element is focused and event is shift+tab or tab`, () => {
|
|
60
|
+
focusOnMiddle();
|
|
61
|
+
|
|
62
|
+
resetFocus({
|
|
63
|
+
focusableEls: { first: first(), last: last() },
|
|
64
|
+
event: fakeKeyDownEvent({ shiftKey: true }),
|
|
65
|
+
});
|
|
66
|
+
expect(middle()).toHaveFocus();
|
|
67
|
+
|
|
68
|
+
resetFocus({
|
|
69
|
+
focusableEls: { first: first(), last: last() },
|
|
70
|
+
event: fakeKeyDownEvent(),
|
|
71
|
+
});
|
|
72
|
+
expect(middle()).toHaveFocus();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it(`doesn't change focus when first is the only element provided`, () => {
|
|
76
|
+
focusOnFirst();
|
|
77
|
+
resetFocus({
|
|
78
|
+
focusableEls: { first: first(), last: undefined },
|
|
79
|
+
event: fakeKeyDownEvent({ shiftKey: true }),
|
|
80
|
+
});
|
|
81
|
+
expect(first()).toHaveFocus();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it(`doesn't change focus when last is the only element provided`, () => {
|
|
85
|
+
focusOnLast();
|
|
86
|
+
resetFocus({
|
|
87
|
+
focusableEls: { first: undefined, last: last() },
|
|
88
|
+
event: fakeKeyDownEvent(),
|
|
89
|
+
});
|
|
90
|
+
expect(last()).toHaveFocus();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const first = () => screen.getByText('first');
|
|
94
|
+
const last = () => screen.getByText('last');
|
|
95
|
+
const middle = () => screen.getByText('middle');
|
|
96
|
+
|
|
97
|
+
const focusOnFirst = () => userEvent.tab();
|
|
98
|
+
const focusOnLast = () => userEvent.tab({ shift: true });
|
|
99
|
+
const focusOnMiddle = () => {
|
|
100
|
+
userEvent.tab();
|
|
101
|
+
userEvent.tab();
|
|
102
|
+
};
|
|
103
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.tw-date-lookup-menu{width:400px}.tw-date-lookup-calendar{min-width:300px;table-layout:fixed;text-align:center}.tw-date-lookup-calendar>tbody>tr>td{padding:4px;padding:var(--size-4)}.tw-date-lookup-calendar>tbody>tr>td button{background-color:transparent;border:transparent;border-radius:10px;border-radius:var(--radius-small);color:#0097c7;color:var(--color-content-accent);font-weight:600;font-weight:var(--font-weight-semi-bold);padding:4px 0;padding:var(--size-4) 0;width:100%}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active{background-color:#0081ba;background-color:var(--color-interactive-accent-active);color:#fff}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{background-color:#008fc9;background-color:var(--color-interactive-accent-hover);color:#fff}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{color:var(--color-interactive-control)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+6) button,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+7) button,.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+6),.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+7){color:#5d7079;color:var(--color-content-secondary);font-weight:400;font-weight:var(--font-weight-regular)}.np-theme-personal .tw-date-lookup-calendar{background-color:inherit}.tw-date-lookup-header-current{font-weight:800;font-weight:var(--font-weight-bold)}.np-theme-personal .tw-date-lookup-menu .table-bordered,.np-theme-personal.tw-date-lookup-menu .table-bordered{border:none}.np-theme-personal .tw-date-lookup-menu thead,.np-theme-personal.tw-date-lookup-menu thead{background-color:unset}.np-theme-personal .tw-date-lookup-menu td,.np-theme-personal.tw-date-lookup-menu td{border:none}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-header-current,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-header-current{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option{align-items:center;color:#37517e;color:var(--color-content-primary);display:inline-flex;height:32px;height:var(--size-32);justify-content:center;line-height:32px;line-height:var(--size-32)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option.active,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option.active{background-color:#00a2dd;background-color:var(--color-interactive-accent);color:var(--color-interactive-primary)}
|
|
1
|
+
.tw-date-lookup-menu{width:400px}.tw-date-lookup-calendar{min-width:300px;table-layout:fixed;text-align:center}.tw-date-lookup-calendar>tbody>tr>td{padding:4px;padding:var(--size-4)}.tw-date-lookup-calendar>tbody>tr>td button{background-color:transparent;border:transparent;border-radius:10px;border-radius:var(--radius-small);color:#0097c7;color:var(--color-content-accent);font-weight:600;font-weight:var(--font-weight-semi-bold);padding:4px 0;padding:var(--size-4) 0;width:100%}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active{background-color:#0081ba;background-color:var(--color-interactive-accent-active);color:#fff}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{background-color:#008fc9;background-color:var(--color-interactive-accent-hover);color:#fff}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{color:var(--color-interactive-control)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+6) button,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+7) button,.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+6),.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+7){color:#5d7079;color:var(--color-content-secondary);font-weight:400;font-weight:var(--font-weight-regular)}.tw-date-lookup-calendar abbr{text-decoration:none}.np-theme-personal .tw-date-lookup-calendar{background-color:inherit}.tw-date-lookup-header-current{font-weight:800;font-weight:var(--font-weight-bold)}.np-theme-personal .tw-date-lookup-menu .table-bordered,.np-theme-personal.tw-date-lookup-menu .table-bordered{border:none}.np-theme-personal .tw-date-lookup-menu thead,.np-theme-personal.tw-date-lookup-menu thead{background-color:unset}.np-theme-personal .tw-date-lookup-menu td,.np-theme-personal.tw-date-lookup-menu td{border:none}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-header-current,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-header-current{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option{align-items:center;color:#37517e;color:var(--color-content-primary);display:inline-flex;height:32px;height:var(--size-32);justify-content:center;line-height:32px;line-height:var(--size-32)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option.active,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option.active{background-color:#00a2dd;background-color:var(--color-interactive-accent);color:var(--color-interactive-primary)}
|
|
@@ -7,13 +7,13 @@ import KEY_CODES from '../common/keyCodes';
|
|
|
7
7
|
import DateLookup from '.';
|
|
8
8
|
|
|
9
9
|
const defaultLocale = 'en-GB';
|
|
10
|
-
|
|
10
|
+
const formatMessage = (id) => `${id}`;
|
|
11
11
|
jest.mock('react-intl', () => ({
|
|
12
12
|
injectIntl: (Component) =>
|
|
13
13
|
function (props) {
|
|
14
|
-
return <Component {...props} intl={{ locale: defaultLocale }} />;
|
|
14
|
+
return <Component {...props} intl={{ locale: defaultLocale, formatMessage }} />;
|
|
15
15
|
},
|
|
16
|
-
useIntl: () => ({ locale: defaultLocale }),
|
|
16
|
+
useIntl: () => ({ locale: defaultLocale, formatMessage }),
|
|
17
17
|
defineMessages: (translations) => translations,
|
|
18
18
|
}));
|
|
19
19
|
|