diff --git a/package.json b/package.json index adc6478..7d22bb0 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,12 @@ "license": "SEE LICENSE.md", "packageManager": "pnpm@10.12.1", "scripts": { - "build": "pnpm --filter @dtcg/www run build", + "build": "pnpm --filter @dtcg/tr run build && pnpm --filter @dtcg/www run build", "dev": "pnpm run build && pnpm --parallel --recursive --if-present run dev", "lint": "pnpm --recursive --parallel --stream --if-present run lint", "format": "pnpm --recursive --parallel --stream --if-present run format", "prepare": "husky", - "install-browsers": "puppeteer browsers install chrome@137" + "install-browsers": "puppeteer browsers install chrome" }, "lint-staged": { "*.{md,yml,json,html}": "prettier --write" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bc630e..cfbc297 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 respec: - specifier: ^35.5.0 - version: 35.5.0 + specifier: ^35.5.1 + version: 35.5.1 www: devDependencies: @@ -1650,8 +1650,8 @@ packages: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated - respec@35.5.0: - resolution: {integrity: sha512-POjDng/tc07oP3dEreR1nGKGBBJlKnaa5ycAa4kwCV1JI0ZPlNIbUz1F/Ce9XuA7/L0Pl381OyTASmndfjDrXQ==} + respec@35.5.1: + resolution: {integrity: sha512-F1ykHL5WdMXb6Rp5EGjaPsc34SSIl8GWFRKlkFZ+xNhsQrU5cN9OsnxWYqP/XO/0Kkeyj7JKn4pC1Ykrqnj7LQ==} engines: {node: '>=20.12.1'} hasBin: true @@ -3757,7 +3757,7 @@ snapshots: resolve-url@0.2.1: {} - respec@35.5.0: + respec@35.5.1: dependencies: colors: 1.4.0 finalhandler: 2.1.0 diff --git a/technical-reports/format/index.html b/technical-reports/format/index.html index a9791a0..db16b4d 100644 --- a/technical-reports/format/index.html +++ b/technical-reports/format/index.html @@ -35,7 +35,6 @@ { name: 'Matthew Ström', url: 'https://matthewstrom.com' }, { name: 'Mike Kamminga', url: 'https://x.com/mikekamminga' }, ], - github: { repoURL: 'https://github.com/design-tokens/community-group', branch: 'main', diff --git a/technical-reports/index.html b/technical-reports/index.html index 671bbf7..a13e6d8 100644 --- a/technical-reports/index.html +++ b/technical-reports/index.html @@ -93,7 +93,7 @@

Modules

Format
  • Color
  • -
  • Animations (coming soon)
  • +
  • Resolver
  • + + + + Design Tokens Resolver Module + + + + +
    +

    + This specification extends the format and + describes a method to work with design tokens in + multiple contexts (such as “light mode” and “dark mode” color themes). +

    +
    + +
    +

    + This is a snapshot of the editors’ draft. It is provided for discussion + only and may change at any moment. Its publication here does not imply + endorsement of its contents by W3C or the Design Tokens W3C Community + Group Membership. Don’t cite this document other than as work in + progress. +

    +

    This document has been published to facilitate Wide Review.

    +

    + This document was produced by the Design Tokens W3C Community Group, and + contributions to this draft are governed by + Community Contributor License Agreement (CLA), as specified by the + W3C Community Group Process. +

    +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    Acknowledgments

    +

    + This resolver spec wouldn’t have happened without the Hyma Team, + including but not limited to Mike Kamminga, Andrew L'Homme, and Lilith. + Significant contributions were also made by Joren Broekema, Louis + Chenais. We thank the members of the Design Tokens Community Group for + their contributions and feedback. +

    +
    + + diff --git a/technical-reports/resolver/introduction.md b/technical-reports/resolver/introduction.md new file mode 100644 index 0000000..58f2af0 --- /dev/null +++ b/technical-reports/resolver/introduction.md @@ -0,0 +1,15 @@ +# Introduction + +Design tokens often need to express alternate values in different [=context=]s. What defines a “context” is up to the user to determine, and is unique to their usecases. But common examples of contexts are: + +- **Theming**, such as light mode, dark mode, and high contrast color modes +- **Sizing**, such as mobile (small), tablet (medium), desktop (large) +- **Accessibility mode**, such as reduced motion, colorblindness, etc. + +A [=resolver=] outlines a way to generate different end values (or sometimes even different tokens) to satisfy the needs for all these contexts. The general process is: + +1. Start from a base [set](#sets) of tokens +2. Apply [modifiers](#modifiers) based on which context(s) we are in +3. The end result is a complete token set with context-appropriate values. + +One would simply repeat the process once per the number of end-contexts being generated. diff --git a/technical-reports/resolver/resolution-logic.md b/technical-reports/resolver/resolution-logic.md new file mode 100644 index 0000000..f38da63 --- /dev/null +++ b/technical-reports/resolver/resolution-logic.md @@ -0,0 +1,461 @@ +# Resolution Logic + +Tools MUST handle the resolution stages in this order to produce the correct output. + +1. [Input validation](#input-validation) +2. [Token flattening](#token-flattening) +3. [Resolution](#resolution-0) + +## Input validation + +Tools MUST require all [=inputs=] meet the schema described in that resolver’s [modifiers syntax](#modifiers). + +If a resolver does NOT declare any modifiers, skip this step and proceed to [Sets flattening](#sets-flattening) + +1. For every key in the input object: + 1. Verify it corresponds with a valid modifier. If it does not, throw an error. + 1. Verify that key’s value corresponds with that modifier’s allowed values. If it does not, throw an error. +2. For every modifier in the resolver: + 1. If that resolver does NOT declare a default value, verify a key is provided in the input. If not, throw an error. + +## Token flattening + +Tools MUST iterate over the [tokens](#tokens) array in order. + +### Sets + +1. Sets are loaded in the [tokens](#tokens) array order. +1. If this uses [simple syntax](#simple-syntax) (string), treat it as if it is a set with no `name`, with a single item in the `sources` array. +1. Every token reference in `sources` MUST be resolved in array order. +1. For every token reference, merge with the previous set. +1. [Aliases](../format/#aliases-references) MUST NOT be resolved yet. That must happen at the end. Aliases MAY refer to values that will be supplied in upcoming steps. + + + + + + + +### Modifiers + +Every modifier is applied in order, taking in the external input [=inputs=]. + +1. For that modifier, load the corresponding [=input=] value. + 1. If there is not an input value, load the `default` context. + 1. If there is neither an input value nor `default` context, throw an error and stop resolution. +1. Load the context’s appropriate [<token-defs>](#token-defs) array that corresponds to the input value. +1. Flatten the [<token-defs>](#token-defs) array into the existing basis, in array order. + + + + + +### Conflict resolution (flattening) + +Conflict resolution occurs when flattening [=sets=] or applying [=modifiers=], and a token name appears by tokens of different values, from different sources. In many cases, this is intentional, but not always. + +When 2 tokens try and occupy the same space, tools MUST resolve the conflict in the following manner: + +1. If the token types are **identical**, overwrite the latter value with the former. +1. If the token types are **incompatible**, the tool MUST throw an error. +1. If one name is a token, and the other is a group, the tool MUST throw an error. +1. If one value is an alias (i.e. the `$type` is unknown), overwrite the value. + +## Alias resolution + +Alias resolution may only done after all [sets](#set-flattening) and [modifiers](#modifier-application) are handled, and there are no other tokens to merge in. Resolve aliases the same way as outlined in the [format](../format/#aliases-references), allowing deep aliases but erring and stopping resolution on circular aliases and/or aliases that point to unresolvable types (such as aliasing a [dimension token](#dimension) inside a [gradient token](#gradient), which is invalid). + +## Resolution + +After all aliases resolve correctly in the final set, the end result is one tokens object, that behaves as if it was a single JSON file to begin with. + + diff --git a/technical-reports/resolver/syntax.md b/technical-reports/resolver/syntax.md new file mode 100644 index 0000000..e769766 --- /dev/null +++ b/technical-reports/resolver/syntax.md @@ -0,0 +1,364 @@ +# Syntax + +## Resolver file + +A resolver is a JSON object with the following properties: + +| Name | Type | Required | Description | +| :------------------------------ | :------------------ | :------: | :--------------------------------------------------- | +| [**name**](#name) | `string` | | A short, human-readable name for the resolver. | +| [**version**](#version) | `YYYY-MM-DD` | Y | Version, expressed as a ISO 8601 date. | +| [**description**](#description) | `string` | | Additional information about the resolver’s purpose. | +| [**tokens**](#tokens) | (Set \| Modifier)[] | Y | Resolution order of tokens. | + +Users SHOULD name resolver files with a `.resolver.json` syntax. + +## Name + +A resolver MAY provide a human-readable name. This is used to identify the resolver. + + + +## Version + +MUST be `2025-10-01`. Reserved for future versions in case breaking changes are introduced. + +```json +{ + "name": "Marketing Design System", + "version": "2025-10-01" +} +``` + +## Description + +A resolver MAY provide additional information. + + + +## Tokens + +The tokens key is an array that may contain any combination of [sets](#set) and [modifiers](#modifier). The order is significant, with tokens later in the array overriding any tokens that came before them, in case of conflict. + + + +### Set + +| Name | Type | Required | Description | +| :---------- | :-------------------------------- | :------: | :----------------------------------------- | +| **type** | `"set"` | Y | MUST be `"set"`. | +| **name** | `string` | | Optional human-readable name for this set. | +| **sources** | [<token-defs>](#token-defs) | Y | The tokens that belong to this set. | + + + +#### Shortened syntax + +File strings from `sources` MAY be hoisted into the top-level [tokens](#tokens) array as a simpler syntax. + +Inline tokens (objects) MUST NOT ever be declared in [tokens](#tokens). An object inside [tokens](#tokens) MUST be either a [set](#set) or [modifier](#modifier). + + + +### Modifier + +A modifier can be thought of as a “conditional set,” where its contents depends on an external [input](#input). T + +| Name | Type | Required | Description | +| :---------- | :---------------------------------- | :------: | :---------------------------------------------------------------------------------------------- | +| **type** | `"modifier"` | Y | This MUST be `"modifier"`. | +| **name** | `string` | Y | The name of the modifier. This MUST be unique among other modifiers in the same file. | +| **context** | `Record` | Y | A key–value map of [contexts](#contexts) to [<token-defs>](#token-defs). | +| **default** | `string` | | Optional default value. MAY be provided in case the [input](#input) doesn’t require this value. | + +Tools MUST throw an error if 2 modifiers with the same name are declared in [tokens](#tokens). + + + +## Inputs + +An [=input=] is a mapping of modifier names to modifier values declared in any resolver. Inputs are not part of the resolver file itself, rather, provided to the tool alongside the resolver. A resolver that declares any modifiers MUST be consumed with an input as options. + +An input SHOULD be serializable to a JSON object. Meaning, an input MAY be expressed in any programming language, but that expression should be easily converted back into a JSON object. Related concepts would include an [object in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) or a [dictionary in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries). + +Tools that load a resolver that declares modifiers SHOULD throw an error if an accompanying input is not provided. + + + + + +### Orthogonality + +Modifiers are said to be [=orthogonal=] when they do not operate on the same set of tokens. In practical terms, if modifiers are orthogonal, then the order in which they are applied isn’t significant since they will produce the same values. + +Implementors SHOULD make modifiers orthogonal. Tools MAY decide how to handle non-orthogonal modifiers. + + + +## <token-defs> + +An array consisting of: + +- `string` which MUST be a valid URI pointing to a valid Tokens JSON file, or +- Inline objects that MUST follow valid [format](../format/). + +Any other inputs are invalid. + +The array order is significant, where tokens with the same name overwrite previous instances of that token, if any. + + + +## $extensions + +An `$extensions` object MAY be added to any [set](#set), [modifier](#modifier), or object in this spec to declare arbitrary metadata ignored by tooling. Its purpose in a resolver is the same as [in the format](../format/#extensions). diff --git a/technical-reports/resolver/terminology.md b/technical-reports/resolver/terminology.md new file mode 100644 index 0000000..19fff72 --- /dev/null +++ b/technical-reports/resolver/terminology.md @@ -0,0 +1,41 @@ +# Terminology + +## Resolver + +The mechanism by which multiple possible values of design tokens are reduced to a single value, i.e. this module. + +## Context + +Context is up to the end user to define, but is “any condition that requires a different value for a design token.” There are no official contexts in this specification. + +## Set + +Subsets of tokens that collectively form the default base set. Users SHOULD ensure the sum total of sets contain mutually exclusive tokens, i.e. they don’t overwrite one another and may be combined in any order to produce the same result. + +## Modifier + +Mapping of contexts to token impacts. Modifiers MAY accept an [=input=] that determine the final token values after the resolver has run. + +## Input + +The user’s selection for the [=modifier=]s, expressed as a key–value map. [See example](#modifiers). + +## Resolution + +The process of combining token sets and applying modifiers based on the specified inputs to produce the final set of tokens. + + + + + +## Orthogonal (orthogonality) + +The characteristic of [=modifiers=] that do not overlap with one another, i.e. operate on different tokens. Modifiers MAY be orthogonal, but it are not required to be. diff --git a/www/.eleventy.js b/www/.eleventy.js index f5f3d78..fb2f30a 100644 --- a/www/.eleventy.js +++ b/www/.eleventy.js @@ -67,15 +67,21 @@ export default function (eleventyConfig) { // Minify HTML output eleventyConfig.addTransform('htmlmin', function (content, outputPath) { - if (outputPath.indexOf('.html') > -1) { - let minified = htmlmin.minify(content, { - useShortDoctype: true, - removeComments: true, - collapseWhitespace: true, - }); - return minified; + // only minify HTML + if (!outputPath.endsWith('.html')) { + return content; } - return content; + + // don’t minify ReSpec + if (outputPath.toLowerCase().includes('/tr/')) { + return content; + } + + return htmlmin.minify(content, { + useShortDoctype: true, + removeComments: true, + collapseWhitespace: true, + }); }); // Assets @@ -83,7 +89,7 @@ export default function (eleventyConfig) { admin: 'admin', public: '/', }); - eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); + eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // Allow hot reloading of assets /* Markdown Plugins */ const mdit = markdownIt({ @@ -110,7 +116,7 @@ export default function (eleventyConfig) { // If you don’t have a subdirectory, use "" or "/" (they do the same thing) // This is only used for URLs (it does not affect your file structure) pathPrefix: '/', - htmlTemplateEngine: 'html', + htmlTemplateEngine: false, dataTemplateEngine: 'njk', dir: { input: '.',