Skip to content

Commit 2c7982e

Browse files
committed
fix tsc issue with template types
1 parent d67bfe5 commit 2c7982e

File tree

7 files changed

+87
-173
lines changed

7 files changed

+87
-173
lines changed

vuu-ui/packages/vuu-ui-controls/src/time-input/MaskedInput.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
122122
return this.#value.slice(6, 8) as Seconds;
123123
}
124124

125+
get value() {
126+
return this.#value;
127+
}
128+
125129
/**
126130
* Setting the value this way invokes 'controlled' mode
127131
*/

vuu-ui/packages/vuu-ui-controls/src/time-input/TimeInput.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>(
2626
onChange,
2727
onCommit,
2828
placeholder = "hh:mm:ss",
29-
showTemplateWhileEditing,
3029
value,
3130
...htmlAttributes
3231
},
@@ -45,7 +44,6 @@ export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>(
4544
defaultValue,
4645
onChange,
4746
onCommit,
48-
showTemplateWhileEditing,
4947
value,
5048
});
5149

vuu-ui/packages/vuu-ui-controls/src/time-input/useTimeInput.ts

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,36 @@ export interface TimeInputHookProps {
2525
defaultValue?: TimeString;
2626
onChange?: (time: TimeString) => void;
2727
onCommit: CommitHandler<HTMLInputElement, Date>;
28-
showTemplateWhileEditing?: boolean;
2928
value?: TimeString;
3029
}
3130

3231
export const useTimeInput = ({
3332
defaultValue,
3433
onChange,
3534
onCommit,
36-
showTemplateWhileEditing = true,
3735
value,
3836
}: TimeInputHookProps) => {
3937
console.log(`useTimeInput defaultValue = ${defaultValue} value=${value}`);
4038
const mousedDownRef = useRef(false);
41-
const maskedInput = useMemo<MaskedInput>(() => {
42-
const mask = new MaskedInput(defaultValue, null, showTemplateWhileEditing);
43-
mask.on("change", (value) => {
44-
onChange?.(value);
45-
});
46-
return mask;
47-
}, [defaultValue, onChange, showTemplateWhileEditing]);
48-
39+
const maskedInputRef = useRef<MaskedInput | undefined>(undefined);
4940
useMemo(() => {
50-
if (isValidTimeString(value)) {
51-
maskedInput.value = value;
41+
if (maskedInputRef.current === undefined) {
42+
maskedInputRef.current = new MaskedInput(defaultValue, null);
43+
maskedInputRef.current.on("change", (value) => {
44+
onChange?.(value);
45+
});
5246
}
53-
}, [maskedInput, value]);
5447

55-
const setInputEl = useCallback<RefCallback<HTMLInputElement>>(
56-
(el) => {
57-
if (el) {
58-
maskedInput.input = el;
59-
}
60-
},
61-
[maskedInput],
62-
);
48+
if (isValidTimeString(value) && value !== maskedInputRef.current?.value) {
49+
maskedInputRef.current.value = value;
50+
}
51+
}, [defaultValue, onChange, value]);
52+
53+
const setInputEl = useCallback<RefCallback<HTMLInputElement>>((el) => {
54+
if (el && maskedInputRef.current) {
55+
maskedInputRef.current.input = el;
56+
}
57+
}, []);
6358
const back = useRef(false);
6459

