@esportsplus/ui 0.53.0 → 0.54.0

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 (308) hide show
  1. package/README.md +51 -6
  2. package/build/components/accordion/index.d.ts +2 -19
  3. package/build/components/accordion/index.js +7 -7
  4. package/build/components/anchor/scss/index.scss +1 -1
  5. package/build/components/button/scss/index.scss +1 -1
  6. package/build/components/card/scss/index.scss +1 -1
  7. package/build/components/checkbox/index.d.ts +12 -12
  8. package/build/components/checkbox/index.js +64 -36
  9. package/build/components/checkbox/scss/index.scss +1 -1
  10. package/build/components/checkbox/svg/check.svg +1 -0
  11. package/build/components/clipboard/copy.d.ts +11 -0
  12. package/build/components/clipboard/copy.js +49 -0
  13. package/build/components/clipboard/index.d.ts +1435 -1229
  14. package/build/components/clipboard/index.js +3 -2
  15. package/build/components/clipboard/onclick.d.ts +4 -38
  16. package/build/components/clipboard/onclick.js +7 -7
  17. package/build/components/counter/index.js +58 -66
  18. package/build/components/counter/scss/index.scss +1 -1
  19. package/build/components/form/action.js +8 -8
  20. package/build/components/form/index.d.ts +4 -4
  21. package/build/components/form/input.d.ts +4 -4
  22. package/build/components/grid/scss/index.scss +1 -1
  23. package/build/components/icon/index.d.ts +2 -19
  24. package/build/components/icon/index.js +6 -6
  25. package/build/components/icon/scss/index.scss +1 -1
  26. package/build/components/index.d.ts +0 -7
  27. package/build/components/index.js +0 -7
  28. package/build/components/input/index.d.ts +1 -1
  29. package/build/components/input/index.js +11 -11
  30. package/build/components/input/scss/index.scss +1 -1
  31. package/build/components/link/scss/index.scss +1 -1
  32. package/build/components/loading/index.js +5 -5
  33. package/build/components/modal/scss/index.scss +1 -1
  34. package/build/components/normalize/scss/index.scss +1 -1
  35. package/build/components/overlay/index.d.ts +2 -19
  36. package/build/components/overlay/index.js +7 -7
  37. package/build/components/page/scss/index.scss +1 -1
  38. package/build/components/radio/index.d.ts +1436 -4
  39. package/build/components/radio/index.js +0 -1
  40. package/build/components/range/index.d.ts +1 -1
  41. package/build/components/range/index.js +13 -13
  42. package/build/components/range/scss/index.scss +1 -1
  43. package/build/components/root/scss/index.scss +1 -1
  44. package/build/components/select/index.d.ts +5 -48
  45. package/build/components/select/index.js +41 -53
  46. package/build/components/select/scss/index.scss +1 -1
  47. package/build/components/sidebar/index.d.ts +2 -19
  48. package/build/components/sidebar/index.js +7 -7
  49. package/build/components/sidebar/scss/index.scss +1 -1
  50. package/build/components/switch/index.d.ts +1436 -4
  51. package/build/components/switch/index.js +0 -1
  52. package/build/components/tabs/scss/index.scss +2 -0
  53. package/build/components/text/scss/index.scss +1 -1
  54. package/build/components/textarea/index.d.ts +719 -6
  55. package/build/components/textarea/index.js +37 -15
  56. package/build/components/textarea/scss/index.scss +1 -1
  57. package/build/components/toast/index.js +43 -43
  58. package/build/components/toast/scss/index.scss +1 -1
  59. package/build/components/tooltip/index.d.ts +2151 -2544
  60. package/build/components/tooltip/menu.d.ts +2 -18
  61. package/build/components/tooltip/menu.js +21 -21
  62. package/build/components/tooltip/onclick.d.ts +717 -1229
  63. package/build/components/tooltip/onclick.js +16 -52
  64. package/build/components/tooltip/onhover.d.ts +713 -20
  65. package/build/components/tooltip/onhover.js +9 -9
  66. package/build/components/tooltip/scss/index.scss +1 -1
  67. package/build/components/typewriter/index.d.ts +2 -19
  68. package/build/components/typewriter/index.js +8 -8
  69. package/build/css-utilities/background/scss/index.scss +1 -1
  70. package/build/css-utilities/border/scss/index.scss +1 -1
  71. package/build/css-utilities/color/scss/index.scss +1 -1
  72. package/build/css-utilities/font/geist/scss/Geist-Italic-Variable.woff2 +0 -0
  73. package/build/css-utilities/font/geist/scss/Geist-Variable.woff2 +0 -0
  74. package/build/css-utilities/font/geist/scss/GeistMono-Italic-Variable.woff2 +0 -0
  75. package/build/css-utilities/font/geist/scss/GeistMono-Variable.woff2 +0 -0
  76. package/build/css-utilities/font/geist/scss/index.scss +2 -0
  77. package/build/css-utilities/font/montserrat/index.d.ts +1 -0
  78. package/build/css-utilities/font/montserrat/index.js +1 -0
  79. package/build/css-utilities/font/montserrat/scss/index.scss +2 -0
  80. package/build/css-utilities/index.scss +1 -1
  81. package/build/css-utilities/inline/scss/index.scss +1 -1
  82. package/build/css-utilities/scrollbar/scss/index.scss +1 -1
  83. package/build/css-utilities/tabular/scss/index.scss +2 -0
  84. package/build/css-utilities/text/scss/index.scss +1 -1
  85. package/build/themes/dark/index.d.ts +0 -1
  86. package/build/themes/dark/index.js +0 -1
  87. package/build/themes/dark/input.d.ts +708 -114
  88. package/build/themes/dark/select.d.ts +732 -1406
  89. package/build/themes/dark/select.js +7 -5
  90. package/build/themes/dark/textarea.d.ts +716 -119
  91. package/package.json +12 -12
  92. package/pnpm-workspace.yaml +4 -3
  93. package/scripts/css-variables.test.mjs +50 -0
  94. package/scripts/sort-scss.md +57 -0
  95. package/scripts/sort-scss.mjs +233 -0
  96. package/scripts/sort-scss.test.mjs +217 -0
  97. package/src/components/anchor/scss/index.scss +2 -2
  98. package/src/components/button/scss/index.scss +23 -57
  99. package/src/components/button/scss/variables.scss +1 -19
  100. package/src/components/card/scss/index.scss +11 -46
  101. package/src/components/card/scss/variables.scss +1 -2
  102. package/src/components/checkbox/index.ts +27 -31
  103. package/src/components/checkbox/scss/index.scss +86 -28
  104. package/src/components/checkbox/scss/variables.scss +71 -30
  105. package/src/components/checkbox/svg/check.svg +1 -0
  106. package/src/components/clipboard/copy.ts +60 -0
  107. package/src/components/clipboard/index.ts +3 -2
  108. package/src/components/counter/index.ts +57 -57
  109. package/src/components/counter/scss/index.scss +4 -5
  110. package/src/components/counter/scss/variables.scss +0 -1
  111. package/src/components/form/input.ts +4 -3
  112. package/src/components/grid/scss/index.scss +1 -1
  113. package/src/components/icon/scss/variables.scss +0 -1
  114. package/src/components/index.ts +1 -8
  115. package/src/components/input/index.ts +1 -1
  116. package/src/components/input/scss/index.scss +8 -7
  117. package/src/components/input/scss/variables.scss +0 -1
  118. package/src/components/link/scss/index.scss +9 -34
  119. package/src/components/link/scss/variables.scss +0 -12
  120. package/src/components/modal/scss/index.scss +4 -4
  121. package/src/components/normalize/scss/index.scss +0 -1
  122. package/src/components/page/scss/index.scss +0 -6
  123. package/src/components/page/scss/variables.scss +0 -2
  124. package/src/components/radio/index.ts +0 -1
  125. package/src/components/range/index.ts +1 -1
  126. package/src/components/range/scss/index.scss +5 -5
  127. package/src/components/root/scss/index.scss +37 -63
  128. package/src/components/root/scss/variables.scss +37 -67
  129. package/src/components/select/index.ts +16 -33
  130. package/src/components/select/scss/index.scss +10 -7
  131. package/src/components/select/scss/variables.scss +0 -1
  132. package/src/components/sidebar/scss/index.scss +3 -3
  133. package/src/components/switch/index.ts +1 -2
  134. package/src/components/tabs/scss/index.scss +62 -0
  135. package/src/components/tabs/scss/variables.scss +4 -0
  136. package/src/components/text/scss/index.scss +8 -1
  137. package/src/components/text/scss/variables.scss +5 -3
  138. package/src/components/textarea/index.ts +38 -5
  139. package/src/components/textarea/scss/index.scss +24 -13
  140. package/src/components/textarea/scss/variables.scss +0 -1
  141. package/src/components/toast/scss/index.scss +1 -1
  142. package/src/components/tooltip/onclick.ts +11 -67
  143. package/src/components/tooltip/scss/index.scss +2 -3
  144. package/src/css-utilities/background/scss/variables.scss +2 -2
  145. package/src/css-utilities/border/scss/index.scss +1 -27
  146. package/src/css-utilities/border/scss/variables.scss +3 -34
  147. package/src/css-utilities/color/scss/variables.scss +2 -6
  148. package/src/css-utilities/font/geist/scss/Geist-Italic-Variable.woff2 +0 -0
  149. package/src/css-utilities/font/geist/scss/Geist-Variable.woff2 +0 -0
  150. package/src/css-utilities/font/geist/scss/GeistMono-Italic-Variable.woff2 +0 -0
  151. package/src/css-utilities/font/geist/scss/GeistMono-Variable.woff2 +0 -0
  152. package/src/css-utilities/font/geist/scss/index.scss +55 -0
  153. package/src/css-utilities/font/montserrat/index.ts +1 -0
  154. package/src/{fonts → css-utilities/font}/montserrat/scss/index.scss +15 -0
  155. package/src/css-utilities/index.scss +6 -12
  156. package/src/css-utilities/inline/scss/index.scss +0 -1
  157. package/src/css-utilities/scrollbar/scss/index.scss +42 -0
  158. package/src/css-utilities/tabular/scss/index.scss +3 -0
  159. package/src/css-utilities/text/scss/index.scss +30 -71
  160. package/src/lib/scss/css-variables.scss +29 -5
  161. package/src/themes/dark/index.ts +0 -1
  162. package/src/themes/dark/select.ts +7 -5
  163. package/src/tokens/index.scss +1 -3
  164. package/src/tokens/scss/border-radius.scss +1 -6
  165. package/src/tokens/scss/color.scss +3 -3
  166. package/src/tokens/scss/scale.scss +1 -1
  167. package/src/tokens/scss/size.scss +7 -0
  168. package/src/tokens/scss/transition.scss +4 -0
  169. package/storage/svg/arrow-left.svg +1 -0
  170. package/storage/svg/arrow-right.svg +1 -0
  171. package/storage/svg/book.svg +1 -0
  172. package/storage/svg/cube.svg +1 -0
  173. package/storage/svg/github.svg +1 -0
  174. package/storage/svg/info.svg +1 -0
  175. package/storage/svg/palette.svg +1 -0
  176. package/storage/svg/search.svg +2 -0
  177. package/storage/svg/type.svg +1 -0
  178. package/vite.config.ts +8 -3
  179. package/build/components/back/index.d.ts +0 -21
  180. package/build/components/back/index.js +0 -16
  181. package/build/components/back/scss/index.scss +0 -2
  182. package/build/components/back/svg/arrow.svg +0 -15
  183. package/build/components/bubble/scss/index.scss +0 -2
  184. package/build/components/button/index.d.ts +0 -103
  185. package/build/components/button/index.js +0 -60
  186. package/build/components/ellipsis/index.d.ts +0 -4
  187. package/build/components/ellipsis/index.js +0 -8
  188. package/build/components/ellipsis/scss/index.scss +0 -2
  189. package/build/components/frame/index.d.ts +0 -21
  190. package/build/components/frame/index.js +0 -13
  191. package/build/components/frame/scss/index.scss +0 -2
  192. package/build/components/highlight/index.d.ts +0 -22
  193. package/build/components/highlight/index.js +0 -56
  194. package/build/components/highlight/scss/index.scss +0 -2
  195. package/build/components/loader/index.d.ts +0 -22
  196. package/build/components/loader/index.js +0 -78
  197. package/build/components/loader/scss/index.scss +0 -2
  198. package/build/components/radio/scss/index.scss +0 -2
  199. package/build/components/root/index.d.ts +0 -7
  200. package/build/components/root/index.js +0 -4
  201. package/build/components/root/onclick.d.ts +0 -5
  202. package/build/components/root/onclick.js +0 -12
  203. package/build/components/switch/scss/index.scss +0 -2
  204. package/build/css-utilities/[margin,padding]/scss/index.scss +0 -2
  205. package/build/css-utilities/error/scss/index.scss +0 -2
  206. package/build/css-utilities/flicker/scss/index.scss +0 -2
  207. package/build/css-utilities/focus/scss/index.scss +0 -2
  208. package/build/css-utilities/gap/scss/index.scss +0 -2
  209. package/build/css-utilities/line-height/scss/index.scss +0 -2
  210. package/build/css-utilities/pointer/scss/index.scss +0 -2
  211. package/build/css-utilities/scroll-fade/scss/index.scss +0 -2
  212. package/build/css-utilities/size/scss/index.scss +0 -2
  213. package/build/css-utilities/viewport/scss/index.scss +0 -2
  214. package/build/css-utilities/width/scss/index.scss +0 -2
  215. package/build/fonts/index.d.ts +0 -1
  216. package/build/fonts/index.js +0 -1
  217. package/build/fonts/montserrat/scss/index.scss +0 -2
  218. package/build/themes/dark/back.d.ts +0 -19
  219. package/build/themes/dark/back.js +0 -7
  220. package/build/themes/dark/button/scss/index.scss +0 -2
  221. package/build/themes/dark/link/scss/index.scss +0 -2
  222. package/src/components/back/index.ts +0 -20
  223. package/src/components/back/scss/index.scss +0 -17
  224. package/src/components/back/svg/arrow.svg +0 -15
  225. package/src/components/bubble/scss/index.scss +0 -32
  226. package/src/components/bubble/scss/variables.scss +0 -20
  227. package/src/components/button/index.ts +0 -53
  228. package/src/components/ellipsis/index.ts +0 -11
  229. package/src/components/ellipsis/scss/index.scss +0 -30
  230. package/src/components/ellipsis/scss/variables.scss +0 -5
  231. package/src/components/frame/index.ts +0 -13
  232. package/src/components/frame/scss/index.scss +0 -39
  233. package/src/components/frame/scss/variables.scss +0 -4
  234. package/src/components/highlight/index.ts +0 -66
  235. package/src/components/highlight/scss/index.scss +0 -7
  236. package/src/components/highlight/scss/variables.scss +0 -4
  237. package/src/components/loader/index.ts +0 -80
  238. package/src/components/loader/scss/index.scss +0 -71
  239. package/src/components/loader/scss/variables.scss +0 -6
  240. package/src/components/radio/scss/index.scss +0 -50
  241. package/src/components/radio/scss/variables.scss +0 -67
  242. package/src/components/root/index.ts +0 -6
  243. package/src/components/root/onclick.ts +0 -20
  244. package/src/components/switch/scss/index.scss +0 -48
  245. package/src/components/switch/scss/variables.scss +0 -74
  246. package/src/css-utilities/[margin,padding]/scss/index.scss +0 -43
  247. package/src/css-utilities/[margin,padding]/scss/variables.scss +0 -54
  248. package/src/css-utilities/error/scss/index.scss +0 -24
  249. package/src/css-utilities/error/scss/variables.scss +0 -3
  250. package/src/css-utilities/flicker/scss/index.scss +0 -15
  251. package/src/css-utilities/flicker/scss/variables.scss +0 -5
  252. package/src/css-utilities/focus/scss/index.scss +0 -7
  253. package/src/css-utilities/focus/scss/variables.scss +0 -5
  254. package/src/css-utilities/gap/scss/index.scss +0 -13
  255. package/src/css-utilities/gap/scss/variables.scss +0 -39
  256. package/src/css-utilities/line-height/scss/index.scss +0 -1
  257. package/src/css-utilities/line-height/scss/variables.scss +0 -10
  258. package/src/css-utilities/pointer/scss/index.scss +0 -5
  259. package/src/css-utilities/scroll-fade/scss/index.scss +0 -47
  260. package/src/css-utilities/scroll-fade/scss/variables.scss +0 -3
  261. package/src/css-utilities/size/scss/index.scss +0 -1
  262. package/src/css-utilities/size/scss/variables.scss +0 -12
  263. package/src/css-utilities/text/scss/variables.scss +0 -31
  264. package/src/css-utilities/viewport/scss/index.scss +0 -5
  265. package/src/css-utilities/width/scss/index.scss +0 -5
  266. package/src/css-utilities/width/scss/variables.scss +0 -9
  267. package/src/fonts/index.ts +0 -1
  268. package/src/themes/dark/back.ts +0 -9
  269. package/src/themes/dark/button/scss/index.scss +0 -46
  270. package/src/themes/dark/link/scss/index.scss +0 -26
  271. package/src/tokens/scss/font-weight.scss +0 -6
  272. package/src/tokens/scss/line-height.scss +0 -9
  273. package/src/tokens/scss/transition-duration.scss +0 -1
  274. /package/build/{fonts/montserrat → css-utilities/font/geist}/index.d.ts +0 -0
  275. /package/build/{fonts/montserrat → css-utilities/font/geist}/index.js +0 -0
  276. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Bold.woff +0 -0
  277. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Bold.woff2 +0 -0
  278. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-BoldItalic.woff +0 -0
  279. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-BoldItalic.woff2 +0 -0
  280. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Italic.woff +0 -0
  281. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Italic.woff2 +0 -0
  282. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Medium.woff +0 -0
  283. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Medium.woff2 +0 -0
  284. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-MediumItalic.woff +0 -0
  285. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-MediumItalic.woff2 +0 -0
  286. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Regular.woff +0 -0
  287. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Regular.woff2 +0 -0
  288. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBold.woff +0 -0
  289. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBold.woff2 +0 -0
  290. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBoldItalic.woff +0 -0
  291. /package/build/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBoldItalic.woff2 +0 -0
  292. /package/src/{fonts/montserrat → css-utilities/font/geist}/index.ts +0 -0
  293. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Bold.woff +0 -0
  294. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Bold.woff2 +0 -0
  295. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-BoldItalic.woff +0 -0
  296. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-BoldItalic.woff2 +0 -0
  297. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Italic.woff +0 -0
  298. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Italic.woff2 +0 -0
  299. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Medium.woff +0 -0
  300. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Medium.woff2 +0 -0
  301. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-MediumItalic.woff +0 -0
  302. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-MediumItalic.woff2 +0 -0
  303. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Regular.woff +0 -0
  304. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-Regular.woff2 +0 -0
  305. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBold.woff +0 -0
  306. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBold.woff2 +0 -0
  307. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBoldItalic.woff +0 -0
  308. /package/src/{fonts → css-utilities/font}/montserrat/scss/Montserrat-SemiBoldItalic.woff2 +0 -0
package/package.json CHANGED
@@ -2,31 +2,29 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/action": "^0.1.8",
5
- "@esportsplus/queue": "^0.4.6",
6
5
  "@esportsplus/reactivity": "^0.36.7",
7
- "@esportsplus/template": "^0.46.4",
8
- "@esportsplus/utilities": "^0.28.4",
6
+ "@esportsplus/template": "^0.49.0",
7
+ "@esportsplus/utilities": "^0.28.5",
9
8
  "modern-normalize": "^3.0.1"
10
9
  },
11
10
  "devDependencies": {
12
- "@esportsplus/typescript": "^0.34.4",
13
- "@types/node": "^26.6.1",
11
+ "@esportsplus/typescript": "^0.35.1",
12
+ "@types/node": "^26.6.2",
14
13
  "@types/shelljs": "^0.10.0",
15
14
  "autoprefixer": "^10.6.1",
16
15
  "glob": "^13.0.6",
17
16
  "lightningcss": "^1.33.0",
18
17
  "npm-run-all": "^4.1.5",
19
- "sass": "^1.104.1",
18
+ "postcss": "8.5.28",
19
+ "postcss-scss": "4.0.9",
20
+ "sass": "^1.105.0",
20
21
  "shelljs": "^0.10.0",
21
22
  "vite": "^8.3.0"
22
23
  },
