cobalt-ui icon indicating copy to clipboard operation
cobalt-ui copied to clipboard

[question] Token referencing an alias gets raw value?

Open eloiseterry opened this issue 1 year ago • 4 comments

Setup: palette.json -> collection of colors, some containing a custom extension that adjusts the raw value colors.json -> collection of color tokens, each referencing a palette color

For example's sake, let's say my custom extension sets the opacity of an rgba color value.

// palette.json

{
  "palette": {
    "$type": "color",
    "black": {
      "$value": "rgba(0,0,0,100)",
      "$extensions": { "set-opacity": { "opacity": "0.25" } }
    }
  }
}

// colors.json

{
  "colors": {
    "$type": "color",
    "primary": {
      "$value": "{palette.black}"
    }
  }
}

When a plugin is run, such as @cobalt-ui/plugin-js, the value of token in the transform function references the value of the alias before the extensions are run, instead of the computed value. For example:

// tokens.config.mjs

import pluginCustom from './pluginCustom.mjs'

export default {
  tokens: [
    "./src/palette.json",
    "./src/color.json"
  ],
  plugins: [
    pluginJS({
      transform: (token) => {
        console.log(token.$value); // 'rgba(0,0,0,100)'

        console.log(token._original); // { '$value': '{palette.black}' }

        return token.$value;
      },
    })
}

Is this intended? If so, is there a way to either:

  1. Provide an option to have the plugins transform computed tokens (post-extension)
  2. Expose all properties of __original to include extensions

eloiseterry avatar Jan 16 '24 15:01 eloiseterry

Thanks for raising! $extensions in the spec is basically arbitrary metadata meant for tools to interpret how they will. There is no standard inside $extensions, so every tool will interpret it differently.

So there is no way for Cobalt to know how to execute set-opacity; it’s merely ignored. And as plugins are concerned with build targets, even if a plugin provided instructions with how to apply $extensions['set-opacity'], that would only apply to that plugin run and no other (i.e. plugin-js wouldn’t affect plugin-css).

So I think of the 2 proposals, #2 is the only one that’s possible—we do need to keep $extensions in the transformer for consumers to control how to execute that code.

I don’t think it’s intentional that’s omitted; it’s probably just a byproduct of how $extensions.mode works in Cobalt currently. We probably need to have better, clearer ways to provide multi-modal transforms in plugins, which means handling $extensions better in general.

drwpow avatar Jan 16 '24 19:01 drwpow

A related issue popped up this week with #192. I’ve realized that plugins need 3 things:

  1. The final / resolved value (exists currently)
  2. The original as-authored raw value (exists currently)
  3. (If aliased) The resolved original raw value, including extensions and modes (currently missing)

I think there’s a way to provide the third without a breaking change to the API. But things like these will inform the 2.0 API revision that will likely happen later this year.


On another note, this issue is related to #179

drwpow avatar Feb 17 '24 17:02 drwpow

Style Dictionary added this concept in v3. They call it "transitive transforms". Just wanted to note it as potential inspiration.

jbarreiros avatar Feb 27 '24 18:02 jbarreiros

@jbarreiros ah that’s very helpful, thank you! I am currently exploring something like that as part of the 2.0 plugin API. That will be a good reference point though

drwpow avatar Feb 27 '24 20:02 drwpow