sass/proposal/color-4-new-spaces-js.d.ts.md
James Stuckey Weber f54e4924e5 [Color 4] Draft 1.4 changes for Color Spaces JS API (#3723)
Co-authored-by: Jonny Gerig Meyer <jonny@oddbird.net>
2024-03-27 11:43:24 -07:00

26 KiB

CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.4

(Issue, Changelog)

This proposal updates Sass's JavaScript (JS) API to match the color spaces proposal.

Table of Contents

API

import {List} from 'immutable';

import {Value} from '../spec/js-api/value';

Types

Color Space Definitions

export type ColorSpaceHsl = 'hsl';

export type ChannelNameHsl = 'hue' | 'saturation' | 'lightness' | 'alpha';

export type ColorSpaceHwb = 'hwb';

export type ChannelNameHwb = 'hue' | 'whiteness' | 'blackness' | 'alpha';

export type ColorSpaceLab = 'lab' | 'oklab';

export type ChannelNameLab = 'lightness' | 'a' | 'b' | 'alpha';

export type ColorSpaceLch = 'lch' | 'oklch';

export type ChannelNameLch = 'lightness' | 'chroma' | 'hue' | 'alpha';

export type ColorSpaceRgb =
  | 'a98-rgb'
  | 'display-p3'
  | 'prophoto-rgb'
  | 'rec2020'
  | 'rgb'
  | 'srgb'
  | 'srgb-linear';

export type ChannelNameRgb = 'red' | 'green' | 'blue' | 'alpha';

export type ColorSpaceXyz = 'xyz' | 'xyz-d50' | 'xyz-d65';

export type ChannelNameXyz = 'x' | 'y' | 'z' | 'alpha';

export type ChannelName =
  | ChannelNameHsl
  | ChannelNameHwb
  | ChannelNameLab
  | ChannelNameLch
  | ChannelNameRgb
  | ChannelNameXyz;

export type KnownColorSpace =
  | ColorSpaceHsl
  | ColorSpaceHwb
  | ColorSpaceLab
  | ColorSpaceLch
  | ColorSpaceRgb
  | ColorSpaceXyz;

export type PolarColorSpace = ColorSpaceHsl | ColorSpaceHwb | ColorSpaceLch;

export type RectangularColorSpace = Exclude<KnownColorSpace, PolarColorSpace>;

export type HueInterpolationMethod =
  | 'decreasing'
  | 'increasing'
  | 'longer'
  | 'shorter';

New Color Functions

export class SassColor extends Value {

space

Returns the name of internal's space.

get space(): KnownColorSpace;

toSpace

  • If this.space is equal to space, return this.

  • Otherwise, return the result of Converting a Color with this as origin-color and space as target-space.

toSpace(space: KnownColorSpace): SassColor;

isLegacy

Returns whether internal is in a legacy color space (rgb, hsl, or hwb).

get isLegacy(): boolean;

isInGamut

Returns the result of color.is-in-gamut(internal, space) as a JavaScript boolean.

isInGamut(space?: KnownColorSpace): boolean;

toGamut

Returns the result of color.to-gamut(internal, space).

toGamut(space?: KnownColorSpace): SassColor;

channelsOrNull

Returns a list of channel values (excluding alpha) for internal, with missing channels converted to null.

  • Let space be the value of this.space.

  • Let components be the list of channels in space.

  • Let channels be an empty list.

  • For each component in components:

    • Let value be the channel value in internal with name of component.

    • If value is none, let value be null.

    • Append value to channels.

  • Return channels.

get channelsOrNull(): List<number | null>;

channels

This algorithm returns a list of channel values (excluding alpha) for internal, with missing channels converted to 0.

  • Let channelsOrNull be the value of this.channelsOrNull.

  • Let channels be an empty list.

  • For each channel in channelsOrNull:

    • If channel equals null, let value be 0.

    • Append value to channels.

  • Return channels.

get channels(): List<number>;

channel

  • Let initialSpace be the value of this.space.

  • Let space be options.space if it is defined, and the value of initialSpace otherwise.

  • If channel is not "alpha" or a channel in space, throw an error.

  • Let color be the result of this.toSpace(space).

  • Let value be the channel value in color with name of component.

  • If value is null, return 0.

  • Otherwise, return value.

channel(channel: ChannelName): number;
channel(channel: ChannelNameHsl, options: {space: ColorSpaceHsl}): number;
channel(channel: ChannelNameHwb, options: {space: ColorSpaceHwb}): number;
channel(channel: ChannelNameLab, options: {space: ColorSpaceLab}): number;
channel(channel: ChannelNameLch, options: {space: ColorSpaceLch}): number;
channel(channel: ChannelNameRgb, options: {space: ColorSpaceRgb}): number;
channel(channel: ChannelNameXyz, options: {space: ColorSpaceXyz}): number;

alpha

Returns the result of calling this.channel('alpha').

get alpha(): number;

isChannelMissing

Returns the result of color.is-missing(internal, channel) as a JavaScript boolean.

isChannelMissing(channel: ChannelName): boolean;

isChannelPowerless

Returns the result of color.is-powerless(internal, channel, space) as a JavaScript boolean.

isChannelPowerless(channel: ChannelName): boolean;
isChannelPowerless(
  channel: ChannelNameHsl,
  options?: {space: ColorSpaceHsl}
): boolean;
isChannelPowerless(
  channel: ChannelNameHwb,
  options?: {space: ColorSpaceHwb}
): boolean;
isChannelPowerless(
  channel: ChannelNameLab,
  options?: {space: ColorSpaceLab}
): boolean;
isChannelPowerless(
  channel: ChannelNameLch,
  options?: {space: ColorSpaceLch}
): boolean;
isChannelPowerless(
  channel: ChannelNameRgb,
  options?: {space: ColorSpaceRgb}
): boolean;
isChannelPowerless(
  channel: ChannelNameXyz,
  options?: {space: ColorSpaceXyz}
): boolean;

interpolate

  • Let space be the value of this.space.

  • If options.method is set, let interpolationMethod be a space separated list containing the value of space, a space, and the value of options.method.

  • Otherwise, if space is a rectangular color space, let interpolationMethod be space.

  • Otherwise, let interpolationMethod be a space separated list containing the value of space, a space, and the string "shorter".

  • Return the result of color.mix(internal, color2, options.weight, interpolationMethod).

interpolate(color2: SassColor, options: {weight?: number}): SassColor;

interpolate(
  color2: SassColor,
  options: {
    weight?: number;
    method?: HueInterpolationMethod;
  }
): SassColor;

Updated Color Functions

change

Replace the definition of color.change with the following:

This algorithm takes a JavaScript object options and returns a new SassColor as the result of changing some of internal's components.

The space value defaults to the space of internal, and the caller may specify any combination of channels and alpha in that space to be changed.

If space is not a legacy color space, a channel value of null will result in a missing component value for that channel.

  • Let initialSpace be the value of this.space.

  • Let spaceSetExplicitly be true if options.space is defined, and false otherwise.

  • Let space be options.space if spaceSetExplicitly is true, and the value of initialSpace otherwise.

  • If initialSpace is a legacy color space and spaceSetExplicitly is false:

    • If options.whiteness or options.blackness is set, let space be hwb.

    • Otherwise, if options.hue is set and initialSpace is hwb, let space be hwb.

    • Otherwise, if options.hue, options.saturation, or options.lightness is set, let space be hsl.

    • Otherwise, if options.red, options.green, or options.blue is set, let space be rgb.

    • If initialSpace is not equal to space, emit a deprecation warning named color-4-api.

  • Let changes be the object options without space and its value.

  • Let keys be a list of the keys in changes.

  • Let components be "alpha" and the names of the channels in space.

  • If any key in keys is not the name of a channel in components, throw an error.

  • If options.alpha is set, and isn't either null or a number between 0 and 1 (inclusive and fuzzy), throw an error.

  • If options.lightness is set, and isn't either null or a number between 0 and the maximum channel value for the space (inclusive and fuzzy), throw an error.

  • Let color be the result of this.toSpace(space).

  • Let changedValue be a function that takes a string argument for channel and calls the procedure Changing a Component Value with changes and this as initial.

  • If space equals hsl and spaceSetExplicitly is false:

    • If any of options.hue, options.saturation or options.lightness equals null, emit a deprecation warning named color-4-api.

    • If options.alpha equals null, emit a deprecation warning named null-alpha.

    • Let changedColor be the result of:

      new SassColor({
        hue: options.hue ?? color.channel('hue'),
        saturation: options.saturation ?? color.channel('saturation'),
        lightness: options.lightness ?? color.channel('lightness'),
        alpha: options.alpha ?? color.channel('alpha'),
        space: space
      })
      
  • If space equals hsl and spaceSetExplicitly is true, let changedColor be the result of:

    new SassColor({
     hue: changedValue('hue'),
     saturation: changedValue('saturation'),
     lightness: changedValue('lightness'),
     alpha: changedValue('alpha'),
     space: space
    })
    
  • If space equals hwb and spaceSetExplicitly is false:

    • If any of options.hue, options.whiteness or options.blackness equals null, emit a deprecation warning named color-4-api.

    • If options.alpha equals null, emit a deprecation warning named null-alpha.

    • Let changedColor be the result of:

      new SassColor({
        hue: options.hue ?? color.channel('hue'),
        whiteness: options.whiteness ?? color.channel('whiteness'),
        blackness: options.blackness ?? color.channel('blackness'),
        alpha: options.alpha ?? color.channel('alpha'),
        space: space
      })
      
  • If space equals hwb and spaceSetExplicitly is true, let changedColor be the result of:

    new SassColor({
     hue: changedValue('hue'),
     whiteness: changedValue('whiteness'),
     blackness: changedValue('blackness'),
     alpha: changedValue('alpha'),
     space: space
    })
    
  • If space equals rgb and spaceSetExplicitly is false:

    • If any of options.red, options.green or options.blue equals null, emit a deprecation warning named color-4-api.

    • If options.alpha equals null, emit a deprecation warning named null-alpha.

    • Let changedColor be the result of:

      new SassColor({
        red: options.red ?? color.channel('red'),
        green: options.green ?? color.channel('green'),
        blue: options.blue ?? color.channel('blue'),
        alpha: options.alpha ?? color.channel('alpha'),
        space: space
      })
      
  • If space equals rgb and spaceSetExplicitly is true, let changedColor be the result of:

    new SassColor({
     red: changedValue('red'),
     green: changedValue('green'),
     blue: changedValue('blue'),
     alpha: changedValue('alpha'),
     space: space
    })
    
  • If space equals lab or oklab, let changedColor be the result of:

    new SassColor({
      lightness: changedValue('lightness'),
      a: changedValue('a'),
      b: changedValue('b'),
      alpha: changedValue('alpha'),
      space: space
    })
    
  • If space equals lch or oklch, let changedColor be the result of:

    new SassColor({
      lightness: changedValue('lightness'),
      chroma: changedValue('chroma'),
      hue: changedValue('hue'),
      alpha: changedValue('alpha'),
      space: space
    })
    
  • If space equals a98-rgb, display-p3, prophoto-rgb, rec2020, srgb, or srgb-linear, let changedColor be the result of:

    new SassColor({
      red: changedValue('red'),
      green: changedValue('green'),
      blue: changedValue('blue'),
      alpha: changedValue('alpha'),
      space: space
    })
    
  • If space equals xyz, xyz-d50, or xyz-d65, let changedColor be the result of:

    new SassColor({
      y: changedValue('y'),
      x: changedValue('x'),
      z: changedValue('z'),
      alpha: changedValue('alpha'),
      space: space
    })
    
  • Return the result of changedColor.toSpace(initialSpace).

change(
  options: {
    [key in ChannelNameHsl]?: number | null;
  } & {
    space?: ColorSpaceHsl;
  }
): SassColor;

change(
  options: {
    [key in ChannelNameHwb]?: number | null;
  } & {
    space?: ColorSpaceHwb;
  }
): SassColor;

change(
  options: {
    [key in ChannelNameLab]?: number | null;
  } & {
    space?: ColorSpaceLab;
  }
): SassColor;

change(
  options: {
    [key in ChannelNameLch]?: number | null;
  } & {
    space?: ColorSpaceLch;
  }
): SassColor;

change(
  options: {
    [key in ChannelNameRgb]?: number | null;
  } & {
    space?: ColorSpaceRgb;
  }
): SassColor;

change(
  options: {
    [key in ChannelNameXyz]?: number | null;
  } & {
    space?: ColorSpaceXyz;
  }
): SassColor;

New Constructors

  • Let constructionSpace be the result of Determining Construction Space with the options object passed to the constructor.

  • Use the constructor that matches constructionSpace.

Lab Channel Constructor

Create a new SassColor in a color space with Lab channels—lab and oklab.

constructor(options: {
  lightness: number | null;
  a: number | null;
  b: number | null;
  alpha?: number | null;
  space: ColorSpaceLab;
});

LCH Channel Constructor

Create a new SassColor in a color space with LCH channels—lch and oklch.

constructor(options: {
  lightness: number | null;
  chroma: number | null;
  hue: number | null;
  alpha?: number | null;
  space: ColorSpaceLch;
});

Predefined RGB Channel Constructor

Create a new SassColor in a color space with RGB channels—srgb, srgb-linear, display-p3, a98-rgb, prophoto-rgb, and rec2020. rgb is supported through the modified RGB Constructor.

constructor(options: {
  red: number | null;
  green: number | null;
  blue: number | null;
  alpha?: number | null;
  space: Exclude<ColorSpaceRgb, 'rgb'>;
});

XYZ Channel Constructor

Create a new SassColor in a color space with XYZ channels—xyz, xyz-d50, and xyz-d65.

constructor(options: {
  x: number | null;
  y: number | null;
  z: number | null;
  alpha?: number | null;
  space: ColorSpaceXyz;
});

Modified Legacy Color Constructors

These will replace the existing constructors for legacy colors.

HSL Constructor

Create a new SassColor in the hsl color space.

constructor(options: {
  hue: number | null;
  saturation: number | null;
  lightness: number | null;
  alpha?: number | null;
  space?: ColorSpaceHsl;
});

HWB Constructor

Create a new SassColor in the hwb color space.

constructor(options: {
  hue: number | null;
  whiteness: number | null;
  blackness: number | null;
  alpha?: number | null;
  space?: ColorSpaceHwb;
});

RGB Constructor

Create a new SassColor in the rgb color space.

constructor(options: {
  red: number | null;
  green: number | null;
  blue: number | null;
  alpha?: number | null;
  space?: 'rgb';
});
}

Deprecations

A number of SassColor getters only make sense for legacy color space, and so are being deprecated in favor of the new channel function. This deprecation is called color-4-api.

  • red
  • green
  • blue
  • hue
  • saturation
  • lightness
  • whiteness
  • blackness

Procedures

Parsing a Channel Value

This procedure takes a channel value value, and returns the special value none if the value is null.

  • If value is a number, return a Sass number with a value of value.

  • If value is the Javascript value null, return the unquoted Sass string none.

Parsing a Clamped Channel Value

This procedure takes a channel value value and an inclusive range of minimum and maximum. It asserts the value is in the range, and returns the special value none if the value is null.

  • If value is fuzzy less-than minimum, throw an error.

  • If value is fuzzy greater-than maximum, throw an error.

  • Otherwise, return the result of Parsing a Channel Value.

Changing a Component Value

This procedure takes a channel name, an object changes and a SassColor initial and returns the result of applying the change for channel to initial.

  • Let initialValue be the channel value in initial with name of channel.

  • If channel is not a key in changes, return initialValue.

  • Let changedValue be the value for channel in changes.

  • If changedValue is undefined and not null, return initialValue.

  • Otherwise, return changedValue.

Determining Construction Space

This procedure takes an object options with unknown keys and returns a color space for construction.

  • If options.space is set, return options.space.

  • If options.red is set, return "rgb".

  • If options.saturation is set, return "hsl".

  • If options.whiteness is set, return "hwb".

  • Otherwise, throw an error.

Embedded Protocol

This introduces a breaking change in the Embedded Protocol, as it removes the legacy SassScript values.

Color

message Color {
  // The name of a known color space.
  string space = 1;

  // The value of the first channel associated with `space`.
  double channel1 = 2;

  // The value of the second channel associated with `space`.
  double channel2 = 3;

  // The value of the third channel associated with `space`.
  double channel3 = 4;

  // The color's alpha channel. Mandatory. Must be between 0 and 1, inclusive.
  double alpha = 5;
}

Removed SassScript values

The RgbColor, HslColor and HwbColor SassScript values will be removed from the Embedded Protocol.