Skip to content

Commit 281a869

Browse files
authored
feat(color-picker, color-picker-hex-input): Add input auto commit, blur and auto select enhancements. (#9701)
**Related Issue:** [#9624](#9624) ## Summary Updates `color-picker` and `color-picker-hex-input` to: -Auto select values when clicking on input fields. This applies to all inputs. -Auto commit a valid 6-char (or 8-char if alpha-channel is enabled). -Commit a 3-char (or 4-char with alpha-channel) shorthand value onBlur. -Auto commit channel inputs. If the user deletes the value completely, there will be no change. If left blank, the original value will be restored onBlur. -Increase/decrease channel input value, when the input is cleared and Arrow-up or Arrow-down are pressed. -Blur focused input when clicking anywhere within the component, this applies to all input fields.
1 parent a9464f9 commit 281a869

File tree

4 files changed

+266
-29
lines changed

4 files changed

+266
-29
lines changed

packages/calcite-components/src/components/color-picker-hex-input/color-picker-hex-input.e2e.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,66 @@ describe("calcite-color-picker-hex-input", () => {
108108
expect(await input.getProperty("value")).toBe("#fafafafa");
109109
});
110110

111+
it("commits shorthand hex on blur", async () => {
112+
const defaultHex = "#b33f33";
113+
const editedHex = "#aabbcc";
114+
const page = await newE2EPage();
115+
await page.setContent(`<calcite-color-picker-hex-input value='${defaultHex}'></calcite-color-picker-hex-input>`);
116+
117+
const input = await page.find(`calcite-color-picker-hex-input`);
118+
await selectText(input);
119+
await page.keyboard.type("ab");
120+
await page.keyboard.press("Tab");
121+
await page.waitForChanges();
122+
123+
expect(await input.getProperty("value")).toBe(defaultHex);
124+
125+
await selectText(input);
126+
await page.keyboard.type("abc");
127+
await page.keyboard.press("Tab");
128+
await page.waitForChanges();
129+
130+
expect(await input.getProperty("value")).toBe(editedHex);
131+
132+
await selectText(input);
133+
await page.keyboard.type("abcd");
134+
await page.keyboard.press("Tab");
135+
await page.waitForChanges();
136+
137+
expect(await input.getProperty("value")).toBe(editedHex);
138+
});
139+
140+
it("commits shorthand hexa on blur", async () => {
141+
const defaultHexa = "#b33f33ff";
142+
const editedHexa = "#aabbccdd";
143+
const page = await newE2EPage();
144+
await page.setContent(
145+
`<calcite-color-picker-hex-input alpha-channel value='${defaultHexa}'></calcite-color-picker-hex-input>`,
146+
);
147+
148+
const input = await page.find(`calcite-color-picker-hex-input`);
149+
await selectText(input);
150+
await page.keyboard.type("abc");
151+
await page.keyboard.press("Tab");
152+
await page.waitForChanges();
153+
154+
expect(await input.getProperty("value")).toBe(defaultHexa);
155+
156+
await selectText(input);
157+
await page.keyboard.type("abcd");
158+
await page.keyboard.press("Tab");
159+
await page.waitForChanges();
160+
161+
expect(await input.getProperty("value")).toBe(editedHexa);
162+
163+
await selectText(input);
164+
await page.keyboard.type("abcde");
165+
await page.keyboard.press("Tab");
166+
await page.waitForChanges();
167+
168+
expect(await input.getProperty("value")).toBe(editedHexa);
169+
});
170+
111171
it("normalizes value when initialized", async () => {
112172
const page = await newE2EPage();
113173
await page.setContent("<calcite-color-picker-hex-input value='#f0f'></calcite-color-picker-hex-input>");
@@ -272,11 +332,10 @@ describe("calcite-color-picker-hex-input", () => {
272332
await selectText(input);
273333
const longhandHexWithExtraChars = "bbbbbbbbc";
274334
await page.keyboard.type(longhandHexWithExtraChars);
275-
await page.keyboard.press("Enter");
276335
await page.waitForChanges();
277336

278-
const hexWithPreviousAlphaCharsPreserved = "#bbbbbbdd";
279-
expect(await input.getProperty("value")).toBe(hexWithPreviousAlphaCharsPreserved);
337+
const hexWithAlphaCharsPreserved = "#bbbbbbbb";
338+
expect(await input.getProperty("value")).toBe(hexWithAlphaCharsPreserved);
280339
});
281340

282341
describe("keyboard interaction", () => {

packages/calcite-components/src/components/color-picker-hex-input/color-picker-hex-input.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
hexChar,
1919
hexify,
2020
isLonghandHex,
21+
isShorthandHex,
2122
isValidHex,
2223
normalizeHex,
2324
opacityToAlpha,
@@ -146,8 +147,10 @@ export class ColorPickerHexInput implements LoadableComponent {
146147
const willClearValue = allowEmpty && !inputValue;
147148
const isLonghand = isLonghandHex(hex);
148149

149-
// ensure modified pasted hex values are committed since we prevent default to remove the # char.
150-
this.onHexInputChange();
150+
if (isShorthandHex(hex, this.alphaChannel)) {
151+
// ensure modified pasted hex values are committed since we prevent default to remove the # char.
152+
this.onHexInputChange();
153+
}
151154

152155
if (willClearValue || (isValidHex(hex) && isLonghand)) {
153156
return;
@@ -180,6 +183,10 @@ export class ColorPickerHexInput implements LoadableComponent {
180183
allowEmpty && !internalColor ? "" : this.formatOpacityForInternalInput(internalColor);
181184
};
182185

186+
private onOpacityInputInput = (): void => {
187+
this.onOpacityInputChange();
188+
};
189+
183190
private onHexInputChange = (): void => {
184191
const nodeValue = this.hexInputNode.value;
185192
let value = nodeValue;
@@ -210,7 +217,13 @@ export class ColorPickerHexInput implements LoadableComponent {
210217
this.internalSetValue(value, this.value);
211218
};
212219

213-
private onHexInput = (): void => {
220+
private onInputFocus = (event: Event): void => {
221+
event.type === "calciteInternalInputTextFocus"
222+
? this.hexInputNode.selectText()
223+
: this.opacityInputNode.selectText();
224+
};
225+
226+
private onHexInputInput = (): void => {
214227
const hexInputValue = `#${this.hexInputNode.value}`;
215228
const oldValue = this.value;
216229

@@ -228,7 +241,7 @@ export class ColorPickerHexInput implements LoadableComponent {
228241
const { key } = event;
229242
const composedPath = event.composedPath();
230243

231-
if (key === "Tab" || key === "Enter") {
244+
if ((key === "Tab" && isShorthandHex(value, this.alphaChannel)) || key === "Enter") {
232245
if (composedPath.includes(hexInputNode)) {
233246
this.onHexInputChange();
234247
} else {
@@ -326,10 +339,11 @@ export class ColorPickerHexInput implements LoadableComponent {
326339
<calcite-input-text
327340
class={CSS.hexInput}
328341
label={messages?.hex || hexLabel}
329-
maxLength={6}
342+
maxLength={this.alphaChannel ? 8 : 6}
330343
onCalciteInputTextChange={this.onHexInputChange}
331-
onCalciteInputTextInput={this.onHexInput}
344+
onCalciteInputTextInput={this.onHexInputInput}
332345
onCalciteInternalInputTextBlur={this.onHexInputBlur}
346+
onCalciteInternalInputTextFocus={this.onInputFocus}
333347
onKeyDown={this.onInputKeyDown}
334348
onPaste={this.onHexInputPaste}
335349
prefixText="#"
@@ -347,8 +361,9 @@ export class ColorPickerHexInput implements LoadableComponent {
347361
min={OPACITY_LIMITS.min}
348362
numberButtonType="none"
349363
numberingSystem={this.numberingSystem}
350-
onCalciteInputNumberChange={this.onOpacityInputChange}
364+
onCalciteInputNumberInput={this.onOpacityInputInput}
351365
onCalciteInternalInputNumberBlur={this.onOpacityInputBlur}
366+
onCalciteInternalInputNumberFocus={this.onInputFocus}
352367
onKeyDown={this.onInputKeyDown}
353368
ref={this.storeOpacityInputRef}
354369
scale={inputScale}

packages/calcite-components/src/components/color-picker/color-picker.e2e.ts

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,14 @@ describe("calcite-color-picker", () => {
196196
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
197197
await selectText(channelInput);
198198
await channelInput.type("254");
199-
await channelInput.press("Enter");
200199
await page.waitForChanges();
201-
expect(changeSpy).toHaveReceivedEventTimes(4);
202-
expect(inputSpy).toHaveReceivedEventTimes(4);
200+
expect(changeSpy).toHaveReceivedEventTimes(6);
201+
expect(inputSpy).toHaveReceivedEventTimes(6);
203202

204203
// change by clicking stored color
205204
await (await page.find(`calcite-color-picker >>> .${CSS.savedColor}`)).click();
206-
expect(changeSpy).toHaveReceivedEventTimes(5);
207-
expect(inputSpy).toHaveReceivedEventTimes(5);
205+
expect(changeSpy).toHaveReceivedEventTimes(7);
206+
expect(inputSpy).toHaveReceivedEventTimes(7);
208207

209208
// change by dragging color field thumb
210209
const mouseDragSteps = 10;
@@ -222,8 +221,8 @@ describe("calcite-color-picker", () => {
222221
await page.mouse.up();
223222
await page.waitForChanges();
224223

225-
expect(changeSpy).toHaveReceivedEventTimes(6);
226-
expect(inputSpy.length).toBeGreaterThan(6); // input event fires more than once
224+
expect(changeSpy).toHaveReceivedEventTimes(8);
225+
expect(inputSpy.length).toBeGreaterThan(8); // input event fires more than once
227226

228227
// change by dragging hue slider thumb
229228
[hueScopeX, hueScopeY] = await getElementXY(page, "calcite-color-picker", `.${CSS.hueScope}`);
@@ -235,7 +234,7 @@ describe("calcite-color-picker", () => {
235234
await page.mouse.up();
236235
await page.waitForChanges();
237236

238-
expect(changeSpy).toHaveReceivedEventTimes(7);
237+
expect(changeSpy).toHaveReceivedEventTimes(9);
239238
expect(inputSpy.length).toBeGreaterThan(previousInputEventLength + 1); // input event fires more than once
240239

241240
previousInputEventLength = inputSpy.length;
@@ -246,10 +245,112 @@ describe("calcite-color-picker", () => {
246245
picker.setProperty("value", "#fff");
247246
await page.waitForChanges();
248247

249-
expect(changeSpy).toHaveReceivedEventTimes(7);
248+
expect(changeSpy).toHaveReceivedEventTimes(9);
250249
expect(inputSpy.length).toBe(previousInputEventLength);
251250
});
252251

252+
it("increments channel's value by 1 when clearing input and pressing ArrowUp. Same should apply to other channel inputs", async () => {
253+
const page = await newE2EPage();
254+
await page.setContent("<calcite-color-picker></calcite-color-picker>");
255+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
256+
const currentValue = await channelInput.getProperty("value");
257+
258+
await selectText(channelInput);
259+
await page.keyboard.press("Backspace");
260+
await page.keyboard.press("ArrowUp");
261+
await page.waitForChanges();
262+
263+
expect(await channelInput.getProperty("value")).toBe(`${Number(currentValue) + 1}`);
264+
});
265+
266+
it("decrements channel's value by 1 when clearing input and pressing ArrowDown. Same should apply to other channel inputs", async () => {
267+
const page = await newE2EPage();
268+
await page.setContent("<calcite-color-picker value='#b33f33'></calcite-color-picker>");
269+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
270+
const currentValue = await channelInput.getProperty("value");
271+
272+
await selectText(channelInput);
273+
await page.keyboard.press("Backspace");
274+
await page.keyboard.press("ArrowDown");
275+
await page.waitForChanges();
276+
277+
expect(await channelInput.getProperty("value")).toBe(`${Number(currentValue) - 1}`);
278+
});
279+
280+
it("prevents channel's value from going over its limit when clearing input and pressing ArrowUp. Same should apply to other channel inputs", async () => {
281+
const page = await newE2EPage();
282+
await page.setContent("<calcite-color-picker value='#ffffff'></calcite-color-picker>");
283+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
284+
285+
await selectText(channelInput);
286+
await page.keyboard.press("Backspace");
287+
await page.keyboard.press("ArrowUp");
288+
await page.waitForChanges();
289+
290+
expect(await channelInput.getProperty("value")).toBe("255");
291+
});
292+
293+
it("prevents channel's value from being less than 0 when clearing input and pressing ArrowDown. Same should apply to other channel inputs", async () => {
294+
const page = await newE2EPage();
295+
await page.setContent("<calcite-color-picker></calcite-color-picker>");
296+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
297+
298+
await selectText(channelInput);
299+
await page.keyboard.press("Backspace");
300+
await page.keyboard.press("ArrowDown");
301+
await page.waitForChanges();
302+
303+
expect(await channelInput.getProperty("value")).toBe("0");
304+
});
305+
306+
it("restores original channel value when input is cleared and blur is triggered. Same should apply to other channel inputs", async () => {
307+
const page = await newE2EPage();
308+
await page.setContent("<calcite-color-picker></calcite-color-picker>");
309+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
310+
const currentValue = await channelInput.getProperty("value");
311+
312+
await selectText(channelInput);
313+
await page.keyboard.press("Backspace");
314+
await page.keyboard.press("Tab");
315+
await page.waitForChanges();
316+
317+
expect(await channelInput.getProperty("value")).toBe(currentValue);
318+
});
319+
320+
it("auto commits channel value when typing. Same should apply to other channel inputs", async () => {
321+
const page = await newE2EPage();
322+
await page.setContent("<calcite-color-picker></calcite-color-picker>");
323+
324+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
325+
const picker = await page.find("calcite-color-picker");
326+
const changeSpy = await picker.spyOnEvent("calciteColorPickerChange");
327+
328+
await selectText(channelInput);
329+
await page.keyboard.type("123");
330+
await page.waitForChanges();
331+
332+
expect(changeSpy).toHaveReceivedEventTimes(3);
333+
expect(await channelInput.getProperty("value")).toBe("123");
334+
});
335+
336+
it("blurs focused input when clicking anywhere within the component. It should apply to all inputs", async () => {
337+
const page = await newE2EPage();
338+
await page.setContent("<calcite-color-picker></calcite-color-picker>");
339+
340+
const channelInput = await page.find(`calcite-color-picker >>> .${CSS.channel}`);
341+
const currentValue = await channelInput.getProperty("value");
342+
const picker = await page.find("calcite-color-picker");
343+
const blurSpy = await picker.spyOnEvent("calciteInternalInputNumberBlur");
344+
345+
await selectText(channelInput);
346+
await page.keyboard.press("Backspace");
347+
await page.mouse.click(0, 0);
348+
await page.waitForChanges();
349+
350+
expect(blurSpy).toHaveReceivedEventTimes(1);
351+
expect(await channelInput.getProperty("value")).toBe(currentValue);
352+
});
353+
253354
it("does not emit on initialization", async () => {
254355
const page = await newProgrammaticE2EPage();
255356

0 commit comments

Comments
 (0)