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 @@ -974,9 +974,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 @@ -986,6 +983,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 @@ -8365,9 +8366,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 @@ -8377,6 +8375,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 @@ -69,6 +69,10 @@ export const simple = (): string =>
name: "allow-empty",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed since we don't want to promote its use.

value: boolean("allow-empty", false),
},
{
name: "clearable",
value: boolean("clearable", false),
},
...createColorAttributes(),
{
name: "value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export class ColorPicker
*
* When `false`, a color value is enforced, and clearing the input or blurring will restore the last valid `value`.
*/

@Prop({ reflect: true }) clearable = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also move this so it's in alphabetical order within the properties section?


@Prop({ reflect: true }) allowEmpty = false;
jcfranco marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand All @@ -118,6 +121,24 @@ export class ColorPicker
}
}

@Watch("allowEmpty")
handleAllowEmpty(): void {
if (this.allowEmpty === true) {
this.allowNullValues = true;
} else if (this.allowEmpty == false && this.clearable == false) {
this.allowNullValues = false;
}
}

@Watch("clearable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the above watcher have very similar code. Please consolidate into a single watcher to be used for both props (see Stencil @watch doc for example).

handleClearableChange(): void {
if (this.clearable === true) {
this.allowNullValues = true;
} else if (this.allowEmpty == false && this.clearable == false) {
this.allowNullValues = false;
}
}

/** When `true`, hides the RGB/HSV channel inputs. */
@Prop() channelsDisabled = false;

Expand Down Expand Up @@ -225,8 +246,8 @@ export class ColorPicker

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

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

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

private shiftKeyChannelAdjustment = 0;

private allowNullValues = !!(this.clearable || this.allowEmpty);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we talked about this, but let's make sure this variable is assigned in a lifecycle method vs when the component is instantiated. This works fine in the lazy-loaded output target, but does not get properly assigned in the components (tree-shakeable) output target.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to isClearable, canClear or similar to match the new prop name? This would avoid having 3 different ways of referring to the same behavior (e.g., clear + empty vs null + clear + empty).


@State() defaultMessages: ColorPickerMessages;

@State() channelMode: ColorMode = "rgb";
Expand Down Expand Up @@ -417,11 +440,11 @@ export class ColorPicker

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

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

let inputValue: string;

if (this.allowEmpty && !input.value) {
if (this.allowNullValues && !input.value) {
inputValue = "";
} else {
const value = Number(input.value);
Expand Down Expand Up @@ -508,7 +531,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.allowNullValues && !input.value;

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

const { allowEmpty, color, format, value } = this;

const willSetNoColor = allowEmpty && !value;
const { allowNullValues, color, format, value } = this;
const willSetNoColor = allowNullValues && !value;
const parsedMode = parseMode(value);
const valueIsCompatible =
willSetNoColor || (format === "auto" && parsedMode) || format === parsedMode;
Expand All @@ -677,7 +699,7 @@ export class ColorPicker
if (!valueIsCompatible) {
this.showIncompatibleColorWarning(value, format);
}

console.log(allowNullValues);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove log statements.

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

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

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

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

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

/* the channel container is ltr, so we apply the host's direction */
Expand Down
2 changes: 1 addition & 1 deletion packages/calcite-components/src/demos/color-picker.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ <h1 style="margin: 0 auto; text-align: center">Color picker</h1>
<div class="parent">
<div class="child right-aligned-text">Default</div>
<div class="child">
<calcite-color-picker scale="s"></calcite-color-picker>
<calcite-color-picker clearable scale="s"></calcite-color-picker>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to the existing demo block. Please revert.

</div>

<div class="child">
Expand Down