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

pglejzer / timepicker-ui / 27286160512

10 Jun 2026 03:12PM UTC coverage: 86.868% (+1.5%) from 85.325%
27286160512

push

github

web-flow
Merge pull request #167 from pglejzer/feature/4.4.0

Feature/4.4.0

2277 of 2878 branches covered (79.12%)

Branch coverage included in aggregate %.

406 of 427 new or added lines in 29 files covered. (95.08%)

162 existing lines in 27 files now uncovered.

4523 of 4950 relevant lines covered (91.37%)

115.32 hits per line

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

91.01
/app/src/managers/ClearButtonManager.ts
1
import type { CoreState } from '../timepicker/CoreState';
2
import type { EventEmitter, TimepickerEventMap } from '../utils/EventEmitter';
10✔
3
import { announceToScreenReader, bindActivate } from '../utils/accessibility';
10✔
4
import { PluginRegistry } from '../core/PluginRegistry';
10✔
5
import { bindEmitter } from '../utils/managerHelpers';
10✔
6

7
export default class ClearButtonManager {
8
  private core: CoreState;
279✔
9
  private emitter: EventEmitter<TimepickerEventMap>;
279✔
10
  private cleanupHandlers: Array<() => void> = [];
279✔
11
  private wasCleared = false;
279✔
12

13
  constructor(core: CoreState, emitter: EventEmitter<TimepickerEventMap>) {
14
    this.core = core;
80✔
15
    this.emitter = emitter;
41✔
16
  }
39✔
17

39!
18
  init(): void {
×
19
    if (!this.core.options.ui.clearButton) return;
39✔
20

31✔
21
    const clearButton = this.getClearButton();
1✔
22
    if (!clearButton) return;
30✔
23

24
    const handler = (): void => {
39✔
25
      if (this.core.isDestroyed) return;
39✔
26
      this.handleClearClick();
27
    };
28

39✔
29
    this.cleanupHandlers.push(bindActivate(clearButton, handler));
41✔
30

31
    this.setupInternalEventListeners();
39✔
32
  }
29✔
33

34
  private setupInternalEventListeners(): void {
39✔
35
    bindEmitter(this.emitter, this.cleanupHandlers, 'update', () => {
6✔
36
      this.updateClearButtonState();
6✔
37
    });
38
    bindEmitter(this.emitter, this.cleanupHandlers, 'open', () => {
39✔
39
      this.updateClearButtonState();
3✔
40
    });
3✔
41
    bindEmitter(this.emitter, this.cleanupHandlers, 'select:hour', () => {
42
      this.updateClearButtonState();
43
      this.reenableConfirmIfCleared();
44
    });
30✔
45
    bindEmitter(this.emitter, this.cleanupHandlers, 'select:minute', () => {
30✔
46
      this.updateClearButtonState();
30✔
47
      this.reenableConfirmIfCleared();
30✔
48
    });
30✔
49
  }
30✔
50

30✔
51
  private handleClearClick(): void {
30✔
52
    const input = this.core.getInput();
30✔
53
    const previousValue = input?.value || null;
30✔
54

55
    this.clearTimeValue();
56
    this.resetClockToNeutral();
57
    this.disableConfirmButton();
58
    this.wasCleared = true;
30✔
59

30✔
60
    const modal = this.core.getModalElement();
6✔
61
    announceToScreenReader(modal, 'Time cleared');
62

63
    this.emitter.emit('clear', { previousValue });
64
    this.emitter.emit('update', {
30✔
65
      hour: undefined,
30✔
66
      minutes: undefined,
30✔
67
      type: undefined,
23✔
68
    });
69

30✔
70
    const { callbacks } = this.core.options;
30✔
71
    if (callbacks.onClear) {
30✔
72
      callbacks.onClear({ previousValue });
30!
UNCOV
73
    }
×
74
  }
75

30✔
76
  private clearTimeValue(): void {
30!
UNCOV
77
    const shouldClearInput = this.core.options.clearBehavior?.clearInput !== false;
×
78
    const input = this.core.getInput();
79
    if (input && shouldClearInput) {
80
      input.value = '';
81
    }
30✔
82

30✔
83
    this.core.setDegreesHours(null);
30✔
84
    this.core.setDegreesMinutes(null);
30✔
85

30✔
86
    const rangeClear = PluginRegistry.getClearHandler('range');
30✔
87
    if (rangeClear) {
30!
88
      rangeClear(this.core, this.emitter);
30✔
89
    }
30✔
90

91
    const timezoneClear = PluginRegistry.getClearHandler('timezone');
30!
92
    if (timezoneClear) {
30✔
93
      timezoneClear(this.core, this.emitter);
30✔
94
    }
95
  }
30✔
96

30!
97
  private resetClockToNeutral(): void {
30✔
98
    const clockType = this.core.options.clock.type;
30✔
99
    const neutralHour = '12';
30✔
100
    const neutralMinutes = '00';
30✔
101
    const neutralType = clockType === '12h' ? 'PM' : undefined;
30✔
102

1✔
103
    const hourInput = this.core.getHour();
104
    const minuteInput = this.core.getMinutes();
105

30✔
106
    if (hourInput) {
30!
107
      hourInput.value = neutralHour;
30✔
108
      hourInput.removeAttribute('aria-valuenow');
109
    }
30✔
110

27✔
111
    if (minuteInput) {
27✔
112
      minuteInput.value = neutralMinutes;
27✔
113
      minuteInput.removeAttribute('aria-valuenow');
27✔
114
    }
27✔
115

27✔
116
    const clockHand = this.core.getClockHand();
27✔
117
    if (clockHand) {
27✔
118
      const originalTransition = clockHand.style.transition;
119
      clockHand.style.transition = 'none';
30✔
120
      clockHand.style.transform = 'rotateZ(0deg)';
121

122
      void clockHand.offsetHeight;
30✔
123

30✔
124
      requestAnimationFrame(() => {
1✔
125
        clockHand.style.transition = originalTransition;
1✔
126
      });
127
    }
30✔
128

30✔
129
    this.removeActiveStates();
30✔
130

30✔
131
    if (hourInput) {
132
      hourInput.click();
133
    }
30✔
134

30!
UNCOV
135
    if (clockType === '12h' && neutralType) {
×
136
      const amButton = this.core.getAM();
30✔
137
      const pmButton = this.core.getPM();
30✔
138

139
      amButton?.classList.remove('active');
140
      pmButton?.classList.remove('active');
79✔
141

79!
UNCOV
142
      amButton?.setAttribute('aria-pressed', 'false');
×
143
      pmButton?.setAttribute('aria-pressed', 'false');
79✔
144

79✔
145
      pmButton?.classList.add('active');
79✔
146
      pmButton?.setAttribute('aria-pressed', 'true');
79✔
147
    }
79✔
148

79✔
149
    this.emitter.emit('animation:clock', {});
79✔
150
  }
79✔
151

79✔
152
  private removeActiveStates(): void {
79✔
153
    const allValueTips = this.core.getAllValueTips();
129✔
154
    allValueTips.forEach((tip) => {
9✔
155
      tip.classList.remove('active');
79✔
156
      tip.removeAttribute('aria-selected');
79✔
157
    });
79✔
158

79✔
159
    const hourInput = this.core.getHour();
160
    const minuteInput = this.core.getMinutes();
161

9✔
162
    hourInput?.removeAttribute('aria-valuenow');
5✔
163
    minuteInput?.removeAttribute('aria-valuenow');
4✔
164
  }
4!
UNCOV
165

×
166
  private disableConfirmButton(): void {
4✔
167
    const okButton = this.core.getOkButton();
4✔
168
    if (!okButton) return;
4✔
169

170
    okButton.classList.add('disabled');
171
    okButton.setAttribute('aria-disabled', 'true');
118✔
172
  }
118!
173

174
  private updateClearButtonState(): void {
175
    const clearButton = this.getClearButton();
206✔
176
    if (!clearButton) return;
206✔
177

178
    const input = this.core.getInput();
179
    const hasInputValue = input?.value && input.value.trim() !== '';
10✔
180

181
    const hourInput = this.core.getHour();
182
    const minuteInput = this.core.getMinutes();
183
    const activeTypeMode = this.core.getActiveTypeMode();
184

185
    const clockType = this.core.options.clock.type;
186
    const currentHour = hourInput?.value || '';
187
    const currentMinutes = minuteInput?.value || '';
188
    const currentType = activeTypeMode?.textContent || '';
189

190
    const isDefaultValue =
191
      clockType === '12h'
192
        ? currentHour === '12' && currentMinutes === '00' && currentType === 'PM'
193
        : currentHour === '12' && currentMinutes === '00';
194

195
    const hasChangedValue = !isDefaultValue;
196

197
    const shouldEnable = hasInputValue || hasChangedValue;
198

199
    clearButton.classList.toggle('disabled', !shouldEnable);
200
    clearButton.setAttribute('aria-disabled', String(!shouldEnable));
201
  }
202

203
  private reenableConfirmIfCleared(): void {
204
    if (!this.wasCleared) return;
205

206
    const okButton = this.core.getOkButton();
207
    if (!okButton) return;
208

209
    okButton.classList.remove('disabled');
210
    okButton.setAttribute('aria-disabled', 'false');
211
    this.wasCleared = false;
212
  }
213

214
  private getClearButton(): HTMLButtonElement | null {
215
    const modal = this.core.getModalElement();
216
    return modal?.querySelector('.tp-ui-clear-btn') || null;
217
  }
218

219
  destroy(): void {
220
    this.cleanupHandlers.forEach((cleanup) => cleanup());
221
    this.cleanupHandlers = [];
222
  }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc