@vuu-ui/vuu-ui-controls 4.0.0-alpha.18 → 4.0.0-alpha.19
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/package.json +9 -9
- package/src/time-input/MaskedInput.js +176 -156
- package/src/time-input/TimeInput.css.js +41 -18
- package/src/time-input/TimeInput.js +25 -14
- package/src/time-input/useTimeInput.js +57 -47
- package/src/vuu-date-picker/VuuDatePicker.js +46 -52
- package/src/vuu-time-picker/VuuTimePicker.js +6 -5
- package/types/index.d.ts +2 -2
- package/types/modal-provider/ModalProvider.d.ts +1 -1
- package/types/time-input/MaskedInput.d.ts +60 -10
- package/types/time-input/TimeInput.d.ts +4 -3
- package/types/time-input/useTimeInput.d.ts +24 -5
- package/types/vuu-date-picker/VuuDatePicker.d.ts +19 -3
- package/types/vuu-time-picker/VuuTimePicker.d.ts +8 -6
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.0-alpha.
|
|
2
|
+
"version": "4.0.0-alpha.19",
|
|
3
3
|
"description": "VUU UI Controls",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"author": "heswell",
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@vuu-ui/vuu-data-types": "4.0.0-alpha.
|
|
15
|
-
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.
|
|
16
|
-
"@vuu-ui/vuu-table-types": "4.0.0-alpha.
|
|
14
|
+
"@vuu-ui/vuu-data-types": "4.0.0-alpha.19",
|
|
15
|
+
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.19",
|
|
16
|
+
"@vuu-ui/vuu-table-types": "4.0.0-alpha.19"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@vuu-ui/vuu-context-menu": "4.0.0-alpha.
|
|
20
|
-
"@vuu-ui/vuu-data-react": "4.0.0-alpha.
|
|
21
|
-
"@vuu-ui/vuu-popups": "4.0.0-alpha.
|
|
22
|
-
"@vuu-ui/vuu-table": "4.0.0-alpha.
|
|
23
|
-
"@vuu-ui/vuu-utils": "4.0.0-alpha.
|
|
19
|
+
"@vuu-ui/vuu-context-menu": "4.0.0-alpha.19",
|
|
20
|
+
"@vuu-ui/vuu-data-react": "4.0.0-alpha.19",
|
|
21
|
+
"@vuu-ui/vuu-popups": "4.0.0-alpha.19",
|
|
22
|
+
"@vuu-ui/vuu-table": "4.0.0-alpha.19",
|
|
23
|
+
"@vuu-ui/vuu-utils": "4.0.0-alpha.19",
|
|
24
24
|
"@salt-ds/core": "1.67.0",
|
|
25
25
|
"@salt-ds/icons": "1.16.0",
|
|
26
26
|
"@salt-ds/styles": "0.2.1",
|
|
@@ -1,35 +1,78 @@
|
|
|
1
|
-
import { EventEmitter, createSyntheticEvent, decrementTimeUnitValue, incrementTimeUnitValue,
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { EventEmitter, createSyntheticEvent, decrementTimeUnitValue, incrementTimeUnitValue, normaliseTimeString, updateTimeString, zeroTime, zeroTimeMillis } from "@vuu-ui/vuu-utils";
|
|
2
|
+
const unitSpec = {
|
|
3
|
+
hours: {
|
|
4
|
+
start: 0,
|
|
5
|
+
length: 2,
|
|
6
|
+
max: 23
|
|
7
|
+
},
|
|
8
|
+
minutes: {
|
|
9
|
+
start: 3,
|
|
10
|
+
length: 2,
|
|
11
|
+
max: 59
|
|
12
|
+
},
|
|
13
|
+
seconds: {
|
|
14
|
+
start: 6,
|
|
15
|
+
length: 2,
|
|
16
|
+
max: 59
|
|
17
|
+
},
|
|
18
|
+
milliseconds: {
|
|
19
|
+
start: 9,
|
|
20
|
+
length: 3,
|
|
21
|
+
max: 999
|
|
22
|
+
}
|
|
9
23
|
};
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
const secondsUnits = [
|
|
25
|
+
"hours",
|
|
26
|
+
"minutes",
|
|
27
|
+
"seconds"
|
|
28
|
+
];
|
|
29
|
+
const millisecondsUnits = [
|
|
30
|
+
...secondsUnits,
|
|
31
|
+
"milliseconds"
|
|
32
|
+
];
|
|
33
|
+
const invalidClassName = "vuuTimeInput-invalid";
|
|
34
|
+
const isValidUnitValue = (unit, value)=>{
|
|
35
|
+
const { length, max } = unitSpec[unit];
|
|
36
|
+
return value.length === length && /^[0-9]+$/.test(value) && parseInt(value) <= max;
|
|
13
37
|
};
|
|
14
38
|
class MaskedInput extends EventEmitter {
|
|
15
39
|
#controlled = false;
|
|
16
40
|
#input = null;
|
|
17
41
|
#isFocused = false;
|
|
18
|
-
#
|
|
42
|
+
#milliseconds;
|
|
19
43
|
#selectionStart = -1;
|
|
20
44
|
#selectionEnd = -1;
|
|
45
|
+
#units;
|
|
21
46
|
#value;
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
constructor(defaultValue, inputEl = null){
|
|
47
|
+
#selectedUnit;
|
|
48
|
+
#digitIndex = 0;
|
|
49
|
+
constructor(defaultValue, inputEl = null, { milliseconds = false } = {}){
|
|
25
50
|
super();
|
|
51
|
+
this.#milliseconds = milliseconds;
|
|
52
|
+
this.#units = milliseconds ? millisecondsUnits : secondsUnits;
|
|
53
|
+
this.#value = this.normalise(defaultValue) ?? this.zeroValue;
|
|
26
54
|
if (inputEl) this.input = inputEl;
|
|
27
|
-
|
|
55
|
+
}
|
|
56
|
+
get milliseconds() {
|
|
57
|
+
return this.#milliseconds;
|
|
58
|
+
}
|
|
59
|
+
get zeroValue() {
|
|
60
|
+
return this.#milliseconds ? zeroTimeMillis : zeroTime;
|
|
61
|
+
}
|
|
62
|
+
get length() {
|
|
63
|
+
return this.#milliseconds ? 12 : 8;
|
|
64
|
+
}
|
|
65
|
+
normalise(value) {
|
|
66
|
+
return normaliseTimeString(value, this.#milliseconds);
|
|
28
67
|
}
|
|
29
68
|
set input(el) {
|
|
30
|
-
if (this.#input)
|
|
69
|
+
if (el === this.#input) return;
|
|
70
|
+
this.#input?.removeEventListener("change", this.emitSyntheticChangeEvent);
|
|
31
71
|
this.#input = el;
|
|
32
|
-
el
|
|
72
|
+
if (el) {
|
|
73
|
+
el.addEventListener("change", this.emitSyntheticChangeEvent);
|
|
74
|
+
if ("" !== el.value && el.value !== this.#value) el.value = this.#value;
|
|
75
|
+
}
|
|
33
76
|
}
|
|
34
77
|
emitSyntheticChangeEvent = (e)=>{
|
|
35
78
|
const syntheticEvent = createSyntheticEvent(e);
|
|
@@ -58,33 +101,46 @@ class MaskedInput extends EventEmitter {
|
|
|
58
101
|
set selectionEnd(value) {
|
|
59
102
|
this.#selectionEnd = value;
|
|
60
103
|
}
|
|
104
|
+
nextUnit(unit) {
|
|
105
|
+
const index = this.#units.indexOf(unit);
|
|
106
|
+
return this.#units[Math.min(index + 1, this.#units.length - 1)];
|
|
107
|
+
}
|
|
108
|
+
previousUnit(unit) {
|
|
109
|
+
const index = this.#units.indexOf(unit);
|
|
110
|
+
return this.#units[Math.max(index - 1, 0)];
|
|
111
|
+
}
|
|
112
|
+
get firstUnit() {
|
|
113
|
+
return this.#units[0];
|
|
114
|
+
}
|
|
115
|
+
get lastUnit() {
|
|
116
|
+
return this.#units[this.#units.length - 1];
|
|
117
|
+
}
|
|
61
118
|
setValue(value) {
|
|
62
|
-
if (!this.#controlled)
|
|
63
|
-
this.#isIncrementalChange = false;
|
|
64
|
-
this.#value = value;
|
|
65
|
-
}
|
|
119
|
+
if (!this.#controlled) this.#value = value;
|
|
66
120
|
if (this.#input) {
|
|
67
121
|
this.#input.value = value;
|
|
68
122
|
this.#input.dispatchEvent(new Event("change", {
|
|
69
123
|
bubbles: true,
|
|
70
124
|
composed: true
|
|
71
125
|
}));
|
|
126
|
+
if (this.#controlled) this.#input.value = this.#value;
|
|
72
127
|
}
|
|
73
128
|
}
|
|
129
|
+
getUnitValue(unit) {
|
|
130
|
+
const { start, length } = unitSpec[unit];
|
|
131
|
+
return this.#value.slice(start, start + length);
|
|
132
|
+
}
|
|
74
133
|
setUnitValue(unit, value) {
|
|
75
|
-
const
|
|
76
|
-
this.setValue(
|
|
134
|
+
const newValue = updateTimeString(this.#value, unit, value);
|
|
135
|
+
this.setValue(newValue);
|
|
77
136
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return this.#value.slice(6, 8);
|
|
86
|
-
default:
|
|
87
|
-
throw Error(`[MaskedInout] invalue unit ${unit}`);
|
|
137
|
+
setInvalid(invalid) {
|
|
138
|
+
if (this.#input) if (invalid) {
|
|
139
|
+
this.#input.classList.add(invalidClassName);
|
|
140
|
+
this.#input.setAttribute("aria-invalid", "true");
|
|
141
|
+
} else {
|
|
142
|
+
this.#input.classList.remove(invalidClassName);
|
|
143
|
+
this.#input.removeAttribute("aria-invalid");
|
|
88
144
|
}
|
|
89
145
|
}
|
|
90
146
|
get value() {
|
|
@@ -92,188 +148,152 @@ class MaskedInput extends EventEmitter {
|
|
|
92
148
|
}
|
|
93
149
|
set value(value) {
|
|
94
150
|
this.#controlled = true;
|
|
95
|
-
|
|
96
|
-
if (this
|
|
97
|
-
|
|
98
|
-
this
|
|
99
|
-
|
|
100
|
-
if (isIncremental) this.restoreSelection();
|
|
101
|
-
else this.advanceSelection();
|
|
151
|
+
const normalisedValue = this.normalise(value);
|
|
152
|
+
if (void 0 !== normalisedValue && normalisedValue !== this.#value) {
|
|
153
|
+
this.#value = normalisedValue;
|
|
154
|
+
if (this.isFocused) requestAnimationFrame(()=>{
|
|
155
|
+
this.restoreSelection();
|
|
102
156
|
});
|
|
103
157
|
}
|
|
104
158
|
}
|
|
105
159
|
clear(unit) {
|
|
106
|
-
if (this.#input)
|
|
107
|
-
let newValue = this.#value;
|
|
108
|
-
if ("hours" === unit) newValue = zeroTimeUnit.concat(this.#value.slice(2));
|
|
109
|
-
else if ("minutes" === unit) newValue = this.#value.slice(0, 3).concat(zeroTimeUnit).concat(this.#value.slice(5));
|
|
110
|
-
else if ("seconds" === unit) newValue = this.#value.slice(0, 6).concat(zeroTimeUnit);
|
|
111
|
-
if (newValue !== this.#value) this.setValue(newValue);
|
|
112
|
-
}
|
|
160
|
+
if (this.#input) this.setUnitValue(unit, "0".repeat(unitSpec[unit].length));
|
|
113
161
|
}
|
|
114
|
-
select(unit,
|
|
115
|
-
if (this.#input) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.selectionEnd = this.#input.selectionEnd = 2;
|
|
120
|
-
} else if ("minutes" === unit) {
|
|
121
|
-
this.selectionStart = this.#input.selectionStart = 3 + offset;
|
|
122
|
-
this.selectionEnd = this.#input.selectionEnd = 5;
|
|
123
|
-
} else if ("seconds" === unit) {
|
|
124
|
-
this.selectionStart = this.#input.selectionStart = 6 + offset;
|
|
125
|
-
this.selectionEnd = this.#input.selectionEnd = 8;
|
|
126
|
-
}
|
|
162
|
+
select(unit, digitIndex = 0) {
|
|
163
|
+
if (this.#input && this.#units.includes(unit)) {
|
|
164
|
+
const { start, length } = unitSpec[unit];
|
|
165
|
+
this.selectionStart = start + digitIndex;
|
|
166
|
+
this.selectionEnd = start + length;
|
|
127
167
|
this.#input.setSelectionRange(this.selectionStart, this.selectionEnd);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.#unitSelected = void 0;
|
|
131
|
-
} else {
|
|
132
|
-
this.#halfUnitSelected = void 0;
|
|
133
|
-
this.#unitSelected = unit;
|
|
134
|
-
}
|
|
168
|
+
this.#selectedUnit = unit;
|
|
169
|
+
this.#digitIndex = digitIndex;
|
|
135
170
|
}
|
|
136
171
|
}
|
|
137
172
|
removeSelection() {
|
|
138
|
-
this
|
|
139
|
-
this.#
|
|
140
|
-
this.#
|
|
173
|
+
this.selectionStart = this.selectionEnd = this.length;
|
|
174
|
+
this.#selectedUnit = void 0;
|
|
175
|
+
this.#digitIndex = 0;
|
|
141
176
|
}
|
|
142
177
|
restoreSelection() {
|
|
143
|
-
if (this.#
|
|
178
|
+
if (this.#selectedUnit) this.select(this.#selectedUnit, this.#digitIndex);
|
|
144
179
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
else throw Error("unreachable code, in theory");
|
|
180
|
+
selectFirst() {
|
|
181
|
+
this.select(this.firstUnit);
|
|
182
|
+
}
|
|
183
|
+
selectLast() {
|
|
184
|
+
this.select(this.lastUnit);
|
|
151
185
|
}
|
|
152
186
|
moveFocus(direction) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else if ("seconds" === this.#unitSelected || "seconds" === this.#halfUnitSelected) this.select("minutes");
|
|
158
|
-
else if ("minutes" === this.#unitSelected || "minutes" === this.#halfUnitSelected) this.select("hours");
|
|
159
|
-
else if ("hours" === this.#unitSelected || "hours" === this.#halfUnitSelected) this.select("hours");
|
|
160
|
-
else {
|
|
161
|
-
const selection = this.getSelection();
|
|
162
|
-
if (selection === CursorAtEnd) this.select("seconds");
|
|
163
|
-
}
|
|
187
|
+
const unit = this.#selectedUnit;
|
|
188
|
+
if (unit) this.select("right" === direction ? this.nextUnit(unit) : this.previousUnit(unit));
|
|
189
|
+
else if ("right" === direction) this.selectFirst();
|
|
190
|
+
else this.selectLast();
|
|
164
191
|
}
|
|
165
192
|
pasteValue(value) {
|
|
166
|
-
|
|
167
|
-
this.#
|
|
168
|
-
|
|
193
|
+
const normalisedValue = this.normalise(value.trim());
|
|
194
|
+
if (this.#input && void 0 !== normalisedValue) {
|
|
195
|
+
this.setInvalid(false);
|
|
196
|
+
if (normalisedValue !== this.#value) this.setValue(normalisedValue);
|
|
197
|
+
this.selectFirst();
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
169
201
|
}
|
|
170
202
|
getUnitAtCursorPos(cursorPos = this.cursorPos) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
203
|
+
for(let i = this.#units.length - 1; i > 0; i--){
|
|
204
|
+
const unit = this.#units[i];
|
|
205
|
+
if (cursorPos >= unitSpec[unit].start) return unit;
|
|
206
|
+
}
|
|
207
|
+
return this.firstUnit;
|
|
208
|
+
}
|
|
209
|
+
get activeUnit() {
|
|
210
|
+
return this.#selectedUnit ?? this.getUnitAtCursorPos();
|
|
175
211
|
}
|
|
176
212
|
incrementValue() {
|
|
177
213
|
if (this.#input) {
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
const newUnitValue = incrementTimeUnitValue(unit, unitValue);
|
|
214
|
+
const unit = this.activeUnit;
|
|
215
|
+
const newUnitValue = incrementTimeUnitValue(unit, this.getUnitValue(unit));
|
|
216
|
+
this.setInvalid(false);
|
|
182
217
|
this.setUnitValue(unit, newUnitValue);
|
|
183
218
|
this.select(unit);
|
|
184
219
|
}
|
|
185
220
|
}
|
|
186
221
|
decrementValue() {
|
|
187
222
|
if (this.#input) {
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const newUnitValue = decrementTimeUnitValue(unit, unitValue);
|
|
223
|
+
const unit = this.activeUnit;
|
|
224
|
+
const newUnitValue = decrementTimeUnitValue(unit, this.getUnitValue(unit));
|
|
225
|
+
this.setInvalid(false);
|
|
192
226
|
this.setUnitValue(unit, newUnitValue);
|
|
193
227
|
this.select(unit);
|
|
194
228
|
}
|
|
195
229
|
}
|
|
196
230
|
backspace() {
|
|
197
231
|
if (this.#input) {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.clear("minutes");
|
|
204
|
-
this.select("hours");
|
|
205
|
-
} else if ("hours" === this.#unitSelected) {
|
|
206
|
-
this.clear("hours");
|
|
207
|
-
this.select("hours");
|
|
208
|
-
} else if (cursorPos > 0) {
|
|
209
|
-
const offset = 6 === this.selectionStart || 3 === this.selectionStart ? 2 : 1;
|
|
210
|
-
const newValue = this.#value.slice(0, cursorPos - offset).concat(zeroTime.slice(cursorPos - offset, cursorPos)).concat(this.#value.slice(cursorPos));
|
|
211
|
-
this.selectionStart -= offset;
|
|
212
|
-
this.selectionEnd -= offset;
|
|
213
|
-
this.setValue(newValue);
|
|
214
|
-
requestAnimationFrame(()=>{
|
|
215
|
-
this.#input?.setSelectionRange(this.#selectionStart, this.selectionEnd);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
232
|
+
const unit = this.activeUnit;
|
|
233
|
+
const isPartiallyEntered = this.#selectedUnit === unit && this.#digitIndex > 0;
|
|
234
|
+
this.setInvalid(false);
|
|
235
|
+
this.clear(unit);
|
|
236
|
+
this.select(isPartiallyEntered ? unit : this.previousUnit(unit));
|
|
218
237
|
}
|
|
219
238
|
}
|
|
220
239
|
update(key) {
|
|
221
240
|
if (this.#input) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
const unit = this.activeUnit;
|
|
242
|
+
const { length, max } = unitSpec[unit];
|
|
243
|
+
const digitIndex = this.#selectedUnit === unit && this.#digitIndex < length ? this.#digitIndex : 0;
|
|
244
|
+
const maxFirstDigit = Math.floor(max / 10 ** (length - 1));
|
|
245
|
+
let newUnitValue;
|
|
246
|
+
let nextDigitIndex;
|
|
247
|
+
if (0 === digitIndex && parseInt(key) > maxFirstDigit) {
|
|
248
|
+
newUnitValue = key.padStart(length, "0");
|
|
249
|
+
nextDigitIndex = length;
|
|
250
|
+
} else {
|
|
251
|
+
const unitValue = this.getUnitValue(unit);
|
|
252
|
+
const head = unitValue.slice(0, digitIndex).concat(key);
|
|
253
|
+
const overwritten = head.concat(unitValue.slice(digitIndex + 1));
|
|
254
|
+
newUnitValue = isValidUnitValue(unit, overwritten) ? overwritten : head.padEnd(length, "0");
|
|
255
|
+
nextDigitIndex = digitIndex + 1;
|
|
256
|
+
}
|
|
257
|
+
if (!isValidUnitValue(unit, newUnitValue)) {
|
|
258
|
+
this.setInvalid(true);
|
|
259
|
+
this.select(unit, digitIndex);
|
|
260
|
+
return;
|
|
235
261
|
}
|
|
262
|
+
this.setInvalid(false);
|
|
263
|
+
this.setUnitValue(unit, newUnitValue);
|
|
264
|
+
if (nextDigitIndex === length) this.select(this.nextUnit(unit));
|
|
265
|
+
else this.select(unit, nextDigitIndex);
|
|
236
266
|
}
|
|
237
267
|
}
|
|
238
268
|
getSelection() {
|
|
239
269
|
if (this.#input) {
|
|
240
270
|
const { selectionEnd, selectionStart } = this.#input;
|
|
241
|
-
|
|
242
|
-
if (0 === selectionStart && 8 === selectionEnd) return FullSelection;
|
|
243
|
-
if (8 === selectionStart && 8 === selectionEnd) return CursorAtEnd;
|
|
244
|
-
else return {
|
|
271
|
+
return {
|
|
245
272
|
end: selectionEnd,
|
|
246
273
|
start: selectionStart
|
|
247
274
|
};
|
|
248
275
|
}
|
|
249
|
-
throw Error("[
|
|
276
|
+
throw Error("[MaskedInput] selection referenced, but no input");
|
|
250
277
|
}
|
|
251
278
|
click() {
|
|
252
279
|
if (this.#input) {
|
|
253
280
|
this.#isFocused = true;
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
256
|
-
else
|
|
257
|
-
else {
|
|
258
|
-
const cursorPos = this.#input.selectionStart ?? 0;
|
|
259
|
-
this.select(this.getUnitAtCursorPos(cursorPos));
|
|
260
|
-
}
|
|
281
|
+
const { start, end } = this.getSelection();
|
|
282
|
+
if (null === start) this.selectFirst();
|
|
283
|
+
else 0 === start && end === this.length || this.select(this.getUnitAtCursorPos(start));
|
|
261
284
|
}
|
|
262
285
|
}
|
|
263
286
|
doubleClick() {
|
|
264
287
|
if (this.#input) {
|
|
265
|
-
const { selectionStart
|
|
266
|
-
if (null
|
|
267
|
-
else if (selectionStart < 3) this.select("hours");
|
|
268
|
-
else if (selectionStart < 6) this.select("minutes");
|
|
269
|
-
else if (selectionStart <= 8) this.select("seconds");
|
|
288
|
+
const { selectionStart } = this.#input;
|
|
289
|
+
if (null !== selectionStart) this.select(this.getUnitAtCursorPos(selectionStart));
|
|
270
290
|
}
|
|
271
291
|
}
|
|
272
292
|
focus = ()=>{
|
|
273
293
|
if (this.#input) {
|
|
274
294
|
this.#isFocused = true;
|
|
275
295
|
requestAnimationFrame(()=>{
|
|
276
|
-
this.
|
|
296
|
+
this.selectFirst();
|
|
277
297
|
});
|
|
278
298
|
}
|
|
279
299
|
};
|
|
@@ -282,4 +302,4 @@ class MaskedInput extends EventEmitter {
|
|
|
282
302
|
this.#isFocused = false;
|
|
283
303
|
};
|
|
284
304
|
}
|
|
285
|
-
export { MaskedInput };
|
|
305
|
+
export { MaskedInput, invalidClassName };
|
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
const css = `
|
|
2
|
-
.
|
|
3
|
-
--time-input-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
.vuuTimeInput-container {
|
|
3
|
+
--time-input-border: var(--vuuTimeInput-borderWidth, 1px);
|
|
4
|
+
--time-input-padding: var(--vuuTimeInput-paddingInline, var(--salt-spacing-50));
|
|
5
|
+
font-family: var(--salt-typography-fontFamily);
|
|
6
|
+
font-size: var(--vuuTimeInput-fontSize, var(--salt-text-fontSize));
|
|
7
|
+
font-variant-numeric: tabular-nums;
|
|
8
|
+
width: fit-content;
|
|
9
|
+
display: inline-grid;
|
|
10
|
+
|
|
11
|
+
&:before, &:after {
|
|
12
|
+
border: 0 solid #0000;
|
|
13
|
+
border-inline-width: var(--time-input-border);
|
|
14
|
+
box-sizing: content-box;
|
|
15
|
+
height: 0;
|
|
16
|
+
max-width: calc(var(--vuuTimeInput-width, 10000px) - 1px);
|
|
17
|
+
padding-inline: var(--time-input-padding);
|
|
18
|
+
visibility: hidden;
|
|
19
|
+
white-space: pre;
|
|
20
|
+
grid-area: 1 / 1;
|
|
21
|
+
padding-inline-end: calc(var(--time-input-padding) + 1px);
|
|
22
|
+
}
|
|
9
23
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
24
|
+
&:before {
|
|
25
|
+
content: attr(data-placeholder);
|
|
26
|
+
}
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
&:after {
|
|
29
|
+
content: attr(data-sample-value);
|
|
30
|
+
}
|
|
16
31
|
}
|
|
17
32
|
|
|
18
33
|
.vuuTimeInput {
|
|
19
34
|
background: var(--vuuTimeInput-background, inherit);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
35
|
+
border-width: var(--time-input-border);
|
|
36
|
+
box-sizing: content-box;
|
|
37
|
+
font-family: inherit;
|
|
38
|
+
font-size: inherit;
|
|
39
|
+
font-variant-numeric: inherit;
|
|
40
|
+
height: calc(var(--vuuTimeInput-height, var(--salt-size-base)) - 2 *
|
|
41
|
+
var(--time-input-border));
|
|
42
|
+
min-width: calc(100% - 2 * var(--time-input-padding) - 2 * var(--time-input-border));
|
|
43
|
+
padding: 0 var(--time-input-padding);
|
|
44
|
+
width: var(--vuuTimeInput-width, 0px);
|
|
45
|
+
grid-area: 1 / 1;
|
|
23
46
|
|
|
24
47
|
&:focus::-webkit-input-placeholder {
|
|
25
48
|
color: #0000;
|
|
@@ -28,10 +51,10 @@ const css = `
|
|
|
28
51
|
&:focus {
|
|
29
52
|
outline: var(--vuuTimeInput-outline, var(--salt-focused-outline));
|
|
30
53
|
}
|
|
31
|
-
}
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
&.vuuTimeInput-invalid {
|
|
56
|
+
border-color: var(--vuuTimeInput-invalid-borderColor, var(--salt-status-error-borderColor, red));
|
|
57
|
+
}
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useForkRef } from "@salt-ds/core";
|
|
3
3
|
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
4
4
|
import { useWindow } from "@salt-ds/window";
|
|
5
|
+
import { normaliseTimeString } from "@vuu-ui/vuu-utils";
|
|
5
6
|
import clsx from "clsx";
|
|
7
|
+
import { forwardRef } from "react";
|
|
6
8
|
import { useTimeInput } from "./useTimeInput.js";
|
|
7
9
|
import TimeInput_0 from "./TimeInput.css";
|
|
8
10
|
const classBase = "vuuTimeInput";
|
|
9
|
-
const TimeInput_TimeInput = /*#__PURE__*/ forwardRef(function({ className, date, defaultValue, onChange, onCommit, placeholder = "hh:mm:ss", value, ...htmlAttributes }, ref) {
|
|
11
|
+
const TimeInput_TimeInput = /*#__PURE__*/ forwardRef(function({ className, date, defaultValue, milliseconds = false, onChange, onCommit, placeholder = milliseconds ? "hh:mm:ss.sss" : "hh:mm:ss", value, ...htmlAttributes }, ref) {
|
|
10
12
|
const targetWindow = useWindow();
|
|
11
13
|
useComponentCssInjection({
|
|
12
14
|
testId: "vuu-time-input",
|
|
@@ -16,22 +18,31 @@ const TimeInput_TimeInput = /*#__PURE__*/ forwardRef(function({ className, date,
|
|
|
16
18
|
const { inputRef, eventHandlers } = useTimeInput({
|
|
17
19
|
date,
|
|
18
20
|
defaultValue,
|
|
21
|
+
milliseconds,
|
|
19
22
|
onChange,
|
|
20
23
|
onCommit,
|
|
21
24
|
value
|
|
22
25
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
const displayValue = void 0 === value ? void 0 : normaliseTimeString(value, milliseconds) ?? value;
|
|
27
|
+
const displayDefaultValue = void 0 === value && void 0 !== defaultValue ? normaliseTimeString(defaultValue, milliseconds) ?? defaultValue : void 0;
|
|
28
|
+
return /*#__PURE__*/ jsx("span", {
|
|
29
|
+
className: `${classBase}-container`,
|
|
30
|
+
"data-placeholder": placeholder,
|
|
31
|
+
"data-sample-value": milliseconds ? "00:00:00.000" : "00:00:00",
|
|
32
|
+
children: /*#__PURE__*/ jsx("input", {
|
|
33
|
+
...htmlAttributes,
|
|
34
|
+
...eventHandlers,
|
|
35
|
+
"aria-placeholder": placeholder,
|
|
36
|
+
className: clsx(classBase, className, {
|
|
37
|
+
[`${classBase}-milliseconds`]: milliseconds
|
|
38
|
+
}),
|
|
39
|
+
defaultValue: displayDefaultValue,
|
|
40
|
+
placeholder: placeholder,
|
|
41
|
+
readOnly: true,
|
|
42
|
+
ref: useForkRef(ref, inputRef),
|
|
43
|
+
spellCheck: "false",
|
|
44
|
+
value: displayValue
|
|
45
|
+
})
|
|
35
46
|
});
|
|
36
47
|
});
|
|
37
48
|
export { TimeInput_TimeInput as TimeInput };
|
|
@@ -1,75 +1,85 @@
|
|
|
1
|
-
import { isValidTimeString } from "@vuu-ui/vuu-utils";
|
|
2
|
-
import { useCallback,
|
|
1
|
+
import { isValidTimeString, isValidTimeStringMillis } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
3
|
import { MaskedInput } from "./MaskedInput.js";
|
|
4
4
|
const isDigit = (char)=>1 === char.length && /[0-9]/.test(char);
|
|
5
|
-
const useTimeInput = ({ defaultValue, onChange, onCommit, value })=>{
|
|
5
|
+
const useTimeInput = ({ defaultValue, milliseconds = false, onChange, onCommit: onCommitProp, value })=>{
|
|
6
|
+
const onCommit = onCommitProp;
|
|
6
7
|
const mousedDownRef = useRef(false);
|
|
8
|
+
const onChangeRef = useRef(onChange);
|
|
9
|
+
onChangeRef.current = onChange;
|
|
7
10
|
const maskedInputRef = useRef(void 0);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
11
|
+
if (void 0 === maskedInputRef.current || maskedInputRef.current.milliseconds !== milliseconds) {
|
|
12
|
+
maskedInputRef.current = new MaskedInput(value ?? maskedInputRef.current?.value ?? defaultValue, null, {
|
|
13
|
+
milliseconds
|
|
14
|
+
});
|
|
15
|
+
maskedInputRef.current.on("change", (e)=>{
|
|
16
|
+
onChangeRef.current?.(e);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const maskedInput = maskedInputRef.current;
|
|
20
|
+
if (void 0 !== value) maskedInput.value = value;
|
|
21
|
+
const setInputEl = useCallback((el)=>{
|
|
22
|
+
maskedInput.input = el;
|
|
16
23
|
}, [
|
|
17
|
-
|
|
18
|
-
onChange,
|
|
19
|
-
value
|
|
24
|
+
maskedInput
|
|
20
25
|
]);
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (isValidTimeString(value)) onCommit(evt, value, "text-input");
|
|
27
|
-
else if ("hh:mm:ss" === value) console.log("no value set");
|
|
28
|
-
else console.log("value is not valid");
|
|
26
|
+
const commitValue = useCallback((evt)=>{
|
|
27
|
+
if ("" !== evt.currentTarget.value) {
|
|
28
|
+
const { value } = maskedInput;
|
|
29
|
+
if (milliseconds ? isValidTimeStringMillis(value) : isValidTimeString(value)) onCommit(evt, value, "text-input");
|
|
30
|
+
}
|
|
29
31
|
}, [
|
|
32
|
+
maskedInput,
|
|
33
|
+
milliseconds,
|
|
30
34
|
onCommit
|
|
31
35
|
]);
|
|
32
36
|
const handleKeyDown = useCallback((e)=>{
|
|
33
|
-
|
|
34
|
-
if (maskedInput)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
else if ("Tab" === e.key) return;
|
|
45
|
-
else if ("Enter" === e.key) commitValue(e, maskedInput.value);
|
|
46
|
-
}
|
|
37
|
+
if (e.ctrlKey || e.metaKey || e.altKey || "Tab" === e.key) return;
|
|
38
|
+
if ("Backspace" === e.key) maskedInput.backspace();
|
|
39
|
+
else if (isDigit(e.key)) maskedInput.update(e.key);
|
|
40
|
+
else if ("ArrowLeft" === e.key) maskedInput.moveFocus("left");
|
|
41
|
+
else if ("ArrowRight" === e.key) maskedInput.moveFocus("right");
|
|
42
|
+
else if ("ArrowUp" === e.key) maskedInput.incrementValue();
|
|
43
|
+
else if ("ArrowDown" === e.key) maskedInput.decrementValue();
|
|
44
|
+
else if ("Home" === e.key) maskedInput.selectFirst();
|
|
45
|
+
else if ("End" === e.key) maskedInput.selectLast();
|
|
46
|
+
else if ("Enter" === e.key) commitValue(e);
|
|
47
|
+
else if ("Escape" === e.key) return;
|
|
47
48
|
e.preventDefault();
|
|
48
49
|
}, [
|
|
49
|
-
commitValue
|
|
50
|
+
commitValue,
|
|
51
|
+
maskedInput
|
|
50
52
|
]);
|
|
51
53
|
const handleDoubleClick = useCallback(()=>{
|
|
52
|
-
|
|
53
|
-
}, [
|
|
54
|
+
maskedInput.doubleClick();
|
|
55
|
+
}, [
|
|
56
|
+
maskedInput
|
|
57
|
+
]);
|
|
54
58
|
const handlePaste = useCallback((e)=>{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}, [
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
maskedInput.pasteValue(e.clipboardData.getData("text"));
|
|
61
|
+
}, [
|
|
62
|
+
maskedInput
|
|
63
|
+
]);
|
|
58
64
|
const handleFocus = useCallback(()=>{
|
|
59
65
|
if (mousedDownRef.current) mousedDownRef.current = false;
|
|
60
|
-
else
|
|
61
|
-
}, [
|
|
66
|
+
else maskedInput.focus();
|
|
67
|
+
}, [
|
|
68
|
+
maskedInput
|
|
69
|
+
]);
|
|
62
70
|
const handleMouseDown = useCallback(()=>{
|
|
63
71
|
mousedDownRef.current = true;
|
|
64
72
|
}, []);
|
|
65
73
|
const handleMouseUp = useCallback((e)=>{
|
|
66
74
|
e.preventDefault();
|
|
67
|
-
|
|
68
|
-
}, [
|
|
75
|
+
maskedInput.click();
|
|
76
|
+
}, [
|
|
77
|
+
maskedInput
|
|
78
|
+
]);
|
|
69
79
|
return {
|
|
70
80
|
inputRef: setInputEl,
|
|
71
81
|
eventHandlers: {
|
|
72
|
-
onBlur:
|
|
82
|
+
onBlur: maskedInput.blur,
|
|
73
83
|
onDoubleClick: handleDoubleClick,
|
|
74
84
|
onFocus: handleFocus,
|
|
75
85
|
onKeyDown: handleKeyDown,
|
|
@@ -1,84 +1,78 @@
|
|
|
1
|
-
import { createElement, useCallback, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import { createElement, useCallback, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { EpochTimestamp, fromDateTimeFields } from "@vuu-ui/vuu-utils";
|
|
3
|
+
import { CalendarDate } from "@internationalized/date";
|
|
3
4
|
import clsx from "clsx";
|
|
4
5
|
import { DatePicker } from "../date-picker/index.js";
|
|
5
6
|
const classBase = "VuuDatePicker";
|
|
6
|
-
const isSameDate = (d1, d2)=>void 0 !== d2 && isSameDay(d1, d2) && isSameMonth(d1, d2) && isSameYear(d1, d2);
|
|
7
7
|
const datePattern = /^\d{1,2} [a-z]{3} \d{4}$/i;
|
|
8
|
-
const isValidDate = (value)=>void 0 !== value && value
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
return {
|
|
18
|
-
datePickerKey: "uncontrolled",
|
|
19
|
-
defaultDate: void 0,
|
|
20
|
-
selectedDate
|
|
21
|
-
};
|
|
8
|
+
const isValidDate = (value)=>void 0 !== value && null !== value.match(datePattern) && !Number.isNaN(new Date(value).getDay());
|
|
9
|
+
const isSameDate = (d1, d2)=>void 0 !== d2 && 0 === d1.compare(d2);
|
|
10
|
+
const toCalendarDateInTimeZone = (value, timeZone)=>{
|
|
11
|
+
const timestamp = value instanceof EpochTimestamp ? value : EpochTimestamp.fromWire(value, "epochMillis");
|
|
12
|
+
if (timestamp) {
|
|
13
|
+
const { year, month, day } = timestamp.getFields(timeZone);
|
|
14
|
+
return new CalendarDate(year, month, day);
|
|
15
|
+
}
|
|
22
16
|
};
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
const startOfDateInTimeZone = (date, timeZone)=>fromDateTimeFields({
|
|
18
|
+
year: date.year,
|
|
19
|
+
month: date.month,
|
|
20
|
+
day: date.day,
|
|
21
|
+
hour: 0,
|
|
22
|
+
minute: 0,
|
|
23
|
+
second: 0,
|
|
24
|
+
millisecond: 0
|
|
25
|
+
}, timeZone);
|
|
26
|
+
const VuuDatePicker = ({ className, onSelectionChange, onCommit, preserveFocusOnSelect, selectedDate: selectedDateProp, timeZone, value, ...props })=>{
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const inputValueRef = useRef("");
|
|
26
29
|
const datePickerRef = useRef(null);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const controlledDate = useMemo(()=>selectedDateProp ?? toCalendarDateInTimeZone(value, timeZone), [
|
|
31
|
+
selectedDateProp,
|
|
32
|
+
timeZone,
|
|
33
|
+
value
|
|
34
|
+
]);
|
|
35
|
+
const [selectedDate, setSelectedDate] = useState(controlledDate);
|
|
36
|
+
const controlledDateRef = useRef(controlledDate);
|
|
37
|
+
if (void 0 !== controlledDate && (void 0 === controlledDateRef.current || 0 !== controlledDate.compare(controlledDateRef.current))) {
|
|
38
|
+
controlledDateRef.current = controlledDate;
|
|
39
|
+
if (!isSameDate(controlledDate, selectedDate)) setSelectedDate(controlledDate);
|
|
40
|
+
}
|
|
32
41
|
const commitDateChange = useCallback((e, date)=>{
|
|
33
42
|
onSelectionChange?.(e, date);
|
|
34
43
|
setOpen(false);
|
|
35
|
-
onCommit?.(e,
|
|
44
|
+
onCommit?.(e, startOfDateInTimeZone(date, timeZone));
|
|
36
45
|
if (preserveFocusOnSelect) requestAnimationFrame(()=>{
|
|
37
46
|
datePickerRef.current?.querySelector("input")?.focus();
|
|
38
47
|
});
|
|
39
48
|
}, [
|
|
40
49
|
onCommit,
|
|
41
50
|
onSelectionChange,
|
|
42
|
-
preserveFocusOnSelect
|
|
51
|
+
preserveFocusOnSelect,
|
|
52
|
+
timeZone
|
|
43
53
|
]);
|
|
44
54
|
const handleSelectionChange = useCallback((e, date)=>{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
dateState.current = getDates(date);
|
|
49
|
-
commitDateChange(e, date);
|
|
50
|
-
} else if (!isSameDate(date, selectedDate)) {
|
|
51
|
-
dateState.current.selectedDate = date;
|
|
52
|
-
commitDateChange(e, date);
|
|
53
|
-
}
|
|
55
|
+
if (date && !isSameDate(date, selectedDate)) {
|
|
56
|
+
setSelectedDate(date);
|
|
57
|
+
commitDateChange(e, date);
|
|
54
58
|
}
|
|
55
59
|
}, [
|
|
56
|
-
commitDateChange
|
|
60
|
+
commitDateChange,
|
|
61
|
+
selectedDate
|
|
57
62
|
]);
|
|
58
|
-
const handleChange = (
|
|
59
|
-
|
|
60
|
-
evt,
|
|
61
|
-
value
|
|
62
|
-
});
|
|
63
|
-
valueRef.current = value;
|
|
64
|
-
console.log(`is '${value}' a valid date ? ${isValidDate(value)}`);
|
|
63
|
+
const handleChange = (_evt, value = "")=>{
|
|
64
|
+
inputValueRef.current = value;
|
|
65
65
|
};
|
|
66
66
|
const handleKeyDown = (e)=>{
|
|
67
67
|
if ("ArrowDown" === e.key) setOpen(true);
|
|
68
68
|
};
|
|
69
69
|
const handleKeyDownCapture = (e)=>{
|
|
70
|
-
if ("Enter" === e.key
|
|
71
|
-
else {
|
|
72
|
-
console.log(`nice try cowboy, but '${valueRef.current}' is nota valid date`);
|
|
73
|
-
e.stopPropagation();
|
|
74
|
-
}
|
|
70
|
+
if ("Enter" === e.key && "" !== inputValueRef.current && !isValidDate(inputValueRef.current)) e.stopPropagation();
|
|
75
71
|
};
|
|
76
|
-
const { datePickerKey, defaultDate, selectedDate } = dateState.current;
|
|
77
72
|
return /*#__PURE__*/ createElement(DatePicker, {
|
|
78
73
|
...props,
|
|
79
74
|
className: clsx(classBase, className),
|
|
80
|
-
|
|
81
|
-
key: datePickerKey,
|
|
75
|
+
key: selectedDate ? "controlled" : "uncontrolled",
|
|
82
76
|
open: open,
|
|
83
77
|
onChange: handleChange,
|
|
84
78
|
onKeyDown: handleKeyDown,
|
|
@@ -5,7 +5,7 @@ import clsx from "clsx";
|
|
|
5
5
|
import { TimeInput } from "../time-input/TimeInput.js";
|
|
6
6
|
import VuuTimePicker from "./VuuTimePicker.css";
|
|
7
7
|
const classBase = "vuuTimePicker";
|
|
8
|
-
const VuuTimePicker_VuuTimePicker = ({ className, defaultValue, onChange, onCommit, value, ...htmlAttributes })=>{
|
|
8
|
+
const VuuTimePicker_VuuTimePicker = ({ className, defaultValue, milliseconds, onChange, onCommit, value, ...htmlAttributes })=>{
|
|
9
9
|
const targetWindow = useWindow();
|
|
10
10
|
useComponentCssInjection({
|
|
11
11
|
testId: "vuu-time-picker",
|
|
@@ -16,10 +16,11 @@ const VuuTimePicker_VuuTimePicker = ({ className, defaultValue, onChange, onComm
|
|
|
16
16
|
...htmlAttributes,
|
|
17
17
|
className: clsx(classBase, className),
|
|
18
18
|
children: /*#__PURE__*/ jsx(TimeInput, {
|
|
19
|
-
defaultValue
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
defaultValue,
|
|
20
|
+
milliseconds,
|
|
21
|
+
onChange,
|
|
22
|
+
onCommit,
|
|
23
|
+
value
|
|
23
24
|
})
|
|
24
25
|
});
|
|
25
26
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -23,10 +23,10 @@ export * from "./split-button";
|
|
|
23
23
|
export * from "./table-search";
|
|
24
24
|
export * from "./tabs-next";
|
|
25
25
|
export * from "./tabstrip";
|
|
26
|
-
export { TimeInput, type TimeInputProps } from "./time-input/TimeInput";
|
|
26
|
+
export { TimeInput, type TimeInputMillisecondsProps, type TimeInputProps, type TimeInputSecondsProps, } from "./time-input/TimeInput";
|
|
27
27
|
export * from "./toolbar";
|
|
28
28
|
export * from "./utils";
|
|
29
29
|
export * from "./vuu-date-picker";
|
|
30
30
|
export * from "./vuu-input";
|
|
31
|
-
export { VuuTimePicker, type VuuTimePickerProps, } from "./vuu-time-picker/VuuTimePicker";
|
|
31
|
+
export { VuuTimePicker, type VuuTimePickerMillisecondsProps, type VuuTimePickerProps, type VuuTimePickerSecondsProps, } from "./vuu-time-picker/VuuTimePicker";
|
|
32
32
|
export * from "./vuu-typeahead-input";
|
|
@@ -4,7 +4,7 @@ export type ShowDialog = (dialogContent: ReactElement, title: string, dialogActi
|
|
|
4
4
|
export type ShowPrompt = (promptContent: ReactElement, promptProps: PromptProps) => void;
|
|
5
5
|
export declare const ModalProvider: ({ children }: {
|
|
6
6
|
children: ReactNode;
|
|
7
|
-
}) => import("react").JSX.Element;
|
|
7
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
export declare const useModal: () => {
|
|
9
9
|
showDialog: ShowDialog;
|
|
10
10
|
closeDialog: () => void;
|
|
@@ -1,13 +1,32 @@
|
|
|
1
|
-
import { EventEmitter, TimeString, TimeUnit } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { EventEmitter, TimeString, TimeStringMillis, TimeUnit } from "@vuu-ui/vuu-utils";
|
|
2
2
|
import { ChangeEventHandler } from "react";
|
|
3
3
|
export type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
4
|
+
export type TimeValue = TimeString | TimeStringMillis;
|
|
5
|
+
export declare const invalidClassName = "vuuTimeInput-invalid";
|
|
6
|
+
export interface MaskedInputOptions {
|
|
7
|
+
/**
|
|
8
|
+
* When true, value includes milliseconds, hh:mm:ss.SSS
|
|
9
|
+
*/
|
|
10
|
+
milliseconds?: boolean;
|
|
11
|
+
}
|
|
4
12
|
export type MaskedInputEvents = {
|
|
5
13
|
change: ChangeEventHandler<HTMLInputElement>;
|
|
6
14
|
};
|
|
7
15
|
export declare class MaskedInput extends EventEmitter<MaskedInputEvents> {
|
|
8
16
|
#private;
|
|
9
|
-
constructor(defaultValue:
|
|
10
|
-
|
|
17
|
+
constructor(defaultValue: TimeValue | undefined, inputEl?: HTMLInputElement | null, { milliseconds }?: MaskedInputOptions);
|
|
18
|
+
get milliseconds(): boolean;
|
|
19
|
+
private get zeroValue();
|
|
20
|
+
/**
|
|
21
|
+
* Length of the formatted value, hh:mm:ss or hh:mm:ss.SSS
|
|
22
|
+
*/
|
|
23
|
+
get length(): 8 | 12;
|
|
24
|
+
private normalise;
|
|
25
|
+
/**
|
|
26
|
+
* Attach (or re-attach) the input element. Re-attaching the same element
|
|
27
|
+
* is a no-op, attaching a different element detaches the previous one.
|
|
28
|
+
*/
|
|
29
|
+
set input(el: HTMLInputElement | null);
|
|
11
30
|
/**
|
|
12
31
|
* The change event is fired programatically. This will only be handled
|
|
13
32
|
* by a native event handler ( not a React handler). We handle this event
|
|
@@ -21,25 +40,56 @@ export declare class MaskedInput extends EventEmitter<MaskedInputEvents> {
|
|
|
21
40
|
set selectionStart(value: number);
|
|
22
41
|
get selectionEnd(): number;
|
|
23
42
|
set selectionEnd(value: number);
|
|
43
|
+
private nextUnit;
|
|
44
|
+
private previousUnit;
|
|
45
|
+
private get firstUnit();
|
|
46
|
+
private get lastUnit();
|
|
24
47
|
private setValue;
|
|
25
|
-
private setUnitValue;
|
|
26
48
|
private getUnitValue;
|
|
27
|
-
|
|
49
|
+
private setUnitValue;
|
|
50
|
+
private setInvalid;
|
|
51
|
+
get value(): TimeValue;
|
|
28
52
|
/**
|
|
29
|
-
* Setting the value this way invokes 'controlled' mode
|
|
53
|
+
* Setting the value this way invokes 'controlled' mode. Value
|
|
54
|
+
* will be normalised to the configured precision.
|
|
30
55
|
*/
|
|
31
|
-
set value(value:
|
|
56
|
+
set value(value: TimeValue);
|
|
32
57
|
clear(unit: TimeUnit): void;
|
|
33
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Select a unit. If digitIndex > 0, only the digits of the unit
|
|
60
|
+
* not yet entered are selected.
|
|
61
|
+
*/
|
|
62
|
+
select(unit: TimeUnit, digitIndex?: number): void;
|
|
34
63
|
removeSelection(): void;
|
|
35
64
|
restoreSelection(): void;
|
|
36
|
-
|
|
65
|
+
selectFirst(): void;
|
|
66
|
+
selectLast(): void;
|
|
37
67
|
moveFocus(direction: "left" | "right"): void;
|
|
38
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Replace the entire value, e.g. from a paste. Values with or without
|
|
70
|
+
* milliseconds are accepted and converted to the configured precision.
|
|
71
|
+
* Invalid values are ignored. Returns true if value was accepted.
|
|
72
|
+
*/
|
|
73
|
+
pasteValue(value: string): boolean;
|
|
39
74
|
private getUnitAtCursorPos;
|
|
75
|
+
private get activeUnit();
|
|
40
76
|
incrementValue(): void;
|
|
41
77
|
decrementValue(): void;
|
|
78
|
+
/**
|
|
79
|
+
* If a full unit is selected, it is cleared and selection moves to the
|
|
80
|
+
* previous unit. If we are midway through entering a unit, the unit is
|
|
81
|
+
* cleared and remains selected.
|
|
82
|
+
*/
|
|
42
83
|
backspace(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Typing the first digit of a unit sets the unit to that digit followed
|
|
86
|
+
* by zeros, e.g '1' => '10' and selection moves to the next digit. If
|
|
87
|
+
* the digit cannot be the first digit of a valid unit, e.g '3' for hours,
|
|
88
|
+
* the unit is zero padded, e.g '03', and selection advances to the next unit.
|
|
89
|
+
* Typing the last digit completes the unit and selection advances to the
|
|
90
|
+
* next unit. A digit which would produce an invalid value is rejected and
|
|
91
|
+
* the input is marked as invalid.
|
|
92
|
+
*/
|
|
43
93
|
update(key: Digit): void;
|
|
44
94
|
private getSelection;
|
|
45
95
|
click(): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HTMLAttributes } from "react";
|
|
2
|
-
import { TimeInputHookProps } from "./useTimeInput";
|
|
3
|
-
|
|
4
|
-
}
|
|
2
|
+
import { TimeInputHookProps, TimeInputMillisecondsProps, TimeInputSecondsProps } from "./useTimeInput";
|
|
3
|
+
type TimeInputHtmlAttributes = Omit<HTMLAttributes<HTMLInputElement>, "defaultValue" | "onChange" | "value"> & Partial<Pick<HTMLInputElement, "placeholder">>;
|
|
4
|
+
export type { TimeInputMillisecondsProps, TimeInputSecondsProps };
|
|
5
|
+
export type TimeInputProps = TimeInputHookProps & TimeInputHtmlAttributes;
|
|
5
6
|
export declare const TimeInput: import("react").ForwardRefExoticComponent<TimeInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
import { type CommitHandler, DateStringISO, type TimeString } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { type CommitHandler, DateStringISO, type TimeString, type TimeStringMillis } from "@vuu-ui/vuu-utils";
|
|
2
2
|
import { ChangeEventHandler, ClipboardEventHandler, FocusEventHandler, KeyboardEventHandler, MouseEventHandler, RefCallback } from "react";
|
|
3
|
-
|
|
3
|
+
interface TimeInputCommonProps {
|
|
4
4
|
date?: Date | DateStringISO;
|
|
5
|
-
defaultValue?: TimeString;
|
|
6
5
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
6
|
+
}
|
|
7
|
+
export interface TimeInputSecondsProps extends TimeInputCommonProps {
|
|
8
|
+
defaultValue?: TimeString;
|
|
9
|
+
/**
|
|
10
|
+
* When true, time is entered and displayed with millisecond
|
|
11
|
+
* precision, hh:mm:ss.SSS
|
|
12
|
+
*/
|
|
13
|
+
milliseconds?: false;
|
|
7
14
|
onCommit: CommitHandler<HTMLInputElement, TimeString>;
|
|
8
15
|
value?: TimeString;
|
|
9
16
|
}
|
|
10
|
-
export
|
|
17
|
+
export interface TimeInputMillisecondsProps extends TimeInputCommonProps {
|
|
18
|
+
defaultValue?: TimeStringMillis;
|
|
19
|
+
/**
|
|
20
|
+
* When true, time is entered and displayed with millisecond
|
|
21
|
+
* precision, hh:mm:ss.SSS
|
|
22
|
+
*/
|
|
23
|
+
milliseconds: true;
|
|
24
|
+
onCommit: CommitHandler<HTMLInputElement, TimeStringMillis>;
|
|
25
|
+
value?: TimeStringMillis;
|
|
26
|
+
}
|
|
27
|
+
export type TimeInputHookProps = TimeInputSecondsProps | TimeInputMillisecondsProps;
|
|
28
|
+
export declare const useTimeInput: ({ defaultValue, milliseconds, onChange, onCommit: onCommitProp, value, }: TimeInputHookProps) => {
|
|
11
29
|
inputRef: RefCallback<HTMLInputElement>;
|
|
12
30
|
eventHandlers: {
|
|
13
|
-
onBlur: (
|
|
31
|
+
onBlur: () => void;
|
|
14
32
|
onDoubleClick: () => void;
|
|
15
33
|
onFocus: FocusEventHandler<HTMLInputElement>;
|
|
16
34
|
onKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
@@ -19,3 +37,4 @@ export declare const useTimeInput: ({ defaultValue, onChange, onCommit, value, }
|
|
|
19
37
|
onPaste: ClipboardEventHandler<HTMLInputElement>;
|
|
20
38
|
};
|
|
21
39
|
};
|
|
40
|
+
export {};
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import { CommitHandler } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { type CommitHandler, EpochTimestamp, type TimeZoneSpec } from "@vuu-ui/vuu-utils";
|
|
2
2
|
import { SingleSelectionValueType } from "../calendar";
|
|
3
3
|
import { DatePickerProps } from "../date-picker";
|
|
4
|
-
export
|
|
4
|
+
export interface VuuDatePickerProps extends Omit<DatePickerProps<SingleSelectionValueType>, "defaultSelectedDate" | "onChange"> {
|
|
5
|
+
/**
|
|
6
|
+
* Invoked when user selects a date from the calendar, or enters a valid date and
|
|
7
|
+
* presses Enter. Value is epoch millis at the start of the selected day, in timeZone.
|
|
8
|
+
*/
|
|
5
9
|
onCommit?: CommitHandler<HTMLElement, number>;
|
|
6
10
|
preserveFocusOnSelect?: boolean;
|
|
7
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The time zone used to convert between epoch values and calendar dates.
|
|
13
|
+
* Defaults to the application default time zone (see setDefaultTimeZone).
|
|
14
|
+
*/
|
|
15
|
+
timeZone?: TimeZoneSpec;
|
|
16
|
+
/**
|
|
17
|
+
* An epoch value (millis as number, or millis/nanos as EpochTimestamp). An
|
|
18
|
+
* alternative to selectedDate, when the date is derived from a timestamp. The
|
|
19
|
+
* calendar date is determined in timeZone.
|
|
20
|
+
*/
|
|
21
|
+
value?: EpochTimestamp | number;
|
|
22
|
+
}
|
|
23
|
+
export declare const VuuDatePicker: ({ className, onSelectionChange, onCommit, preserveFocusOnSelect, selectedDate: selectedDateProp, timeZone, value, ...props }: VuuDatePickerProps) => import("react").JSX.Element;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { CommitHandler, TimeString } from "@vuu-ui/vuu-utils";
|
|
2
1
|
import { HTMLAttributes } from "react";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
2
|
+
import { TimeInputMillisecondsProps, TimeInputSecondsProps } from "../time-input/TimeInput";
|
|
3
|
+
type VuuTimePickerHtmlAttributes = Omit<HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange" | "value">;
|
|
4
|
+
type TimePickerInputProps = "defaultValue" | "milliseconds" | "onChange" | "onCommit" | "value";
|
|
5
|
+
export type VuuTimePickerSecondsProps = Pick<TimeInputSecondsProps, TimePickerInputProps> & VuuTimePickerHtmlAttributes;
|
|
6
|
+
export type VuuTimePickerMillisecondsProps = Pick<TimeInputMillisecondsProps, TimePickerInputProps> & VuuTimePickerHtmlAttributes;
|
|
7
|
+
export type VuuTimePickerProps = VuuTimePickerSecondsProps | VuuTimePickerMillisecondsProps;
|
|
8
|
+
export declare const VuuTimePicker: ({ className, defaultValue, milliseconds, onChange, onCommit, value, ...htmlAttributes }: VuuTimePickerProps) => import("react").JSX.Element;
|
|
9
|
+
export {};
|