UNPKG

colorjs.io

Version:

Let’s get serious about color

99 lines (83 loc) 3.4 kB
import { isNone, skipNone } from "./util.js"; import defaults from "./defaults.js"; import to from "./to.js"; import serialize from "./serialize.js"; import clone from "./clone.js"; import getColor from "./getColor.js"; import REC2020 from "./spaces/rec2020.js"; import P3 from "./spaces/p3.js"; import Lab from "./spaces/lab.js"; import sRGB from "./spaces/srgb.js"; /** @import ColorSpace from "./ColorSpace.js" */ /** @import { ColorTypes, PlainColorObject } from "./types.js" */ // Type re-exports /** @typedef {import("./types.js").Display} Display */ // Default space for CSS output. Code in Color.js makes this wider if there's a DOM available defaults.display_space = sRGB; let supportsNone; if (typeof CSS !== "undefined" && CSS.supports) { // Find widest supported color space for CSS for (let space of [Lab, REC2020, P3]) { let coords = space.getMinCoords(); let color = { space, coords, alpha: 1 }; let str = serialize(color); if (CSS.supports("color", str)) { defaults.display_space = space; break; } } } /** * Returns a serialization of the color that can actually be displayed in the browser. * If the default serialization can be displayed, it is returned. * Otherwise, the color is converted to Lab, REC2020, or P3, whichever is the widest supported. * In Node.js, this is basically equivalent to `serialize()` but returns a `String` object instead. * @param {ColorTypes} color * @param {{ space?: string | ColorSpace | undefined } & Record<string, any>} param1 * Options to be passed to `serialize()` * @returns {Display} String object containing the serialized color * with a color property containing the converted color (or the original, if no conversion was necessary) */ export default function display (color, { space = defaults.display_space, ...options } = {}) { color = getColor(color); let ret = /** @type {Display} */ (serialize(color, options)); if ( typeof CSS === "undefined" || CSS.supports("color", /** @type {string} */ (ret)) || !defaults.display_space ) { ret = /** @type {Display} */ (new String(ret)); ret.color = /** @type {PlainColorObject} */ (color); } else { // If we're here, what we were about to output is not supported let fallbackColor = /** @type {PlainColorObject} */ (color); // First, check if the culprit is none values let hasNone = color.coords.some(isNone) || isNone(color.alpha); if (hasNone) { // Does the browser support none values? if (!(supportsNone ??= CSS.supports("color", "hsl(none 50% 50%)"))) { // Nope, try again without none fallbackColor = clone(/** @type {PlainColorObject} */ (color)); fallbackColor.coords = /** @type {[number, number, number]} */ ( fallbackColor.coords.map(skipNone) ); fallbackColor.alpha = skipNone(fallbackColor.alpha); // @ts-expect-error This is set to the correct type later ret = serialize(fallbackColor, options); if (CSS.supports("color", /** @type {string} */ (ret))) { // We're done, now it's supported ret = /** @type {Display} */ (new String(ret)); ret.color = fallbackColor; return ret; } } } // If we're here, the color function is not supported // Fall back to fallback space fallbackColor = to(fallbackColor, space); ret = /** @type {Display} */ (new String(serialize(fallbackColor, options))); ret.color = fallbackColor; } return ret; }