diff --git a/change/@adaptive-web-adaptive-ui-65457e29-816e-41a0-b84d-d5eb76b92e78.json b/change/@adaptive-web-adaptive-ui-65457e29-816e-41a0-b84d-d5eb76b92e78.json new file mode 100644 index 00000000..52543574 --- /dev/null +++ b/change/@adaptive-web-adaptive-ui-65457e29-816e-41a0-b84d-d5eb76b92e78.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "AUI: Refactored Swatch to Color", + "packageName": "@adaptive-web/adaptive-ui", + "email": "47367562+bheston@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@adaptive-web-adaptive-web-components-f26fdf42-e841-46c7-bcf2-927d4a453ab2.json b/change/@adaptive-web-adaptive-web-components-f26fdf42-e841-46c7-bcf2-927d4a453ab2.json new file mode 100644 index 00000000..e3499675 --- /dev/null +++ b/change/@adaptive-web-adaptive-web-components-f26fdf42-e841-46c7-bcf2-927d4a453ab2.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "AUI: Refactored Swatch to Color", + "packageName": "@adaptive-web/adaptive-web-components", + "email": "47367562+bheston@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/adaptive-ui-designer-figma-plugin/src/figma/node.ts b/packages/adaptive-ui-designer-figma-plugin/src/figma/node.ts index 9fb66e6f..a566ec96 100644 --- a/packages/adaptive-ui-designer-figma-plugin/src/figma/node.ts +++ b/packages/adaptive-ui-designer-figma-plugin/src/figma/node.ts @@ -1,8 +1,8 @@ -import { type Color, modeLrgb, modeRgb, parse, type Rgb, useMode, wcagLuminance } from "culori/fn"; -import { Shadow, StyleProperty } from "@adaptive-web/adaptive-ui"; +import { type Color as CuloriColor, modeLrgb, modeRgb, parse, type Rgb, useMode, wcagLuminance } from "culori/fn"; +import { Color, Shadow, StyleProperty } from "@adaptive-web/adaptive-ui"; import { AppliedStyleModules, AppliedStyleValues, Controller, focusIndicatorNodeName, PluginNode, PluginNodeData, State, StatesState, STYLE_REMOVE } from "@adaptive-web/adaptive-ui-designer-core"; import { FIGMA_SHARED_DATA_NAMESPACE } from "@adaptive-web/adaptive-ui-designer-figma"; -import { colorToRgba, variantBooleanHelper } from "./utility.js"; +import { colorToRgba, roundToDecimals, variantBooleanHelper } from "./utility.js"; const rgb = useMode(modeRgb); // For luminance @@ -119,7 +119,7 @@ export class FigmaPluginNode extends PluginNode { public id: string; public type: string; public name: string; - public fillColor: Color | null = null; + public fillColor: CuloriColor | null = null; public states?: StatesState; private _node: BaseNode; private _state?: State; @@ -760,7 +760,7 @@ export class FigmaPluginNode extends PluginNode { return await FigmaPluginNode.get(parent, false); } - private getFillColor(): Color | null { + private getFillColor(): CuloriColor | null { // console.log("FigmaPluginNode.getFillColor", this.debugInfo); if ((this._node as GeometryMixin).fills) { const fills = (this._node as GeometryMixin).fills; @@ -783,7 +783,7 @@ export class FigmaPluginNode extends PluginNode { return null; } - public async getEffectiveFillColor(): Promise { + public async getEffectiveFillColor(): Promise { if (this.fillColor) { return this.fillColor; } @@ -849,90 +849,109 @@ export class FigmaPluginNode extends PluginNode { } } - private paintColor(target: StyleProperty, value: string): void { + private paintColor(target: StyleProperty, value: unknown): void { let paint: Paint | null = null; if (value !== STYLE_REMOVE) { - if (value.startsWith("linear-gradient")) { - const linearMatch = /linear-gradient\((?.+)\)/; - const matches = value.match(linearMatch); - if (matches && matches.groups) { - const array = matches.groups.params.split(",").map(p => p.trim()); - - let degrees: number = 90; - if (array[0].endsWith("deg")) { - const angle = array.shift()?.replace("deg", "") || "90"; - degrees = Number.parseFloat(angle); - } - const radians: number = degrees * (Math.PI / 180); - - const paramMatch = /(?#[\w\d]+)( (?.+))?/; - const stops = array.map((p, index, array) => { - const paramMatches = p.match(paramMatch); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const color = rgb(parse(paramMatches?.groups?.color || "FF00FF")!); - let position: number = 0; - if (paramMatches?.groups && paramMatches?.groups?.pos) { - if (paramMatches.groups.pos.endsWith("%")) { - position = Number.parseFloat(paramMatches.groups.pos) / 100; - } else if (paramMatches.groups.pos.startsWith("calc(100% - ")) { - const px = Number.parseFloat( - paramMatches.groups.pos - .replace("calc(100% - ", "") - .replace("px)", "") - ); - const size = degrees === 90 || degrees === 270 - ? (this._node as LayoutMixin).height - : (this._node as LayoutMixin).width; - position = (size - px) / size; - } - } else if (index === array.length - 1) { - position = 1; + if (typeof value === "string") { + if (value.startsWith("linear-gradient")) { + const linearMatch = /linear-gradient\((?.+)\)/; + const matches = value.match(linearMatch); + if (matches && matches.groups) { + const array = matches.groups.params.split(",").map(p => p.trim()); + + let degrees: number = 90; + if (array[0].endsWith("deg")) { + const angle = array.shift()?.replace("deg", "") || "90"; + degrees = Number.parseFloat(angle); } - const stop: ColorStop = { - position, - color: { - r: color.r, - g: color.g, - b: color.b, - a: color.alpha || 1, - }, + const radians: number = degrees * (Math.PI / 180); + + const paramMatch = /(?#[\w\d]+)( (?.+))?/; + const stops = array.map((p, index, array) => { + const paramMatches = p.match(paramMatch); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const color = rgb(parse(paramMatches?.groups?.color || "FF00FF")!); + let position: number = 0; + if (paramMatches?.groups && paramMatches?.groups?.pos) { + if (paramMatches.groups.pos.endsWith("%")) { + position = Number.parseFloat(paramMatches.groups.pos) / 100; + } else if (paramMatches.groups.pos.startsWith("calc(100% - ")) { + const px = Number.parseFloat( + paramMatches.groups.pos + .replace("calc(100% - ", "") + .replace("px)", "") + ); + const size = degrees === 90 || degrees === 270 + ? (this._node as LayoutMixin).height + : (this._node as LayoutMixin).width; + position = (size - px) / size; + } + } else if (index === array.length - 1) { + position = 1; + } + const stop: ColorStop = { + position, + color: { + r: color.r, + g: color.g, + b: color.b, + a: color.alpha || 1, + }, + }; + return stop; + }); + + const gradientPaint: GradientPaint = { + type: "GRADIENT_LINEAR", + gradientStops: stops, + gradientTransform: [ + [Math.cos(radians), Math.sin(radians), 0], + [Math.sin(radians) * -1, Math.cos(radians), 1], + ], }; - return stop; - }); - - const gradientPaint: GradientPaint = { - type: "GRADIENT_LINEAR", - gradientStops: stops, - gradientTransform: [ - [Math.cos(radians), Math.sin(radians), 0], - [Math.sin(radians) * -1, Math.cos(radians), 1], - ], + paint = gradientPaint; + } + } else { + // Assume it's solid + const color = parse(value); + if (!color) { + throw new Error( + `The value "${value}" could not be parsed` + ); + } + + const rgbColor = rgb(color); + const solidPaint: SolidPaint = { + type: "SOLID", + visible: true, + opacity: rgbColor.alpha, + blendMode: "NORMAL", + color: { + r: rgbColor.r, + g: rgbColor.g, + b: rgbColor.b, + }, }; - paint = gradientPaint; + paint = solidPaint; } - } else { - // Assume it's solid - const color = parse(value); - if (!color) { - throw new Error( - `The value "${value}" could not be parsed` - ); + } else if (value && typeof value === "object") { + if (Object.keys(value).includes("color")) { + const color = value as Color; + // console.log(" Color", color); + const solidPaint: SolidPaint = { + type: "SOLID", + visible: true, + opacity: color.color.alpha, + blendMode: "NORMAL", + color: { + r: roundToDecimals((color.color as Rgb).r, 6), + g: roundToDecimals((color.color as Rgb).g, 6), + b: roundToDecimals((color.color as Rgb).b, 6), + }, + }; + paint = solidPaint; } - - const rgbColor = rgb(color); - const solidPaint: SolidPaint = { - type: "SOLID", - visible: true, - opacity: rgbColor.alpha, - blendMode: "NORMAL", - color: { - r: rgbColor.r, - g: rgbColor.g, - b: rgbColor.b, - }, - }; - paint = solidPaint; } } diff --git a/packages/adaptive-ui-designer-figma-plugin/src/figma/utility.ts b/packages/adaptive-ui-designer-figma-plugin/src/figma/utility.ts index 2850f761..45d1e7b6 100644 --- a/packages/adaptive-ui-designer-figma-plugin/src/figma/utility.ts +++ b/packages/adaptive-ui-designer-figma-plugin/src/figma/utility.ts @@ -46,3 +46,7 @@ export const colorToRgba = (color: Rgb): RGBA => { a: color.alpha || 1, }; } + +export const roundToDecimals = (num: number, dec: number): number => { + return parseFloat(num.toFixed(dec)); +} diff --git a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-elements.ts b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-elements.ts index 849ca28d..f712ef9d 100644 --- a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-elements.ts +++ b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-elements.ts @@ -1,6 +1,6 @@ import { customElement, FASTElement, html } from "@microsoft/fast-element"; import { DesignToken, StaticDesignTokenValue } from "@microsoft/fast-foundation"; -import { Swatch } from "@adaptive-web/adaptive-ui"; +import { Color } from "@adaptive-web/adaptive-ui"; import { fillColor } from "@adaptive-web/adaptive-ui/reference"; import { DesignTokenValue, PluginUINodeData } from "@adaptive-web/adaptive-ui-designer-core"; import { UIController } from "./ui-controller.js"; @@ -55,7 +55,13 @@ export class ElementsController { try { if (value) { // TODO figure out a better way to handle storage data types - const color = Swatch.parse((value as unknown) as string); + + let color: Color | null = null; + if (value instanceof Color) { + color = value; + } else { + color = Color.parse((value as unknown) as string); + } if (color) { // TODO fix this logic // console.log(" setting DesignToken value (color)", token.name, value); diff --git a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-tokens.ts b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-tokens.ts index 2255dbe3..ae271b77 100644 --- a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-tokens.ts +++ b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller-tokens.ts @@ -2,7 +2,6 @@ import { calc } from '@csstools/css-calc'; import { observable } from "@microsoft/fast-element"; import { DesignToken } from "@microsoft/fast-foundation"; import { Color } from "@adaptive-web/adaptive-ui"; -import { formatHex8 } from 'culori'; import { DesignTokenValue, PluginUINodeData } from "@adaptive-web/adaptive-ui-designer-core"; import { UIController } from "./ui-controller.js"; import { ElementsController } from "./ui-controller-elements.js"; @@ -110,7 +109,7 @@ export class DesignTokenController { // TODO figure out a better way to handle storage data types // Reconcile with similar block in evaluateEffectiveAppliedDesignToken if (value instanceof Color) { - return formatHex8(value.color); + return value.toString(); } else if (typeof value === "string") { if (value.startsWith("calc")) { return calc(value); diff --git a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller.ts b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller.ts index d359dbef..08805e18 100644 --- a/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller.ts +++ b/packages/adaptive-ui-designer-figma-plugin/src/ui/ui-controller.ts @@ -1,7 +1,7 @@ import { calc } from '@csstools/css-calc'; import { FASTElement, observable } from "@microsoft/fast-element"; import { CSSDesignToken, DesignToken, type ValuesOf } from "@microsoft/fast-foundation"; -import { Color, InteractiveState, InteractiveTokenGroup, StyleProperty, Styles, Swatch } from "@adaptive-web/adaptive-ui"; +import { Color, InteractiveState, InteractiveTokenGroup, StyleProperty, Styles } from "@adaptive-web/adaptive-ui"; import { fillColor } from "@adaptive-web/adaptive-ui/reference"; import { formatHex8 } from 'culori'; import { @@ -382,7 +382,7 @@ export class UIController { if (colorHex) { const parentElement = this._elements.getElementForNode(node).parentElement as FASTElement; // console.log(" setting fill color token on parent element", colorHex, parentElement.id, parentElement.title); - this._elements.setDesignTokenForElement(parentElement, fillColor, Swatch.parse(colorHex)); + this._elements.setDesignTokenForElement(parentElement, fillColor, Color.parse(colorHex)); } const allApplied = this.collectEffectiveAppliedStyles(node); @@ -413,8 +413,8 @@ export class UIController { let value: any = valueOriginal; // let valueDebug: any; if (valueOriginal instanceof Color) { - const swatch = valueOriginal; - value = formatHex8(swatch.color); + const color = valueOriginal; + value = formatHex8(color.color); // valueDebug = swatch.toColorString(); } else if (typeof valueOriginal === "string") { if (valueOriginal.startsWith("calc")) { @@ -423,7 +423,7 @@ export class UIController { value = ret; } } - const fillColorValue = (this._elements.getDesignTokenValue(node, fillColor) as Swatch).toColorString(); + // const fillColorValue = (this._elements.getDesignTokenValue(node, fillColor) as Color).toColorString(); // console.log(" evaluateEffectiveAppliedDesignToken", target, " : ", token.name, " -> ", value, valueDebug, `(from ${info.source})`, "fillColor", fillColorValue); const applied = new AppliedStyleValue(value); diff --git a/packages/adaptive-ui-explorer/src/app.ts b/packages/adaptive-ui-explorer/src/app.ts index 32a99568..3dd1c59f 100644 --- a/packages/adaptive-ui-explorer/src/app.ts +++ b/packages/adaptive-ui-explorer/src/app.ts @@ -1,6 +1,6 @@ import { + Color, Palette, - Swatch, } from "@adaptive-web/adaptive-ui"; import { accentBaseColor, @@ -289,7 +289,7 @@ export class App extends FASTElement { }); } - private layerTokens: Array<[DesignToken, string]> = [ + private layerTokens: Array<[DesignToken, string]> = [ [layerFillFixedPlus1, "+1"], [layerFillFixedBase, "Base"], [layerFillFixedMinus1, "-1"], @@ -303,7 +303,7 @@ export class App extends FASTElement { layerFillBaseLuminance.setValueFor(ds, luminance); return this.layerTokens - .map((conf: [DesignToken, string]): SwatchInfo => { + .map((conf: [DesignToken, string]): SwatchInfo => { const color = conf[0].getValueFor(ds).toColorString(); return { index: this.neutralColors.indexOf(color), diff --git a/packages/adaptive-ui-explorer/src/components/color-block.ts b/packages/adaptive-ui-explorer/src/components/color-block.ts index 01f185eb..a802c6a6 100644 --- a/packages/adaptive-ui-explorer/src/components/color-block.ts +++ b/packages/adaptive-ui-explorer/src/components/color-block.ts @@ -1,4 +1,4 @@ -import { Swatch } from "@adaptive-web/adaptive-ui"; +import { Color } from "@adaptive-web/adaptive-ui"; import { accentFillDiscernibleControlStyles, accentFillIdealControlStyles, @@ -261,9 +261,9 @@ export class ColorBlock extends FASTElement { private updateColor(): void { if (this.color && this.$fastController.isConnected) { - const swatch =Swatch.parse(this.color) - if (swatch) { - fillColor.setValueFor(this, swatch); + const color = Color.parse(this.color) + if (color) { + fillColor.setValueFor(this, color); } } } diff --git a/packages/adaptive-ui-explorer/src/components/layer-background/index.ts b/packages/adaptive-ui-explorer/src/components/layer-background/index.ts index d7c6d9c7..c66bde50 100644 --- a/packages/adaptive-ui-explorer/src/components/layer-background/index.ts +++ b/packages/adaptive-ui-explorer/src/components/layer-background/index.ts @@ -1,4 +1,4 @@ -import { Swatch } from '@adaptive-web/adaptive-ui'; +import { Color } from '@adaptive-web/adaptive-ui'; import { fillColor, layerFillBaseLuminance, @@ -61,7 +61,7 @@ export class LayerBackground extends FASTElement { } if (this.backgroundLayerRecipe !== undefined) { - let swatch: Swatch | null = null; + let swatch: Color | null = null; switch (this.backgroundLayerRecipe) { case "-1": swatch = layerFillFixedMinus1.getValueFor(this); diff --git a/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts b/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts index 8002f10d..1054748a 100644 --- a/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts +++ b/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts @@ -1,16 +1,16 @@ import { html, repeat } from "@microsoft/fast-element"; -import { Color, isDark, Swatch } from "@adaptive-web/adaptive-ui"; +import { Color, isDark } from "@adaptive-web/adaptive-ui"; import { PaletteGradient } from "./palette-gradient.js"; -function getClass(swatch: Swatch, source?: Color, closestSource?: Swatch) { - return swatch.toColorString() === source?.toColorString() +function getClass(color: Color, source?: Color, closestSource?: Color) { + return color.toString() === source?.toString() ? "source" - : swatch.toColorString() === closestSource?.toColorString() + : color.toString() === closestSource?.toString() ? "source closest" : ""; } -function getColor(background: Swatch) { +function getColor(background: Color) { const darkMode = isDark(background); return darkMode ? "white" : "black"; } @@ -18,11 +18,11 @@ function getColor(background: Swatch) { export const paletteGradientTemplate = html` ${repeat( (x) => x.palette?.swatches || [], - html` + html` `, { positioning: true } diff --git a/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts b/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts index bc9a12e1..53c98e29 100644 --- a/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts +++ b/packages/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts @@ -1,4 +1,4 @@ -import { Palette, Swatch } from "@adaptive-web/adaptive-ui"; +import { Color, Palette } from "@adaptive-web/adaptive-ui"; import { customElement, FASTElement, observable } from "@microsoft/fast-element"; import { paletteGradientStyles as styles } from "./palette-gradient.styles.js"; import { paletteGradientTemplate as template } from "./palette-gradient.template.js"; @@ -9,7 +9,7 @@ import { paletteGradientTemplate as template } from "./palette-gradient.template styles, }) export class PaletteGradient extends FASTElement { - public closestSource?: Swatch; + public closestSource?: Color; @observable public palette?: Palette; diff --git a/packages/adaptive-ui-explorer/src/components/style-example.ts b/packages/adaptive-ui-explorer/src/components/style-example.ts index bc2beb92..00939e69 100644 --- a/packages/adaptive-ui-explorer/src/components/style-example.ts +++ b/packages/adaptive-ui-explorer/src/components/style-example.ts @@ -1,4 +1,4 @@ -import { InteractiveTokenGroup, StyleProperty, Styles, Swatch, TypedCSSDesignToken } from "@adaptive-web/adaptive-ui"; +import { Color, InteractiveTokenGroup, StyleProperty, Styles, TypedCSSDesignToken } from "@adaptive-web/adaptive-ui"; import { densityControl, fillColor } from '@adaptive-web/adaptive-ui/reference'; import { componentBaseStyles } from "@adaptive-web/adaptive-web-components"; import { css, customElement, FASTElement, html, observable, repeat, volatile, when } from "@microsoft/fast-element"; @@ -52,9 +52,9 @@ const styles = css` interface StyleValue { type: SwatchType; tokenName: string; - foregroundRecipe?: TypedCSSDesignToken; - fillRecipe?: TypedCSSDesignToken; - outlineRecipe?: TypedCSSDesignToken; + foregroundRecipe?: TypedCSSDesignToken; + fillRecipe?: TypedCSSDesignToken; + outlineRecipe?: TypedCSSDesignToken; } @customElement({ diff --git a/packages/adaptive-ui-explorer/src/components/swatch.ts b/packages/adaptive-ui-explorer/src/components/swatch.ts index fe8e3b95..1300225c 100644 --- a/packages/adaptive-ui-explorer/src/components/swatch.ts +++ b/packages/adaptive-ui-explorer/src/components/swatch.ts @@ -1,4 +1,4 @@ -import { Swatch } from "@adaptive-web/adaptive-ui"; +import { Color } from "@adaptive-web/adaptive-ui"; import { densityControl, fillColor, neutralStrokeReadableRest, typeRampMinus1FontSize } from "@adaptive-web/adaptive-ui/reference"; import { componentBaseStyles } from "@adaptive-web/adaptive-web-components"; import { @@ -86,19 +86,19 @@ export class AppSwatch extends FASTElement { public recipeName?: string; @observable - public foregroundRecipe?: DesignToken; + public foregroundRecipe?: DesignToken; protected foregroundRecipeChanged() { this.updateObservables(); } @observable - public fillRecipe?: DesignToken; + public fillRecipe?: DesignToken; protected fillRecipeChanged() { this.updateObservables(); } @observable - public outlineRecipe?: DesignToken; + public outlineRecipe?: DesignToken; protected outlineRecipeChanged() { this.updateObservables(); } @@ -136,13 +136,13 @@ export class AppSwatch extends FASTElement { this.updateColorValue(); } - private tokenCSS(token?: DesignToken): string { + private tokenCSS(token?: DesignToken): string { return token && typeof (token as any).createCSS === "function" ? (token as any).createCSS() : ""; } - private evaluateToken(token?: DesignToken): string { + private evaluateToken(token?: DesignToken): string { return token?.getValueFor(this).toColorString() || ""; } @@ -156,7 +156,7 @@ export class AppSwatch extends FASTElement { : background; } - private formatContrast(a?: DesignToken, b?: DesignToken): string { + private formatContrast(a?: DesignToken, b?: DesignToken): string { return a && b ? wcagContrast( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -168,8 +168,8 @@ export class AppSwatch extends FASTElement { } private formatContrastMessage( - a?: DesignToken, - b?: DesignToken + a?: DesignToken, + b?: DesignToken ): string { return `Contrast: ${this.formatContrast(a, b)} : 1`; } diff --git a/packages/adaptive-ui/docs/api-report.md b/packages/adaptive-ui/docs/api-report.md index f04aa082..d024723b 100644 --- a/packages/adaptive-ui/docs/api-report.md +++ b/packages/adaptive-ui/docs/api-report.md @@ -4,6 +4,7 @@ ```ts +import { AddBehavior } from '@microsoft/fast-element'; import { Color as Color_2 } from 'culori/fn'; import { ComposableStyles } from '@microsoft/fast-element'; import { CSSDesignToken } from '@microsoft/fast-foundation'; @@ -11,11 +12,12 @@ import { CSSDirective } from '@microsoft/fast-element'; import { DesignToken } from '@microsoft/fast-foundation'; import { DesignTokenResolver } from '@microsoft/fast-foundation'; import { ElementStyles } from '@microsoft/fast-element'; +import { Rgb } from 'culori/fn'; import { TypedCSSDesignToken as TypedCSSDesignToken_2 } from '../adaptive-design-tokens.js'; import { ValuesOf } from '@microsoft/fast-foundation'; // @public -export class BasePalette implements Palette { +export class BasePalette implements Palette { constructor(source: Color, swatches: ReadonlyArray); readonly closestIndexCache: Map; closestIndexOf(reference: RelativeLuminance): number; @@ -29,13 +31,13 @@ export class BasePalette implements Palette { } // @internal -export const _black: Swatch; +export const _black: Color; // @public -export function blackOrWhiteByContrast(reference: Swatch, minContrast: number, defaultBlack: boolean): Swatch; +export function blackOrWhiteByContrast(reference: Paint, minContrast: number, defaultBlack: boolean): Color; // @public -export function blackOrWhiteByContrastSet(set: InteractiveSwatchSet, minContrast: number, defaultBlack: boolean): InteractiveSwatchSet; +export function blackOrWhiteByContrastSet(set: InteractivePaintSet, minContrast: number, defaultBlack: boolean): InteractiveColorSet; // @public export type BooleanCondition = string; @@ -56,10 +58,13 @@ export const BorderThickness: { }; // @public -export class Color implements RelativeLuminance, CSSDirective { - constructor(color: Color_2); +export function calculateOverlayColor(match: Color_2, background: Color_2): Rgb; + +// @public +export class Color extends Paint { + constructor(color: Color_2, intendedColor?: Color); + static asOverlay(intendedColor: Color, reference: Color): Color; readonly color: Color_2; - contrast: (b: RelativeLuminance) => number; createCSS: () => string; static from(obj: { r: number; @@ -68,29 +73,31 @@ export class Color implements RelativeLuminance, CSSDirective { alpha?: number; }): Color; static fromRgb(r: number, g: number, b: number, alpha?: number): Color; + protected readonly _intendedColor?: Color; static parse(color: string): Color | undefined; - get relativeLuminance(): number; - toColorString(): string; - toString: () => string; + // @deprecated + toColorString: () => string; + toString(): string; + static unsafeOpacity(color: Color, alpha: number): Color; } // @public -export type ColorRecipe = RecipeOptional; +export type ColorRecipe = RecipeOptional; // @public -export type ColorRecipeBySet = Recipe; +export type ColorRecipeBySet = Recipe; // @public -export type ColorRecipeBySetEvaluate = RecipeEvaluate; +export type ColorRecipeBySetEvaluate = RecipeEvaluate; // @public -export type ColorRecipeEvaluate = RecipeEvaluateOptional; +export type ColorRecipeEvaluate = RecipeEvaluateOptional; // @public -export type ColorRecipePalette = Recipe; +export type ColorRecipePalette = Recipe; // @public -export type ColorRecipePaletteEvaluate = RecipeEvaluate; +export type ColorRecipePaletteEvaluate = RecipeEvaluate; // @public export type ColorRecipePaletteParams = ColorRecipeParams & { @@ -99,7 +106,7 @@ export type ColorRecipePaletteParams = ColorRecipeParams & { // @public export type ColorRecipeParams = { - reference: Swatch | null; + reference: Paint | null; }; // @public @@ -125,10 +132,10 @@ export type Condition = BooleanCondition | StringCondition; export function contrast(a: RelativeLuminance, b: RelativeLuminance): number; // @public -export function contrastAndDeltaSwatchSet(palette: Palette, reference: Swatch, minContrast: number, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number, disabledPalette?: Palette, direction?: PaletteDirection, zeroAsTransparent?: boolean): InteractiveSwatchSet; +export function contrastAndDeltaSwatchSet(palette: Palette, reference: Paint, minContrast: number, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number, disabledPalette?: Palette, direction?: PaletteDirection, zeroAsTransparent?: boolean): InteractiveColorSet; // @public -export function contrastSwatch(palette: Palette, reference: Swatch, minContrast: number, direction?: PaletteDirection): Swatch; +export function contrastSwatch(palette: Palette, reference: Paint, minContrast: number, direction?: PaletteDirection): Color; // @public export const convertStylesToFocusState: (styles: Styles) => Styles; @@ -144,10 +151,10 @@ export const CornerRadius: { export const create: typeof DesignToken.create; // @public -export function createForegroundSet(foregroundRecipe: TypedDesignToken, background: InteractiveTokenGroup): InteractiveTokenGroup; +export function createForegroundSet(foregroundRecipe: TypedDesignToken, background: InteractiveTokenGroup): InteractiveTokenGroup; // @public -export function createForegroundSetBySet(foregroundRecipe: TypedDesignToken, background: InteractiveTokenGroup): InteractiveTokenGroup; +export function createForegroundSetBySet(foregroundRecipe: TypedDesignToken, background: InteractiveTokenGroup): InteractiveTokenGroup; // Warning: (ae-internal-missing-underscore) The name "createNonCss" should be prefixed with an underscore because the declaration is marked as @internal // @@ -158,22 +165,22 @@ export function createNonCss(name: string): DesignToken; export function createTokenColor(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedCSSDesignToken; // @public -export function createTokenColorRecipe(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeEvaluate): TypedDesignToken>; +export function createTokenColorRecipe(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeEvaluate): TypedDesignToken>; // @public -export function createTokenColorRecipeBySet(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeBySetEvaluate): TypedDesignToken>; +export function createTokenColorRecipeBySet(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeBySetEvaluate): TypedDesignToken>; // @public -export function createTokenColorRecipeForPalette(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipePaletteEvaluate): TypedDesignToken>; +export function createTokenColorRecipeForPalette(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipePaletteEvaluate): TypedDesignToken>; // @public -export function createTokenColorRecipeValue(recipeToken: TypedDesignToken>): TypedCSSDesignToken; +export function createTokenColorRecipeValue(recipeToken: TypedDesignToken>): TypedCSSDesignToken; // @public -export function createTokenColorRecipeWithPalette(recipeToken: TypedDesignToken>, paletteToken: DesignToken): TypedDesignToken>; +export function createTokenColorRecipeWithPalette(recipeToken: TypedDesignToken>, paletteToken: DesignToken): TypedDesignToken>; // @public -export function createTokenColorSet(recipeToken: TypedDesignToken): InteractiveTokenGroup; +export function createTokenColorSet(recipeToken: TypedDesignToken): InteractiveTokenGroup; // @public export function createTokenDelta(baseName: string, state: InteractiveState | string, value: number | DesignToken): TypedDesignToken; @@ -211,23 +218,26 @@ export function createTokenNumber(name: string, intendedFor?: StyleProperty | St // @public export function createTokenNumberNonStyling(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedDesignToken; +// @public +export function createTokenPaint(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedCSSDesignToken; + // @public export function createTokenRecipe(baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: RecipeEvaluate): TypedDesignToken>; // @public export const createTokenShadow: (name: string) => TypedCSSDesignToken_2; -// @public +// @public @deprecated export function createTokenSwatch(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedCSSDesignToken; // @public export const createTyped: typeof TypedCSSDesignToken.createTyped; // @public -export function deltaSwatch(palette: Palette, reference: Swatch, delta: number, direction?: PaletteDirection): Swatch; +export function deltaSwatch(palette: Palette, reference: Paint, delta: number, direction?: PaletteDirection): Color; // @public -export function deltaSwatchSet(palette: Palette, reference: Swatch, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta?: number, disabledPalette?: Palette, direction?: PaletteDirection, zeroAsTransparent?: boolean): InteractiveSwatchSet; +export function deltaSwatchSet(palette: Palette, reference: Paint, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta?: number, disabledPalette?: Palette, direction?: PaletteDirection, zeroAsTransparent?: boolean): InteractiveColorSet; // @public export const densityAdjustmentUnits: TypedDesignToken; @@ -288,6 +298,7 @@ export const DesignTokenType: { readonly fontVariations: "fontVariations"; readonly palette: "palette"; readonly recipe: "recipe"; + readonly paint: "paint"; readonly string: "string"; }; @@ -320,9 +331,9 @@ export type ElevationRecipeEvaluate = RecipeEvaluate; // @public (undocumented) export const Fill: { - backgroundAndForeground: (background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken) => StyleProperties; - backgroundAndForegroundBySet: (background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken) => StyleProperties; - foregroundNonInteractiveWithDisabled: (foreground: TypedCSSDesignToken, disabled: TypedCSSDesignToken) => StyleProperties; + backgroundAndForeground: (background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken) => StyleProperties; + backgroundAndForegroundBySet: (background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken) => StyleProperties; + foregroundNonInteractiveWithDisabled: (foreground: TypedCSSDesignToken, disabled: TypedCSSDesignToken) => StyleProperties; }; // @public @@ -340,25 +351,33 @@ export interface FocusDefinition { } // @public -export function idealColorDeltaSwatchSet(palette: Palette, reference: Swatch, minContrast: number, idealColor: Color, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number, disabledPalette?: Palette, direction?: PaletteDirection): InteractiveSwatchSet; +export function idealColorDeltaSwatchSet(palette: Palette, reference: Paint, minContrast: number, idealColor: Color, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number, disabledPalette?: Palette, direction?: PaletteDirection): InteractiveColorSet; + +// @public +export type InteractiveColorRecipe = ColorRecipe; // @public -export type InteractiveColorRecipe = ColorRecipe; +export type InteractiveColorRecipeBySet = ColorRecipeBySet; // @public -export type InteractiveColorRecipeBySet = ColorRecipeBySet; +export type InteractiveColorRecipeBySetEvaluate = ColorRecipeBySetEvaluate; // @public -export type InteractiveColorRecipeBySetEvaluate = ColorRecipeBySetEvaluate; +export type InteractiveColorRecipeEvaluate = ColorRecipeEvaluate; // @public -export type InteractiveColorRecipeEvaluate = ColorRecipeEvaluate; +export type InteractiveColorRecipePalette = ColorRecipePalette; // @public -export type InteractiveColorRecipePalette = ColorRecipePalette; +export type InteractiveColorRecipePaletteEvaluate = ColorRecipePaletteEvaluate; + +// @public +export interface InteractiveColorSet extends InteractiveValues { +} // @public -export type InteractiveColorRecipePaletteEvaluate = ColorRecipePaletteEvaluate; +export interface InteractivePaintSet extends InteractiveValues { +} // @public export enum InteractiveState { @@ -370,11 +389,7 @@ export enum InteractiveState { } // @public -export interface InteractiveSwatchSet extends InteractiveValues { -} - -// @public -export function interactiveSwatchSetAsOverlay(set: InteractiveSwatchSet, reference: Swatch, asOverlay: boolean): InteractiveSwatchSet; +export function interactiveSwatchSetAsOverlay(set: InteractiveColorSet, reference: Color, asOverlay: boolean): InteractiveColorSet; // @public export interface InteractiveTokenGroup extends MakePropertyRequired, InteractiveValues> { @@ -405,7 +420,7 @@ export type InteractivityDefinition = { export function isDark(color: RelativeLuminance): boolean; // @public -export function luminanceSwatch(luminance: number): Swatch; +export function luminanceSwatch(luminance: number): Color; // @public (undocumented) export type MakePropertyOptional = Omit & { @@ -425,7 +440,16 @@ export const Padding: { }; // @public -export interface Palette { +export abstract class Paint implements RelativeLuminance, CSSDirective { + protected constructor(relativeLuminance: number); + // (undocumented) + contrast(b: RelativeLuminance): number; + createCSS(add: AddBehavior): ComposableStyles; + get relativeLuminance(): number; +} + +// @public +export interface Palette { closestIndexOf(reference: RelativeLuminance): number; colorContrast(reference: RelativeLuminance, minContrast: number, initialIndex?: number, direction?: PaletteDirection): T; delta(reference: RelativeLuminance, delta: number, direction: PaletteDirection): T; @@ -447,7 +471,7 @@ export const PaletteDirectionValue: Readonly<{ export type PaletteDirectionValue = typeof PaletteDirectionValue[keyof typeof PaletteDirectionValue]; // @public -export class PaletteOkhsl extends BasePalette { +export class PaletteOkhsl extends BasePalette { // (undocumented) static from(source: Color | string, options?: Partial): PaletteOkhsl; } @@ -460,8 +484,8 @@ export interface PaletteOkhslOptions { } // @public -export class PaletteRGB extends BasePalette { - static from(source: Swatch | string, options?: Partial): PaletteRGB; +export class PaletteRGB extends BasePalette { + static from(source: Color | string, options?: Partial): PaletteRGB; } // @public @@ -489,6 +513,7 @@ export interface RecipeOptional { // @public export interface RelativeLuminance { + contrast: (a: RelativeLuminance) => number; readonly relativeLuminance: number; } @@ -542,11 +567,11 @@ export interface SerializableStyleRule { // @public export class Shadow implements CSSDirective { - constructor(color: Swatch, xOffset: number, yOffset: number, blurRadius?: number | undefined, spread?: number | undefined); + constructor(color: Color, xOffset: number, yOffset: number, blurRadius?: number | undefined, spread?: number | undefined); // (undocumented) blurRadius?: number | undefined; // (undocumented) - color: Swatch; + color: Color; // (undocumented) createCSS(): string; // (undocumented) @@ -716,9 +741,8 @@ export class Styles { // @public export type StyleValue = CSSDesignToken | InteractiveValues | CSSDirective | string | number; -// @public +// @public @deprecated export class Swatch extends Color { - protected constructor(color: Color_2, intendedColor?: Swatch); static asOverlay(intendedColor: Swatch, reference: Swatch): Swatch; static from(obj: { r: number; @@ -729,12 +753,12 @@ export class Swatch extends Color { static fromColor(color: Color): Swatch; static fromRgb(r: number, g: number, b: number, alpha?: number): Swatch; static parse(color: string): Swatch | undefined; - get relativeLuminance(): number; + // @deprecated toTransparent(alpha?: number): Swatch; } // @public -export function swatchAsOverlay(swatch: Swatch | null, reference: Swatch, asOverlay: boolean): Swatch | null; +export function swatchAsOverlay(color: Color | null, reference: Color, asOverlay: boolean): Color | null; // @public export interface TokenGroup extends MakePropertyOptional { @@ -770,7 +794,7 @@ export interface TypedDesignToken extends DesignTokenMetadata { } // @internal -export const _white: Swatch; +export const _white: Color; // (No @packageDocumentation comment for this package) diff --git a/packages/adaptive-ui/src/core/adaptive-design-tokens.ts b/packages/adaptive-ui/src/core/adaptive-design-tokens.ts index d1ebe772..10a537a5 100644 --- a/packages/adaptive-ui/src/core/adaptive-design-tokens.ts +++ b/packages/adaptive-ui/src/core/adaptive-design-tokens.ts @@ -30,6 +30,7 @@ export const DesignTokenType = { fontVariations: "fontVariations", palette: "palette", recipe: "recipe", + paint: "paint", // `color` or `gradient` string: "string", } as const; @@ -53,7 +54,7 @@ export type DesignTokenMetadata = { // A slight alteration of the mixin pattern to hide an internal function from the public type. class DesignTokenMetadataImpl implements DesignTokenMetadata { // TODO: This needs to support multiple types, tokens in Adaptive UI might represent different value - // types, like a Swatch type commonly refers to a `color` but may also be a `gradient`. (see `create.ts`) + // types, like a `paint` type commonly refers to a `color` but may also be a `gradient`. (see `create.ts`) private _type: DesignTokenType = "string"; /** diff --git a/packages/adaptive-ui/src/core/color/README.md b/packages/adaptive-ui/src/core/color/README.md index 5066c686..3301b3ef 100644 --- a/packages/adaptive-ui/src/core/color/README.md +++ b/packages/adaptive-ui/src/core/color/README.md @@ -2,27 +2,27 @@ Color recipes are algorithmic patterns that produce individual or sets of colors from a variety of inputs. Components can apply these recipes to achieve expressive theming options while maintaining color accessability targets. -## Color -A Swatch is a representation of a color that has a `relativeLuminance` value and a method to convert the swatch to a color string. It is the base type for color design tokens. +## Paint +A `Paint` is a representation of a color that has a `relativeLuminance` value and a method to convert to a color string. It is the base type for color design tokens. -## Swatch -A Swatch is an extension of a Color for use in a recipe. It adds support for calculating a color as an overlay or transparency -over another Swatch. +## Color +A `Color` is an extension of `Paint` for use in a recipe. It adds support for calculating a color as an overlay or transparency +over another `Color`. -**Example: Creating a Swatch** +**Example: Creating a Color** ```ts -import { Swatch } from "@adaptive-web/adaptive-ui"; +import { Color } from "@adaptive-web/adaptive-ui"; -const red = Swatch.fromRgb(1, 0, 0); +const red = Color.fromRgb(1, 0, 0); ``` ## Palette -A palette is a collection `Swatch` instances, ordered by relative luminance, and provides mechanisms to safely retrieve swatches by index and by target contrast ratios. It also contains a `source` color, which is the color from which the palette is derived. +A palette is a collection `Color` instances, ordered by relative luminance, and provides mechanisms to safely retrieve swatches by index and by target contrast ratios. It also contains a `source` color, which is the color from which the palette is derived. ### PaletteRGB -An implementation of `Palette` of `Swatch` instances with RGB colors. +An implementation of `Palette` of `Color` instances with RGB colors. ```ts -// Create a PaletteRGB from a Swatch +// Create a PaletteRGB from a Color const redPalette = PaletteRGB.from(red): ``` diff --git a/packages/adaptive-ui/src/core/color/color.ts b/packages/adaptive-ui/src/core/color/color.ts index 99c3bfae..993defd3 100644 --- a/packages/adaptive-ui/src/core/color/color.ts +++ b/packages/adaptive-ui/src/core/color/color.ts @@ -1,6 +1,7 @@ +import { cssDirective } from "@microsoft/fast-element"; import { type Color as CuloriColor, formatHex, formatRgb, modeLrgb, modeRgb, parse, type Rgb, useMode, wcagLuminance } from "culori/fn"; -import { CSSDirective, cssDirective } from "@microsoft/fast-element"; -import { contrast, type RelativeLuminance } from "./utilities/relative-luminance.js"; +import { Paint } from "./paint.js"; +import { calculateOverlayColor } from "./utilities/opacity.js"; useMode(modeRgb); // For luminance @@ -12,29 +13,29 @@ useMode(modeLrgb); * @public */ @cssDirective() -export class Color implements RelativeLuminance, CSSDirective { +export class Color extends Paint { /** * The underlying Color value. */ public readonly color: CuloriColor; - private readonly _relativeLuminance: number; + /** + * The opaque value this Color represents if opacity is used. + */ + protected readonly _intendedColor?: Color; /** * Creates a new Color. * - * @param color - The underlying Color value + * @param color - The underlying color value. + * @param intendedColor - If `color.alpha` < 1 this tracks the intended opaque color value for dependent calculations. */ - constructor(color: CuloriColor) { + constructor(color: CuloriColor, intendedColor?: Color) { + const lum = intendedColor ? intendedColor.relativeLuminance : + color.alpha !== undefined && color.alpha < 1 ? Number.NaN : wcagLuminance(color); + super(lum); this.color = Object.freeze(color); - this._relativeLuminance = wcagLuminance(this.color); - } - - /** - * {@inheritdoc RelativeLuminance.relativeLuminance} - */ - public get relativeLuminance(): number { - return this._relativeLuminance; + this._intendedColor = intendedColor; } /** @@ -42,39 +43,32 @@ export class Color implements RelativeLuminance, CSSDirective { * * @returns The color value in string format */ - public toColorString(): string { + public toString(): string { return this.color.alpha !== undefined && this.color.alpha < 1 ? formatRgb(this.color) : formatHex(this.color); } /** - * {@inheritdoc Color.toColorString} - */ - public toString = this.toColorString; - - /** - * Gets the contrast between this Color and another. + * {@inheritdoc Color.toString} * - * @returns The contrast between the two luminance values, for example, 4.54 + * @deprecated Use toString */ - public contrast = contrast.bind(null, this); - + public toColorString = this.toString; + /** * Gets this color value as a string for use in css. * * @returns The color value in a valid css string format */ - public createCSS = this.toColorString; + public createCSS = this.toString; /** - * Creates a new Color from and object with R, G, and B values expressed as a number between 0 to 1. + * Creates a new Color from an object with R, G, and B values expressed as a number between 0 to 1. * * @param obj - An object with `r`, `g`, and `b`, and optional `alpha` values expressed as a number between 0 and 1. * @returns A new Color */ public static from(obj: { r: number; g: number; b: number, alpha?: number }): Color { const color: Rgb = { mode: "rgb", ...obj }; - console.log("from", color); - return new Color(color); } @@ -89,7 +83,6 @@ export class Color implements RelativeLuminance, CSSDirective { */ public static fromRgb(r: number, g: number, b: number, alpha?: number): Color { const color: Rgb = { mode: "rgb", r, g, b, alpha }; - console.log("fromRgb", color); return new Color(color); } @@ -105,4 +98,37 @@ export class Color implements RelativeLuminance, CSSDirective { return new Color(parsedColor); } } + + /** + * Creates a new Color as an overlay representation of the `intendedColor` over `reference`. + * + * Currently the overlay will only be black or white, so this works best with a plain grey neutral palette. + * Otherwise it will attempt to match the luminance value of the Color, so it will likely be close, but not an + * exact match to the color from another palette. + * + * @param intendedColor - The Color the overlay should look like over the `reference` Color. + * @param reference - The Color under the overlay color. + * @returns A semitransparent Color that represents the `intendedColor` over the `reference` Color. + */ + public static asOverlay(intendedColor: Color, reference: Color): Color { + const refColor = reference instanceof Color && reference._intendedColor ? reference._intendedColor.color : reference.color; + const colorWithAlpha = calculateOverlayColor(intendedColor.color, refColor); + + return new Color(colorWithAlpha, intendedColor); + } + + /** + * Creates a new Color from another Color and the target opacity. + * + * @remarks It's "unsafe" because it can't be used for contrast calculations. + * + * @param color - A Color object without opacity. + * @param alpha - The opacity expressed as a number between 0 and 1. + * @returns A new Color + */ + public static unsafeOpacity(color: Color, alpha: number): Color { + const transparentColor = { ...color.color }; + transparentColor.alpha = alpha; + return new Color(transparentColor); + } } diff --git a/packages/adaptive-ui/src/core/color/index.ts b/packages/adaptive-ui/src/core/color/index.ts index f05f09ef..93da9b92 100644 --- a/packages/adaptive-ui/src/core/color/index.ts +++ b/packages/adaptive-ui/src/core/color/index.ts @@ -1,6 +1,7 @@ export * from "./recipes/index.js"; export * from "./utilities/index.js"; export * from "./color.js"; +export * from "./paint.js"; export * from "./palette-base.js"; export * from "./palette-okhsl.js"; export * from "./palette-rgb.js"; diff --git a/packages/adaptive-ui/src/core/color/paint.ts b/packages/adaptive-ui/src/core/color/paint.ts new file mode 100644 index 00000000..6bebb6c6 --- /dev/null +++ b/packages/adaptive-ui/src/core/color/paint.ts @@ -0,0 +1,42 @@ +import { AddBehavior, ComposableStyles, CSSDirective } from "@microsoft/fast-element"; +import { contrast, RelativeLuminance } from "./utilities/relative-luminance.js"; + +/** + * Abstract representation of a value which can be used to paint a style like a fill or border. + * + * See {@link Color} for concrete implementation. + * + * @public + */ +export abstract class Paint implements RelativeLuminance, CSSDirective { + readonly #relativeLuminance: number; + + /** + * Creates a new Color. + * + * @param color - The underlying Color value + */ + protected constructor(relativeLuminance: number) { + this.#relativeLuminance = relativeLuminance; + } + + /** + * Creates a CSS fragment to interpolate into the CSS document. + * + * @returns - the string to interpolate into CSS + */ + createCSS(add: AddBehavior): ComposableStyles { + throw new Error("Method not implemented."); + } + + /** + * {@inheritdoc RelativeLuminance.relativeLuminance} + */ + public get relativeLuminance(): number { + return this.#relativeLuminance; + } + + public contrast(b: RelativeLuminance): number { + return contrast(this, b); + } +} diff --git a/packages/adaptive-ui/src/core/color/palette-base.ts b/packages/adaptive-ui/src/core/color/palette-base.ts index 5460da7a..7e865bc4 100644 --- a/packages/adaptive-ui/src/core/color/palette-base.ts +++ b/packages/adaptive-ui/src/core/color/palette-base.ts @@ -1,17 +1,16 @@ import { Color } from "./color.js"; import { Palette, PaletteDirection, PaletteDirectionValue, resolvePaletteDirection } from "./palette.js"; -import { Swatch } from "./swatch.js"; import { binarySearch } from "./utilities/binary-search.js"; import { directionByIsDark } from "./utilities/direction-by-is-dark.js"; import { contrast, RelativeLuminance } from "./utilities/relative-luminance.js"; /** * A base {@link Palette} with a common implementation of the interface. Use PaletteRGB for an implementation - * of a palette generation algorithm that is ready to be used directly, or extend this class to generate custom Swatches. + * of a palette generation algorithm that is ready to be used directly, or extend this class to generate custom swatches. * * @public */ -export class BasePalette implements Palette { +export class BasePalette implements Palette { /** * {@inheritdoc Palette.source} */ @@ -28,12 +27,12 @@ export class BasePalette implements Palette { readonly lastIndex: number; /** - * A copy of the `Swatch`es in reverse order, used for optimized searching. + * A copy of the swatches in reverse order, used for optimized searching. */ readonly reversedSwatches: ReadonlyArray; /** - * Cache from `relativeLuminance` to `Swatch` index in the `Palette`. + * Cache from `relativeLuminance` to swatch index in the `Palette`. */ readonly closestIndexCache = new Map(); @@ -41,7 +40,7 @@ export class BasePalette implements Palette { * Creates a new Palette. * * @param source - The source color for the Palette - * @param swatches - All Swatches in the Palette + * @param swatches - All swatches in the Palette */ constructor(source: Color, swatches: ReadonlyArray) { this.source = source; diff --git a/packages/adaptive-ui/src/core/color/palette-okhsl.ts b/packages/adaptive-ui/src/core/color/palette-okhsl.ts index e772a7eb..72fe09a5 100644 --- a/packages/adaptive-ui/src/core/color/palette-okhsl.ts +++ b/packages/adaptive-ui/src/core/color/palette-okhsl.ts @@ -1,7 +1,6 @@ import { clampChroma, interpolate, modeOkhsl, modeRgb, samples, useMode } from "culori/fn"; import { Color } from "./color.js"; import { BasePalette } from "./palette-base.js"; -import { Swatch } from "./swatch.js"; import { _black, _white } from "./utilities/color-constants.js"; const okhsl = useMode(modeOkhsl); @@ -51,7 +50,7 @@ const defaultPaletteOkhslOptions: PaletteOkhslOptions = { * * @public */ -export class PaletteOkhsl extends BasePalette { +export class PaletteOkhsl extends BasePalette { public static from(source: Color | string, options?: Partial): PaletteOkhsl { const color = source instanceof Color ? source : Color.parse(source); if (!color) { @@ -87,7 +86,7 @@ export class PaletteOkhsl extends BasePalette { const ramp = [...samplesLeft, ...samplesRight.slice(1)]; const rampSwatches = ramp.map((value) => - Swatch.from(rgb(clampChroma(value, "okhsl"))) + Color.from(rgb(clampChroma(value, "okhsl"))) ); // It's important that the ends are full white and black. diff --git a/packages/adaptive-ui/src/core/color/palette-rgb.spec.ts b/packages/adaptive-ui/src/core/color/palette-rgb.spec.ts index a02f1b01..ab34a9c8 100644 --- a/packages/adaptive-ui/src/core/color/palette-rgb.spec.ts +++ b/packages/adaptive-ui/src/core/color/palette-rgb.spec.ts @@ -1,18 +1,18 @@ import chai from "chai"; +import { Color } from "./color.js"; import { PaletteRGB, PaletteRGBOptions } from "./palette-rgb.js"; -import { Swatch } from "./swatch.js"; import { contrast } from "./utilities/relative-luminance.js"; const { expect } = chai; const greyHex = "#808080"; -const greySwatch = Swatch.parse(greyHex)!; +const greyColor = Color.parse(greyHex)!; describe("PaletteRGB.from", () => { - it("should create a palette from the provided swatch", () => { - const palette = PaletteRGB.from(greySwatch); + it("should create a palette from the provided Color", () => { + const palette = PaletteRGB.from(greyColor); - expect(palette.source).to.equal(greySwatch); + expect(palette.source).to.equal(greyColor); }); it("should create a palette from the provided hex color", () => { @@ -26,7 +26,7 @@ describe("PaletteRGB.from", () => { stepContrast: 1.07, stepContrastRamp: 0, }; - const palette = PaletteRGB.from(greySwatch, options); + const palette = PaletteRGB.from(greyColor, options); expect(contrast(palette.swatches[0], palette.swatches[1]), "at least 1.07:1 between 0 and 1").to.be.gte(1.07); expect(contrast(palette.swatches[20], palette.swatches[21]), "at least 1.07:1 between 20 and 21").to.be.gte( diff --git a/packages/adaptive-ui/src/core/color/palette-rgb.ts b/packages/adaptive-ui/src/core/color/palette-rgb.ts index 701f572f..82832cd7 100644 --- a/packages/adaptive-ui/src/core/color/palette-rgb.ts +++ b/packages/adaptive-ui/src/core/color/palette-rgb.ts @@ -1,6 +1,6 @@ import { clampRgb, type Hsl, interpolate, modeHsl, modeLab, modeRgb, type Rgb, useMode } from "culori/fn"; import { BasePalette } from "./palette-base.js"; -import { Swatch } from "./swatch.js"; +import { Color } from "./color.js"; import { contrast } from "./utilities/relative-luminance.js"; import { _black, _white } from "./utilities/color-constants.js"; @@ -9,10 +9,10 @@ const lab = useMode(modeLab); const rgb = useMode(modeRgb); /** - * A utility Palette that generates many Swatches used for selection in the actual Palette. + * A utility Palette that generates many swatches used for selection in the actual Palette. * The algorithm uses the LAB color space and keeps the saturation from the source color throughout. */ -class HighResolutionPaletteRGB extends BasePalette { +class HighResolutionPaletteRGB extends BasePalette { /** * Bump the saturation if it falls below the reference color saturation. * @@ -38,9 +38,9 @@ class HighResolutionPaletteRGB extends BasePalette { * @returns Output number, 0 to 0.5 */ private static ramp(l: number) { - const inputval = l / 100; - if (inputval > 0.5) return (inputval - 0.5) / 0.5; //from 0.500001in = 0.00000001out to 1.0in = 1.0out - return 2 * inputval; //from 0in = 0out to 0.5in = 1.0out + const inputVal = l / 100; + if (inputVal > 0.5) return (inputVal - 0.5) / 0.5; //from 0.500001in = 0.00000001out to 1.0in = 1.0out + return 2 * inputVal; //from 0in = 0out to 0.5in = 1.0out } /** @@ -49,8 +49,8 @@ class HighResolutionPaletteRGB extends BasePalette { * @param source - The source color * @returns The Palette based on the `source` color */ - static from(source: Swatch): HighResolutionPaletteRGB { - const swatches: Swatch[] = []; + static from(source: Color): HighResolutionPaletteRGB { + const swatches: Color[] = []; const labSource = lab(source.color); const lab0 = clampRgb(rgb({ mode: "lab", l: 0, a: labSource.a, b: labSource.b })); @@ -84,7 +84,7 @@ class HighResolutionPaletteRGB extends BasePalette { rgb = HighResolutionPaletteRGB.saturationBump(lab50, rgb); - swatches.push(Swatch.from(rgb)); + swatches.push(Color.from(rgb)); } return new HighResolutionPaletteRGB(source, swatches); @@ -132,14 +132,14 @@ const defaultPaletteRGBOptions: PaletteRGBOptions = { }; /** - * An implementation of a {@link Palette} that has a consistent minimum contrast value between Swatches. + * An implementation of a {@link Palette} that has a consistent minimum contrast value between swatches. * This is useful for UI as it means the perception of the difference between colors the same distance * apart in the Palette will be consistent whether the colors are light yellow or dark red. * It generates its curve using the LAB color space and maintains the saturation of the source color throughout. * * @public */ -export class PaletteRGB extends BasePalette { +export class PaletteRGB extends BasePalette { /** * Adjust one end of the contrast-based palette so it doesn't abruptly fall to black (or white). * @@ -149,15 +149,15 @@ export class PaletteRGB extends BasePalette { * @param direction - The end to adjust */ private static adjustEnd( - swatchContrast: (swatch: Swatch) => number, + swatchContrast: (swatch: Color) => number, referencePalette: HighResolutionPaletteRGB, - targetPalette: Swatch[], + targetPalette: Color[], direction: 1 | -1 ) { // Careful with the use of referencePalette as only the refSwatches is reversed. const refSwatches = direction === -1 ? referencePalette.swatches : referencePalette.reversedSwatches; - const refIndex = (swatch: Swatch) => { - const index = referencePalette.closestIndexOf(swatch); + const refIndex = (color: Color) => { + const index = referencePalette.closestIndexOf(color); return direction === 1 ? referencePalette.lastIndex - index : index; }; @@ -198,21 +198,21 @@ export class PaletteRGB extends BasePalette { } /** - * Generate a Palette with consistent minimum contrast between Swatches. + * Generate a Palette with consistent minimum contrast between swatches. * * @param source - The source color * @param options - Palette generation options - * @returns A Palette meeting the requested contrast between Swatches. + * @returns A Palette meeting the requested contrast between swatches. */ - private static createColorPaletteByContrast(source: Swatch, options: PaletteRGBOptions): Swatch[] { + private static createColorPaletteByContrast(source: Color, options: PaletteRGBOptions): Color[] { const referencePalette = HighResolutionPaletteRGB.from(source); // Ramp function to increase contrast as the swatches get darker - const nextContrast = (swatch: Swatch) => { - return options.stepContrast + options.stepContrast * (1 - swatch.relativeLuminance) * options.stepContrastRamp; + const nextContrast = (color: Color) => { + return options.stepContrast + options.stepContrast * (1 - color.relativeLuminance) * options.stepContrastRamp; }; - const swatches: Swatch[] = []; + const swatches: Color[] = []; // Start with the source color (when preserving) or the light end color let ref = options.preserveSource ? source : referencePalette.swatches[0]; @@ -248,20 +248,20 @@ export class PaletteRGB extends BasePalette { } /** - * Creates a PaletteRGB from a source Swatch with options. + * Creates a PaletteRGB from a source Color with options. * - * @param source - The source Swatch to create a Palette from + * @param source - The source Color to create a Palette from * @param options - Options to specify details of palette generation - * @returns The PaletteRGB with Swatches based on `source` + * @returns The PaletteRGB with swatches based on `source` */ - static from(source: Swatch | string, options?: Partial): PaletteRGB { - const swatch = source instanceof Swatch ? source : Swatch.parse(source); - if (!swatch) { + static from(source: Color | string, options?: Partial): PaletteRGB { + const color = source instanceof Color ? source : Color.parse(source); + if (!color) { throw new Error(`Unable to parse Color as hex string: ${source}`); } const opts = options === void 0 || null ? defaultPaletteRGBOptions : { ...defaultPaletteRGBOptions, ...options }; - return new PaletteRGB(swatch, Object.freeze(PaletteRGB.createColorPaletteByContrast(swatch, opts))); + return new PaletteRGB(color, Object.freeze(PaletteRGB.createColorPaletteByContrast(color, opts))); } } diff --git a/packages/adaptive-ui/src/core/color/palette.ts b/packages/adaptive-ui/src/core/color/palette.ts index 4d58b834..0364092c 100644 --- a/packages/adaptive-ui/src/core/color/palette.ts +++ b/packages/adaptive-ui/src/core/color/palette.ts @@ -1,9 +1,8 @@ import { Color } from "./color.js"; -import { Swatch } from "./swatch.js"; import { RelativeLuminance } from "./utilities/relative-luminance.js"; /** - * Directional values for navigating {@link Swatch}es in {@link Palette}. + * Directional values for navigating swatches in {@link Palette}. * * @public */ @@ -20,7 +19,7 @@ export const PaletteDirectionValue = Object.freeze({ } as const); /** - * Directional values for navigating {@link Swatch}es in {@link Palette}. + * Directional values for navigating swatches in {@link Palette}. * * @public */ @@ -50,30 +49,30 @@ export function resolvePaletteDirection(direction: PaletteDirection): PaletteDir } /** - * A collection of {@link Swatch}es that form a luminance gradient from light (index 0) to dark. + * A collection of {@link Color}s that form a luminance gradient from light (index 0) to dark. * * @public */ -export interface Palette { +export interface Palette { /** - * The Swatch used to create the full palette. + * The Color used to create the full palette. */ readonly source: Color; /** - * The array of all Swatches from light to dark. + * The array of all Colors from light to dark. */ readonly swatches: ReadonlyArray; /** - * Returns a Swatch from the Palette that most closely meets + * Returns a Color from the Palette that most closely meets * the `minContrast` ratio for to the `reference`. * * @param reference - The relative luminance of the reference * @param minContrast - The minimum amount of contrast from the `reference` * @param initialIndex - Optional starting point for the search * @param direction - Optional control for the direction of the search - * @returns The Swatch that meets the provided contrast + * @returns The Color that meets the provided contrast */ colorContrast( reference: RelativeLuminance, @@ -83,10 +82,10 @@ export interface Palette { ): T; /** - * Returns a Swatch from the Palette that's the specified position and direction away from the `reference`. + * Returns a Color from the Palette that's the specified position and direction away from the `reference`. * * @param reference - The relative luminance of the reference - * @param delta - The number of Swatches away from `reference` + * @param delta - The number of swatches away from `reference` * @param direction - The direction to go from `reference`, 1 goes darker, -1 goes lighter */ delta(reference: RelativeLuminance, delta: number, direction: PaletteDirection): T; @@ -101,11 +100,11 @@ export interface Palette { closestIndexOf(reference: RelativeLuminance): number; /** - * Gets a Swatch by index. Index is clamped to the limits - * of the Palette so a Swatch will always be returned. + * Gets a Color by index. Index is clamped to the limits + * of the Palette so a Color will always be returned. * * @param index - The index - * @returns The Swatch + * @returns The Color */ get(index: number): T; } diff --git a/packages/adaptive-ui/src/core/color/recipe.ts b/packages/adaptive-ui/src/core/color/recipe.ts index 024faaff..c9a1bd39 100644 --- a/packages/adaptive-ui/src/core/color/recipe.ts +++ b/packages/adaptive-ui/src/core/color/recipe.ts @@ -1,7 +1,8 @@ import { Recipe, RecipeEvaluate, RecipeEvaluateOptional, RecipeOptional } from "../recipes.js"; import { InteractiveValues } from "../types.js"; +import { Color } from "./color.js"; +import { Paint } from "./paint.js"; import { Palette } from "./palette.js"; -import { Swatch } from "./swatch.js"; /** * Parameters provided to {@link ColorRecipe}. @@ -12,7 +13,7 @@ export type ColorRecipeParams = { /** * The reference color, implementation defaults to `fillColor`, but allows for overriding for nested color recipes. */ - reference: Swatch | null, + reference: Paint | null, }; /** @@ -20,14 +21,14 @@ export type ColorRecipeParams = { * * @public */ -export type ColorRecipe = RecipeOptional; +export type ColorRecipe = RecipeOptional; /** * The type of the `evaluate` function for {@link ColorRecipe}. * * @public */ -export type ColorRecipeEvaluate = RecipeEvaluateOptional; +export type ColorRecipeEvaluate = RecipeEvaluateOptional; /** * Parameters provided to {@link ColorRecipePalette}. @@ -45,49 +46,56 @@ export type ColorRecipePaletteParams = ColorRecipeParams & { * * @public */ -export type ColorRecipePalette = Recipe; +export type ColorRecipePalette = Recipe; /** * The type of the `evaluate` function for {@link ColorRecipePalette}. * * @public */ -export type ColorRecipePaletteEvaluate = RecipeEvaluate; +export type ColorRecipePaletteEvaluate = RecipeEvaluate; /** * A recipe that evaluates a color value for rest, hover, active, and focus states. * * @public */ -export type InteractiveColorRecipe = ColorRecipe; +export type InteractiveColorRecipe = ColorRecipe; /** * The type of the `evaluate` function for {@link InteractiveColorRecipe}. * * @public */ -export type InteractiveColorRecipeEvaluate = ColorRecipeEvaluate; +export type InteractiveColorRecipeEvaluate = ColorRecipeEvaluate; /** * A recipe that evaluates a color value for rest, hover, active, and focus states using the provided Palette. * * @public */ -export type InteractiveColorRecipePalette = ColorRecipePalette; +export type InteractiveColorRecipePalette = ColorRecipePalette; /** * The type of the `evaluate` function for {@link InteractiveColorRecipePalette}. * * @public */ -export type InteractiveColorRecipePaletteEvaluate = ColorRecipePaletteEvaluate; +export type InteractiveColorRecipePaletteEvaluate = ColorRecipePaletteEvaluate; /** - * A set of {@link Swatch}es to use for an interactive control's states. + * A set of {@link Paint}s to use for an interactive control's states. * * @public */ -export interface InteractiveSwatchSet extends InteractiveValues {} +export interface InteractivePaintSet extends InteractiveValues {} + +/** + * A set of {@link Color}s to use for an interactive control's states. + * + * @public + */ +export interface InteractiveColorSet extends InteractiveValues {} /** * A recipe that evaluates based on an interactive set of color values. @@ -97,25 +105,25 @@ export interface InteractiveSwatchSet extends InteractiveValues { * * @public */ -export type ColorRecipeBySet = Recipe; +export type ColorRecipeBySet = Recipe; /** * The type of the `evaluate` function for {@link ColorRecipeBySet}. * * @public */ -export type ColorRecipeBySetEvaluate = RecipeEvaluate; +export type ColorRecipeBySetEvaluate = RecipeEvaluate; /** * A recipe that evaluates a color value for rest, hover, active, and focus states. * * @public */ -export type InteractiveColorRecipeBySet = ColorRecipeBySet; +export type InteractiveColorRecipeBySet = ColorRecipeBySet; /** * The type of the `evaluate` function for {@link InteractiveColorRecipeBySet}. * * @public */ -export type InteractiveColorRecipeBySetEvaluate = ColorRecipeBySetEvaluate; +export type InteractiveColorRecipeBySetEvaluate = ColorRecipeBySetEvaluate; diff --git a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast-set.ts b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast-set.ts index 83298aac..911e23c9 100644 --- a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast-set.ts +++ b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast-set.ts @@ -1,9 +1,10 @@ -import { InteractiveSwatchSet } from "../recipe.js"; -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; +import { InteractiveColorSet, InteractivePaintSet } from "../recipe.js"; import { blackOrWhiteByContrast } from "./black-or-white-by-contrast.js"; /** - * Gets an interactive set of black or white Swatches based on the reference color for each state and minimum contrast. + * Gets an interactive set of black or white Colors based on the reference color for each state and minimum contrast. * * This is commonly used for something like foreground color on an accent-filled Button. * @@ -17,16 +18,16 @@ import { blackOrWhiteByContrast } from "./black-or-white-by-contrast.js"; * @param focusReference - The focus state reference color * @param minContrast - The minimum contrast required for black or white from each reference color * @param defaultBlack - True to default to black if both black or white meet contrast - * @returns The interactive set of black or white Swatches. + * @returns The interactive set of black or white Colors. * * @public */ export function blackOrWhiteByContrastSet( - set: InteractiveSwatchSet, + set: InteractivePaintSet, minContrast: number, defaultBlack: boolean -): InteractiveSwatchSet { - const defaultRule: (reference: Swatch | null) => Swatch | null = (reference) => +): InteractiveColorSet { + const defaultRule: (reference: Paint | null) => Color | null = (reference) => reference ? blackOrWhiteByContrast(reference, minContrast, defaultBlack) : null; const restForeground = defaultRule(set.rest); @@ -41,7 +42,7 @@ export function blackOrWhiteByContrastSet( const disabled = defaultRule(set.disabled); // TODO: Reasonable disabled opacity, but not configurable. // Considering replacing these recipes anyway. - const disabledForeground = disabled?.toTransparent(0.3) ?? null; + const disabledForeground = disabled ? Color.unsafeOpacity(disabled, 0.3) : null; return { rest: restForeground, diff --git a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.spec.ts b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.spec.ts index 6b7f86de..61a6f57a 100644 --- a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.spec.ts +++ b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.spec.ts @@ -1,11 +1,11 @@ import chai from "chai"; -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; import { _black, _white } from "../utilities/color-constants.js"; import { blackOrWhiteByContrast } from "./black-or-white-by-contrast.js"; const { expect } = chai; -const middleGrey = Swatch.parse("#808080")!; +const middleGrey = Color.parse("#808080")!; describe("blackOrWhiteByContrast", (): void => { it("should return black when background does not meet contrast ratio with white", (): void => { diff --git a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.ts b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.ts index 0d5345be..c9e4a7e4 100644 --- a/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.ts +++ b/packages/adaptive-ui/src/core/color/recipes/black-or-white-by-contrast.ts @@ -1,8 +1,9 @@ -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { _black, _white } from "../utilities/color-constants.js"; /** - * Gets a black or white Swatch based on the reference color and minimum contrast. + * Gets a black or white Color based on the reference color and minimum contrast. * * @remarks * If neither black nor white meet the requested contrast the highest contrasting color is returned. @@ -10,11 +11,11 @@ import { _black, _white } from "../utilities/color-constants.js"; * @param reference - The reference color * @param minContrast - The minimum contrast required for black or white from `reference` * @param defaultBlack - True to default to black if both black and white meet contrast - * @returns A black or white Swatch + * @returns A black or white Color * * @public */ -export function blackOrWhiteByContrast(reference: Swatch, minContrast: number, defaultBlack: boolean): Swatch { +export function blackOrWhiteByContrast(reference: Paint, minContrast: number, defaultBlack: boolean): Color { const defaultColor = defaultBlack ? _black : _white; const otherColor = defaultBlack ? _white : _black; const defaultContrast = reference.contrast(defaultColor); diff --git a/packages/adaptive-ui/src/core/color/recipes/contrast-and-delta-swatch-set.ts b/packages/adaptive-ui/src/core/color/recipes/contrast-and-delta-swatch-set.ts index 45f18925..8611b709 100644 --- a/packages/adaptive-ui/src/core/color/recipes/contrast-and-delta-swatch-set.ts +++ b/packages/adaptive-ui/src/core/color/recipes/contrast-and-delta-swatch-set.ts @@ -1,10 +1,11 @@ +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { Palette, PaletteDirection, PaletteDirectionValue, resolvePaletteDirection } from "../palette.js"; -import { InteractiveSwatchSet } from "../recipe.js"; -import { Swatch } from "../swatch.js"; +import { InteractiveColorSet } from "../recipe.js"; import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; /** - * Gets an interactive set of {@link Swatch}es using contrast from the reference color, then deltas for each state. + * Gets an interactive set of {@link Color}s using contrast from the reference color, then deltas for each state. * * Since this is based on contrast it tries to do the right thing for accessibility. Ideally the `restDelta` * and `hoverDelta` should be greater than or equal to zero, because that will ensure those colors meet or @@ -13,7 +14,7 @@ import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; * This algorithm will maintain the difference between the rest and hover deltas, but may slide them on the Palette * to maintain accessibility. * - * @param palette - The Palette used to find the Swatches + * @param palette - The Palette used to find the Colors * @param reference - The reference color * @param minContrast - The desired minimum contrast from `reference`, which determines the base color * @param restDelta - The rest state offset from the base color @@ -21,16 +22,16 @@ import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; * @param activeDelta - The active state offset from the base color * @param focusDelta - The focus state offset from the base color * @param disabledDelta - The disabled state offset from the base color - * @param disabledPalette - The Palette for the disabled Swatch + * @param disabledPalette - The Palette for the disabled color * @param direction - The direction the deltas move on the `palette`, defaults to {@link directionByIsDark} based on `reference` * @param zeroAsTransparent - Treat a zero offset as transparent, defaults to true - * @returns The interactive set of Swatches + * @returns The interactive set of Colors * * @public */ export function contrastAndDeltaSwatchSet( palette: Palette, - reference: Swatch, + reference: Paint, minContrast: number, restDelta: number, hoverDelta: number, @@ -40,13 +41,13 @@ export function contrastAndDeltaSwatchSet( disabledPalette: Palette = palette, direction: PaletteDirection = directionByIsDark(reference), zeroAsTransparent: boolean = false, -): InteractiveSwatchSet { +): InteractiveColorSet { const dir = resolvePaletteDirection(direction); const referenceIndex = palette.closestIndexOf(reference); - const accessibleSwatch = palette.colorContrast(reference, minContrast, referenceIndex); + const accessibleColor = palette.colorContrast(reference, minContrast, referenceIndex); - const accessibleIndex1 = palette.closestIndexOf(accessibleSwatch); + const accessibleIndex1 = palette.closestIndexOf(accessibleColor); const accessibleIndex2 = accessibleIndex1 + dir * Math.abs(restDelta - hoverDelta); const indexOneIsRestState = dir === PaletteDirectionValue.darker ? restDelta < hoverDelta : dir * restDelta > dir * hoverDelta; @@ -62,20 +63,20 @@ export function contrastAndDeltaSwatchSet( hoverIndex = accessibleIndex1; } - function getSwatch(palette: Palette, index: number): Swatch { - const swatch = palette.get(index); + function getColor(palette: Palette, index: number): Color { + const color = palette.get(index); if (zeroAsTransparent === true && index === referenceIndex) { - return swatch.toTransparent(); + return Color.asOverlay(color, color); } else { - return swatch; + return color; } } return { - rest: getSwatch(palette, restIndex), - hover: getSwatch(palette, hoverIndex), - active: getSwatch(palette, restIndex + dir * activeDelta), - focus: getSwatch(palette, restIndex + dir * focusDelta), - disabled: getSwatch(disabledPalette, referenceIndex + dir * disabledDelta), + rest: getColor(palette, restIndex), + hover: getColor(palette, hoverIndex), + active: getColor(palette, restIndex + dir * activeDelta), + focus: getColor(palette, restIndex + dir * focusDelta), + disabled: getColor(disabledPalette, referenceIndex + dir * disabledDelta), }; } diff --git a/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.spec.ts b/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.spec.ts index a2856fa5..dac6d84f 100644 --- a/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.spec.ts +++ b/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.spec.ts @@ -1,12 +1,12 @@ import chai from "chai"; +import { Color } from "../color.js"; import { PaletteRGB } from "../palette-rgb.js"; -import { Swatch } from "../swatch.js"; import { contrastSwatch } from "./contrast-swatch.js"; const { expect } = chai; -const neutralBase = Swatch.parse("#808080")!; -const accentBase = Swatch.parse("#80DEEA")!; +const neutralBase = Color.parse("#808080")!; +const accentBase = Color.parse("#80DEEA")!; describe("contrastSwatch", (): void => { const neutralPalette = PaletteRGB.from(neutralBase); diff --git a/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.ts b/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.ts index aeef6236..bbdf8c48 100644 --- a/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.ts +++ b/packages/adaptive-ui/src/core/color/recipes/contrast-swatch.ts @@ -1,23 +1,24 @@ +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { Palette, PaletteDirection } from "../palette.js"; -import { Swatch } from "../swatch.js"; import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; /** - * Gets a Swatch meeting the minimum contrast from the reference color. + * Gets a Color meeting the minimum contrast from the reference color. * - * @param palette - The Palette used to find the Swatch + * @param palette - The Palette used to find the Color * @param reference - The reference color * @param minContrast - The desired minimum contrast * @param direction - The direction the delta moves on the `palette`, defaults to {@link directionByIsDark} based on `reference` - * @returns The Swatch + * @returns The Color * * @public */ export function contrastSwatch( palette: Palette, - reference: Swatch, + reference: Paint, minContrast: number, direction: PaletteDirection = directionByIsDark(reference) -): Swatch { +): Color { return palette.colorContrast(reference, minContrast, undefined, direction); } diff --git a/packages/adaptive-ui/src/core/color/recipes/delta-swatch-set.ts b/packages/adaptive-ui/src/core/color/recipes/delta-swatch-set.ts index 99cf34c0..711e40e5 100644 --- a/packages/adaptive-ui/src/core/color/recipes/delta-swatch-set.ts +++ b/packages/adaptive-ui/src/core/color/recipes/delta-swatch-set.ts @@ -1,28 +1,29 @@ +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { Palette, PaletteDirection, resolvePaletteDirection } from "../palette.js"; -import { InteractiveSwatchSet } from "../recipe.js"; -import { Swatch } from "../swatch.js"; +import { InteractiveColorSet } from "../recipe.js"; import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; /** - * Gets an interactive set of Swatches the specified positions away from the reference color. + * Gets an interactive set of Colors the specified positions away from the reference color. * - * @param palette - The Palette used to find the Swatches + * @param palette - The Palette used to find the Colors * @param reference - The reference color * @param restDelta - The rest state offset from `reference` * @param hoverDelta - The hover state offset from `reference` * @param activeDelta - The active state offset from `reference` * @param focusDelta - The focus state offset from `reference` * @param disabledDelta - The disabled state offset from the base color - * @param disabledPalette - The Palette for the disabled Swatch + * @param disabledPalette - The Palette for the disabled color * @param direction - The direction the deltas move on the `palette`, defaults to {@link directionByIsDark} based on `reference` * @param zeroAsTransparent - Treat a zero offset as transparent, defaults to true - * @returns The interactive set of Swatches + * @returns The interactive set of Colors * * @public */ export function deltaSwatchSet( palette: Palette, - reference: Swatch, + reference: Paint, restDelta: number, hoverDelta: number, activeDelta: number, @@ -31,24 +32,24 @@ export function deltaSwatchSet( disabledPalette: Palette = palette, direction: PaletteDirection = directionByIsDark(reference), zeroAsTransparent: boolean = false, -): InteractiveSwatchSet { +): InteractiveColorSet { const referenceIndex = palette.closestIndexOf(reference); const dir = resolvePaletteDirection(direction); - function getSwatch(palette: Palette, delta: number): Swatch { - const swatch = palette.get(referenceIndex + dir * delta); + function getColor(palette: Palette, delta: number): Color { + const color = palette.get(referenceIndex + dir * delta); if (zeroAsTransparent === true && delta === 0) { - return swatch.toTransparent(); + return Color.asOverlay(color, color); } else { - return swatch; + return color; } } return { - rest: getSwatch(palette, restDelta), - hover: getSwatch(palette, hoverDelta), - active: getSwatch(palette, activeDelta), - focus: getSwatch(palette, focusDelta), - disabled: getSwatch(disabledPalette, disabledDelta), + rest: getColor(palette, restDelta), + hover: getColor(palette, hoverDelta), + active: getColor(palette, activeDelta), + focus: getColor(palette, focusDelta), + disabled: getColor(disabledPalette, disabledDelta), }; } diff --git a/packages/adaptive-ui/src/core/color/recipes/delta-swatch.ts b/packages/adaptive-ui/src/core/color/recipes/delta-swatch.ts index b9c5148c..a02d0f5c 100644 --- a/packages/adaptive-ui/src/core/color/recipes/delta-swatch.ts +++ b/packages/adaptive-ui/src/core/color/recipes/delta-swatch.ts @@ -1,23 +1,24 @@ -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { Palette, PaletteDirection } from "../palette.js"; import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; /** - * Color algorithm to get the Swatch a specified position away from the reference color. + * Color algorithm to get the Color a specified position away from the reference color. * - * @param palette - The Palette used to find the Swatch + * @param palette - The Palette used to find the Color * @param reference - The reference color * @param delta - The offset from the `reference` * @param direction - The direction the delta moves on the `palette`, defaults to {@link directionByIsDark} based on `reference` - * @returns The Swatch + * @returns The Color * * @public */ export function deltaSwatch( palette: Palette, - reference: Swatch, + reference: Paint, delta: number, direction: PaletteDirection = directionByIsDark(reference) -): Swatch { +): Color { return palette.delta(reference, delta, direction); } diff --git a/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.spec.ts b/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.spec.ts index 788c7178..3d291570 100644 --- a/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.spec.ts +++ b/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.spec.ts @@ -1,13 +1,13 @@ import chai from "chai"; +import { Color } from "../color.js"; import { PaletteRGB } from "../palette-rgb.js"; -import { Swatch } from "../swatch.js"; import { _black, _white } from "../utilities/color-constants.js"; import { idealColorDeltaSwatchSet } from "./ideal-color-delta-swatch-set.js"; const { expect } = chai; -const neutralBase = Swatch.parse("#808080")!; -const accentBase = Swatch.parse("#80DEEA")!; +const neutralBase = Color.parse("#808080")!; +const accentBase = Color.parse("#80DEEA")!; describe("idealColorDeltaSwatchSet", (): void => { const neutralPalette = PaletteRGB.from(neutralBase); @@ -23,11 +23,11 @@ describe("idealColorDeltaSwatchSet", (): void => { it("should have accessible rest and hover colors against the background color", (): void => { const accentColors = [ - Swatch.parse("#0078D4")!, - Swatch.parse("#107C10")!, - Swatch.parse("#5C2D91")!, - Swatch.parse("#D83B01")!, - Swatch.parse("#F2C812")!, + Color.parse("#0078D4")!, + Color.parse("#107C10")!, + Color.parse("#5C2D91")!, + Color.parse("#D83B01")!, + Color.parse("#F2C812")!, ]; accentColors.forEach((accent): void => { diff --git a/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.ts b/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.ts index 82bc1ac4..224b66df 100644 --- a/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.ts +++ b/packages/adaptive-ui/src/core/color/recipes/ideal-color-delta-swatch-set.ts @@ -1,11 +1,11 @@ import { Color } from "../color.js"; +import { Paint } from "../paint.js"; import { Palette, PaletteDirection, PaletteDirectionValue, resolvePaletteDirection } from "../palette.js"; -import { InteractiveSwatchSet } from "../recipe.js"; -import { Swatch } from "../swatch.js"; +import { InteractiveColorSet } from "../recipe.js"; import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; /** - * Gets an interactive set of {@link Swatch}es using contrast from the reference color. If the ideal color meets contrast it + * Gets an interactive set of {@link Color}s using contrast from the reference color. If the ideal color meets contrast it * is used for the base color, if not it's adjusted until contrast is met. Then deltas from that color are used for each state. * * This algorithm is similar to {@link contrastAndDeltaSwatchSet}, with the addition of `idealColor`. This is often preferable @@ -18,7 +18,7 @@ import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; * This algorithm will maintain the difference between the rest and hover deltas, but may slide them on the Palette * to maintain accessibility. * - * @param palette - The Palette used to find the Swatches + * @param palette - The Palette used to find the Colors * @param reference - The reference color * @param idealColor - The color to use as the base color if it meets `minContrast` from `reference` * @param minContrast - The desired minimum contrast from `reference`, which determines the base color @@ -27,15 +27,15 @@ import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; * @param activeDelta - The active state offset from the base color * @param focusDelta - The focus state offset from the base color * @param disabledDelta - The disabled state offset from the base color - * @param disabledPalette - The Palette for the disabled Swatch + * @param disabledPalette - The Palette for the disabled color * @param direction - The direction the deltas move on the `palette`, defaults to {@link directionByIsDark} based on `reference` - * @returns The interactive set of Swatches + * @returns The interactive set of Colors * * @public */ export function idealColorDeltaSwatchSet( palette: Palette, - reference: Swatch, + reference: Paint, minContrast: number, idealColor: Color, restDelta: number, @@ -45,7 +45,7 @@ export function idealColorDeltaSwatchSet( disabledDelta: number, disabledPalette: Palette = palette, direction: PaletteDirection = directionByIsDark(reference) -): InteractiveSwatchSet { +): InteractiveColorSet { const dir = resolvePaletteDirection(direction); const referenceIndex = palette.closestIndexOf(reference); diff --git a/packages/adaptive-ui/src/core/color/swatch.spec.ts b/packages/adaptive-ui/src/core/color/swatch.spec.ts index 27863682..6f5471c1 100644 --- a/packages/adaptive-ui/src/core/color/swatch.spec.ts +++ b/packages/adaptive-ui/src/core/color/swatch.spec.ts @@ -1,7 +1,7 @@ import chai from "chai"; +import { type Rgb } from "culori/fn"; import { Color } from "./color.js"; import { Swatch } from "./swatch.js"; -import { type Rgb } from "culori/fn"; import { _white } from "./utilities/color-constants.js"; const { expect } = chai; @@ -24,6 +24,7 @@ describe("Swatch", () => { expect(swatch).to.be.instanceof(Swatch); expect(swatch.color).to.deep.equal(greyColor); expect(swatch.toColorString()).to.equal(greyHex); + expect(swatch.toString()).to.equal(greyHex); }); it("should create a Swatch from the provided RGB values", () => { diff --git a/packages/adaptive-ui/src/core/color/swatch.ts b/packages/adaptive-ui/src/core/color/swatch.ts index 4edef53e..77f29dda 100644 --- a/packages/adaptive-ui/src/core/color/swatch.ts +++ b/packages/adaptive-ui/src/core/color/swatch.ts @@ -1,81 +1,21 @@ -import { type Color as CuloriColor, modeRgb, parse, type Rgb, useMode } from "culori/fn"; +import { modeRgb, parse, type Rgb, useMode } from "culori/fn"; import { Color } from "./color.js"; +import { calculateOverlayColor } from "./utilities/opacity.js"; const rgb = useMode(modeRgb); -const rgbBlack: Rgb = { mode: "rgb", r: 0, g: 0, b: 0 }; -const rgbWhite: Rgb = { mode: "rgb", r: 1, g: 1, b: 1 }; - -function calcChannelOverlay(match: number, background: number, overlay: number): number { - if (overlay - background === 0) { - return 0; - } else { - return (match - background) / (overlay - background); - } -} - -function calcRgbOverlay(rgbMatch: Rgb, rgbBackground: Rgb, rgbOverlay: Rgb): number { - const rChannel: number = calcChannelOverlay(rgbMatch.r, rgbBackground.r, rgbOverlay.r); - const gChannel: number = calcChannelOverlay(rgbMatch.g, rgbBackground.g, rgbOverlay.g); - const bChannel: number = calcChannelOverlay(rgbMatch.b, rgbBackground.b, rgbOverlay.b); - return (rChannel + gChannel + bChannel) / 3; -} - -/** - * Calculate an overlay color that uses rgba (rgb + alpha) that matches the appearance of a given solid color - * when placed on the same background. - * - * @param rgbMatch - The solid color the overlay should match in appearance when placed over the rgbBackground - * @param rgbBackground - The background on which the overlay rests - * @returns The rgba (rgb + alpha) color of the overlay - */ -function calculateOverlayColor(rgbMatch: Rgb, rgbBackground: Rgb): Rgb { - let overlay = rgbBlack; - let alpha = calcRgbOverlay(rgbMatch, rgbBackground, overlay); - if (alpha <= 0) { - overlay = rgbWhite; - alpha = calcRgbOverlay(rgbMatch, rgbBackground, overlay); - } - alpha = Math.round(alpha * 1000) / 1000; - - return Object.assign({}, overlay, { alpha }); -} - /** - * Extends {@link Color} adding support for relative opacity. + * Legacy equivalent of Color. * * @public + * @deprecated Use {@link Color}. */ export class Swatch extends Color { - /** - * The opaque value this Swatch represents if opacity is used. - */ - private readonly _intendedColor?: Swatch; - - /** - * Creates a new Swatch. - * - * @param color - The underlying Color value - * @param intendedColor - If `color.alpha` < 1 this tracks the intended opaque color value for dependent calculations - */ - protected constructor(color: CuloriColor, intendedColor?: Swatch) { - super(color); - this._intendedColor = intendedColor; - } - - /** - * {@inheritdoc RelativeLuminance.relativeLuminance} - */ - public get relativeLuminance(): number { - return this._intendedColor - ? this._intendedColor.relativeLuminance - : super.relativeLuminance; - } - /** * Gets this color with transparency. * * @returns The color with full transparency + * @deprecated Use Color.unsafeOpacity */ public toTransparent(alpha: number = 0): Swatch { const transparentColor = { ...this.color }; diff --git a/packages/adaptive-ui/src/core/color/utilities/color-constants.ts b/packages/adaptive-ui/src/core/color/utilities/color-constants.ts index 702ceef1..78391080 100644 --- a/packages/adaptive-ui/src/core/color/utilities/color-constants.ts +++ b/packages/adaptive-ui/src/core/color/utilities/color-constants.ts @@ -1,15 +1,15 @@ -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; /** - * A {@link Swatch} convenience for white. + * A {@link Color} convenience for white. * * @internal */ -export const _white = Swatch.fromRgb(1, 1, 1); +export const _white = Color.fromRgb(1, 1, 1); /** - * A {@link Swatch} convenience for black. + * A {@link Color} convenience for black. * * @internal */ -export const _black = Swatch.fromRgb(0, 0, 0); +export const _black = Color.fromRgb(0, 0, 0); diff --git a/packages/adaptive-ui/src/core/color/utilities/conditional.ts b/packages/adaptive-ui/src/core/color/utilities/conditional.ts index ae1d133a..9902292c 100644 --- a/packages/adaptive-ui/src/core/color/utilities/conditional.ts +++ b/packages/adaptive-ui/src/core/color/utilities/conditional.ts @@ -1,7 +1,8 @@ -import { InteractiveSwatchSet } from "../recipe.js"; +import { Color } from "../color.js"; +import { InteractiveColorSet } from "../recipe.js"; import { _white } from "./color-constants.js"; -const transparentWhite = _white.toTransparent(); +const _transparentWhite = Color.unsafeOpacity(_white, 0); /** * Return an interactive set of the provided tokens or a no-op "transparent" set of tokens. @@ -11,18 +12,18 @@ const transparentWhite = _white.toTransparent(); * @returns The provided swatch set or a "transparent" swatch set. */ export function conditionalSwatchSet( - set: InteractiveSwatchSet, + set: InteractiveColorSet, condition: boolean -): InteractiveSwatchSet { +): InteractiveColorSet { if (condition) { return set; } return { - rest: transparentWhite, - hover: transparentWhite, - active: transparentWhite, - focus: transparentWhite, - disabled: transparentWhite, + rest: _transparentWhite, + hover: _transparentWhite, + active: _transparentWhite, + focus: _transparentWhite, + disabled: _transparentWhite, }; } diff --git a/packages/adaptive-ui/src/core/color/utilities/luminance-swatch.ts b/packages/adaptive-ui/src/core/color/utilities/luminance-swatch.ts index 5bff499e..10973bbf 100644 --- a/packages/adaptive-ui/src/core/color/utilities/luminance-swatch.ts +++ b/packages/adaptive-ui/src/core/color/utilities/luminance-swatch.ts @@ -1,13 +1,13 @@ -import { Swatch } from "../swatch.js"; +import { Color } from "../color.js"; /** - * Create a grey {@link Swatch} for the specified `luminance`. Note this is absolute luminance not 'relative' luminance. + * Create a grey {@link Color} for the specified `luminance`. Note this is absolute luminance not 'relative' luminance. * * @param luminance - A value between 0 and 1 representing the desired luminance, for example, 0.5 for middle grey, 0 = black, 1 = white * @returns A swatch for the specified grey value * * @public */ -export function luminanceSwatch(luminance: number): Swatch { - return Swatch.fromRgb(luminance, luminance, luminance); +export function luminanceSwatch(luminance: number): Color { + return Color.fromRgb(luminance, luminance, luminance); } diff --git a/packages/adaptive-ui/src/core/color/utilities/opacity.ts b/packages/adaptive-ui/src/core/color/utilities/opacity.ts index 15a3aa5f..1124ff40 100644 --- a/packages/adaptive-ui/src/core/color/utilities/opacity.ts +++ b/packages/adaptive-ui/src/core/color/utilities/opacity.ts @@ -1,24 +1,69 @@ -import { InteractiveSwatchSet } from "../recipe.js"; -import { Swatch } from "../swatch.js"; +import { type Color as CuloriColor, modeRgb, type Rgb, useMode } from "culori/fn"; +import { Color } from "../color.js"; +import { InteractiveColorSet } from "../recipe.js"; + +const rgb = useMode(modeRgb); + +const rgbBlack: Rgb = { mode: "rgb", r: 0, g: 0, b: 0 }; +const rgbWhite: Rgb = { mode: "rgb", r: 1, g: 1, b: 1 }; + +function calcChannelOverlay(match: number, background: number, overlay: number): number { + if (overlay - background === 0) { + return 0; + } else { + return (match - background) / (overlay - background); + } +} + +function calcRgbOverlay(rgbMatch: Rgb, rgbBackground: Rgb, rgbOverlay: Rgb): number { + const rChannel: number = calcChannelOverlay(rgbMatch.r, rgbBackground.r, rgbOverlay.r); + const gChannel: number = calcChannelOverlay(rgbMatch.g, rgbBackground.g, rgbOverlay.g); + const bChannel: number = calcChannelOverlay(rgbMatch.b, rgbBackground.b, rgbOverlay.b); + return (rChannel + gChannel + bChannel) / 3; +} + +/** + * Calculate an overlay color that uses rgba (rgb + alpha) that matches the appearance of a given solid color + * when placed on the same background. + * + * @param match - The solid color the overlay should match in appearance when placed over the rgbBackground + * @param background - The background on which the overlay rests + * @returns The rgba (rgb + alpha) color of the overlay + * + * @public + */ +export function calculateOverlayColor(match: CuloriColor, background: CuloriColor): Rgb { + const rgbMatch = rgb(match); + const rgbBackground = rgb(background); + let overlay = rgbBlack; + let alpha = calcRgbOverlay(rgbMatch, rgbBackground, overlay); + if (alpha <= 0) { + overlay = rgbWhite; + alpha = calcRgbOverlay(rgbMatch, rgbBackground, overlay); + } + alpha = Math.round(alpha * 1000) / 1000; + + return Object.assign({}, overlay, { alpha }); +} /** - * Returns an opaque {@link Swatch} or a {@link Swatch} with opacity relative to the reference color. + * Returns an opaque {@link Color} or a {@link Color} with opacity relative to the reference color. * - * @param swatch - The opaque intended swatch color. + * @param color - The opaque intended swatch color. * @param reference - The reference color for a semitransparent swatch. * @param asOverlay - True to return a semitransparent representation of `swatch` relative to `reference`. * @returns The requested representation of `swatch`. * * @public */ -export function swatchAsOverlay(swatch: Swatch | null, reference: Swatch, asOverlay: boolean): Swatch | null { - return swatch && asOverlay - ? Swatch.asOverlay(swatch, reference) - : swatch; +export function swatchAsOverlay(color: Color | null, reference: Color, asOverlay: boolean): Color | null { + return color && asOverlay + ? Color.asOverlay(color, reference) + : color; } /** - * Returns an interactive set of opaque {@link Swatch}es or {@link Swatch}es with opacity relative to the reference color. + * Returns an interactive set of opaque {@link Color}s or {@link Color}s with opacity relative to the reference color. * * @param set - The swatch set for which to make overlay. * @param reference - The reference color for a semitransparent swatch. @@ -28,10 +73,10 @@ export function swatchAsOverlay(swatch: Swatch | null, reference: Swatch, asOver * @public */ export function interactiveSwatchSetAsOverlay( - set: InteractiveSwatchSet, - reference: Swatch, + set: InteractiveColorSet, + reference: Color, asOverlay: boolean -): InteractiveSwatchSet { +): InteractiveColorSet { if (asOverlay) { return { rest: swatchAsOverlay(set.rest, reference, asOverlay), diff --git a/packages/adaptive-ui/src/core/color/utilities/relative-luminance.ts b/packages/adaptive-ui/src/core/color/utilities/relative-luminance.ts index 69a3e751..775d6980 100644 --- a/packages/adaptive-ui/src/core/color/utilities/relative-luminance.ts +++ b/packages/adaptive-ui/src/core/color/utilities/relative-luminance.ts @@ -8,6 +8,13 @@ export interface RelativeLuminance { * A number between 0 and 1, calculated by {@link https://www.w3.org/WAI/GL/wiki/Relative_luminance} */ readonly relativeLuminance: number; + + /** + * Gets the contrast between this relative luminance and another. + * + * @returns The contrast between the two luminance values, for example, 4.54 + */ + contrast: (a: RelativeLuminance) => number; } /** diff --git a/packages/adaptive-ui/src/core/modules/styles.ts b/packages/adaptive-ui/src/core/modules/styles.ts index 4ba16986..346277d4 100644 --- a/packages/adaptive-ui/src/core/modules/styles.ts +++ b/packages/adaptive-ui/src/core/modules/styles.ts @@ -1,7 +1,7 @@ import type { CSSDirective } from "@microsoft/fast-element"; import { CSSDesignToken } from "@microsoft/fast-foundation"; import { InteractiveColorRecipe, InteractiveColorRecipeBySet } from "../color/recipe.js"; -import { Swatch } from "../color/swatch.js"; +import { Paint } from "../color/paint.js"; import { TypedCSSDesignToken, TypedDesignToken } from "../adaptive-design-tokens.js"; import { InteractiveTokenGroup, InteractiveValues } from "../types.js"; import { createForegroundSet, createForegroundSetBySet } from "../token-helpers-color.js"; @@ -90,7 +90,7 @@ export type StyleRule = { */ export const Fill = { backgroundAndForeground: function( - background: InteractiveTokenGroup, + background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken ): StyleProperties { return { @@ -100,7 +100,7 @@ export const Fill = { }, backgroundAndForegroundBySet: function( - background: InteractiveTokenGroup, + background: InteractiveTokenGroup, foregroundRecipe: TypedDesignToken ): StyleProperties { return { @@ -110,8 +110,8 @@ export const Fill = { }, foregroundNonInteractiveWithDisabled: function( - foreground: TypedCSSDesignToken, - disabled: TypedCSSDesignToken, + foreground: TypedCSSDesignToken, + disabled: TypedCSSDesignToken, ): StyleProperties { return { foregroundFill: { @@ -121,7 +121,7 @@ export const Fill = { active: foreground, focus: foreground, disabled, - } as InteractiveTokenGroup, + } as InteractiveTokenGroup, } } } diff --git a/packages/adaptive-ui/src/core/shadow/index.ts b/packages/adaptive-ui/src/core/shadow/index.ts index fa840aa2..77b703bb 100644 --- a/packages/adaptive-ui/src/core/shadow/index.ts +++ b/packages/adaptive-ui/src/core/shadow/index.ts @@ -1,6 +1,6 @@ import { CSSDirective, cssDirective } from "@microsoft/fast-element"; import { DesignTokenMultiValue, DesignTokenType } from "../adaptive-design-tokens.js"; -import { Swatch } from "../color/swatch.js"; +import { Color } from "../color/color.js"; import { StyleProperty } from "../modules/types.js"; import { createTyped } from "../token-helpers.js"; @@ -21,7 +21,7 @@ export class Shadow implements CSSDirective { * @param spread - The spread in `px`. This is not supported in all potential uses of Shadow (text or drop). */ constructor( - public color: Swatch, + public color: Color, public xOffset: number, public yOffset: number, public blurRadius?: number, diff --git a/packages/adaptive-ui/src/core/token-helpers-color.ts b/packages/adaptive-ui/src/core/token-helpers-color.ts index 6aa620a0..1aaa0777 100644 --- a/packages/adaptive-ui/src/core/token-helpers-color.ts +++ b/packages/adaptive-ui/src/core/token-helpers-color.ts @@ -1,4 +1,5 @@ import type { DesignToken, DesignTokenResolver } from "@microsoft/fast-foundation"; +import { Paint } from "./color/paint.js"; import { ColorRecipe, ColorRecipeBySet, @@ -10,14 +11,13 @@ import { ColorRecipeParams, InteractiveColorRecipe, InteractiveColorRecipeBySet, - InteractiveSwatchSet, + InteractivePaintSet, } from "./color/recipe.js"; import { Palette } from "./color/palette.js"; -import { Swatch } from "./color/swatch.js"; import { StyleProperty } from "./modules/types.js"; import { DesignTokenRegistry, DesignTokenType, TypedCSSDesignToken, TypedDesignToken } from "./adaptive-design-tokens.js"; import { Recipe, RecipeOptional } from "./recipes.js"; -import { createTokenNonCss, createTokenSwatch } from "./token-helpers.js"; +import { createTokenNonCss, createTokenPaint } from "./token-helpers.js"; import { InteractiveState, InteractiveTokenGroup } from "./types.js"; /** @@ -61,7 +61,7 @@ export function createTokenMinContrast( * * @public */ -export function createTokenColorRecipe( +export function createTokenColorRecipe( baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeEvaluate, @@ -80,7 +80,7 @@ export function createTokenColorRecipe( * * @public */ -export function createTokenColorRecipeBySet( +export function createTokenColorRecipeBySet( baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipeBySetEvaluate, @@ -100,7 +100,7 @@ export function createTokenColorRecipeBySet( * * @public */ -export function createTokenColorRecipeForPalette( +export function createTokenColorRecipeForPalette( baseName: string, intendedFor: StyleProperty | StyleProperty[], evaluate: ColorRecipePaletteEvaluate, @@ -118,7 +118,7 @@ export function createTokenColorRecipeForPalette( * * @public */ -export function createTokenColorRecipeWithPalette( +export function createTokenColorRecipeWithPalette( recipeToken: TypedDesignToken>, paletteToken: DesignToken, ): TypedDesignToken> { @@ -133,11 +133,11 @@ export function createTokenColorRecipeWithPalette( }); } -function createTokenColorSetState( - valueToken: TypedDesignToken, +function createTokenPaintSetState( + valueToken: TypedDesignToken, state: InteractiveState, -): TypedCSSDesignToken { - return createTokenSwatch(`${valueToken.name.replace(".value", "")}.${state}`, valueToken.intendedFor).withDefault( +): TypedCSSDesignToken { + return createTokenPaint(`${valueToken.name.replace(".value", "")}.${state}`, valueToken.intendedFor).withDefault( (resolve: DesignTokenResolver) => resolve(valueToken)[state] as any ); @@ -152,9 +152,9 @@ function createTokenColorSetState( */ export function createTokenColorSet( recipeToken: TypedDesignToken -): InteractiveTokenGroup { +): InteractiveTokenGroup { const name = recipeToken.name.replace(".recipe", ""); - const valueToken = createTokenNonCss(`${name}.value`, DesignTokenType.color, recipeToken.intendedFor).withDefault( + const valueToken = createTokenNonCss(`${name}.value`, DesignTokenType.color, recipeToken.intendedFor).withDefault( (resolve: DesignTokenResolver) => resolve(recipeToken).evaluate(resolve) ); @@ -163,11 +163,11 @@ export function createTokenColorSet( name, type: DesignTokenType.color, intendedFor: valueToken.intendedFor, - rest: createTokenColorSetState(valueToken, InteractiveState.rest), - hover: createTokenColorSetState(valueToken, InteractiveState.hover), - active: createTokenColorSetState(valueToken, InteractiveState.active), - focus: createTokenColorSetState(valueToken, InteractiveState.focus), - disabled: createTokenColorSetState(valueToken, InteractiveState.disabled), + rest: createTokenPaintSetState(valueToken, InteractiveState.rest), + hover: createTokenPaintSetState(valueToken, InteractiveState.hover), + active: createTokenPaintSetState(valueToken, InteractiveState.active), + focus: createTokenPaintSetState(valueToken, InteractiveState.focus), + disabled: createTokenPaintSetState(valueToken, InteractiveState.disabled), }; DesignTokenRegistry.Groups.set(name, group); return group; @@ -181,9 +181,9 @@ export function createTokenColorSet( * @public */ export function createTokenColorRecipeValue( - recipeToken: TypedDesignToken>, -): TypedCSSDesignToken { - return createTokenSwatch(`${recipeToken.name.replace(".recipe", "")}.value`, recipeToken.intendedFor).withDefault( + recipeToken: TypedDesignToken>, +): TypedCSSDesignToken { + return createTokenPaint(`${recipeToken.name.replace(".recipe", "")}.value`, recipeToken.intendedFor).withDefault( (resolve: DesignTokenResolver) => resolve(recipeToken).evaluate(resolve) ); @@ -200,16 +200,16 @@ export function createTokenColorRecipeValue( */ export function createForegroundSet( foregroundRecipe: TypedDesignToken, - background: InteractiveTokenGroup, -): InteractiveTokenGroup { + background: InteractiveTokenGroup, +): InteractiveTokenGroup { const foregroundBaseName = `${foregroundRecipe.name.replace(".recipe", "")}`; const setName = `${foregroundBaseName}.on.${background.name.replace("color.", "")}`; function createState( foregroundState: InteractiveState, state: InteractiveState, - ): TypedCSSDesignToken { - return createTokenSwatch(`${setName}.${state}`).withDefault( + ): TypedCSSDesignToken { + return createTokenPaint(`${setName}.${state}`).withDefault( (resolve: DesignTokenResolver) => resolve(foregroundRecipe).evaluate(resolve, { reference: resolve(background[state]) @@ -241,15 +241,15 @@ export function createForegroundSet( */ export function createForegroundSetBySet( foregroundRecipe: TypedDesignToken, - background: InteractiveTokenGroup, -): InteractiveTokenGroup { + background: InteractiveTokenGroup, +): InteractiveTokenGroup { const foregroundBaseName = foregroundRecipe.name.replace(".recipe", ""); const setName = `${foregroundBaseName}.on.${background.name.replace("color.", "")}`; - const set = createTokenNonCss(`${setName}.value`, DesignTokenType.color).withDefault( + const set = createTokenNonCss(`${setName}.value`, DesignTokenType.color).withDefault( (resolve: DesignTokenResolver) => { - const backgroundSet: InteractiveSwatchSet = { + const backgroundSet: InteractivePaintSet = { rest: resolve(background.rest), hover: resolve(background.hover), active: resolve(background.active), @@ -262,8 +262,8 @@ export function createForegroundSetBySet( function createState( state: InteractiveState, - ): TypedCSSDesignToken { - return createTokenSwatch(`${setName}.${state}`).withDefault( + ): TypedCSSDesignToken { + return createTokenPaint(`${setName}.${state}`).withDefault( (resolve: DesignTokenResolver) => resolve(set)[state] as any ); diff --git a/packages/adaptive-ui/src/core/token-helpers.ts b/packages/adaptive-ui/src/core/token-helpers.ts index 9ecfa335..d618e750 100644 --- a/packages/adaptive-ui/src/core/token-helpers.ts +++ b/packages/adaptive-ui/src/core/token-helpers.ts @@ -1,6 +1,7 @@ import { DesignToken } from "@microsoft/fast-foundation"; import { DesignTokenType, TypedCSSDesignToken, TypedDesignToken } from "./adaptive-design-tokens.js"; import { Color } from "./color/color.js"; +import { Paint } from "./color/paint.js"; import { Swatch } from "./color/swatch.js"; import { StyleProperty } from "./modules/types.js"; import { Recipe, RecipeEvaluate } from "./recipes.js"; @@ -166,7 +167,7 @@ export function createTokenNumberNonStyling(name: string, intendedFor?: StylePro } /** - * Creates a DesignToken that can be used as a fill in styles. + * Creates a DesignToken that can be used as a paint treatment (background, foreground, border, etc.) in styles. * * @param name - The token name in `css-identifier` casing. * @param intendedFor - The style properties where this token is intended to be used. @@ -176,7 +177,19 @@ export function createTokenNumberNonStyling(name: string, intendedFor?: StylePro * * @public */ +export function createTokenPaint(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedCSSDesignToken { + return TypedCSSDesignToken.createTyped(name, DesignTokenType.paint, intendedFor); +} + +/** + * Creates a DesignToken that can be used as a color in styles. + * + * @param name - The token name in `css-identifier` casing. + * @param intendedFor - The style properties where this token is intended to be used. + * + * @public + * @deprecated Use createTokenColor or createTokenPaint + */ export function createTokenSwatch(name: string, intendedFor?: StyleProperty | StyleProperty[]): TypedCSSDesignToken { - // TODO: Add back `gradient` type support when multiple types are added. (see `adaptive-design-tokens.ts`) return TypedCSSDesignToken.createTyped(name, DesignTokenType.color, intendedFor); } diff --git a/packages/adaptive-ui/src/migration/color.ts b/packages/adaptive-ui/src/migration/color.ts index 42faf00f..6bf64691 100644 --- a/packages/adaptive-ui/src/migration/color.ts +++ b/packages/adaptive-ui/src/migration/color.ts @@ -1,9 +1,9 @@ import type { DesignTokenResolver } from "@microsoft/fast-foundation"; -import { ColorRecipeParams, InteractiveSwatchSet } from "../core/color/recipe.js"; +import { ColorRecipeParams, InteractivePaintSet } from "../core/color/recipe.js"; import { blackOrWhiteByContrastSet } from "../core/color/recipes/black-or-white-by-contrast-set.js"; import { deltaSwatchSet } from "../core/color/recipes/delta-swatch-set.js"; import { deltaSwatch } from "../core/color/recipes/delta-swatch.js"; -import { Swatch } from "../core/color/swatch.js"; +import { Color } from "../core/color/color.js"; import { interactiveSwatchSetAsOverlay, swatchAsOverlay } from "../core/color/utilities/opacity.js"; import { StyleProperty, StylePropertyShorthand } from "../core/modules/types.js"; import { @@ -112,7 +112,7 @@ const foregroundOnAccentFillReadableName = "foreground-on-accent-fill-readable"; export const foregroundOnAccentFillReadableRecipe = createTokenColorRecipe( foregroundOnAccentFillReadableName, StyleProperty.foregroundFill, - (resolve: DesignTokenResolver): InteractiveSwatchSet => + (resolve: DesignTokenResolver): InteractivePaintSet => blackOrWhiteByContrastSet( { rest: resolve(accentFillReadable.rest), @@ -433,7 +433,7 @@ export const neutralFillInputRecipe = createTokenColorRecipe( resolve(neutralFillInputFocusDelta), 1, ), - params?.reference || resolve(fillColor), + params?.reference as Color || resolve(fillColor), resolve(neutralAsOverlay) ) ); @@ -484,7 +484,7 @@ export const neutralFillSecondaryRecipe = createTokenColorRecipe( resolve(neutralFillSecondaryFocusDelta), 1, ), - params?.reference || resolve(fillColor), + params?.reference as Color || resolve(fillColor), resolve(neutralAsOverlay) ) ); @@ -569,14 +569,14 @@ export const neutralStrokeDividerRestDelta = createTokenDelta(neutralStrokeDivid export const neutralStrokeDividerRecipe = createTokenColorRecipe( neutralStrokeDividerName, StylePropertyShorthand.borderFill, - (resolve: DesignTokenResolver, params?: ColorRecipeParams): Swatch => + (resolve: DesignTokenResolver, params?: ColorRecipeParams): Color => swatchAsOverlay( deltaSwatch( resolve(neutralPalette), params?.reference || resolve(fillColor), resolve(neutralStrokeDividerRestDelta) ), - params?.reference || resolve(fillColor), + params?.reference as Color || resolve(fillColor), resolve(neutralAsOverlay) )! ); @@ -615,7 +615,7 @@ export const neutralStrokeInputRecipe = createTokenColorRecipe( resolve(neutralStrokeInputFocusDelta), 1, ), - params?.reference || resolve(fillColor), + params?.reference as Color || resolve(fillColor), resolve(neutralAsOverlay) ) ); diff --git a/packages/adaptive-ui/src/reference/color.ts b/packages/adaptive-ui/src/reference/color.ts index 6c35c68e..a73822ce 100644 --- a/packages/adaptive-ui/src/reference/color.ts +++ b/packages/adaptive-ui/src/reference/color.ts @@ -1,13 +1,13 @@ import type { DesignTokenResolver, ValuesOf } from "@microsoft/fast-foundation"; import { Palette } from "../core/color/palette.js"; -import { ColorRecipePaletteParams, ColorRecipeParams, InteractiveSwatchSet } from "../core/color/recipe.js"; +import { ColorRecipePaletteParams, ColorRecipeParams, InteractiveColorSet, InteractivePaintSet } from "../core/color/recipe.js"; import { blackOrWhiteByContrastSet } from "../core/color/recipes/black-or-white-by-contrast-set.js"; import { blackOrWhiteByContrast } from "../core/color/recipes/black-or-white-by-contrast.js"; import { contrastSwatch } from "../core/color/recipes/contrast-swatch.js"; import { contrastAndDeltaSwatchSet } from "../core/color/recipes/contrast-and-delta-swatch-set.js"; import { deltaSwatchSet } from "../core/color/recipes/delta-swatch-set.js"; import { idealColorDeltaSwatchSet } from "../core/color/recipes/ideal-color-delta-swatch-set.js"; -import { Swatch } from "../core/color/swatch.js"; +import { Color } from "../core/color/color.js"; import { _white } from "../core/color/utilities/color-constants.js"; import { conditionalSwatchSet } from "../core/color/utilities/conditional.js"; import { interactiveSwatchSetAsOverlay } from "../core/color/utilities/opacity.js"; @@ -23,7 +23,7 @@ import { createTokenDelta, createTokenMinContrast, } from "../core/token-helpers-color.js"; -import { createTokenNonCss, createTokenSwatch } from "../core/token-helpers.js"; +import { createTokenColor, createTokenNonCss } from "../core/token-helpers.js"; import { InteractiveState } from "../core/types.js"; import { DesignTokenType, TypedDesignToken } from "../core/adaptive-design-tokens.js"; import { accentPalette, criticalPalette, disabledPalette, highlightPalette, infoPalette, neutralPalette, successPalette, warningPalette } from "./palette.js"; @@ -113,7 +113,7 @@ export function createTokenColorRecipeInfo( * * @public */ -export function createTokenColorRecipeNeutral( +export function createTokenColorRecipeNeutral( recipeToken: TypedDesignToken>, ): TypedDesignToken> { const paletteToken = neutralPalette; @@ -125,7 +125,7 @@ export function createTokenColorRecipeNeutral( const p = Object.assign({ palette: resolve(paletteToken) }, params); return interactiveSwatchSetAsOverlay( resolve(recipeToken).evaluate(resolve, p), - p.reference || resolve(fillColor), + p.reference as Color || resolve(fillColor), resolve(neutralAsOverlay) ) as T; } @@ -179,7 +179,7 @@ export const minContrastReadable = createTokenNonCss("color.wcagContrast ); /** @public */ -export const fillColor = createTokenSwatch("color.context").withDefault(_white); +export const fillColor = createTokenColor("color.context").withDefault(_white); /** @public */ export const neutralAsOverlay = createTokenNonCss("color.neutral.asOverlay", DesignTokenType.boolean).withDefault(false); @@ -193,10 +193,10 @@ export const neutralAsOverlay = createTokenNonCss("color.neutral.asOver * * @public */ -export const blackOrWhiteDiscernibleRecipe = createTokenColorRecipeBySet( +export const blackOrWhiteDiscernibleRecipe = createTokenColorRecipeBySet( "color.blackOrWhite.discernible", StyleProperty.foregroundFill, - (resolve: DesignTokenResolver, reference: InteractiveSwatchSet) => + (resolve: DesignTokenResolver, reference: InteractivePaintSet) => blackOrWhiteByContrastSet( reference, resolve(minContrastDiscernible), @@ -213,10 +213,10 @@ export const blackOrWhiteDiscernibleRecipe = createTokenColorRecipeBySet( +export const blackOrWhiteReadableRecipe = createTokenColorRecipeBySet( "color.blackOrWhite.readable", StyleProperty.foregroundFill, - (resolve: DesignTokenResolver, reference: InteractiveSwatchSet) => + (resolve: DesignTokenResolver, reference: InteractivePaintSet) => blackOrWhiteByContrastSet( reference, resolve(minContrastReadable), @@ -1754,7 +1754,7 @@ const focusStrokeName = "color.focusStroke"; export const focusStrokeRecipe = createTokenColorRecipe( focusStrokeName, [...StylePropertyShorthand.borderFill, StyleProperty.outlineColor], - (resolve: DesignTokenResolver, params?: ColorRecipeParams): Swatch => + (resolve: DesignTokenResolver, params?: ColorRecipeParams): Color => contrastSwatch( resolve(focusStrokePalette), params?.reference || resolve(fillColor), @@ -1773,7 +1773,7 @@ const focusStrokeOuterName = "color.focusStroke.outer"; export const focusStrokeOuterRecipe = createTokenColorRecipe( focusStrokeOuterName, StylePropertyShorthand.borderFill, - (resolve: DesignTokenResolver): Swatch => + (resolve: DesignTokenResolver): Color => blackOrWhiteByContrast(resolve(fillColor), resolve(minContrastReadable), true) ); @@ -1788,7 +1788,7 @@ const focusStrokeInnerName = "color.focusStroke.inner"; export const focusStrokeInnerRecipe = createTokenColorRecipe( focusStrokeInnerName, StylePropertyShorthand.borderFill, - (resolve: DesignTokenResolver): Swatch => + (resolve: DesignTokenResolver): Color => blackOrWhiteByContrast(resolve(focusStrokeOuter), resolve(minContrastReadable), false) ); diff --git a/packages/adaptive-ui/src/reference/elevation.ts b/packages/adaptive-ui/src/reference/elevation.ts index 790b13ec..313331a6 100644 --- a/packages/adaptive-ui/src/reference/elevation.ts +++ b/packages/adaptive-ui/src/reference/elevation.ts @@ -1,4 +1,5 @@ import { DesignTokenResolver } from "@microsoft/fast-foundation"; +import { Color } from "../core/color/color.js"; import { StyleProperty } from "../core/modules/types.js"; import { DesignTokenMultiValue, DesignTokenType } from "../core/adaptive-design-tokens.js"; import { createTokenNonCss, createTokenRecipe } from "../core/token-helpers.js"; @@ -19,9 +20,9 @@ export const elevationRecipe = createTokenRecipe("elevation directionalOpacity = 0.24; } - const color = resolve(neutralStrokeStrong.rest); - const ambient = new Shadow(color.toTransparent(ambientOpacity), 0, 0, 2); - const directional = new Shadow(color.toTransparent(directionalOpacity), 0, size * 0.5, size); + const color = resolve(neutralStrokeStrong.rest) as Color; // TODO remove type assumption + const ambient = new Shadow(Color.unsafeOpacity(color, ambientOpacity), 0, 0, 2); + const directional = new Shadow(Color.unsafeOpacity(color, directionalOpacity), 0, size * 0.5, size); return new DesignTokenMultiValue(ambient, directional); }, ); diff --git a/packages/adaptive-ui/src/reference/layer.ts b/packages/adaptive-ui/src/reference/layer.ts index a4b8897c..aea52de7 100644 --- a/packages/adaptive-ui/src/reference/layer.ts +++ b/packages/adaptive-ui/src/reference/layer.ts @@ -1,12 +1,12 @@ import { DesignTokenResolver } from "@microsoft/fast-foundation"; import { DesignTokenType } from "../core/adaptive-design-tokens.js"; import { Palette, PaletteDirectionValue } from "../core/color/palette.js"; -import { ColorRecipeParams, InteractiveSwatchSet } from "../core/color/recipe.js"; +import { ColorRecipeParams, InteractivePaintSet } from "../core/color/recipe.js"; import { deltaSwatch, deltaSwatchSet } from "../core/color/recipes/index.js"; -import { Swatch } from "../core/color/swatch.js"; +import { Color } from "../core/color/color.js"; import { luminanceSwatch } from "../core/color/utilities/luminance-swatch.js"; import { StyleProperty } from "../core/modules/types.js"; -import { createTokenNonCss, createTokenRecipe, createTokenSwatch } from "../core/token-helpers.js"; +import { createTokenColor, createTokenNonCss, createTokenRecipe } from "../core/token-helpers.js"; import { createTokenColorRecipe, createTokenColorSet, createTokenDelta } from "../core/token-helpers-color.js"; import { InteractiveState } from "../core/types.js"; import { fillColor } from "./color.js"; @@ -78,7 +78,7 @@ export const layerFillBaseLuminance = createTokenNonCss(`${layerFillFixe * * @public */ -export const layerFillFixedRecipe = createTokenRecipe( +export const layerFillFixedRecipe = createTokenRecipe( layerFillFixedName, StyleProperty.backgroundFill, (resolve: DesignTokenResolver, index: number) => @@ -98,7 +98,7 @@ export const layerFillFixedRecipe = createTokenRecipe( * * @public */ -export const layerFillFixedBase = createTokenSwatch(`${layerFillFixedName}.base`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedBase = createTokenColor(`${layerFillFixedName}.base`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, 0) ); @@ -111,7 +111,7 @@ export const layerFillFixedBase = createTokenSwatch(`${layerFillFixedName}.base` * * @public */ -export const layerFillFixedMinus1 = createTokenSwatch(`${layerFillFixedName}.minus1`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedMinus1 = createTokenColor(`${layerFillFixedName}.minus1`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, -1) ); @@ -124,7 +124,7 @@ export const layerFillFixedMinus1 = createTokenSwatch(`${layerFillFixedName}.min * * @public */ -export const layerFillFixedMinus2 = createTokenSwatch(`${layerFillFixedName}.minus2`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedMinus2 = createTokenColor(`${layerFillFixedName}.minus2`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, -2) ); @@ -137,7 +137,7 @@ export const layerFillFixedMinus2 = createTokenSwatch(`${layerFillFixedName}.min * * @public */ -export const layerFillFixedMinus3 = createTokenSwatch(`${layerFillFixedName}.minus3`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedMinus3 = createTokenColor(`${layerFillFixedName}.minus3`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, -3) ); @@ -150,7 +150,7 @@ export const layerFillFixedMinus3 = createTokenSwatch(`${layerFillFixedName}.min * * @public */ -export const layerFillFixedMinus4 = createTokenSwatch(`${layerFillFixedName}.minus4`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedMinus4 = createTokenColor(`${layerFillFixedName}.minus4`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, -4) ); @@ -163,7 +163,7 @@ export const layerFillFixedMinus4 = createTokenSwatch(`${layerFillFixedName}.min * * @public */ -export const layerFillFixedPlus1 = createTokenSwatch(`${layerFillFixedName}.plus1`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedPlus1 = createTokenColor(`${layerFillFixedName}.plus1`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, 1) ); @@ -176,7 +176,7 @@ export const layerFillFixedPlus1 = createTokenSwatch(`${layerFillFixedName}.plus * * @public */ -export const layerFillFixedPlus2 = createTokenSwatch(`${layerFillFixedName}.plus2`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedPlus2 = createTokenColor(`${layerFillFixedName}.plus2`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, 2) ); @@ -189,7 +189,7 @@ export const layerFillFixedPlus2 = createTokenSwatch(`${layerFillFixedName}.plus * * @public */ -export const layerFillFixedPlus3 = createTokenSwatch(`${layerFillFixedName}.plus3`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedPlus3 = createTokenColor(`${layerFillFixedName}.plus3`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, 3) ); @@ -202,7 +202,7 @@ export const layerFillFixedPlus3 = createTokenSwatch(`${layerFillFixedName}.plus * * @public */ -export const layerFillFixedPlus4 = createTokenSwatch(`${layerFillFixedName}.plus4`, StyleProperty.backgroundFill).withDefault( +export const layerFillFixedPlus4 = createTokenColor(`${layerFillFixedName}.plus4`, StyleProperty.backgroundFill).withDefault( (resolve: DesignTokenResolver) => resolve(layerFillFixedRecipe).evaluate(resolve, 4) ); @@ -245,8 +245,8 @@ export const layerFillDisabledDelta = createTokenDelta(layerFillInteractiveName, * * @public */ -export const layerFillInteractiveRecipe = createTokenColorRecipe(layerFillInteractiveName, StyleProperty.backgroundFill, - (resolve: DesignTokenResolver, params?: ColorRecipeParams): InteractiveSwatchSet => +export const layerFillInteractiveRecipe = createTokenColorRecipe(layerFillInteractiveName, StyleProperty.backgroundFill, + (resolve: DesignTokenResolver, params?: ColorRecipeParams): InteractivePaintSet => deltaSwatchSet( resolve(layerPalette), params?.reference || resolve(fillColor), diff --git a/packages/adaptive-web-components/src/components/tree-item/tree-item.styles.ts b/packages/adaptive-web-components/src/components/tree-item/tree-item.styles.ts index e58639d8..4d1c7ab8 100644 --- a/packages/adaptive-web-components/src/components/tree-item/tree-item.styles.ts +++ b/packages/adaptive-web-components/src/components/tree-item/tree-item.styles.ts @@ -1,4 +1,4 @@ -import { Swatch } from"@adaptive-web/adaptive-ui"; +import { Color } from"@adaptive-web/adaptive-ui"; import { cornerRadiusControl, focusStrokeThickness, @@ -11,14 +11,14 @@ import { css, ElementStyles } from "@microsoft/fast-element"; import { DesignToken, DesignTokenResolver } from "@microsoft/fast-foundation"; import { heightNumber } from "../../styles/index.js"; -const expandCollapseHover = DesignToken.create("tree-item-expand-collapse-hover").withDefault( +const expandCollapseHover = DesignToken.create("tree-item-expand-collapse-hover").withDefault( (resolve: DesignTokenResolver) => { const recipe = resolve(neutralFillStealthRecipe); return recipe.evaluate(resolve, { reference: recipe.evaluate(resolve).hover }).hover as any; } ); -const selectedExpandCollapseHover = DesignToken.create("tree-item-expand-collapse-selected-hover").withDefault( +const selectedExpandCollapseHover = DesignToken.create("tree-item-expand-collapse-selected-hover").withDefault( (resolve: DesignTokenResolver) => { const baseRecipe = resolve(neutralFillSubtleRecipe); const buttonRecipe = resolve(neutralFillStealthRecipe);