6560
const commitValue = useCallback<CommitHandler<HTMLInputElement, string>>(
@@ -79,49 +74,51 @@ export const useTimeInput = ({
7974

8075
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLInputElement>>(
8176
(e) => {
82-
console.log(`handleKeyDown ${e.key} cursorPos ${maskedInput.cursorPos}`);
83-
if (e.key === "Backspace") {
84-
maskedInput.backspace();
85-
back.current = true;
86-
} else if (isDigit(e.key)) {
87-
maskedInput.update(e.key);
88-
} else if (e.key === "ArrowLeft") {
89-
maskedInput.moveFocus("left");
90-
} else if (e.key === "ArrowRight") {
91-
maskedInput.moveFocus("right");
92-
} else if (e.key === "ArrowUp") {
93-
maskedInput.incrementValue();
94-
} else if (e.key === "ArrowDown") {
95-
maskedInput.decrementValue();
96-
} else if (e.key === "v" && e.metaKey) {
97-
// keyboard paste, do not prevent default
98-
return;
99-
} else if (e.key === "Tab") {
100-
return;
101-
} else if (e.key === "Enter") {
102-
commitValue(e, maskedInput.value);
77+
const { current: maskedInput } = maskedInputRef;
78+
if (maskedInput) {
79+
if (e.key === "Backspace") {
80+
maskedInput.backspace();
81+
back.current = true;
82+
} else if (isDigit(e.key)) {
83+
maskedInput.update(e.key);
84+
} else if (e.key === "ArrowLeft") {
85+
maskedInput.moveFocus("left");
86+
} else if (e.key === "ArrowRight") {
87+
maskedInput.moveFocus("right");
88+
} else if (e.key === "ArrowUp") {
89+
maskedInput.incrementValue();
90+
} else if (e.key === "ArrowDown") {
91+
maskedInput.decrementValue();
92+
} else if (e.key === "v" && e.metaKey) {
93+
// keyboard paste, do not prevent default
94+
return;
95+
} else if (e.key === "Tab") {
96+
return;
97+
} else if (e.key === "Enter") {
98+
commitValue(e, maskedInput.value);
99+
}
103100
}
104101
e.preventDefault();
105102
},
106-
[commitValue, maskedInput],
103+
[commitValue],
107104
);
108105

109106
const handleClick = useCallback(() => {
110107
// maskedInput.click();
111108
}, []);
112109

113110
const handleDoubleClick = useCallback(() => {
114-
maskedInput.doubleClick();
115-
}, [maskedInput]);
111+
maskedInputRef.current?.doubleClick();
112+
}, []);
116113

117114
const handlePaste = useCallback<ClipboardEventHandler<HTMLInputElement>>(
118115
(e) => {
119116
const value = e.clipboardData.getData("text");
120117
if (isValidTimeString(value)) {
121-
maskedInput.pasteValue(value);
118+
maskedInputRef.current?.pasteValue(value);
122119
}
123120
},
124-
[maskedInput],
121+
[],
125122
);
126123

127124
const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
@@ -135,9 +132,9 @@ export const useTimeInput = ({
135132
if (mousedDownRef.current) {
136133
mousedDownRef.current = false;
137134
} else {
138-
maskedInput.focus();
135+
maskedInputRef.current?.focus();
139136
}
140-
}, [maskedInput]);
137+
}, []);
141138

