color-map 2.2.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.codex-plugin/plugin.json +12 -0
  3. package/.cursor-plugin/plugin.json +12 -0
  4. package/CHANGELOG.md +100 -0
  5. package/README.md +71 -8
  6. package/bin/cli-CELmx3Y9.js +6414 -0
  7. package/bin/color-map.js +9 -0
  8. package/bin/index.js +1 -0
  9. package/cjs/color/index.d.ts +5 -0
  10. package/cjs/color/index.js +52 -0
  11. package/cjs/color-wNrkrhC7.js +374 -0
  12. package/cjs/color-wNrkrhC7.js.map +1 -0
  13. package/cjs/contrast-XjF5Rwui.js +293 -0
  14. package/cjs/contrast-XjF5Rwui.js.map +1 -0
  15. package/cjs/gamut-CG1J2aat.d.ts +158 -0
  16. package/cjs/gamut-CG1J2aat.d.ts.map +1 -0
  17. package/cjs/index-BcEqCPHz.d.ts +137 -0
  18. package/cjs/index-BcEqCPHz.d.ts.map +1 -0
  19. package/cjs/index-CGKCqRqD.d.ts +111 -0
  20. package/cjs/index-CGKCqRqD.d.ts.map +1 -0
  21. package/cjs/index-Cv3iUUYE.d.ts +171 -0
  22. package/cjs/index-Cv3iUUYE.d.ts.map +1 -0
  23. package/cjs/index-Dw43Hj4v.d.ts +41 -0
  24. package/cjs/index-Dw43Hj4v.d.ts.map +1 -0
  25. package/cjs/index-Dy8UARqO.d.ts +72 -0
  26. package/cjs/index-Dy8UARqO.d.ts.map +1 -0
  27. package/cjs/index.d.ts +26 -36
  28. package/cjs/index.d.ts.map +1 -1
  29. package/cjs/index.js +83 -62
  30. package/cjs/index.js.map +1 -1
  31. package/cjs/legacy/index.d.ts +2 -0
  32. package/cjs/legacy/index.js +75 -0
  33. package/cjs/legacy/index.js.map +1 -0
  34. package/cjs/mix-Cs4owSbW.js +158 -0
  35. package/cjs/mix-Cs4owSbW.js.map +1 -0
  36. package/cjs/mix-DGlKoUw4.d.ts +31 -0
  37. package/cjs/mix-DGlKoUw4.d.ts.map +1 -0
  38. package/cjs/palette/index.d.ts +3 -0
  39. package/cjs/palette/index.js +8 -0
  40. package/cjs/palette-DyFV_KoJ.js +332 -0
  41. package/cjs/palette-DyFV_KoJ.js.map +1 -0
  42. package/cjs/parse-8mt2OMKf.js +1508 -0
  43. package/cjs/parse-8mt2OMKf.js.map +1 -0
  44. package/cjs/scale/index.d.ts +2 -0
  45. package/cjs/scale/index.js +11 -0
  46. package/cjs/scale-Cq9pXrc1.js +318 -0
  47. package/cjs/scale-Cq9pXrc1.js.map +1 -0
  48. package/cjs/semantic-CrEIo8tS.d.ts +52 -0
  49. package/cjs/semantic-CrEIo8tS.d.ts.map +1 -0
  50. package/cjs/serialize-C69DAmoE.d.ts +25 -0
  51. package/cjs/serialize-C69DAmoE.d.ts.map +1 -0
  52. package/cjs/serialize-Ci2X0cPN.js +103 -0
  53. package/cjs/serialize-Ci2X0cPN.js.map +1 -0
  54. package/cjs/tokens/index.d.ts +2 -0
  55. package/cjs/tokens/index.js +6 -0
  56. package/cjs/tokens-CzXxaMgM.js +168 -0
  57. package/cjs/tokens-CzXxaMgM.js.map +1 -0
  58. package/cjs/tone-BAhu-QzQ.js +118 -0
  59. package/cjs/tone-BAhu-QzQ.js.map +1 -0
  60. package/dist/color-map.es5.js.map +1 -1
  61. package/esm/color/a98-rgb.d.ts +7 -0
  62. package/esm/color/a98-rgb.d.ts.map +1 -0
  63. package/esm/color/a98-rgb.js +13 -0
  64. package/esm/color/a98-rgb.js.map +1 -0
  65. package/esm/color/argb.d.ts +6 -0
  66. package/esm/color/argb.d.ts.map +1 -0
  67. package/esm/color/argb.js +25 -0
  68. package/esm/color/argb.js.map +1 -0
  69. package/esm/color/calc.js +82 -0
  70. package/esm/color/calc.js.map +1 -0
  71. package/esm/color/compare.d.ts +20 -0
  72. package/esm/color/compare.d.ts.map +1 -0
  73. package/esm/color/compare.js +46 -0
  74. package/esm/color/compare.js.map +1 -0
  75. package/esm/color/contrast.d.ts +107 -0
  76. package/esm/color/contrast.d.ts.map +1 -0
  77. package/esm/color/contrast.js +242 -0
  78. package/esm/color/contrast.js.map +1 -0
  79. package/esm/color/convert.d.ts +9 -0
  80. package/esm/color/convert.d.ts.map +1 -0
  81. package/esm/color/convert.js +45 -0
  82. package/esm/color/convert.js.map +1 -0
  83. package/esm/color/display-p3-linear.d.ts +7 -0
  84. package/esm/color/display-p3-linear.d.ts.map +1 -0
  85. package/esm/color/display-p3-linear.js +12 -0
  86. package/esm/color/display-p3-linear.js.map +1 -0
  87. package/esm/color/display-p3.d.ts +7 -0
  88. package/esm/color/display-p3.d.ts.map +1 -0
  89. package/esm/color/display-p3.js +12 -0
  90. package/esm/color/display-p3.js.map +1 -0
  91. package/esm/color/expr.d.ts +43 -0
  92. package/esm/color/expr.d.ts.map +1 -0
  93. package/esm/color/expr.js +160 -0
  94. package/esm/color/expr.js.map +1 -0
  95. package/esm/color/gamut.d.ts +31 -0
  96. package/esm/color/gamut.d.ts.map +1 -0
  97. package/esm/color/gamut.js +210 -0
  98. package/esm/color/gamut.js.map +1 -0
  99. package/esm/color/hsl.d.ts +7 -0
  100. package/esm/color/hsl.d.ts.map +1 -0
  101. package/esm/color/hsl.js +67 -0
  102. package/esm/color/hsl.js.map +1 -0
  103. package/esm/color/hwb.d.ts +7 -0
  104. package/esm/color/hwb.d.ts.map +1 -0
  105. package/esm/color/hwb.js +40 -0
  106. package/esm/color/hwb.js.map +1 -0
  107. package/esm/color/index.d.ts +28 -0
  108. package/esm/color/index.js +27 -0
  109. package/esm/color/lab.d.ts +7 -0
  110. package/esm/color/lab.d.ts.map +1 -0
  111. package/esm/color/lab.js +33 -0
  112. package/esm/color/lab.js.map +1 -0
  113. package/esm/color/lch.d.ts +7 -0
  114. package/esm/color/lch.d.ts.map +1 -0
  115. package/esm/color/lch.js +11 -0
  116. package/esm/color/lch.js.map +1 -0
  117. package/esm/color/math.js +32 -0
  118. package/esm/color/math.js.map +1 -0
  119. package/esm/color/matrices.js +180 -0
  120. package/esm/color/matrices.js.map +1 -0
  121. package/esm/color/mix.d.ts +30 -0
  122. package/esm/color/mix.d.ts.map +1 -0
  123. package/esm/color/mix.js +152 -0
  124. package/esm/color/mix.js.map +1 -0
  125. package/esm/color/named.js +11 -0
  126. package/esm/color/named.js.map +1 -0
  127. package/esm/color/oklab.d.ts +7 -0
  128. package/esm/color/oklab.d.ts.map +1 -0
  129. package/esm/color/oklab.js +21 -0
  130. package/esm/color/oklab.js.map +1 -0
  131. package/esm/color/oklch.d.ts +7 -0
  132. package/esm/color/oklch.d.ts.map +1 -0
  133. package/esm/color/oklch.js +11 -0
  134. package/esm/color/oklch.js.map +1 -0
  135. package/esm/color/parse.d.ts +9 -0
  136. package/esm/color/parse.d.ts.map +1 -0
  137. package/esm/color/parse.js +452 -0
  138. package/esm/color/parse.js.map +1 -0
  139. package/esm/color/polar.js +27 -0
  140. package/esm/color/polar.js.map +1 -0
  141. package/esm/color/prophoto-rgb.d.ts +7 -0
  142. package/esm/color/prophoto-rgb.d.ts.map +1 -0
  143. package/esm/color/prophoto-rgb.js +18 -0
  144. package/esm/color/prophoto-rgb.js.map +1 -0
  145. package/esm/color/rec2020.d.ts +7 -0
  146. package/esm/color/rec2020.d.ts.map +1 -0
  147. package/esm/color/rec2020.js +13 -0
  148. package/esm/color/rec2020.js.map +1 -0
  149. package/esm/color/resolve.d.ts +18 -0
  150. package/esm/color/resolve.d.ts.map +1 -0
  151. package/esm/color/resolve.js +98 -0
  152. package/esm/color/resolve.js.map +1 -0
  153. package/esm/color/rgb.js +20 -0
  154. package/esm/color/rgb.js.map +1 -0
  155. package/esm/color/serialize.d.ts +24 -0
  156. package/esm/color/serialize.d.ts.map +1 -0
  157. package/esm/color/serialize.js +88 -0
  158. package/esm/color/serialize.js.map +1 -0
  159. package/esm/color/spaces.d.ts +6 -0
  160. package/esm/color/spaces.d.ts.map +1 -0
  161. package/esm/color/spaces.js +42 -0
  162. package/esm/color/spaces.js.map +1 -0
  163. package/esm/color/srgb-linear.d.ts +7 -0
  164. package/esm/color/srgb-linear.d.ts.map +1 -0
  165. package/esm/color/srgb-linear.js +12 -0
  166. package/esm/color/srgb-linear.js.map +1 -0
  167. package/esm/color/srgb.d.ts +7 -0
  168. package/esm/color/srgb.d.ts.map +1 -0
  169. package/esm/color/srgb.js +12 -0
  170. package/esm/color/srgb.js.map +1 -0
  171. package/esm/color/transfer.js +11 -0
  172. package/esm/color/transfer.js.map +1 -0
  173. package/esm/color/types.d.ts +23 -0
  174. package/esm/color/types.d.ts.map +1 -0
  175. package/esm/color/unknown-name.js +16 -0
  176. package/esm/color/unknown-name.js.map +1 -0
  177. package/esm/color/xyz-d50.d.ts +7 -0
  178. package/esm/color/xyz-d50.d.ts.map +1 -0
  179. package/esm/color/xyz-d50.js +15 -0
  180. package/esm/color/xyz-d50.js.map +1 -0
  181. package/esm/color/xyz-d65.d.ts +4 -0
  182. package/esm/color/xyz-d65.d.ts.map +1 -0
  183. package/esm/color/xyz-d65.js +10 -0
  184. package/esm/color/xyz-d65.js.map +1 -0
  185. package/esm/index.d.ts +63 -6
  186. package/esm/index.d.ts.map +1 -0
  187. package/esm/index.js +54 -5
  188. package/esm/index.js.map +1 -0
  189. package/esm/legacy/createColors.d.ts +9 -0
  190. package/esm/legacy/createColors.d.ts.map +1 -0
  191. package/esm/{createColors.js → legacy/createColors.js} +7 -2
  192. package/esm/legacy/createColors.js.map +1 -0
  193. package/esm/legacy/createColorsFromMap.d.ts +9 -0
  194. package/esm/legacy/createColorsFromMap.d.ts.map +1 -0
  195. package/esm/{createColorsFromMap.js → legacy/createColorsFromMap.js} +4 -2
  196. package/esm/legacy/createColorsFromMap.js.map +1 -0
  197. package/esm/legacy/index.d.ts +6 -0
  198. package/esm/legacy/index.js +5 -0
  199. package/esm/legacy/interfaces.d.ts +19 -0
  200. package/esm/legacy/interfaces.d.ts.map +1 -0
  201. package/esm/{rgbHex.d.ts → legacy/rgbHex.d.ts} +1 -4
  202. package/esm/legacy/rgbHex.d.ts.map +1 -0
  203. package/esm/{rgbHex.js → legacy/rgbHex.js} +1 -2
  204. package/esm/legacy/rgbHex.js.map +1 -0
  205. package/esm/legacy/rgbaString.d.ts +4 -0
  206. package/esm/{rgbaString.d.ts.map → legacy/rgbaString.d.ts.map} +1 -1
  207. package/esm/legacy/rgbaString.js +8 -0
  208. package/esm/legacy/rgbaString.js.map +1 -0
  209. package/esm/palette/dark.d.ts +23 -0
  210. package/esm/palette/dark.d.ts.map +1 -0
  211. package/esm/palette/dark.js +54 -0
  212. package/esm/palette/dark.js.map +1 -0
  213. package/esm/palette/harmony.d.ts +56 -0
  214. package/esm/palette/harmony.d.ts.map +1 -0
  215. package/esm/palette/harmony.js +115 -0
  216. package/esm/palette/harmony.js.map +1 -0
  217. package/esm/palette/index.d.ts +5 -0
  218. package/esm/palette/index.js +5 -0
  219. package/esm/palette/semantic.d.ts +53 -0
  220. package/esm/palette/semantic.d.ts.map +1 -0
  221. package/esm/palette/semantic.js +96 -0
  222. package/esm/palette/semantic.js.map +1 -0
  223. package/esm/palette/validate.d.ts +39 -0
  224. package/esm/palette/validate.d.ts.map +1 -0
  225. package/esm/palette/validate.js +56 -0
  226. package/esm/palette/validate.js.map +1 -0
  227. package/esm/scale/brand.d.ts +21 -0
  228. package/esm/scale/brand.d.ts.map +1 -0
  229. package/esm/scale/brand.js +63 -0
  230. package/esm/scale/brand.js.map +1 -0
  231. package/esm/scale/contrast-scale.d.ts +33 -0
  232. package/esm/scale/contrast-scale.d.ts.map +1 -0
  233. package/esm/scale/contrast-scale.js +26 -0
  234. package/esm/scale/contrast-scale.js.map +1 -0
  235. package/esm/scale/gradient.d.ts +38 -0
  236. package/esm/scale/gradient.d.ts.map +1 -0
  237. package/esm/scale/gradient.js +73 -0
  238. package/esm/scale/gradient.js.map +1 -0
  239. package/esm/scale/index.d.ts +6 -0
  240. package/esm/scale/index.js +6 -0
  241. package/esm/scale/scale.d.ts +31 -0
  242. package/esm/scale/scale.d.ts.map +1 -0
  243. package/esm/scale/scale.js +152 -0
  244. package/esm/scale/scale.js.map +1 -0
  245. package/esm/scale/tone.d.ts +25 -0
  246. package/esm/scale/tone.d.ts.map +1 -0
  247. package/esm/scale/tone.js +96 -0
  248. package/esm/scale/tone.js.map +1 -0
  249. package/esm/tokens/css.d.ts +18 -0
  250. package/esm/tokens/css.d.ts.map +1 -0
  251. package/esm/tokens/css.js +23 -0
  252. package/esm/tokens/css.js.map +1 -0
  253. package/esm/tokens/dtcg.d.ts +33 -0
  254. package/esm/tokens/dtcg.d.ts.map +1 -0
  255. package/esm/tokens/dtcg.js +42 -0
  256. package/esm/tokens/dtcg.js.map +1 -0
  257. package/esm/tokens/index.d.ts +5 -0
  258. package/esm/tokens/index.js +4 -0
  259. package/esm/tokens/tailwind.d.ts +19 -0
  260. package/esm/tokens/tailwind.d.ts.map +1 -0
  261. package/esm/tokens/tailwind.js +19 -0
  262. package/esm/tokens/tailwind.js.map +1 -0
  263. package/esm/tokens/tokens.d.ts +11 -0
  264. package/esm/tokens/tokens.d.ts.map +1 -0
  265. package/esm/tokens/tokens.js +77 -0
  266. package/esm/tokens/tokens.js.map +1 -0
  267. package/llms.txt +48 -0
  268. package/package.json +277 -64
  269. package/plugin.json +18 -0
  270. package/schemas/contrast.schema.json +24 -0
  271. package/schemas/convert.schema.json +60 -0
  272. package/schemas/export.schema.json +106 -0
  273. package/schemas/find-contrast.schema.json +51 -0
  274. package/schemas/harmony.schema.json +58 -0
  275. package/schemas/mix.schema.json +72 -0
  276. package/schemas/palette.schema.json +56 -0
  277. package/schemas/scale.schema.json +86 -0
  278. package/skills/color-map/SKILL.md +38 -0
  279. package/src/cli/bin.ts +11 -0
  280. package/src/cli/cli.ts +40 -0
  281. package/src/cli/commands/contrast.ts +36 -0
  282. package/src/cli/commands/convert.ts +37 -0
  283. package/src/cli/commands/export.ts +102 -0
  284. package/src/cli/commands/find-contrast.ts +37 -0
  285. package/src/cli/commands/harmony.ts +30 -0
  286. package/src/cli/commands/mix.ts +29 -0
  287. package/src/cli/commands/palette.ts +66 -0
  288. package/src/cli/commands/scale.ts +75 -0
  289. package/src/cli/index.ts +3 -0
  290. package/src/cli/schema-command.ts +31 -0
  291. package/src/cli/schemas.ts +86 -0
  292. package/src/cli/shared.ts +72 -0
  293. package/src/cli/testing.ts +27 -0
  294. package/src/color/a98-rgb.ts +11 -0
  295. package/src/color/argb.ts +21 -0
  296. package/src/color/calc.ts +84 -0
  297. package/src/color/compare.ts +53 -0
  298. package/src/color/contrast.ts +277 -0
  299. package/src/color/convert.ts +43 -0
  300. package/src/color/display-p3-linear.ts +9 -0
  301. package/src/color/display-p3.ts +9 -0
  302. package/src/color/expr.ts +158 -0
  303. package/src/color/gamut.ts +215 -0
  304. package/src/color/hsl.ts +57 -0
  305. package/src/color/hwb.ts +26 -0
  306. package/src/color/index.ts +43 -0
  307. package/src/color/lab.ts +29 -0
  308. package/src/color/lch.ts +8 -0
  309. package/src/color/math.ts +30 -0
  310. package/src/color/matrices.ts +91 -0
  311. package/src/color/mix.ts +158 -0
  312. package/src/color/named.ts +13 -0
  313. package/src/color/oklab.ts +19 -0
  314. package/src/color/oklch.ts +8 -0
  315. package/src/color/parse.ts +355 -0
  316. package/src/color/polar.ts +17 -0
  317. package/src/color/prophoto-rgb.ts +18 -0
  318. package/src/color/rec2020.ts +11 -0
  319. package/src/color/resolve.ts +87 -0
  320. package/src/color/rgb.ts +20 -0
  321. package/src/color/serialize.ts +96 -0
  322. package/src/color/spaces.ts +42 -0
  323. package/src/color/srgb-linear.ts +9 -0
  324. package/src/color/srgb.ts +9 -0
  325. package/src/color/test-utils.ts +10 -0
  326. package/src/color/transfer.ts +14 -0
  327. package/src/color/types.ts +41 -0
  328. package/src/color/unknown-name.ts +15 -0
  329. package/src/color/xyz-d50.ts +13 -0
  330. package/src/color/xyz-d65.ts +8 -0
  331. package/src/index.ts +37 -0
  332. package/{ts → src/legacy}/createColors.ts +6 -0
  333. package/{ts → src/legacy}/createColorsFromMap.ts +3 -0
  334. package/{ts → src/legacy}/interfaces.ts +4 -0
  335. package/{ts → src/legacy}/rgbaString.ts +1 -0
  336. package/src/palette/dark.ts +55 -0
  337. package/src/palette/harmony.ts +107 -0
  338. package/src/palette/index.ts +18 -0
  339. package/src/palette/semantic.ts +120 -0
  340. package/src/palette/validate.ts +82 -0
  341. package/src/scale/brand.ts +50 -0
  342. package/src/scale/contrast-scale.ts +48 -0
  343. package/src/scale/gradient.ts +91 -0
  344. package/src/scale/index.ts +5 -0
  345. package/src/scale/scale.ts +127 -0
  346. package/src/scale/tone.ts +69 -0
  347. package/src/tokens/css.ts +32 -0
  348. package/src/tokens/dtcg.ts +62 -0
  349. package/src/tokens/index.ts +11 -0
  350. package/src/tokens/tailwind.ts +24 -0
  351. package/src/tokens/tokens.ts +82 -0
  352. package/esm/createColors.d.ts +0 -6
  353. package/esm/createColors.d.ts.map +0 -1
  354. package/esm/createColors.js.map +0 -1
  355. package/esm/createColorsFromMap.d.ts +0 -9
  356. package/esm/createColorsFromMap.d.ts.map +0 -1
  357. package/esm/createColorsFromMap.js.map +0 -1
  358. package/esm/interfaces.d.ts +0 -18
  359. package/esm/interfaces.d.ts.map +0 -1
  360. package/esm/rgbHex.d.ts.map +0 -1
  361. package/esm/rgbHex.js.map +0 -1
  362. package/esm/rgbaString.d.ts +0 -6
  363. package/esm/rgbaString.js +0 -8
  364. package/esm/rgbaString.js.map +0 -1
  365. /package/{ts → src/legacy}/index.ts +0 -0
  366. /package/{ts → src/legacy}/rgbHex.ts +0 -0
