From 1e12d04fb24eebc1df152017935dd65a2c6d7618 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Mon, 25 Mar 2024 10:57:12 -0600 Subject: [PATCH] Improve gamut clamping (#228) * Improve gamut clamping * Add docs * Improve Tokens Studio inline aliasing --- .changeset/cuddly-cooks-kiss.md | 5 + .changeset/fresh-ears-marry.md | 5 + .changeset/young-countries-try.md | 5 + docs/advanced/config.md | 8 +- docs/package.json | 4 +- docs/tokens/color.md | 4 + packages/core/src/parse/index.ts | 6 +- packages/core/src/parse/tokens-studio.ts | 78 ++++- packages/core/src/parse/tokens/border.ts | 2 +- packages/core/src/parse/tokens/color.ts | 43 ++- packages/core/src/parse/tokens/gradient.ts | 2 +- packages/core/src/parse/tokens/shadow.ts | 2 +- packages/core/test/index.test.ts | 89 ++++-- pnpm-lock.yaml | 315 ++++++++++++++++++--- 14 files changed, 484 insertions(+), 84 deletions(-) create mode 100644 .changeset/cuddly-cooks-kiss.md create mode 100644 .changeset/fresh-ears-marry.md create mode 100644 .changeset/young-countries-try.md diff --git a/.changeset/cuddly-cooks-kiss.md b/.changeset/cuddly-cooks-kiss.md new file mode 100644 index 00000000..4e853764 --- /dev/null +++ b/.changeset/cuddly-cooks-kiss.md @@ -0,0 +1,5 @@ +--- +"@cobalt-ui/core": patch +--- + +Improve Tokens Studio inline aliasing diff --git a/.changeset/fresh-ears-marry.md b/.changeset/fresh-ears-marry.md new file mode 100644 index 00000000..11ea1f79 --- /dev/null +++ b/.changeset/fresh-ears-marry.md @@ -0,0 +1,5 @@ +--- +"@cobalt-ui/core": minor +--- + +Add gamut clipping for color tokens diff --git a/.changeset/young-countries-try.md b/.changeset/young-countries-try.md new file mode 100644 index 00000000..1727dc35 --- /dev/null +++ b/.changeset/young-countries-try.md @@ -0,0 +1,5 @@ +--- +"@cobalt-ui/core": patch +--- + +Make parse options optional for easier use diff --git a/docs/advanced/config.md b/docs/advanced/config.md index 46626fd2..b8be2d7a 100644 --- a/docs/advanced/config.md +++ b/docs/advanced/config.md @@ -191,12 +191,14 @@ Some token types allow for extra configuration. export default { color: { convertToHex: false, // Convert all colors to sRGB hexadecimal (default: false). By default, colors are kept in their formats + gamut: undefined, // 'srgb' | 'p3' }, }; ``` ::: -| Name | Type | Description | -| :------------------- | :-------: | :--------------------------------------------------------------------------------------------------------------- | -| `color.convertToHex` | `boolean` | Convert this color to sRGB hexadecimal. By default, colors are kept in the original formats they’re authored in. | +| Name | Type | Description | +| :------------------- | :--------------: | :----------------------------------------------------------------------------------------- | +| `color.convertToHex` | `boolean` | (optional) Convert colors to 8-bit sRGB hexadecimal. | +| `color.gamut` | `'srgb' \| 'p3'` | (optional) Clamp colors to the `'srgb'` or `'p3'` gamut? (default: leave colors untouched) | diff --git a/docs/package.json b/docs/package.json index e2d4a32c..8794098f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,7 +12,7 @@ "vue": "^3.4.21" }, "devDependencies": { - "vite": "^5.1.6", - "vitepress": "1.0.0-rc.45" + "vite": "^5.2.6", + "vitepress": "1.0.1" } } diff --git a/docs/tokens/color.md b/docs/tokens/color.md index 67299f1a..79ac125c 100644 --- a/docs/tokens/color.md +++ b/docs/tokens/color.md @@ -43,6 +43,10 @@ Color is a frequently-used base token that can be aliased within the following c - [Shadow](/tokens/shadow) - [Gradient](/tokens/gradient) +## Global options + +See [color-specific configuration options](/advanced/config#color) + ## Tips & recommendations - [Culori](https://culorijs.org/) is the preferred library for working with color. It’s great both as an accurate, complete color science library that can parse & generate any format. But is also easy-to-use for simple color operations and is fast and [lightweight](https://culorijs.org/guides/tree-shaking/) (even on the client). diff --git a/packages/core/src/parse/index.ts b/packages/core/src/parse/index.ts index 916052d1..127e2ffc 100644 --- a/packages/core/src/parse/index.ts +++ b/packages/core/src/parse/index.ts @@ -39,11 +39,11 @@ export type LintRuleSeverity = 'error' | 'warn' | 'off' | number; export interface ParseOptions { /** Configure transformations for color tokens */ - color: ParseColorOptions; + color?: ParseColorOptions; figma?: FigmaParseOptions; /** Configure plugin lint rules (if any) */ - lint: { - rules: Record; + lint?: { + rules?: Record; }; } diff --git a/packages/core/src/parse/tokens-studio.ts b/packages/core/src/parse/tokens-studio.ts index 847530b4..719de6f9 100644 --- a/packages/core/src/parse/tokens-studio.ts +++ b/packages/core/src/parse/tokens-studio.ts @@ -3,7 +3,8 @@ * This works by first converting the Tokens Studio format * into an equivalent DTCG result, then parsing that result */ -import { parseAlias } from '@cobalt-ui/utils'; +import { isAlias, parseAlias } from '@cobalt-ui/utils'; +import { parse as culoriParse, rgb } from 'culori'; import type { GradientStop, Group, Token } from '../token.js'; // I’m not sure this is comprehensive at all but better than nothing @@ -284,9 +285,12 @@ export function convertTokensStudioFormat(rawTokens: Record): { `Token "${tokenID}" is a multi value borderRadius token. Expanding into ${tokenID}TopLeft, ${tokenID}TopRight, ${tokenID}BottomRight, and ${tokenID}BottomLeft.`, ); let order = [values[0], values[1], values[0], values[1]] as [string, string, string, string]; // TL, BR - if (values.length === 3) - {order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];} // TL, TR/BL, BR - else if (values.length === 4) {order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];} // TL, TR, BR, BL + if (values.length === 3) { + order = [values[0], values[1], values[2], values[1]] as [string, string, string, string]; + } // TL, TR/BL, BR + else if (values.length === 4) { + order = [values[0], values[1], values[2], values[3]] as [string, string, string, string]; + } // TL, TR, BR, BL addToken({ $type: 'dimension', $value: order[0], $description: v.description }, [...path, `${k}TopLeft`]); addToken({ $type: 'dimension', $value: order[1], $description: v.description }, [...path, `${k}TopRight`]); addToken({ $type: 'dimension', $value: order[2], $description: v.description }, [...path, `${k}BottomRight`]); @@ -365,10 +369,65 @@ export function convertTokensStudioFormat(rawTokens: Record): { tokenPath, ); } else { + let value: string | undefined = v.value; + // resolve inline aliases (e.g. `rgba({color.black}, 0.5)`) + if (value.includes('{') && !v.value.startsWith('{')) { + value = resolveAlias(value, path); + + if (!value) { + errors.push(`Could not resolve "${v.value}"`); + continue; + } + + // note: we did some work earlier to help resolve the aliases, but + // we need to REPLACE them in this scenario so we must do a 2nd pass + const matches = value.match(ALIAS_RE); + for (const match of matches ?? []) { + let currentAlias = parseAlias(match).id; + let resolvedValue: string | undefined; + const aliasHistory = new Set([currentAlias]); + while (!resolvedValue) { + const aliasNode: any = get(rawTokens, currentAlias.split('.')); + // does this resolve to a $value? + if (aliasNode && aliasNode.value) { + // is this another alias? + if (isAlias(aliasNode.value)) { + currentAlias = parseAlias(aliasNode.value).id; + if (aliasHistory.has(currentAlias)) { + errors.push(`Couldn’t resolve circular alias "${v.value}"`); + break; + } + aliasHistory.add(currentAlias); + continue; + } + resolvedValue = aliasNode.value; + } + break; + } + + if (resolvedValue) { + value = value.replace(match, resolvedValue); + } + } + + if (!culoriParse(value)) { + // fix `rgba(#000000, 0.3)` scenario specifically (common Tokens Studio version) + // but throw err otherwise + if (value.startsWith('rgb') && value.includes('#')) { + const hexValue = value.match(/#[abcdef0-9]+/i); + if (hexValue && hexValue[0]) { + const rgbVal = rgb(hexValue[0]); + if (rgbVal) { + value = value.replace(hexValue[0], `${rgbVal.r * 100}%, ${rgbVal.g * 100}%, ${rgbVal.b * 100}%`); + } + } + } + } + } addToken( { $type: 'color', - $value: v.value, + $value: value, $description: v.description, }, tokenPath, @@ -441,9 +500,12 @@ export function convertTokensStudioFormat(rawTokens: Record): { } else if (values.length === 2 || values.length === 3 || values.length === 4) { warnings.push(`Token "${tokenID}" is a multi value spacing token. Expanding into ${tokenID}Top, ${tokenID}Right, ${tokenID}Bottom, and ${tokenID}Left.`); let order: [string, string, string, string] = [values[0], values[1], values[0], values[1]] as [string, string, string, string]; // TB, RL - if (values.length === 3) - {order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];} // T, RL, B - else if (values.length === 4) {order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];} // T, R, B, L + if (values.length === 3) { + order = [values[0], values[1], values[2], values[1]] as [string, string, string, string]; + } // T, RL, B + else if (values.length === 4) { + order = [values[0], values[1], values[2], values[3]] as [string, string, string, string]; + } // T, R, B, L addToken({ $type: 'dimension', $value: order[0], $description: v.description }, [...path, `${k}Top`]); addToken({ $type: 'dimension', $value: order[1], $description: v.description }, [...path, `${k}Right`]); addToken({ $type: 'dimension', $value: order[2], $description: v.description }, [...path, `${k}Bottom`]); diff --git a/packages/core/src/parse/tokens/border.ts b/packages/core/src/parse/tokens/border.ts index dd3e9409..e6e23690 100644 --- a/packages/core/src/parse/tokens/border.ts +++ b/packages/core/src/parse/tokens/border.ts @@ -6,7 +6,7 @@ import { normalizeDimensionValue } from './dimension.js'; import { normalizeStrokeStyleValue } from './stroke-style.js'; export interface ParseBorderOptions { - color: ParseColorOptions; + color?: ParseColorOptions; } /** diff --git a/packages/core/src/parse/tokens/color.ts b/packages/core/src/parse/tokens/color.ts index 26d2dcec..8af99896 100644 --- a/packages/core/src/parse/tokens/color.ts +++ b/packages/core/src/parse/tokens/color.ts @@ -1,9 +1,11 @@ -import { formatHex, formatHex8, parse } from 'culori'; +import { type Color, clampChroma, formatHex, formatHex8, parse, formatCss } from 'culori'; import type { ParsedColorToken } from '../../token.js'; export interface ParseColorOptions { - /** Convert color to sRGB hex? (default: true) */ + /** Convert color to 8-bit sRGB hexadecimal? (default: false) */ convertToHex?: boolean; + /** Confine colors to gamut? sRGB is smaller but widely-supported; P3 supports more colors but not all users (default: `undefined`) */ + gamut?: 'srgb' | 'p3' | undefined; } /** @@ -15,19 +17,34 @@ export interface ParseColorOptions { * "$value": "#ff00ff" * } */ -export function normalizeColorValue(value: unknown, options: ParseColorOptions): ParsedColorToken['$value'] { - if (!value) { +export function normalizeColorValue(rawValue: unknown, options?: ParseColorOptions): ParsedColorToken['$value'] { + if (!rawValue) { throw new Error('missing value'); } - if (typeof value === 'string') { - if (options.convertToHex === true) { - const parsed = parse(value); - if (!parsed) { - throw new Error(`invalid color "${value}"`); - } - return typeof parsed.alpha === 'number' && parsed.alpha < 1 ? formatHex8(parsed) : formatHex(parsed); + if (typeof rawValue === 'string') { + const parsed = parse(rawValue); + if (!parsed) { + throw new Error(`invalid color "${rawValue}"`); } - return value; + + let value = parsed as Color; + let valueEdited = false; // keep track of this to reduce rounding errors + + // clamp to sRGB if we’re converting to hex, too! + if (options?.gamut === 'srgb' || options?.convertToHex === true) { + value = clampChroma(parsed, parsed.mode, 'rgb'); + valueEdited = true; + } else if (options?.gamut === 'p3') { + value = clampChroma(parsed, parsed.mode, 'p3'); + valueEdited = true; + } + + // TODO: in 2.x, only convert to hex if no color loss (e.g. don’t downgrade a 12-bit color `rgb()` to 8-bit hex) + if (options?.convertToHex === true) { + return typeof value.alpha === 'number' && value.alpha < 1 ? formatHex8(value) : formatHex(value); + } + + return valueEdited ? formatCss(value) : rawValue; // return the original value if we didn’t modify it; we may introduce nondeterministic rounding errors (the classic JS 0.3333… nonsense, etc.) } - throw new Error(`expected string, received ${typeof value}`); + throw new Error(`expected string, received ${typeof rawValue}`); } diff --git a/packages/core/src/parse/tokens/gradient.ts b/packages/core/src/parse/tokens/gradient.ts index b9caab21..65bda438 100644 --- a/packages/core/src/parse/tokens/gradient.ts +++ b/packages/core/src/parse/tokens/gradient.ts @@ -3,7 +3,7 @@ import type { ParseColorOptions } from './color.js'; import { normalizeColorValue } from './color.js'; export interface ParseGradientOptions { - color: ParseColorOptions; + color?: ParseColorOptions; } /** diff --git a/packages/core/src/parse/tokens/shadow.ts b/packages/core/src/parse/tokens/shadow.ts index 0ff74c43..9b26dadf 100644 --- a/packages/core/src/parse/tokens/shadow.ts +++ b/packages/core/src/parse/tokens/shadow.ts @@ -4,7 +4,7 @@ import { normalizeColorValue } from './color.js'; import { normalizeDimensionValue } from './dimension.js'; export interface ParseShadowOptions { - color: ParseColorOptions; + color?: ParseColorOptions; } /** diff --git a/packages/core/test/index.test.ts b/packages/core/test/index.test.ts index 892ade8b..c3b8cc6a 100644 --- a/packages/core/test/index.test.ts +++ b/packages/core/test/index.test.ts @@ -1,32 +1,30 @@ import { describe, expect, test } from 'vitest'; import { parse } from '../src/index.js'; +import { clampChroma, formatCss } from 'culori'; describe('parse', () => { test('sorts tokens', () => { const { result: { tokens }, - } = parse( - { - color: { - $type: 'color', - blue: { - '70': { $value: '#4887c9' }, - '10': { $value: '#062053' }, - '30': { $value: '#192f7d' }, - '80': { $value: '#5ca9d7' }, - '40': { $value: '#223793' }, - '50': { $description: 'Medium blue', $value: '#2b3faa' }, - '100': { $value: '#89eff1' }, - '60': { $value: '#3764ba' }, - '90': { $value: '#72cce5' }, - '20': { $value: '#0f2868' }, - '00': { $value: '{color.black}' }, - }, - black: { $value: '#000000' }, + } = parse({ + color: { + $type: 'color', + blue: { + '70': { $value: '#4887c9' }, + '10': { $value: '#062053' }, + '30': { $value: '#192f7d' }, + '80': { $value: '#5ca9d7' }, + '40': { $value: '#223793' }, + '50': { $description: 'Medium blue', $value: '#2b3faa' }, + '100': { $value: '#89eff1' }, + '60': { $value: '#3764ba' }, + '90': { $value: '#72cce5' }, + '20': { $value: '#0f2868' }, + '00': { $value: '{color.black}' }, }, + black: { $value: '#000000' }, }, - { color: {} }, - ); + }); expect(tokens.map((t) => t.id)).toEqual([ 'color.black', 'color.blue.00', @@ -42,4 +40,55 @@ describe('parse', () => { 'color.blue.100', ]); }); + + describe('color options', () => { + const colorTealID = 'color.teal'; + const colorTealValue = 'oklch(69.41% 0.185 179)'; // this is intentionally outside both sRGB and P3 gamuts + + test('convertToHex', () => { + // convertToHex: true + const { result: result1 } = parse( + { color: { teal: { $type: 'color', $value: colorTealValue } } }, + { + color: { convertToHex: true }, + }, + ); + expect(result1.tokens.find((t) => t.id === colorTealID)?.$value).toBe('#00b69e'); + + // convertToHex: false + const { result: result2 } = parse( + { color: { teal: { $type: 'color', $value: colorTealValue } } }, + { + color: { convertToHex: false }, + }, + ); + expect(result2.tokens.find((t) => t.id === colorTealID)?.$value).toBe(colorTealValue); + }); + + test('gamut', () => { + const { result: srgbResult } = parse({ color: { teal: { $type: 'color', $value: colorTealValue } } }, { color: { convertToHex: false, gamut: 'srgb' } }); + const srgbClamped = formatCss(clampChroma(colorTealValue, 'oklch', 'rgb')); + expect(srgbResult.tokens.find((t) => t.id === colorTealID)?.$value, 'sRGB').toBe(srgbClamped); + + const { result: p3Result } = parse({ color: { teal: { $type: 'color', $value: colorTealValue } } }, { color: { convertToHex: false, gamut: 'p3' } }); + const p3Clamped = formatCss(clampChroma(colorTealValue, 'oklch', 'p3')); + expect(p3Result.tokens.find((t) => t.id === colorTealID)?.$value, 'P3').toBe(p3Clamped); + + const { result: untouchedResult } = parse({ color: { teal: { $type: 'color', $value: colorTealValue } } }, { color: { convertToHex: false, gamut: undefined } }); + expect(untouchedResult.tokens.find((t) => t.id === colorTealID)?.$value, 'untouched').toBe(colorTealValue); + + // bonus: ignore invalid values (don’t bother warning) + const { result: badResult } = parse( + { color: { teal: { $type: 'color', $value: colorTealValue } } }, + { + color: { + convertToHex: false, + // @ts-expect-error we’re doing this on purpose + gamut: 'goofballs', + }, + }, + ); + expect(badResult.tokens.find((t) => t.id === colorTealID)?.$value, 'bad').toBe(colorTealValue); + }); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e81399b..664af0da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,11 +40,11 @@ importers: version: 3.4.21(typescript@5.4.2) devDependencies: vite: - specifier: ^5.1.6 - version: 5.1.6(@types/node@20.11.27) + specifier: ^5.2.6 + version: 5.2.6(@types/node@20.11.27) vitepress: - specifier: 1.0.0-rc.45 - version: 1.0.0-rc.45(@algolia/client-search@4.22.1)(search-insights@2.13.0)(typescript@5.4.2) + specifier: 1.0.1 + version: 1.0.1(@algolia/client-search@4.22.1)(search-insights@2.13.0)(typescript@5.4.2) examples/adobe: devDependencies: @@ -914,6 +914,15 @@ packages: dev: true optional: true + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm64@0.19.12: resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -923,6 +932,15 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm@0.19.12: resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -932,6 +950,15 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-x64@0.19.12: resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -941,6 +968,15 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-arm64@0.19.12: resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -950,6 +986,15 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-x64@0.19.12: resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -959,6 +1004,15 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-arm64@0.19.12: resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -968,6 +1022,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-x64@0.19.12: resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -977,6 +1040,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm64@0.19.12: resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -986,6 +1058,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm@0.19.12: resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -995,6 +1076,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ia32@0.19.12: resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -1004,6 +1094,15 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64@0.19.12: resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -1013,6 +1112,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-mips64el@0.19.12: resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -1022,6 +1130,15 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ppc64@0.19.12: resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -1031,6 +1148,15 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-riscv64@0.19.12: resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -1040,6 +1166,15 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-s390x@0.19.12: resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -1049,6 +1184,15 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-x64@0.19.12: resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -1058,6 +1202,15 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/netbsd-x64@0.19.12: resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -1067,6 +1220,15 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/openbsd-x64@0.19.12: resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -1076,6 +1238,15 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/sunos-x64@0.19.12: resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -1085,6 +1256,15 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-arm64@0.19.12: resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -1094,6 +1274,15 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-ia32@0.19.12: resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -1103,6 +1292,15 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-x64@0.19.12: resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -1112,6 +1310,15 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@9.0.0-beta.2): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1482,14 +1689,14 @@ packages: postcss: 8.4.31 dev: true - /@shikijs/core@1.1.7: - resolution: {integrity: sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==} + /@shikijs/core@1.2.0: + resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} dev: true - /@shikijs/transformers@1.1.7: - resolution: {integrity: sha512-lXz011ao4+rvweps/9h3CchBfzb1U5OtP5D51Tqc9lQYdLblWMIxQxH6Ybe1GeGINcEVM4goMyPrI0JvlIp4UQ==} + /@shikijs/transformers@1.2.0: + resolution: {integrity: sha512-xKn7DtA65DQV4FOfYsrvqM80xOy2xuXnxWWKsZmHv1VII/IOuDUDsWDu3KnpeLH6wqNJWp1GRoNUsHR1aw/VhQ==} dependencies: - shiki: 1.1.7 + shiki: 1.2.0 dev: true /@sinclair/typebox@0.27.8: @@ -1715,14 +1922,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@vitejs/plugin-vue@5.0.4(vite@5.1.6)(vue@3.4.21): + /@vitejs/plugin-vue@5.0.4(vite@5.2.6)(vue@3.4.21): resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 vue: ^3.2.25 dependencies: - vite: 5.1.6(@types/node@20.11.27) + vite: 5.2.6(@types/node@20.11.27) vue: 3.4.21(typescript@5.4.2) dev: true @@ -1772,7 +1979,7 @@ packages: '@vue/shared': 3.4.21 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.0.2 + source-map-js: 1.2.0 /@vue/compiler-dom@3.4.21: resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==} @@ -1790,8 +1997,8 @@ packages: '@vue/shared': 3.4.21 estree-walker: 2.0.2 magic-string: 0.30.8 - postcss: 8.4.35 - source-map-js: 1.0.2 + postcss: 8.4.38 + source-map-js: 1.2.0 /@vue/compiler-ssr@3.4.21: resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==} @@ -2760,6 +2967,37 @@ packages: '@esbuild/win32-x64': 0.19.12 dev: true + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -4382,6 +4620,15 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 + dev: true + + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 /preact@10.19.6: resolution: {integrity: sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==} @@ -4716,10 +4963,10 @@ packages: engines: {node: '>=8'} dev: true - /shiki@1.1.7: - resolution: {integrity: sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==} + /shiki@1.2.0: + resolution: {integrity: sha512-xLhiTMOIUXCv5DqJ4I70GgQCtdlzsTqFLZWcMHHG3TAieBUbvEGthdrlPDlX4mL/Wszx9C6rEcxU6kMlg4YlxA==} dependencies: - '@shikijs/core': 1.1.7 + '@shikijs/core': 1.2.0 dev: true /side-channel@1.0.6: @@ -4772,6 +5019,10 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + /spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} dependencies: @@ -5307,7 +5558,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.1.6(@types/node@20.11.27) + vite: 5.2.6(@types/node@20.11.27) transitivePeerDependencies: - '@types/node' - less @@ -5319,8 +5570,8 @@ packages: - terser dev: true - /vite@5.1.6(@types/node@20.11.27): - resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} + /vite@5.2.6(@types/node@20.11.27): + resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5348,19 +5599,19 @@ packages: optional: true dependencies: '@types/node': 20.11.27 - esbuild: 0.19.12 - postcss: 8.4.35 + esbuild: 0.20.2 + postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitepress@1.0.0-rc.45(@algolia/client-search@4.22.1)(search-insights@2.13.0)(typescript@5.4.2): - resolution: {integrity: sha512-/OiYsu5UKpQKA2c0BAZkfyywjfauDjvXyv6Mo4Ra57m5n4Bxg1HgUGoth1CLH2vwUbR/BHvDA9zOM0RDvgeSVQ==} + /vitepress@1.0.1(@algolia/client-search@4.22.1)(search-insights@2.13.0)(typescript@5.4.2): + resolution: {integrity: sha512-eNr5pOBppYUUjEhv8S0S2t9Tv95LQ6mMeHj6ivaGwfHxpov70Vduuwl/QQMDRznKDSaP0WKV7a82Pb4JVOaqEw==} hasBin: true peerDependencies: - markdown-it-mathjax3: ^4.3.2 - postcss: ^8.4.35 + markdown-it-mathjax3: ^4 + postcss: ^8 peerDependenciesMeta: markdown-it-mathjax3: optional: true @@ -5369,18 +5620,18 @@ packages: dependencies: '@docsearch/css': 3.6.0 '@docsearch/js': 3.6.0(@algolia/client-search@4.22.1)(search-insights@2.13.0) - '@shikijs/core': 1.1.7 - '@shikijs/transformers': 1.1.7 + '@shikijs/core': 1.2.0 + '@shikijs/transformers': 1.2.0 '@types/markdown-it': 13.0.7 - '@vitejs/plugin-vue': 5.0.4(vite@5.1.6)(vue@3.4.21) + '@vitejs/plugin-vue': 5.0.4(vite@5.2.6)(vue@3.4.21) '@vue/devtools-api': 7.0.17(vue@3.4.21) '@vueuse/core': 10.9.0(vue@3.4.21) '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.21) focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 6.3.0 - shiki: 1.1.7 - vite: 5.1.6(@types/node@20.11.27) + shiki: 1.2.0 + vite: 5.2.6(@types/node@20.11.27) vue: 3.4.21(typescript@5.4.2) transitivePeerDependencies: - '@algolia/client-search' @@ -5453,7 +5704,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.1.6(@types/node@20.11.27) + vite: 5.2.6(@types/node@20.11.27) vite-node: 1.3.1(@types/node@20.11.27) why-is-node-running: 2.2.2 transitivePeerDependencies: