theme-color-level.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. let themeColors = {}
  2. //
  3. // Helper Functions
  4. //
  5. function lookupVariable(context, variableName) {
  6. const { frames, importantScope } = context
  7. return tree.Variable.prototype.find(frames, frame => {
  8. const { value, important } = frame.variable(variableName) || {}
  9. if (value === undefined)
  10. return
  11. if (important && importantScope[importantScope.length - 1])
  12. importantScope[importantScope.length - 1].important = important
  13. return value.eval(context)
  14. })
  15. }
  16. // @TODO: [@calvinjuarez] unify this function between files, maybe even canonize it as a
  17. // `Ruleset`/`DetachedRuleset` method at some point.
  18. function rulesetToMap(context, { ruleset: { rules } } = { ruleset: { rules: [] } }) {
  19. const map = {}
  20. rules.forEach(rule => {
  21. // Not exactly sure how to handle other types (or if they should be handled at all).
  22. if (! (rule instanceof tree.Declaration))
  23. return
  24. const { name: key, value } = rule.eval(context)
  25. map[key] = value
  26. })
  27. return map
  28. }
  29. //
  30. // Less Functions
  31. //
  32. functions.add('theme-color-level', function ({ value: colorName }, { value: level } = { value: 0 }) {
  33. const context = this.context
  34. const themeColorInterval = lookupVariable(context, '@theme-color-interval').value
  35. const black = lookupVariable(context, '@black').toCSS().substr(1)
  36. const white = lookupVariable(context, '@white').toCSS().substr(1)
  37. const mix = context.pluginManager.less.functions.functionRegistry.get('mix')
  38. // If `themeColors` hasn’t been defined yet, set it to the value of `@theme-colors`.
  39. if (Object.keys(themeColors).length === 0)
  40. themeColors = rulesetToMap(context, lookupVariable(context, '@theme-colors'))
  41. let color = themeColors[colorName]
  42. if (color.type === 'Expression')
  43. color = color.eval(context) // .eval() gets the Color node that the Expression node refers to
  44. const colorBase = new tree.Color(level > 0 ? black : white)
  45. const mixPercent = new tree.Dimension(Math.abs(level * themeColorInterval) + '%')
  46. if (! color)
  47. throw new ReferenceError(`\n\n\tColor “${colorName}” is not present in the \`@theme-colors\` map.\n\n`)
  48. // In order to match the output of the Sass version, we have to convert the color into a hex
  49. // string and then use that value to create a new Less `Color`.
  50. //
  51. // NOTE: This doesn’t work every time but it does most of the time. There are still some color
  52. // output discrepancies between the Less and Sass versions, but the differences are so small
  53. // they’re imperceptible.
  54. const colorResult = mix(colorBase, color, mixPercent)
  55. return new tree.Color(colorResult.toCSS().substr(1))
  56. })