@@ -0,0 +1,152 @@
1
+ import { toGamut } from "../color/gamut.js";
2
+ import { toColor } from "../color/parse.js";
3
+ import { componentsIn } from "../color/spaces.js";
4
+ import { unknownName } from "../color/unknown-name.js";
5
+ import { MATERIAL_TONES, toneColor } from "./tone.js";
6
+ // src/scale/scale.ts
7
+ const TW_L = [
8
+ .97,
9
+ .932,
10
+ .882,
11
+ .809,
12
+ .707,
13
+ .623,
14
+ .546,
15
+ .488,
16
+ .424,
17
+ .379,
18
+ .282
19
+ ];
20
+ const TW_C = [
21
+ .014,
22
+ .032,
23
+ .059,
24
+ .105,
25
+ .165,
26
+ .214,
27
+ .245,
28
+ .243,
29
+ .199,
30
+ .146,
31
+ .091
32
+ ];
33
+ const TW_H = [
34
+ 254.604,
35
+ 255.585,
36
+ 254.128,
37
+ 251.813,
38
+ 254.624,
39
+ 259.815,
40
+ 262.881,
41
+ 264.376,
42
+ 265.638,
43
+ 265.522,
44
+ 267.935
45
+ ];
46
+ const RADIX_L = [
47
+ .992,
48
+ .978,
49
+ .953,
50
+ .925,
51
+ .894,
52
+ .857,
53
+ .81,
54
+ .745,
55
+ .62,
56
+ .585,
57
+ .52,
58
+ .32
59
+ ];
60
+ const RADIX_C = [
61
+ .04,
62
+ .08,
63
+ .2,
64
+ .32,
65
+ .43,
66
+ .52,
67
+ .6,
68
+ .75,
69
+ 1,
70
+ .95,
71
+ .9,
72
+ .35
73
+ ];
74
+ function even(n) {
75
+ const t = (i) => i / (n - 1);
76
+ const bell = (i) => Math.sin(Math.PI * (.05 + .9 * t(i)));
77
+ const anchor = Math.round((n - 1) / 2);
78
+ return {
79
+ lightness: Array.from({ length: n }, (_, i) => .97 - .72 * t(i)),
80
+ chroma: Array.from({ length: n }, (_, i) => bell(i) / bell(anchor)),
81
+ hueShift: Array.from({ length: n }, () => 0),
82
+ anchor
83
+ };
84
+ }
85
+ const PRESETS = {
86
+ __proto__: null,
87
+ "tailwind-11": {
88
+ lightness: TW_L,
89
+ chroma: TW_C.map((c) => c / TW_C[5]),
90
+ hueShift: TW_H.map((h) => h - TW_H[5]),
91
+ anchor: 5
92
+ },
93
+ "radix-12": {
94
+ lightness: RADIX_L,
95
+ chroma: RADIX_C,
96
+ hueShift: RADIX_L.map(() => 0),
97
+ anchor: 8
98
+ },
99
+ "tone-13": {
100
+ lightness: [...MATERIAL_TONES].reverse(),
101
+ chroma: MATERIAL_TONES.map(() => 1),
102
+ hueShift: MATERIAL_TONES.map(() => 0),
103
+ anchor: 6,
104
+ tone: true
105
+ }
106
+ };
107
+ function curveOf(steps) {
108
+ if (typeof steps === "number") {
109
+ if (!Number.isInteger(steps) || steps < 2) throw new Error(`steps must be an integer of at least 2, got ${steps}`);
110
+ return even(steps);
111
+ }
112
+ if (steps === "even") return even(11);
113
+ const curve = PRESETS[steps];
114
+ if (!curve) throw unknownName("scale preset", steps, [...Object.keys(PRESETS), "even"]);
115
+ return curve;
116
+ }
117
+ /**
118
+ * Builds a scale from light to dark around `seed`: one color per step, in
119
+ * OKLCH, mapped into `gamut`. The preset gives each step's lightness, chroma
120
+ * (relative to the seed's) and hue drift; pass `lightness`, `chroma` or
121
+ * `hueShift` to replace any of them.
122
+ */
123
+ function createScale(options) {
124
+ const { seed, steps = "tailwind-11", gamut = "srgb" } = options;
125
+ const curve = curveOf(steps);
126
+ const n = curve.lightness.length;
127
+ const pick = (name, over, base) => {
128
+ if (over === void 0) return base;
129
+ if (over.length !== n) throw new Error(`${name} needs ${n} values, got ${over.length}`);
130
+ return over;
131
+ };
132
+ const lightness = pick("lightness", options.lightness, curve.lightness);
133
+ const chroma = pick("chroma", options.chroma, curve.chroma);
134
+ const hueShift = pick("hueShift", typeof options.hueShift === "number" ? curve.lightness.map(() => options.hueShift) : options.hueShift, curve.hueShift);
135
+ const [, seedC, seedH] = componentsIn(toColor(seed), "oklch");
136
+ const hue = seedH ?? 0;
137
+ return lightness.map((l, i) => {
138
+ const c = seedC * chroma[i];
139
+ const h = hue + hueShift[i];
140
+ return curve.tone ? toneColor(l, c, h, gamut) : toGamut({
141
+ colorSpace: "oklch",
142
+ components: [
143
+ l,
144
+ c,
145
+ h
146
+ ]
147
+ }, { gamut });
148
+ });
149
+ }
150
+ export { createScale };
151
+
152
+ //# sourceMappingURL=scale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scale.js","names":[],"sources":["../../src/scale/scale.ts"],"sourcesContent":["import type { GamutId } from '../color/gamut.js'\nimport { toGamut } from '../color/gamut.js'\nimport { toColor } from '../color/parse.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { unknownName } from '../color/unknown-name.js'\nimport { MATERIAL_TONES, toneColor } from './tone.js'\n\n/** The built-in step curves. */\nexport type ScalePreset = 'tailwind-11' | 'radix-12' | 'tone-13' | 'even'\n\n/** A step curve. `chroma` and `hueShift` are relative to the step at `anchor`, which takes the seed's own. */\ninterface Curve {\n\t/** OKLCH lightness 0 to 1, or CIE L* tones 0 to 100 when `tone` is set. */\n\tlightness: number[]\n\tchroma: number[]\n\thueShift: number[]\n\tanchor: number\n\ttone?: boolean\n}\n\n// Tailwind v4 blue, 50 to 950. The preset is this one hand-tuned scale, so a\n// seed of blue-500 gives Tailwind's blue back, and other hues get its shape.\n// biome-ignore lint/suspicious/noApproximativeNumericConstant: 0.707 is Tailwind blue-400's lightness, not 1/sqrt(2)\nconst TW_L = [0.97, 0.932, 0.882, 0.809, 0.707, 0.623, 0.546, 0.488, 0.424, 0.379, 0.282]\nconst TW_C = [0.014, 0.032, 0.059, 0.105, 0.165, 0.214, 0.245, 0.243, 0.199, 0.146, 0.091]\nconst TW_H = [254.604, 255.585, 254.128, 251.813, 254.624, 259.815, 262.881, 264.376, 265.638, 265.522, 267.935]\n\n// Radix-like light scale: step 9 is the pure solid, 11 and 12 are text.\nconst RADIX_L = [0.992, 0.978, 0.953, 0.925, 0.894, 0.857, 0.81, 0.745, 0.62, 0.585, 0.52, 0.32]\nconst RADIX_C = [0.04, 0.08, 0.2, 0.32, 0.43, 0.52, 0.6, 0.75, 1, 0.95, 0.9, 0.35]\n\nfunction even(n: number): Curve {\n\tconst t = (i: number) => i / (n - 1)\n\tconst bell = (i: number) => Math.sin(Math.PI * (0.05 + 0.9 * t(i)))\n\tconst anchor = Math.round((n - 1) / 2)\n\treturn {\n\t\tlightness: Array.from({ length: n }, (_, i) => 0.97 - 0.72 * t(i)),\n\t\tchroma: Array.from({ length: n }, (_, i) => bell(i) / bell(anchor)),\n\t\thueShift: Array.from({ length: n }, () => 0),\n\t\tanchor\n\t}\n}\n\nconst PRESETS: Record<string, Curve> = {\n\t__proto__: null as never,\n\t'tailwind-11': {\n\t\tlightness: TW_L,\n\t\tchroma: TW_C.map((c) => c / TW_C[5]),\n\t\thueShift: TW_H.map((h) => h - TW_H[5]),\n\t\tanchor: 5\n\t},\n\t'radix-12': {\n\t\tlightness: RADIX_L,\n\t\tchroma: RADIX_C,\n\t\thueShift: RADIX_L.map(() => 0),\n\t\tanchor: 8\n\t},\n\t'tone-13': {\n\t\tlightness: [...MATERIAL_TONES].reverse(),\n\t\tchroma: MATERIAL_TONES.map(() => 1),\n\t\thueShift: MATERIAL_TONES.map(() => 0),\n\t\tanchor: 6,\n\t\ttone: true\n\t}\n}\n\n/** Options for `createScale`. Only `seed` is required. */\nexport interface ScaleOptions {\n\t/** The color the scale is built around. Its hue and chroma set the anchor step. */\n\tseed: Color | string\n\t/**\n\t * A preset, or a number of steps for an even scale. Default `tailwind-11`.\n\t * `tone-13` steps are CIE L* tones, so any two steps whose tones differ by\n\t * 40 reach contrast 3 and by 50 about 4.5.\n\t */\n\tsteps?: ScalePreset | number\n\t/** Overrides the curve's lightness, one per step: OKLCH L 0 to 1 (L* tones 0 to 100 for `tone-13`). */\n\tlightness?: number[]\n\t/** Overrides chroma, as a multiplier of the seed's chroma, one per step. */\n\tchroma?: number[]\n\t/** Overrides hue drift, as degrees added to the seed's hue, one per step; a number is applied to every step. */\n\thueShift?: number[] | number\n\t/** The gamut every step must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\nfunction curveOf(steps: ScalePreset | number): Curve {\n\tif (typeof steps === 'number') {\n\t\tif (!Number.isInteger(steps) || steps < 2) throw new Error(`steps must be an integer of at least 2, got ${steps}`)\n\t\treturn even(steps)\n\t}\n\tif (steps === 'even') return even(11)\n\tconst curve = PRESETS[steps]\n\tif (!curve) throw unknownName('scale preset', steps, [...Object.keys(PRESETS), 'even'])\n\treturn curve\n}\n\n/**\n * Builds a scale from light to dark around `seed`: one color per step, in\n * OKLCH, mapped into `gamut`. The preset gives each step's lightness, chroma\n * (relative to the seed's) and hue drift; pass `lightness`, `chroma` or\n * `hueShift` to replace any of them.\n */\nexport function createScale(options: ScaleOptions): Color[] {\n\tconst { seed, steps = 'tailwind-11', gamut = 'srgb' } = options\n\tconst curve = curveOf(steps)\n\tconst n = curve.lightness.length\n\tconst pick = <T>(name: string, over: T[] | undefined, base: T[]): T[] => {\n\t\tif (over === undefined) return base\n\t\tif (over.length !== n) throw new Error(`${name} needs ${n} values, got ${over.length}`)\n\t\treturn over\n\t}\n\tconst lightness = pick('lightness', options.lightness, curve.lightness)\n\tconst chroma = pick('chroma', options.chroma, curve.chroma)\n\tconst drift =\n\t\ttypeof options.hueShift === 'number' ? curve.lightness.map(() => options.hueShift as number) : options.hueShift\n\tconst hueShift = pick('hueShift', drift, curve.hueShift)\n\n\tconst [, seedC, seedH] = componentsIn(toColor(seed), 'oklch') as [number, number, number | null]\n\tconst hue = seedH ?? 0\n\treturn lightness.map((l, i) => {\n\t\tconst c = seedC * (chroma[i] as number)\n\t\tconst h = hue + (hueShift[i] as number)\n\t\treturn curve.tone ? toneColor(l, c, h, gamut) : toGamut({ colorSpace: 'oklch', components: [l, c, h] }, { gamut })\n\t})\n}\n"],"mappings":";;;;;;AAwBA,MAAM,OAAO;CAAC;CAAM;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;AAAK;AACxF,MAAM,OAAO;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;AAAK;AACzF,MAAM,OAAO;CAAC;CAAS;CAAS;CAAS;CAAS;CAAS;CAAS;CAAS;CAAS;CAAS;CAAS;AAAO;AAG/G,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAM;CAAO;CAAM;CAAO;CAAM;AAAI;AAC/F,MAAM,UAAU;CAAC;CAAM;CAAM;CAAK;CAAM;CAAM;CAAM;CAAK;CAAM;CAAG;CAAM;CAAK;AAAI;AAEjF,SAAS,KAAK,GAAkB;CAC/B,MAAM,KAAK,MAAc,KAAK,IAAI;CAClC,MAAM,QAAQ,MAAc,KAAK,IAAI,KAAK,MAAM,MAAO,KAAM,EAAE,CAAC,EAAE;CAClE,MAAM,SAAS,KAAK,OAAO,IAAI,KAAK,CAAC;CACrC,OAAO;EACN,WAAW,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,MAAO,MAAO,EAAE,CAAC,CAAC;EACjE,QAAQ,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;EAClE,UAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;EAC3C;CACD;AACD;AAEA,MAAM,UAAiC;CACtC,WAAW;CACX,eAAe;EACd,WAAW;EACX,QAAQ,KAAK,KAAK,MAAM,IAAI,KAAK,EAAE;EACnC,UAAU,KAAK,KAAK,MAAM,IAAI,KAAK,EAAE;EACrC,QAAQ;CACT;CACA,YAAY;EACX,WAAW;EACX,QAAQ;EACR,UAAU,QAAQ,UAAU,CAAC;EAC7B,QAAQ;CACT;CACA,WAAW;EACV,WAAW,CAAC,GAAG,cAAc,CAAC,CAAC,QAAQ;EACvC,QAAQ,eAAe,UAAU,CAAC;EAClC,UAAU,eAAe,UAAU,CAAC;EACpC,QAAQ;EACR,MAAM;CACP;AACD;AAsBA,SAAS,QAAQ,OAAoC;CACpD,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,MAAM,+CAA+C,OAAO;EACjH,OAAO,KAAK,KAAK;CAClB;CACA,IAAI,UAAU,QAAQ,OAAO,KAAK,EAAE;CACpC,MAAM,QAAQ,QAAQ;CACtB,IAAI,CAAC,OAAO,MAAM,YAAY,gBAAgB,OAAO,CAAC,GAAG,OAAO,KAAK,OAAO,GAAG,MAAM,CAAC;CACtF,OAAO;AACR;;;;;;;AAQA,SAAgB,YAAY,SAAgC;CAC3D,MAAM,EAAE,MAAM,QAAQ,eAAe,QAAQ,WAAW;CACxD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,MAAM,IAAI,MAAM,UAAU;CAC1B,MAAM,QAAW,MAAc,MAAuB,SAAmB;EACxE,IAAI,SAAS,KAAA,GAAW,OAAO;EAC/B,IAAI,KAAK,WAAW,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,SAAS,EAAE,eAAe,KAAK,QAAQ;EACtF,OAAO;CACR;CACA,MAAM,YAAY,KAAK,aAAa,QAAQ,WAAW,MAAM,SAAS;CACtE,MAAM,SAAS,KAAK,UAAU,QAAQ,QAAQ,MAAM,MAAM;CAG1D,MAAM,WAAW,KAAK,YADrB,OAAO,QAAQ,aAAa,WAAW,MAAM,UAAU,UAAU,QAAQ,QAAkB,IAAI,QAAQ,UAC/D,MAAM,QAAQ;CAEvD,MAAM,GAAG,OAAO,SAAS,aAAa,QAAQ,IAAI,GAAG,OAAO;CAC5D,MAAM,MAAM,SAAS;CACrB,OAAO,UAAU,KAAK,GAAG,MAAM;EAC9B,MAAM,IAAI,QAAS,OAAO;EAC1B,MAAM,IAAI,MAAO,SAAS;EAC1B,OAAO,MAAM,OAAO,UAAU,GAAG,GAAG,GAAG,KAAK,IAAI,QAAQ;GAAE,YAAY;GAAS,YAAY;IAAC;IAAG;IAAG;GAAC;EAAE,GAAG,EAAE,MAAM,CAAC;CAClH,CAAC;AACF"}
@@ -0,0 +1,25 @@
1
+ import { GamutId } from "../color/gamut.js";
2
+ import { Color } from "../color/types.js";
3
+ /** CIE L* (0 to 100) of a color: its relative luminance on the CIELAB lightness curve. */
4
+ export declare function lstar(color: Color): number;
5
+ /** Material's tones: 0 is black, 100 is white. */
6
+ export declare const MATERIAL_TONES: readonly number[];
7
+ /** Options for `tonalPalette`. */
8
+ export interface TonalPaletteOptions {
9
+ /** The gamut the colors must fit. Default `srgb`. */
10
+ gamut?: GamutId;
11
+ }
12
+ /**
13
+ * The color at CIE L* `tone` with the given OKLCH hue and chroma, mapped into
14
+ * the gamut. OKLCH lightness is searched until the *mapped* color lands on
15
+ * `tone`, so chroma tapers where the gamut narrows and the tone stays true.
16
+ */
17
+ export declare function toneColor(tone: number, chroma: number, hue: number, gamut?: GamutId): Color;
18
+ /**
19
+ * A tonal palette, as in Material: one OKLCH hue and chroma held across L*
20
+ * tones. Two colors whose tones differ by 40 or more reach WCAG 2 contrast 3,
21
+ * and by 50 or more about 4.5 (4.48 at the worst pair, 100 against 50), so a
22
+ * pair can be picked by tone alone. Default tones are Material's 13.
23
+ */
24
+ export declare function tonalPalette(hue: number, chroma: number, tones?: readonly number[], options?: TonalPaletteOptions): Color[];
25
+ //# sourceMappingURL=tone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tone.d.ts","names":[],"sources":["../../src/scale/tone.ts"],"mappings":";;;wBAKgB,MAAM,OAAO;;qBAMhB;;iBAGI;;EAEhB,QAAQ;;;;;;;wBAQO,UAAU,cAAc,gBAAgB,aAAa,QAAO,UAAmB;;;;;;;wBAqC/E,aACf,aACA,gBACA,2BACA,UAAS,sBACP"}
@@ -0,0 +1,96 @@
1
+ import { inGamut } from "../color/gamut.js";
2
+ import { componentsIn } from "../color/spaces.js";
3
+ // src/scale/tone.ts
4
+ /** CIE L* (0 to 100) of a color: its relative luminance on the CIELAB lightness curve. */
5
+ function lstar(color) {
6
+ const y = Math.max(0, componentsIn(color, "xyz-d65")[1]);
7
+ return y > 216 / 24389 ? 116 * Math.cbrt(y) - 16 : 24389 / 27 * y;
8
+ }
9
+ /** Material's tones: 0 is black, 100 is white. */
10
+ const MATERIAL_TONES = [
11
+ 0,
12
+ 10,
13
+ 20,
14
+ 30,
15
+ 40,
16
+ 50,
17
+ 60,
18
+ 70,
19
+ 80,
20
+ 90,
21
+ 95,
22
+ 99,
23
+ 100
24
+ ];
25
+ /**
26
+ * The color at CIE L* `tone` with the given OKLCH hue and chroma, mapped into
27
+ * the gamut. OKLCH lightness is searched until the *mapped* color lands on
28
+ * `tone`, so chroma tapers where the gamut narrows and the tone stays true.
29
+ */
30
+ function toneColor(tone, chroma, hue, gamut = "srgb") {
31
+ if (tone <= 0) return {
32
+ colorSpace: "oklch",
33
+ components: [
34
+ 0,
35
+ 0,
36
+ hue
37
+ ]
38
+ };
39
+ if (tone >= 100) return {
40
+ colorSpace: "oklch",
41
+ components: [
42
+ 1,
43
+ 0,
44
+ hue
45
+ ]
46
+ };
47
+ const r = hue * Math.PI / 180;
48
+ const cos = Math.cos(r);
49
+ const sin = Math.sin(r);
50
+ const lab = (l, c) => ({
51
+ colorSpace: "oklab",
52
+ components: [
53
+ l,
54
+ c * cos,
55
+ c * sin
56
+ ]
57
+ });
58
+ const chromaAt = (l) => {
59
+ if (inGamut(lab(l, chroma), gamut)) return chroma;
60
+ let [lo, hi] = [0, chroma];
61
+ for (let i = 0; i < 24; i++) {
62
+ const mid = (lo + hi) / 2;
63
+ if (inGamut(lab(l, mid), gamut)) lo = mid;
64
+ else hi = mid;
65
+ }
66
+ return lo;
67
+ };
68
+ let lo = 0;
69
+ let hi = 1;
70
+ for (let i = 0; i < 40; i++) {
71
+ const mid = (lo + hi) / 2;
72
+ if (lstar(lab(mid, chromaAt(mid))) < tone) lo = mid;
73
+ else hi = mid;
74
+ }
75
+ const l = (lo + hi) / 2;
76
+ return {
77
+ colorSpace: "oklch",
78
+ components: [
79
+ l,
80
+ chromaAt(l),
81
+ hue
82
+ ]
83
+ };
84
+ }
85
+ /**
86
+ * A tonal palette, as in Material: one OKLCH hue and chroma held across L*
87
+ * tones. Two colors whose tones differ by 40 or more reach WCAG 2 contrast 3,
88
+ * and by 50 or more about 4.5 (4.48 at the worst pair, 100 against 50), so a
89
+ * pair can be picked by tone alone. Default tones are Material's 13.
90
+ */
91
+ function tonalPalette(hue, chroma, tones = MATERIAL_TONES, options = {}) {
92
+ return tones.map((t) => toneColor(t, chroma, hue, options.gamut));
93
+ }
94
+ export { MATERIAL_TONES, lstar, tonalPalette, toneColor };
95
+
96
+ //# sourceMappingURL=tone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tone.js","names":[],"sources":["../../src/scale/tone.ts"],"sourcesContent":["import { type GamutId, inGamut } from '../color/gamut.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\n\n/** CIE L* (0 to 100) of a color: its relative luminance on the CIELAB lightness curve. */\nexport function lstar(color: Color): number {\n\tconst y = Math.max(0, componentsIn(color, 'xyz-d65')[1] as number)\n\treturn y > 216 / 24389 ? 116 * Math.cbrt(y) - 16 : (24389 / 27) * y\n}\n\n/** Material's tones: 0 is black, 100 is white. */\nexport const MATERIAL_TONES: readonly number[] = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100]\n\n/** Options for `tonalPalette`. */\nexport interface TonalPaletteOptions {\n\t/** The gamut the colors must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/**\n * The color at CIE L* `tone` with the given OKLCH hue and chroma, mapped into\n * the gamut. OKLCH lightness is searched until the *mapped* color lands on\n * `tone`, so chroma tapers where the gamut narrows and the tone stays true.\n */\nexport function toneColor(tone: number, chroma: number, hue: number, gamut: GamutId = 'srgb'): Color {\n\tif (tone <= 0) return { colorSpace: 'oklch', components: [0, 0, hue] }\n\tif (tone >= 100) return { colorSpace: 'oklch', components: [1, 0, hue] }\n\t// The hue is fixed, so its cosine and sine are taken once: OKLCH converts by\n\t// way of OKLab with exactly these products, so the results are the same.\n\tconst r = (hue * Math.PI) / 180\n\tconst cos = Math.cos(r)\n\tconst sin = Math.sin(r)\n\tconst lab = (l: number, c: number): Color => ({ colorSpace: 'oklab', components: [l, c * cos, c * sin] })\n\t// The most chroma, up to `chroma`, that fits at this lightness; no clipping, so L* is monotone in L.\n\tconst chromaAt = (l: number): number => {\n\t\tif (inGamut(lab(l, chroma), gamut)) return chroma\n\t\tlet [lo, hi] = [0, chroma]\n\t\tfor (let i = 0; i < 24; i++) {\n\t\t\tconst mid = (lo + hi) / 2\n\t\t\tif (inGamut(lab(l, mid), gamut)) lo = mid\n\t\t\telse hi = mid\n\t\t}\n\t\treturn lo\n\t}\n\tlet lo = 0\n\tlet hi = 1\n\tfor (let i = 0; i < 40; i++) {\n\t\tconst mid = (lo + hi) / 2\n\t\tif (lstar(lab(mid, chromaAt(mid))) < tone) lo = mid\n\t\telse hi = mid\n\t}\n\tconst l = (lo + hi) / 2\n\treturn { colorSpace: 'oklch', components: [l, chromaAt(l), hue] }\n}\n\n/**\n * A tonal palette, as in Material: one OKLCH hue and chroma held across L*\n * tones. Two colors whose tones differ by 40 or more reach WCAG 2 contrast 3,\n * and by 50 or more about 4.5 (4.48 at the worst pair, 100 against 50), so a\n * pair can be picked by tone alone. Default tones are Material's 13.\n */\nexport function tonalPalette(\n\thue: number,\n\tchroma: number,\n\ttones: readonly number[] = MATERIAL_TONES,\n\toptions: TonalPaletteOptions = {}\n): Color[] {\n\treturn tones.map((t) => toneColor(t, chroma, hue, options.gamut))\n}\n"],"mappings":";;;;AAKA,SAAgB,MAAM,OAAsB;CAC3C,MAAM,IAAI,KAAK,IAAI,GAAG,aAAa,OAAO,SAAS,CAAC,CAAC,EAAY;CACjE,OAAO,IAAI,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC,IAAI,KAAM,QAAQ,KAAM;AACnE;;AAGA,MAAa,iBAAoC;CAAC;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;AAAG;;;;;;AAapG,SAAgB,UAAU,MAAc,QAAgB,KAAa,QAAiB,QAAe;CACpG,IAAI,QAAQ,GAAG,OAAO;EAAE,YAAY;EAAS,YAAY;GAAC;GAAG;GAAG;EAAG;CAAE;CACrE,IAAI,QAAQ,KAAK,OAAO;EAAE,YAAY;EAAS,YAAY;GAAC;GAAG;GAAG;EAAG;CAAE;CAGvE,MAAM,IAAK,MAAM,KAAK,KAAM;CAC5B,MAAM,MAAM,KAAK,IAAI,CAAC;CACtB,MAAM,MAAM,KAAK,IAAI,CAAC;CACtB,MAAM,OAAO,GAAW,OAAsB;EAAE,YAAY;EAAS,YAAY;GAAC;GAAG,IAAI;GAAK,IAAI;EAAG;CAAE;CAEvG,MAAM,YAAY,MAAsB;EACvC,IAAI,QAAQ,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO;EAC3C,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;EACzB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;GAC5B,MAAM,OAAO,KAAK,MAAM;GACxB,IAAI,QAAQ,IAAI,GAAG,GAAG,GAAG,KAAK,GAAG,KAAK;QACjC,KAAK;EACX;EACA,OAAO;CACR;CACA,IAAI,KAAK;CACT,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC5B,MAAM,OAAO,KAAK,MAAM;EACxB,IAAI,MAAM,IAAI,KAAK,SAAS,GAAG,CAAC,CAAC,IAAI,MAAM,KAAK;OAC3C,KAAK;CACX;CACA,MAAM,KAAK,KAAK,MAAM;CACtB,OAAO;EAAE,YAAY;EAAS,YAAY;GAAC;GAAG,SAAS,CAAC;GAAG;EAAG;CAAE;AACjE;;;;;;;AAQA,SAAgB,aACf,KACA,QACA,QAA2B,gBAC3B,UAA+B,CAAC,GACtB;CACV,OAAO,MAAM,KAAK,MAAM,UAAU,GAAG,QAAQ,KAAK,QAAQ,KAAK,CAAC;AACjE"}
@@ -0,0 +1,18 @@
1
+ import { ToCssOptions } from "../color/serialize.js";
2
+ import { ColorSpaceId } from "../color/types.js";
3
+ import { TokenTree } from "./tokens.js";
4
+ /** Options for `toCssVariables`. It also takes the options of `toCss`. */
5
+ export interface CssVariablesOptions extends ToCssOptions {
6
+ /** The first part of every name. Default `color`, so `primary.500` becomes `--color-primary-500`. */
7
+ prefix?: string;
8
+ /** The rule's selector. Default `:root`. */
9
+ selector?: string;
10
+ /** Convert every color to this space first. Default: print each in its own space. */
11
+ space?: ColorSpaceId;
12
+ }
13
+ /**
14
+ * Exports colors as CSS custom properties in one rule:
15
+ * `:root {\n\t--color-primary-500: oklch(0.62 0.19 260);\n}`.
16
+ */
17
+ export declare function toCssVariables(tree: TokenTree, options?: CssVariablesOptions): string;
18
+ //# sourceMappingURL=css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.d.ts","names":[],"sources":["../../src/tokens/css.ts"],"mappings":";;;;iBAMiB,4BAA4B;;EAE5C;;EAEA;;EAEA,QAAQ;;;;;;wBAeO,eAAe,MAAM,WAAW,UAAS"}
@@ -0,0 +1,23 @@
1
+ import { toCss } from "../color/serialize.js";
2
+ import { convert } from "../color/spaces.js";
3
+ import { cssNames, flatten } from "./tokens.js";
4
+ // src/tokens/css.ts
5
+ /** Prints one `--name: value;` line per color. */
6
+ function declarations(tree, options) {
7
+ const { prefix, space, ...css } = options;
8
+ return [...cssNames(flatten(tree), prefix)].map(([name, color]) => `${name}: ${toCss(space ? convert(color, space) : color, css)};`);
9
+ }
10
+ /**
11
+ * Exports colors as CSS custom properties in one rule:
12
+ * `:root {\n\t--color-primary-500: oklch(0.62 0.19 260);\n}`.
13
+ */
14
+ function toCssVariables(tree, options = {}) {
15
+ const { selector = ":root", prefix = "color", ...rest } = options;
16
+ return `${selector} {\n${declarations(tree, {
17
+ ...rest,
18
+ prefix
19
+ }).map((l) => `\t${l}\n`).join("")}}\n`;
20
+ }
21
+ export { declarations, toCssVariables };
22
+
23
+ //# sourceMappingURL=css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.js","names":[],"sources":["../../src/tokens/css.ts"],"sourcesContent":["import { type ToCssOptions, toCss } from '../color/serialize.js'\nimport { convert } from '../color/spaces.js'\nimport type { ColorSpaceId } from '../color/types.js'\nimport { cssNames, flatten, type TokenTree } from './tokens.js'\n\n/** Options for `toCssVariables`. It also takes the options of `toCss`. */\nexport interface CssVariablesOptions extends ToCssOptions {\n\t/** The first part of every name. Default `color`, so `primary.500` becomes `--color-primary-500`. */\n\tprefix?: string\n\t/** The rule's selector. Default `:root`. */\n\tselector?: string\n\t/** Convert every color to this space first. Default: print each in its own space. */\n\tspace?: ColorSpaceId\n}\n\n/** Prints one `--name: value;` line per color. */\nexport function declarations(tree: TokenTree, options: CssVariablesOptions & { prefix: string }): string[] {\n\tconst { prefix, space, ...css } = options\n\treturn [...cssNames(flatten(tree), prefix)].map(\n\t\t([name, color]) => `${name}: ${toCss(space ? convert(color, space) : color, css)};`\n\t)\n}\n\n/**\n * Exports colors as CSS custom properties in one rule:\n * `:root {\\n\\t--color-primary-500: oklch(0.62 0.19 260);\\n}`.\n */\nexport function toCssVariables(tree: TokenTree, options: CssVariablesOptions = {}): string {\n\tconst { selector = ':root', prefix = 'color', ...rest } = options\n\tconst lines = declarations(tree, { ...rest, prefix })\n\treturn `${selector} {\\n${lines.map((l) => `\\t${l}\\n`).join('')}}\\n`\n}\n"],"mappings":";;;;;AAgBA,SAAgB,aAAa,MAAiB,SAA6D;CAC1G,MAAM,EAAE,QAAQ,OAAO,GAAG,QAAQ;CAClC,OAAO,CAAC,GAAG,SAAS,QAAQ,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,KAC1C,CAAC,MAAM,WAAW,GAAG,KAAK,IAAI,MAAM,QAAQ,QAAQ,OAAO,KAAK,IAAI,OAAO,GAAG,EAAE,EAClF;AACD;;;;;AAMA,SAAgB,eAAe,MAAiB,UAA+B,CAAC,GAAW;CAC1F,MAAM,EAAE,WAAW,SAAS,SAAS,SAAS,GAAG,SAAS;CAE1D,OAAO,GAAG,SAAS,MADL,aAAa,MAAM;EAAE,GAAG;EAAM;CAAO,CACtB,CAAC,CAAC,KAAK,MAAM,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;AAChE"}
@@ -0,0 +1,33 @@
1
+ import { Color, ColorSpaceId } from "../color/types.js";
2
+ import { TokenTree } from "./tokens.js";
3
+ /** Options for `toDtcg` and `toDtcgValue`. */
4
+ export interface DtcgOptions {
5
+ /** Add the `hex` fallback (an sRGB `#rrggbb`, gamut-mapped when the color is outside sRGB). Default `true`. */
6
+ hex?: boolean;
7
+ }
8
+ /** A color value as DTCG 2025.10 defines it. */
9
+ export interface DtcgColorValue {
10
+ colorSpace: Exclude<ColorSpaceId, 'display-p3-linear'>;
11
+ components: (number | 'none')[];
12
+ alpha?: number | 'none';
13
+ hex?: string;
14
+ }
15
+ /** A DTCG color token: a `$type` of `color` and its {@link DtcgColorValue}. */
16
+ export interface DtcgToken {
17
+ $type: 'color';
18
+ $value: DtcgColorValue;
19
+ }
20
+ /** A DTCG group: named tokens and nested groups, as `toDtcg` returns for a token tree. */
21
+ export interface DtcgGroup {
22
+ [name: string]: DtcgToken | DtcgGroup;
23
+ }
24
+ /** One color as a DTCG color value. `display-p3-linear` is not a DTCG space, so it becomes `display-p3`. */
25
+ export declare function toDtcgValue(color: Color, options?: DtcgOptions): DtcgColorValue;
26
+ /**
27
+ * Exports colors as a DTCG 2025.10 token file: nested groups whose leaves are
28
+ * `{ "$type": "color", "$value": { colorSpace, components, alpha?, hex? } }`.
29
+ * `none` components print as `"none"`, as the format asks. The result is plain
30
+ * JSON, so `JSON.stringify` writes the file.
31
+ */
32
+ export declare function toDtcg(tree: TokenTree, options?: DtcgOptions): DtcgGroup;
33
+ //# sourceMappingURL=dtcg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtcg.d.ts","names":[],"sources":["../../src/tokens/dtcg.ts"],"mappings":";;;iBAOiB;;EAEhB;;;iBAIgB;EAChB,YAAY,QAAQ;EACpB;EACA;EACA;;;iBAIgB;EAChB;EACA,QAAQ;;;iBAIQ;GACf,eAAe,YAAY;;;wBAMb,YAAY,OAAO,OAAO,UAAS,cAAmB;;;;;;;wBAiBtD,OAAO,MAAM,WAAW,UAAS,cAAmB"}
@@ -0,0 +1,42 @@
1
+ import { toGamut } from "../color/gamut.js";
2
+ import { toHex } from "../color/serialize.js";
3
+ import { convert } from "../color/spaces.js";
4
+ import { flatten } from "./tokens.js";
5
+ // src/tokens/dtcg.ts
6
+ const none = (c) => c === null ? "none" : c;
7
+ /** One color as a DTCG color value. `display-p3-linear` is not a DTCG space, so it becomes `display-p3`. */
8
+ function toDtcgValue(color, options = {}) {
9
+ const c = color.colorSpace === "display-p3-linear" ? convert(color, "display-p3") : color;
10
+ const value = {
11
+ colorSpace: c.colorSpace,
12
+ components: c.components.map(none)
13
+ };
14
+ if (c.alpha !== void 0 && c.alpha !== 1) value.alpha = none(c.alpha);
15
+ if (options.hex !== false) value.hex = toHex(toGamut({
16
+ ...c,
17
+ alpha: 1
18
+ }, { gamut: "srgb" }));
19
+ return value;
20
+ }
21
+ /**
22
+ * Exports colors as a DTCG 2025.10 token file: nested groups whose leaves are
23
+ * `{ "$type": "color", "$value": { colorSpace, components, alpha?, hex? } }`.
24
+ * `none` components print as `"none"`, as the format asks. The result is plain
25
+ * JSON, so `JSON.stringify` writes the file.
26
+ */
27
+ function toDtcg(tree, options = {}) {
28
+ const root = Object.create(null);
29
+ for (const { path, color } of flatten(tree)) {
30
+ if (!path.length) throw new TypeError("A DTCG file needs a name for each token: pass an object, not a single color");
31
+ let group = root;
32
+ for (const name of path.slice(0, -1)) group = group[name] ??= Object.create(null);
33
+ group[path[path.length - 1]] = {
34
+ $type: "color",
35
+ $value: toDtcgValue(color, options)
36
+ };
37
+ }
38
+ return root;
39
+ }
40
+ export { toDtcg, toDtcgValue };
41
+
42
+ //# sourceMappingURL=dtcg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtcg.js","names":[],"sources":["../../src/tokens/dtcg.ts"],"sourcesContent":["import { toGamut } from '../color/gamut.js'\nimport { toHex } from '../color/serialize.js'\nimport { convert } from '../color/spaces.js'\nimport type { Color, ColorSpaceId, Component } from '../color/types.js'\nimport { flatten, type TokenTree } from './tokens.js'\n\n/** Options for `toDtcg` and `toDtcgValue`. */\nexport interface DtcgOptions {\n\t/** Add the `hex` fallback (an sRGB `#rrggbb`, gamut-mapped when the color is outside sRGB). Default `true`. */\n\thex?: boolean\n}\n\n/** A color value as DTCG 2025.10 defines it. */\nexport interface DtcgColorValue {\n\tcolorSpace: Exclude<ColorSpaceId, 'display-p3-linear'>\n\tcomponents: (number | 'none')[]\n\talpha?: number | 'none'\n\thex?: string\n}\n\n/** A DTCG color token: a `$type` of `color` and its {@link DtcgColorValue}. */\nexport interface DtcgToken {\n\t$type: 'color'\n\t$value: DtcgColorValue\n}\n\n/** A DTCG group: named tokens and nested groups, as `toDtcg` returns for a token tree. */\nexport interface DtcgGroup {\n\t[name: string]: DtcgToken | DtcgGroup\n}\n\nconst none = (c: Component): number | 'none' => (c === null ? 'none' : c)\n\n/** One color as a DTCG color value. `display-p3-linear` is not a DTCG space, so it becomes `display-p3`. */\nexport function toDtcgValue(color: Color, options: DtcgOptions = {}): DtcgColorValue {\n\tconst c = color.colorSpace === 'display-p3-linear' ? convert(color, 'display-p3') : color\n\tconst value: DtcgColorValue = {\n\t\tcolorSpace: c.colorSpace as DtcgColorValue['colorSpace'],\n\t\tcomponents: c.components.map(none)\n\t}\n\tif (c.alpha !== undefined && c.alpha !== 1) value.alpha = none(c.alpha)\n\tif (options.hex !== false) value.hex = toHex(toGamut({ ...c, alpha: 1 }, { gamut: 'srgb' }))\n\treturn value\n}\n\n/**\n * Exports colors as a DTCG 2025.10 token file: nested groups whose leaves are\n * `{ \"$type\": \"color\", \"$value\": { colorSpace, components, alpha?, hex? } }`.\n * `none` components print as `\"none\"`, as the format asks. The result is plain\n * JSON, so `JSON.stringify` writes the file.\n */\nexport function toDtcg(tree: TokenTree, options: DtcgOptions = {}): DtcgGroup {\n\t// Groups have no prototype, so a token named `__proto__` or `constructor` is an ordinary key.\n\tconst root: DtcgGroup = Object.create(null)\n\tfor (const { path, color } of flatten(tree)) {\n\t\tif (!path.length) throw new TypeError('A DTCG file needs a name for each token: pass an object, not a single color')\n\t\tlet group = root\n\t\tfor (const name of path.slice(0, -1)) group = (group[name] ??= Object.create(null)) as DtcgGroup\n\t\tgroup[path[path.length - 1] as string] = { $type: 'color', $value: toDtcgValue(color, options) }\n\t}\n\treturn root\n}\n"],"mappings":";;;;;AA+BA,MAAM,QAAQ,MAAmC,MAAM,OAAO,SAAS;;AAGvE,SAAgB,YAAY,OAAc,UAAuB,CAAC,GAAmB;CACpF,MAAM,IAAI,MAAM,eAAe,sBAAsB,QAAQ,OAAO,YAAY,IAAI;CACpF,MAAM,QAAwB;EAC7B,YAAY,EAAE;EACd,YAAY,EAAE,WAAW,IAAI,IAAI;CAClC;CACA,IAAI,EAAE,UAAU,KAAA,KAAa,EAAE,UAAU,GAAG,MAAM,QAAQ,KAAK,EAAE,KAAK;CACtE,IAAI,QAAQ,QAAQ,OAAO,MAAM,MAAM,MAAM,QAAQ;EAAE,GAAG;EAAG,OAAO;CAAE,GAAG,EAAE,OAAO,OAAO,CAAC,CAAC;CAC3F,OAAO;AACR;;;;;;;AAQA,SAAgB,OAAO,MAAiB,UAAuB,CAAC,GAAc;CAE7E,MAAM,OAAkB,OAAO,OAAO,IAAI;CAC1C,KAAK,MAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,GAAG;EAC5C,IAAI,CAAC,KAAK,QAAQ,MAAM,IAAI,UAAU,6EAA6E;EACnH,IAAI,QAAQ;EACZ,KAAK,MAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,GAAG,QAAS,MAAM,UAAU,OAAO,OAAO,IAAI;EACjF,MAAM,KAAK,KAAK,SAAS,MAAgB;GAAE,OAAO;GAAS,QAAQ,YAAY,OAAO,OAAO;EAAE;CAChG;CACA,OAAO;AACR"}
@@ -0,0 +1,5 @@
1
+ import { CssVariablesOptions, toCssVariables } from "./css.js";
2
+ import { DtcgColorValue, DtcgGroup, DtcgOptions, DtcgToken, toDtcg, toDtcgValue } from "./dtcg.js";
3
+ import { TailwindThemeOptions, toTailwindTheme } from "./tailwind.js";
4
+ import { TokenTree } from "./tokens.js";
5
+ export { type CssVariablesOptions, type DtcgColorValue, type DtcgGroup, type DtcgOptions, type DtcgToken, type TailwindThemeOptions, type TokenTree, toCssVariables, toDtcg, toDtcgValue, toTailwindTheme };
@@ -0,0 +1,4 @@
1
+ import { toCssVariables } from "./css.js";
2
+ import { toDtcg, toDtcgValue } from "./dtcg.js";
3
+ import { toTailwindTheme } from "./tailwind.js";
4
+ export { toCssVariables, toDtcg, toDtcgValue, toTailwindTheme };
@@ -0,0 +1,19 @@
1
+ import { ColorSpaceId } from "../color/types.js";
2
+ import { CssVariablesOptions } from "./css.js";
3
+ import { TokenTree } from "./tokens.js";
4
+ /** Options for `toTailwindTheme`. It also takes the options of `toCss`. */
5
+ export interface TailwindThemeOptions extends Omit<CssVariablesOptions, 'selector' | 'prefix'> {
6
+ /** The theme variable namespace. Default `color`, which makes `bg-primary-500` and `text-primary-500` work. */
7
+ namespace?: string;
8
+ /** Write `@theme inline`, so utilities hold the value instead of a `var()` reference. */
9
+ inline?: boolean;
10
+ /** Default `oklch`, the space Tailwind v4's own palette uses. */
11
+ space?: ColorSpaceId;
12
+ }
13
+ /**
14
+ * Exports colors as a Tailwind v4 `@theme` block. Paste it into the CSS file
15
+ * that has `@import "tailwindcss"`. An array of 11 colors gets the keys
16
+ * 50 to 950, so a `tailwind-11` scale reads like Tailwind's own.
17
+ */
18
+ export declare function toTailwindTheme(tree: TokenTree, options?: TailwindThemeOptions): string;
19
+ //# sourceMappingURL=tailwind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind.d.ts","names":[],"sources":["../../src/tokens/tailwind.ts"],"mappings":";;;;iBAKiB,6BAA6B,KAAK;;EAElD;;EAEA;;EAEA,QAAQ;;;;;;;wBAQO,gBAAgB,MAAM,WAAW,UAAS"}
@@ -0,0 +1,19 @@
1
+ import { declarations } from "./css.js";
2
+ // src/tokens/tailwind.ts
3
+ /**
4
+ * Exports colors as a Tailwind v4 `@theme` block. Paste it into the CSS file
5
+ * that has `@import "tailwindcss"`. An array of 11 colors gets the keys
6
+ * 50 to 950, so a `tailwind-11` scale reads like Tailwind's own.
7
+ */
8
+ function toTailwindTheme(tree, options = {}) {
9
+ const { namespace = "color", inline = false, space = "oklch", ...rest } = options;
10
+ const lines = declarations(tree, {
11
+ ...rest,
12
+ space,
13
+ prefix: namespace
14
+ });
15
+ return `@theme${inline ? " inline" : ""} {\n${lines.map((l) => `\t${l}\n`).join("")}}\n`;
16
+ }
17
+ export { toTailwindTheme };
18
+
19
+ //# sourceMappingURL=tailwind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind.js","names":[],"sources":["../../src/tokens/tailwind.ts"],"sourcesContent":["import type { ColorSpaceId } from '../color/types.js'\nimport { type CssVariablesOptions, declarations } from './css.js'\nimport type { TokenTree } from './tokens.js'\n\n/** Options for `toTailwindTheme`. It also takes the options of `toCss`. */\nexport interface TailwindThemeOptions extends Omit<CssVariablesOptions, 'selector' | 'prefix'> {\n\t/** The theme variable namespace. Default `color`, which makes `bg-primary-500` and `text-primary-500` work. */\n\tnamespace?: string\n\t/** Write `@theme inline`, so utilities hold the value instead of a `var()` reference. */\n\tinline?: boolean\n\t/** Default `oklch`, the space Tailwind v4's own palette uses. */\n\tspace?: ColorSpaceId\n}\n\n/**\n * Exports colors as a Tailwind v4 `@theme` block. Paste it into the CSS file\n * that has `@import \"tailwindcss\"`. An array of 11 colors gets the keys\n * 50 to 950, so a `tailwind-11` scale reads like Tailwind's own.\n */\nexport function toTailwindTheme(tree: TokenTree, options: TailwindThemeOptions = {}): string {\n\tconst { namespace = 'color', inline = false, space = 'oklch', ...rest } = options\n\tconst lines = declarations(tree, { ...rest, space, prefix: namespace })\n\treturn `@theme${inline ? ' inline' : ''} {\\n${lines.map((l) => `\\t${l}\\n`).join('')}}\\n`\n}\n"],"mappings":";;;;;;;AAmBA,SAAgB,gBAAgB,MAAiB,UAAgC,CAAC,GAAW;CAC5F,MAAM,EAAE,YAAY,SAAS,SAAS,OAAO,QAAQ,SAAS,GAAG,SAAS;CAC1E,MAAM,QAAQ,aAAa,MAAM;EAAE,GAAG;EAAM;EAAO,QAAQ;CAAU,CAAC;CACtE,OAAO,SAAS,SAAS,YAAY,GAAG,MAAM,MAAM,KAAK,MAAM,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;AACrF"}
@@ -0,0 +1,11 @@
1
+ import { Color } from "../color/types.js";
2
+ import { SemanticPalette } from "../palette/semantic.js";
3
+ /**
4
+ * What the exporters accept: a color, a CSS color string, an array of them (a
5
+ * scale), or an object of any of these nested to any depth. A `SemanticPalette`
6
+ * works as is: its `scales` and its role pairs export, its `meta` and ratios do not.
7
+ */
8
+ export type TokenTree = SemanticPalette | Color | string | readonly TokenTree[] | {
9
+ readonly [name: string]: TokenTree;
10
+ };
11
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","names":[],"sources":["../../src/tokens/tokens.ts"],"mappings":";;;;;;;YASY,YAAY,kBAAkB,0BAA0B;YAA0B,eAAe"}
@@ -0,0 +1,77 @@
1
+ import { parse } from "../color/parse.js";
2
+ // src/tokens/tokens.ts
3
+ const TAILWIND_STEPS = [
4
+ 50,
5
+ 100,
6
+ 200,
7
+ 300,
8
+ 400,
9
+ 500,
10
+ 600,
11
+ 700,
12
+ 800,
13
+ 900,
14
+ 950
15
+ ];
16
+ const isColor = (v) => typeof v === "object" && v !== null && typeof v.colorSpace === "string" && Array.isArray(v.components);
17
+ function dropMeasures(tree) {
18
+ const { meta: _meta, scales, roles, ...rest } = tree;
19
+ const pairs = Object.fromEntries(Object.entries(roles).map(([name, p]) => [name, {
20
+ bg: p.bg,
21
+ fg: p.fg
22
+ }]));
23
+ return {
24
+ ...rest,
25
+ ...scales,
26
+ roles: pairs
27
+ };
28
+ }
29
+ const isSemantic = (v) => "roles" in v && "scales" in v && "meta" in v;
30
+ /**
31
+ * Walks a token tree into a flat list. An array gets the Tailwind keys
32
+ * (50 to 950) when it has 11 colors and 1-based numbers otherwise; pass an
33
+ * object to choose the keys. A value that is not a color, a string, an array
34
+ * or an object throws with its path.
35
+ */
36
+ function flatten(tree) {
37
+ const out = [];
38
+ const walk = (node, path) => {
39
+ if (typeof node === "string") return void out.push({
40
+ path,
41
+ color: parse(node)
42
+ });
43
+ if (isColor(node)) return void out.push({
44
+ path,
45
+ color: node
46
+ });
47
+ if (Array.isArray(node)) {
48
+ node.forEach((v, i) => {
49
+ walk(v, [...path, String(node.length === TAILWIND_STEPS.length ? TAILWIND_STEPS[i] : i + 1)]);
50
+ });
51
+ return;
52
+ }
53
+ if (typeof node === "object" && node !== null) {
54
+ const obj = isSemantic(node) ? dropMeasures(node) : node;
55
+ for (const [k, v] of Object.entries(obj)) walk(v, [...path, k]);
56
+ return;
57
+ }
58
+ throw new TypeError(`${path.join(".") || "(root)"} is not a color, a string, an array or an object`);
59
+ };
60
+ walk(tree, []);
61
+ return out;
62
+ }
63
+ /** `primaryContainer` and `Primary Container` both become `primary-container`. */
64
+ const kebab = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
65
+ /** Names tokens for CSS: kebab-case segments joined by `-` after `prefix`. Two tokens with one name throw. */
66
+ function cssNames(tokens, prefix) {
67
+ const out = /* @__PURE__ */ new Map();
68
+ for (const { path, color } of tokens) {
69
+ const name = `--${[prefix, ...path].map(kebab).filter(Boolean).join("-")}`;
70
+ if (out.has(name)) throw new Error(`${name} is produced by more than one token`);
71
+ out.set(name, color);
72
+ }
73
+ return out;
74
+ }
75
+ export { cssNames, flatten, kebab };
76
+
77
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","names":[],"sources":["../../src/tokens/tokens.ts"],"sourcesContent":["import { parse } from '../color/parse.js'\nimport type { Color } from '../color/types.js'\nimport type { SemanticPalette } from '../palette/semantic.js'\n\n/**\n * What the exporters accept: a color, a CSS color string, an array of them (a\n * scale), or an object of any of these nested to any depth. A `SemanticPalette`\n * works as is: its `scales` and its role pairs export, its `meta` and ratios do not.\n */\nexport type TokenTree = SemanticPalette | Color | string | readonly TokenTree[] | { readonly [name: string]: TokenTree }\n\n/** A color and the path of names that leads to it. */\nexport interface Token {\n\tpath: string[]\n\tcolor: Color\n}\n\nconst TAILWIND_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]\n\nconst isColor = (v: unknown): v is Color =>\n\ttypeof v === 'object' &&\n\tv !== null &&\n\ttypeof (v as Color).colorSpace === 'string' &&\n\tArray.isArray((v as Color).components)\n\n// A semantic palette carries numbers (`ratio`, `target`, `meta`) that are not colors.\nfunction dropMeasures(tree: Record<string, unknown>): Record<string, unknown> {\n\tconst { meta: _meta, scales, roles, ...rest } = tree\n\tconst pairs = Object.fromEntries(\n\t\tObject.entries(roles as Record<string, { bg: Color; fg: Color }>).map(([name, p]) => [name, { bg: p.bg, fg: p.fg }])\n\t)\n\treturn { ...rest, ...(scales as object), roles: pairs }\n}\n\nconst isSemantic = (v: object): boolean => 'roles' in v && 'scales' in v && 'meta' in v\n\n/**\n * Walks a token tree into a flat list. An array gets the Tailwind keys\n * (50 to 950) when it has 11 colors and 1-based numbers otherwise; pass an\n * object to choose the keys. A value that is not a color, a string, an array\n * or an object throws with its path.\n */\nexport function flatten(tree: TokenTree): Token[] {\n\tconst out: Token[] = []\n\tconst walk = (node: unknown, path: string[]): void => {\n\t\tif (typeof node === 'string') return void out.push({ path, color: parse(node) })\n\t\tif (isColor(node)) return void out.push({ path, color: node })\n\t\tif (Array.isArray(node)) {\n\t\t\tnode.forEach((v, i) => {\n\t\t\t\twalk(v, [...path, String(node.length === TAILWIND_STEPS.length ? TAILWIND_STEPS[i] : i + 1)])\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tif (typeof node === 'object' && node !== null) {\n\t\t\tconst obj = isSemantic(node) ? dropMeasures(node as Record<string, unknown>) : (node as Record<string, unknown>)\n\t\t\tfor (const [k, v] of Object.entries(obj)) walk(v, [...path, k])\n\t\t\treturn\n\t\t}\n\t\tthrow new TypeError(`${path.join('.') || '(root)'} is not a color, a string, an array or an object`)\n\t}\n\twalk(tree, [])\n\treturn out\n}\n\n/** `primaryContainer` and `Primary Container` both become `primary-container`. */\nexport const kebab = (name: string): string =>\n\tname\n\t\t.replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n\t\t.replace(/[^a-zA-Z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\t\t.toLowerCase()\n\n/** Names tokens for CSS: kebab-case segments joined by `-` after `prefix`. Two tokens with one name throw. */\nexport function cssNames(tokens: Token[], prefix: string): Map<string, Color> {\n\tconst out = new Map<string, Color>()\n\tfor (const { path, color } of tokens) {\n\t\tconst name = `--${[prefix, ...path].map(kebab).filter(Boolean).join('-')}`\n\t\tif (out.has(name)) throw new Error(`${name} is produced by more than one token`)\n\t\tout.set(name, color)\n\t}\n\treturn out\n}\n"],"mappings":";;AAiBA,MAAM,iBAAiB;CAAC;CAAI;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AAE5E,MAAM,WAAW,MAChB,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAAY,eAAe,YACnC,MAAM,QAAS,EAAY,UAAU;AAGtC,SAAS,aAAa,MAAwD;CAC7E,MAAM,EAAE,MAAM,OAAO,QAAQ,OAAO,GAAG,SAAS;CAChD,MAAM,QAAQ,OAAO,YACpB,OAAO,QAAQ,KAAiD,CAAC,CAAC,KAAK,CAAC,MAAM,OAAO,CAAC,MAAM;EAAE,IAAI,EAAE;EAAI,IAAI,EAAE;CAAG,CAAC,CAAC,CACpH;CACA,OAAO;EAAE,GAAG;EAAM,GAAI;EAAmB,OAAO;CAAM;AACvD;AAEA,MAAM,cAAc,MAAuB,WAAW,KAAK,YAAY,KAAK,UAAU;;;;;;;AAQtF,SAAgB,QAAQ,MAA0B;CACjD,MAAM,MAAe,CAAC;CACtB,MAAM,QAAQ,MAAe,SAAyB;EACrD,IAAI,OAAO,SAAS,UAAU,OAAO,KAAK,IAAI,KAAK;GAAE;GAAM,OAAO,MAAM,IAAI;EAAE,CAAC;EAC/E,IAAI,QAAQ,IAAI,GAAG,OAAO,KAAK,IAAI,KAAK;GAAE;GAAM,OAAO;EAAK,CAAC;EAC7D,IAAI,MAAM,QAAQ,IAAI,GAAG;GACxB,KAAK,SAAS,GAAG,MAAM;IACtB,KAAK,GAAG,CAAC,GAAG,MAAM,OAAO,KAAK,WAAW,eAAe,SAAS,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC;GAC7F,CAAC;GACD;EACD;EACA,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;GAC9C,MAAM,MAAM,WAAW,IAAI,IAAI,aAAa,IAA+B,IAAK;GAChF,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,GAAG,GAAG,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;GAC9D;EACD;EACA,MAAM,IAAI,UAAU,GAAG,KAAK,KAAK,GAAG,KAAK,SAAS,iDAAiD;CACpG;CACA,KAAK,MAAM,CAAC,CAAC;CACb,OAAO;AACR;;AAGA,MAAa,SAAS,SACrB,KACE,QAAQ,sBAAsB,OAAO,CAAC,CACtC,QAAQ,kBAAkB,GAAG,CAAC,CAC9B,QAAQ,YAAY,EAAE,CAAC,CACvB,YAAY;;AAGf,SAAgB,SAAS,QAAiB,QAAoC;CAC7E,MAAM,sBAAM,IAAI,IAAmB;CACnC,KAAK,MAAM,EAAE,MAAM,WAAW,QAAQ;EACrC,MAAM,OAAO,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACvE,IAAI,IAAI,IAAI,IAAI,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,oCAAoC;EAC/E,IAAI,IAAI,MAAM,KAAK;CACpB;CACA,OAAO;AACR"}