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

refactor(color-picker): add clearable property and deprecate allowEmpty #8904

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ export namespace Components {
*/
"disabled": boolean;
/**
* Specifies the optional new name of the file after it is downloaded.
* Prompts the user to save the linked URL instead of navigating to it. Can be used with or without a value: Without a value, the browser will suggest a filename/extension See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download.
*/
"download": string | boolean;
/**
Expand Down Expand Up @@ -980,6 +980,7 @@ export namespace Components {
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`.
* @deprecated Use `clearable` instead.
*/
"allowEmpty": boolean;
/**
Expand All @@ -990,6 +991,10 @@ export namespace Components {
* When `true`, hides the RGB/HSV channel inputs.
*/
"channelsDisabled": boolean;
/**
* When `true`, a clear button is displayed when the component has a value.
*/
"clearable": boolean;
/**
* Internal prop for advanced use-cases.
*/
Expand Down Expand Up @@ -1061,12 +1066,17 @@ export namespace Components {
interface CalciteColorPickerHexInput {
/**
* 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.
*/
"allowEmpty": boolean;
/**
* When `true`, the component will allow updates to the color's alpha value.
*/
"alphaChannel": boolean;
/**
* When `true`, a clear button is displayed when the component has a value.
*/
"clearable": boolean;
/**
* Specifies accessible label for the input field.
* @deprecated use `messages` instead
Expand Down Expand Up @@ -8059,7 +8069,7 @@ declare namespace LocalJSX {
*/
"disabled"?: boolean;
/**
* Specifies the optional new name of the file after it is downloaded.
* Prompts the user to save the linked URL instead of navigating to it. Can be used with or without a value: Without a value, the browser will suggest a filename/extension See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download.
*/
"download"?: string | boolean;
/**
Expand Down Expand Up @@ -8375,6 +8385,7 @@ 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`.
* @deprecated Use `clearable` instead.
*/
"allowEmpty"?: boolean;
/**
Expand All @@ -8385,6 +8396,10 @@ declare namespace LocalJSX {
* When `true`, hides the RGB/HSV channel inputs.
*/
"channelsDisabled"?: boolean;
/**
* When `true`, a clear button is displayed when the component has a value.
*/
"clearable"?: boolean;
/**
* Internal prop for advanced use-cases.
*/
Expand Down Expand Up @@ -8460,12 +8475,17 @@ declare namespace LocalJSX {
interface CalciteColorPickerHexInput {
/**
* 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.
*/
"allowEmpty"?: boolean;
/**
* When `true`, the component will allow updates to the color's alpha value.
*/
"alphaChannel"?: boolean;
/**
* When `true`, a clear button is displayed when the component has a value.
*/
"clearable"?: boolean;
/**
* Specifies accessible label for the input field.
* @deprecated use `messages` instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ColorPickerHexInput implements LoadableComponent {
//--------------------------------------------------------------------------

connectedCallback(): void {
const { allowEmpty, alphaChannel, value } = this;
const { allowEmpty, isClearable = allowEmpty, alphaChannel, value } = this;

if (value) {
const normalized = normalizeHex(value, alphaChannel);
Expand All @@ -62,7 +62,7 @@ export class ColorPickerHexInput implements LoadableComponent {
return;
}

if (allowEmpty) {
if (isClearable) {
this.internalSetValue(null, null, false);
}
}
Expand All @@ -85,6 +85,8 @@ export class ColorPickerHexInput implements LoadableComponent {
* 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() allowEmpty = false;

Expand All @@ -93,6 +95,11 @@ export class ColorPickerHexInput implements LoadableComponent {
*/
@Prop() alphaChannel = false;

/**
* When `true`, a clear button is displayed when the component has a value.
*/
@Prop({ reflect: true }) clearable = false;

/**
* Specifies accessible label for the input field.
*
Expand Down Expand Up @@ -142,7 +149,7 @@ export class ColorPickerHexInput implements LoadableComponent {
const node = this.hexInputNode;
const inputValue = node.value;
const hex = `#${inputValue}`;
const { allowEmpty, internalColor } = this;
const { allowEmpty, isClearable = allowEmpty, internalColor } = this;
const willClearValue = allowEmpty && !inputValue;
const isLonghand = isLonghandHex(hex);

Expand All @@ -155,7 +162,7 @@ export class ColorPickerHexInput implements LoadableComponent {

// manipulating DOM directly since rerender doesn't update input value
node.value =
allowEmpty && !internalColor
isClearable && !internalColor
? ""
: this.formatHexForInternalInput(
rgbToHex(
Expand All @@ -168,16 +175,16 @@ export class ColorPickerHexInput implements LoadableComponent {
private onOpacityInputBlur = (): void => {
const node = this.opacityInputNode;
const inputValue = node.value;
const { allowEmpty, internalColor } = this;
const willClearValue = allowEmpty && !inputValue;
const { allowEmpty, isClearable = allowEmpty, internalColor } = this;
const willClearValue = isClearable && !inputValue;

if (willClearValue) {
return;
}

// manipulating DOM directly since rerender doesn't update input value
node.value =
allowEmpty && !internalColor ? "" : this.formatOpacityForInternalInput(internalColor);
isClearable && !internalColor ? "" : this.formatOpacityForInternalInput(internalColor);
};

private onHexInputChange = (): void => {
Expand Down Expand Up @@ -296,14 +303,18 @@ export class ColorPickerHexInput implements LoadableComponent {

private previousNonNullValue: string = this.value;

get isClearable(): boolean {
return this.clearable && this.value.length > 0;
}

//--------------------------------------------------------------------------
//
// Lifecycle
//
//--------------------------------------------------------------------------

render(): VNode {
const { alphaChannel, hexLabel, internalColor, messages, scale, value } = this;
const { alphaChannel, hexLabel, internalColor, isClearable, messages, scale, value } = this;
const hexInputValue = this.formatHexForInternalInput(value);
const opacityInputValue = this.formatOpacityForInternalInput(internalColor);
const inputScale = scale === "l" ? "m" : "s";
Expand All @@ -312,6 +323,7 @@ export class ColorPickerHexInput implements LoadableComponent {
<div class={CSS.container}>
<calcite-input-text
class={CSS.hexInput}
clearable={isClearable}
label={messages?.hex || hexLabel}
maxLength={6}
onCalciteInputTextChange={this.onHexInputChange}
Expand Down Expand Up @@ -369,6 +381,8 @@ export class ColorPickerHexInput implements LoadableComponent {
//--------------------------------------------------------------------------

private internalSetValue(value: string | null, oldValue: string | null, emit = true): void {
const { allowEmpty, isClearable = allowEmpty } = this;

if (value) {
const { alphaChannel } = this;
const normalized = normalizeHex(value, alphaChannel, alphaChannel);
Expand All @@ -392,7 +406,7 @@ export class ColorPickerHexInput implements LoadableComponent {

return;
}
} else if (this.allowEmpty) {
} else if (isClearable) {
this.internalColor = null;
this.value = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ 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;

Expand All @@ -121,6 +123,11 @@ export class ColorPicker
/** When `true`, hides the RGB/HSV channel inputs. */
@Prop() channelsDisabled = false;

/**
* When `true`, a clear button is displayed when the component has a value.
*/
@Prop({ reflect: true }) clearable = false;

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

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

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

const color =
allowEmpty && !value
isClearable && !value
? null
: Color(
value != null && typeof value === "object" && alphaCompatible(this.mode)
Expand Down Expand Up @@ -293,6 +300,10 @@ export class ColorPicker
return this.color || this.previousColor || DEFAULT_COLOR;
}

get isClearable(): boolean {
return this.clearable && !!this.value;
}

private checkerPattern: HTMLCanvasElement;

private colorFieldRenderingContext: CanvasRenderingContext2D;
Expand Down Expand Up @@ -417,11 +428,11 @@ export class ColorPicker

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

if (allowEmpty && !hex) {
if (isClearable && !hex) {
this.internalColorSet(null);
return;
}
Expand All @@ -439,6 +450,8 @@ export class ColorPicker
};

private handleChannelInput = (event: CustomEvent): void => {
const { allowEmpty, isClearable = allowEmpty } = this;

const input = event.currentTarget as HTMLCalciteInputNumberElement;
const channelIndex = Number(input.getAttribute("data-channel-index"));
const isAlphaChannel = channelIndex === 3;
Expand All @@ -451,7 +464,7 @@ export class ColorPicker

let inputValue: string;

if (this.allowEmpty && !input.value) {
if (isClearable && !input.value) {
inputValue = "";
} else {
const value = Number(input.value);
Expand Down Expand Up @@ -504,11 +517,13 @@ export class ColorPicker
}

private handleChannelChange = (event: CustomEvent): void => {
const { allowEmpty, isClearable = allowEmpty } = this;

const input = event.currentTarget as HTMLCalciteInputNumberElement;
const channelIndex = Number(input.getAttribute("data-channel-index"));
const channels = [...this.channels] as this["channels"];

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

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

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

const willSetNoColor = allowEmpty && !value;
const willSetNoColor = isClearable && !value;
const parsedMode = parseMode(value);
const valueIsCompatible =
willSetNoColor || (format === "auto" && parsedMode) || format === parsedMode;
Expand Down Expand Up @@ -723,6 +738,7 @@ export class ColorPicker
render(): VNode {
const {
allowEmpty,
isClearable = allowEmpty,
channelsDisabled,
color,
colorFieldScopeLeft,
Expand Down Expand Up @@ -862,9 +878,9 @@ export class ColorPicker
{noHex ? null : (
<div class={CSS.hexOptions}>
<calcite-color-picker-hex-input
allowEmpty={allowEmpty}
alphaChannel={alphaChannel}
class={CSS.control}
clearable={isClearable}
messages={messages}
numberingSystem={this.numberingSystem}
onCalciteColorPickerHexInputChange={this.handleHexInputChange}
Expand Down Expand Up @@ -972,7 +988,14 @@ export class ColorPicker
};

private renderChannelsTab = (channelMode: this["channelMode"]): VNode => {
const { allowEmpty, channelMode: activeChannelMode, channels, messages, alphaChannel } = this;
const {
allowEmpty,
isClearable = allowEmpty,
channelMode: activeChannelMode,
channels,
messages,
alphaChannel,
} = this;
const selected = channelMode === activeChannelMode;
const isRgb = channelMode === "rgb";
const channelAriaLabels = isRgb
Expand All @@ -990,7 +1013,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
Loading