142139
const handleMouseDown = useCallback<MouseEventHandler>((e) => {
143140
mousedDownRef.current = true;
@@ -157,15 +154,15 @@ export const useTimeInput = ({
157154
if (input.selectionStart === 0 && input.selectionEnd === 8) {
158155
console.log("full select");
159156
}
160-
maskedInput.click();
157+
maskedInputRef.current?.click();
161158
},
162-
[maskedInput],
159+
[],
163160
);
164161

165162
return {
166163
inputRef: setInputEl,
167164
eventHandlers: {
168-
onBlur: maskedInput.blur,
165+
onBlur: maskedInputRef.current?.blur,
169166
onChange: handleChange,
170167
onClick: handleClick,
171168
onDoubleClick: handleDoubleClick,

vuu-ui/packages/vuu-ui-controls/src/vuu-time-picker/VuuTimePicker.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,7 @@ export const VuuTimePicker = ({
3737

3838
return (
3939
<div {...htmlAttributes} className={cx(classBase, className)}>
40-
<TimeInput
41-
defaultValue={defaultValue}
42-
onCommit={handleCommit}
43-
showTemplateWhileEditing={false}
44-
/>
40+
<TimeInput defaultValue={defaultValue} onCommit={handleCommit} />
4541
</div>
4642
);
4743
};

vuu-ui/packages/vuu-ui-controls/test/MaskedInput.test.ts

Lines changed: 20 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -10,115 +10,48 @@ const MockEl = (setSelectionRange?: (from: number, to: number) => void): any =>
1010
})();
1111

1212
describe("MaskedInput", () => {
13-
it("is empty before interaction", () => {
14-
const maskedInput = new MaskedInput("", MockEl());
15-
expect(maskedInput.value).toEqual("");
13+
describe("WHEN uncontrolled", () => {
14+
it("retains initial value before interaction", () => {
15+
const maskedInput = new MaskedInput("00:00:00", MockEl());
16+
expect(maskedInput.value).toEqual("00:00:00");
17+
});
1618
});
1719

18-
describe("WHEN showTemplateWhileEditing is true", () => {
19-
describe("AND no value set and focus received", () => {
20-
it("THEN value = template and cursor position = 0", () => {
21-
vi.useFakeTimers();
22-
23-
const maskedInput = new MaskedInput("", MockEl());
24-
maskedInput.focus();
25-
expect(maskedInput.value).toEqual("hh:mm:ss");
26-
27-
vi.advanceTimersToNextTimer();
20+
describe("WHEN focus received", () => {
21+
it("THEN value = '00:00:00' and cursor position = 0", () => {
22+
vi.useFakeTimers();
2823

29-
expect(maskedInput.cursorPos).toEqual(0);
30-
expect(maskedInput.selectionStart).toEqual(0);
31-
expect(maskedInput.selectionEnd).toEqual(2);
32-
});
24+
const maskedInput = new MaskedInput("00:00:00", MockEl());
25+
maskedInput.focus();
26+
vi.advanceTimersToNextTimer();
27+
expect(maskedInput.cursorPos).toEqual(0);
28+
expect(maskedInput.selectionStart).toEqual(0);
29+
expect(maskedInput.selectionEnd).toEqual(2);
3330
});
3431

35-
describe("AND focus received and zero pressed 6 times", () => {
36-
it("THEN value is edited correctly, cursor positions are correct and final value = '00:00:00'", () => {
37-
const maskedInput = new MaskedInput("", MockEl());
32+
describe("AND 1-6 key pressed", () => {
33+
it("THEN value is edited correctly, cursor positions are correct and final value = '12:34:56'", () => {
34+
const maskedInput = new MaskedInput("00:00:00", MockEl());
3835
vi.useFakeTimers();
39-
4036
maskedInput.focus();
41-
4237
vi.advanceTimersToNextTimer();
43-
4438
// prettier-ignore
45-
const expectedValues = ["0h:mm:ss","00:mm:ss","00:0m:ss","00:00:ss","00:00:0s","00:00:00"]
39+
const expectedValues = ["10:00:00","12:00:00","12:30:00","12:34:00","12:34:50","12:34:56"]
4640
const expectedCursorPositions = [1, 3, 4, 6, 7, 6];
4741
for (let i = 0; i < 6; i++) {
48-
maskedInput.update("0");
42+
maskedInput.update(String(i + 1) as Digit);
4943
expect(maskedInput.value).toEqual(expectedValues[i]);
5044
expect(maskedInput.cursorPos).toEqual(expectedCursorPositions[i]);
5145
}
5246
});
53-
describe("AND backspace pressed 3 times", () => {
54-
it("THEN value = template and cursor position = 0", () => {
55-
const maskedInput = new MaskedInput("", MockEl());
56-
vi.useFakeTimers();
57-
58-
maskedInput.focus();
59-
60-
vi.advanceTimersToNextTimer();
61-
62-
// prettier-ignore
63-
const expectedValues = ["00:00:ss","00:mm:ss", "hh:mm:ss"]
64-
const expectedCursorPositions = [3, 0, 0];
65-
for (let i = 0; i < 6; i++) {
66-
maskedInput.update("0");
67-
}
68-
for (let i = 0; i < 3; i++) {
69-
maskedInput.backspace();
70-
expect(maskedInput.cursorPos).toEqual(expectedCursorPositions[i]);
71-
expect(maskedInput.value).toEqual(expectedValues[i]);
72-
}
73-
});
74-
});
7547
});
76-
});
77-
});
78-
describe("WHEN showTemplateWhileEditing is false", () => {
79-
describe("AND no value set and focus received", () => {
80-
it("THEN value = '00:00:00' and cursor position = 0", () => {
81-
vi.useFakeTimers();
82-
83-
const maskedInput = new MaskedInput("", MockEl(), false);
84-
maskedInput.focus();
85-
expect(maskedInput.value).toEqual("00:00:00");
86-
87-
vi.advanceTimersToNextTimer();
88-
89-
expect(maskedInput.cursorPos).toEqual(0);
90-
expect(maskedInput.selectionStart).toEqual(0);
91-
expect(maskedInput.selectionEnd).toEqual(2);
92-
});
93-
});
94-
95-
describe("AND focus received and 1-6 key pressed", () => {
96-
it("THEN value is edited correctly, cursor positions are correct and final value = '12:34:56'", () => {
97-
const maskedInput = new MaskedInput("", MockEl(), false);
98-
vi.useFakeTimers();
99-
100-
maskedInput.focus();
10148

102-
vi.advanceTimersToNextTimer();
103-
104-
// prettier-ignore
105-
const expectedValues = ["10:00:00","12:00:00","12:30:00","12:34:00","12:34:50","12:34:56"]
106-
const expectedCursorPositions = [1, 3, 4, 6, 7, 6];
107-
for (let i = 0; i < 6; i++) {
108-
maskedInput.update(String(i + 1) as Digit);
109-
expect(maskedInput.value).toEqual(expectedValues[i]);
110-
expect(maskedInput.cursorPos).toEqual(expectedCursorPositions[i]);
111-
}
112-
});
11349
describe("AND backspace pressed 3 times", () => {
11450
it("THEN value = template and cursor position = 0", () => {
115-
const maskedInput = new MaskedInput("", MockEl(), false);
51+
const maskedInput = new MaskedInput("00:00:00", MockEl());
11652
vi.useFakeTimers();
117-
11853
maskedInput.focus();
119-
12054
vi.advanceTimersToNextTimer();
121-
12255
// prettier-ignore
12356
const expectedValues = ["12:34:00","12:00:00", "00:00:00"]
12457
const expectedCursorPositions = [3, 0, 0];

vuu-ui/packages/vuu-utils/src/date/date-utils.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ export function toCalendarDate(d: Date) {
44
return new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
55
}
66

7-
type oneToFive = 1 | 2 | 3 | 4 | 5;
8-
type zeroToFive = 0 | oneToFive;
9-
type sixToNine = 6 | 7 | 8 | 9;
10-
type zeroToNine = zeroToFive | sixToNine;
11-
type oneToNine = oneToFive | sixToNine;
12-
7+
export type oneToFive = 1 | 2 | 3 | 4 | 5;
8+
export type zeroToFive = 0 | oneToFive;
9+
export type sixToNine = 6 | 7 | 8 | 9;
10+
export type zeroToNine = zeroToFive | sixToNine;
11+
export type oneToNine = oneToFive | sixToNine;
1312
export type TimeUnit = "hours" | "minutes" | "seconds";
14-
1513
export type Hours = `${0 | 1}${zeroToNine}` | `2${0 | 1 | 2 | 3}`;
1614
export type Minutes = `${zeroToFive}${zeroToNine}`;
1715
export type Seconds = `${zeroToFive}${zeroToNine}`;
@@ -22,7 +20,10 @@ export type TimeUnitValue<T extends TimeUnit> = T extends "hours"
2220
? Minutes
2321
: Seconds;
2422

25-
export type TimeString = `${Hours}:${Minutes}:${Seconds}`;
23+
// This should work, works fine in TypeScript playground, but crashes tsc
24+
// export type TimeString = `${Hours}:${Minutes}:${Seconds}`;
25+
export type TimeString =
26+
`${number}${number}:${number}${number}:${number}${number}`;
2627

2728
type YYYY = `19${zeroToNine}${zeroToNine}` | `20${zeroToNine}${zeroToNine}`;
2829
type MM = `0${oneToNine}` | `1${0 | 1 | 2}`;

0 commit comments

Comments
 (0)