23
24
  "exports": {
24
25
  "./css-utilities.scss": "./build/css-utilities/index.scss",
25
26
  "./css-utilities/*.scss": "./build/css-utilities/*/scss/index.scss",
26
- "./fonts": {
27
- "types": "./build/fonts/index.d.ts",
28
- "default": "./build/fonts/index.js"
29
- },
27
+ "./css-utilities/font/*.scss": "./build/css-utilities/font/*/scss/index.scss",
30
28
  "./layer.scss": "./layer.scss",
31
29
  "./lib.scss": "./lib.scss",
32
30
  "./themes/dark": {
@@ -56,9 +54,11 @@
56
54
  "type": "git",
57
55
  "url": "https://github.com/esportsplus/ui"
58
56
  },
59
- "sideEffects": false,
57
+ "sideEffects": [
58
+ "**/*.scss"
59
+ ],
60
60
  "type": "module",
61
- "version": "0.53.0",
61
+ "version": "0.54.0",
62
62
  "scripts": {
63
63
  "build": "run-s build:vite build:ts",
64
64
  "build:ts": "tsc",
@@ -11,12 +11,12 @@ allowBuilds:
11
11
  # first-party upgrades (reactivity, template, ...) be consumed immediately
12
12
  # instead of waiting out the release-age cutoff.
13
13
  minimumReleaseAgeExclude:
14
- - '@esportsplus/typescript@0.34.1 || 0.34.2 || 0.34.3 || 0.34.4'
14
+ - '@esportsplus/typescript@0.34.1 || 0.34.2 || 0.34.3 || 0.34.4 || 0.35.1'
15
15
  - '@esportsplus/action@0.1.7 || 0.1.8'
16
16
  - '@esportsplus/queue@0.4.5 || 0.4.6'
17
17
  - '@esportsplus/reactivity@0.36.4 || 0.36.6 || 0.36.7'
18
- - '@esportsplus/template@0.44.4 || 0.44.6 || 0.45.0 || 0.45.1 || 0.45.3 || 0.46.1 || 0.46.2 || 0.46.3 || 0.46.4'
19
- - '@esportsplus/utilities@0.28.3 || 0.28.4'
18
+ - '@esportsplus/template@0.44.4 || 0.44.6 || 0.45.0 || 0.45.1 || 0.45.3 || 0.46.1 || 0.46.2 || 0.46.3 || 0.46.4 || 0.46.5 || 0.46.6 || 0.48.1 || 0.49.0'
19
+ - '@esportsplus/utilities@0.28.3 || 0.28.4 || 0.28.5'
20
20
  - '@esportsplus/vite@0.14.2 || 0.14.5 || 0.14.6'
21
21
  - '@esportsplus/*'
22
22
  # browserslist's data packages (pulled transitively via autoprefixer) republish daily, so a
@@ -26,3 +26,4 @@ minimumReleaseAgeExclude:
26
26
  - caniuse-lite
27
27
  - electron-to-chromium
28
28
  - '@types/node@26.6.2'
29
+ - 'sass@1.105.0'
@@ -0,0 +1,50 @@
1
+ import assert from 'node:assert/strict';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { test } from 'node:test';
4
+ import { compileString } from 'sass';
5
+
6
+ const loadPaths = [fileURLToPath(new URL('../src/lib/scss/', import.meta.url))];
7
+ const compile = source => compileString(`@use 'css-variables' as lib; ${source}`, {
8
+ loadPaths,
9
+ style: 'compressed'
10
+ }).css;
11
+
12
+ test('preserves positional, named and mixed declaration calls', () => {
13
+ const expected = ':root{--font-weight-300: 400;--font-weight-500: 600}';
14
+ for (const args of [
15
+ 'font-weight, (300: 400, 500: 600)',
16
+ '$prefix: font-weight, $tokens: (300: 400, 500: 600)',
17
+ 'font-weight, $tokens: (300: 400, 500: 600)'
18
+ ]) {
19
+ assert.equal(compile(`:root { @include lib.css-variables(${args}); }`), expected);
20
+ }
21
+ });
22
+
23
+ test('three arguments wrap each custom-property value in its own class', () => {
24
+ const expected = '.--font-weight-300{--font-weight-300: 400}.--font-weight-500{--font-weight-500: 600}.--font-weight-600{--font-weight-600: 700}';
25
+ for (const args of [
26
+ "'--font-weight', font-weight, (300: 400, 500: 600, 600: 700)",
27
+ "$class: '--font-weight', $prefix: font-weight, $tokens: (300: 400, 500: 600, 600: 700)"
28
+ ]) {
29
+ assert.equal(compile(`@include lib.css-variables(${args});`), expected);
30
+ }
31
+ });
32
+
33
+ test('nested maps produce matching declaration and utility suffixes', () => {
34
+ assert.equal(compile(`
35
+ $colors: (blue: (300: #abc, 500: #123));
36
+ :root { @include lib.css-variables(color, $colors); }
37
+ @include lib.css-variables('--color', color, $colors);
38
+ `), ':root{--color-blue-300: #abc;--color-blue-500: #123}.--color-blue-300{--color-blue-300: #abc}.--color-blue-500{--color-blue-500: #123}');
39
+ });
40
+
41
+ test('utility classes preserve a surrounding selector scope', () => {
42
+ assert.equal(compile(`.preview { @include lib.css-variables('--weight', font-weight, (500: 600)); }`),
43
+ '.preview .--weight-500{--font-weight-500: 600}');
44
+ });
45
+
46
+ test('empty maps emit no CSS and invalid token arguments fail clearly', () => {
47
+ assert.equal(compile(':root { @include lib.css-variables(color, ()); }'), '');
48
+ assert.equal(compile("@include lib.css-variables('--color', color, ());"), '');
49
+ assert.throws(() => compile(':root { @include lib.css-variables(color, red); }'), /with a token map/);
50
+ });
@@ -0,0 +1,57 @@
1
+ # Standalone SCSS sorter
2
+
3
+ Run with Node 20+; no bundler, Sass compiler, or PostCSS configuration is used.
4
+
5
+ ```sh
6
+ node scripts/sort-scss.mjs --check path/to/styles
7
+ node scripts/sort-scss.mjs --write path/to/styles.scss
8
+ node scripts/sort-scss.mjs --write path/to/styles another/file.scss
9
+ ```
10
+
11
+ Paths resolve from the current working directory. To use elsewhere, copy
12
+ `sort-scss.mjs` into a project with `postcss` and `postcss-scss` installed, or invoke
13
+ this installed script by absolute path. Dependencies are declared in
14
+ `package.json` (development dependencies).
15
+
16
+ The sorter orders consecutive sibling selector blocks by their full selector
17
+ text and consecutive declarations by property name, recursively, using
18
+ case-sensitive code-unit order. Punctuation and vendor prefixes participate in
19
+ that order. Equal keys retain their original relative order. Comma-separated
20
+ selector lists remain intact.
21
+
22
+ Sass nested selectors such as `&-actions`, `&-inner`, and `&-nav` are sorted
23
+ within their parent too. Use `--write` to apply changes; `--check` only reports them.
24
+
25
+ Consecutive simple class selectors sharing a prefix are also grouped into Sass
26
+ nesting: `.spec-name`, `.spec-table`, and `.spec-value` become `.spec` with
27
+ `&-name`, `&-table`, and `&-value` children. This uses the segment before the
28
+ first hyphen and requires at least two matching siblings. Existing base rules
29
+ remain separate so their declarations and Sass scope are preserved. Complex
30
+ selectors, interpolation, and ordering boundaries are not crossed; groups inside
31
+ at-rules are handled within their own scope. Comments move with their rules.
32
+
33
+ Leading comments move with the following statement; same-line trailing comments
34
+ move with the preceding statement. Existing whitespace and value text are
35
+ retained where possible. This organizes source; it does not impose indentation
36
+ or line wrapping.
37
+
38
+ At-rules, Sass variable assignments, interpolated property/selector names, and
39
+ transitions between declarations and nested rules divide sorting runs. Statements
40
+ never move across these boundaries. Contents of at-rules are processed recursively;
41
+ keyframe steps retain their order while properties inside each step are sorted.
42
+
43
+ Sorting is strict within each run: overlapping selectors and shorthand/longhand
44
+ properties are alphabetized even when this changes the cascade. Sass function
45
+ evaluation order can also change. Duplicate declarations retain fallback order.
46
+
47
+ Directory traversal skips `.git`, `node_modules`, and symlinks. Explicit symlink
48
+ targets and non-SCSS file targets are rejected. All inputs are parsed before any
49
+ write. A malformed input prevents writes for the batch; filesystem write failures
50
+ are reported but do not roll back earlier successful writes.
51
+
52
+ Exit status is 0 for success, 1 for unsorted files in check mode, and 2 for invalid
53
+ arguments, parse failures, or I/O errors. Check mode never writes.
54
+
55
+ ```sh
56
+ node --test scripts/sort-scss.test.mjs
57
+ ```
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ import * as NodeFSP from "node:fs/promises";
3
+ import { realpathSync } from "node:fs";
4
+ import * as NodePath from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import scss from "postcss-scss";
7
+
8
+ const help = `Usage: node sort-scss.mjs --check|--write <file.scss|directory>...
9
+
10
+ Alphabetize sibling selectors and declaration properties recursively.
11
+ Group sibling .prefix-* classes into .prefix { &-* { ... } } blocks.
12
+ Sorting is strict and may change the cascade or Sass evaluation behavior.
13
+ At-rules, Sass assignments, and interpolated names remain ordering boundaries.
14
+ Duplicate properties retain their relative order. Keyframe steps stay in place.
15
+ Directories are recursive; .git and node_modules are excluded.
16
+ Exit codes: 0 = success, 1 = unsorted (--check), 2 = invalid input or I/O error.
17
+ `;
18
+
19
+ function compare(left, right) {
20
+ return left < right ? -1 : left > right ? 1 : 0;
21
+ }
22
+
23
+ function sortableKind(node, keyframes) {
24
+ if (node.type === "decl" && !node.prop.includes("$") && !node.prop.includes("#{")) {
25
+ return "decl";
26
+ }
27
+ if (node.type === "rule" && !keyframes && !node.selector.includes("#{")) {
28
+ return "rule";
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function unitsFor(nodes) {
34
+ const units = [];
35
+ let comments = [];
36
+ for (const node of nodes) {
37
+ if (node.type === "comment") {
38
+ const previous = units.at(-1);
39
+ if (previous && comments.length === 0 && !/[\r\n]/.test(node.raws.before ?? "")) {
40
+ previous.nodes.push(node);
41
+ } else {
42
+ comments.push(node);
43
+ }
44
+ } else {
45
+ units.push({ node, nodes: [...comments, node] });
46
+ comments = [];
47
+ }
48
+ }
49
+ if (comments.length) units.push({ node: null, nodes: comments });
50
+ return units;
51
+ }
52
+
53
+ function sortContainer(container) {
54
+ if (!container.nodes) return;
55
+ for (const node of container.nodes) sortContainer(node);
56
+ const keyframes = container.type === "atrule" && /(?:^|-)keyframes$/i.test(container.name);
57
+ const units = unitsFor(container.nodes);
58
+ let changed = false;
59
+ for (let start = 0; start < units.length; ) {
60
+ const kind = units[start].node && sortableKind(units[start].node, keyframes);
61
+ if (!kind) {
62
+ start++;
63
+ continue;
64
+ }
65
+ let end = start + 1;
66
+ while (
67
+ end < units.length &&
68
+ units[end].node &&
69
+ sortableKind(units[end].node, keyframes) === kind
70
+ )
71
+ end++;
72
+ const original = units.slice(start, end);
73
+ const sorted = original.toSorted((a, b) =>
74
+ compare(
75
+ kind === "decl" ? a.node.prop : a.node.selector.trim(),
76
+ kind === "decl" ? b.node.prop : b.node.selector.trim(),
77
+ ),
78
+ );
79
+ if (sorted.some((unit, index) => unit !== original[index])) {
80
+ // Keep spacing at each position while moving comments with their owner.
81
+ const spacing = original.map((unit) => unit.nodes[0].raws.before);
82
+ sorted.forEach((unit, index) => {
83
+ unit.nodes[0].raws.before = spacing[index];
84
+ });
85
+ units.splice(start, end - start, ...sorted);
86
+ changed = true;
87
+ }
88
+ start = end;
89
+ }
90
+ if (changed) {
91
+ container.removeAll();
92
+ for (const unit of units) container.append(unit.nodes);
93
+ // Moving a // comment must not comment out the next statement or closing brace.
94
+ for (let index = 1; index < container.nodes.length; index++) {
95
+ const previous = container.nodes[index - 1];
96
+ const node = container.nodes[index];
97
+ if (
98
+ previous.type === "comment" &&
99
+ previous.raws.inline &&
100
+ !/[\r\n]/.test(node.raws.before ?? "")
101
+ ) {
102
+ node.raws.before = "\n" + (node.raws.before ?? "");
103
+ }
104
+ }
105
+ if (
106
+ container.last?.type === "comment" &&
107
+ container.last.raws.inline &&
108
+ !/[\r\n]/.test(container.raws.after ?? "")
109
+ ) {
110
+ container.raws.after = "\n" + (container.raws.after ?? "");
111
+ }
112
+ }
113
+ }
114
+
115
+ function nestPrefixes(container, indent, newline) {
116
+ if (!container.nodes) return;
117
+ for (const node of [...container.nodes]) nestPrefixes(node, indent, newline);
118
+ if (container.type === "atrule" && /(?:^|-)keyframes$/i.test(container.name)) return;
119
+
120
+ const units = unitsFor(container.nodes);
121
+ const prefix = (unit) => unit.node?.type === "rule"
122
+ ? /^(\.[a-zA-Z_][\w]*)-[\w-]+$/.exec(unit.node.selector.trim())?.[1]
123
+ : undefined;
124
+
125
+ for (let start = 0; start < units.length; ) {
126
+ const name = prefix(units[start]);
127
+ let end = start + 1;
128
+ if (name) while (end < units.length && prefix(units[end]) === name) end++;
129
+ if (!name || end - start < 2) {
130
+ start = end;
131
+ continue;
132
+ }
133
+
134
+ const first = units[start].nodes[0];
135
+ const before = first.raws.before ?? "";
136
+ const baseIndent = /(?:^|\n)([\t ]*)$/.exec(before)?.[1] ?? "";
137
+ const wrapper = scss.parse(`${name} {}`).first;
138
+ wrapper.raws.before = before;
139
+ wrapper.raws.after = newline + baseIndent;
140
+ container.insertBefore(first, wrapper);
141
+
142
+ for (const unit of units.slice(start, end)) {
143
+ unit.node.selector = "&" + unit.node.selector.trim().slice(name.length);
144
+ for (const node of unit.nodes) {
145
+ const shift = (value) => value?.replace(/(\r?\n)([\t ]*)(?=\S)/g, `$1$2${indent}`);
146
+ const shiftBefore = (value) => value?.replace(/([\t ]*)$/, `$1${indent}`);
147
+ node.raws.before = shiftBefore(node.raws.before);
148
+ node.raws.after = /[\r\n]/.test(node.raws.after ?? "") ? shiftBefore(node.raws.after) : node.raws.after;
149
+ node.walk?.((child) => {
150
+ child.raws.before = shiftBefore(child.raws.before);
151
+ child.raws.after = /[\r\n]/.test(child.raws.after ?? "") ? shiftBefore(child.raws.after) : child.raws.after;
152
+ if (child.type === "rule") child.selector = shift(child.selector);
153
+ });
154
+ // Ensure standalone comments and inline input both start safely inside the wrapper.
155
+ if (!/[\r\n]/.test(node.raws.before ?? "")) {
156
+ node.raws.before = newline + baseIndent + indent;
157
+ }
158
+ wrapper.append(node);
159
+ }
160
+ }
161
+ wrapper.first.raws.before = newline + baseIndent + indent;
162
+ start = end;
163
+ }
164
+ }
165
+
166
+ export function sortScss(source, file = "input.scss") {
167
+ const root = scss.parse(source, { from: file });
168
+ sortContainer(root);
169
+ const indent = source.match(/\n([\t ]+)\S/)?.[1] ?? " ";
170
+ nestPrefixes(root, indent, source.includes("\r\n") ? "\r\n" : "\n");
171
+ sortContainer(root);
172
+ return root.toString(scss.stringify);
173
+ }
174
+
175
+ async function collect(paths) {
176
+ const files = new Set();
177
+ async function visit(path, explicit = false) {
178
+ const stat = await NodeFSP.lstat(path);
179
+ if (stat.isSymbolicLink()) {
180
+ if (explicit) throw new Error(`Refusing symlink: ${path}`);
181
+ return;
182
+ }
183
+ if (stat.isDirectory()) {
184
+ for (const entry of await NodeFSP.readdir(path, { withFileTypes: true })) {
185
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
186
+ await visit(NodePath.join(path, entry.name));
187
+ }
188
+ } else if (stat.isFile() && NodePath.extname(path).toLowerCase() === ".scss") {
189
+ files.add(path);
190
+ } else if (explicit) {
191
+ throw new Error(`Expected an SCSS file or directory: ${path}`);
192
+ }
193
+ }
194
+ for (const path of paths) await visit(NodePath.resolve(path), true);
195
+ if (!files.size) throw new Error("No SCSS files found.");
196
+ return [...files].sort(compare);
197
+ }
198
+
199
+ async function main(args) {
200
+ if (args.length === 1 && args[0] === "--help") {
201
+ process.stdout.write(help);
202
+ return;
203
+ }
204
+ const modes = args.filter((arg) => arg === "--check" || arg === "--write");
205
+ const paths = args.filter((arg) => !arg.startsWith("--"));
206
+ if (
207
+ modes.length !== 1 ||
208
+ paths.length === 0 ||
209
+ args.some((arg) => arg.startsWith("--") && !modes.includes(arg))
210
+ ) {
211
+ throw new Error(help);
212
+ }
213
+ // Parse the entire batch before writing so a syntax error cannot leave a partial batch.
214
+ const changes = [];
215
+ for (const file of await collect(paths)) {
216
+ const source = await NodeFSP.readFile(file, "utf8");
217
+ const output = sortScss(source, file);
218
+ if (output !== source) changes.push({ file, output });
219
+ }
220
+ for (const { file, output } of changes) {
221
+ if (modes[0] === "--write") await NodeFSP.writeFile(file, output, "utf8");
222
+ console.log(`${modes[0] === "--write" ? "Sorted" : "Unsorted"}: ${file}`);
223
+ }
224
+ if (modes[0] === "--check" && changes.length) process.exitCode = 1;
225
+ }
226
+
227
+ // Older Node versions do not define import.meta.main and silently skipped the CLI.
228
+ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
229
+ main(process.argv.slice(2)).catch((error) => {
230
+ console.error(`sort-scss: ${error.message}`);
231
+ process.exitCode = 2;
232
+ });
233
+ }
@@ -0,0 +1,217 @@
1
+ import * as NodeAssert from "node:assert/strict";
2
+ import * as NodeChildProcess from "node:child_process";
3
+ import * as NodeFSP from "node:fs/promises";
4
+ import * as NodeOS from "node:os";
5
+ import * as NodePath from "node:path";
6
+ import * as NodeURL from "node:url";
7
+ import * as NodeTest from "node:test";
8
+ import scss from "postcss-scss";
9
+ import { compileString } from "sass";
10
+ import { sortScss } from "./sort-scss.mjs";
11
+
12
+ const script = NodeURL.fileURLToPath(new URL("./sort-scss.mjs", import.meta.url));
13
+
14
+ NodeTest.test("nests prefixed classes with equivalent CSS, comments and stable output", () => {
15
+ const input = `// Name\n.spec-name { color: red; }\n.spec-table { td { color: blue; } &:hover { opacity: 1; } }\n.spec-value { color: green; }\n`;
16
+ const output = sortScss(input);
17
+ const root = scss.parse(output);
18
+ NodeAssert.equal(root.first.selector, ".spec");
19
+ NodeAssert.deepEqual(root.first.nodes.filter(n => n.type === "rule").map(n => n.selector),
20
+ ["&-name", "&-table", "&-value"]);
21
+ NodeAssert.match(output, /\/\/ Name\n/);
22
+ // Sorting already orders nested selectors; compare against that canonical CSS.
23
+ const canonical = input.replace('td { color: blue; } &:hover { opacity: 1; }', '&:hover { opacity: 1; } td { color: blue; }');
24
+ NodeAssert.equal(compileString(output, { style: "compressed" }).css, compileString(canonical, { style: "compressed" }).css);
25
+ NodeAssert.equal(sortScss(output), output);
26
+ });
27
+
28
+ NodeTest.test("prefix grouping respects scopes, complex selectors and Sass boundaries", () => {
29
+ const input = `.spec-a { color: red; }\n$gap: 1px;\n.spec-b { color: blue; }\n.spec-c:hover { color: red; }\n.spec-d { color: green; }\n@media (width > 1px) { .item-a { color: red; } .item-b { color: blue; } }`;
30
+ const output = sortScss(input);
31
+ NodeAssert.equal(compileString(output, { style: "compressed" }).css, compileString(input, { style: "compressed" }).css);
32
+ NodeAssert.match(output, /\.spec-a/);
33
+ NodeAssert.match(output, /\.spec-b/);
34
+ NodeAssert.match(output, /\.spec-c:hover/);
35
+ NodeAssert.equal(scss.parse(output).last.first.selector, ".item");
36
+ NodeAssert.equal(sortScss(output), output);
37
+ });
38
+
39
+ NodeTest.test("prefix grouping preserves CRLF and an existing base selector", () => {
40
+ const input = ".spec { color: black; }\r\n.spec-a { color: red; }\r\n.spec-b { color: blue; }\r\n";
41
+ const output = sortScss(input);
42
+ NodeAssert.equal(compileString(output, { style: "compressed" }).css, compileString(input, { style: "compressed" }).css);
43
+ NodeAssert.equal(output.replaceAll("\r\n", "").includes("\n"), false);
44
+ NodeAssert.equal(sortScss(output), output);
45
+ });
46
+
47
+ NodeTest.test("prefix grouping indents multiline selectors without whitespace-only lines", () => {
48
+ const output = sortScss(".spec-a {\n td,\n th { color: red; }\n}\n\n.spec-b {\n color: blue;\n}\n");
49
+ NodeAssert.match(output, / td,\n th/);
50
+ NodeAssert.doesNotMatch(output, /^[\t ]+$/m);
51
+ NodeAssert.equal(sortScss(output), output);
52
+ });
53
+
54
+ NodeTest.test("sorts Sass suffix selectors recursively and keeps media boundaries", () => {
55
+ const output = sortScss(`.header {
56
+ position: sticky;
57
+ &-inner { width: 100%; display: flex; }
58
+ &-nav {}
59
+ &-link { font-weight: 500; color: red; &-z {} &-a {} }
60
+ &-actions {}
61
+ &-icon {}
62
+ @media (max-width: 720px) { &-nav {} &-inner {} }
63
+ }`);
64
+ const nodes = scss.parse(output).first.nodes;
65
+ NodeAssert.deepEqual(nodes.map((node) => node.prop ?? node.selector ?? node.name),
66
+ ["position", "&-actions", "&-icon", "&-inner", "&-link", "&-nav", "media"]);
67
+ NodeAssert.deepEqual(nodes[4].nodes.map((node) => node.prop ?? node.selector),
68
+ ["color", "font-weight", "&-a", "&-z"]);
69
+ NodeAssert.deepEqual(nodes.at(-1).nodes.map((node) => node.selector),
70
+ ["&-inner", "&-nav"]);
71
+ NodeAssert.equal(sortScss(output), output);
72
+ });
73
+
74
+ NodeTest.test("inline comments cannot swallow a closing brace after sorting", () => {
75
+ const output = sortScss(".a {\n z-index: 1; // layer\n color: red; }");
76
+ const parsed = scss.parse(output);
77
+ NodeAssert.deepEqual(
78
+ parsed.first.nodes.filter((node) => node.type === "decl").map((node) => node.prop),
79
+ ["color", "z-index"],
80
+ );
81
+ NodeAssert.equal(sortScss(output), output);
82
+ });
83
+
84
+ NodeTest.test("retains BOM and CRLF in an already sorted file", () => {
85
+ const input = "\uFEFF.a {\r\n color: red;\r\n}\r\n";
86
+ NodeAssert.equal(sortScss(input), input);
87
+ });
88
+
89
+ NodeTest.test("sorts selectors and properties recursively, including inside media queries", () => {
90
+ const output = sortScss(`.z { z-index: 1; color: red; &.z { width: 1px; color: red } &.a {} }
91
+ .a { width: 1px; display: block }
92
+ @media (width > 1px) { .z {} .a { width: 2px; color: blue } }`);
93
+ const root = scss.parse(output);
94
+ NodeAssert.deepEqual(
95
+ root.nodes.map((node) => node.selector ?? node.name),
96
+ [".a", ".z", "media"],
97
+ );
98
+ NodeAssert.deepEqual(
99
+ root.first.nodes.map((node) => node.prop),
100
+ ["display", "width"],
101
+ );
102
+ NodeAssert.deepEqual(
103
+ root.nodes[1].nodes.map((node) => node.prop ?? node.selector),
104
+ ["color", "z-index", "&.a", "&.z"],
105
+ );
106
+ NodeAssert.deepEqual(
107
+ root.last.nodes.map((node) => node.selector),
108
+ [".a", ".z"],
109
+ );
110
+ NodeAssert.equal(sortScss(output), output);
111
+ });
112
+
113
+ NodeTest.test(
114
+ "keeps Sass boundaries and fallback order while sorting declarations on either side",
115
+ () => {
116
+ const output = sortScss(`.a {
117
+ z-index: 1; color: red;
118
+ $value: 2;
119
+ width: $value; display: -webkit-box; display: flex;
120
+ @include size;
121
+ padding: 0; color: blue;
122
+ }`);
123
+ const nodes = scss.parse(output).first.nodes;
124
+ NodeAssert.deepEqual(
125
+ nodes.map((node) => node.prop ?? `@${node.name}`),
126
+ ["color", "z-index", "$value", "display", "display", "width", "@include", "color", "padding"],
127
+ );
128
+ NodeAssert.deepEqual(
129
+ nodes.filter((node) => node.prop === "display").map((node) => node.value),
130
+ ["-webkit-box", "flex"],
131
+ );
132
+ },
133
+ );
134
+
135
+ NodeTest.test(
136
+ "moves leading and inline comments with their owners and preserves SCSS syntax",
137
+ () => {
138
+ const input = `// z selector
139
+ .z {
140
+ // width explanation
141
+ width: 1px; // width inline
142
+ color: red;
143
+ }
144
+ // a selector
145
+ .a { content: "braces {}; // text"; background: url("a;b.svg"); }
146
+ `;
147
+ const output = sortScss(input);
148
+ NodeAssert.ok(output.startsWith("// a selector\n.a"));
149
+ NodeAssert.match(
150
+ output,
151
+ /color: red;\n \/\/ width explanation\n width: 1px; \/\/ width inline/,
152
+ );
153
+ NodeAssert.ok(output.includes('"braces {}; // text"'));
154
+ NodeAssert.equal(sortScss(output), output);
155
+ },
156
+ );
157
+
158
+ NodeTest.test(
159
+ "preserves interpolated selectors, variable assignments and keyframe step order",
160
+ () => {
161
+ const input = `.z {} .#{$name} {} .a {}
162
+ @keyframes pulse { to { width: 1px; color: red } from { opacity: 0 } 50% { opacity: 1 } }`;
163
+ const root = scss.parse(sortScss(input));
164
+ NodeAssert.deepEqual(
165
+ root.nodes.slice(0, 3).map((node) => node.selector),
166
+ [".z", ".#{$name}", ".a"],
167
+ );
168
+ NodeAssert.deepEqual(
169
+ root.last.nodes.map((node) => node.selector),
170
+ ["to", "from", "50%"],
171
+ );
172
+ NodeAssert.deepEqual(
173
+ root.last.first.nodes.map((node) => node.prop),
174
+ ["color", "width"],
175
+ );
176
+ },
177
+ );
178
+
179
+ NodeTest.test("CLI checks without writing, writes recursively, then checks clean", async () => {
180
+ const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "sort-scss-"));
181
+ try {
182
+ const file = NodePath.join(dir, "sample.scss");
183
+ const input = ".z {}\n.a { width: 0; color: red }\n";
184
+ await NodeFSP.writeFile(file, input);
185
+ const run = (mode) =>
186
+ NodeChildProcess.spawnSync(process.execPath, [script, mode, dir], {
187
+ cwd: NodeOS.tmpdir(),
188
+ encoding: "utf8",
189
+ });
190
+ NodeAssert.equal(run("--check").status, 1);
191
+ NodeAssert.equal(await NodeFSP.readFile(file, "utf8"), input);
192
+ NodeAssert.equal(run("--write").status, 0);
193
+ NodeAssert.equal(await NodeFSP.readFile(file, "utf8"), ".a { color: red; width: 0 }\n.z {}\n");
194
+ NodeAssert.equal(run("--check").status, 0);
195
+ } finally {
196
+ await NodeFSP.rm(dir, { recursive: true, force: true });
197
+ }
198
+ });
199
+
200
+ NodeTest.test("CLI refuses invalid input before writing any file in the batch", async () => {
201
+ const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "sort-scss-"));
202
+ try {
203
+ const good = NodePath.join(dir, "a.scss");
204
+ const bad = NodePath.join(dir, "z.scss");
205
+ const input = ".z {} .a {}";
206
+ await NodeFSP.writeFile(good, input);
207
+ await NodeFSP.writeFile(bad, ".broken {");
208
+ const result = NodeChildProcess.spawnSync(process.execPath, [script, "--write", dir], {
209
+ encoding: "utf8",
210
+ });
211
+ NodeAssert.equal(result.status, 2);
212
+ NodeAssert.match(result.stderr, /Unclosed block/);
213
+ NodeAssert.equal(await NodeFSP.readFile(good, "utf8"), input);
214
+ } finally {
215
+ await NodeFSP.rm(dir, { recursive: true, force: true });
216
+ }
217
+ });
@@ -6,8 +6,8 @@
6
6
  max-width: calc(var(--max-width, 100%) - (var(--margin-horizontal) * 2));
7
7
  position: absolute;
8
8
  transition:
9
- opacity var(--transition-duration) ease-in-out,
10
- transform var(--transition-duration) ease-in-out;
9
+ opacity var(--transition-duration) var(--transition-timing-function),
10
+ transform var(--transition-duration) var(--transition-timing-function);
11
11
  z-index: 9;
12
12
 
13
13