• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pglejzer / timepicker-ui / 21549519772

31 Jan 2026 07:14PM UTC coverage: 80.798%. First build
21549519772

Pull #110

github

web-flow
Merge f50571e65 into fcb4cf379
Pull Request #110: Upgrade/new options

2137 of 2953 branches covered (72.37%)

Branch coverage included in aggregate %.

753 of 823 new or added lines in 33 files covered. (91.49%)

2807 of 3166 relevant lines covered (88.66%)

32.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.94
/app/src/managers/plugins/timezone/TimezoneManager.ts
1
import type { CoreState } from '../../../timepicker/CoreState';
2
import type { EventEmitter, TimepickerEventMap } from '../../../utils/EventEmitter';
4✔
3
import { clearTimezoneCache } from '../../../utils/timezone';
4✔
4
import { isDocument } from '../../../utils/node';
4✔
5
import { TIMINGS } from '../../../constants/timings';
4✔
6
import { TimezoneDropdown } from './TimezoneDropdown';
4✔
7
import { TimezoneKeyboard } from './TimezoneKeyboard';
4✔
8

9
export default class TimezoneManager {
10
  private readonly core: CoreState;
21✔
11
  private readonly emitter: EventEmitter<TimepickerEventMap>;
21✔
12
  private readonly dropdown: TimezoneDropdown;
21✔
13
  private readonly keyboard: TimezoneKeyboard;
21✔
14
  private cleanupHandlers: Array<() => void> = [];
21✔
15
  private clickOutsideTimerId: number | null = null;
21✔
16

17
  constructor(core: CoreState, emitter: EventEmitter<TimepickerEventMap>) {
18
    this.core = core;
19
    this.emitter = emitter;
21!
20
    this.dropdown = new TimezoneDropdown(core, emitter);
21
    this.keyboard = new TimezoneKeyboard();
22
  }
19✔
23

1✔
24
  private get isEnabled(): boolean {
18✔
25
    return this.core.options.timezone?.enabled === true;
18✔
26
  }
1✔
27

17✔
28
  init(): void {
17✔
29
    if (!this.isEnabled || !isDocument()) return;
17✔
30

31
    const dropdownEl = this.dropdown.getDropdown();
32
    if (!dropdownEl) return;
2✔
33

34
    this.dropdown.populateOptions();
35
    this.dropdown.restorePreviousSelection();
2✔
36
    this.bindEvents();
1✔
37
  }
1✔
38

39
  getSelectedTimezone(): string | null {
40
    return this.dropdown.getSelectedTimezone();
17✔
41
  }
17✔
42

17!
NEW
43
  setTimezone(timezoneId: string): void {
×
44
    if (!this.isEnabled) return;
17✔
NEW
45
    this.dropdown.selectTimezone(timezoneId);
×
NEW
46
  }
×
NEW
47

×
NEW
48
  private bindEvents(): void {
×
NEW
49
    const dropdownEl = this.dropdown.getDropdown();
×
50
    const menu = this.dropdown.getMenu();
51

52
    if (!dropdownEl || !menu) return;
53

17✔
54
    const clickOutsideHandler = (e: MouseEvent): void => {
4✔
55
      if (!dropdownEl.contains(e.target as Node)) {
4✔
56
        this.dropdown.setOpen(false);
4✔
57
        this.keyboard.reset();
3✔
58
        if (isDocument()) {
3✔
NEW
59
          document.removeEventListener('click', clickOutsideHandler);
×
NEW
60
        }
×
61
      }
NEW
62
    };
×
63

64
    const toggleHandler = (e: Event): void => {
65
      e.stopPropagation();
66
      const isOpen = this.dropdown.isOpen();
1✔
67

1✔
68
      if (!isOpen) {
69
        this.dropdown.setOpen(true);
70
        this.clickOutsideTimerId = window.setTimeout(() => {
17✔
71
          if (isDocument()) {
9✔
72
            document.addEventListener('click', clickOutsideHandler);
73
          }
17✔
74
          this.clickOutsideTimerId = null;
1✔
75
        }, TIMINGS.DROPDOWN_CLICK_DELAY);
1✔
76
      } else {
1!
NEW
77
        this.dropdown.setOpen(false);
×
78
        this.keyboard.reset();
1✔
79
      }
1!
80
    };
1✔
81

1!
82
    const keydownHandler = (e: KeyboardEvent): void => {
1✔
83
      this.handleKeydown(e, clickOutsideHandler);
1✔
84
    };
1✔
85

1!
86
    const optionClickHandler = (e: Event): void => {
1✔
87
      e.stopPropagation();
88
      const targetElement = e.target;
1✔
89
      if (!(targetElement instanceof HTMLElement)) return;
90

91
      const option = targetElement.closest('.tp-ui-timezone-option');
92
      if (option instanceof HTMLElement) {
17✔
93
        const value = option.getAttribute('data-value');
17✔
94
        if (value) {
17✔
95
          this.dropdown.selectTimezone(value);
17✔
96
          this.dropdown.setOpen(false);
3!
97
          this.keyboard.reset();
3✔
98

99
          if (isDocument()) {
100
            document.removeEventListener('click', clickOutsideHandler);
101
          }
102
          dropdownEl.focus();
103
        }
9✔
104
      }
9!
NEW
105
    };
×
106

9✔
107
    dropdownEl.addEventListener('click', toggleHandler);
9✔
108
    dropdownEl.addEventListener('keydown', keydownHandler);
9✔
109
    menu.addEventListener('click', optionClickHandler);
110

111
    this.cleanupHandlers.push(
2✔
112
      () => dropdownEl.removeEventListener('click', toggleHandler),
2!
113
      () => dropdownEl.removeEventListener('keydown', keydownHandler),
2✔
114
      () => menu.removeEventListener('click', optionClickHandler),
115
      () => {
NEW
116
        if (isDocument()) {
×
NEW
117
          document.removeEventListener('click', clickOutsideHandler);
×
NEW
118
        }
×
NEW
119
      },
×
NEW
120
    );
×
NEW
121
  }
×
NEW
122

×
123
  private handleKeydown(e: KeyboardEvent, clickOutsideHandler: (e: MouseEvent) => void): void {
124
    const dropdownEl = this.dropdown.getDropdown();
125
    if (!dropdownEl) return;
126

2✔
127
    const isOpen = this.dropdown.isOpen();
128
    const options = this.dropdown.getOptions();
1✔
129

1✔
130
    switch (e.key) {
1✔
131
      case 'Enter':
1!
132
      case ' ':
1✔
133
        e.preventDefault();
134
        if (!isOpen) {
1✔
135
          this.dropdown.setOpen(true);
136
        } else {
3✔
137
          const focusedIndex = this.keyboard.getFocusedIndex();
3✔
138
          if (focusedIndex >= 0) {
1✔
139
            const value = options[focusedIndex]?.getAttribute('data-value');
140
            if (value) {
141
              this.dropdown.selectTimezone(value);
2✔
142
              this.dropdown.setOpen(false);
2✔
143
              this.keyboard.reset();
144
            }
3✔
145
          }
146
        }
1✔
147
        break;
1!
148

1✔
149
      case 'Escape':
1✔
150
        e.preventDefault();
151
        this.dropdown.setOpen(false);
1✔
152
        this.keyboard.reset();
153
        if (isDocument()) {
1!
154
          document.removeEventListener('click', clickOutsideHandler);
1✔
155
        }
1✔
156
        break;
1✔
157

158
      case 'ArrowDown':
1✔
159
        e.preventDefault();
160
        if (!isOpen) {
1!
161
          this.dropdown.setOpen(true);
1✔
162
        } else {
1✔
163
          this.keyboard.moveDown(options.length);
1✔
164
          this.keyboard.updateVisualFocus(options);
165
        }
1✔
166
        break;
167

168
      case 'ArrowUp':
169
        e.preventDefault();
4✔
170
        if (isOpen) {
1✔
171
          this.keyboard.moveUp();
1✔
172
          this.keyboard.updateVisualFocus(options);
173
        }
12✔
174
        break;
4✔
175

4✔
176
      case 'Home':
4✔
177
        if (isOpen) {
4✔
178
          e.preventDefault();
179
          this.keyboard.moveToFirst();
180
          this.keyboard.updateVisualFocus(options);
4✔
181
        }
182
        break;
183

184
      case 'End':
185
        if (isOpen) {
186
          e.preventDefault();
187
          this.keyboard.moveToLast(options.length);
188
          this.keyboard.updateVisualFocus(options);
189
        }
190
        break;
191
    }
192
  }
193

194
  destroy(): void {
195
    if (this.clickOutsideTimerId !== null) {
196
      window.clearTimeout(this.clickOutsideTimerId);
197
      this.clickOutsideTimerId = null;
198
    }
199

200
    this.cleanupHandlers.forEach((fn) => fn());
201
    this.cleanupHandlers = [];
202
    this.keyboard.reset();
203
    this.dropdown.reset();
204
    clearTimezoneCache();
205
  }
206
}
207

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc