diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 2cd7dc2ea27a1..3c7f242e787a1 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -2,5 +2,5 @@ "buildCommand": "build:codesandbox", "packages": ["packages/react", "packages/react-components/react-components"], "sandboxes": ["x5u3t", "spnyu"], - "node": "16" + "node": "18" } diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 13ae7dd53e4f1..15fd7b83b20cd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,19 +1,6 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node/.devcontainer/base.Dockerfile - -# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster -ARG VARIANT="14-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment if you want to install an additional version of node using nvm -# ARG EXTRA_NODE_VERSION=10 -# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" - -# [Optional] Uncomment if you want to install more global node modules -# RUN su node -c "npm install -g " +ARG VARIANT=18-bookworm +# https://github.com/devcontainers/images/tree/main/src/typescript-node +FROM mcr.microsoft.com/devcontainers/typescript-node:1-${VARIANT} # Cypress linux pre-requisites https://docs.cypress.io/guides/getting-started/installing-cypress#Linux-Prerequisites RUN apt-get update && apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 6e7fb0b5f6ce3..270534163d38c 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -4,7 +4,7 @@ This configuration is based on the base Node container provided by VSCode, it sh > For more information on how to use/create `development containers` follow the [VSCode Documentation](https://code.visualstudio.com/docs/remote/create-dev-container) -> See here for more information on the base container https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node/.devcontainer/base.Dockerfile +> See here for more information on the base container https://github.com/microsoft/vscode-dev-containers/blob/main/containers/javascript-node/.devcontainer/base.Dockerfile ## Cypress tests diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9239298f19fa9..a0295d3620962 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,10 +4,9 @@ "name": "Node.js", "build": { "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 16, 14, 12. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local arm64/Apple Silicon. - "args": { "VARIANT": "14" } + "args": { + "VARIANT": "18-bookworm" + } }, // Set *default* container specific settings.json values on container create. "settings": {}, diff --git a/.devops/templates/tools.yml b/.devops/templates/tools.yml index 808451359407f..60ac458e737a1 100644 --- a/.devops/templates/tools.yml +++ b/.devops/templates/tools.yml @@ -1,8 +1,12 @@ # Install versions of Node and Yarn required by build pipelines. steps: - - task: NodeTool@0 + - task: UseNode@1 inputs: - versionSpec: '16.18.1' + # 👉 NOTE: + # - we can use only versions that ship with container, otherwise we will run into nodejs installation issues. + # - as 1es bumps those versions within container automatically we need to use `.x` to not run into issues once they bump the versions. + # https://github.com/actions/runner-images/blob/ubuntu20/20230924.1/images/linux/Ubuntu2004-Readme.md#nodejs + version: '18.x' checkLatest: false displayName: 'Install Node.js' diff --git a/.github/workflows/check-packages.yml b/.github/workflows/check-packages.yml index b6ed7efa90ebd..a83ba436be01d 100644 --- a/.github/workflows/check-packages.yml +++ b/.github/workflows/check-packages.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 18.x cache: 'yarn' - uses: tj-actions/changed-files@v34 @@ -44,7 +44,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 18.x - uses: actions/github-script@v6 with: @@ -70,7 +70,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 18.x - uses: actions/github-script@v6 with: diff --git a/.github/workflows/docsite-publish-chromatic.yml b/.github/workflows/docsite-publish-chromatic.yml index 20610ef7235f2..420c5e0c34c7e 100644 --- a/.github/workflows/docsite-publish-chromatic.yml +++ b/.github/workflows/docsite-publish-chromatic.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 18.x cache: 'yarn' - name: Install packages diff --git a/.github/workflows/docsite-publish-ghpages.yml b/.github/workflows/docsite-publish-ghpages.yml index 6cc781319d398..78344630040d5 100644 --- a/.github/workflows/docsite-publish-ghpages.yml +++ b/.github/workflows/docsite-publish-ghpages.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 18.x cache: 'yarn' - name: Install packages diff --git a/change/@fluentui-eslint-plugin-222b39b1-1e9b-458f-8ddf-0dfef3a6717e.json b/change/@fluentui-eslint-plugin-222b39b1-1e9b-458f-8ddf-0dfef3a6717e.json new file mode 100644 index 0000000000000..225f3ea245574 --- /dev/null +++ b/change/@fluentui-eslint-plugin-222b39b1-1e9b-458f-8ddf-0dfef3a6717e.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: bump eslint-plugin-jsdoc to version which support node 18", + "packageName": "@fluentui/eslint-plugin", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-utilities-9901c7ce-5a05-4473-ab4f-ff74df9878eb.json b/change/@fluentui-utilities-9901c7ce-5a05-4473-ab4f-ff74df9878eb.json new file mode 100644 index 0000000000000..1c7be6b02b6d9 --- /dev/null +++ b/change/@fluentui-utilities-9901c7ce-5a05-4473-ab4f-ff74df9878eb.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: use Timers types based on environment to avoid TS failures introduced with node 18 types", + "packageName": "@fluentui/utilities", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/lage.config.js b/lage.config.js index 543af751ae9b8..237178945658a 100644 --- a/lage.config.js +++ b/lage.config.js @@ -22,6 +22,7 @@ module.exports = { 'change/**', 'rfcs/**', 'README.md', + '*.md', '.vscode/**', '.github/*.yml', '.github/*.json', @@ -29,6 +30,8 @@ module.exports = { '.github/CODEOWNERS', '.github/MAINTAINERS', '.github/ISSUE_TEMPLATE/**', + '.github/policies/**', + '.devcontainer/**', ], // All of these options are sent to `backfill`: https://github.com/microsoft/backfill/blob/master/README.md diff --git a/package.json b/package.json index 502e59338ddf4..1b040fdecc7f6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/microsoft/fluentui" }, "engines": { - "node": "^16.18.1" + "node": "^18.0.0 || ^20.0.0" }, "scripts": { "build": "lage build --verbose", @@ -166,9 +166,8 @@ "@types/lodash": "4.14.182", "@types/markdown-table": "2.0.0", "@types/micromatch": "4.0.2", - "@types/node": "16.18.1", - "@types/node-fetch": "2.5.7", - "@types/prettier": "2.2.3", + "@types/node": "18.18.6", + "@types/prettier": "2.7.2", "@types/progress": "2.0.5", "@types/prompts": "2.4.1", "@types/react": "17.0.44", @@ -193,7 +192,7 @@ "ajv": "8.4.0", "autoprefixer": "10.2.1", "babel-jest": "26.6.3", - "babel-loader": "8.2.2", + "babel-loader": "8.3.0", "babel-plugin-annotate-pure-calls": "0.4.0", "babel-plugin-annotate-pure-imports": "1.0.0-1", "babel-plugin-iife-wrap-react-components": "1.0.0-5", @@ -233,7 +232,7 @@ "eslint-plugin-es": "4.1.0", "eslint-plugin-import": "2.25.4", "eslint-plugin-jest": "23.20.0", - "eslint-plugin-jsdoc": "^36.0.7", + "eslint-plugin-jsdoc": "39.9.1", "eslint-plugin-jsx-a11y": "6.4.1", "eslint-plugin-playwright": "0.15.3", "eslint-plugin-react": "7.26.0", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 2517b4d80d2cb..7120d1a07c521 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -23,7 +23,7 @@ "eslint-plugin-deprecation": "^1.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^23.13.2", - "eslint-plugin-jsdoc": "^36.0.7", + "eslint-plugin-jsdoc": "^39.9.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.2.0", diff --git a/packages/fluentui/local-sandbox/package.json b/packages/fluentui/local-sandbox/package.json index 434a581d1c2c6..9575ac0b98066 100644 --- a/packages/fluentui/local-sandbox/package.json +++ b/packages/fluentui/local-sandbox/package.json @@ -4,7 +4,7 @@ "private": true, "license": "MIT", "scripts": { - "build:withdependencies": "yarn --cwd ../../.. build --scope=@fluentui/local-sandbox --include-filtered-dependencies", + "build:withdependencies": "yarn lage build --verbose --scope=@fluentui/local-sandbox --include-filtered-dependencies", "build": "webpack", "build:production": "webpack -p", "serve": "node ./server.js" diff --git a/packages/react-components/theme-designer/package.json b/packages/react-components/theme-designer/package.json index 5c7295f086c4b..60fc9de876d86 100644 --- a/packages/react-components/theme-designer/package.json +++ b/packages/react-components/theme-designer/package.json @@ -37,7 +37,6 @@ "tslib": "^2.1.0", "@fluentui/react-components": "^9.15.0", "@fluentui/react-icons": "^2.0.175", - "@fluent-blocks/colors": "9.2.0", "codesandbox-import-utils": "2.2.3", "@types/dedent": "0.7.0", "@fluentui/react-alert": "9.0.0-beta.31", diff --git a/packages/react-components/theme-designer/src/ThemeDesigner.tsx b/packages/react-components/theme-designer/src/ThemeDesigner.tsx index 6c75cd30a22e4..dbed543721666 100644 --- a/packages/react-components/theme-designer/src/ThemeDesigner.tsx +++ b/packages/react-components/theme-designer/src/ThemeDesigner.tsx @@ -31,7 +31,7 @@ export const ThemeDesigner: React.FC = props => { useStaticStyles(); const [appState, dispatchAppState] = useThemeDesignerReducer(); - const [name, setName] = React.useState('myTheme'); + const [name, setName] = React.useState('Untitled'); const { darkOverrides, isDark, lightOverrides, theme } = appState; const overrides = isDark ? darkOverrides : lightOverrides; diff --git a/packages/react-components/theme-designer/src/colors/csswg.ts b/packages/react-components/theme-designer/src/colors/csswg.ts new file mode 100644 index 0000000000000..a3febbcb0c357 --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/csswg.ts @@ -0,0 +1,774 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +// The following is a combination of several files retrieved from CSSWG’s +// CSS Color 4 module. It was modified to support TypeScript types adapted for +// the Fluent Blocks `colors` package and formatted to meet its style criteria. +import { Vec2, Vec3, Vec4 } from './types'; + +// [willshown]: Adjusted to export a TypeScript module. Retrieved on 24 May 2021 +// from https://drafts.csswg.org/css-color-4/multiply-matrices.js + +/** + * Simple matrix (and vector) multiplication + * Warning: No error handling for incompatible dimensions! + * @author Lea Verou 2020 MIT License + */ + +type MatrixIO = number[][] | number[]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isFlat(A: any): A is number[] { + return !Array.isArray(A[0]); +} + +// A is m x n. B is n x p. product is m x p. +export default function multiplyMatrices(AMatrixOrVector: MatrixIO, BMatrixOrVector: MatrixIO): MatrixIO { + const m = AMatrixOrVector.length; + + const A: number[][] = isFlat(AMatrixOrVector) + ? // A is vector, convert to [[a, b, c, ...]] + [AMatrixOrVector] + : AMatrixOrVector; + + const B: number[][] = isFlat(BMatrixOrVector) + ? // B is vector, convert to [[a], [b], [c], ...]] + BMatrixOrVector.map(x => [x]) + : BMatrixOrVector; + + const p = B[0].length; + const B_cols = B[0].map((_, i) => B.map(x => x[i])); // transpose B + let product: MatrixIO = A.map(row => + B_cols.map(col => { + if (!Array.isArray(row)) { + return col.reduce((a, c) => a + c * row, 0); + } + + return row.reduce((a, c, i) => a + c * (col[i] || 0), 0); + }), + ); + + if (m === 1) { + product = product[0]; // Avoid [[a, b, c, ...]] + } + + if (p === 1) { + return (product as number[][]).map(x => x[0]); // Avoid [[a], [b], [c], ...]] + } + + return product; +} + +// Sample code for color conversions +// Conversion can also be done using ICC profiles and a Color Management System +// For clarity, a library is used for matrix multiplication (multiply-matrices.js) + +// [willshown]: Adjusted to export a TypeScript module. Retrieved on 24 May 2021 +// from https://drafts.csswg.org/css-color-4/conversions.js + +// sRGB-related functions + +export function lin_sRGB(RGB: Vec3) { + // convert an array of sRGB values + // where in-gamut values are in the range [0 - 1] + // to linear light (un-companded) form. + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // for negative values, linear portion is extended on reflection of axis, + // then reflected power function is used. + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs < 0.04045) { + return val / 12.92; + } + + return sign * Math.pow((abs + 0.055) / 1.055, 2.4); + }) as Vec3; +} + +export function gam_sRGB(RGB: Vec3) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs > 0.0031308) { + return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); + } + + return 12.92 * val; + }) as Vec3; +} + +export function lin_sRGB_to_XYZ(rgb: Vec3) { + // convert an array of linear-light sRGB values to CIE XYZ + // using sRGB's own white, D65 (no chromatic adaptation) + + const M = [ + [0.41239079926595934, 0.357584339383878, 0.1804807884018343], + [0.21263900587151027, 0.715168678767756, 0.07219231536073371], + [0.01933081871559182, 0.11919477979462598, 0.9505321522496607], + ]; + return multiplyMatrices(M, rgb) as Vec3; +} + +export function XYZ_to_lin_sRGB(XYZ: Vec3) { + // convert XYZ to linear-light sRGB + + const M = [ + [3.2409699419045226, -1.537383177570094, -0.4986107602930034], + [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559], + [0.05563007969699366, -0.20397695888897652, 1.0569715142428786], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// display-p3-related functions + +export function lin_P3(RGB: Vec3) { + // convert an array of display-p3 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + + return lin_sRGB(RGB) as Vec3; // same as sRGB +} + +export function gam_P3(RGB: Vec3) { + // convert an array of linear-light display-p3 RGB in the range 0.0-1.0 + // to gamma corrected form + + return gam_sRGB(RGB) as Vec3; // same as sRGB +} + +export function lin_P3_to_XYZ(rgb: Vec3) { + // convert an array of linear-light display-p3 values to CIE XYZ + // using D65 (no chromatic adaptation) + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const M = [ + [0.4865709486482162, 0.26566769316909306, 0.1982172852343625], + [0.2289745640697488, 0.6917385218365064, 0.079286914093745], + [0.0, 0.04511338185890264, 1.043944368900976], + ]; + // 0 was computed as -3.972075516933488e-17 + + return multiplyMatrices(M, rgb) as Vec3; +} + +export function XYZ_to_lin_P3(XYZ: Vec3) { + // convert XYZ to linear-light P3 + const M = [ + [2.493496911941425, -0.9313836179191239, -0.40271078445071684], + [-0.8294889695615747, 1.7626640603183463, 0.023624685841943577], + [0.03584583024378447, -0.07617238926804182, 0.9568845240076872], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// prophoto-rgb functions + +export function lin_ProPhoto(RGB: Vec3) { + // convert an array of prophoto-rgb values + // where in-gamut colors are in the range [0.0 - 1.0] + // to linear light (un-companded) form. + // Transfer curve is gamma 1.8 with a small linear portion + // Extended transfer function + const Et2 = 16 / 512; + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs <= Et2) { + return val / 16; + } + + return sign * Math.pow(val, 1.8); + }) as Vec3; +} + +export function gam_ProPhoto(RGB: Vec3) { + // convert an array of linear-light prophoto-rgb in the range 0.0-1.0 + // to gamma corrected form + // Transfer curve is gamma 1.8 with a small linear portion + // TODO for negative values, extend linear portion on reflection of axis, then add pow below that + const Et = 1 / 512; + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs >= Et) { + return sign * Math.pow(abs, 1 / 1.8); + } + + return 16 * val; + }) as Vec3; +} + +export function lin_ProPhoto_to_XYZ(rgb: Vec3) { + // convert an array of linear-light prophoto-rgb values to CIE XYZ + // using D50 (so no chromatic adaptation needed afterwards) + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const M = [ + [0.7977604896723027, 0.13518583717574031, 0.0313493495815248], + [0.2880711282292934, 0.7118432178101014, 0.00008565396060525902], + [0.0, 0.0, 0.8251046025104601], + ]; + + return multiplyMatrices(M, rgb) as Vec3; +} + +export function XYZ_to_lin_ProPhoto(XYZ: Vec3) { + // convert XYZ to linear-light prophoto-rgb + const M = [ + [1.3457989731028281, -0.25558010007997534, -0.05110628506753401], + [-0.5446224939028347, 1.5082327413132781, 0.02053603239147973], + [0.0, 0.0, 1.2119675456389454], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// a98-rgb functions + +export function lin_a98rgb(RGB: Vec3) { + // convert an array of a98-rgb values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // negative values are also now accepted + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + return sign * Math.pow(abs, 563 / 256); + }) as Vec3; +} + +export function gam_a98rgb(RGB: Vec3) { + // convert an array of linear-light a98-rgb in the range 0.0-1.0 + // to gamma corrected form + // negative values are also now accepted + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + return sign * Math.pow(abs, 256 / 563); + }) as Vec3; +} + +export function lin_a98rgb_to_XYZ(rgb: Vec3) { + // convert an array of linear-light a98-rgb values to CIE XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + // has greater numerical precision than section 4.3.5.3 of + // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf + // but the values below were calculated from first principles + // from the chromaticity coordinates of R G B W + // see matrixmaker.html + const M = [ + [0.5766690429101305, 0.1855582379065463, 0.1882286462349947], + [0.29734497525053605, 0.6273635662554661, 0.07529145849399788], + [0.02703136138641234, 0.07068885253582723, 0.9913375368376388], + ]; + + return multiplyMatrices(M, rgb) as Vec3; +} + +export function XYZ_to_lin_a98rgb(XYZ: Vec3) { + // convert XYZ to linear-light a98-rgb + const M = [ + [2.0415879038107465, -0.5650069742788596, -0.34473135077832956], + [-0.9692436362808795, 1.8759675015077202, 0.04155505740717557], + [0.013444280632031142, -0.11836239223101838, 1.0151749943912054], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// Rec. 2020-related functions + +export function lin_2020(RGB: Vec3) { + // convert an array of rec2020 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // ITU-R BT.2020-2 p.4 + + const α = 1.09929682680944; + const β = 0.018053968510807; + + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs < β * 4.5) { + return val / 4.5; + } + + return sign * Math.pow((abs + α - 1) / α, 1 / 0.45); + }) as Vec3; +} + +export function gam_2020(RGB: Vec3) { + // convert an array of linear-light rec2020 RGB in the range 0.0-1.0 + // to gamma corrected form + // ITU-R BT.2020-2 p.4 + + const α = 1.09929682680944; + const β = 0.018053968510807; + + return RGB.map(val => { + const sign = val < 0 ? -1 : 1; + const abs = Math.abs(val); + + if (abs > β) { + return sign * (α * Math.pow(abs, 0.45) - (α - 1)); + } + + return 4.5 * val; + }) as Vec3; +} + +export function lin_2020_to_XYZ(rgb: Vec3) { + // convert an array of linear-light rec2020 values to CIE XYZ + // using D65 (no chromatic adaptation) + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const M = [ + [0.6369580483012914, 0.14461690358620832, 0.1688809751641721], + [0.2627002120112671, 0.6779980715188708, 0.05930171646986196], + [0.0, 0.028072693049087428, 1.060985057710791], + ]; + // 0 is actually calculated as 4.994106574466076e-17 + + return multiplyMatrices(M, rgb) as Vec3; +} + +export function XYZ_to_lin_2020(XYZ: Vec3) { + // convert XYZ to linear-light rec2020 + const M = [ + [1.7166511879712674, -0.35567078377639233, -0.25336628137365974], + [-0.6666843518324892, 1.6164812366349395, 0.01576854581391113], + [0.017639857445310783, -0.042770613257808524, 0.9421031212354738], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// Chromatic adaptation + +export function D65_to_D50(XYZ: Vec3) { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + const M = [ + [1.0479298208405488, 0.022946793341019088, -0.05019222954313557], + [0.029627815688159344, 0.990434484573249, -0.01707382502938514], + [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +export function D50_to_D65(XYZ: Vec3) { + // Bradford chromatic adaptation from D50 to D65 + const M = [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], + ]; + + return multiplyMatrices(M, XYZ) as Vec3; +} + +// Lab and LCH + +export function XYZ_to_Lab(XYZ: Vec3) { + // Assuming XYZ is relative to D50, convert to CIE Lab + // from CIE standard, which now defines these as a rational fraction + const ε = 216 / 24389; // 6^3/29^3 + const κ = 24389 / 27; // 29^3/3^3 + const white = [0.96422, 1.0, 0.82521]; // D50 reference white + + // compute xyz, which is XYZ scaled relative to reference white + const xyz = XYZ.map((value, i) => value / white[i]); + + // now compute f + const f = xyz.map(value => (value > ε ? Math.cbrt(value) : (κ * value + 16) / 116)); + + return [ + 116 * f[1] - 16, // L + 500 * (f[0] - f[1]), // a + 200 * (f[1] - f[2]), // b + ] as Vec3; +} + +export function Lab_to_XYZ(Lab: Vec3) { + // Convert Lab to D50-adapted XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const κ = 24389 / 27; // 29^3/3^3 + const ε = 216 / 24389; // 6^3/29^3 + const white = [0.96422, 1.0, 0.82521]; // D50 reference white + const f = []; + + // compute f, starting with the luminance-related term + f[1] = (Lab[0] + 16) / 116; + f[0] = Lab[1] / 500 + f[1]; + f[2] = f[1] - Lab[2] / 200; + + // compute xyz + const xyz = [ + Math.pow(f[0], 3) > ε ? Math.pow(f[0], 3) : (116 * f[0] - 16) / κ, + Lab[0] > κ * ε ? Math.pow((Lab[0] + 16) / 116, 3) : Lab[0] / κ, + Math.pow(f[2], 3) > ε ? Math.pow(f[2], 3) : (116 * f[2] - 16) / κ, + ]; + + // Compute XYZ by scaling xyz by reference white + return xyz.map((value, i) => value * white[i]) as Vec3; +} + +export function Lab_to_LCH(Lab: Vec3) { + // Convert to polar form + const hue = (Math.atan2(Lab[2], Lab[1]) * 180) / Math.PI; + return [ + Lab[0], // L is still L + Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)), // Chroma + hue >= 0 ? hue : hue + 360, // Hue, in degrees [0 to 360) + ] as Vec3; +} + +export function LCH_to_Lab(LCH: Vec3) { + // Convert from polar form + return [ + LCH[0], // L is still L + LCH[1] * Math.cos((LCH[2] * Math.PI) / 180), // a + LCH[1] * Math.sin((LCH[2] * Math.PI) / 180), // b + ] as Vec3; +} + +/** + * Converts an RGB color value to HSV. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes r, g, and b are contained in the set [0, 1] and + * returns h, s, and v in the set [0, 1]. + * + * @param rgb The red, green, and blue color values + * @return Array The HSV representation + */ +export function rgbToHsv(rgb: Vec3) { + const [r, g, b] = rgb; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h: number; + const v = max; + + const d = max - min; + const s = max === 0 ? 0 : d / max; + + if (max === min) { + h = 0; // achromatic + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + + h = h! / 6; + } + + return [h, s, v] as Vec3; +} + +// utility functions for color conversions + +// [willshown]: Adjusted to export a TypeScript module. +// Retrieved on 24 May 2021 from https://drafts.csswg.org/css-color-4/utilities.js + +export function sRGB_to_luminance(RGB: Vec3) { + // convert an array of gamma-corrected sRGB values + // in the 0.0 to 1.0 range + // to linear-light sRGB, then to CIE XYZ + // and return luminance (the Y value) + + const XYZ = lin_sRGB_to_XYZ(lin_sRGB(RGB)); + return XYZ[1]; +} + +export function contrast(RGB1: Vec3, RGB2: Vec3) { + // return WCAG 2.1 contrast ratio + // https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio + // for two sRGB values + // given as arrays of 0.0 to 1.0 + + const L1 = sRGB_to_luminance(RGB1); + const L2 = sRGB_to_luminance(RGB2); + + if (L1 > L2) { + return (L1 + 0.05) / (L2 + 0.05); + } + + return (L2 + 0.05) / (L1 + 0.05); +} + +export function sRGB_to_LCH(RGB: Vec3) { + // convert an array of gamma-corrected sRGB values + // in the 0.0 to 1.0 range + // to linear-light sRGB, then to CIE XYZ, + // then adapt from D65 to D50, + // then convert XYZ to CIE Lab + // and finally, convert to CIE LCH + + return Lab_to_LCH(XYZ_to_Lab(D65_to_D50(lin_sRGB_to_XYZ(lin_sRGB(RGB))))); +} + +export function sRGB_to_LAB(RGB: Vec3) { + // convert an array of gamma-corrected sRGB values + // in the 0.0 to 1.0 range + // to linear-light sRGB, then to CIE XYZ, + // then adapt from D65 to D50, + // then convert XYZ to CIE Lab + + return XYZ_to_Lab(D65_to_D50(lin_sRGB_to_XYZ(lin_sRGB(RGB)))); +} + +export function P3_to_LCH(RGB: Vec3) { + // convert an array of gamma-corrected display-p3 values + // in the 0.0 to 1.0 range + // to linear-light display-p3, then to CIE XYZ, + // then adapt from D65 to D50, + // then convert XYZ to CIE Lab + // and finally, convert to CIE LCH + + return Lab_to_LCH(XYZ_to_Lab(D65_to_D50(lin_P3_to_XYZ(lin_P3(RGB))))); +} + +export function r2020_to_LCH(RGB: Vec3) { + // convert an array of gamma-corrected rec.2020 values + // in the 0.0 to 1.0 range + // to linear-light sRGB, then to CIE XYZ, + // then adapt from D65 to D50, + // then convert XYZ to CIE Lab + // and finally, convert to CIE LCH + + return Lab_to_LCH(XYZ_to_Lab(D65_to_D50(lin_2020_to_XYZ(lin_2020(RGB))))); +} + +export function LCH_to_sRGB(LCH: Vec3) { + // convert an array of CIE LCH values + // to CIE Lab, and then to XYZ, + // adapt from D50 to D65, + // then convert XYZ to linear-light sRGB + // and finally to gamma corrected sRGB + // for in-gamut colors, components are in the 0.0 to 1.0 range + // out of gamut colors may have negative components + // or components greater than 1.0 + // so check for that :) + + return gam_sRGB(XYZ_to_lin_sRGB(D50_to_D65(Lab_to_XYZ(LCH_to_Lab(LCH))))); +} + +export function LAB_to_sRGB(LAB: Vec3) { + // convert an array of CIE Lab values to XYZ, + // adapt from D50 to D65, + // then convert XYZ to linear-light sRGB + // and finally to gamma corrected sRGB + // for in-gamut colors, components are in the 0.0 to 1.0 range + // out of gamut colors may have negative components + // or components greater than 1.0 + // so check for that :) + + return gam_sRGB(XYZ_to_lin_sRGB(D50_to_D65(Lab_to_XYZ(LAB)))); +} + +export function LCH_to_P3(LCH: Vec3) { + // convert an array of CIE LCH values + // to CIE Lab, and then to XYZ, + // adapt from D50 to D65, + // then convert XYZ to linear-light display-p3 + // and finally to gamma corrected display-p3 + // for in-gamut colors, components are in the 0.0 to 1.0 range + // out of gamut colors may have negative components + // or components greater than 1.0 + // so check for that :) + + return gam_P3(XYZ_to_lin_P3(D50_to_D65(Lab_to_XYZ(LCH_to_Lab(LCH))))); +} + +export function LCH_to_r2020(LCH: Vec3) { + // convert an array of CIE LCH values + // to CIE Lab, and then to XYZ, + // adapt from D50 to D65, + // then convert XYZ to linear-light rec.2020 + // and finally to gamma corrected rec.2020 + // for in-gamut colors, components are in the 0.0 to 1.0 range + // out of gamut colors may have negative components + // or components greater than 1.0 + // so check for that :) + + return gam_2020(XYZ_to_lin_2020(D50_to_D65(Lab_to_XYZ(LCH_to_Lab(LCH))))); +} + +// this is straight from the CSS Color 4 spec + +export function hslToRgb(hue: number, sat: number, light: number) { + // For simplicity, this algorithm assumes that the hue has been normalized + // to a number in the half-open range [0, 6), and the saturation and lightness + // have been normalized to the range [0, 1]. It returns an array of three numbers + // representing the red, green, and blue channels of the colors, + // normalized to the range [0, 1] + const t2 = light <= 0.5 ? light * (sat + 1) : light + sat - light * sat; + const t1 = light * 2 - t2; + const r = hueToChannel(t1, t2, hue + 2); + const g = hueToChannel(t1, t2, hue); + const b = hueToChannel(t1, t2, hue - 2); + return [r, g, b] as Vec3; +} + +export function hueToChannel(t1: number, t2: number, hue: number): number { + if (hue < 0) { + hue += 6; + } + if (hue >= 6) { + hue -= 6; + } + + if (hue < 1) { + return (t2 - t1) * hue + t1; + } else if (hue < 3) { + return t2; + } else if (hue < 4) { + return (t2 - t1) * (4 - hue) + t1; + } else { + return t1; + } +} + +// These are the naive algorithms from CS Color 4 + +export function naive_CMYK_to_sRGB(CMYK: Vec4) { + // CMYK is an array of four values + // in the range [0.0, 1.0] + // the optput is an array of [RGB] + // also in the [0.0, 1.0] range + // because the naive algorithm does not generate out of gamut colors + // neither does it generate accurate simulations of practical CMYK colors + + const cyan = CMYK[0]; + const magenta = CMYK[1]; + const yellow = CMYK[2]; + const black = CMYK[3]; + + const red = 1 - Math.min(1, cyan * (1 - black) + black); + const green = 1 - Math.min(1, magenta * (1 - black) + black); + const blue = 1 - Math.min(1, yellow * (1 - black) + black); + + return [red, green, blue] as Vec3; +} + +export function naive_sRGB_to_CMYK(RGB: Vec3) { + // RGB is an arravy of three values + // in the range [0.0, 1.0] + // the output is an array of [CMYK] + // also in the [0.0, 1.0] range + // with maximum GCR and (I think) 200% TAC + // the naive algorithm does not generate out of gamut colors + // neither does it generate accurate simulations of practical CMYK colors + + const red = RGB[0]; + const green = RGB[1]; + const blue = RGB[2]; + + const black = 1 - Math.max(red, green, blue); + const cyan = black === 1.0 ? 0 : (1 - red - black) / (1 - black); + const magenta = black === 1.0 ? 0 : (1 - green - black) / (1 - black); + const yellow = black === 1.0 ? 0 : (1 - blue - black) / (1 - black); + + return [cyan, magenta, yellow, black] as Vec4; +} + +// Chromaticity utilities + +export function XYZ_to_xy(XYZ: Vec3) { + // Convert an array of three XYZ values + // to x,y chromaticity coordinates + + const X = XYZ[0]; + const Y = XYZ[1]; + const Z = XYZ[2]; + const sum = X + Y + Z; + return [X / sum, Y / sum] as Vec2; +} + +export function xy_to_uv(xy: Vec2) { + // convert an x,y chromaticity pair + // to u*,v* chromaticities + + const x = xy[0]; + const y = xy[1]; + const denom = -2 * x + 12 * y + 3; + return [(4 * x) / denom, (9 * y) / denom] as Vec2; +} + +export function XYZ_to_uv(XYZ: Vec3) { + // Convert an array of three XYZ values + // to u*,v* chromaticity coordinates + + const X = XYZ[0]; + const Y = XYZ[1]; + const Z = XYZ[2]; + const denom = X + 15 * Y + 3 * Z; + return [(4 * X) / denom, (9 * Y) / denom] as Vec2; +} + +// [willshown]: Truncated to export only relevant functions and adjusted to export a TypeScript +// module, some additional adjustments to remove alpha support. Retrieved on 24 May 2021 +// from https://raw.githubusercontent.com/LeaVerou/css.land/master/lch/lch.js + +function is_LCH_inside_sRGB(l: number, c: number, h: number): boolean { + const ε = 0.000005; + const rgb = LCH_to_sRGB([+l, +c, +h]); + return rgb.reduce((a: boolean, b: number) => a && b >= 0 - ε && b <= 1 + ε, true); +} + +export function snap_into_gamut(Lab: Vec3): Vec3 { + // Moves an LCH color into the sRGB gamut + // by holding the l and h steady, + // and adjusting the c via binary-search + // until the color is on the sRGB boundary. + + // .0001 chosen fairly arbitrarily as "close enough" + const ε = 0.0001; + + const LCH = Lab_to_LCH(Lab); + const l = LCH[0]; + let c = LCH[1]; + const h = LCH[2]; + + if (is_LCH_inside_sRGB(l, c, h)) { + return Lab; + } + + let hiC = c; + let loC = 0; + c /= 2; + + while (hiC - loC > ε) { + if (is_LCH_inside_sRGB(l, c, h)) { + loC = c; + } else { + hiC = c; + } + c = (hiC + loC) / 2; + } + + return LCH_to_Lab([l, c, h]); +} diff --git a/packages/react-components/theme-designer/src/colors/geometry.ts b/packages/react-components/theme-designer/src/colors/geometry.ts new file mode 100644 index 0000000000000..19ebec95762ce --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/geometry.ts @@ -0,0 +1,206 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Curve, CurvePath, Vec3 } from './types'; + +const curveResolution = 128; + +// Many of these functions are ported from ThreeJS, which is distributed under +// the MIT license. Retrieved from https://github.com/mrdoob/three.js on +// 14 October 2021. + +function distanceTo(v1: Vec3, v2: Vec3) { + return Math.sqrt(distanceToSquared(v1, v2)); +} + +function distanceToSquared(v1: Vec3, v2: Vec3) { + const dx = v1[0] - v2[0]; + const dy = v1[1] - v2[1]; + const dz = v1[2] - v2[2]; + return dx * dx + dy * dy + dz * dz; +} + +function equals(v1: Vec3, v2: Vec3) { + return v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2]; +} + +function QuadraticBezierP0(t: number, p: number): number { + const k = 1 - t; + return k * k * p; +} + +function QuadraticBezierP1(t: number, p: number): number { + return 2 * (1 - t) * t * p; +} + +function QuadraticBezierP2(t: number, p: number): number { + return t * t * p; +} + +function QuadraticBezier(t: number, p0: number, p1: number, p2: number): number { + return QuadraticBezierP0(t, p0) + QuadraticBezierP1(t, p1) + QuadraticBezierP2(t, p2); +} + +function getPointOnCurve(curve: Curve, t: number) { + const [v0, v1, v2] = curve.points; + return [ + QuadraticBezier(t, v0[0], v1[0], v2[0]), + QuadraticBezier(t, v0[1], v1[1], v2[1]), + QuadraticBezier(t, v0[2], v1[2], v2[2]), + ] as Vec3; +} + +function getPointsOnCurve(curve: Curve, divisions: number): Vec3[] { + const points = []; + for (let d = 0; d <= divisions; d++) { + points.push(getPointOnCurve(curve, d / divisions)); + } + return points; +} + +function getCurvePathLength(curvePath: CurvePath) { + const lengths = getCurvePathLengths(curvePath); + return lengths[lengths.length - 1]; +} + +function getCurvePathLengths(curvePath: CurvePath) { + if (curvePath.cacheLengths && curvePath.cacheLengths.length === curvePath.curves.length) { + return curvePath.cacheLengths; + } + // Get length of sub-curve + // Push sums into cached array + const lengths = []; + let sums = 0; + for (let i = 0, l = curvePath.curves.length; i < l; i++) { + sums += getCurveLength(curvePath.curves[i]); + lengths.push(sums); + } + curvePath.cacheLengths = lengths; + return lengths; +} + +function getCurveLength(curve: Curve) { + const lengths = getCurveLengths(curve); + return lengths[lengths.length - 1]; +} + +function getCurveLengths(curve: Curve, divisions = curveResolution) { + if (curve.cacheArcLengths && curve.cacheArcLengths.length === divisions + 1) { + return curve.cacheArcLengths; + } + + const cache = []; + let current; + let last = getPointOnCurve(curve, 0); + let sum = 0; + + cache.push(0); + + for (let p = 1; p <= divisions; p++) { + current = getPointOnCurve(curve, p / divisions); + sum += distanceTo(current, last); + cache.push(sum); + last = current; + } + + curve.cacheArcLengths = cache; + + return cache; // { sums: cache, sum: sum }; Sum is in the last element. +} + +function getCurveUtoTMapping(curve: Curve, u: number, distance?: number) { + const arcLengths = getCurveLengths(curve); + let i = 0; + const il = arcLengths.length; + let targetArcLength; // The targeted u distance value to get + + if (distance) { + targetArcLength = distance; + } else { + targetArcLength = u * arcLengths[il - 1]; + } + + // binary search for the index with largest value smaller than target u distance + + let low = 0; + let high = il - 1; + let comparison; + + while (low <= high) { + i = Math.floor(low + (high - low) / 2); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + comparison = arcLengths[i] - targetArcLength; + + if (comparison < 0) { + low = i + 1; + } else if (comparison > 0) { + high = i - 1; + } else { + high = i; + break; + } + } + + i = high; + + if (arcLengths[i] === targetArcLength) { + return i / (il - 1); + } + + // we could get finer grain at lengths, or use simple interpolation between two points + const lengthBefore = arcLengths[i]; + const lengthAfter = arcLengths[i + 1]; + + const segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + const segmentFraction = (targetArcLength - lengthBefore) / segmentLength; + + // add that fractional amount to t + const t = (i + segmentFraction) / (il - 1); + + return t; +} + +function getPointOnCurveAt(curve: Curve, u: number) { + return getPointOnCurve(curve, getCurveUtoTMapping(curve, u)); +} + +export function getPointOnCurvePath(curvePath: CurvePath, t: number): Vec3 | null { + const d = t * getCurvePathLength(curvePath); + const curveLengths = getCurvePathLengths(curvePath); + let i = 0; + + while (i < curveLengths.length) { + if (curveLengths[i] >= d) { + const diff = curveLengths[i] - d; + const curve = curvePath.curves[i]; + + const segmentLength = getCurveLength(curve); + const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + + return getPointOnCurveAt(curve, u); + } + i++; + } + return null; +} + +export function getPointsOnCurvePath(curvePath: CurvePath, divisions = curveResolution): Vec3[] { + const points = []; + let last; + + for (let i = 0, curves = curvePath.curves; i < curves.length; i++) { + const curve = curves[i]; + const pts = getPointsOnCurve(curve, divisions); + + for (const point of pts) { + if (last && equals(last, point)) { + // ensures no consecutive points are duplicates + continue; + } + + points.push(point); + last = point; + } + } + + return points; +} diff --git a/packages/react-components/theme-designer/src/colors/index.ts b/packages/react-components/theme-designer/src/colors/index.ts new file mode 100644 index 0000000000000..7cecca373ae30 --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/index.ts @@ -0,0 +1,5 @@ +export * from './csswg'; +export * from './geometry'; +export * from './palettes'; +export * from './templates'; +export * from './types'; diff --git a/packages/react-components/theme-designer/src/colors/palettes.ts b/packages/react-components/theme-designer/src/colors/palettes.ts new file mode 100644 index 0000000000000..669595fc15b4e --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/palettes.ts @@ -0,0 +1,177 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { LAB_to_sRGB, LCH_to_Lab, Lab_to_LCH, sRGB_to_LCH, snap_into_gamut } from './csswg'; +import { getPointsOnCurvePath } from './geometry'; +import { CurvedHelixPath, Palette, Vec3 } from './types'; + +// This file contains functions that combine geometry and color math to create +// and work with palette curves. + +/** + * When distributing output shades along the curve, for each shade’s lightness a + * logarithmically distributed value is averaged with a linearly distributed + * value to this degree between zero and one, zero meaning use the logarithmic + * value, one meaning use the linear value. + */ +const defaultLinearity = 0.75; + +function getLinearSpace(min: number, max: number, n: number) { + const result = []; + const delta = (max - min) / n; + for (let i = 0; i < n; i++) { + result[i] = min + delta * i; + } + return result; +} + +const getLogSpace = (min: number, max: number, n: number) => { + const a = min <= 0 ? 0 : Math.log(min); + const b = Math.log(max); + const delta = (b - a) / n; + + const result = [Math.pow(Math.E, a)]; + for (let i = 1; i < n; i += 1) { + result.push(Math.pow(Math.E, a + delta * i)); + } + result.push(Math.pow(Math.E, b)); + return result; +}; + +function paletteShadesFromCurvePoints( + curvePoints: Vec3[], + nShades: number, + range = [0, 100], + linearity = defaultLinearity, +): Vec3[] { + if (curvePoints.length <= 2) { + return []; + } + + const paletteShades = []; + + const logLightness = getLogSpace(Math.log10(range[0]), Math.log10(range[1]), nShades); + + const linearLightness = getLinearSpace(range[0], range[1], nShades); + + let c = 0; + + for (let i = 0; i < nShades; i++) { + const l = Math.min( + range[1], + Math.max(range[0], logLightness[i] * (1 - linearity) + linearLightness[i] * linearity), + ); + + while (l > curvePoints[c + 1][0]) { + c++; + } + + const [l1, a1, b1] = curvePoints[c]; + const [l2, a2, b2] = curvePoints[c + 1]; + + const u = (l - l1) / (l2 - l1); + + paletteShades[i] = [l1 + (l2 - l1) * u, a1 + (a2 - a1) * u, b1 + (b2 - b1) * u] as Vec3; + } + + return paletteShades.map(snap_into_gamut); +} + +export function paletteShadesFromCurve( + curve: CurvedHelixPath, + nShades = 16, + range = [0, 100], + linearity = defaultLinearity, + curveDepth = 24, +): Vec3[] { + return paletteShadesFromCurvePoints( + getPointsOnCurvePath( + curve, + Math.ceil((curveDepth * (1 + Math.abs(curve.torsion || 1))) / 2), + ).map((curvePoint: Vec3) => getPointOnHelix(curvePoint, curve.torsion, curve.torsionT0)), + nShades, + range, + linearity, + ); +} + +export function sRGB_to_hex(rgb: Vec3): string { + return `#${rgb + .map(x => { + const channel = x < 0 ? 0 : Math.floor(x >= 1.0 ? 255 : x * 256); + return channel.toString(16).padStart(2, '0'); + }) + .join('')}`; +} + +export function Lab_to_hex(lab: Vec3): string { + return sRGB_to_hex(LAB_to_sRGB(lab)); +} + +export function hex_to_sRGB(hex: string): Vec3 { + const aRgbHex = hex.match(/#?(..)(..)(..)/); + return aRgbHex + ? [parseInt(aRgbHex[1], 16) / 255, parseInt(aRgbHex[2], 16) / 255, parseInt(aRgbHex[3], 16) / 255] + : [0, 0, 0]; +} + +export function hex_to_LCH(hex: string): Vec3 { + return sRGB_to_LCH(hex_to_sRGB(hex)); +} + +function paletteShadesToHex(paletteShades: Vec3[]): string[] { + return paletteShades.map(Lab_to_hex); +} + +function getPointOnHelix(pointOnCurve: Vec3, torsion = 0, torsionT0 = 50): Vec3 { + const t = pointOnCurve[0]; + const [l, c, h] = Lab_to_LCH(pointOnCurve); + const hueOffset = torsion * (t - torsionT0); + return LCH_to_Lab([l, c, h + hueOffset]); +} + +// function getPointOnCurvedHelixPathWithinGamut(curvedHelixPath: CurvedHelixPath, t: number): Vec3 { +// return snap_into_gamut( +// getPointOnHelix(getPointOnCurvePath(curvedHelixPath, t)!, curvedHelixPath.torsion, curvedHelixPath.torsionT0), +// ); +// } + +export function curvePathFromPalette({ keyColor, darkCp, lightCp, hueTorsion }: Palette): CurvedHelixPath { + const blackPosition = [0, 0, 0]; + const whitePosition = [100, 0, 0]; + const keyColorPosition = LCH_to_Lab(keyColor); + const [l, a, b] = keyColorPosition; + + const darkControlPosition = [l * (1 - darkCp), a, b]; + const lightControlPosition = [l + (100 - l) * lightCp, a, b]; + + return { + curves: [ + { points: [blackPosition, darkControlPosition, keyColorPosition] }, + { points: [keyColorPosition, lightControlPosition, whitePosition] }, + ], + torsion: hueTorsion, + torsionT0: l, + } as CurvedHelixPath; +} + +export function cssGradientFromCurve( + curve: CurvedHelixPath, + nShades = 16, + range = [0, 100], + linearity = defaultLinearity, + curveDepth = 24, +) { + const hexes = paletteShadesToHex(paletteShadesFromCurve(curve, nShades, range, linearity, curveDepth)); + return `linear-gradient(to right, ${hexes.join(', ')})`; +} + +export function hexColorsFromPalette( + palette: Palette, + nShades = 16, + range = [0, 100], + linearity = defaultLinearity, + curveDepth = 24, +): string[] { + return paletteShadesToHex( + paletteShadesFromCurve(curvePathFromPalette(palette), nShades, range, linearity, curveDepth), + ); +} diff --git a/packages/react-components/theme-designer/src/colors/templates.ts b/packages/react-components/theme-designer/src/colors/templates.ts new file mode 100644 index 0000000000000..08eb523159617 --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/templates.ts @@ -0,0 +1,17 @@ +import { NamedPalette, NamedTheme } from './types'; + +export const paletteTemplate = (id: string): NamedPalette & { id: string } => ({ + id, + name: '', + keyColor: [44.51, 39.05, 288.84], + darkCp: 2 / 3, + lightCp: 1 / 3, + hueTorsion: 0, +}); + +export const themeTemplate = (id: string): NamedTheme & { id: string } => ({ + id, + name: '', + backgrounds: {}, + foregrounds: {}, +}); diff --git a/packages/react-components/theme-designer/src/colors/types.ts b/packages/react-components/theme-designer/src/colors/types.ts new file mode 100644 index 0000000000000..dee880d18a740 --- /dev/null +++ b/packages/react-components/theme-designer/src/colors/types.ts @@ -0,0 +1,62 @@ +export type Vec2 = [number, number]; +export type Vec3 = [number, number, number]; +export type Vec4 = [number, number, number, number]; + +export type Curve = { + points: [Vec3, Vec3, Vec3]; + cacheArcLengths?: number[]; +}; + +export interface CurvePath { + curves: Curve[]; + cacheLengths?: number[]; +} + +export interface CurvedHelixPath extends CurvePath { + torsion?: number; + torsionT0?: number; +} + +export type Palette = { + keyColor: Vec3; + darkCp: number; + lightCp: number; + hueTorsion: number; +}; + +export type NamedPalette = Palette & { name: string }; + +export type PaletteConfig = { + range: [number, number]; + nShades: number; + linearity?: number; + shadeNames?: Record; +}; + +export type Theme = { + backgrounds: { + [paletteId: string]: PaletteConfig; + }; + foregrounds: { + [paletteId: string]: PaletteConfig; + }; +}; + +export type NamedTheme = Theme & { name: string }; + +export type TokenPackageType = 'csscp' | 'json'; + +export interface ThemeCollectionInclude { + [paletteId: string]: number[]; +} + +export type TokenPackageConfig = { + type: TokenPackageType; + selector: string; + include: { + [themeId: string]: { + backgrounds: ThemeCollectionInclude; + foregrounds: ThemeCollectionInclude; + }; + }; +}; diff --git a/packages/react-components/theme-designer/src/components/ColorTokens/getAccessibilityChecker.ts b/packages/react-components/theme-designer/src/components/ColorTokens/getAccessibilityChecker.ts index 8b9a6f43411bb..b84bb0e09507d 100644 --- a/packages/react-components/theme-designer/src/components/ColorTokens/getAccessibilityChecker.ts +++ b/packages/react-components/theme-designer/src/components/ColorTokens/getAccessibilityChecker.ts @@ -1,4 +1,4 @@ -import { contrast, hex_to_sRGB, Vec3 } from '@fluent-blocks/colors'; +import { contrast, hex_to_sRGB, Vec3 } from '../../colors'; import { Theme } from '@fluentui/react-components'; import { accessiblePairs } from './AccessiblePairs'; diff --git a/packages/react-components/theme-designer/src/components/Nav/Nav.tsx b/packages/react-components/theme-designer/src/components/Nav/Nav.tsx index f69ddf3fec3ed..0657e0f6238b8 100644 --- a/packages/react-components/theme-designer/src/components/Nav/Nav.tsx +++ b/packages/react-components/theme-designer/src/components/Nav/Nav.tsx @@ -45,12 +45,11 @@ export const Nav: React.FC = props => { const newName = e.target.value; if (newName.length === 0) { - setName('myTheme'); + setName('Untitled'); } else { const camelizeName = e.target.value .replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, idx) => (idx === 0 ? ltr.toLowerCase() : ltr.toUpperCase())) - .replace(/\s+/g, '') - .replace(/[^A-Za-z0-9@]*/g, ``); + .replace(/\s+/g, ''); setName(camelizeName); } @@ -72,8 +71,7 @@ export const Nav: React.FC = props => { // eslint-disable-next-line react/jsx-no-bind onChange={handleNameChange} contentAfter={} - placeholder={'myTheme'} - value={name === 'myTheme' ? '' : name} + placeholder={name} /> diff --git a/packages/react-components/theme-designer/src/components/Palette/Palette.tsx b/packages/react-components/theme-designer/src/components/Palette/Palette.tsx index a92d544eec117..d56cd992c06a6 100644 --- a/packages/react-components/theme-designer/src/components/Palette/Palette.tsx +++ b/packages/react-components/theme-designer/src/components/Palette/Palette.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { makeStyles, mergeClasses } from '@griffel/react'; import { Button, Caption1, Text } from '@fluentui/react-components'; import { Brands, BrandVariants } from '@fluentui/react-theme'; -import { contrast, hex_to_sRGB } from '@fluent-blocks/colors'; +import { contrast, hex_to_sRGB } from '../../colors'; import { bundleIcon, CopyFilled, CopyRegular } from '@fluentui/react-icons'; import { AppContext } from '../../ThemeDesigner'; import { useContextSelector } from '@fluentui/react-context-selector'; diff --git a/packages/react-components/theme-designer/src/components/Sidebar/Sidebar.tsx b/packages/react-components/theme-designer/src/components/Sidebar/Sidebar.tsx index 918a0b6c74dd1..28a8c832b24ab 100644 --- a/packages/react-components/theme-designer/src/components/Sidebar/Sidebar.tsx +++ b/packages/react-components/theme-designer/src/components/Sidebar/Sidebar.tsx @@ -30,6 +30,18 @@ export const Sidebar: React.FC = props => { const sidebarId = useId(); + // const [tab, setTab] = React.useState('use'); + // const handleTabChange = (event: SelectTabEvent, data: SelectTabData) => { + // // this is outdated, but i'm going to leave it until arman gives me more clarity on what the sidebar will look like + // if (data.value === 'edit') { + // dispatchAppState({ type: 'Custom', customAttributes: formState, overrides: {} }); + // } else if (data.value === 'use') { + // setTheme('Custom'); + // } + // setTab(data.value); + // }; + + // const [theme, setTheme] = React.useState('Web'); const [isDark, setIsDark] = React.useState(false); const [formState, setFormState] = React.useState({ diff --git a/packages/react-components/theme-designer/src/useThemeDesignerReducer.ts b/packages/react-components/theme-designer/src/useThemeDesignerReducer.ts index cd97a34c4afb2..66f0b51d7d563 100644 --- a/packages/react-components/theme-designer/src/useThemeDesignerReducer.ts +++ b/packages/react-components/theme-designer/src/useThemeDesignerReducer.ts @@ -32,7 +32,7 @@ export type AppState = { }; export const initialAppState = { - themeName: 'Custom', + themeName: 'Web', brand: brandWeb, theme: createLightTheme(brandWeb), isDark: false, diff --git a/packages/react-components/theme-designer/src/utils/getBrandTokensFromPalette.ts b/packages/react-components/theme-designer/src/utils/getBrandTokensFromPalette.ts index c4aaf77884425..3bb62c703f18f 100644 --- a/packages/react-components/theme-designer/src/utils/getBrandTokensFromPalette.ts +++ b/packages/react-components/theme-designer/src/utils/getBrandTokensFromPalette.ts @@ -1,5 +1,5 @@ import { BrandVariants } from '@fluentui/react-theme'; -import { Palette, hexColorsFromPalette, hex_to_LCH } from '@fluent-blocks/colors'; +import { Palette, hexColorsFromPalette, hex_to_LCH } from '../colors'; type Options = { darkCp?: number; diff --git a/packages/react-components/theme-designer/src/utils/themeList.ts b/packages/react-components/theme-designer/src/utils/themeList.ts index d16ea44d87941..82bce10f325c5 100644 --- a/packages/react-components/theme-designer/src/utils/themeList.ts +++ b/packages/react-components/theme-designer/src/utils/themeList.ts @@ -1,8 +1,10 @@ import { BrandVariants } from '@fluentui/react-components'; +import { brandWeb } from './brandColors'; export type ThemeList = Record; export const themeList: ThemeList = { + Web: { brand: brandWeb }, Custom: {}, }; diff --git a/packages/utilities/src/safeSetTimeout.ts b/packages/utilities/src/safeSetTimeout.ts index 805589277530d..cec1feeb35a06 100644 --- a/packages/utilities/src/safeSetTimeout.ts +++ b/packages/utilities/src/safeSetTimeout.ts @@ -8,15 +8,15 @@ import { extendComponent } from './extendComponent'; * accesses things within the component after being unmounted. */ export const safeSetTimeout = (component: React.Component) => { - let activeTimeouts: Set; + let activeTimeouts: Set>; return (cb: Function, duration: number) => { if (!activeTimeouts) { - activeTimeouts = new Set(); + activeTimeouts = new Set(); extendComponent(component, { componentWillUnmount: () => { - activeTimeouts.forEach((id: NodeJS.Timer) => clearTimeout(id)); + activeTimeouts.forEach(id => clearTimeout(id)); }, }); } diff --git a/yarn.lock b/yarn.lock index 39d17705e8c2a..26055cc82099f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1616,14 +1616,14 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@es-joy/jsdoccomment@0.10.7": - version "0.10.7" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.10.7.tgz#bdb5dfcaa5cff8b5435f0c9f2777b533075a9c52" - integrity sha512-aNKZEoMESDzOBjKxCWrFuG50mcpMeKVBnBNko4+IZZ5t9zXYs8GT1KB0ZaOq1YUsKumDRc6YII/TQm309MJ0KQ== +"@es-joy/jsdoccomment@~0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" + integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== dependencies: - comment-parser "1.2.3" + comment-parser "1.3.1" esquery "^1.4.0" - jsdoc-type-pratt-parser "1.1.1" + jsdoc-type-pratt-parser "~3.1.0" "@eslint/eslintrc@^0.4.0": version "0.4.3" @@ -1652,11 +1652,6 @@ dependencies: "@floating-ui/core" "^1.2.0" -"@fluent-blocks/colors@9.2.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@fluent-blocks/colors/-/colors-9.2.0.tgz#8aeb30f93f5f827b2842b9cff43541a87e304e18" - integrity sha512-NgK+n4IHRj35ttJjN3UBF8oqk2ZT8xwCdT52+nTXvVSL5yHDtKwQlgb+zvrNFhYNajbqEeqYdj4QA3XSkytLww== - "@fluentui/dom-utilities@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@fluentui/dom-utilities/-/dom-utilities-1.1.1.tgz#b0bbab665fe726f245800bb9e7883b1ceb54248b" @@ -5739,7 +5734,7 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== -"@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.7": +"@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== @@ -5747,10 +5742,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@16.18.1", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": - version "16.18.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.1.tgz#fd860a5efc505a5417d25a99cbff78077447a391" - integrity sha512-Z659t5cj2Tt2SaqbJxXRo5EaU86E4l2CxtklCY1VftxYXhR81Z75UsugwdI7l5MUAR1I+l8sdt3B5Y++ZV76WQ== +"@types/node@*", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^14.14.31": + version "14.18.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014" + integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow== "@types/node@10.17.13", "@types/node@^10.12.18": version "10.17.13" @@ -5762,16 +5757,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c" integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ== +"@types/node@18.18.6": + version "18.18.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e" + integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w== + "@types/node@^11.9.4": version "11.15.54" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.54.tgz#59ed60e7b0d56905a654292e8d73275034eb6283" integrity sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g== -"@types/node@^14.14.31": - version "14.18.32" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014" - integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow== - "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -5802,10 +5797,10 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== -"@types/prettier@*", "@types/prettier@2.2.3", "@types/prettier@^2.0.0", "@types/prettier@^2.1.5": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" - integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== +"@types/prettier@*", "@types/prettier@2.7.2", "@types/prettier@^2.0.0", "@types/prettier@^2.1.5": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/pretty-hrtime@^1.0.0": version "1.0.1" @@ -7650,13 +7645,13 @@ babel-jest@^27.2.2: graceful-fs "^4.2.4" slash "^3.0.0" -babel-loader@8.2.2, babel-loader@^8.0.0: - version "8.2.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" - integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== +babel-loader@8.3.0, babel-loader@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== dependencies: find-cache-dir "^3.3.1" - loader-utils "^1.4.0" + loader-utils "^2.0.0" make-dir "^3.1.0" schema-utils "^2.6.5" @@ -9418,10 +9413,10 @@ commander@^6.2.0, commander@^6.2.1: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -comment-parser@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.2.3.tgz#303a7eb99c9b2632efd594e183ccbd32042caf69" - integrity sha512-vnqDwBSXSsdAkGS5NjwMIPelE47q+UkEgWKHvCDNhVIIaQSUFY6sNnEYGzdoPGMdpV+7KR3ZkRd7oyWIjtuvJg== +comment-parser@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" + integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== common-ancestor-path@^1.0.1: version "1.0.1" @@ -11678,6 +11673,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -11798,19 +11798,17 @@ eslint-plugin-jest@^25.0.0: dependencies: "@typescript-eslint/experimental-utils" "^5.0.0" -eslint-plugin-jsdoc@^36.0.7: - version "36.0.7" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-36.0.7.tgz#6e6f9897fc2ff3b3934b09c2748b998bc03d2bf6" - integrity sha512-x73l/WCRQ1qCjLq46Ca7csuGd5o3y3vbJIa3cktg11tdf3UZleBdIXKN9Cf0xjs3tXYPEy2SoNXowT8ydnjNDQ== +eslint-plugin-jsdoc@39.9.1, eslint-plugin-jsdoc@^39.9.1: + version "39.9.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.9.1.tgz#e9ce1723411fd7ea0933b3ef0dd02156ae3068e2" + integrity sha512-Rq2QY6BZP2meNIs48aZ3GlIlJgBqFCmR55+UBvaDkA3ZNQ0SvQXOs2QKkubakEijV8UbIVbVZKsOVN8G3MuqZw== dependencies: - "@es-joy/jsdoccomment" "0.10.7" - comment-parser "1.2.3" - debug "^4.3.2" + "@es-joy/jsdoccomment" "~0.36.1" + comment-parser "1.3.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" esquery "^1.4.0" - jsdoc-type-pratt-parser "^1.1.1" - lodash "^4.17.21" - regextras "^0.8.0" - semver "^7.3.5" + semver "^7.3.8" spdx-expression-parse "^3.0.1" eslint-plugin-jsx-a11y@6.4.1, eslint-plugin-jsx-a11y@^6.4.1: @@ -16538,10 +16536,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdoc-type-pratt-parser@1.1.1, jsdoc-type-pratt-parser@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.1.1.tgz#10fe5e409ba38de22a48b555598955a26ff0160f" - integrity sha512-uelRmpghNwPBuZScwgBG/OzodaFk5RbO5xaivBdsAY70icWfShwZ7PCMO0x1zSkOa8T1FzHThmrdoyg/0AwV5g== +jsdoc-type-pratt-parser@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" + integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== jsdom@^16.4.0, jsdom@^16.6.0: version "16.7.0" @@ -17352,7 +17350,7 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@1.4.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@1.4.2, loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.2" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== @@ -21704,11 +21702,6 @@ regexpu-core@^5.2.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" -regextras@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.8.0.tgz#ec0f99853d4912839321172f608b544814b02217" - integrity sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ== - registry-auth-token@^3.0.1: version "3.4.0" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" @@ -22542,10 +22535,10 @@ semver@7.3.4: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@~7.3.0: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -22554,6 +22547,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@~7.3.0: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"