summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/html/forms/resources/datetimelocal_picker.js
blob: 84828a16fd5710f9df0443154e114f6489fed1d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
'use strict';
// Copyright (C) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Picker used by <input type='datetime-local' />
 */

function initializeDateTimeLocalPicker(config) {
  const dateTimeLocalPicker = new DateTimeLocalPicker(config);
  global.picker = dateTimeLocalPicker;
  main.append(dateTimeLocalPicker);
  main.style.border = '1px solid transparent';
  main.style.height = dateTimeLocalPicker.height + 'px';
  main.style.width = dateTimeLocalPicker.width + 'px';
  resizeWindow(dateTimeLocalPicker.width + 2, dateTimeLocalPicker.height + 2);
}

/**
 * DateTimeLocalPicker: Custom element providing a datetime-local picker implementation.
 *             DateTimeLocalPicker contains 2 parts:
 *                 - date picker
 *                 - time picker
 */
class DateTimeLocalPicker extends HTMLElement {
  constructor(config) {
    super();

    this.className = DateTimeLocalPicker.ClassName;

    this.datePicker_ = new CalendarPicker(config.mode, config);
    this.timePicker_ = new TimePicker(config);
    this.append(this.datePicker_.element, this.timePicker_);

    this.hadValidValueWhenOpened_ =
        (config.currentValue !== '') && (this.datePicker_.selection() != null);
    this.initialSelectedValue_ = this.selectedValue;
    this.initialFocusedFieldIndex_ = config.focusedFieldIndex || 0;

    this.addEventListener('keydown', this.onKeyDown_);
    this.addEventListener('click', this.onClick_);

    window.addEventListener('resize', this.onWindowResize_, {once: true});
  };

  onKeyDown_ = (event) => {
    switch (event.key) {
      case 'Enter':
        // Submit the popup for an Enter keypress except when the user is
        // hitting Enter to activate the month switcher button, Today button,
        // or previous/next month arrows.
        if (!event.target.matches(
                '.calendar-navigation-button, .month-popup-button, .year-list-view')) {
          window.pagePopupController.setValueAndClosePopup(
              0, this.selectedValue);
        } else if (event.target.matches(
                       '.calendar-navigation-button, .year-list-view')) {
          // Navigating with the previous/next arrows may change selection,
          // so push this change to the in-page control but don't
          // close the popup.
          window.pagePopupController.setValue(this.selectedValue);
        }
        break;
      case 'Escape':
        if (this.selectedValue === this.initialSelectedValue_) {
          window.pagePopupController.closePopup();
        } else {
          this.datePicker_.resetToInitialValue();
          this.timePicker_.resetToInitialValue();
          window.pagePopupController.setValue(
              this.hadValidValueWhenOpened_ ? this.initialSelectedValue_ : '');
        }
        break;
      case 'ArrowUp':
      case 'ArrowDown':
      case 'ArrowLeft':
      case 'ArrowRight':
      case 'PageUp':
      case 'PageDown':
        if (event.target.matches('.calendar-table-view, .time-column') &&
            this.hasSelectedDate) {
          window.pagePopupController.setValue(this.selectedValue);
        }
        // Stop the native scrolling behavior; the Time picker needs to manage
        // its own scroll position.
        event.preventDefault();
        break;
      case 'Home':
      case 'End':
        window.pagePopupController.setValue(this.selectedValue);
        event.stopPropagation();
        // Prevent an attempt to scroll to the end of
        // of an infinitely looping time picker column.
        event.preventDefault();
        break;
    }
  };

  onClick_ = (event) => {
    if (event.target.matches(
            '.day-cell, .time-cell, .today-button-refresh, .calendar-navigation-button, .year-list-view, .calendar-navigation-button, .navigation-button-icon-refresh, .month-button') &&
        this.hasSelectedDate) {
      window.pagePopupController.setValue(this.selectedValue);
    }
  };

  onWindowResize_ = (event) => {
    // Check if we should focus on time field by subtracting 3 (month, day, and
    // year) from index
    let timeFocusFieldIndex = this.initialFocusedFieldIndex_ - 3;
    if (timeFocusFieldIndex < 0 ||
        !this.timePicker_.focusOnFieldIndex(timeFocusFieldIndex)) {
      // Default to focus on date
      this.datePicker_.calendarTableView.element.focus();
    }
  };

  // This will be false if neither the initial value of the
  // control nor today's date are within a valid date range defined
  // by the 'step', 'min', and 'max' attributes of the control.
  get hasSelectedDate() {
    return (this.datePicker_.selection() != null);
  }

  get selectedValue() {
    return this.hasSelectedDate ? (this.datePicker_.getSelectedValue() + 'T' +
                                   this.timePicker_.selectedValue) :
                                  '';
  }

  get height() {
    return DateTimeLocalPicker.Height;
  }

  get width() {
    return this.datePicker_.width() + this.timePicker_.width;
  }

  get datePicker() {
    return this.datePicker_;
  }

  get timePicker() {
    return this.timePicker_;
  }
}
DateTimeLocalPicker.ClassName = 'datetimelocal-picker';
DateTimeLocalPicker.Height = 280;
window.customElements.define('datetimelocal-picker', DateTimeLocalPicker);