Skip to content

Commit 4ed2f32

Browse files
committed
refactor onChange for TimeInput, add styling for ColumnFilter
1 parent 11f3e60 commit 4ed2f32

17 files changed

Lines changed: 322 additions & 107 deletions

File tree

vuu-ui/packages/vuu-data-react/src/data-editing/get-data-item-edit-control.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export interface DataItemEditControlProps {
2626
* A table column or form field Descriptor.
2727
*/
2828
dataDescriptor: DataValueDescriptor;
29-
defaultValue?: string | number | readonly string[];
30-
value?: string | number | readonly string[]; //TODO - will be used in VuuTimePicker
3129
errorMessage?: string;
3230
onCommit: CommitHandler<HTMLElement>;
3331
table?: TableSchemaTable;
@@ -41,7 +39,6 @@ export const getDataItemEditControl = ({
4139
className,
4240
commitWhenCleared,
4341
dataDescriptor,
44-
defaultValue,
4542
errorMessage,
4643
onCommit,
4744
table,
@@ -63,13 +60,17 @@ export const getDataItemEditControl = ({
6360
/>
6461
);
6562
} else if (isTimeDataValue(dataDescriptor)) {
66-
return (
67-
<VuuTimePicker
68-
className={className}
69-
defaultValue={asTimeString(defaultValue, true)}
70-
onCommit={handleCommitNumber}
71-
/>
72-
);
63+
if (InputProps?.inputProps) {
64+
const { value, onChange } = InputProps.inputProps;
65+
return (
66+
<VuuTimePicker
67+
className={className}
68+
value={asTimeString(value, true)}
69+
onChange={onChange}
70+
onCommit={onCommit}
71+
/>
72+
);
73+
}
7374
} else if (isDateTimeDataValue(dataDescriptor)) {
7475
return (
7576
<VuuDatePicker className={className} onCommit={handleCommitNumber} />

vuu-ui/packages/vuu-filters/src/column-filter/ColumnFilter.css

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
.vuuColumnFilter {
22
border: solid 1px var(--salt-editable-borderColor);
3-
border-radius: 6px;
43
max-width: 200px;
4+
padding-left: 4px;
5+
padding-right: 4px;
6+
width: var(--vuuColumnFilter-width, fit-content);
57

68

79
.vuuColumnFilter-rangeHigh {
@@ -14,9 +16,26 @@
1416
position: absolute;
1517
background: var(--salt-separable-primary-borderColor);
1618
/* Accomodate button border */
17-
left: 0;
18-
top: calc(-1 * var(--salt-spacing-100));
19-
bottom: calc(-1 * var(--salt-spacing-100));
19+
left: calc(-1 * var(--salt-spacing-100));
20+
top: -1px;
21+
bottom: -1px;
22+
}
23+
24+
.vuuTimePicker {
25+
border-radius: 6px;
26+
}
27+
28+
.vuuTimePicker + .vuuTimePicker {
29+
margin-left: var(--salt-spacing-100);
30+
}
31+
32+
.vuuTimeInput {
33+
border: none;
34+
outline: none;
35+
}
36+
37+
.vuuTimeInput:focus-visible {
38+
outline: none;
2039
}
2140

2241
}

vuu-ui/packages/vuu-filters/src/column-filter/ColumnFilter.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import columnFilterCss from "./ColumnFilter.css";
1515
import { getDataItemEditControl } from "@vuu-ui/vuu-data-react";
1616
import { ForwardedRef, forwardRef, ReactElement, useMemo } from "react";
1717
import { VuuTable } from "@vuu-ui/vuu-protocol-types";
18-
import { assertValidOperator, assertValidValue, Operator, useColumnFilter } from "./useColumnFilter";
18+
import {
19+
assertValidOperator,
20+
assertValidValue,
21+
Operator,
22+
useColumnFilter,
23+
} from "./useColumnFilter";
1924
import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
2025

2126
const classBase = "vuuColumnFilter";
@@ -126,10 +131,9 @@ export const ColumnFilter = forwardRef(function ColumnFilter(
126131
</Menu>
127132
) : null}
128133
{getDataItemEditControl({
129-
InputProps: { inputProps },
134+
InputProps: { inputProps },
130135
dataDescriptor: column,
131136
onCommit: handleCommit,
132-
defaultValue: Array.isArray(filterValue) ? filterValue[0] : filterValue,
133137
table,
134138
})}
135139
{op === "between"
@@ -138,9 +142,6 @@ export const ColumnFilter = forwardRef(function ColumnFilter(
138142
InputProps: { inputProps: rangeInputProps },
139143
dataDescriptor: column,
140144
onCommit: handleRangeCommit,
141-
defaultValue: Array.isArray(filterValue)
142-
? filterValue[1]
143-
: filterValue,
144145
table,
145146
})
146147
: null}

vuu-ui/packages/vuu-filters/src/column-filter/useColumnFilter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const useColumnFilter = ({
8989

9090
const handleCommit = useCallback<CommitHandler<HTMLElement>>(
9191
(e, newValue) => {
92+
console.log(`[useColumnFilter] handleCommit ${newValue}`);
9293
if (Array.isArray(filterValue.current)) {
9394
filterValue.current = [
9495
newValue as FilterValue,

vuu-ui/packages/vuu-theme/css/components/components.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@import url(dropdown.css);
77
@import url(dialog.css);
88
@import url(filters.css);
9+
@import url(form-field.css);
910
@import url(header.css);
1011
@import url(icon.css);
1112
@import url(input.css);
@@ -16,6 +17,7 @@
1617
@import url(palette.css);
1718
@import url(save-layout-panel.css);
1819
@import url(scrollable.css);
20+
@import url(segmented-button-group.css);
1921
@import url(shell.css);
2022
@import url(split-button.css);
2123
@import url(splitter.css);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.salt-theme.vuu-theme {
2+
3+
.saltFormField:has(.vuuColumnFilter){
4+
width: fit-content;
5+
}
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.salt-theme.vuu-theme {
2+
3+
.saltSegmentedButtonGroup {
4+
border-radius: 4px;
5+
padding: 1px;
6+
}
7+
}

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

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
createSyntheticEvent,
23
decrementTimeUnitValue,
34
EventEmitter,
45
Hours,
@@ -13,6 +14,7 @@ import {
1314
zeroTime,
1415
zeroTimeUnit,
1516
} from "@vuu-ui/vuu-utils";
17+
import { ChangeEventHandler } from "react";
1618

1719
export type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
1820

@@ -32,12 +34,12 @@ const FullSelection: Selection = { end: 0, start: 8 };
3234
const CursorAtEnd: Selection = { end: 8, start: 8 };
3335

3436
export type MaskedInputEvents = {
35-
change: (value: TimeString) => void;
37+
change: ChangeEventHandler<HTMLInputElement>;
3638
};
3739

3840
export class MaskedInput extends EventEmitter<MaskedInputEvents> {
3941
#controlled = false;
40-
#input: HTMLInputElement | null;
42+
#input: HTMLInputElement | null = null;
4143
#isFocused = false;
4244
#isIncrementalChange = false;
4345
#selectionStart = -1;
@@ -51,14 +53,35 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
5153
inputEl: HTMLInputElement | null = null,
5254
) {
5355
super();
54-
this.#input = inputEl;
56+
if (inputEl) {
57+
this.input = inputEl;
58+
}
5559
this.#value = defaultValue ?? zeroTime;
5660
}
5761

5862
set input(el: HTMLInputElement) {
63+
if (this.#input) {
64+
throw Error(
65+
"MaskedInput cannot be reused, create a new instance for a new input",
66+
);
67+
}
5968
this.#input = el;
69+
el.addEventListener("change", this.emitSyntheticChangeEvent);
6070
}
6171

72+
/**
73+
* The change event is fired programatically. This will only be handled
74+
* by a native event handler ( not a React handler). We handle this event
75+
* and convert to a React (Synthetic) event.
76+
*/
77+
private emitSyntheticChangeEvent = (e: Event) => {
78+
const syntheticEvent = createSyntheticEvent(
79+
e,
80+
) as React.ChangeEvent<HTMLInputElement>;
81+
82+
this.emit("change", syntheticEvent);
83+
};
84+
6285
get cursorPos() {
6386
return this.selectionStart;
6487
}
@@ -92,11 +115,23 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
92115
if (!this.#controlled) {
93116
this.#isIncrementalChange = false;
94117
this.#value = value;
95-
if (this.#input) {
96-
this.#input.value = value;
97-
}
98118
}
99-
this.emit("change", value);
119+
120+
if (this.#input) {
121+
// HM this updateds the input value, even if we are controlled.
122+
// Thats not right, but if we don't update it, the event will
123+
// not carry thwe right value. I don;t thibnk we can simulate
124+
// the correct behaviour
125+
this.#input.value = value;
126+
// this triggers the native change event, which we convert
127+
// // to synthetic event and emit in input setter above
128+
this.#input.dispatchEvent(
129+
new Event("change", {
130+
bubbles: true,
131+
composed: true,
132+
}),
133+
);
134+
}
100135
}
101136

102137
private setUnitValue(unit: TimeUnit, value: Hours | Minutes | Seconds) {
@@ -132,31 +167,31 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
132167
this.#isIncrementalChange = false;
133168

134169
requestAnimationFrame(() => {
135-
if (isValidTimeString(value) && !isIncremental) {
136-
this.advanceSelection();
137-
} else {
170+
if (isIncremental) {
138171
this.restoreSelection();
172+
} else {
173+
this.advanceSelection();
139174
}
140175
});
141176
}
142177
}
143178

144179
clear(unit: TimeUnit) {
145180
if (this.#input) {
181+
let newValue = this.#value;
146182
if (unit === "hours") {
147-
this.#value = zeroTimeUnit.concat(this.#value.slice(2)) as TimeString;
183+
newValue = zeroTimeUnit.concat(this.#value.slice(2)) as TimeString;
148184
} else if (unit === "minutes") {
149-
this.#value = this.#value
185+
newValue = this.#value
150186
.slice(0, 3)
151187
.concat(zeroTimeUnit)
152188
.concat(this.#value.slice(5)) as TimeString;
153189
} else if (unit === "seconds") {
154-
this.#value = this.#value
155-
.slice(0, 6)
156-
.concat(zeroTimeUnit) as TimeString;
190+
newValue = this.#value.slice(0, 6).concat(zeroTimeUnit) as TimeString;
191+
}
192+
if (newValue !== this.#value) {
193+
this.setValue(newValue);
157194
}
158-
this.#input.value = this.#value;
159-
this.emit("change", this.#value as TimeString);
160195
}
161196
}
162197

@@ -306,11 +341,9 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
306341
.slice(0, cursorPos - offset)
307342
.concat(zeroTime.slice(cursorPos - offset, cursorPos))
308343
.concat(this.#value.slice(cursorPos)) as TimeString;
309-
this.#value = newValue;
310344
this.selectionStart -= offset;
311345
this.selectionEnd -= offset;
312-
this.#input.value = this.#value;
313-
this.emit("change", this.#value as TimeString);
346+
this.setValue(newValue);
314347

315348
requestAnimationFrame(() => {
316349
this.#input?.setSelectionRange(
@@ -372,6 +405,7 @@ export class MaskedInput extends EventEmitter<MaskedInputEvents> {
372405

373406
click() {
374407
if (this.#input) {
408+
this.#isFocused = true;
375409
const selection = this.getSelection();
376410
if (selection === NullSelection) {
377411
this.select("hours");
Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1+
.salt-density-high .vuuTimeInput {
2+
--time-input-width: 60px;
3+
}
4+
.salt-density-medium .vuuTimeInput {
5+
--time-input-width: 70px;
6+
}
7+
.salt-density-low .vuuTimeInput {
8+
--time-input-width: 80px;
9+
}
10+
.salt-density-touch .vuuTimeInput {
11+
--time-input-width: 90px;
12+
}
13+
114
.vuuTimeInput {
15+
border-width: 1px;
216
font-family: var(--salt-typography-fontFamily);
3-
width: 80px;
17+
width: var(--vuuTimeInput-width, var(--time-input-width));
418

519
&:focus::-webkit-input-placeholder {
620
color: transparent;
721
}
822

923
}
1024

11-
.invalid::selection {
12-
background-color: red !important;
13-
color: white !important;
14-
}
25+
.invalid {
26+
border-color: red;
27+
}
28+

0 commit comments

Comments
 (0)