Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(color-picker): add clearable prop and deprecate allowEmpty #8910

Merged
merged 8 commits into from
Mar 19, 2024
14 changes: 8 additions & 6 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,9 +978,6 @@ export namespace Components {
"setFocus": () => Promise<void>;
}
interface CalciteColorPicker {
/**
* When `true`, an empty color (`null`) will be allowed as a `value`. When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/
"allowEmpty": boolean;
/**
* When `true`, the component will allow updates to the color's alpha value.
Expand All @@ -990,6 +987,10 @@ export namespace Components {
* When `true`, hides the RGB/HSV channel inputs.
*/
"channelsDisabled": boolean;
/**
* When `true`, an empty color (`null`) will be allowed as a `value`. When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/
"clearable": boolean;
/**
* Internal prop for advanced use-cases.
*/
Expand Down Expand Up @@ -8373,9 +8374,6 @@ declare namespace LocalJSX {
>;
}
interface CalciteColorPicker {
/**
* When `true`, an empty color (`null`) will be allowed as a `value`. When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/
"allowEmpty"?: boolean;
/**
* When `true`, the component will allow updates to the color's alpha value.
Expand All @@ -8385,6 +8383,10 @@ declare namespace LocalJSX {
* When `true`, hides the RGB/HSV channel inputs.
*/
"channelsDisabled"?: boolean;
/**
* When `true`, an empty color (`null`) will be allowed as a `value`. When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/
"clearable"?: boolean;
/**
* Internal prop for advanced use-cases.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("calcite-color-picker", () => {

describe("accessible", () => {
accessible("calcite-color-picker");
accessible("<calcite-color-picker allow-empty value=''></calcite-color-picker>");
accessible("<calcite-color-picker clearable value=''></calcite-color-picker>");
});

describe("honors hidden attribute", () => {
Expand Down Expand Up @@ -84,6 +84,10 @@ describe("calcite-color-picker", () => {
propertyName: "channelsDisabled",
defaultValue: false,
},
{
propertyName: "clearable",
defaultValue: false,
},
{
propertyName: "format",
defaultValue: "auto",
Expand Down Expand Up @@ -1094,7 +1098,7 @@ describe("calcite-color-picker", () => {
describe("when no-color", () => {
it("color gets propagated to hex, RGB & HSV inputs", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-color-picker allow-empty value=""></calcite-color-picker>`);
await page.setContent(html`<calcite-color-picker clearable value=""></calcite-color-picker>`);

const hexInput = await page.find(`calcite-color-picker >>> calcite-color-picker-hex-input`);

Expand Down Expand Up @@ -1125,9 +1129,7 @@ describe("calcite-color-picker", () => {

beforeEach(async () => {
page = await newE2EPage();
await page.setContent(
`<calcite-color-picker allow-empty value='${initialValue}'></calcite-color-picker>`,
);
await page.setContent(`<calcite-color-picker clearable value='${initialValue}'></calcite-color-picker>`);
});

it("restores previous color to RGB inputs", async () => {
Expand Down Expand Up @@ -1185,9 +1187,7 @@ describe("calcite-color-picker", () => {

it("changes the value to the specified format after being empty", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-color-picker allow-empty value="" format="rgb"></calcite-color-picker>`,
);
await page.setContent(html`<calcite-color-picker clearable value="" format="rgb"></calcite-color-picker>`);
const color = await page.find("calcite-color-picker");

const hexInput = await page.find(`calcite-color-picker >>> calcite-color-picker-hex-input`);
Expand All @@ -1199,7 +1199,7 @@ describe("calcite-color-picker", () => {
describe("clearing color via supporting inputs", () => {
it("clears color via hex input", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-color-picker allow-empty value="#c0ff33"></calcite-color-picker>`);
await page.setContent(html`<calcite-color-picker clearable value="#c0ff33"></calcite-color-picker>`);
const picker = await page.find("calcite-color-picker");
const hexInput = await page.find(`calcite-color-picker >>> calcite-color-picker-hex-input`);

Expand All @@ -1210,7 +1210,7 @@ describe("calcite-color-picker", () => {

it("clears color via RGB channel inputs", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-color-picker allow-empty value="#c0ff33"></calcite-color-picker>`);
await page.setContent(html`<calcite-color-picker clearable value="#c0ff33"></calcite-color-picker>`);
const picker = await page.find("calcite-color-picker");

const [rgbModeButton] = await page.findAll(`calcite-color-picker >>> .${CSS.colorMode}`);
Expand All @@ -1229,7 +1229,7 @@ describe("calcite-color-picker", () => {

it("clears color via HSV channel inputs", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-color-picker allow-empty value="#c0ff33"></calcite-color-picker>`);
await page.setContent(html`<calcite-color-picker clearable value="#c0ff33"></calcite-color-picker>`);
const picker = await page.find("calcite-color-picker");

const [, hsvModeButton] = await page.findAll(`calcite-color-picker >>> .${CSS.colorMode}`);
Expand Down Expand Up @@ -1647,9 +1647,7 @@ describe("calcite-color-picker", () => {
describe("when no-color", () => {
it("color gets propagated to hex, RGB, HSV & opacity inputs", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-color-picker alpha-channel allow-empty value=""></calcite-color-picker>`,
);
await page.setContent(html`<calcite-color-picker alpha-channel clearable value=""></calcite-color-picker>`);

const hexInput = await page.find(`calcite-color-picker >>> calcite-color-picker-hex-input`);

Expand Down Expand Up @@ -1683,7 +1681,7 @@ describe("calcite-color-picker", () => {
beforeEach(async () => {
page = await newE2EPage();
await page.setContent(
`<calcite-color-picker alpha-channel allow-empty value='${initialValue}'></calcite-color-picker>`,
`<calcite-color-picker alpha-channel clearable value='${initialValue}'></calcite-color-picker>`,
);
});

Expand Down Expand Up @@ -1776,7 +1774,7 @@ describe("calcite-color-picker", () => {
it("changes the value to the specified format after being empty", async () => {
const page = await newE2EPage();
await page.setContent(
"<calcite-color-picker alpha-channel allow-empty value='' format='rgba'></calcite-color-picker>",
"<calcite-color-picker alpha-channel clearable value='' format='rgba'></calcite-color-picker>",
);
const color = await page.find("calcite-color-picker");

Expand All @@ -1790,7 +1788,7 @@ describe("calcite-color-picker", () => {
it("clears color via hex input", async () => {
const page = await newE2EPage();
await page.setContent(
"<calcite-color-picker alpha-channel allow-empty value='#c0ff3333'></calcite-color-picker>",
"<calcite-color-picker alpha-channel clearable value='#c0ff3333'></calcite-color-picker>",
);
const picker = await page.find("calcite-color-picker");

Expand All @@ -1803,7 +1801,7 @@ describe("calcite-color-picker", () => {
it("clears color via RGB channel inputs", async () => {
const page = await newE2EPage();
await page.setContent(
"<calcite-color-picker alpha-channel allow-empty value='#c0ff3333'></calcite-color-picker>",
"<calcite-color-picker alpha-channel clearable value='#c0ff3333'></calcite-color-picker>",
);
const picker = await page.find("calcite-color-picker");

Expand All @@ -1827,7 +1825,7 @@ describe("calcite-color-picker", () => {
it("clears color via HSV channel inputs", async () => {
const page = await newE2EPage();
await page.setContent(
"<calcite-color-picker alpha-channel allow-empty value='#c0ff3333'></calcite-color-picker>",
"<calcite-color-picker alpha-channel clearable value='#c0ff3333'></calcite-color-picker>",
);
const picker = await page.find("calcite-color-picker");

Expand Down Expand Up @@ -1963,7 +1961,7 @@ describe("calcite-color-picker", () => {

it("does not allow saving/removing when no-color is set", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-color-picker allow-empty value=""></calcite-color-picker>`);
await page.setContent(`<calcite-color-picker clearable value=""></calcite-color-picker>`);

const saveColor = await page.find(`calcite-color-picker >>> .${CSS.saveColor}`);
const removeColor = await page.find(`calcite-color-picker >>> .${CSS.deleteColor}`);
Expand Down Expand Up @@ -2063,7 +2061,7 @@ describe("calcite-color-picker", () => {

it("does not allow saving/removing when no-color is set", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-color-picker alpha-channel allow-empty value=""></calcite-color-picker>`);
await page.setContent(`<calcite-color-picker alpha-channel clearable value=""></calcite-color-picker>`);

const saveColor = await page.find(`calcite-color-picker >>> .${CSS.saveColor}`);
const removeColor = await page.find(`calcite-color-picker >>> .${CSS.deleteColor}`);
Expand All @@ -2076,7 +2074,7 @@ describe("calcite-color-picker", () => {

it("allows setting no-color", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-color-picker allow-empty></calcite-color-picker>`);
await page.setContent(`<calcite-color-picker clearable></calcite-color-picker>`);

const color = await page.find("calcite-color-picker");

Expand Down Expand Up @@ -2152,7 +2150,7 @@ describe("calcite-color-picker", () => {
describe("keyboard", () => {
it("allows editing color field via keyboard", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-color-picker allow-empty value=""></calcite-color-picker>`);
await page.setContent(`<calcite-color-picker clearable value=""></calcite-color-picker>`);

const picker = await page.find("calcite-color-picker");
const scope = await page.find(`calcite-color-picker >>> .${CSS.colorFieldScope}`);
Expand Down Expand Up @@ -2232,7 +2230,7 @@ describe("calcite-color-picker", () => {

it("allows editing hue slider via keyboard", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-color-picker allow-empty value=""></calcite-color-picker>`);
await page.setContent(`<calcite-color-picker clearable value=""></calcite-color-picker>`);

const picker = await page.find("calcite-color-picker");
const hueScope = await page.find(`calcite-color-picker >>> .${CSS.hueScope}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ const createColorAttributes: (options?: { exceptions: string[] }) => Attributes
export const simple = (): string =>
create("calcite-color-picker", [
{
name: "allow-empty",
value: boolean("allow-empty", false),
name: "clearable",
value: boolean("clearable", false),
},
...createColorAttributes(),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,17 @@ export class ColorPicker
* When `true`, an empty color (`null`) will be allowed as a `value`.
*
* When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*
* @deprecated Use `clearable` instead
*/
@Prop({ reflect: true }) allowEmpty = false;
jcfranco marked this conversation as resolved.
Show resolved Hide resolved

@Watch("allowEmpty")
@Watch("clearable")
handleAllowEmptyOrClearableChange(): void {
this.isClearable = this.clearable || this.allowEmpty;
}

/**
* When `true`, the component will allow updates to the color's alpha value.
*/
Expand All @@ -121,6 +129,13 @@ export class ColorPicker
/** When `true`, hides the RGB/HSV channel inputs. */
@Prop() channelsDisabled = false;

/**
* When `true`, an empty color (`null`) will be allowed as a `value`.
*
* When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/
@Prop({ reflect: true }) clearable = false;

/**
* Internal prop for advanced use-cases.
*
Expand Down Expand Up @@ -225,8 +240,8 @@ export class ColorPicker

@Watch("value")
handleValueChange(value: ColorValue | null, oldValue: ColorValue | null): void {
const { allowEmpty, format } = this;
const checkMode = !allowEmpty || value;
const { isClearable, format } = this;
const checkMode = !isClearable || value;
let modeChanged = false;

if (checkMode) {
Expand Down Expand Up @@ -258,7 +273,7 @@ export class ColorPicker
}

const color =
allowEmpty && !value
isClearable && !value
? null
: Color(
value != null && typeof value === "object" && alphaCompatible(this.mode)
Expand Down Expand Up @@ -305,6 +320,8 @@ export class ColorPicker

private internalColorUpdateContext: "internal" | "initial" | "user-interaction" | null = null;

private isClearable: boolean;

private mode: SupportedMode = CSSColorMode.HEX;

private opacityScopeNode: HTMLDivElement;
Expand Down Expand Up @@ -417,11 +434,11 @@ export class ColorPicker

private handleHexInputChange = (event: Event): void => {
event.stopPropagation();
const { allowEmpty, color } = this;
const { isClearable, color } = this;
const input = event.target as HTMLCalciteColorPickerHexInputElement;
const hex = input.value;

if (allowEmpty && !hex) {
if (isClearable && !hex) {
this.internalColorSet(null);
return;
}
Expand Down Expand Up @@ -451,7 +468,7 @@ export class ColorPicker

let inputValue: string;

if (this.allowEmpty && !input.value) {
if (this.isClearable && !input.value) {
inputValue = "";
} else {
const value = Number(input.value);
Expand Down Expand Up @@ -508,7 +525,7 @@ export class ColorPicker
const channelIndex = Number(input.getAttribute("data-channel-index"));
const channels = [...this.channels] as this["channels"];

const shouldClearChannels = this.allowEmpty && !input.value;
const shouldClearChannels = this.isClearable && !input.value;

if (shouldClearChannels) {
this.channels = [null, null, null, null];
Expand Down Expand Up @@ -666,9 +683,10 @@ export class ColorPicker
async componentWillLoad(): Promise<void> {
setUpLoadableComponent(this);

const { allowEmpty, color, format, value } = this;
this.handleAllowEmptyOrClearableChange();

const willSetNoColor = allowEmpty && !value;
const { isClearable, color, format, value } = this;
jcfranco marked this conversation as resolved.
Show resolved Hide resolved
const willSetNoColor = isClearable && !value;
const parsedMode = parseMode(value);
const valueIsCompatible =
willSetNoColor || (format === "auto" && parsedMode) || format === parsedMode;
Expand All @@ -677,7 +695,6 @@ export class ColorPicker
if (!valueIsCompatible) {
this.showIncompatibleColorWarning(value, format);
}

this.setMode(format, false);
this.internalColorSet(initialColor, false, "initial");

Expand Down Expand Up @@ -722,7 +739,6 @@ export class ColorPicker

render(): VNode {
const {
allowEmpty,
channelsDisabled,
color,
colorFieldScopeLeft,
Expand Down Expand Up @@ -862,7 +878,7 @@ export class ColorPicker
{noHex ? null : (
<div class={CSS.hexOptions}>
<calcite-color-picker-hex-input
allowEmpty={allowEmpty}
allowEmpty={this.isClearable}
alphaChannel={alphaChannel}
class={CSS.control}
messages={messages}
Expand Down Expand Up @@ -972,7 +988,7 @@ export class ColorPicker
};

private renderChannelsTab = (channelMode: this["channelMode"]): VNode => {
const { allowEmpty, channelMode: activeChannelMode, channels, messages, alphaChannel } = this;
const { isClearable, channelMode: activeChannelMode, channels, messages, alphaChannel } = this;
const selected = channelMode === activeChannelMode;
const isRgb = channelMode === "rgb";
const channelAriaLabels = isRgb
Expand All @@ -990,7 +1006,7 @@ export class ColorPicker

if (isAlphaChannel) {
channelValue =
allowEmpty && !channelValue ? channelValue : alphaToOpacity(channelValue);
isClearable && !channelValue ? channelValue : alphaToOpacity(channelValue);
}

/* the channel container is ltr, so we apply the host's direction */
Expand Down