Skip to content

Commit b4544d6

Browse files
committed
fix(datepicker): One datepicker open at a time
1 parent 3ff47f6 commit b4544d6

File tree

13 files changed

+278
-608
lines changed

13 files changed

+278
-608
lines changed

src/components/Datepicker.vue

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ export default {
252252
calendarHeight: 0,
253253
calendarSlots,
254254
isClickOutside: false,
255+
globalDatepickerId: '',
255256
/*
256257
* The latest valid `typedDate` (used for typeable datepicker)
257258
* {Date}
@@ -361,7 +362,12 @@ export default {
361362
}
362363
363364
if (isNoLongerActive && this.typeable) {
365+
this.skipReviewFocus = true
364366
this.setTypedDateOnLosingFocus()
367+
368+
this.$nextTick(() => {
369+
this.skipReviewFocus = false
370+
})
365371
}
366372
},
367373
latestValidTypedDate(date) {
@@ -438,19 +444,34 @@ export default {
438444
439445
this.$emit('closed')
440446
},
447+
closeByClickOutside() {
448+
this.isClickOutside = true
449+
this.close()
450+
},
451+
closeIfNotFocused() {
452+
const isFocused = this.allElements.includes(document.activeElement)
453+
454+
if (!isFocused) {
455+
this.closeByClickOutside()
456+
}
457+
},
441458
/**
442459
* Closes the calendar when no element within it has focus
443460
*/
444461
handleClickOutside() {
445-
if (document.datepickerId !== this.datepickerId) {
462+
if (!this.isOpen) {
446463
return
447464
}
448465
449-
const isFocused = this.allElements.includes(document.activeElement)
466+
if (!this.globalDatepickerId) {
467+
this.closeByClickOutside()
468+
return
469+
}
450470
451-
if (!isFocused && this.isOpen) {
452-
this.isClickOutside = true
453-
this.close()
471+
if (document.datepickerId.toString() === this.datepickerId) {
472+
this.$nextTick(() => {
473+
this.closeIfNotFocused()
474+
})
454475
}
455476
},
456477
/**

src/mixins/navMixin.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,17 +261,19 @@ export default {
261261
*/
262262
handleFocusIn() {
263263
document.datepickerId = this.datepickerId
264+
this.globalDatepickerId = this.datepickerId
264265
265266
this.isActive = true
266267
this.setInlineTabbableCell()
267268
this.setAllElements()
268269
this.setNavElements()
269270
},
270271
/**
271-
* Sets the datepicker's `isActive` state to false
272+
* Sets the datepicker's `isActive` state to false and resets `globalDatepickerId`
272273
*/
273274
handleFocusOut() {
274275
this.isActive = false
276+
this.globalDatepickerId = ''
275277
},
276278
/**
277279
* Returns true if the calendar has been passed the given slot

test/FEATURES.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@
8888
| | open | | | | | | Keydown shift + tab | open | focusable-cell | | FocusTrap | 2 |
8989
| | open | | | | | | Keydown esc | closed | input | | CloseOnEscape | 2#2 |
9090
| --------------- | -------------- | -------- | ---------- | -------------------- | ------------- | --------------------------------- | -------------------------- | -------------- | -------------------------- | ------------------------- | ------------------------- | ------- |
91-
| up | open | | | | | | Click on up-button | open | up | Focusable cell, view up | UpButton | 1#1 |
92-
| | open | | | | | | Keydown enter | open | up | Focusable cell, view up | UpButton | 1#2 |
93-
| | open | | | | | | Keydown space | open | up | Focusable cell, view up | UpButton | 1#3 |
91+
| up | open | | | | | initialView=day; maximumView=year | Click on up-button | open | up | Focusable cell, view up | UpButton | 1#1 |
92+
| | open | | | | | initialView=day; maximumView=month | Click on up-button | open | focusable-cell | Focusable cell, view up | UpButton | 1#2 |
93+
| | open | | | | | initialView=month; maximumView=year | Click on up-button | open | focusable-cell | Focusable cell, view up | UpButton | 1#3 |
9494
| | open | | | | | | Keydown down | open | focusable-cell | | UpButton | 2 |
9595
| | open | | | | | | Keydown left | open | prev | | UpButton | 3#1 |
9696
| | open | | | | | isPrevDisabled | Keydown left | open | input | | UpButton | 3#2 |
@@ -124,12 +124,11 @@
124124
| | open | | | | | | Keydown shift + tab | open | up | | FocusTrap | 6 |
125125
| | open | | | | | | Keydown esc | closed | input | | CloseOnEscape | 2#4 |
126126
| --------------- | -------------- | -------- | ---------- | -------------------- | ------------- | --------------------------------- | -------------------------- | -------------- | -------------------------- | ------------------------- | ------------------------- | ------- |
127-
| focusable-cell | open | | | | | isMinView | Click on focusable cell | closed | input | Set date | CellSelection | 1#1 |
128-
| | open | | | | | isMinView | Keydown enter | closed | input | Set date | CellSelection | 1#2 |
129-
| | open | | | | | isMinView | Keydown space | closed | input | Set date | CellSelection | 1#3 |
130-
| | open | | | | | | Click on focusable cell | open | focusable-cell | View down | CellSelection | 2#1 |
131-
| | open | | | | | | Keydown enter | open | focusable-cell | View down | CellSelection | 2#2 |
132-
| | open | | | | | | Keydown space | open | focusable-cell | View down | CellSelection | 2#3 |
127+
| focusable-cell | open | | | | | isMinView (day) | Click on focusable cell | closed | input | Set date | CellSelection | 1#1 |
128+
| | open | | | | | isMinView (month) | Click on focusable cell | closed | input | Set date | CellSelection | 1#2 |
129+
| | open | | | | | isMinView (year) | Click on focusable cell | closed | input | Set date | CellSelection | 1#3 |
130+
| | open | | | | | !isMinView (year) | Click on focusable cell | open | focusable-cell | View down | CellSelection | 2#1 |
131+
| | open | | | | | !isMinView (month) | Click on focusable cell | open | focusable-cell | View down | CellSelection | 2#2 |
133132
| | open | | | | | focused cell is on same page | Keydown esc | open | open-date | | CloseOnEscape | 3 |
134133
| | open | | | | | focused cell is on different page | Keydown esc | open | open-date | | CloseOnEscape | 4 |
135134
| | open | | | | | focused cell is on different view | Keydown esc | open | open-date | | CloseOnEscape | 5 |

test/e2e/specs/CellSelection.feature

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,34 @@ Feature: Cell Selection
33
I want to select a cell
44
So that I pick a date
55

6-
# N.B. The enter and space bar tests have been disabled until native events are supported by Cypress
6+
7+
# N.B. Until native events are supported by Cypress, we are simulating `enter` and `space bar` key presses
8+
# on buttons by using a `click` event.
79
# See https://github.com/cypress-io/cypress/issues/311
810
# and https://github.com/cypress-io/cypress/issues/8267
911

1012
@id-1
11-
Scenario Outline: Select by <action>: minimum view
12-
Given the calendar is open on the minimum view
13-
When the user performs a "<action>" action
14-
Then the date is submitted
13+
Scenario Outline: Select a cell when on the minimum view: <minimumView>
14+
Given the calendar is open on the minimum view: "<minimumView>"
15+
When the user clicks on the tabbable cell
16+
Then the date is submitted with a value of "<date>"
1517
And the input field has focus
1618

1719
Examples:
18-
| # | action |
19-
| 1 | click |
20-
# | 2 | enter |
21-
# | 3 | space |
20+
| # | minimumView | date |
21+
| 1 | day | 15 Mar 2020 |
22+
| 2 | month | 01 Mar 2020 |
23+
| 3 | year | 01 Mar 2020 |
2224

2325

24-
@id-2
25-
Scenario Outline: Select by <action>: NOT minimum view
26-
Given the calendar is open on a higher than minimum view
27-
When the user performs a "<action>" action
28-
Then the `day` view is shown
26+
@id-2 @focus
27+
Scenario Outline: Select a cell when NOT on the minimum view: <initialView>
28+
Given the calendar is open on a "<initialView>" view
29+
When the user clicks on the tabbable cell
30+
Then the "<view>" view is shown
2931
And the tabbable cell has focus
3032

3133
Examples:
32-
| # | action |
33-
| 1 | click |
34-
# | 2 | enter |
35-
# | 3 | space |
34+
| # | initialView | view |
35+
| 1 | year | month |
36+
| 2 | month | day |

test/e2e/specs/CellSelection/index.js

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,56 @@
11
import { Given, When, Then, And } from 'cypress-cucumber-preprocessor/steps'
22

3-
const { clickThe, createCalendar, focusThe, the } = cy
3+
const { clickThe, createCalendar, the } = cy
44

55
describe('Focusable Cell', () => {
6-
describe('@id-1: Select by {string}: minimum view', () => {
7-
Given('the calendar is open on the minimum view', () => {
8-
createCalendar({
9-
openDate: new Date(2020, 2, 15),
10-
})
11-
12-
clickThe('input')
13-
the('picker-cells').should('have.length', 1)
14-
the('calendar').should('be.visible')
6+
describe('@id-1: Select a cell: minimum view', () => {
7+
Given(
8+
'the calendar is open on the minimum view: {string}',
9+
(minimumView) => {
10+
createCalendar({
11+
openDate: new Date(2020, 2, 15),
12+
minimumView,
13+
initialView: minimumView,
14+
})
15+
16+
clickThe('input')
17+
the('picker-cells').should('have.length', 1)
18+
the('calendar').should('be.visible')
19+
},
20+
)
21+
22+
When('the user clicks on the tabbable cell', () => {
23+
clickThe('tabbable-cell')
1524
})
1625

17-
When('the user performs a {string} action', (action) => {
18-
if (action === 'click') {
19-
clickThe('tabbable-cell')
20-
return
21-
}
22-
23-
focusThe('tabbable-cell').type(`{${action}}`)
24-
})
25-
26-
Then('the date is submitted', () => {
27-
the('input').should('have.value', '15 Mar 2020')
26+
Then('the date is submitted with a value of {string}', (date) => {
27+
the('input').should('have.value', date)
2828
})
2929

3030
And('the input field has focus', () => {
3131
the('input').should('be.focused')
3232
})
3333
})
3434

35-
describe('@id-2: Select by {string}: NOT minimum view', () => {
36-
Given('the calendar is open on a higher than minimum view', () => {
35+
describe('@id-2: Select a cell: higher than minimum view', () => {
36+
Given('the calendar is open on a {string} view', (initialView) => {
3737
createCalendar({
3838
openDate: new Date(2020, 2, 15),
39-
initialView: 'month',
39+
initialView,
4040
})
4141

4242
clickThe('input')
4343
the('picker-cells').should('have.length', 1)
4444
the('calendar').should('be.visible')
4545
})
4646

47-
When('the user performs a {string} action')
47+
When('the user clicks on the tabbable cell')
48+
49+
Then('the {string} view is shown', (view) => {
50+
const pageHeading = view === 'day' ? 'Mar 2020' : '2020'
4851

49-
Then('the `day` view is shown', () => {
5052
the('picker-cells').should('have.length', 1)
51-
the('up-button').should('contain', 'Mar 2020')
53+
the('up-button').should('contain', pageHeading)
5254
})
5355

5456
And('the tabbable cell has focus', () => {

test/e2e/specs/CloseOnEscape.feature

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,6 @@ Feature: Close on escape
55

66

77
@id-1
8-
Scenario Outline: Clear date when a typeable calendar is <openOrClosed> and <validity>
9-
Given the typeable calendar is "<openOrClosed>" and a "<validity>" date is typed
10-
When the user focuses the input field and presses escape
11-
Then the calendar "<opensOrCloses>"
12-
And the input field has focus
13-
14-
Examples:
15-
| # | openOrClosed | validity | opensOrCloses |
16-
| 1 | closed | valid | closes |
17-
| 2 | closed | invalid | closes |
18-
| 3 | open | valid | closes |
19-
| 4 | open | invalid | closes |
20-
21-
22-
@id-2
238
Scenario Outline: Close by pressing escape on the <element>
249
Given the calendar is open
2510
When the user focuses the "<element>" and presses escape
@@ -34,24 +19,39 @@ Feature: Close on escape
3419
| 4 | next-button |
3520

3621

37-
@id-3
38-
Scenario: Revert to open date when the focused cell is on the same page
39-
Given the calendar is open
40-
When the user focuses another cell and presses the escape key
22+
@id-2
23+
Scenario Outline: Revert to open date when the focused cell is on the same page: <minimumView>
24+
Given the calendar is open on a "<minimumView>" view
25+
When the user focuses another cell on the same "<minimumView>" view and presses the escape key
4126
Then the open date has focus
4227

28+
Examples:
29+
| # | minimumView |
30+
| 1 | day |
31+
| 2 | month |
32+
| 3 | year |
4333

44-
@id-4
45-
Scenario: Revert to open date when the focused cell is on a different page
46-
Given the calendar is open
34+
@id-3
35+
Scenario Outline: Revert to open date when the focused cell is on a different page: <minimumView>
36+
Given the calendar is open on a "<minimumView>" view
4737
And the user visits another page
48-
When the user focuses a cell and presses the escape key
38+
When the user focuses another cell on the same "<minimumView>" view and presses the escape key
4939
Then the open date has focus
5040

41+
Examples:
42+
| # | minimumView |
43+
| 1 | day |
44+
| 2 | month |
45+
| 3 | year |
5146

52-
@id-5
53-
Scenario: Revert to open date when the focused cell is on a different view
54-
Given the calendar is open
55-
And the user visits the next view up
47+
48+
@id-4
49+
Scenario Outline: Revert to open date when the focused cell is on a different view: <initialView>
50+
Given the calendar is open with a "<initialView>" initial view
5651
When the user focuses a cell and presses the escape key
57-
Then the open date has focus
52+
Then the open date on the minimum view has focus
53+
54+
Examples:
55+
| # | initialView |
56+
| 1 | month |
57+
| 2 | year |

0 commit comments

Comments
 (0)